From 584f7e9e7334d338c4be8dbba36712ef746c80b3 Mon Sep 17 00:00:00 2001 From: dela Date: Tue, 25 Aug 2026 14:43:17 +0800 Subject: [PATCH] Initial K3 snapshot: 0.5B KDA/MLA/MoE train path Standalone tree split from LLMRL/projects/kda. Includes Triton dt_bias backward fix, train_k3 --preset 0.5b, SFT, Docker runtime, and tests. --- .dockerignore | 15 + .gitignore | 23 + Dockerfile | 48 + README.md | 295 ++ compose.yaml | 29 + data/eval/.gitkeep | 1 + data/eval/en2zh.ref.txt | 20 + data/eval/en2zh.src.txt | 20 + data/eval/zh2en.ref.txt | 20 + data/eval/zh2en.src.txt | 20 + data/pretrain/.gitkeep | 1 + data/sft/.gitkeep | 2 + data/sft/toy.jsonl | 32 + inspect_tensors.py | 124 + kda/__init__.py | 14 + kda/_fla/LICENSE | 21 + kda/_fla/README.md | 9 + kda/_fla/__init__.py | 7 + kda/_fla/modules/__init__.py | 1 + kda/_fla/modules/backends/__init__.py | 3 + kda/_fla/modules/l2norm.py | 299 ++ kda/_fla/ops/__init__.py | 1 + kda/_fla/ops/backends/__init__.py | 34 + kda/_fla/ops/common/__init__.py | 1 + kda/_fla/ops/common/chunk_delta_h.py | 806 ++++ kda/_fla/ops/common/chunk_h.py | 432 ++ kda/_fla/ops/common/gate.py | 110 + kda/_fla/ops/cp/__init__.py | 13 + kda/_fla/ops/cp/chunk_delta_h.py | 11 + kda/_fla/ops/gla/__init__.py | 1 + kda/_fla/ops/gla/chunk.py | 1530 ++++++ kda/_fla/ops/kda/__init__.py | 7 + kda/_fla/ops/kda/chunk.py | 443 ++ kda/_fla/ops/kda/chunk_bwd.py | 651 +++ kda/_fla/ops/kda/chunk_fwd.py | 134 + kda/_fla/ops/kda/chunk_intra.py | 962 ++++ .../ops/kda/chunk_intra_token_parallel.py | 182 + kda/_fla/ops/kda/fused_recurrent.py | 491 ++ kda/_fla/ops/kda/gate.py | 514 ++ kda/_fla/ops/kda/wy_fast.py | 369 ++ kda/_fla/ops/utils/__init__.py | 14 + kda/_fla/ops/utils/cache.py | 449 ++ kda/_fla/ops/utils/constant.py | 10 + kda/_fla/ops/utils/cumsum.py | 468 ++ kda/_fla/ops/utils/index.py | 183 + kda/_fla/ops/utils/op.py | 101 + kda/_fla/ops/utils/softplus.py | 115 + kda/_fla/utils/__init__.py | 92 + kda/_fla/utils/_compat.py | 65 + kda/_fla/utils/_config.py | 17 + kda/_fla/utils/_decorators.py | 336 ++ kda/_fla/utils/_device.py | 245 + kda/_fla/utils/_testing.py | 41 + kda/layers/__init__.py | 23 + kda/layers/attn_res.py | 519 +++ kda/layers/block.py | 51 + kda/layers/kda_attn.py | 99 + kda/layers/latent_moe.py | 118 + kda/layers/mla.py | 97 + kda/layers/rmsnorm.py | 17 + kda/layers/swiglu.py | 20 + kda/models/__init__.py | 7 + kda/models/causal_lm.py | 123 + kda/models/config.py | 62 + kda/models/k3_config.py | 128 + kda/ops/__init__.py | 5 + kda/ops/api.py | 167 + kda/ops/recurrent/__init__.py | 5 + kda/ops/recurrent/fused.py | 73 + kda/ops/reference/__init__.py | 13 + kda/ops/reference/chunkwise.py | 155 + kda/ops/reference/gate.py | 47 + kda/ops/reference/recurrent.py | 298 ++ kda/ops/triton/__init__.py | 7 + kda/ops/triton/chunk.py | 5 + kda/ops/triton/chunk_bwd.py | 5 + kda/ops/triton/chunk_fwd.py | 37 + kda/ops/triton/gate.py | 36 + kda/ops/triton/wy_fast.py | 5 + kda/training/__init__.py | 5 + kda/training/data.py | 355 ++ kda/training/eval_mt.py | 139 + kda/training/prompts.py | 7 + kda/training/schedule.py | 47 + kda/training/success.py | 86 + kda/training/toy.py | 110 + kda/training/train_tokenizer.py | 51 + notes/ledger.yaml | 187 + notes/notes-macros.tex | 94 + notes/notes.pdf | Bin 0 -> 572085 bytes notes/notes.tex | 39 + notes/sections/sec-01.tex | 127 + notes/sections/sec-02.tex | 111 + notes/sections/sec-03.tex | 148 + notes/sections/sec-04.tex | 114 + notes/sections/sec-05.tex | 75 + notes/sections/sec-06.tex | 173 + notes/sections/sec-07.tex | 158 + notes/sections/sec-08.tex | 162 + notes/sections/sec-09.tex | 379 ++ notes/sections/sec-10.tex | 157 + notes/sections/sec-11.tex | 183 + notes/verify_gref_overflow.py | 106 + notes/verify_l2norm_stability.py | 83 + notes/verify_severity_sweep.py | 72 + pyproject.toml | 43 + pyrightconfig.json | 17 + scripts/container_help.py | 43 + scripts/export_flores.py | 61 + tests/__init__.py | 1 + tests/correctness/__init__.py | 1 + tests/correctness/test_api.py | 107 + tests/correctness/test_chunkwise.py | 47 + tests/correctness/test_decay_span.py | 99 + tests/correctness/test_gate.py | 52 + tests/correctness/test_recurrent.py | 34 + tests/inference/__init__.py | 1 + tests/inference/test_recurrent_decode.py | 30 + tests/integration/__init__.py | 1 + tests/integration/test_attn_res.py | 176 + tests/integration/test_causal_checkpoint.py | 64 + tests/integration/test_causal_lm.py | 78 + tests/integration/test_ckpt_compat.py | 56 + tests/integration/test_eval_files.py | 23 + tests/integration/test_eval_mt.py | 44 + tests/integration/test_k3_arch.py | 145 + tests/integration/test_pretrain_data.py | 53 + tests/integration/test_schedule.py | 21 + tests/integration/test_sft_data.py | 52 + tests/integration/test_tensorlens.py | 193 + tests/integration/test_torchlens.py | 83 + tests/integration/test_train_overfit.py | 32 + tests/kernels/__init__.py | 1 + tests/kernels/test_gate.py | 20 + tests/kernels/test_triton_bwd.py | 67 + tests/kernels/test_triton_fwd.py | 29 + train.py | 6 + train_k3.py | 461 ++ train_sft.py | 196 + uv.lock | 4133 +++++++++++++++++ 140 files changed, 21592 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 compose.yaml create mode 100644 data/eval/.gitkeep create mode 100644 data/eval/en2zh.ref.txt create mode 100644 data/eval/en2zh.src.txt create mode 100644 data/eval/zh2en.ref.txt create mode 100644 data/eval/zh2en.src.txt create mode 100644 data/pretrain/.gitkeep create mode 100644 data/sft/.gitkeep create mode 100644 data/sft/toy.jsonl create mode 100644 inspect_tensors.py create mode 100644 kda/__init__.py create mode 100644 kda/_fla/LICENSE create mode 100644 kda/_fla/README.md create mode 100644 kda/_fla/__init__.py create mode 100644 kda/_fla/modules/__init__.py create mode 100644 kda/_fla/modules/backends/__init__.py create mode 100644 kda/_fla/modules/l2norm.py create mode 100644 kda/_fla/ops/__init__.py create mode 100644 kda/_fla/ops/backends/__init__.py create mode 100644 kda/_fla/ops/common/__init__.py create mode 100644 kda/_fla/ops/common/chunk_delta_h.py create mode 100644 kda/_fla/ops/common/chunk_h.py create mode 100644 kda/_fla/ops/common/gate.py create mode 100644 kda/_fla/ops/cp/__init__.py create mode 100644 kda/_fla/ops/cp/chunk_delta_h.py create mode 100644 kda/_fla/ops/gla/__init__.py create mode 100644 kda/_fla/ops/gla/chunk.py create mode 100644 kda/_fla/ops/kda/__init__.py create mode 100644 kda/_fla/ops/kda/chunk.py create mode 100644 kda/_fla/ops/kda/chunk_bwd.py create mode 100644 kda/_fla/ops/kda/chunk_fwd.py create mode 100644 kda/_fla/ops/kda/chunk_intra.py create mode 100644 kda/_fla/ops/kda/chunk_intra_token_parallel.py create mode 100644 kda/_fla/ops/kda/fused_recurrent.py create mode 100644 kda/_fla/ops/kda/gate.py create mode 100644 kda/_fla/ops/kda/wy_fast.py create mode 100644 kda/_fla/ops/utils/__init__.py create mode 100644 kda/_fla/ops/utils/cache.py create mode 100644 kda/_fla/ops/utils/constant.py create mode 100644 kda/_fla/ops/utils/cumsum.py create mode 100644 kda/_fla/ops/utils/index.py create mode 100644 kda/_fla/ops/utils/op.py create mode 100644 kda/_fla/ops/utils/softplus.py create mode 100644 kda/_fla/utils/__init__.py create mode 100644 kda/_fla/utils/_compat.py create mode 100644 kda/_fla/utils/_config.py create mode 100644 kda/_fla/utils/_decorators.py create mode 100644 kda/_fla/utils/_device.py create mode 100644 kda/_fla/utils/_testing.py create mode 100644 kda/layers/__init__.py create mode 100644 kda/layers/attn_res.py create mode 100644 kda/layers/block.py create mode 100644 kda/layers/kda_attn.py create mode 100644 kda/layers/latent_moe.py create mode 100644 kda/layers/mla.py create mode 100644 kda/layers/rmsnorm.py create mode 100644 kda/layers/swiglu.py create mode 100644 kda/models/__init__.py create mode 100644 kda/models/causal_lm.py create mode 100644 kda/models/config.py create mode 100644 kda/models/k3_config.py create mode 100644 kda/ops/__init__.py create mode 100644 kda/ops/api.py create mode 100644 kda/ops/recurrent/__init__.py create mode 100644 kda/ops/recurrent/fused.py create mode 100644 kda/ops/reference/__init__.py create mode 100644 kda/ops/reference/chunkwise.py create mode 100644 kda/ops/reference/gate.py create mode 100644 kda/ops/reference/recurrent.py create mode 100644 kda/ops/triton/__init__.py create mode 100644 kda/ops/triton/chunk.py create mode 100644 kda/ops/triton/chunk_bwd.py create mode 100644 kda/ops/triton/chunk_fwd.py create mode 100644 kda/ops/triton/gate.py create mode 100644 kda/ops/triton/wy_fast.py create mode 100644 kda/training/__init__.py create mode 100644 kda/training/data.py create mode 100644 kda/training/eval_mt.py create mode 100644 kda/training/prompts.py create mode 100644 kda/training/schedule.py create mode 100644 kda/training/success.py create mode 100644 kda/training/toy.py create mode 100644 kda/training/train_tokenizer.py create mode 100644 notes/ledger.yaml create mode 100644 notes/notes-macros.tex create mode 100644 notes/notes.pdf create mode 100644 notes/notes.tex create mode 100644 notes/sections/sec-01.tex create mode 100644 notes/sections/sec-02.tex create mode 100644 notes/sections/sec-03.tex create mode 100644 notes/sections/sec-04.tex create mode 100644 notes/sections/sec-05.tex create mode 100644 notes/sections/sec-06.tex create mode 100644 notes/sections/sec-07.tex create mode 100644 notes/sections/sec-08.tex create mode 100644 notes/sections/sec-09.tex create mode 100644 notes/sections/sec-10.tex create mode 100644 notes/sections/sec-11.tex create mode 100644 notes/verify_gref_overflow.py create mode 100644 notes/verify_l2norm_stability.py create mode 100644 notes/verify_severity_sweep.py create mode 100644 pyproject.toml create mode 100644 pyrightconfig.json create mode 100644 scripts/container_help.py create mode 100644 scripts/export_flores.py create mode 100644 tests/__init__.py create mode 100644 tests/correctness/__init__.py create mode 100644 tests/correctness/test_api.py create mode 100644 tests/correctness/test_chunkwise.py create mode 100644 tests/correctness/test_decay_span.py create mode 100644 tests/correctness/test_gate.py create mode 100644 tests/correctness/test_recurrent.py create mode 100644 tests/inference/__init__.py create mode 100644 tests/inference/test_recurrent_decode.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/integration/test_attn_res.py create mode 100644 tests/integration/test_causal_checkpoint.py create mode 100644 tests/integration/test_causal_lm.py create mode 100644 tests/integration/test_ckpt_compat.py create mode 100644 tests/integration/test_eval_files.py create mode 100644 tests/integration/test_eval_mt.py create mode 100644 tests/integration/test_k3_arch.py create mode 100644 tests/integration/test_pretrain_data.py create mode 100644 tests/integration/test_schedule.py create mode 100644 tests/integration/test_sft_data.py create mode 100644 tests/integration/test_tensorlens.py create mode 100644 tests/integration/test_torchlens.py create mode 100644 tests/integration/test_train_overfit.py create mode 100644 tests/kernels/__init__.py create mode 100644 tests/kernels/test_gate.py create mode 100644 tests/kernels/test_triton_bwd.py create mode 100644 tests/kernels/test_triton_fwd.py create mode 100644 train.py create mode 100644 train_k3.py create mode 100644 train_sft.py create mode 100644 uv.lock diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9c188af --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +# Build context is projects/kda/. Keep the image a runtime, not a data dump. +.venv/ +**/__pycache__/ +**/*.pyc +**/*.pyo +**/.pytest_cache/ +**/.ruff_cache/ +ckpts/ +*.pt +notes/ +.git/ +.gitignore +uv.lock +pyrightconfig.json +inspect_tensors.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5442a06 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.venv/ +swanlog/ +.pytest_cache/ +__pycache__/ +*.py[cod] +ckpts/ +data/spm_4k.* +data/pretrain/* +!data/pretrain/.gitkeep +data/sft/* +!data/sft/.gitkeep +!data/sft/toy.jsonl +data/eval/flores* + +# LaTeX build output +*.aux +*.log +*.out +*.toc +*.fls +*.fdb_latexmk +*.xdv +wiki jsonl diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..af01994 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +# syntax=docker/dockerfile:1 +# +# Single GPU runtime for train + SwanLab client + eval. +# Build: docker build -t kda: . +# +# Does not bake data, checkpoints, or API keys. Mount them at run time. +# Host: NVIDIA driver >= 570, nvidia-container-toolkit. See README. + +FROM pytorch/pytorch:2.9.0-cuda12.8-cudnn9-devel + +ENV DEBIAN_FRONTEND=noninteractive \ + PIP_NO_CACHE_DIR=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONPATH=/workspace/kda \ + HF_HOME=/cache/huggingface \ + HUGGINGFACE_HUB_CACHE=/cache/huggingface \ + HF_HUB_DISABLE_TELEMETRY=1 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace/kda + +# Layer cache: install deps from pyproject before the rest of the tree. +COPY pyproject.toml ./ +COPY kda ./kda +# Base image already has torch/cuda/triton; do not let pip re-resolve torch. +RUN pip install --no-cache-dir --no-deps -e . && \ + pip install --no-cache-dir \ + "einops>=0.7.0" \ + "packaging>=23.0" \ + "sentencepiece>=0.2.0" \ + "datasets>=3.0.0" \ + "transformers>=4.51.0" \ + "swanlab>=0.6.0" \ + "sacrebleu>=2.4.0" \ + "langdetect>=1.0.9" \ + "pytest>=7.0" + +COPY . /workspace/kda +RUN pip install --no-cache-dir --no-deps -e . && \ + mkdir -p /cache/huggingface /workspace/kda/ckpts /workspace/kda/swanlog \ + /data/pretrain /data/eval /data/sft + +# Require an explicit entry (train / eval / pytest / swanlab ping). +CMD ["python", "scripts/container_help.py"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..b431165 --- /dev/null +++ b/README.md @@ -0,0 +1,295 @@ +# KDA 训练 → 推理 手写实现 + +从 naive recurrent 到 fused Triton kernel + 训练 + 推理,逐层手写实现。 +对拍时复用上游 `flash-linear-attention` 的 `naive_recurrent_kda` / `naive_chunk_kda` 作为参考。 + +## 目录结构 + +``` +kda/ +├── pyproject.toml +├── README.md +├── train.py # KDA-only toy overfit +├── train_k3.py # K3-like 双语 wiki 预训练 +├── train_sft.py # zh↔en 指令 SFT(模板与 eval_mt 相同) +├── Dockerfile # 训练 / SwanLab 客户端 / 评估 同一 GPU 镜像 +├── compose.yaml +├── scripts/container_help.py # 镜像默认入口(拒绝无命令启动) +├── kda/ # 可安装包 `from kda import ...` +│ ├── _fla/ # FLA NVIDIA-Triton KDA 子集(不 import 上游 fla 包) +│ ├── ops/ +│ │ ├── api.py # 显式选择 reference / triton / fla +│ │ ├── reference/ # L1/L2 + gate 的 PyTorch 正确性实现 +│ │ ├── triton/ # vendored FLA chunk/gate/wy wrappers +│ │ └── recurrent/ # L6 fused recurrent decode + state cache +│ ├── layers/ # kda_attn / mla / swiglu / latent_moe / block +│ │ # attn_res.py = 深度残差 mixer(config.attnres) +│ ├── models/ # CausalLM + KDAConfig / K3Config +│ └── training/ # toy、双语 wiki、SFT、eval_mt / success +└── tests/ + ├── correctness/ # recurrent、chunkwise、gate reference + ├── kernels/ # Triton / FLA 对拍 + ├── inference/ # recurrent decode + └── integration/ # Causal LM toy overfit、K3 架构 +``` + +## 实现路线表 + +| # | 层级 | 文件 | 核心公式 / 关键操作 | 需手写的梯度 | 验证方法 | atol | +|---|------|------|---------------------|--------------|----------|------| +| L1 | Naive recurrent fwd+bwd | `ops/reference/recurrent.py` | `S_t = exp(g_t)⊙S + (β_t⊙k_t)⊗(v_t − k_t·S)`
`o_t = (q_t·scale)·S` | `dq,dk,dv,dg,dβ` | `torch.autograd.gradcheck` (float64) | 1e-4 | +| L2 | Naive chunked fwd+bwd | `ops/reference/chunkwise.py` | ① g→cumsum in chunk
② 构造下三角系统
③ triangular solve
④ chunk 间状态传递 | PyTorch autograd | recurrent 对拍 + gradcheck | 1e-4 | +| L3 | Triton fused fwd | `ops/triton/chunk_fwd.py` | chunk 内与 chunk 间并行计算 | — | Triton out vs L2 | 1e-4 | +| L4 | Triton fused bwd | `ops/triton/chunk_bwd.py` | dAv、状态反传、dqkg、intra 修正 | 端到端梯度 | gradcheck + L2 bwd 对拍 | 1e-3 | +| L5 | Gate reference / fusion | `ops/reference/gate.py`, `ops/triton/gate.py` | standard gate 与 safe gate 使用官方语义 | `dA_log,d_dt_bias,dg` | 公式对拍 + gradcheck | 1e-4 | +| L6 | recurrent decode | `ops/recurrent/fused.py` | `KDAState [B,HV,K,V]` | — | 逐 token vs L1 | 1e-5 | +| L7 | Model + training | `layers/`, `models/`, `training/` | KDA Causal LM + shifted CE + toy overfit | AdamW autograd | loss < 0.1 | — | + +## 关键约束 / 风险点 + +| 项 | 约定 | +|---|---| +| **GVA (G=HV/H)** | `repeat_interleave(G, dim=2)` 把 q/k 从 H 扩到 HV;bwd 的 `dq/dk` 在 HV 维算完后 **必须 `view(B,T,H,G,K).sum(dim=3)` 回到 H 维** | +| **scale** | `1/√K`,在 forward 入口乘入 q(不要散布到 kernel 内部) | +| **gradcheck** | 强制 `dtype=torch.float64`;`eps=1e-6, atol=1e-4`;inputs 显式 `requires_grad_(True)` | +| **chunk_size** | 默认 64;必须 `T % BT == 0`;H100 可上 128 | +| **exp 下溢** | g 应用前 clamp;gate 路径 `lower_bound=−5.0`;显式 exp 路径用 `−softplus(−g)` 防大量 0 | +| **L4 dxs 落位** | `dq, dk` 在 HV 维算完后归约到 H;`dv/dg/dβ` 在 HV 维直接输出 | +| **naive 测试 shape** | T=16, B=2, H=2, HV=4, K=V=8 | +| **fused_whole 测试 shape** | T=128/512, B=2/4, H=4, HV=8, K=V=64 | + +## 建议执行顺序 + +1. **L1 → L2**:先纯 PyTorch 把 fwd+bwd 写对,gradcheck 双保险 +2. **L3 → L4**:写 Triton kernel 时,L2 当 reference,逐项 diff +3. **L5**:和 L3/L4 **解耦测试**(用 PyTorch 等价 naive gate 当参考) +4. **L6**:复用 L1 单步公式,独立测试 +5. **L7**:把 L3+L4+L5 黏到 layer 里,配 SwiGLU + RMSNorm + CE 训练 + +## 测试状态 + +``` +| Layer | Test | Status | max_diff | atol | Notes | +|-------|----------------------|----------|--------------|--------|--------------| +| L1/L2 | reference correctness | PASSED | | | recurrent + chunkwise | +| L3/L4 | vendored FLA Triton chunk | PASSED | | | CUDA, chunk_size 32/64, q/k L2-norm | +| L5 reference | gate formula + gradcheck | PASSED | | | standard + safe gate | +| L5 Triton | fused gate | PASSED | | | vendored FLA kda_gate_* | +| L6 | recurrent decode | PASSED | | | vendored FLA fused_recurrent_kda | +| L7 | train overfit | PASSED | | | reference backend | +| L7 | TorchLens trace/extract | PASSED | | | 计算图展开 + 激活提取 | +| L7 | TensorLens viewer API | PASSED | | | trace/normalize/Flask 端点 | +| K3 | hybrid + MLA + MoE | PASSED | | | `test_k3_arch.py` 6 项 | +| AttnRes | 深度残差 mixer | PASSED | | | `test_attn_res.py` 14 项 | +``` + +## 参考资源 + +- 上游 naive 实现 (`对拍用`): `(optional sibling) flash-linear-attention/fla/ops/kda/naive.py` +- 上游 fused kernel (`读源码用`): `(optional sibling) flash-linear-attention/fla/ops/kda/chunk_{fwd,bwd,intra}.py` +- KDA 笔记: `(optional sibling) flash-linear-attention/KDA_学习笔记.md` +- KDA paper: https://arxiv.org/abs/2510.26692 +- AttnRes paper: https://arxiv.org/abs/2603.15031 +- 本项目完整笔记(LaTeX): `notes/notes.tex` → `notes/notes.pdf` + +## 当前进度 + +- [x] L1 — recurrent PyTorch reference + gradcheck +- [x] L2 — chunked PyTorch reference + recurrent 对拍 + gradcheck +- [x] L7 — 可训练 CausalLM、toy overfit、checkpoint round-trip +- [x] 统一 `ops.api.chunk_kda` 接口:`reference` / `triton` / `fla` 显式选择 +- [x] K3-like — Gated MLA + LatentMoE + Hybrid 3:1,`train_k3.py` 双语 wiki 预训练 +- [x] L3/L4 — vendored FLA Triton chunk fwd/bwd(`kda/_fla`,不静默 import 上游 `fla`) +- [x] L5 — vendored FLA fused gate +- [x] L6 — vendored FLA fused recurrent decode +- [x] AttnRes — 深度残差 mixer 接入 `CausalLM`(`config.attnres = off | full | block`) +- [x] 翻译训练环 — `--max-tokens` / `--resume` / held-out / cosine、`train_sft.py`、冻结 `data/eval` + +当前可直接运行小模型训练: + +```bash +PYTHONPATH=. python train.py +``` + +默认 `reference` 始终使用本仓库 PyTorch 实现。`backend="triton"` 走 `kda/_fla` +里搬运的 FLA NVIDIA Triton kernel(CUDA,`chunk_size` 为 32 或 64)。`backend="fla"` +仍要求完整安装上游 `flash-linear-attention`,只用于显式对拍。 + +## Kimi K3 小规模复现(KDA + Gated MLA + Stable LatentMoE) + +按 `learning/kimi-k3-notes` 的架构笔记,复现 K3 的**核心三件套**(Hybrid Attention +三选一 + MoE),规模受 6GB 显存限制缩小约 30 倍: + +| K3 组件 | 真实 K3 | 本复现 (toy) | 实现 | +|---|---|---|---| +| KDA | safe gate, NoPE, H=HV=96 | H=HV=8, K=V=16 | `kda/ops/`(已验证)| +| Hybrid | 每 4 层 1×Gated MLA,末层 MLA | 同 pattern (L=4) | `kda/layers/mla.py` | +| Gated MLA | kv_lora 512, NoPE, 矩阵吸收 | kv_lora 32, 吸收版 | 同上 | +| Stable LatentMoE | ℓ=d/2=3584, 896/16, shared 2 | ℓ=d/2=128, 16/2, shared 2 | `kda/layers/latent_moe.py` | +| SiTU-GLU | β1=4, β2=25 | 同 | 同上 | +| AttnRes | Block, S≈L/8 个 DecoderBlock | 同(`--attnres block`,默认 off)| `kda/layers/attn_res.py` | + +验证(`tests/integration/test_k3_arch.py`,6 项全过): + +- MLA 矩阵吸收版 **fwd+bwd 均与解压版逐位一致** +- LatentMoE Top-k 路由权重正确、shared 全宽贡献 +- Hybrid pattern(每 4 层 1 MLA + 末层强制) +- K3 模型因果性 + 小模型单 batch 收敛 + +### 复现训练 + +目标是 **~0.5B zh↔en 指令翻译模型**(`K3Config.preset("0.5b")` = 482M,tied Qwen3 词表)。本机 RTX 3060 6GB 只跑 8M 全流程孪生;0.5B 预训练需要 **32–40GB Ampere bf16**。 + +成功标准是冻结集上的 `translation_success()`,**不是** wiki train loss。wiki 预训练没见过 `Translate to English:\n...`,预训练阶段 `eval_mt` 的 success_rate 预期 ≈0。 + +```bash +# 8M 孪生(自训 8k SentencePiece;默认 zh+en wiki,缓存 data/pretrain/) +uv run python kda/training/train_tokenizer.py --out data/spm_4k --vocab-size 8192 --limit 20000 +uv run python train_k3.py --preset toy --limit 8000 --steps 800 --batch 4 --seq-len 256 +uv run python train_sft.py --ckpt ckpts/k3_wiki.pt --data data/sft/toy.jsonl \ + --src data/eval/zh2en.src.txt --ref data/eval/zh2en.ref.txt --target-lang en + +# AttnRes 对照(默认 off,块大小不给则自动 ≈ L/8 个 DecoderBlock) +uv run python train_k3.py --preset toy --attnres block --attnres-block-size 2 + +# ~0.5B 冒烟(默认 --steps 2000 ≈ 8.2M token,不是语言模型) +uv run python train_k3.py --preset 0.5b --attnres block + +# 32–40GB Ampere:1B-token 双语预训练(可 --resume ckpts/k3_0.5b_last.pt 续到 2–5B) +uv run python train_k3.py --preset 0.5b --attnres block \ + --max-tokens 1000000000 --warmup 2000 +``` + +`0.5b` 预设:`d=768`,`L=24`(6×3 KDA + 1 MLA),`H=12`,`head=64`,`chunk=64`,LatentMoE `ℓ=384` / 16 routed / Top-2 / shared 2,tied Qwen3 embedding,activation checkpoint 默认开。训练默认 seq 2048、micro-batch 2、grad-acc 8、lr 3e-4、warmup **64 optimizer steps**。checkpoint:`ckpts/k3_0.5b.pt`,另写 `_last` / `_best`(best 按 held-out CE)。 + +Token 会计:`step` 仍是 micro-batch;有效 token = `batch × seq_len × micro_steps`。默认 0.5b 冒烟是 **8.2M token ≈ 0.017 tok/param**。翻译前置 LM 的最低有意义预算是 **1B token**(`--max-tokens`),不是 2000 step。 + +训练指标参考(800 步 @ 8M 参数,RTX 3060,bf16 + grad clip 1.0): + +- loss:9.0(ln 8192 均匀起步)→ best 3.81,无 nan +- 生成:乱码 → 高频中文片段 → 短句雏形 +- fp16 AMP 会在 KDA `exp(g)/cumsum` 上发散,必须 bf16 或 fp32 + +入口:`train.py` 用 `KDAConfig`;`train_k3.py` / `train_sft.py` 用 checkpoint 里的 `K3Config`(或 KDA)。SFT 指令模板与 `eval_mt` 相同,见 `kda/training/prompts.py`。冻结评测句在 `data/eval/`(不要拿去训练)。FLORES 导出:`uv run python scripts/export_flores.py --out data/eval`。 + +## Docker 训练镜像 + +一个 GPU 运行时覆盖 **训练、SwanLab 客户端监控、冻结集评估**。构建上下文 = 本目录。 +默认 `CMD` 只打印用法并退出码 2,必须显式传入口,避免误跑 toy overfit。 + +语料、checkpoint、`SWANLAB_API_KEY`、HF 词表缓存 **不打进镜像**,运行时挂载。 + +### 版本 / CUDA 矩阵(与本地开发环境一致,已实测) + +| 组件 | 版本 | 来源 | +|------|------|------| +| 基础镜像 | `pytorch/pytorch:2.9.0-cuda12.8-cudnn9-devel` | Docker Hub(~9.6GB)| +| Python | 3.12 | 基础镜像自带 | +| torch | 2.9.0+cu128 | 基础镜像自带 | +| CUDA | 12.8 | 基础镜像 runtime | +| cuDNN | 9.x | 基础镜像自带 | +| triton | 3.5.0 | torch 附带 | +| numpy | 2.3.x | torch 附带 | +| 训练依赖 | einops / sentencepiece / datasets / transformers | `pyproject.toml` | +| 监控 / 评估 | swanlab / sacrebleu / langdetect | extra `train` | + +宿主要求: + +- **NVIDIA driver >= 570.00**(CUDA 12.8 最低要求)。本机 driver 610.57 ✅ +- **nvidia-container-toolkit**(容器内用 GPU 的前置,见下) +- GPU 架构:cu128 完整支持 Ampere 及以后(RTX 3060 / sm_86 ✅) +- 显存注意:RTX 3060 只有 6GB,toy 训练无压力;大 batch / `chunk_size=128` + 可能 OOM,按需调小 + +### 宿主机一次性配置:nvidia-container-toolkit + +当前机器**未安装**,`docker run --gpus all` 会报 +`failed to discover GPU vendor from CDI`。安装并配置: + +```bash +# Arch Linux +sudo pacman -S nvidia-container-toolkit +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker + +# Ubuntu / Debian(若换机器) +curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ + | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg +curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \ + | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \ + | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list +sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit +sudo nvidia-ctk runtime configure --runtime=docker +sudo systemctl restart docker +``` + +验证: + +```bash +docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi +# 应能看到 RTX 3060 与 driver 610.57 +``` + +### 构建 + +```bash +docker build -t kda:latest . +``` + +> `--network=host` 必要:本机 docker daemon 配置了代理 `http://127.0.0.1:7890`, +> 构建容器内的 `127.0.0.1` 指向容器自身而非宿主,不走 host 网络时 pip 下载会失败。 + +### 运行 + +无命令时只打印用法(退出码 2)。正式跑必须写入口。 + +```bash +# 用法 +docker run --rm kda:latest + +# 监控连通(训练机出网到 api.swanlab.cn) +docker run --rm --gpus all -e SWANLAB_API_KEY kda:latest swanlab ping + +# toy 训练 + 云端监控;ckpt 落在宿主 +mkdir -p ckpts +docker run --rm --gpus all \ + -e SWANLAB_API_KEY \ + -v "$PWD/ckpts:/workspace/kda/ckpts" \ + -v "$PWD/data:/workspace/kda/data" \ + kda:latest python train_k3.py --preset toy --attnres off + +# 0.5b 冒烟(词表走 HF 缓存卷)。真正的 1B-token 预训练加 --max-tokens 1000000000 +docker run --rm --gpus all \ + -e SWANLAB_API_KEY \ + -v "$PWD/ckpts:/workspace/kda/ckpts" \ + -v "$PWD/data:/workspace/kda/data" \ + -v hf-cache:/cache/huggingface \ + kda:latest python train_k3.py --preset 0.5b --attnres block + +# 评估(src/ref 一行一句,挂冻结集) +docker run --rm --gpus all \ + -v "$PWD/ckpts:/workspace/kda/ckpts" \ + -v "$PWD/data/eval:/data/eval:ro" \ + kda:latest python -m kda.training.eval_mt \ + --ckpt /workspace/kda/ckpts/k3_wiki.pt \ + --src /data/eval/zh2en.src.txt --ref /data/eval/zh2en.ref.txt \ + --target-lang en + +# 镜像内测试 +docker run --rm --gpus all kda:latest python -m pytest -q \ + tests/correctness tests/integration/test_causal_lm.py \ + tests/integration/test_k3_arch.py tests/integration/test_attn_res.py \ + tests/integration/test_eval_mt.py + +# 交互 +docker run --rm -it --gpus all --entrypoint bash kda:latest +``` + +也可用 `docker compose run --rm train python train_k3.py --preset toy`(`compose.yaml`)。 + +### 镜像内验证 + +```bash +docker run --rm kda:latest python -c "import torch, einops, swanlab, sacrebleu; print(torch.__version__, torch.version.cuda)" +# 预期: 2.9.0+cu128 12.8 +``` diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..f88d50a --- /dev/null +++ b/compose.yaml @@ -0,0 +1,29 @@ +# GPU train / eval against the image in Dockerfile. +# docker compose run --rm train python train_k3.py --preset toy +# docker compose run --rm train swanlab ping +# docker compose run --rm train python -m kda.training.eval_mt --ckpt ... +# +# Secrets and corpora stay on the host. + +services: + train: + build: . + image: kda:latest + gpus: all + ipc: host + shm_size: "2gb" + working_dir: /workspace/kda + environment: + SWANLAB_API_KEY: ${SWANLAB_API_KEY:-} + HF_HOME: /cache/huggingface + HUGGINGFACE_HUB_CACHE: /cache/huggingface + volumes: + - ./ckpts:/workspace/kda/ckpts + - ./data:/workspace/kda/data + - ${PRETRAIN_DATA:-./data/pretrain}:/data/pretrain:ro + - ${EVAL_DATA:-./data/eval}:/data/eval:ro + - ${SFT_DATA:-./data/sft}:/data/sft:ro + - hf-cache:/cache/huggingface + +volumes: + hf-cache: diff --git a/data/eval/.gitkeep b/data/eval/.gitkeep new file mode 100644 index 0000000..83d2af2 --- /dev/null +++ b/data/eval/.gitkeep @@ -0,0 +1 @@ +# Mount as /data/eval: one sentence per line, e.g. zh2en.src.txt + zh2en.ref.txt diff --git a/data/eval/en2zh.ref.txt b/data/eval/en2zh.ref.txt new file mode 100644 index 0000000..3403a96 --- /dev/null +++ b/data/eval/en2zh.ref.txt @@ -0,0 +1,20 @@ +今天天气很好。 +请把窗户打开。 +猫坐在垫子上。 +这本书值得一读。 +他昨天去了北京。 +我们需要更多的训练数据。 +太阳从东边升起。 +她正在学习线性代数。 +不要把评测集拿去训练。 +河对面有一座旧桥。 +科学是对自然的系统探索。 +他们在公园里散步。 +这台电脑的内存是十六吉字节。 +翻译时不要照抄原文。 +春天的风很温和。 +我把钥匙放在桌子上了。 +火车中午到达。 +水在一百摄氏度沸腾。 +小模型仍然可以学会狭窄的任务。 +冻结测试文件保持只读。 diff --git a/data/eval/en2zh.src.txt b/data/eval/en2zh.src.txt new file mode 100644 index 0000000..f0c03be --- /dev/null +++ b/data/eval/en2zh.src.txt @@ -0,0 +1,20 @@ +The weather is very nice today. +Please open the window. +The cat is sitting on the mat. +This book is worth reading. +He went to Beijing yesterday. +We need more training data. +The sun rises in the east. +She is studying linear algebra. +Do not train on the evaluation set. +There is an old bridge across the river. +Science is the systematic exploration of nature. +They are walking in the park. +This computer has sixteen gigabytes of memory. +Do not copy the source text when translating. +The spring wind is gentle. +I left the keys on the table. +The train arrives at noon. +Water boils at one hundred degrees Celsius. +A small model can still learn a narrow task. +Keep the frozen test files read-only. diff --git a/data/eval/zh2en.ref.txt b/data/eval/zh2en.ref.txt new file mode 100644 index 0000000..93b3860 --- /dev/null +++ b/data/eval/zh2en.ref.txt @@ -0,0 +1,20 @@ +The weather is very nice today. +The development of artificial intelligence has changed the world. +Please open the window. +The cat is sitting on the mat. +This book is worth reading. +The meeting will start at three in the afternoon. +He went to Beijing yesterday. +We need more training data. +The sun rises in the east. +This question still has no answer. +She is studying linear algebra. +Do not train on the evaluation set. +There is an old bridge across the river. +Please wait a moment, I will be right back. +Science is the systematic exploration of nature. +They are walking in the park. +This computer has sixteen gigabytes of memory. +Do not copy the source text when translating. +The spring wind is gentle. +I left the keys on the table. diff --git a/data/eval/zh2en.src.txt b/data/eval/zh2en.src.txt new file mode 100644 index 0000000..75e32ed --- /dev/null +++ b/data/eval/zh2en.src.txt @@ -0,0 +1,20 @@ +今天天气很好。 +人工智能的发展改变了世界。 +请把窗户打开。 +猫坐在垫子上。 +这本书值得一读。 +会议将在下午三点开始。 +他昨天去了北京。 +我们需要更多的训练数据。 +太阳从东边升起。 +这个问题还没有答案。 +她正在学习线性代数。 +不要把评测集拿去训练。 +河对面有一座旧桥。 +请稍等,我马上回来。 +科学是对自然的系统探索。 +他们在公园里散步。 +这台电脑的内存是十六吉字节。 +翻译时不要照抄原文。 +春天的风很温和。 +我把钥匙放在桌子上了。 diff --git a/data/pretrain/.gitkeep b/data/pretrain/.gitkeep new file mode 100644 index 0000000..fb73d36 --- /dev/null +++ b/data/pretrain/.gitkeep @@ -0,0 +1 @@ +# Mount bilingual pretrain shards here (not baked into the image). diff --git a/data/sft/.gitkeep b/data/sft/.gitkeep new file mode 100644 index 0000000..3965022 --- /dev/null +++ b/data/sft/.gitkeep @@ -0,0 +1,2 @@ +SFT bitext (jsonl or tsv) is mounted here. toy.jsonl is a tiny in-repo twin set. +OPUS / WMT dumps stay out of git. diff --git a/data/sft/toy.jsonl b/data/sft/toy.jsonl new file mode 100644 index 0000000..4905ded --- /dev/null +++ b/data/sft/toy.jsonl @@ -0,0 +1,32 @@ +{"src": "你好。", "tgt": "Hello.", "target_lang": "en"} +{"src": "Hello.", "tgt": "你好。", "target_lang": "zh"} +{"src": "谢谢。", "tgt": "Thank you.", "target_lang": "en"} +{"src": "Thank you.", "tgt": "谢谢。", "target_lang": "zh"} +{"src": "我爱北京。", "tgt": "I love Beijing.", "target_lang": "en"} +{"src": "I love Beijing.", "tgt": "我爱北京。", "target_lang": "zh"} +{"src": "现在几点?", "tgt": "What time is it now?", "target_lang": "en"} +{"src": "What time is it now?", "tgt": "现在几点?", "target_lang": "zh"} +{"src": "水是透明的。", "tgt": "Water is transparent.", "target_lang": "en"} +{"src": "Water is transparent.", "tgt": "水是透明的。", "target_lang": "zh"} +{"src": "他是一名教师。", "tgt": "He is a teacher.", "target_lang": "en"} +{"src": "He is a teacher.", "tgt": "他是一名教师。", "target_lang": "zh"} +{"src": "明天会下雨。", "tgt": "It will rain tomorrow.", "target_lang": "en"} +{"src": "It will rain tomorrow.", "tgt": "明天会下雨。", "target_lang": "zh"} +{"src": "请坐。", "tgt": "Please sit down.", "target_lang": "en"} +{"src": "Please sit down.", "tgt": "请坐。", "target_lang": "zh"} +{"src": "这是一只狗。", "tgt": "This is a dog.", "target_lang": "en"} +{"src": "This is a dog.", "tgt": "这是一只狗。", "target_lang": "zh"} +{"src": "大门在左边。", "tgt": "The gate is on the left.", "target_lang": "en"} +{"src": "The gate is on the left.", "tgt": "大门在左边。", "target_lang": "zh"} +{"src": "我们走吧。", "tgt": "Let's go.", "target_lang": "en"} +{"src": "Let's go.", "tgt": "我们走吧。", "target_lang": "zh"} +{"src": "夜空中有星星。", "tgt": "There are stars in the night sky.", "target_lang": "en"} +{"src": "There are stars in the night sky.", "tgt": "夜空中有星星。", "target_lang": "zh"} +{"src": "面包放在厨房里。", "tgt": "The bread is in the kitchen.", "target_lang": "en"} +{"src": "The bread is in the kitchen.", "tgt": "面包放在厨房里。", "target_lang": "zh"} +{"src": "孩子在睡觉。", "tgt": "The child is sleeping.", "target_lang": "en"} +{"src": "The child is sleeping.", "tgt": "孩子在睡觉。", "target_lang": "zh"} +{"src": "这座山很高。", "tgt": "This mountain is very high.", "target_lang": "en"} +{"src": "This mountain is very high.", "tgt": "这座山很高。", "target_lang": "zh"} +{"src": "请关上门。", "tgt": "Please close the door.", "target_lang": "en"} +{"src": "Please close the door.", "tgt": "请关上门。", "target_lang": "zh"} diff --git a/inspect_tensors.py b/inspect_tensors.py new file mode 100644 index 0000000..96098fa --- /dev/null +++ b/inspect_tensors.py @@ -0,0 +1,124 @@ +"""Inspect KDA activations: print stats, TorchLens extract, or TensorLens web UI. + + uv run python inspect_tensors.py # shape / min / max / nan + uv run python inspect_tensors.py --lens torch # named activations + uv run python inspect_tensors.py --lens web # http://127.0.0.1:8000 +""" +from __future__ import annotations + +import argparse + +import torch + +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig + +MODULES = ( + "embedding", + "blocks.0.attn.q_proj", + "blocks.0.attn.k_proj", + "blocks.0.attn.v_proj", + "blocks.0.attn", + "blocks.0.ffn", + "blocks.0", + "norm", +) + + +def _model() -> CausalLM: + torch.manual_seed(51) + return CausalLM( + KDAConfig( + hidden_size=16, + num_hidden_layers=1, + num_heads=2, + num_value_heads=2, + head_dim=4, + chunk_size=4, + vocab_size=32, + intermediate_size=32, + kda_backend="reference", + ) + ).eval() + + +def _tokens() -> torch.Tensor: + return torch.tensor([[1, 2, 3, 4]]) + + +def _named_modules(model: torch.nn.Module) -> dict[str, torch.nn.Module]: + return dict(model.named_modules()) + + +def capture_activations(model: CausalLM, x: torch.Tensor) -> dict[str, torch.Tensor]: + captured: dict[str, torch.Tensor] = {} + hooks = [] + modules = _named_modules(model) + for name in MODULES: + module = modules[name] + + def _hook(_module, _inp, out, key=name): + captured[key] = out.detach() + + hooks.append(module.register_forward_hook(_hook)) + with torch.no_grad(): + captured["logits"] = model(x).detach() + for hook in hooks: + hook.remove() + return captured + + +def print_stats(tensors: dict[str, torch.Tensor]) -> None: + print(f"{'name':28} {'shape':18} {'dtype':10} {'min':>10} {'max':>10} {'mean':>10} nan/inf") + for name, tensor in tensors.items(): + finite = torch.isfinite(tensor) + n_bad = int((~finite).sum()) + stats = tensor.float() if tensor.is_floating_point() else tensor + print( + f"{name:28} {str(tuple(tensor.shape)):18} {str(tensor.dtype):10} " + f"{stats.min().item():10.4f} {stats.max().item():10.4f} " + f"{stats.float().mean().item():10.4f} {n_bad}" + ) + + +def inspect_torchlens(model: CausalLM, x: torch.Tensor) -> None: + import torchlens as tl + + names = [*MODULES, "output"] + with torch.no_grad(): + acts = tl.extract(model, x, names) + print_stats(acts) + + +def inspect_web(model: CausalLM, x: torch.Tensor, host: str, port: int) -> None: + from tensorlens.tensorlens import trace + from tensorlens.web.server import app + + acts = capture_activations(model, x) + for name, tensor in acts.items(): + trace(name, tensor.cpu().float().numpy(), normalization="minmax") + trace("lm_head.weight", model.lm_head.weight.detach().cpu().float().numpy(), normalization="minmax") + print(f"TensorLens: http://{host}:{port} (Ctrl-C to stop)") + app.run(host=host, port=port, debug=False, use_reloader=False) + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--lens", choices=("print", "torch", "web"), default="print") + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=8000) + args = parser.parse_args() + + model = _model() + x = _tokens() + if args.lens == "torch": + inspect_torchlens(model, x) + return + if args.lens == "web": + inspect_web(model, x, args.host, args.port) + return + print_stats(capture_activations(model, x)) + + +if __name__ == "__main__": + main() diff --git a/kda/__init__.py b/kda/__init__.py new file mode 100644 index 0000000..2de8c74 --- /dev/null +++ b/kda/__init__.py @@ -0,0 +1,14 @@ +"""KDA operators, composable layers, and CausalLM.""" + +from .models.causal_lm import CausalLM +from .models.config import KDAConfig +from .models.k3_config import K3Config +from .ops import chunk_kda + +__all__ = [ + "CausalLM", + "KDAConfig", + "K3Config", + "chunk_kda", +] +__version__ = "0.0.1" diff --git a/kda/_fla/LICENSE b/kda/_fla/LICENSE new file mode 100644 index 0000000..7ced501 --- /dev/null +++ b/kda/_fla/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023-2026 Songlin Yang, Yu Zhang, Zhiyuan Li + +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/kda/_fla/README.md b/kda/_fla/README.md new file mode 100644 index 0000000..955438c --- /dev/null +++ b/kda/_fla/README.md @@ -0,0 +1,9 @@ +# Vendored FLA KDA kernels + +Subset of [flash-linear-attention](https://github.com/fla-org/flash-linear-attention) +used by `kda.ops` `backend="triton"`. + +- License: MIT (see `LICENSE`) +- Upstream version tag in `__init__.py` +- Import path is `kda._fla.*`, not `fla.*` +- Not included: context parallel, Ascend, TileLang, `flash_kda`, non-KDA ops diff --git a/kda/_fla/__init__.py b/kda/_fla/__init__.py new file mode 100644 index 0000000..0d5715c --- /dev/null +++ b/kda/_fla/__init__.py @@ -0,0 +1,7 @@ +"""Vendored NVIDIA-Triton KDA path from flash-linear-attention (MIT). + +This package is imported as ``kda._fla``, never as the upstream ``fla`` +distribution. Context-parallel, Ascend, and TileLang backends are omitted. +""" + +__version__ = "0.5.2" diff --git a/kda/_fla/modules/__init__.py b/kda/_fla/modules/__init__.py new file mode 100644 index 0000000..be8b25b --- /dev/null +++ b/kda/_fla/modules/__init__.py @@ -0,0 +1 @@ +# Vendored FLA modules used by KDA (l2norm). diff --git a/kda/_fla/modules/backends/__init__.py b/kda/_fla/modules/backends/__init__.py new file mode 100644 index 0000000..24050e4 --- /dev/null +++ b/kda/_fla/modules/backends/__init__.py @@ -0,0 +1,3 @@ +from kda._fla.ops.backends import BackendRegistry, BaseBackend, dispatch + +__all__ = ["BackendRegistry", "BaseBackend", "dispatch"] diff --git a/kda/_fla/modules/l2norm.py b/kda/_fla/modules/l2norm.py new file mode 100644 index 0000000..06dc2b6 --- /dev/null +++ b/kda/_fla/modules/l2norm.py @@ -0,0 +1,299 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import torch.nn as nn +import triton +import triton.language as tl + +from kda._fla.modules.backends import dispatch +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.utils import IS_AMD, autotune_cache_kwargs, input_guard + +BT_LIST = [8, 16, 32, 64, 128] +NUM_WARPS_AUTOTUNE = [1, 2, 4, 8, 16] if IS_AMD else [1, 2, 4, 8, 16, 32] + + +@triton.autotune( + configs=[triton.Config({}, num_warps=num_warps) for num_warps in NUM_WARPS_AUTOTUNE], + key=["D"], + **autotune_cache_kwargs, +) +@triton.jit +def l2norm_fwd_kernel1( + x, + y, + rstd, + eps, + D, + BD: tl.constexpr, +): + i_t = tl.program_id(0).to(tl.int64) + x += i_t * D + y += i_t * D + # Compute mean and variance + cols = tl.arange(0, BD) + mask = cols < D + + b_x = tl.load(x + cols, mask=mask, other=0.0).to(tl.float32) + b_rstd = 1 / tl.sqrt(tl.sum(b_x * b_x) + eps) + b_y = b_x * b_rstd + tl.store(y + cols, b_y, mask=mask) + tl.store(rstd + i_t, b_rstd) + + +@triton.autotune( + configs=[triton.Config({}, num_warps=num_warps) for num_warps in NUM_WARPS_AUTOTUNE], + key=["D"], + **autotune_cache_kwargs, +) +@triton.jit +def l2norm_bwd_kernel1( + y, + rstd, + dy, + dx, + eps, + D, + BD: tl.constexpr, +): + i_t = tl.program_id(0).to(tl.int64) + y += i_t * D + dx += i_t * D + dy += i_t * D + + cols = tl.arange(0, BD) + mask = cols < D + b_y = tl.load(y + cols, mask=mask, other=0.0).to(tl.float32) + b_rstd = tl.load(rstd + i_t).to(tl.float32) + b_dy = tl.load(dy + cols, mask=mask, other=0.0).to(tl.float32) + b_dx = b_dy * b_rstd - tl.sum(b_dy * b_y) * b_y * b_rstd + tl.store(dx + cols, b_dx, mask=mask) + + +@fla_cache_autotune( + configs=[triton.Config({"BT": BT}, num_warps=num_warps) for num_warps in [1, 2, 4, 8, 16] for BT in BT_LIST], + key=["D", "NB"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def l2norm_fwd_kernel( + x, + y, + rstd, + eps, + T, + D: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + BT: tl.constexpr, +): + i_t = tl.program_id(0).to(tl.int64) + o_t = i_t * BT + tl.arange(0, BT) + o_d = tl.arange(0, BD) + m_t = o_t < T + m_x = m_t[:, None] & (o_d[None, :] < D) + p_x = x + o_t[:, None] * D + o_d[None, :] + p_y = y + o_t[:, None] * D + o_d[None, :] + p_rstd = rstd + o_t + + b_x = tl.load(p_x, mask=m_x, other=0.0).to(tl.float32) + b_rstd = 1 / tl.sqrt(tl.sum(b_x * b_x, 1) + eps) + b_y = b_x * b_rstd[:, None] + + tl.store(p_y, b_y.to(p_y.dtype.element_ty), mask=m_x) + tl.store(p_rstd, b_rstd.to(p_rstd.dtype.element_ty), mask=m_t) + + +@fla_cache_autotune( + configs=[triton.Config({"BT": BT}, num_warps=num_warps) for num_warps in [1, 2, 4, 8, 16] for BT in BT_LIST], + key=["D", "NB"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def l2norm_bwd_kernel( + y, + rstd, + dy, + dx, + eps, + T, + D: tl.constexpr, + BD: tl.constexpr, + NB: tl.constexpr, + BT: tl.constexpr, +): + i_t = tl.program_id(0).to(tl.int64) + o_t = i_t * BT + tl.arange(0, BT) + o_d = tl.arange(0, BD) + m_t = o_t < T + m_x = m_t[:, None] & (o_d[None, :] < D) + p_y = y + o_t[:, None] * D + o_d[None, :] + p_rstd = rstd + o_t + p_dy = dy + o_t[:, None] * D + o_d[None, :] + p_dx = dx + o_t[:, None] * D + o_d[None, :] + + b_y = tl.load(p_y, mask=m_x, other=0.0).to(tl.float32) + b_rstd = tl.load(p_rstd, mask=m_t, other=0.0).to(tl.float32) + b_dy = tl.load(p_dy, mask=m_x, other=0.0).to(tl.float32) + b_dx = b_dy * b_rstd[:, None] - tl.sum(b_dy * b_y, 1)[:, None] * b_y * b_rstd[:, None] + tl.store(p_dx, b_dx.to(p_dx.dtype.element_ty), mask=m_x) + + +@dispatch('modules') +def l2norm_fwd( + x: torch.Tensor, + eps: float = 1e-6, + output_dtype: torch.dtype | None = None, +): + x_shape_og = x.shape + x = x.view(-1, x.shape[-1]) + # allocate output + if output_dtype is None: + y = torch.empty_like(x) + else: + y = torch.empty_like(x, dtype=output_dtype) + assert y.stride(-1) == 1 + T, D = x.shape[0], x.shape[-1] + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // x.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer doesn't support feature dim >= 64KB.") + + rstd = torch.empty((T,), dtype=torch.float32, device=x.device) + if D <= 512: + # NOTE(tylerr): Avoid excessive recompilation and autotuning by tolerating a larger range + # of T before recompiling the kernel. + # NB = triton.cdiv(T, 2048) + NB = triton.cdiv(T, 2048 * 32) + + def grid(meta): + return (triton.cdiv(T, meta["BT"]),) + + l2norm_fwd_kernel[grid]( + x=x, + y=y, + rstd=rstd, + eps=eps, + T=T, + D=D, + BD=BD, + NB=NB, + ) + else: + l2norm_fwd_kernel1[(T,)]( + x=x, + y=y, + rstd=rstd, + eps=eps, + D=D, + BD=BD, + ) + return y.view(x_shape_og), rstd.view(x_shape_og[:-1]) + + +@dispatch('modules') +def l2norm_bwd( + y: torch.Tensor, + rstd: torch.Tensor, + dy: torch.Tensor, + eps: float = 1e-6, +): + y_shape_og = y.shape + y = y.view(-1, dy.shape[-1]) + dy = dy.view(-1, dy.shape[-1]) + assert dy.shape == y.shape + # allocate output + dx = torch.empty_like(y) + T, D = y.shape[0], y.shape[-1] + # Less than 64KB per feature: enqueue fused kernel + MAX_FUSED_SIZE = 65536 // y.element_size() + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D)) + if D > BD: + raise RuntimeError("This layer norm doesn't support feature dim >= 64KB.") + + if D <= 512: + # NOTE(tylerr): Avoid excessive recompilation and autotuning by tolerating a larger range + # of T before recompiling the kernel. + # NB = triton.cdiv(T, 2048) + NB = triton.cdiv(T, 2048 * 32) + + def grid(meta): + return (triton.cdiv(T, meta["BT"]),) + + l2norm_bwd_kernel[grid]( + y=y, + rstd=rstd, + dy=dy, + dx=dx, + eps=eps, + T=T, + D=D, + BD=BD, + NB=NB, + ) + else: + l2norm_bwd_kernel1[(T,)]( + y=y, + rstd=rstd, + dy=dy, + dx=dx, + eps=eps, + D=D, + BD=BD, + ) + + return dx.view(y_shape_og) + + +class L2NormFunction(torch.autograd.Function): + @staticmethod + @input_guard + def forward( + ctx, + x, + eps=1e-6, + output_dtype=None, + ): + y, rstd = l2norm_fwd(x, eps, output_dtype) + ctx.eps = eps + ctx.x_dtype = x.dtype + ctx.save_for_backward(y, rstd) + return y + + @staticmethod + @input_guard + def backward(ctx, dy): + y, rstd = ctx.saved_tensors + dx = l2norm_bwd(y, rstd, dy, ctx.eps) + return dx, None, None + + +def l2norm( + x: torch.Tensor, + eps: float = 1e-6, + output_dtype: torch.dtype | None = None, +) -> torch.Tensor: + return L2NormFunction.apply(x, eps, output_dtype) + + +l2_norm = l2norm + + +class L2Norm(nn.Module): + def __init__( + self, + eps: float = 1e-6, + output_dtype: torch.dtype | None = None, + ): + super().__init__() + self.eps = eps + self.output_dtype = output_dtype + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return l2norm(x, self.eps, self.output_dtype) diff --git a/kda/_fla/ops/__init__.py b/kda/_fla/ops/__init__.py new file mode 100644 index 0000000..1869051 --- /dev/null +++ b/kda/_fla/ops/__init__.py @@ -0,0 +1 @@ +# Vendored FLA ops subset. diff --git a/kda/_fla/ops/backends/__init__.py b/kda/_fla/ops/backends/__init__.py new file mode 100644 index 0000000..386459f --- /dev/null +++ b/kda/_fla/ops/backends/__init__.py @@ -0,0 +1,34 @@ +"""Identity dispatch: keep the NVIDIA Triton implementation in this tree.""" + +from __future__ import annotations + +from collections.abc import Callable +from typing import TypeVar + +F = TypeVar("F", bound=Callable) + + +def dispatch(operation: str): + def decorator(func: F) -> F: + return func + + return decorator + + +class BaseBackend: + backend_type = "triton" + + def is_available(self) -> bool: + return True + + def is_enabled(self) -> bool: + return True + + +class BackendRegistry: + @classmethod + def ensure_initialized(cls, operation: str) -> None: + return None + + +__all__ = ["BackendRegistry", "BaseBackend", "dispatch"] diff --git a/kda/_fla/ops/common/__init__.py b/kda/_fla/ops/common/__init__.py new file mode 100644 index 0000000..9dd4499 --- /dev/null +++ b/kda/_fla/ops/common/__init__.py @@ -0,0 +1 @@ +# Vendored FLA common kernels used by KDA. diff --git a/kda/_fla/ops/common/chunk_delta_h.py b/kda/_fla/ops/common/chunk_delta_h.py new file mode 100644 index 0000000..6412161 --- /dev/null +++ b/kda/_fla/ops/common/chunk_delta_h.py @@ -0,0 +1,806 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.op import exp2 +from kda._fla.utils import ( + IS_INTEL, + IS_NVIDIA_BLACKWELL, + IS_NVIDIA_HOPPER, + autotune_cache_kwargs, + check_shared_mem, +) + +NUM_WARPS = [2, 4] if IS_NVIDIA_HOPPER else [2, 4, 8, 16] + +# TODO: Triton mainline fixes a Blackwell tl.dot recurrence race. +# Keep this kernel on num_warps=2 for Blackwell until Triton 3.8 is released +# and we re-validate the wider config space. +# Intel needs more warps than NVIDIA here: 8 warps is ~1.5x faster than the best +# config reachable under the [2, 4] cap. +if IS_NVIDIA_BLACKWELL: + GATED_DELTA_RULE_FWD_H_NUM_WARPS = [2] +elif IS_INTEL: + GATED_DELTA_RULE_FWD_H_NUM_WARPS = [2, 4, 8, 16] +else: + GATED_DELTA_RULE_FWD_H_NUM_WARPS = [2, 4] + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_GK': lambda args: args['gk'] is not None, + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'SAVE_NEW_VALUE': lambda args: args['v_new'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for num_warps in GATED_DELTA_RULE_FWD_H_NUM_WARPS + for num_stages in ([2, 3, 4] if check_shared_mem('ampere') else [2, 1]) + for BV in ([32, 64] if check_shared_mem('ada') else [32]) + ], + key=['H', 'HV', 'K', 'V', 'BT', 'STATE_V_FIRST'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gated_delta_rule_fwd_kernel_h_blockdim64( + k, + v, + w, + v_new, + g, + gk, + h, + h0, + ht, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + SAVE_NEW_VALUE: tl.constexpr, + STATE_V_FIRST: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + pid = tl.program_id(0) + NV = tl.cdiv(V, BV) + i_v, i_nh = pid % NV, (pid // NV).to(tl.int64) + i_n, i_h = i_nh // HV, i_nh % HV + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int64) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + if STATE_V_FIRST: + b_h1 = tl.zeros([BV, 64], dtype=tl.float32) + if K > 64: + b_h2 = tl.zeros([BV, 64], dtype=tl.float32) + if K > 128: + b_h3 = tl.zeros([BV, 64], dtype=tl.float32) + if K > 192: + b_h4 = tl.zeros([BV, 64], dtype=tl.float32) + else: + b_h1 = tl.zeros([64, BV], dtype=tl.float32) + if K > 64: + b_h2 = tl.zeros([64, BV], dtype=tl.float32) + if K > 128: + b_h3 = tl.zeros([64, BV], dtype=tl.float32) + if K > 192: + b_h4 = tl.zeros([64, BV], dtype=tl.float32) + + # calculate offset + h += (boh * HV + i_h).to(tl.int64) * K*V + v += (bos * HV + i_h).to(tl.int64) * V + k += (bos * H + i_h // (HV // H)).to(tl.int64) * K + w += (bos * HV + i_h).to(tl.int64) * K + if SAVE_NEW_VALUE: + v_new += (bos * HV + i_h).to(tl.int64) * V + + if USE_INITIAL_STATE: + h0 = h0 + i_nh * K*V + if STORE_FINAL_STATE: + ht = ht + i_nh * K*V + + # load initial state + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + o_k1 = tl.arange(0, 64) + m_k1 = o_k1 < K + o_k2 = 64 + o_k1 + m_k2 = o_k2 < K + o_k3 = 128 + o_k1 + m_k3 = o_k3 < K + o_k4 = 192 + o_k1 + m_k4 = o_k4 < K + if USE_INITIAL_STATE: + if STATE_V_FIRST: + p_h0_1 = h0 + o_v[:, None] * K + o_k1[None, :] + m_h0_1 = m_v[:, None] & m_k1[None, :] + else: + p_h0_1 = h0 + o_k1[:, None] * V + o_v[None, :] + m_h0_1 = m_k1[:, None] & m_v[None, :] + b_h1 += tl.load(p_h0_1, mask=m_h0_1, other=0.0).to(tl.float32) + if K > 64: + if STATE_V_FIRST: + p_h0_2 = h0 + o_v[:, None] * K + o_k2[None, :] + m_h0_2 = m_v[:, None] & m_k2[None, :] + else: + p_h0_2 = h0 + o_k2[:, None] * V + o_v[None, :] + m_h0_2 = m_k2[:, None] & m_v[None, :] + b_h2 += tl.load(p_h0_2, mask=m_h0_2, other=0.0).to(tl.float32) + if K > 128: + if STATE_V_FIRST: + p_h0_3 = h0 + o_v[:, None] * K + o_k3[None, :] + m_h0_3 = m_v[:, None] & m_k3[None, :] + else: + p_h0_3 = h0 + o_k3[:, None] * V + o_v[None, :] + m_h0_3 = m_k3[:, None] & m_v[None, :] + b_h3 += tl.load(p_h0_3, mask=m_h0_3, other=0.0).to(tl.float32) + if K > 192: + if STATE_V_FIRST: + p_h0_4 = h0 + o_v[:, None] * K + o_k4[None, :] + m_h0_4 = m_v[:, None] & m_k4[None, :] + else: + p_h0_4 = h0 + o_k4[:, None] * V + o_v[None, :] + m_h0_4 = m_k4[:, None] & m_v[None, :] + b_h4 += tl.load(p_h0_4, mask=m_h0_4, other=0.0).to(tl.float32) + + # main recurrence + for i_t in range(NT): + i_t_int64 = i_t.to(tl.int64) + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + if STATE_V_FIRST: + p_h1 = h + i_t_int64 * HV*K*V + o_v[:, None] * K + o_k1[None, :] + m_h1 = m_v[:, None] & m_k1[None, :] + else: + p_h1 = h + i_t_int64 * HV*K*V + o_k1[:, None] * V + o_v[None, :] + m_h1 = m_k1[:, None] & m_v[None, :] + tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), mask=m_h1) + if K > 64: + if STATE_V_FIRST: + p_h2 = h + i_t_int64 * HV*K*V + o_v[:, None] * K + o_k2[None, :] + m_h2 = m_v[:, None] & m_k2[None, :] + else: + p_h2 = h + i_t_int64 * HV*K*V + o_k2[:, None] * V + o_v[None, :] + m_h2 = m_k2[:, None] & m_v[None, :] + tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), mask=m_h2) + if K > 128: + if STATE_V_FIRST: + p_h3 = h + i_t_int64 * HV*K*V + o_v[:, None] * K + o_k3[None, :] + m_h3 = m_v[:, None] & m_k3[None, :] + else: + p_h3 = h + i_t_int64 * HV*K*V + o_k3[:, None] * V + o_v[None, :] + m_h3 = m_k3[:, None] & m_v[None, :] + tl.store(p_h3, b_h3.to(p_h3.dtype.element_ty), mask=m_h3) + if K > 192: + if STATE_V_FIRST: + p_h4 = h + i_t_int64 * HV*K*V + o_v[:, None] * K + o_k4[None, :] + m_h4 = m_v[:, None] & m_k4[None, :] + else: + p_h4 = h + i_t_int64 * HV*K*V + o_k4[:, None] * V + o_v[None, :] + m_h4 = m_k4[:, None] & m_v[None, :] + tl.store(p_h4, b_h4.to(p_h4.dtype.element_ty), mask=m_h4) + + p_w = w + o_t[:, None] * (HV*K) + o_k1[None, :] + b_w = tl.load(p_w, mask=m_t[:, None] & m_k1[None, :], other=0.0) + if STATE_V_FIRST: + b_v = tl.dot(b_w, tl.trans(b_h1).to(b_w.dtype)) + else: + b_v = tl.dot(b_w, b_h1.to(b_w.dtype)) + if K > 64: + p_w = w + o_t[:, None] * (HV*K) + o_k2[None, :] + b_w = tl.load(p_w, mask=m_t[:, None] & m_k2[None, :], other=0.0) + if STATE_V_FIRST: + b_v += tl.dot(b_w, tl.trans(b_h2).to(b_w.dtype)) + else: + b_v += tl.dot(b_w, b_h2.to(b_w.dtype)) + if K > 128: + p_w = w + o_t[:, None] * (HV*K) + o_k3[None, :] + b_w = tl.load(p_w, mask=m_t[:, None] & m_k3[None, :], other=0.0) + if STATE_V_FIRST: + b_v += tl.dot(b_w, tl.trans(b_h3).to(b_w.dtype)) + else: + b_v += tl.dot(b_w, b_h3.to(b_w.dtype)) + if K > 192: + p_w = w + o_t[:, None] * (HV*K) + o_k4[None, :] + b_w = tl.load(p_w, mask=m_t[:, None] & m_k4[None, :], other=0.0) + if STATE_V_FIRST: + b_v += tl.dot(b_w, tl.trans(b_h4).to(b_w.dtype)) + else: + b_v += tl.dot(b_w, b_h4.to(b_w.dtype)) + p_v = v + o_t[:, None] * (HV*V) + o_v[None, :] + b_v = tl.load(p_v, mask=m_t[:, None] & m_v[None, :], other=0.0) - b_v + + if SAVE_NEW_VALUE: + p_v = v_new + o_t[:, None] * (HV*V) + o_v[None, :] + tl.store(p_v, b_v.to(p_v.dtype.element_ty), mask=m_t[:, None] & m_v[None, :]) + + last_idx = min((i_t + 1) * BT, T) - 1 + if USE_G: + b_g_last = tl.load(g + (bos * HV + last_idx * HV + i_h).to(tl.int64)).to(tl.float32) + p_g = g + (bos * HV + i_h).to(tl.int64) + o_t * HV + b_g = tl.load(p_g, mask=m_t, other=0.0).to(tl.float32) + b_v = b_v * tl.where(m_t, exp2(b_g_last - b_g), 0)[:, None] + b_g_last = exp2(b_g_last) + b_h1 *= b_g_last + if K > 64: + b_h2 *= b_g_last + if K > 128: + b_h3 *= b_g_last + if K > 192: + b_h4 *= b_g_last + + if USE_GK: + o_k1 = tl.arange(0, 64) + b_gk_last1 = tl.load(gk + (bos + last_idx) * HV*K + i_h * K + o_k1, mask=(o_k1 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_h1 *= exp2(b_gk_last1)[None, :] + else: + b_h1 *= exp2(b_gk_last1)[:, None] + if K > 64: + o_k2 = 64 + o_k1 + b_gk_last2 = tl.load(gk + (bos + last_idx) * HV*K + i_h * K + o_k2, mask=(o_k2 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_h2 *= exp2(b_gk_last2)[None, :] + else: + b_h2 *= exp2(b_gk_last2)[:, None] + if K > 128: + o_k3 = 128 + o_k1 + b_gk_last3 = tl.load(gk + (bos + last_idx) * HV*K + i_h * K + o_k3, mask=(o_k3 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_h3 *= exp2(b_gk_last3)[None, :] + else: + b_h3 *= exp2(b_gk_last3)[:, None] + if K > 192: + o_k4 = 192 + o_k1 + b_gk_last4 = tl.load(gk + (bos + last_idx) * HV*K + i_h * K + o_k4, mask=(o_k4 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_h4 *= exp2(b_gk_last4)[None, :] + else: + b_h4 *= exp2(b_gk_last4)[:, None] + b_v = b_v.to(k.dtype.element_ty) + + p_k = k + o_k1[:, None] + o_t[None, :] * (H*K) + b_k = tl.load(p_k, mask=m_k1[:, None] & m_t[None, :], other=0.0) + if STATE_V_FIRST: + b_h1 += tl.trans(tl.dot(b_k, b_v)) + else: + b_h1 += tl.dot(b_k, b_v) + if K > 64: + p_k = k + o_k2[:, None] + o_t[None, :] * (H*K) + b_k = tl.load(p_k, mask=m_k2[:, None] & m_t[None, :], other=0.0) + if STATE_V_FIRST: + b_h2 += tl.trans(tl.dot(b_k, b_v)) + else: + b_h2 += tl.dot(b_k, b_v) + if K > 128: + p_k = k + o_k3[:, None] + o_t[None, :] * (H*K) + b_k = tl.load(p_k, mask=m_k3[:, None] & m_t[None, :], other=0.0) + if STATE_V_FIRST: + b_h3 += tl.trans(tl.dot(b_k, b_v)) + else: + b_h3 += tl.dot(b_k, b_v) + if K > 192: + p_k = k + o_k4[:, None] + o_t[None, :] * (H*K) + b_k = tl.load(p_k, mask=m_k4[:, None] & m_t[None, :], other=0.0) + if STATE_V_FIRST: + b_h4 += tl.trans(tl.dot(b_k, b_v)) + else: + b_h4 += tl.dot(b_k, b_v) + + if STORE_FINAL_STATE: + if STATE_V_FIRST: + p_ht = ht + o_v[:, None] * K + o_k1[None, :] + m_ht = m_v[:, None] & m_k1[None, :] + else: + p_ht = ht + o_k1[:, None] * V + o_v[None, :] + m_ht = m_k1[:, None] & m_v[None, :] + tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), mask=m_ht) + if K > 64: + if STATE_V_FIRST: + p_ht = ht + o_v[:, None] * K + o_k2[None, :] + m_ht = m_v[:, None] & m_k2[None, :] + else: + p_ht = ht + o_k2[:, None] * V + o_v[None, :] + m_ht = m_k2[:, None] & m_v[None, :] + tl.store(p_ht, b_h2.to(p_ht.dtype.element_ty), mask=m_ht) + if K > 128: + if STATE_V_FIRST: + p_ht = ht + o_v[:, None] * K + o_k3[None, :] + m_ht = m_v[:, None] & m_k3[None, :] + else: + p_ht = ht + o_k3[:, None] * V + o_v[None, :] + m_ht = m_k3[:, None] & m_v[None, :] + tl.store(p_ht, b_h3.to(p_ht.dtype.element_ty), mask=m_ht) + if K > 192: + if STATE_V_FIRST: + p_ht = ht + o_v[:, None] * K + o_k4[None, :] + m_ht = m_v[:, None] & m_k4[None, :] + else: + p_ht = ht + o_k4[:, None] * V + o_v[None, :] + m_ht = m_k4[:, None] & m_v[None, :] + tl.store(p_ht, b_h4.to(p_ht.dtype.element_ty), mask=m_ht) + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'USE_GK': lambda args: args['gk'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in ([2, 3, 4] if check_shared_mem('ampere') else [1]) + for BV in ([32, 64] if check_shared_mem('ada') else [32]) + ], + key=['H', 'HV', 'K', 'V', 'BT', 'BV', 'USE_G', 'STATE_V_FIRST'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gated_delta_rule_bwd_kernel_dhu_blockdim64( + q, + k, + w, + g, + gk, + dht, + dh0, + do, + dh, + dv, + dv2, + cu_seqlens, + chunk_offsets, + scale, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_GK: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + STATE_V_FIRST: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + pid = tl.program_id(0) + NV = tl.cdiv(V, BV) + i_v, i_nh = pid % NV, (pid // NV).to(tl.int64) + i_n, i_h = i_nh // HV, i_nh % HV + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + NT = tl.cdiv(T, BT) + boh = tl.load(chunk_offsets + i_n).to(tl.int64) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + if STATE_V_FIRST: + b_dh1 = tl.zeros([BV, 64], dtype=tl.float32) + if K > 64: + b_dh2 = tl.zeros([BV, 64], dtype=tl.float32) + if K > 128: + b_dh3 = tl.zeros([BV, 64], dtype=tl.float32) + if K > 192: + b_dh4 = tl.zeros([BV, 64], dtype=tl.float32) + else: + b_dh1 = tl.zeros([64, BV], dtype=tl.float32) + if K > 64: + b_dh2 = tl.zeros([64, BV], dtype=tl.float32) + if K > 128: + b_dh3 = tl.zeros([64, BV], dtype=tl.float32) + if K > 192: + b_dh4 = tl.zeros([64, BV], dtype=tl.float32) + + # calculate offset + q += (bos * H + i_h // (HV // H)).to(tl.int64) * K + k += (bos * H + i_h // (HV // H)).to(tl.int64) * K + w += (bos * HV + i_h).to(tl.int64) * K + do += (bos * HV + i_h).to(tl.int64) * V + dv += (bos * HV + i_h).to(tl.int64) * V + dv2 += (bos * HV + i_h).to(tl.int64) * V + dh += (boh * HV + i_h).to(tl.int64) * K*V + if USE_GK: + gk += (bos * HV + i_h).to(tl.int64) * K + + if USE_INITIAL_STATE: + dh0 += i_nh * K*V + if USE_FINAL_STATE_GRADIENT: + dht += i_nh * K*V + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + o_k1 = tl.arange(0, 64) + m_k1 = o_k1 < K + o_k2 = 64 + o_k1 + m_k2 = o_k2 < K + o_k3 = 128 + o_k1 + m_k3 = o_k3 < K + o_k4 = 192 + o_k1 + m_k4 = o_k4 < K + if USE_FINAL_STATE_GRADIENT: + if STATE_V_FIRST: + p_dht1 = dht + o_v[:, None] * K + o_k1[None, :] + m_dht1 = m_v[:, None] & m_k1[None, :] + else: + p_dht1 = dht + o_k1[:, None] * V + o_v[None, :] + m_dht1 = m_k1[:, None] & m_v[None, :] + b_dh1 += tl.load(p_dht1, mask=m_dht1, other=0.0) + if K > 64: + if STATE_V_FIRST: + p_dht2 = dht + o_v[:, None] * K + o_k2[None, :] + m_dht2 = m_v[:, None] & m_k2[None, :] + else: + p_dht2 = dht + o_k2[:, None] * V + o_v[None, :] + m_dht2 = m_k2[:, None] & m_v[None, :] + b_dh2 += tl.load(p_dht2, mask=m_dht2, other=0.0) + if K > 128: + if STATE_V_FIRST: + p_dht3 = dht + o_v[:, None] * K + o_k3[None, :] + m_dht3 = m_v[:, None] & m_k3[None, :] + else: + p_dht3 = dht + o_k3[:, None] * V + o_v[None, :] + m_dht3 = m_k3[:, None] & m_v[None, :] + b_dh3 += tl.load(p_dht3, mask=m_dht3, other=0.0) + if K > 192: + if STATE_V_FIRST: + p_dht4 = dht + o_v[:, None] * K + o_k4[None, :] + m_dht4 = m_v[:, None] & m_k4[None, :] + else: + p_dht4 = dht + o_k4[:, None] * V + o_v[None, :] + m_dht4 = m_k4[:, None] & m_v[None, :] + b_dh4 += tl.load(p_dht4, mask=m_dht4, other=0.0) + + for i_t in range(NT - 1, -1, -1): + i_t_int64 = i_t.to(tl.int64) + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + if STATE_V_FIRST: + p_dh1 = dh + i_t_int64*HV*K*V + o_v[:, None] * K + o_k1[None, :] + m_dh1 = m_v[:, None] & m_k1[None, :] + else: + p_dh1 = dh + i_t_int64*HV*K*V + o_k1[:, None] * V + o_v[None, :] + m_dh1 = m_k1[:, None] & m_v[None, :] + tl.store(p_dh1, b_dh1.to(p_dh1.dtype.element_ty), mask=m_dh1) + if K > 64: + if STATE_V_FIRST: + p_dh2 = dh + i_t_int64*HV*K*V + o_v[:, None] * K + o_k2[None, :] + m_dh2 = m_v[:, None] & m_k2[None, :] + else: + p_dh2 = dh + i_t_int64*HV*K*V + o_k2[:, None] * V + o_v[None, :] + m_dh2 = m_k2[:, None] & m_v[None, :] + tl.store(p_dh2, b_dh2.to(p_dh2.dtype.element_ty), mask=m_dh2) + if K > 128: + if STATE_V_FIRST: + p_dh3 = dh + i_t_int64*HV*K*V + o_v[:, None] * K + o_k3[None, :] + m_dh3 = m_v[:, None] & m_k3[None, :] + else: + p_dh3 = dh + i_t_int64*HV*K*V + o_k3[:, None] * V + o_v[None, :] + m_dh3 = m_k3[:, None] & m_v[None, :] + tl.store(p_dh3, b_dh3.to(p_dh3.dtype.element_ty), mask=m_dh3) + if K > 192: + if STATE_V_FIRST: + p_dh4 = dh + i_t_int64*HV*K*V + o_v[:, None] * K + o_k4[None, :] + m_dh4 = m_v[:, None] & m_k4[None, :] + else: + p_dh4 = dh + i_t_int64*HV*K*V + o_k4[:, None] * V + o_v[None, :] + m_dh4 = m_k4[:, None] & m_v[None, :] + tl.store(p_dh4, b_dh4.to(p_dh4.dtype.element_ty), mask=m_dh4) + + last_idx = min((i_t + 1) * BT, T) - 1 + if USE_G: + bg_last = tl.load(g + (bos + last_idx) * HV + i_h).to(tl.float32) + p_g = g + bos * HV + i_h + o_t * HV + b_g = tl.load(p_g, mask=m_t, other=0.0).to(tl.float32) + bg_last_exp = exp2(bg_last) + b_g_exp = exp2(b_g) + p_dv = dv + o_t[:, None] * (HV*V) + o_v[None, :] + p_dv2 = dv2 + o_t[:, None] * (HV*V) + o_v[None, :] + p_do = do + o_t[:, None] * (HV*V) + o_v[None, :] + + b_do = tl.load(p_do, mask=m_t[:, None] & m_v[None, :], other=0.0) + + # Update dv + p_k = k + o_t[:, None] * (H*K) + o_k1[None, :] + b_k = tl.load(p_k, mask=m_t[:, None] & m_k1[None, :], other=0.0) + if USE_GK: + o_k1 = tl.arange(0, 64) + b_gk_last1 = tl.load(gk + last_idx * HV*K + o_k1, mask=(o_k1 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_dv = tl.dot(b_k, tl.trans(b_dh1).to(b_k.dtype)) + else: + b_dv = tl.dot(b_k, b_dh1.to(b_k.dtype)) + + if K > 64: + p_k = k + o_t[:, None] * (H*K) + o_k2[None, :] + b_k = tl.load(p_k, mask=m_t[:, None] & m_k2[None, :], other=0.0) + if USE_GK: + b_gk_last2 = tl.load(gk + last_idx * HV*K + o_k2, mask=(o_k2 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_dv += tl.dot(b_k, tl.trans(b_dh2).to(b_k.dtype)) + else: + b_dv += tl.dot(b_k, b_dh2.to(b_k.dtype)) + + if K > 128: + p_k = k + o_t[:, None] * (H*K) + o_k3[None, :] + b_k = tl.load(p_k, mask=m_t[:, None] & m_k3[None, :], other=0.0) + if USE_GK: + b_gk_last3 = tl.load(gk + last_idx * HV*K + o_k3, mask=(o_k3 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_dv += tl.dot(b_k, tl.trans(b_dh3).to(b_k.dtype)) + else: + b_dv += tl.dot(b_k, b_dh3.to(b_k.dtype)) + + if K > 192: + p_k = k + o_t[:, None] * (H*K) + o_k4[None, :] + b_k = tl.load(p_k, mask=m_t[:, None] & m_k4[None, :], other=0.0) + if USE_GK: + b_gk_last4 = tl.load(gk + last_idx * HV*K + o_k4, mask=(o_k4 < K), other=0.).to(tl.float32) + if STATE_V_FIRST: + b_dv += tl.dot(b_k, tl.trans(b_dh4).to(b_k.dtype)) + else: + b_dv += tl.dot(b_k, b_dh4.to(b_k.dtype)) + + if USE_G: + b_dv *= tl.where(m_t, exp2(bg_last - b_g), 0)[:, None] + b_dv += tl.load(p_dv, mask=m_t[:, None] & m_v[None, :], other=0.0) + + tl.store(p_dv2, b_dv.to(p_dv.dtype.element_ty), mask=m_t[:, None] & m_v[None, :]) + # Update dh + p_w = w + o_k1[:, None] + o_t[None, :] * (HV*K) + p_q = q + o_k1[:, None] + o_t[None, :] * (H*K) + b_w = tl.load(p_w, mask=m_k1[:, None] & m_t[None, :], other=0.0) + b_q = tl.load(p_q, mask=m_k1[:, None] & m_t[None, :], other=0.0) + if USE_G: + b_dh1 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + if STATE_V_FIRST: + b_dh1 *= exp2(b_gk_last1)[None, :] + else: + b_dh1 *= exp2(b_gk_last1[:, None]) + if STATE_V_FIRST: + b_dh1 += tl.trans(tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype))) + else: + b_dh1 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 64: + p_q = q + o_k2[:, None] + o_t[None, :] * (H*K) + p_w = w + o_k2[:, None] + o_t[None, :] * (HV*K) + b_q = tl.load(p_q, mask=m_k2[:, None] & m_t[None, :], other=0.0) + b_w = tl.load(p_w, mask=m_k2[:, None] & m_t[None, :], other=0.0) + if USE_G: + b_dh2 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + if STATE_V_FIRST: + b_dh2 *= exp2(b_gk_last2)[None, :] + else: + b_dh2 *= exp2(b_gk_last2[:, None]) + if STATE_V_FIRST: + b_dh2 += tl.trans(tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype))) + else: + b_dh2 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 128: + p_q = q + o_k3[:, None] + o_t[None, :] * (H*K) + p_w = w + o_k3[:, None] + o_t[None, :] * (HV*K) + b_q = tl.load(p_q, mask=m_k3[:, None] & m_t[None, :], other=0.0) + b_w = tl.load(p_w, mask=m_k3[:, None] & m_t[None, :], other=0.0) + if USE_G: + b_dh3 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + if STATE_V_FIRST: + b_dh3 *= exp2(b_gk_last3)[None, :] + else: + b_dh3 *= exp2(b_gk_last3[:, None]) + if STATE_V_FIRST: + b_dh3 += tl.trans(tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype))) + else: + b_dh3 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + if K > 192: + p_q = q + o_k4[:, None] + o_t[None, :] * (H*K) + p_w = w + o_k4[:, None] + o_t[None, :] * (HV*K) + b_q = tl.load(p_q, mask=m_k4[:, None] & m_t[None, :], other=0.0) + b_w = tl.load(p_w, mask=m_k4[:, None] & m_t[None, :], other=0.0) + if USE_G: + b_dh4 *= bg_last_exp + b_q = b_q * b_g_exp[None, :] + if USE_GK: + if STATE_V_FIRST: + b_dh4 *= exp2(b_gk_last4)[None, :] + else: + b_dh4 *= exp2(b_gk_last4[:, None]) + if STATE_V_FIRST: + b_dh4 += tl.trans(tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype))) + else: + b_dh4 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(b_w, b_dv.to(b_w.dtype)) + + if USE_INITIAL_STATE: + if STATE_V_FIRST: + p_dh0 = dh0 + o_v[:, None] * K + o_k1[None, :] + m_dh0 = m_v[:, None] & m_k1[None, :] + else: + p_dh0 = dh0 + o_k1[:, None] * V + o_v[None, :] + m_dh0 = m_k1[:, None] & m_v[None, :] + tl.store(p_dh0, b_dh1.to(p_dh0.dtype.element_ty), mask=m_dh0) + if K > 64: + if STATE_V_FIRST: + p_dh1 = dh0 + o_v[:, None] * K + o_k2[None, :] + m_dh1 = m_v[:, None] & m_k2[None, :] + else: + p_dh1 = dh0 + o_k2[:, None] * V + o_v[None, :] + m_dh1 = m_k2[:, None] & m_v[None, :] + tl.store(p_dh1, b_dh2.to(p_dh1.dtype.element_ty), mask=m_dh1) + if K > 128: + if STATE_V_FIRST: + p_dh2 = dh0 + o_v[:, None] * K + o_k3[None, :] + m_dh2 = m_v[:, None] & m_k3[None, :] + else: + p_dh2 = dh0 + o_k3[:, None] * V + o_v[None, :] + m_dh2 = m_k3[:, None] & m_v[None, :] + tl.store(p_dh2, b_dh3.to(p_dh2.dtype.element_ty), mask=m_dh2) + if K > 192: + if STATE_V_FIRST: + p_dh3 = dh0 + o_v[:, None] * K + o_k4[None, :] + m_dh3 = m_v[:, None] & m_k4[None, :] + else: + p_dh3 = dh0 + o_k4[:, None] * V + o_v[None, :] + m_dh3 = m_k4[:, None] & m_v[None, :] + tl.store(p_dh3, b_dh4.to(p_dh3.dtype.element_ty), mask=m_dh3) + + +@dispatch('common') +def chunk_gated_delta_rule_fwd_h( + k: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + chunk_size: int = 64, + save_new_value: bool = True, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + cu_seqlens_cpu: torch.LongTensor | None = None, + chunk_indices: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]: + B, T, H, K, V, HV = *k.shape, u.shape[-1], u.shape[2] + BT = chunk_size + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + assert K <= 256, "current kernel does not support head dimension larger than 256." + + if state_v_first: + h = k.new_empty(B, NT, HV, V, K) + final_state = k.new_zeros(N, HV, V, K, dtype=torch.float32) if output_final_state else None + else: + h = k.new_empty(B, NT, HV, K, V) + final_state = k.new_zeros(N, HV, K, V, dtype=torch.float32) if output_final_state else None + + v_new = torch.empty_like(u) if save_new_value else None + def grid(meta): return (triton.cdiv(V, meta['BV']) * N * HV, ) + chunk_gated_delta_rule_fwd_kernel_h_blockdim64[grid]( + k=k, + v=u, + w=w, + v_new=v_new, + g=g, + gk=gk, + h=h, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BT=BT, + STATE_V_FIRST=state_v_first, + ) + return h, v_new, final_state + + +@dispatch('common') +def chunk_gated_delta_rule_bwd_dhu( + q: torch.Tensor, + k: torch.Tensor, + w: torch.Tensor, + do: torch.Tensor, + dv: torch.Tensor, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + h0: torch.Tensor | None = None, + dht: torch.Tensor | None = None, + scale: float | None = None, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V, HV = *q.shape, do.shape[-1], do.shape[2] + # N: the actual number of sequences in the batch with either equal or variable lengths + BT = chunk_size + assert K <= 256, "current kernel does not support head dimension being larger than 256." + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + if state_v_first: + dh = q.new_empty(B, NT, HV, V, K) + else: + dh = q.new_empty(B, NT, HV, K, V) + dh0 = torch.empty_like(h0, dtype=torch.float32) if h0 is not None else None + dv2 = torch.empty_like(dv) + + def grid(meta): return (triton.cdiv(V, meta['BV']) * N * HV, ) + chunk_gated_delta_rule_bwd_kernel_dhu_blockdim64[grid]( + q=q, + k=k, + w=w, + g=g, + gk=gk, + dht=dht, + dh0=dh0, + do=do, + dh=dh, + dv=dv, + dv2=dv2, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + scale=scale, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BT=BT, + STATE_V_FIRST=state_v_first, + ) + return dh, dh0, dv2 diff --git a/kda/_fla/ops/common/chunk_h.py b/kda/_fla/ops/common/chunk_h.py new file mode 100644 index 0000000..b069976 --- /dev/null +++ b/kda/_fla/ops/common/chunk_h.py @@ -0,0 +1,432 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.utils import prepare_chunk_offsets +from kda._fla.ops.utils.op import exp2 +from kda._fla.utils import autotune_cache_kwargs, check_shared_mem + +BKV_LIST = [32, 64] if check_shared_mem() else [16, 32] + + +@triton.heuristics({ + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BKV_LIST + for BV in BKV_LIST + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV', 'STATE_V_FIRST'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_fwd_kernel_h( + k, + v, + h, + g, + g_gamma, + gk, + gv, + h0, + ht, + cu_seqlens, + split_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, + STATE_V_FIRST: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + NT, NS = tl.cdiv(T, BT), tl.cdiv(T, BS) + boh = tl.load(split_offsets + i_n).to(tl.int64) + else: + bos, eos = i_n * T, i_n * T + T + NT, NS = tl.cdiv(T, BT), tl.cdiv(T, BS) + boh = i_n * NS + NTS = BS // BT + + if USE_G_GAMMA: + # decay rate given the head index + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + + # [BK, BV] accumulator; STATE_V_FIRST only flips the stored state's HBM layout to [V, K], applied at the load/store below. + b_h = tl.zeros([BK, BV], dtype=tl.float32) + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + if USE_INITIAL_STATE: + if STATE_V_FIRST: + p_h0 = h0 + i_nh * K*V + o_v[:, None] * K + o_k[None, :] + b_h = tl.trans(tl.load(p_h0, mask=(o_v[:, None] < V) & (o_k[None, :] < K), other=0.0)).to(tl.float32) + else: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h = tl.load(p_h0, mask=(o_k[:, None] < K) & (o_v[None, :] < V), other=0.0).to(tl.float32) + + for i_t in range(NT): + i_s = i_t // NTS + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + p_k = k + (bos*H + i_h) * K + o_k[:, None] + o_t[None, :] * (H*K) + p_v = v + (bos*H + i_h) * V + o_t[:, None] * (H*V) + o_v[None, :] + + o_h = ((boh + i_s) * H + i_h).to(tl.int64) * K*V + if STATE_V_FIRST: + p_h = h + o_h + o_v[:, None] * K + o_k[None, :] + m_h = (o_v[:, None] < V) & (o_k[None, :] < K) + else: + p_h = h + o_h + o_k[:, None] * V + o_v[None, :] + m_h = (o_k[:, None] < K) & (o_v[None, :] < V) + + if i_t % NTS == 0: + tl.store(p_h, (tl.trans(b_h) if STATE_V_FIRST else b_h).to(p_h.dtype.element_ty), mask=m_h) + # [BK, BT] + b_k = tl.load(p_k, mask=(o_k[:, None] < K) & m_t[None, :], other=0.0) + # [BT, BV] + b_v = tl.load(p_v, mask=m_t[:, None] & (o_v < V)[None, :], other=0.0) + last_idx = min((i_t + 1) * BT, T) - 1 + + # scalar decay + if USE_G: + b_g_last = tl.load(g + bos * H + last_idx * H + i_h) + p_g = g + bos*H + (i_t * BT + tl.arange(0, BT)) * H + i_h + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_h *= exp2(b_g_last) + b_v = (b_v * exp2(b_g_last - b_g)[:, None]).to(b_v.dtype) + + if USE_G_GAMMA: + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_h *= exp2(b_g_last) + b_v = (b_v * exp2(b_g_last - b_g)[:, None]).to(b_v.dtype) + + # vector decay, h = Diag(gk) @ h + if USE_GK: + p_gk = gk + (bos*H + i_h) * K + o_k[:, None] + o_t[None, :] * (H*K) + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_gk = tl.load(p_gk, mask=(o_k[:, None] < K) & m_t[None, :], other=0.0) + b_h *= exp2(b_gk_last)[:, None] + b_k = (b_k * exp2(b_gk_last[:, None] - b_gk)).to(b_k.dtype) + + # vector decay, h = h @ Diag(gv) + if USE_GV: + p_gv = gv + (bos*H + i_h) * V + o_t[:, None] * (H*V) + o_v[None, :] + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_gv = tl.load(p_gv, mask=m_t[:, None] & (o_v < V)[None, :], other=0.0) + b_h *= exp2(b_gv_last)[None, :] + b_v = (b_v * exp2(b_gv_last[None, :] - b_gv)).to(b_v.dtype) + + b_h += tl.dot(b_k, b_v) + + if STORE_FINAL_STATE: + if STATE_V_FIRST: + p_ht = ht + i_nh * K*V + o_v[:, None] * K + o_k[None, :] + tl.store(p_ht, tl.trans(b_h).to(p_ht.dtype.element_ty), mask=(o_v[:, None] < V) & (o_k[None, :] < K)) + else: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=(o_k[:, None] < K) & (o_v[None, :] < V)) + + +@triton.heuristics({ + 'STORE_INITIAL_STATE_GRADIENT': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BKV_LIST + for BV in BKV_LIST + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'USE_G', 'USE_GK', 'USE_GV', 'STATE_V_FIRST'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_bwd_kernel_dh( + q, + g, + g_gamma, + gk, + gv, + do, + dh, + dht, + dh0, + cu_seqlens, + split_offsets, + scale, + T, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + USE_G: tl.constexpr, + USE_G_GAMMA: tl.constexpr, + USE_GK: tl.constexpr, + USE_GV: tl.constexpr, + STORE_INITIAL_STATE_GRADIENT: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr, + STATE_V_FIRST: tl.constexpr, +): + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2).to(tl.int64) + i_n, i_hq = i_nh // HQ, i_nh % HQ + i_h = i_hq // NG + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + NT = tl.cdiv(T, BT) + NS = tl.cdiv(T, BS) + boh = tl.load(split_offsets + i_n).to(tl.int64) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + NS = tl.cdiv(T, BS) + boh = i_n * NS + + if USE_G_GAMMA: + b_gamma = tl.load(g_gamma + i_h) + b_g = b_gamma * (tl.arange(0, BT) + 1) + + # [BK, BV] accumulator; STATE_V_FIRST only flips the stored state's HBM layout to [V, K], applied at the load/store below. + b_dh = tl.zeros([BK, BV], dtype=tl.float32) + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + if USE_FINAL_STATE_GRADIENT: + if STATE_V_FIRST: + p_dht = dht + i_nh * K*V + o_v[:, None] * K + o_k[None, :] + b_dh += tl.trans(tl.load(p_dht, mask=(o_v[:, None] < V) & (o_k[None, :] < K), other=0.0)).to(tl.float32) + else: + p_dht = dht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_dh += tl.load(p_dht, mask=(o_k[:, None] < K) & (o_v[None, :] < V), other=0.0).to(tl.float32) + + for i_t in range(NT - 1, -1, -1): + i_s = i_t // (BS // BT) + o_dh = ((boh + i_s) * H + i_h).to(tl.int64) * K*V + if STATE_V_FIRST: + p_dh = dh + o_dh + o_v[:, None] * K + o_k[None, :] + m_dh = (o_v[:, None] < V) & (o_k[None, :] < K) + else: + p_dh = dh + o_dh + o_k[:, None] * V + o_v[None, :] + m_dh = (o_k[:, None] < K) & (o_v[None, :] < V) + + if i_t % (BS // BT) == 0: + tl.store(p_dh, (tl.trans(b_dh) if STATE_V_FIRST else b_dh).to(p_dh.dtype.element_ty), mask=m_dh) + last_idx = min(i_t * BT + BT, T) - 1 + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + # [BK, BT] + p_q = q + (bos*HQ + i_hq) * K + o_k[:, None] + o_t[None, :] * (HQ*K) + p_do = do + (bos*HQ + i_hq) * V + o_t[:, None] * (HQ*V) + o_v[None, :] + b_q = tl.load(p_q, mask=(o_k[:, None] < K) & m_t[None, :], other=0.0) + b_q = (b_q * scale).to(b_q.dtype) + # [BT, BV] + b_do = tl.load(p_do, mask=m_t[:, None] & (o_v < V)[None, :], other=0.0) + + if USE_G: + p_g = g + (bos + i_t * BT + tl.arange(0, BT)) * H + i_h + b_g_last = tl.load(g + (bos + last_idx) * H + i_h) + b_g = tl.load(p_g, mask=(i_t * BT + tl.arange(0, BT) < T), other=0.) + b_q = (b_q * exp2(b_g)[None, :]).to(b_q.dtype) + b_dh *= exp2(b_g_last) + + if USE_G_GAMMA: + b_g_last = b_gamma * min(BT, T - i_t * BT) + b_q = (b_q * exp2(b_g)[None, :]).to(b_q.dtype) + b_dh *= exp2(b_g_last) + + if USE_GK: + p_gk = gk + (bos*H + i_h) * K + o_k[:, None] + o_t[None, :] * (H*K) + p_gk_last = gk + (bos + last_idx) * H*K + i_h * K + i_k * BK + tl.arange(0, BK) + + b_gk = tl.load(p_gk, mask=(o_k[:, None] < K) & m_t[None, :], other=0.0) + b_gk_last = tl.load(p_gk_last, mask=(i_k * BK + tl.arange(0, BK) < K), other=0.) + b_q = (b_q * exp2(b_gk)).to(b_q.dtype) + b_dh *= exp2(b_gk_last)[:, None] + + if USE_GV: + p_gv = gv + (bos*H + i_h) * V + o_t[:, None] * (H*V) + o_v[None, :] + p_gv_last = gv + (bos + last_idx) * H*V + i_h * V + i_v * BV + tl.arange(0, BV) + + b_gv = tl.load(p_gv, mask=m_t[:, None] & (o_v < V)[None, :], other=0.0) + b_gv_last = tl.load(p_gv_last, mask=(i_v * BV + tl.arange(0, BV) < V), other=0.) + b_do = (b_do * exp2(b_gv)) + b_dh *= exp2(b_gv_last)[None, :] + + b_dh += tl.dot(b_q, b_do.to(b_q.dtype)) + + if STORE_INITIAL_STATE_GRADIENT: + if STATE_V_FIRST: + p_dh0 = dh0 + i_nh * K*V + o_v[:, None] * K + o_k[None, :] + tl.store(p_dh0, tl.trans(b_dh).to(p_dh0.dtype.element_ty), mask=(o_v[:, None] < V) & (o_k[None, :] < K)) + else: + p_dh0 = dh0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_dh0, b_dh.to(p_dh0.dtype.element_ty), mask=(o_k[:, None] < K) & (o_v[None, :] < V)) + + +@dispatch('common') +def chunk_fwd_h( + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + h0: torch.Tensor | None = None, + output_final_state: bool = False, + state_v_first: bool = False, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + split_size: int | None = None, + states_in_fp32: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + BS = BT if split_size is None else split_size + assert BS % BT == 0, f"The `split_size` (got {BS}) must be a multiple of `chunk_size` {BT}" + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NS, split_offsets = B, triton.cdiv(T, BS), None + else: + split_offsets = prepare_chunk_offsets(cu_seqlens, BS) + N, NS = len(cu_seqlens) - 1, split_offsets[-1].item() + + # `state_v_first` stores the states in V-first `[V, K]` layout instead of `[K, V]` + state_shape = (V, K) if state_v_first else (K, V) + h = k.new_empty(B, NS, H, *state_shape, dtype=k.dtype if not states_in_fp32 else torch.float) + ht = k.new_empty(N, H, *state_shape, dtype=torch.float) if output_final_state else None + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * H) + chunk_fwd_kernel_h[grid]( + k=k, + v=v, + h=h, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + h0=h0, + ht=ht, + cu_seqlens=cu_seqlens, + split_offsets=split_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + USE_G=g is not None, + USE_G_GAMMA=g_gamma is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + STATE_V_FIRST=state_v_first, + ) + return h, ht + + +@dispatch('common') +def chunk_bwd_dh( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + do: torch.Tensor, + h0: torch.Tensor, + dht: torch.Tensor, + scale: float, + g: torch.Tensor | None = None, + g_gamma: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + gv: torch.Tensor | None = None, + state_v_first: bool = False, + cu_seqlens: torch.Tensor | None = None, + chunk_size: int = 64, + split_size: int | None = None, + states_in_fp32: bool = False, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HQ = q.shape[2] + BT = chunk_size + BS = BT if split_size is None else split_size + assert BS % BT == 0, f"The `split_size` (got {BS}) must be a multiple of `chunk_size` {BT}" + # N: the actual number of sequences in the batch with either equal or variable lengths + # NG: number of groups in GQA + if cu_seqlens is None: + N, NS, split_offsets = B, triton.cdiv(T, BS), None + else: + split_offsets = prepare_chunk_offsets(cu_seqlens, BS) + N, NS = len(cu_seqlens) - 1, split_offsets[-1].item() + NG = HQ // H + + # `state_v_first` stores the states in V-first `[V, K]` layout instead of `[K, V]` + state_shape = (V, K) if state_v_first else (K, V) + dh = k.new_empty(B, NS, HQ, *state_shape, dtype=k.dtype if not states_in_fp32 else torch.float) + dh0 = torch.empty_like(h0, dtype=torch.float) if h0 is not None else None + + def grid(meta): return (triton.cdiv(K, meta['BK']), triton.cdiv(V, meta['BV']), N * H) + chunk_bwd_kernel_dh[grid]( + q=q, + g=g, + g_gamma=g_gamma, + gk=gk, + gv=gv, + do=do, + dh=dh, + dht=dht, + dh0=dh0, + cu_seqlens=cu_seqlens, + split_offsets=split_offsets, + scale=scale, + T=T, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + BS=BS, + NG=NG, + USE_G=g is not None, + USE_G_GAMMA=g_gamma is not None, + USE_GK=gk is not None, + USE_GV=gv is not None, + STATE_V_FIRST=state_v_first, + ) + return dh, dh0 diff --git a/kda/_fla/ops/common/gate.py b/kda/_fla/ops/common/gate.py new file mode 100644 index 0000000..d95849c --- /dev/null +++ b/kda/_fla/ops/common/gate.py @@ -0,0 +1,110 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +# Shared gate helpers reused across delta-rule family ops (KDA, GDN, ...). + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +@triton.jit +def fused_beta_sigmoid_fwd_kernel( + x, + y, + scale, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0).to(tl.int64) + offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE).to(tl.int64) + mask = offs < n_elements + b_x = tl.load(x + offs, mask=mask, other=0).to(tl.float32) + b_y = scale * tl.sigmoid(b_x) + tl.store(y + offs, b_y.to(y.dtype.element_ty), mask=mask) + + +@triton.jit +def fused_beta_sigmoid_bwd_kernel( + x, + dy, + dx, + scale, + n_elements, + BLOCK_SIZE: tl.constexpr, +): + pid = tl.program_id(0).to(tl.int64) + offs = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE).to(tl.int64) + mask = offs < n_elements + b_x = tl.load(x + offs, mask=mask, other=0).to(tl.float32) + b_dy = tl.load(dy + offs, mask=mask, other=0).to(tl.float32) + b_y = tl.sigmoid(b_x) + b_dx = b_dy * scale * b_y * (1.0 - b_y) + tl.store(dx + offs, b_dx.to(dx.dtype.element_ty), mask=mask) + + +_BETA_SIGMOID_BLOCK_SIZE = 2048 +_BETA_SIGMOID_NUM_WARPS = 8 + + +@dispatch('common') +def fused_beta_sigmoid_fwd(x: torch.Tensor, scale: float = 1.0) -> torch.Tensor: + y = torch.empty_like(x, dtype=torch.float32) + n_elements = x.numel() + grid = (triton.cdiv(n_elements, _BETA_SIGMOID_BLOCK_SIZE),) + fused_beta_sigmoid_fwd_kernel[grid]( + x, + y, + scale, + n_elements, + BLOCK_SIZE=_BETA_SIGMOID_BLOCK_SIZE, + num_warps=_BETA_SIGMOID_NUM_WARPS, + ) + return y + + +@dispatch('common') +def fused_beta_sigmoid_bwd(x: torch.Tensor, dy: torch.Tensor, scale: float = 1.0) -> torch.Tensor: + dx = torch.empty_like(x) + n_elements = x.numel() + grid = (triton.cdiv(n_elements, _BETA_SIGMOID_BLOCK_SIZE),) + fused_beta_sigmoid_bwd_kernel[grid]( + x, + dy, + dx, + scale, + n_elements, + BLOCK_SIZE=_BETA_SIGMOID_BLOCK_SIZE, + num_warps=_BETA_SIGMOID_NUM_WARPS, + ) + return dx + + +class BetaSigmoidFunction(torch.autograd.Function): + @staticmethod + @input_guard + @autocast_custom_fwd + def forward(ctx, x: torch.Tensor, scale: float = 1.0) -> torch.Tensor: + y = fused_beta_sigmoid_fwd(x, scale) + ctx.save_for_backward(x) + ctx.scale = scale + return y + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, dy: torch.Tensor): + (x,) = ctx.saved_tensors + dx = fused_beta_sigmoid_bwd(x, dy, ctx.scale) + return dx.type_as(x), None + + +def fused_beta_sigmoid(x: torch.Tensor, scale: float = 1.0) -> torch.Tensor: + return BetaSigmoidFunction.apply(x, scale) diff --git a/kda/_fla/ops/cp/__init__.py b/kda/_fla/ops/cp/__init__.py new file mode 100644 index 0000000..0febcba --- /dev/null +++ b/kda/_fla/ops/cp/__init__.py @@ -0,0 +1,13 @@ +"""Context-parallel stubs. Pass ``cp_context=None`` (the default).""" + + +class FLACPContext: + cu_seqlens = None + cu_seqlens_cpu = None + + +def build_cp_context(*args, **kwargs): + raise RuntimeError("Context parallel is not included in the vendored KDA kernels") + + +__all__ = ["FLACPContext", "build_cp_context"] diff --git a/kda/_fla/ops/cp/chunk_delta_h.py b/kda/_fla/ops/cp/chunk_delta_h.py new file mode 100644 index 0000000..ff9fc9e --- /dev/null +++ b/kda/_fla/ops/cp/chunk_delta_h.py @@ -0,0 +1,11 @@ +"""Context-parallel hooks referenced by KDA fwd/bwd. Not implemented here.""" + + +def _cp_unsupported(*args, **kwargs): + raise RuntimeError("Context parallel is not included in the vendored KDA kernels") + + +chunk_gated_delta_rule_fwd_h_pre_process = _cp_unsupported +compress_h0 = _cp_unsupported +chunk_gated_delta_rule_bwd_dhu_pre_process = _cp_unsupported +expand_h0 = _cp_unsupported diff --git a/kda/_fla/ops/gla/__init__.py b/kda/_fla/ops/gla/__init__.py new file mode 100644 index 0000000..735708b --- /dev/null +++ b/kda/_fla/ops/gla/__init__.py @@ -0,0 +1 @@ +# Vendored GLA chunk output kernel used by KDA. diff --git a/kda/_fla/ops/gla/chunk.py b/kda/_fla/ops/gla/chunk.py new file mode 100644 index 0000000..82b1d87 --- /dev/null +++ b/kda/_fla/ops/gla/chunk.py @@ -0,0 +1,1530 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.common.chunk_h import chunk_bwd_dh, chunk_fwd_h +from kda._fla.ops.utils import prepare_chunk_indices +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.constant import RCP_LN2 +from kda._fla.ops.utils.cumsum import chunk_local_cumsum +from kda._fla.ops.utils.op import exp2 +from kda._fla.utils import autotune_cache_kwargs, check_shared_mem, input_guard + +BK_LIST = [32, 64] if check_shared_mem() else [16, 32] +BV_LIST = [64, 128] if check_shared_mem('ampere') else [16, 32] + + +def _prune_gla_bwd_configs(configs, nargs, **kwargs): + # Keep a tile only if it leaves headroom below its dim, or is the smallest + # option (so small dims still autotune); this avoids a software-pipelined + # block load prefetching past the tensor. K/V arrive as launch kwargs. + args = {**(nargs or {}), **kwargs} + K, V = args['K'], args['V'] + min_bk = min(c.kwargs['BK'] for c in configs) + min_bv = min(c.kwargs['BV'] for c in configs) + return [ + c for c in configs + if (c.kwargs['BK'] < K or c.kwargs['BK'] == min_bk) + and (c.kwargs['BV'] < V or c.kwargs['BV'] == min_bv) + ] + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BC'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_inter( + q, + k, + g, + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + i_i, i_j = i_c // NC, i_c % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + if i_i <= i_j: + return + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + o_i = i_t * BT + i_i * BC + tl.arange(0, BC) + o_j = i_t * BT + i_j * BC + tl.arange(0, BC) + m_i = o_i < T + m_j = o_j < T + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + m_qk = m_i[:, None] & m_k[None, :] + m_kj = m_k[:, None] & m_j[None, :] + + p_q = q + (bos*H+i_h)*K + o_i[:, None] * (H*K) + o_k[None, :] + p_g = g + (bos*H+i_h)*K + o_i[:, None] * (H*K) + o_k[None, :] + p_k = k + (bos*H+i_h)*K + o_k[:, None] + o_j[None, :] * (H*K) + p_gk = g + (bos*H+i_h)*K + o_k[:, None] + o_j[None, :] * (H*K) + p_gn = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + # [BC, BK] + b_q = tl.load(p_q, mask=m_qk, other=0.0) + b_g = tl.load(p_g, mask=m_qk, other=0.0) + b_qg = b_q * exp2(b_g - b_gn[None, :]) * scale + # [BK, BC] + b_k = tl.load(p_k, mask=m_kj, other=0.0) + b_gk = tl.load(p_gk, mask=m_kj, other=0.0) + b_kg = b_k * exp2(b_gn[:, None] - b_gk) + # [BC, BC] using tf32 to improve precision here. + b_A += tl.dot(b_qg, b_kg) + + o_jA = i_j * BC + tl.arange(0, BC) + m_A = m_i[:, None] & (o_jA[None, :] < BT) + p_A = A + (bos*H + i_h)*BT + o_i[:, None] * (H*BT) + o_jA[None, :] + tl.store(p_A, b_A.to(A.dtype.element_ty), mask=m_A) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3] + ], + key=['BK', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_intra( + q, + k, + g, + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_i, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + i_j = i_i + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = tl.arange(0, BK) + o_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BT + i_j * BC + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + g += (bos * H + i_h) * K + A += (bos * H + i_h) * BT + + o_c = i_t * BT + i_i * BC + tl.arange(0, BC) + m_qk = m_A[:, None] & m_k[None, :] + p_q = q + o_c[:, None] * (H*K) + o_k[None, :] + p_g = g + o_c[:, None] * (H*K) + o_k[None, :] + b_q = tl.load(p_q, mask=m_qk, other=0.0) + b_g = tl.load(p_g, mask=m_qk, other=0.0) + + p_k = k + (i_t * BT + i_j * BC) * H*K + o_k + p_gk = g + (i_t * BT + i_j * BC) * H*K + o_k + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_k = tl.load(p_k, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_A = tl.sum(b_q * b_k[None, :] * exp2(b_g - b_gk[None, :]), 1) * scale + tl.store(A + o_A + j, b_A, mask=m_A) + p_k += H*K + p_gk += H*K + + tl.debug_barrier() + b_A = tl.zeros([BC, BC], dtype=tl.float32) + tl.store(A + o_A[:, None] + o_i, b_A, mask=m_A[:, None] & (o_i[:, None] < o_i)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['BC', 'BK'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_intra_split( + q, + k, + g, + A, + cu_seqlens, + chunk_indices, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_tc, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + i_t, i_i = (i_tc // NC).to(tl.int64), i_tc % NC + i_j = i_i + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = i_k * BK + tl.arange(0, BK) + o_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BC + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + g += (bos * H + i_h) * K + A += ((i_k * all + bos) * H + i_h) * BC + + o_c = i_t * BT + i_i * BC + tl.arange(0, BC) + m_qk = m_A[:, None] & m_k[None, :] + p_q = q + o_c[:, None] * (H*K) + o_k[None, :] + p_g = g + o_c[:, None] * (H*K) + o_k[None, :] + b_q = tl.load(p_q, mask=m_qk, other=0.0) + b_g = tl.load(p_g, mask=m_qk, other=0.0) + + p_k = k + (i_t * BT + i_j * BC) * H*K + o_k + p_gk = g + (i_t * BT + i_j * BC) * H*K + o_k + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_k = tl.load(p_k, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_A = tl.sum(b_q * b_k[None, :] * exp2(b_g - b_gk[None, :]), 1) * scale + tl.store(A + o_A + j, b_A, mask=m_A) + p_k += H*K + p_gk += H*K + + tl.debug_barrier() + b_A = tl.zeros([BC, BC], dtype=tl.float32) + tl.store(A + o_A[:, None] + o_i, b_A, mask=m_A[:, None] & (o_i[:, None] < o_i)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + triton.Config({}, num_warps=8), + ], + key=['BC'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_A_kernel_intra_sub_intra_merge( + A, + A2, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + NK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + if i_t * BT + i_c * BC >= T: + return + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + o_c = i_t * BT + i_c * BC + tl.arange(0, BC) + o_i = tl.arange(0, BC) + m_c = o_c < T + m_A = m_c[:, None] & (o_i[None, :] < BC) + m_A2 = m_c[:, None] & ((i_c * BC + o_i)[None, :] < BT) + for i_k in range(0, NK): + p_A = A + (i_k*all+bos)*H*BC+i_h*BC + o_c[:, None] * (H*BC) + o_i[None, :] + b_A += tl.load(p_A, mask=m_A, other=0.0) + p_A2 = A2 + (bos*H+i_h)*BT + o_c[:, None] * (H*BT) + (i_c * BC + o_i)[None, :] + tl.store(p_A2, b_A.to(A2.dtype.element_ty), mask=m_A2) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for BV in [64, 128] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'HV', 'STATE_V_FIRST'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_fwd_kernel_o( + q, + v, + g, + h, + o, + A, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + STATE_V_FIRST: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + if IS_VARLEN: + i_tg = i_t.to(tl.int64) + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = (i_b * NT + i_t).to(tl.int64) + bos, eos = (i_b * T).to(tl.int64), (i_b * T + T).to(tl.int64) + + m_s = tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :] + + q += (bos * H + i_h) * K + g += (bos * HV + i_hv) * K + v += (bos * HV + i_hv) * V + o += (bos * HV + i_hv) * V + h += (i_tg * HV + i_hv).to(tl.int64) * K * V + A += (bos * HV + i_hv) * BT + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + o_t = i_t * BT + tl.arange(0, BT) + o_v = i_v * BV + tl.arange(0, BV) + o_i = tl.arange(0, BT) + m_t = o_t < T + m_v = o_v < V + m_tv = m_t[:, None] & m_v[None, :] + m_A = m_t[:, None] & (o_i[None, :] < BT) + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + m_qk = m_t[:, None] & m_k[None, :] + p_q = q + o_t[:, None] * (H*K) + o_k[None, :] + p_g = g + o_t[:, None] * (HV*K) + o_k[None, :] + if STATE_V_FIRST: + p_h = h + o_v[:, None] * K + o_k[None, :] + m_h = m_v[:, None] & m_k[None, :] + else: + p_h = h + o_k[:, None] * V + o_v[None, :] + m_h = m_k[:, None] & m_v[None, :] + + # [BT, BK] + b_q = tl.load(p_q, mask=m_qk, other=0.0) + # [BT, BK] + b_g = tl.load(p_g, mask=m_qk, other=0.0).to(tl.float32) + # [BT, BK] + b_qg = (b_q * exp2(b_g)).to(b_q.dtype) + b_h = tl.load(p_h, mask=m_h, other=0.0) + if i_k >= 0: + if STATE_V_FIRST: + b_o += tl.dot(b_qg, tl.trans(b_h).to(b_qg.dtype)) + else: + b_o += tl.dot(b_qg, b_h.to(b_qg.dtype)) + b_o *= scale + p_v = v + o_t[:, None] * (HV*V) + o_v[None, :] + p_o = o + o_t[:, None] * (HV*V) + o_v[None, :] + p_A = A + o_t[:, None] * (HV*BT) + o_i[None, :] + # [BT, BV] + b_v = tl.load(p_v, mask=m_tv, other=0.0) + # [BT, BT] + b_A = tl.load(p_A, mask=m_A, other=0.0) + b_A = tl.where(m_s, b_A, 0.).to(b_v.dtype) + b_o += tl.dot(b_A, b_v) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=m_tv) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BK', 'NC', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_intra( + q, + k, + g, + dA, + dq, + dk, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_kc, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + i_k, i_i = i_kc // NC, i_kc % NC + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + if i_t * BT + i_i * BC >= T: + return + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + o_c = i_t * BT + i_i * BC + tl.arange(0, BC) + m_c = o_c < T + m_ck = m_c[:, None] & m_k[None, :] + p_g = g + (bos*H + i_h) * K + o_c[:, None] * (H*K) + o_k[None, :] + # [BC, BK] + b_g = tl.load(p_g, mask=m_ck, other=0.0) + + b_dq = tl.zeros([BC, BK], dtype=tl.float32) + if i_i > 0: + p_gn = g + (bos + i_t * BT + i_i * BC) * H*K + i_h*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(0, i_i): + o_j = i_t * BT + i_j * BC + tl.arange(0, BC) + o_jA = i_j * BC + tl.arange(0, BC) + m_jk = (o_j[:, None] < T) & m_k[None, :] + m_da = m_c[:, None] & (o_jA[None, :] < BT) + p_k = k+(bos*H+i_h)*K + o_j[:, None] * (H*K) + o_k[None, :] + p_gk = g+(bos*H+i_h)*K + o_j[:, None] * (H*K) + o_k[None, :] + p_dA = dA+(bos*H+i_h)*BT + o_c[:, None] * (H*BT) + o_jA[None, :] + # [BC, BK] + b_k = tl.load(p_k, mask=m_jk, other=0.0) + b_gk = tl.load(p_gk, mask=m_jk, other=0.0) + b_kg = b_k * exp2(b_gn[None, :] - b_gk) + # [BC, BC] + b_dA = tl.load(p_dA, mask=m_da, other=0.0) + + b_dq += tl.dot(b_dA, b_kg) + b_dq *= exp2(b_g - b_gn[None, :]) + o_i = tl.arange(0, BC) + m_dA = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + o_dA = bos*H*BT + (i_t * BT + i_i * BC + tl.arange(0, BC)) * H*BT + i_h * BT + i_i * BC + p_kj = k + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gkj = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_dq = dq + (bos*H + i_h) * K + o_c[:, None] * (H*K) + o_k[None, :] + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dA = tl.load(dA + o_dA + j, mask=m_dA, other=0) + # [BK,] + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] >= j + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dq += tl.where(m_i, b_dA[:, None] * b_kj[None, :] * exp2(b_g - b_gkj[None, :]), 0.) + p_kj += H*K + p_gkj += H*K + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), mask=m_ck) + + tl.debug_barrier() + # [BC, BK] + b_dk = tl.zeros([BC, BK], dtype=tl.float32) + + NC = min(NC, tl.cdiv(T - i_t * BT, BC)) + if i_i < NC - 1: + p_gn = g + (bos + min(i_t * BT + i_i * BC + BC, T) - 1) * H*K + i_h * K + o_k + + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(i_i + 1, NC): + o_j = i_t * BT + i_j * BC + o_i + o_iA = i_i * BC + tl.arange(0, BC) + m_j = o_j < T + m_jk = m_j[:, None] & m_k[None, :] + m_da = (o_iA[:, None] < BT) & m_j[None, :] + p_q = q + (bos*H+i_h)*K + o_j[:, None] * (H*K) + o_k[None, :] + p_gq = g + (bos*H+i_h)*K + o_j[:, None] * (H*K) + o_k[None, :] + p_dA = dA + (bos*H+i_h)*BT + o_iA[:, None] + o_j[None, :] * (H*BT) + # [BC, BK] + b_q = tl.load(p_q, mask=m_jk, other=0.0) + b_gq = tl.load(p_gq, mask=m_jk, other=0.0) + b_qg = b_q * tl.where(m_j[:, None], exp2(b_gq - b_gn[None, :]), 0) + # [BC, BC] + b_dA = tl.load(p_dA, mask=m_da, other=0.0) + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dk += tl.dot(b_dA, b_qg) + b_dk *= exp2(b_gn[None, :] - b_g) + o_dA = bos*H*BT + (i_t * BT + i_i * BC) * H*BT + i_h * BT + i_i * BC + tl.arange(0, BC) + p_qj = q + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gqj = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_dk = dk + (bos*H+i_h)*K + o_c[:, None] * (H*K) + o_k[None, :] + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dA = tl.load(dA + o_dA + j * H*BT) + # [BK,] + b_qj = tl.load(p_qj, mask=m_k, other=0).to(tl.float32) + b_gqj = tl.load(p_gqj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] <= j + b_dk += tl.where(m_i, b_dA[:, None] * b_qj[None, :] * exp2(b_gqj[None, :] - b_g), 0.) + p_qj += H*K + p_gqj += H*K + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=m_ck) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BV', 'BT'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_dA( + v, + do, + dA, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + o_t = i_t * BT + tl.arange(0, BT) + o_i = tl.arange(0, BT) + m_t = o_t < T + m_A = m_t[:, None] & (o_i[None, :] < BT) + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + m_tv = m_t[:, None] & m_v[None, :] + m_vt = m_v[:, None] & m_t[None, :] + p_do = do + (bos*H + i_h) * V + o_t[:, None] * (H*V) + o_v[None, :] + p_v = v + (bos*H + i_h) * V + o_v[:, None] + o_t[None, :] * (H*V) + b_v = tl.load(p_v, mask=m_vt, other=0.0) + b_do = tl.load(p_do, mask=m_tv, other=0.0) + + b_dA += tl.dot(b_do, b_v) + + p_dA = dA + (bos * H + i_h) * BT + o_t[:, None] * (H*BT) + o_i[None, :] + m_s = tl.arange(0, BT)[:, None] >= tl.arange(0, BT)[None, :] + b_dA = tl.where(m_s, b_dA * scale, 0.) + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), mask=m_A) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'STATE_V_FIRST', 'K', 'V'], + prune_configs_by={'early_config_prune': _prune_gla_bwd_configs}, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_dv( + k, + g, + A, + do, + dh, + dv, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, + STATE_V_FIRST: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + o_v = i_v * BV + tl.arange(0, BV) + o_i = tl.arange(0, BT) + m_t = o_t < T + m_v = o_v < V + m_A = (o_i[:, None] < BT) & m_t[None, :] + m_tv = m_t[:, None] & m_v[None, :] + p_A = A + (bos * H + i_h) * BT + o_i[:, None] + o_t[None, :] * (H*BT) + p_do = do + (bos * H + i_h) * V + o_t[:, None] * (H*V) + o_v[None, :] + p_dv = dv + (bos * H + i_h) * V + o_t[:, None] * (H*V) + o_v[None, :] + b_A = tl.load(p_A, mask=m_A, other=0.0) + b_do = tl.load(p_do, mask=m_tv, other=0.0) + + b_A = tl.where(tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :], b_A, 0.) + # (SY 09/17) important to disallow tf32 here to maintain a good precision. + b_dv = tl.dot(b_A, b_do.to(b_A.dtype), allow_tf32=False) + + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + m_tk = m_t[:, None] & m_k[None, :] + m_kvd = m_k[:, None] & m_v[None, :] + + p_k = k + (bos * H + i_h) * K + o_t[:, None] * (H*K) + o_k[None, :] + p_gk = g + (bos * H + i_h) * K + o_t[:, None] * (H*K) + o_k[None, :] + p_gn = g + (bos + min(i_t * BT + BT, T) - 1)*H*K + i_h * K + o_k + if STATE_V_FIRST: + # dh stored as [V, K]; read a logical [BK, BV] tile via on-the-fly transpose + p_dh = dh + (i_tg * H + i_h) * K*V + o_k[:, None] + o_v[None, :] * K + else: + p_dh = dh + (i_tg * H + i_h) * K*V + o_k[:, None] * V + o_v[None, :] + + b_k = tl.load(p_k, mask=m_tk, other=0.0) + b_gk = tl.load(p_gk, mask=m_tk, other=0.0) + b_dh = tl.load(p_dh, mask=m_kvd, other=0.0) + + b_gn = exp2(tl.load(p_gn, mask=m_k, other=0)[None, :] - b_gk) + b_k = (b_k * b_gn).to(b_k.dtype) + # [BT, BV] + # (SY 09/17) it is ok to have bf16 interchunk gradient contribution here + b_dv += tl.dot(b_k, b_dh.to(b_k.dtype)) + + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), mask=m_tv) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT', 'STATE_V_FIRST', 'K', 'V'], + prune_configs_by={'early_config_prune': _prune_gla_bwd_configs}, + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_gla_bwd_kernel_inter( + q, + k, + v, + g, + h, + do, + dh, + dq, + dk, + dq2, + dk2, + dg, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, + STATE_V_FIRST: tl.constexpr, +): + i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_tg = i_t + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = i_b * NT + i_t + bos, eos = i_b * T, i_b * T + T + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + g += (bos * H + i_h) * K + h += (i_tg * H + i_h) * K*V + do += (bos * H + i_h) * V + dh += (i_tg * H + i_h) * K*V + dq += (bos * H + i_h) * K + dk += (bos * H + i_h) * K + dq2 += (bos * H + i_h) * K + dk2 += (bos * H + i_h) * K + dg += (bos * H + i_h) * K + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_tk = m_t[:, None] & m_k[None, :] + p_gk = g + o_t[:, None] * (H*K) + o_k[None, :] + b_gk = tl.load(p_gk, mask=m_tk, other=0.0) + p_gn = g + (min(T, i_t * BT + BT) - 1) * H*K + o_k + b_gn = tl.load(p_gn, mask=m_k, other=0) + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dgk = tl.zeros([BK], dtype=tl.float32) + + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + m_tv = m_t[:, None] & m_v[None, :] + m_vk = m_v[:, None] & m_k[None, :] + p_v = v + o_t[:, None] * (H*V) + o_v[None, :] + p_do = do + o_t[:, None] * (H*V) + o_v[None, :] + if STATE_V_FIRST: + # h / dh stored as [V, K] -- the [BV, BK] tile is now a contiguous read + p_h = h + o_v[:, None] * K + o_k[None, :] + p_dh = dh + o_v[:, None] * K + o_k[None, :] + else: + p_h = h + o_v[:, None] + o_k[None, :] * V + p_dh = dh + o_v[:, None] + o_k[None, :] * V + # [BT, BV] + b_v = tl.load(p_v, mask=m_tv, other=0.0) + b_do = tl.load(p_do, mask=m_tv, other=0.0) + # [BV, BK] + b_h = tl.load(p_h, mask=m_vk, other=0.0) + b_dh = tl.load(p_dh, mask=m_vk, other=0.0) + + # [BK] + b_dgk += tl.sum(b_h * b_dh, axis=0) + # [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) + + b_dgk *= exp2(b_gn) + b_dq *= scale + b_dq = b_dq * exp2(b_gk) + b_dk = b_dk * exp2(b_gn[None, :] - b_gk) + p_q = q + o_t[:, None] * (H*K) + o_k[None, :] + p_k = k + o_t[:, None] * (H*K) + o_k[None, :] + p_dq = dq + o_t[:, None] * (H*K) + o_k[None, :] + p_dk = dk + o_t[:, None] * (H*K) + o_k[None, :] + b_q = tl.load(p_q, mask=m_tk, other=0.0) + b_k = tl.load(p_k, mask=m_tk, other=0.0) + b_dgk += tl.sum(b_dk * b_k, axis=0) + b_dq += tl.load(p_dq, mask=m_tk, other=0.0) + b_dk += tl.load(p_dk, mask=m_tk, other=0.0) + b_dg = b_q * b_dq - b_k * b_dk + # tl.debug_barrier() + b_dg = b_dg - tl.cumsum(b_dg, axis=0) + tl.sum(b_dg, axis=0)[None, :] + b_dgk[None, :] + # Buggy due to strange triton compiler issue. + # m_s = tl.where(tl.arange(0, BT)[:, None] <= tl.arange(0, BT)[None, :], 1., 0.) + # b_dg = tl.dot(m_s, b_dg, allow_tf32=False) + b_dgk[None, :] + p_dq = dq2 + o_t[:, None] * (H*K) + o_k[None, :] + p_dk = dk2 + o_t[:, None] * (H*K) + o_k[None, :] + p_dg = dg + o_t[:, None] * (H*K) + o_k[None, :] + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), mask=m_tk) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=m_tk) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), mask=m_tk) + + +@dispatch('gla') +def chunk_gla_fwd_intra_gk( + q: torch.Tensor, + k: torch.Tensor, + g: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + B, T, H, K = k.shape + BT = chunk_size + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BC = min(16, BT) + NC = triton.cdiv(BT, BC) + + A = q.new_empty(B, T, H, BT, dtype=torch.float) + grid = (NT, NC * NC, B * H) + chunk_gla_fwd_A_kernel_intra_sub_inter[grid]( + q=q, + k=k, + g=g, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + NC=NC, + ) + + grid = (NT, NC, B * H) + # load the entire [BC, K] blocks into SRAM at once + if K <= 256: + BK = max(triton.next_power_of_2(K), 16) + chunk_gla_fwd_A_kernel_intra_sub_intra[grid]( + q=q, + k=k, + g=g, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + ) + # split then merge + else: + BK = min(128, triton.next_power_of_2(K)) + NK = triton.cdiv(K, BK) + A_intra = q.new_empty(NK, B, T, H, BC, dtype=torch.float) + + grid = (NK, NT * NC, B * H) + chunk_gla_fwd_A_kernel_intra_sub_intra_split[grid]( + q=q, + k=k, + g=g, + A=A_intra, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + + grid = (NT, NC, B * H) + chunk_gla_fwd_A_kernel_intra_sub_intra_merge[grid]( + A=A_intra, + A2=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + B=B, + H=H, + BT=BT, + BC=BC, + NK=NK, + ) + return A + + +@dispatch('gla') +def chunk_gla_fwd_o_gk( + q: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + A: torch.Tensor, + h: torch.Tensor, + scale: float, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + B, T, H, K, HV, V = *q.shape, v.shape[2], v.shape[-1] + BT = chunk_size + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + # Please ensure zeros, since vllm will use padding v + o = torch.zeros_like(v) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * HV) + chunk_gla_fwd_kernel_o[grid]( + q=q, + v=v, + g=g, + h=h, + o=o, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BT=BT, + STATE_V_FIRST=state_v_first, + ) + return o + + +@dispatch('gla') +def chunk_gla_bwd_dA( + v: torch.Tensor, + do: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + B, T, H, V = v.shape + BT = chunk_size + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + BV = min(64, triton.next_power_of_2(V)) + + dA = v.new_empty(B, T, H, BT, dtype=torch.float) + grid = (NT, B * H) + chunk_gla_bwd_kernel_dA[grid]( + v=v, + do=do, + dA=dA, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + V=V, + BT=BT, + BV=BV, + ) + return dA + + +@dispatch('gla') +def chunk_gla_bwd_dv( + k: torch.Tensor, + g: torch.Tensor, + A: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, do.shape[-1] + BT = chunk_size + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dv = torch.empty_like(do) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * H) + chunk_gla_bwd_kernel_dv[grid]( + k=k, + g=g, + A=A, + do=do, + dh=dh, + dv=dv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + STATE_V_FIRST=state_v_first, + ) + return dv + + +@dispatch('gla') +def chunk_gla_bwd_dqk_intra( + q: torch.Tensor, + k: torch.Tensor, + g: torch.Tensor, + dA: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + B, T, H, K = q.shape + BT = chunk_size + BC = min(16, BT) + BK = min(64, triton.next_power_of_2(K)) + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + NK = triton.cdiv(K, BK) + + dq = torch.empty_like(q, dtype=torch.float) + dk = torch.empty_like(k, dtype=torch.float) + grid = (NK * NC, NT, B * H) + chunk_gla_bwd_kernel_intra[grid]( + q=q, + k=k, + g=g, + dA=dA, + dq=dq, + dk=dk, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + return dq, dk + + +@dispatch('gla') +def chunk_gla_bwd_dqkg( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + g: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + dq: torch.Tensor, + dk: torch.Tensor, + scale: float | None = None, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + B, T, H, K, V = *k.shape, v.shape[-1] + BT = chunk_size + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dg = torch.empty_like(g) + dq2 = torch.empty_like(dq) + dk2 = torch.empty_like(dk) + def grid(meta): return (triton.cdiv(K, meta['BK']), NT, B * H) + chunk_gla_bwd_kernel_inter[grid]( + q=q, + k=k, + v=v, + g=g, + h=h, + do=do, + dh=dh, + dq=dq, + dk=dk, + dq2=dq2, + dk2=dk2, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + STATE_V_FIRST=state_v_first, + ) + return dq2, dk2, dg + + +def chunk_gla_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + g_cumsum: torch.Tensor | None, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + if g_cumsum is None: + g_cumsum = chunk_local_cumsum( + g, + chunk_size, + scale=RCP_LN2, + cu_seqlens=cu_seqlens, + ) + + h, ht = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=g_cumsum, + gv=None, + h0=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=False, + state_v_first=state_v_first, + ) + + # the intra A is kept in fp32 + # the computation has very marginal effect on the entire throughput + A = chunk_gla_fwd_intra_gk( + q=q, + k=k, + g=g_cumsum, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + ) + o = chunk_gla_fwd_o_gk( + q=q, + v=v, + g=g_cumsum, + A=A, + h=h, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=state_v_first, + ) + return g_cumsum, A, h, ht, o + + +def chunk_gla_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + g_cumsum: torch.Tensor | None, + scale: float, + initial_state: torch.Tensor, + h: torch.Tensor, + A: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + if g_cumsum is None: + g_cumsum = chunk_local_cumsum( + g, + chunk_size, + scale=RCP_LN2, + cu_seqlens=cu_seqlens, + ) + + if h is None: + h, _ = chunk_fwd_h( + k=k, + v=v, + g=None, + gk=g_cumsum, + gv=None, + h0=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=True, + state_v_first=state_v_first, + ) + dh, dh0 = chunk_bwd_dh( + q=q, + k=k, + v=v, + g=None, + gk=g_cumsum, + gv=None, + do=do, + h0=initial_state, + dht=dht, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + states_in_fp32=True, + state_v_first=state_v_first, + ) + + dv = chunk_gla_bwd_dv( + k=k, + g=g_cumsum, + A=A, + do=do, + dh=dh, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=state_v_first, + ) + + # dq dk in fp32 + dA = chunk_gla_bwd_dA( + v=v, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + ) + dq, dk = chunk_gla_bwd_dqk_intra( + q=q, + k=k, + g=g_cumsum, + dA=dA, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + ) + dq, dk, dg = chunk_gla_bwd_dqkg( + q=q, + k=k, + v=v, + h=h, + g=g_cumsum, + do=do, + dh=dh, + dq=dq, + dk=dk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=state_v_first, + ) + return dq, dk, dv, dg, dh0 + + +class ChunkGLAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q, + k, + v, + g, + scale, + initial_state, + output_final_state, + state_v_first, + cu_seqlens, + cu_seqlens_cpu, + ): + chunk_size = min(64, max(16, triton.next_power_of_2(q.shape[1]))) + if cu_seqlens is not None: + chunk_indices = prepare_chunk_indices( + cu_seqlens, + chunk_size, + cu_seqlens_cpu=cu_seqlens_cpu, + ) + else: + chunk_indices = None + + g_cumsum, A, _, ht, o = chunk_gla_fwd( + q=q, + k=k, + v=v, + g=g, + g_cumsum=None, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=state_v_first, + ) + # recompute g_cumsum in bwd pass + if g.dtype != torch.float: + g_cumsum = None + else: + g = None + ctx.save_for_backward(q, k, v, g, g_cumsum, initial_state, A, chunk_indices) + ctx.chunk_size = chunk_size + ctx.scale = scale + ctx.cu_seqlens = cu_seqlens + ctx.state_v_first = state_v_first + return o, ht + + @staticmethod + @input_guard + def backward(ctx, do, dht): + q, k, v, g, g_cumsum, initial_state, A, chunk_indices = ctx.saved_tensors + chunk_size, scale, cu_seqlens = ctx.chunk_size, ctx.scale, ctx.cu_seqlens + dq, dk, dv, dg, dh0 = chunk_gla_bwd( + q=q, + k=k, + v=v, + g=g, + g_cumsum=g_cumsum, + scale=scale, + h=None, + A=A, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=ctx.state_v_first, + ) + return dq.to(q), dk.to(k), dv.to(v), dg, None, dh0, None, None, None, None + + +@torch.compiler.disable +def chunk_gla( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + scale: int | None = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + cu_seqlens_cpu: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, H, V]`. + g (torch.Tensor): + Forget gates of shape `[B, T, H, K]`. + scale (Optional[float]): + Scale factor for the attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, H, K, V]` (or `[N, H, V, K]` if `state_v_first=True`) + for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, H, K, V]` + (or `[N, H, V, K]` if `state_v_first=True`). Default: `False`. + state_v_first (Optional[bool]): + Store the recurrent state in V-first `[V, K]` layout instead of the default `[K, V]`. Default: `False`. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, H, V]`. + final_state (torch.Tensor): + Final state of shape `[N, H, K, V]` (or `[N, H, V, K]` if `state_v_first=True`) + if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.gla import chunk_gla + # inputs with equal lengths + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = torch.randn(B, T, H, K, device='cuda') + >>> v = torch.randn(B, T, H, V, device='cuda') + >>> g = F.logsigmoid(torch.randn(B, T, H, K, device='cuda')) + >>> h0 = torch.randn(B, H, K, V, device='cuda') + >>> o, ht = chunk_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g = map(lambda x: rearrange(x, 'b t h d -> 1 (b t) h d'), (q, k, v, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = chunk_gla( + q, k, v, g, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = q.shape[-1] ** -0.5 + if initial_state is not None: + assert initial_state.dtype == torch.float32, "initial_state must be in float32." + assert q.shape == k.shape == g.shape, "q, k, g must have the same shape." + assert v.shape == (*q.shape[:3], v.shape[-1]), "v must be of shape (batch size, seq len, num of head, head dim)." + o, final_state = ChunkGLAFunction.apply( + q, + k, + v, + g, + scale, + initial_state, + output_final_state, + state_v_first, + cu_seqlens, + cu_seqlens_cpu, + ) + return o, final_state diff --git a/kda/_fla/ops/kda/__init__.py b/kda/_fla/ops/kda/__init__.py new file mode 100644 index 0000000..483321f --- /dev/null +++ b/kda/_fla/ops/kda/__init__.py @@ -0,0 +1,7 @@ +from .chunk import chunk_kda +from .fused_recurrent import fused_recurrent_kda + +__all__ = [ + "chunk_kda", + "fused_recurrent_kda", +] diff --git a/kda/_fla/ops/kda/chunk.py b/kda/_fla/ops/kda/chunk.py new file mode 100644 index 0000000..0f99931 --- /dev/null +++ b/kda/_fla/ops/kda/chunk.py @@ -0,0 +1,443 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +# Related files are modified and supported by the Moonshot AI Team + +import warnings + +import torch + +from kda._fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from kda._fla.ops.backends import dispatch +from kda._fla.ops.common.gate import fused_beta_sigmoid, fused_beta_sigmoid_bwd +from kda._fla.ops.cp import FLACPContext +from kda._fla.ops.kda.chunk_bwd import chunk_kda_bwd +from kda._fla.ops.kda.chunk_fwd import chunk_kda_fwd +from kda._fla.ops.utils.index import prepare_chunk_indices +from kda._fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + + +class ChunkKDAFunction(torch.autograd.Function): + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + A_log: torch.Tensor, + dt_bias: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + use_gate_in_kernel: bool = False, + use_beta_sigmoid_in_kernel: bool = False, + allow_neg_eigval: bool = False, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + cu_seqlens_cpu: torch.LongTensor | None = None, + safe_gate: bool = False, + lower_bound: float | None = None, + chunk_size: int = 64, + disable_recompute: bool = False, + return_intermediate_states: bool = False, + cp_context: FLACPContext | None = None, + ): + # Apply l2norm + q_rstd, k_rstd = None, None + if use_qk_l2norm_in_kernel: + q, q_rstd = l2norm_fwd(q) + k, k_rstd = l2norm_fwd(k) + + beta_raw = beta + if use_beta_sigmoid_in_kernel: + beta = fused_beta_sigmoid(beta_raw, scale=2.0 if allow_neg_eigval else 1.0) + + chunk_indices = None + if cu_seqlens is not None: + chunk_indices = prepare_chunk_indices( + cu_seqlens, + chunk_size, + cu_seqlens_cpu=cu_seqlens_cpu, + ) + + g_input = g + + (o, final_state, g_cumsum, Aqk, Akk, w, u, qg, kg, v_new, h, initial_state) = chunk_kda_fwd( + q=q, + k=k, + v=v, + g=g_input, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + cu_seqlens_cpu=cu_seqlens_cpu, + chunk_indices=chunk_indices, + safe_gate=safe_gate, + lower_bound=lower_bound, + use_gate_in_kernel=use_gate_in_kernel, + A_log=A_log, + dt_bias=dt_bias, + chunk_size=chunk_size, + disable_recompute=disable_recompute, + return_intermediate_states=return_intermediate_states, + cp_context=cp_context, + state_v_first=state_v_first, + ) + + if return_intermediate_states: + assert torch.is_inference_mode_enabled(), "return_intermediate_states is only allowed in inference mode" + assert disable_recompute is False, "return_intermediate_states must be used with disable_recompute=False" + return o.type_as(q), final_state, h + + ctx.save_for_backward( + q, q_rstd, k, k_rstd, v, g_cumsum, g_input, beta_raw, beta, A_log, dt_bias, Aqk, Akk, + w, u, qg, kg, v_new, h, + initial_state, cu_seqlens, chunk_indices + ) + ctx.chunk_size = chunk_size + ctx.safe_gate = safe_gate + ctx.scale = scale + ctx.lower_bound = lower_bound + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + ctx.use_gate_in_kernel = use_gate_in_kernel + ctx.use_beta_sigmoid_in_kernel = use_beta_sigmoid_in_kernel + ctx.allow_neg_eigval = allow_neg_eigval + ctx.disable_recompute = disable_recompute + ctx.cp_context = cp_context + ctx.state_v_first = state_v_first + return o.type_as(q), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor, + ): + (q, q_rstd, k, k_rstd, v, g_cumsum, g_input, beta_raw, beta, A_log, dt_bias, Aqk, Akk, + w, u, qg, kg, v_new, h, + initial_state, cu_seqlens, chunk_indices) = ( + ctx.saved_tensors + ) + + dq, dk, dv, db, dg, dh0, dA, dbias = chunk_kda_bwd( + q=q, + k=k, + v=v, + beta=beta, + Aqk=Aqk, + Akk=Akk, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + g=g_cumsum, + g_org=g_input if ctx.use_gate_in_kernel else None, + state_v_first=ctx.state_v_first, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + chunk_size=ctx.chunk_size, + safe_gate=ctx.safe_gate, + lower_bound=ctx.lower_bound, + use_gate_in_kernel=ctx.use_gate_in_kernel, + A_log=A_log, + dt_bias=dt_bias, + disable_recompute=ctx.disable_recompute, + cp_context=ctx.cp_context, + w=w, + u=u, + qg=qg, + kg=kg, + v_new=v_new, + h=h, + ) + if ctx.use_qk_l2norm_in_kernel: + dq = l2norm_bwd(q, q_rstd, dq) + dk = l2norm_bwd(k, k_rstd, dk) + if ctx.use_beta_sigmoid_in_kernel: + db = fused_beta_sigmoid_bwd(beta_raw, db, scale=2.0 if ctx.allow_neg_eigval else 1.0) + + return (dq.to(q), dk.to(k), dv.to(v), dg.to(g_input), db.to(beta_raw), dA, dbias, None, dh0, + None, None, None, None, None, None, None, None, None, None, None, None, None, None) + + +@dispatch('kda') +@torch.compiler.disable +def chunk_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + use_gate_in_kernel: bool = False, + use_beta_sigmoid_in_kernel: bool = False, + allow_neg_eigval: bool = False, + safe_gate: bool = False, + lower_bound: float | None = None, + disable_recompute: bool = False, + return_intermediate_states: bool = False, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + cu_seqlens_cpu: torch.LongTensor | None = None, + cp_context: FLACPContext = None, + **kwargs, +): + r""" + Args: + q (torch.Tensor): + queries of shape ``[B, T, H, K]``. + k (torch.Tensor): + keys of shape ``[B, T, H, K]``. + v (torch.Tensor): + values of shape ``[B, T, HV, V]``. + GVA (Grouped Value Attention) is applied if ``HV > H``, where ``HV`` must be divisible by ``H``. + g (torch.Tensor): + (forget) gating tensor (in log space!) of shape ``[B, T, HV, K]``. + When ``use_gate_in_kernel=False`` (default), ``g`` should be the pre-computed decay value. + When ``use_gate_in_kernel=True``, ``g`` is the raw input before gate activation; + the kernel fuses ``-exp(A_log) * softplus(g + dt_bias)`` + chunk cumsum internally. + beta (torch.Tensor): + betas of shape ``[B, T, HV]``. + scale (Optional[float]): + Scale factor for the KDA attention scores. + If not provided, it will default to ``1 / sqrt(K)``. Default: ``None``. + initial_state (Optional[torch.Tensor]): + Initial state of shape ``[N, HV, K, V]`` for ``N`` input sequences. + For equal-length input sequences, ``N`` equals the batch size ``B``. + Default: ``None``. + output_final_state (Optional[bool]): + Whether to output the final state of shape ``[N, HV, K, V]``. Default: ``False``. + use_qk_l2norm_in_kernel (bool): + Whether to apply L2norm to the q,k tensor internally. Default: ``False``. + use_gate_in_kernel (bool): + Whether to compute the log-space KDA decay internally. + - If ``True``: + The passed ``g`` acts as the raw input for ``-exp(A_log) * softplus(g + dt_bias.view(HV, K))``. + Note that as part of the input arguments, + ``A_log`` (shape ``[HV]``) and the optional ``dt_bias`` (shape ``[HV * K]``) should be provided. + When ``lower_bound`` is set, ``A_log`` may be ``None``, + in which case the gate is ``lower_bound * sigmoid(g + dt_bias)``. + - If ``False``, ``g`` is expected to be the pre-computed decay value. + Default: ``False``. + use_beta_sigmoid_in_kernel (bool): + Whether to apply ``torch.sigmoid(beta)`` before launching the chunk kernel. + - If ``True``, the passed ``beta`` acts as the raw beta logits. + - If ``False``, ``beta`` is expected to already be in post-sigmoid space. + Default: ``False``. + allow_neg_eigval (bool): + Whether to allow negative eigenvalues by scaling ``beta`` to ``[0, 2)``. + Only takes effect together with ``use_beta_sigmoid_in_kernel=True``, in which case + the kernel computes ``2 * sigmoid(beta)`` instead of ``sigmoid(beta)``. + Default: ``False``. + safe_gate (bool): + Whether to clamp the gate to ``[lower_bound, 0)`` and enable M=16 TensorCore + acceleration for higher throughput. Requires ``lower_bound`` to be set. + Default: ``False``. + lower_bound (Optional[float]): + Lower bound for the forget gate (in log space). When set together with + ``safe_gate=True``, changes the gate activation from + ``-exp(A_log) * softplus(g + dt_bias)`` to + ``lower_bound * sigmoid(exp(A_log) * (g + dt_bias))``, + which naturally clamps the output to ``[lower_bound, 0)``. + Recommended value: ``-5`` (i.e., per-step decay ``exp(-5) ≈ 0.0067``). + Default: ``None``. + disable_recompute (bool): + Whether to disable gradient recomputation in the kernel. When ``True``, the kernel + will save all intermediate activations for backward pass, which is beneficial + for training small models at the cost of increased memory usage. Default: ``False``. + return_intermediate_states (bool): + If True, returns intermediate state ``h`` for inference scenarios (e.g., vLLM). + Must be used within ``torch.inference_mode()`` and will return a 3-tuple instead of 2-tuple. + This is not intended for training as it bypasses autograd. Default: ``False``. + state_v_first (Optional[bool]): + Store the recurrent state in V-first ``[V, K]`` layout instead of the default ``[K, V]``. Default: ``False``. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape ``[N+1]`` used for variable-length training, + consistent with the FlashAttention API. + cu_seqlens_cpu (torch.LongTensor): + Cumulative sequence lengths of shape ``[N+1]`` used for variable-length training, + consistent with the FlashAttention API. + cp_context (Optional[FLACPContext]): + Context parallel context for distributed training across multiple devices. + When provided, ``initial_state`` and ``output_final_state`` are not supported, + and ``cu_seqlens`` will be overridden by the context. Default: ``None``. + + Returns: + - Normal mode (return_intermediate_states=False): A tuple (o, final_state) + o (torch.Tensor): + Outputs of shape ``[B, T, HV, V]``. + final_state (torch.Tensor): + Final state of shape ``[N, HV, K, V]`` if ``output_final_state=True`` else ``None``. + - Inference mode (return_intermediate_states=True): A tuple (o, final_state, h) + o (torch.Tensor): + Outputs of shape ``[B, T, HV, V]``. + final_state (torch.Tensor): + Final state of shape ``[N, HV, K, V]`` if ``output_final_state=True`` else ``None``. + h (torch.Tensor): + Intermediate states of shape ``[B, NT, HV, K, V]`` and dtype ``bfloat16``. + - For equal-length sequences: ``NT = ceil(T / chunk_size)`` + - For variable-length sequences (cu_seqlens): B is always 1 (flattened), + NT is the total number of chunks across all sequences. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.kda import chunk_kda + # inputs with equal lengths (no GVA, HV == H) + >>> B, T, H, K, V = 4, 2048, 4, 512, 512 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> k = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') + >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda') + >>> g = torch.rand(B, T, H, K, dtype=torch.bfloat16, device='cuda') + >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda') + >>> A_log = torch.randn(H, dtype=torch.float32, device='cuda') + >>> dt_bias = torch.randn(H * K, dtype=torch.float32, device='cuda') + >>> o, ht = chunk_kda( + q, k, v, g, beta, + A_log=A_log, + dt_bias=dt_bias, + use_qk_l2norm_in_kernel=True, + use_gate_in_kernel=True, + initial_state=h0, + output_final_state=True + ) + # GVA mode (HV > H) + >>> HV = 8 # 2x more value heads than qk heads + >>> v = torch.randn(B, T, HV, V, dtype=torch.bfloat16, device='cuda') + >>> g = torch.rand(B, T, HV, K, dtype=torch.bfloat16, device='cuda') + >>> beta = torch.rand(B, T, HV, dtype=torch.bfloat16, device='cuda') + >>> h0 = torch.randn(B, HV, K, V, dtype=torch.bfloat16, device='cuda') + >>> A_log = torch.randn(HV, dtype=torch.float32, device='cuda') + >>> dt_bias = torch.randn(HV * K, dtype=torch.float32, device='cuda') + >>> o, ht = chunk_kda( + q, k, v, g, beta, + A_log=A_log, + dt_bias=dt_bias, + use_qk_l2norm_in_kernel=True, + use_gate_in_kernel=True, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o, ht = chunk_kda( + q, k, v, g, beta, + A_log=A_log, + dt_bias=dt_bias, + use_qk_l2norm_in_kernel=True, + use_gate_in_kernel=True, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if 'transpose_state_layout' in kwargs: + if state_v_first: + raise ValueError("Cannot pass both `state_v_first` and the deprecated `transpose_state_layout`.") + warnings.warn( + "`transpose_state_layout` is deprecated and renamed to `state_v_first`.", + DeprecationWarning, + stacklevel=2, + ) + state_v_first = kwargs.pop('transpose_state_layout') + + if cp_context is not None: + assert initial_state is None, "Initial state is not supported for CP" + assert output_final_state is False, "Output final state is not supported for CP" + assert cp_context.cu_seqlens is not None, "cu_seqlens is required for CP" + # Override cu_seqlens and cu_seqlens_cpu with the ones from the context + cu_seqlens = cp_context.cu_seqlens + if cp_context.cu_seqlens_cpu is not None: + cu_seqlens_cpu = cp_context.cu_seqlens_cpu + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if initial_state is not None: + assert initial_state.dtype == torch.float32, "initial_state must be in float32." + + A_log, dt_bias = None, None + if use_gate_in_kernel: + A_log, dt_bias = kwargs.get("A_log"), kwargs.get("dt_bias") + if A_log is None and lower_bound is None: + raise ValueError("`A_log` must be provided when `use_gate_in_kernel=True` and `lower_bound` is not set.") + + chunk_size = kwargs.pop("chunk_size", 64) + if chunk_size not in (32, 64): + raise ValueError(f"`chunk_size` must be either 32 or 64 for KDA, got {chunk_size}.") + + if safe_gate and use_gate_in_kernel: + if lower_bound is None: + raise ValueError("`lower_bound` must be specified when `safe_gate=True` and `use_gate_in_kernel=True`.") + if not (-5 <= lower_bound < 0): + raise ValueError(f"`lower_bound` must be in the safe range [-5, 0), got {lower_bound}.") + + if allow_neg_eigval and not use_beta_sigmoid_in_kernel: + raise ValueError("`allow_neg_eigval=True` requires `use_beta_sigmoid_in_kernel=True`.") + + # Validate head dimensions for GVA + B, T, H, K, HV = *q.shape, v.shape[2] + assert q.shape == k.shape, f"q and k must have the same shape, got q={q.shape} vs k={k.shape}" + assert K <= 256, f"Currently we only support key headdim <=256 for KDA, got {K}." + assert HV % H == 0, ( + f"For GVA, num_v_heads (HV={HV}) must be evenly divisible by num_qk_heads (H={H}), " + f"but got HV % H = {HV % H}" + ) + assert g.shape == (B, T, HV, K), f"g must have shape [B, T, HV, K]={[B, T, HV, K]}, got {list(g.shape)}" + assert beta.shape == (B, T, HV), f"beta must have shape [B, T, HV]={[B, T, HV]}, got {list(beta.shape)}" + + if scale is None: + scale = K ** -0.5 + return ChunkKDAFunction.apply( + q, + k, + v, + g, + beta, + A_log, + dt_bias, + scale, + initial_state, + output_final_state, + use_qk_l2norm_in_kernel, + use_gate_in_kernel, + use_beta_sigmoid_in_kernel, + allow_neg_eigval, + state_v_first, + cu_seqlens, + cu_seqlens_cpu, + safe_gate, + lower_bound, + chunk_size, + disable_recompute, + return_intermediate_states, + cp_context, + ) diff --git a/kda/_fla/ops/kda/chunk_bwd.py b/kda/_fla/ops/kda/chunk_bwd.py new file mode 100644 index 0000000..9b8a88a --- /dev/null +++ b/kda/_fla/ops/kda/chunk_bwd.py @@ -0,0 +1,651 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.common.chunk_delta_h import ( + chunk_gated_delta_rule_bwd_dhu, + chunk_gated_delta_rule_fwd_h, +) +from kda._fla.ops.cp import FLACPContext +from kda._fla.ops.cp.chunk_delta_h import ( + chunk_gated_delta_rule_bwd_dhu_pre_process, + expand_h0, +) +from kda._fla.ops.kda.chunk_intra import chunk_kda_bwd_intra +from kda._fla.ops.kda.gate import kda_gate_bwd, kda_gate_chunk_cumsum +from kda._fla.ops.kda.wy_fast import recompute_w_u_fwd +from kda._fla.ops.utils import chunk_local_cumsum, prepare_chunk_indices +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.constant import RCP_LN2 +from kda._fla.ops.utils.op import exp2 +from kda._fla.utils import ( + IS_NVIDIA_HOPPER, + IS_NVIDIA_SM100, + autotune_cache_kwargs, + check_shared_mem, +) + +BK_LIST = [32, 64] if check_shared_mem() else [16, 32] +BV_LIST = [64, 128] if check_shared_mem("ampere") else [16, 32] +NUM_WARPS = [2, 4] if IS_NVIDIA_HOPPER else [2, 4, 8] + + +@triton.heuristics( + { + "IS_VARLEN": lambda args: args["cu_seqlens"] is not None, + } +) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=["H", "HV", "K", "V", "BT", "BK", "BV"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunk_kda_bwd_kernel_dAv( + q, + k, + v, + A, + do, + dv, + dA, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + if IS_VARLEN: + i_n, i_t = ( + tl.load(chunk_indices + i_t * 2).to(tl.int32), + tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64), + ) + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int64), + tl.load(cu_seqlens + i_n + 1).to(tl.int64), + ) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + # offset calculation + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * HV + i_hv) * V + do += (bos * HV + i_hv) * V + dv += (bos * HV + i_hv) * V + dA += (bos * HV + i_hv) * BT + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + o_A = tl.arange(0, BT) + m_AT = (o_A[:, None] < BT) & m_t[None, :] + p_A = A + (bos * HV + i_hv) * BT + o_A[:, None] + o_t[None, :] * (HV * BT) + b_A = tl.load(p_A, mask=m_AT, other=0.0) + + m_A = (o_t[:, None] <= o_t[None, :]) & (m_t[:, None] & m_t) + b_A = tl.where(m_A, b_A, 0).to(do.dtype.element_ty) + + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + m_vT = m_v[:, None] & m_t[None, :] + m_tv = m_t[:, None] & m_v[None, :] + p_v = v + o_v[:, None] + o_t[None, :] * (HV * V) + p_do = do + o_t[:, None] * (HV * V) + o_v[None, :] + p_dv = dv + o_t[:, None] * (HV * V) + o_v[None, :] + # [BV, BT] + b_v = tl.load(p_v, mask=m_vT, other=0.0) + # [BT, BV] + b_do = tl.load(p_do, mask=m_tv, other=0.0) + # [BT, BT] + b_dA += tl.dot(b_do, b_v) + # [BT, BV] + b_dv = tl.dot(b_A.to(b_do.dtype), b_do) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), mask=m_tv) + + m_dA = m_t[:, None] & (o_A[None, :] < BT) + p_dA = dA + o_t[:, None] * (HV * BT) + o_A[None, :] + b_dA = tl.where(o_t[:, None] >= o_t, b_dA * scale, 0.0) + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), mask=m_dA) + + +@triton.heuristics( + { + "IS_VARLEN": lambda args: args["cu_seqlens"] is not None, + } +) +@fla_cache_autotune( + configs=[ + triton.Config({"BK": BK, "BV": BV}, num_warps=num_warps, num_stages=num_stages) + for BK in BK_LIST + for BV in BV_LIST + for num_warps in NUM_WARPS + for num_stages in [2, 3, 4] + if not (IS_NVIDIA_HOPPER and BK == 32 and num_warps == 4) + if not (IS_NVIDIA_SM100 and BK == 32 and num_warps != 2) + ], + key=["BT", "HV", "STATE_V_FIRST"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=["T"]) +def chunk_kda_bwd_kernel_wy_dqkg_fused( + q, + k, + v, + v_new, + g, + beta, + A, + h, + do, + dh, + dq, + dk, + dv, + dv2, + dg, + db, + dA, + cu_seqlens, + chunk_indices, + scale, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + STATE_V_FIRST: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + + if IS_VARLEN: + i_tg = i_t.to(tl.int64) + i_n, i_t = ( + tl.load(chunk_indices + i_t * 2).to(tl.int32), + tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64), + ) + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int64), + tl.load(cu_seqlens + i_n + 1).to(tl.int64), + ) + T = (eos - bos).to(tl.int32) + NT = tl.cdiv(T, BT) + else: + NT = tl.cdiv(T, BT) + i_tg = (i_b * NT + i_t).to(tl.int64) + bos, eos = (i_b * T).to(tl.int64), (i_b * T + T).to(tl.int64) + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + m_last = o_t == min(T, i_t * BT + BT) - 1 + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + v += (bos * HV + i_hv) * V + v_new += (bos * HV + i_hv) * V + g += (bos * HV + i_hv) * K + beta += bos * HV + i_hv + A += (bos * HV + i_hv) * BT + h += (i_tg * HV + i_hv) * K * V + do += (bos * HV + i_hv) * V + dh += (i_tg * HV + i_hv) * K * V + dq += (bos * HV + i_hv) * K + dk += (bos * HV + i_hv) * K + dv += (bos * HV + i_hv) * V + dv2 += (bos * HV + i_hv) * V + dg += (bos * HV + i_hv) * K + db += bos * HV + i_hv + dA += (bos * HV + i_hv) * BT + + p_beta = beta + o_t * HV + b_beta = tl.load(p_beta, mask=m_t, other=0.0) + + o_A = tl.arange(0, BT) + m_AT = (o_A[:, None] < BT) & m_t[None, :] + p_A = A + o_A[:, None] + o_t[None, :] * (HV * BT) + b_A = tl.load(p_A, mask=m_AT, other=0.0) + + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + b_db = tl.zeros([BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + m_tk = m_t[:, None] & m_k[None, :] + + p_k = k + o_t[:, None] * (H * K) + o_k[None, :] + p_g = g + o_t[:, None] * (HV * K) + o_k[None, :] + b_k = tl.load(p_k, mask=m_tk, other=0.0) + b_g = tl.load(p_g, mask=m_tk, other=0.0).to(tl.float32) + + p_gn = g + (min(T, i_t * BT + BT) - 1).to(tl.int64) * HV * K + o_k + b_gn = tl.load(p_gn, mask=m_k, other=0).to(tl.float32) + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + b_dw = tl.zeros([BT, BK], dtype=tl.float32) + b_dgk = tl.zeros([BK], dtype=tl.float32) + + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + m_tv = m_t[:, None] & (o_v[None, :] < V) + m_h = (o_v[:, None] < V) & m_k[None, :] + p_v_new = v_new + o_t[:, None] * (HV * V) + o_v[None, :] + p_do = do + o_t[:, None] * (HV * V) + o_v[None, :] + if STATE_V_FIRST: + p_h = h + o_v[:, None] * K + o_k[None, :] + p_dh = dh + o_v[:, None] * K + o_k[None, :] + else: + p_h = h + o_v[:, None] + o_k[None, :] * V + p_dh = dh + o_v[:, None] + o_k[None, :] * V + p_dv = dv + o_t[:, None] * (HV * V) + o_v[None, :] + # [BT, BV] + b_v_new = tl.load(p_v_new, mask=m_tv, other=0.0) + b_do = tl.load(p_do, mask=m_tv, other=0.0) + # [BV, BK] + b_h = tl.load(p_h, mask=m_h, other=0.0) + b_dh = tl.load(p_dh, mask=m_h, other=0.0) + # [BT, BV] + b_dv = tl.load(p_dv, mask=m_tv, other=0.0) + + b_dgk += tl.sum(b_h * b_dh, axis=0) + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + b_dk += tl.dot(b_v_new, b_dh.to(b_v_new.dtype)) + b_dw += tl.dot(b_dv.to(b_v_new.dtype), b_h.to(b_v_new.dtype)) + tl.debug_barrier() # DO NOT REMOVE THIS LINE! + if i_k == 0: + p_v = v + o_t[:, None] * (HV * V) + o_v[None, :] + p_dv2 = dv2 + o_t[:, None] * (HV * V) + o_v[None, :] + + b_v = tl.load(p_v, mask=m_tv, other=0.0) + + b_dA += tl.dot(b_dv, tl.trans(b_v)) + + b_dvb = tl.dot(b_A, b_dv) + b_dv2 = b_dvb * b_beta[:, None] + b_db += tl.sum(b_dvb * b_v, 1) + + tl.store(p_dv2, b_dv2.to(p_dv2.dtype.element_ty), mask=m_tv) + + b_gk_exp = exp2(b_g) + b_gb = b_gk_exp * b_beta[:, None] + b_dgk *= exp2(b_gn) + b_dq = b_dq * b_gk_exp * scale + b_dk = b_dk * tl.where(m_t[:, None], exp2(b_gn[None, :] - b_g), 0) + + b_kg = b_k * b_gk_exp + + b_dw = -b_dw.to(b_A.dtype) + b_dA += tl.dot(b_dw, tl.trans(b_kg.to(b_A.dtype))) + + b_dkgb = tl.dot(b_A, b_dw) + b_db += tl.sum(b_dkgb * b_kg, 1) + + p_q = q + o_t[:, None] * (H * K) + o_k[None, :] + b_q = tl.load(p_q, mask=m_tk, other=0.0) + b_kdk = b_k * b_dk + b_dgk += tl.sum(b_kdk, axis=0) + b_dg = ( + b_q * b_dq + - b_kdk + + m_last[:, None] * b_dgk + + b_kg * b_dkgb * b_beta[:, None] + ) + b_dk = b_dk + b_dkgb * b_gb + + p_dq = dq + o_t[:, None] * (HV * K) + o_k[None, :] + p_dk = dk + o_t[:, None] * (HV * K) + o_k[None, :] + p_dg = dg + o_t[:, None] * (HV * K) + o_k[None, :] + tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), mask=m_tk) + tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), mask=m_tk) + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), mask=m_tk) + + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_dA = tl.where(m_A, b_dA * b_beta[None, :], 0) + b_dA = tl.dot(b_dA.to(b_A.dtype), b_A) + b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) + b_dA = tl.where(m_A, -b_dA, 0) + + m_dA = m_t[:, None] & (o_A[None, :] < BT) + p_dA = dA + o_t[:, None] * (HV * BT) + o_A[None, :] + p_db = db + o_t * HV + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), mask=m_dA) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), mask=m_t) + + +@dispatch("kda") +def chunk_kda_bwd_dAv( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + do: torch.Tensor, + A: torch.Tensor | None = None, + scale: float = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, HV, V = *k.shape, do.shape[2], do.shape[-1] + BT = chunk_size + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + # H100 can have larger block size + if check_shared_mem("hopper", k.device.index): + CONST_TILING = 128 + elif check_shared_mem: + CONST_TILING = 64 + else: + CONST_TILING = 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + dA = v.new_empty(B, T, HV, BT, dtype=torch.float) + dv = torch.empty_like(do) + grid = (NT, B * HV) + chunk_kda_bwd_kernel_dAv[grid]( + q=q, + k=k, + v=v, + A=A, + do=do, + dv=dv, + dA=dA, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dA, dv + + +@dispatch("kda") +def chunk_kda_bwd_wy_dqkg_fused( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + v_new: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + h: torch.Tensor, + do: torch.Tensor, + dh: torch.Tensor, + dv: torch.Tensor, + scale: float | None = None, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, +): + B, T, H, K, HV, V = *k.shape, v.shape[2], v.shape[-1] + BT = chunk_size + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + # dq, dk are allocated at HV dimension; caller reduces to H if GVA + dq = g.new_empty(B, T, HV, K, dtype=torch.float) + dk = g.new_empty(B, T, HV, K, dtype=torch.float) + dv2 = torch.empty_like(v) + dg = torch.empty_like(g, dtype=torch.float) + db = torch.empty_like(beta, dtype=torch.float) + dA = torch.empty_like(A, dtype=torch.float) + + grid = (NT, B * HV) + chunk_kda_bwd_kernel_wy_dqkg_fused[grid]( + q=q, + k=k, + v=v, + v_new=v_new, + g=g, + beta=beta, + A=A, + h=h, + do=do, + dh=dh, + dq=dq, + dk=dk, + dv=dv, + dv2=dv2, + dg=dg, + db=db, + dA=dA, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BT=BT, + STATE_V_FIRST=state_v_first, + ) + dv = dv2 + return dq, dk, dv, db, dg, dA + + +def chunk_kda_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + Aqk: torch.Tensor, + Akk: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + g: torch.Tensor | None = None, + g_org: torch.Tensor | None = None, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + chunk_indices: torch.LongTensor | None = None, + chunk_size: int = 64, + safe_gate: bool = False, + lower_bound: float | None = None, + use_gate_in_kernel: bool = False, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + disable_recompute: bool = False, + cp_context: FLACPContext | None = None, + **kwargs, +): + H, HV = q.shape[2], v.shape[2] + G = HV // H + + if disable_recompute is False: + if use_gate_in_kernel: + g = kda_gate_chunk_cumsum( + g=g_org, + A_log=A_log, + dt_bias=dt_bias, + scale=RCP_LN2, + chunk_size=chunk_size, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + lower_bound=lower_bound, + ) + w, u, qg, kg = recompute_w_u_fwd( + q=q, + k=k, + v=v, + beta=beta, + A=Akk, + gk=g, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + ) + if cp_context is not None: + # Restore the full initial_state tensor from the compressed version. + # Only the first sequence's state is non-zero as it's the only one that could be cross-rank. + initial_state = expand_h0(initial_state, context=cp_context) + h, v_new, _ = chunk_gated_delta_rule_fwd_h( + k=kg, + w=w, + u=u, + gk=g, + initial_state=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + chunk_size=chunk_size, + state_v_first=state_v_first, + ) + else: + w, u, qg, kg, v_new, h = ( + kwargs["w"], + kwargs["u"], + kwargs["qg"], + kwargs["kg"], + kwargs["v_new"], + kwargs["h"], + ) + if cp_context is not None: + # Restore the full initial_state tensor from the compressed version. + # Only the first sequence's state is non-zero as it's the only one that could be cross-rank. + initial_state = expand_h0(initial_state, context=cp_context) + + # dAqk = do @ v.T + # dv = A @ do + dAqk, dv = chunk_kda_bwd_dAv( + q=q, + k=k, + v=v_new, + do=do, + A=Aqk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + ) + + if cp_context is not None: + # initial_state is None in the CP mode + # We only need to compute dht of current rank and pass it to the backward kernel + dht, initial_state = chunk_gated_delta_rule_bwd_dhu_pre_process( + q=qg, + k=kg, + w=w, + do=do, + dv=dv, + gk=g, + scale=scale, + cu_seqlens=cu_seqlens, + dht=dht, + initial_state=initial_state, + context=cp_context, + chunk_size=chunk_size, + state_v_first=state_v_first, + ) + + dh, dh0, dv = chunk_gated_delta_rule_bwd_dhu( + q=qg, + k=kg, + w=w, + gk=g, + h0=initial_state, + dht=dht, + do=do, + dv=dv, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=state_v_first, + ) + + dq, dk, dv, db, dg, dAkk = chunk_kda_bwd_wy_dqkg_fused( + q=q, + k=k, + v=v, + v_new=v_new, + g=g, + beta=beta, + A=Akk, + h=h, + do=do, + dh=dh, + dv=dv, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=state_v_first, + ) + + dq, dk, db, dg = chunk_kda_bwd_intra( + q=q, + k=k, + g=g, + beta=beta, + dAqk=dAqk, + dAkk=dAkk, + dq=dq, + dk=dk, + db=db, + dg=dg, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + safe_gate=safe_gate, + ) + + # For GVA, reduce dq and dk from [B, T, HV, K] back to [B, T, H, K] + if HV > H: + dq = dq.view(*dq.shape[:2], H, G, dq.shape[-1]).sum(dim=3) + dk = dk.view(*dk.shape[:2], H, G, dk.shape[-1]).sum(dim=3) + + dA, dbias = None, None + dg = chunk_local_cumsum( + dg, + chunk_size=chunk_size, + reverse=True, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + ) + if use_gate_in_kernel: + dg, dA, dbias = kda_gate_bwd( + g=g_org, A_log=A_log, dt_bias=dt_bias, dyg=dg, lower_bound=lower_bound + ) + + return dq, dk, dv, db, dg, dh0, dA, dbias diff --git a/kda/_fla/ops/kda/chunk_fwd.py b/kda/_fla/ops/kda/chunk_fwd.py new file mode 100644 index 0000000..10a4b8e --- /dev/null +++ b/kda/_fla/ops/kda/chunk_fwd.py @@ -0,0 +1,134 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch + +from kda._fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_fwd_h +from kda._fla.ops.cp import FLACPContext +from kda._fla.ops.cp.chunk_delta_h import chunk_gated_delta_rule_fwd_h_pre_process, compress_h0 +from kda._fla.ops.gla.chunk import chunk_gla_fwd_o_gk +from kda._fla.ops.kda.chunk_intra import chunk_kda_fwd_intra +from kda._fla.ops.kda.gate import kda_gate_chunk_cumsum +from kda._fla.ops.utils import chunk_local_cumsum +from kda._fla.ops.utils.constant import RCP_LN2 + + +def chunk_kda_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + cu_seqlens_cpu: torch.LongTensor | None = None, + chunk_indices: torch.LongTensor | None = None, + chunk_size: int = 64, + safe_gate: bool = False, + lower_bound: float | None = None, + use_gate_in_kernel: bool = False, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + disable_recompute: bool = False, + return_intermediate_states: bool = False, + cp_context: FLACPContext | None = None, +): + # Apply gate activation + g_org = None + if use_gate_in_kernel: + g_org = g + g = kda_gate_chunk_cumsum( + g=g_org, + A_log=A_log, + dt_bias=dt_bias, + scale=RCP_LN2, + chunk_size=chunk_size, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + lower_bound=lower_bound, + ) + else: + g = chunk_local_cumsum( + g=g, + scale=RCP_LN2, + chunk_size=chunk_size, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices + ) + + # qg = None if disable_recompute is False + w, u, qg, kg, Aqk, Akk = chunk_kda_fwd_intra( + q=q, + k=k, + v=v, + gk=g, + beta=beta, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + safe_gate=safe_gate, + disable_recompute=disable_recompute + ) + + if cp_context is not None: + initial_state = chunk_gated_delta_rule_fwd_h_pre_process( + k=kg, + w=w, + u=u, + gk=g, + cu_seqlens=cu_seqlens, + initial_state=initial_state, + context=cp_context, + chunk_size=chunk_size, + state_v_first=state_v_first, + ) + + h, v_new, final_state = chunk_gated_delta_rule_fwd_h( + k=kg, + w=w, + u=u, + gk=g, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + cu_seqlens_cpu=cu_seqlens_cpu, + chunk_indices=chunk_indices, + chunk_size=chunk_size, + state_v_first=state_v_first, + ) + + if cp_context is not None: + # In Context Parallel (CP) mode, global initial states are not supported at the entry point. + # The `initial_state` here is computed internally via inter-rank communication. + # Since only the first sequence in the local batch can be a continuation of a cross-rank sequence, + # only the first state in the tensor is relevant. We compress it to optimize memory for `save_for_backward`. + initial_state = compress_h0(initial_state, context=cp_context) + + o = chunk_gla_fwd_o_gk( + q=q, + v=v_new, + g=g, + A=Aqk, + h=h, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=chunk_size, + chunk_indices=chunk_indices, + state_v_first=state_v_first, + ) + if disable_recompute is False: + # Delete to save memory + w, u, qg, kg, v_new = None, None, None, None, None + if not return_intermediate_states: + h = None + if use_gate_in_kernel: + g = None + return o, final_state, g, Aqk, Akk, w, u, qg, kg, v_new, h, initial_state diff --git a/kda/_fla/ops/kda/chunk_intra.py b/kda/_fla/ops/kda/chunk_intra.py new file mode 100644 index 0000000..e691aee --- /dev/null +++ b/kda/_fla/ops/kda/chunk_intra.py @@ -0,0 +1,962 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.kda.chunk_intra_token_parallel import chunk_kda_fwd_intra_token_parallel +from kda._fla.ops.kda.wy_fast import recompute_w_u_fwd +from kda._fla.ops.utils import prepare_chunk_indices +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.op import exp2, gather +from kda._fla.utils import IS_GATHER_SUPPORTED, IS_TF32_SUPPORTED, autotune_cache_kwargs + +if IS_TF32_SUPPORTED: + SOLVE_TRIL_DOT_PRECISION = tl.constexpr('tf32') +else: + SOLVE_TRIL_DOT_PRECISION = tl.constexpr('ieee') + +################################################################################ +# Fused inter + solve_tril kernel: compute off-diagonal Akk and solve in one pass +################################################################################ + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps) + for BK in [32, 64] + for num_warps in [1, 2, 4] + ], + key=["H", "HV", "K", "BT", "BC", "NC"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_kda_fwd_kernel_inter_solve_fused( + q, + k, + g, + beta, + Aqk, + Akkd, + Akk, + scale, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + NC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_SAFE_GATE: tl.constexpr, +): + """ + Fused kernel: compute inter-subchunk Akk + solve_tril in one pass. + Prerequisite: token_parallel has already computed diagonal Akk blocks in Akkd. + + This kernel: + 1. Computes off-diagonal Aqk blocks -> writes to global + 2. Computes off-diagonal Akk blocks -> keeps in registers + 3. Loads diagonal Akk blocks from Akkd (fp32) + 4. Does forward substitution on diagonals + 5. Computes merged Akk_inv + 6. Writes Akk_inv to Akk + """ + i_t, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + if i_t * BT >= T: + return + + i_tc0 = i_t * BT + i_tc1 = i_t * BT + BC + i_tc2 = i_t * BT + 2 * BC + i_tc3 = i_t * BT + 3 * BC + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + g += (bos * HV + i_hv) * K + Aqk += (bos * HV + i_hv) * BT + Akk += (bos * HV + i_hv) * BT + Akkd += (bos * HV + i_hv) * BC + + o_i = tl.arange(0, BC) + m_tc1 = (i_tc1 + o_i) < T + m_tc2 = (i_tc2 + o_i) < T + m_tc3 = (i_tc3 + o_i) < T + o_c0 = i_tc0 + o_i + o_c1 = i_tc1 + o_i + o_c2 = i_tc2 + o_i + o_c3 = i_tc3 + o_i + m_tc0 = o_c0 < T + m_A0 = m_tc0[:, None] & (o_i[None, :] < BT) + m_A1 = m_tc1[:, None] & (o_i[None, :] < BT) + m_A2 = m_tc2[:, None] & (o_i[None, :] < BT) + m_A3 = m_tc3[:, None] & (o_i[None, :] < BT) + + b_Aqk10 = tl.zeros([BC, BC], dtype=tl.float32) + b_Akk10 = tl.zeros([BC, BC], dtype=tl.float32) + + b_Aqk20 = tl.zeros([BC, BC], dtype=tl.float32) + b_Akk20 = tl.zeros([BC, BC], dtype=tl.float32) + b_Aqk21 = tl.zeros([BC, BC], dtype=tl.float32) + b_Akk21 = tl.zeros([BC, BC], dtype=tl.float32) + + b_Aqk30 = tl.zeros([BC, BC], dtype=tl.float32) + b_Akk30 = tl.zeros([BC, BC], dtype=tl.float32) + b_Aqk31 = tl.zeros([BC, BC], dtype=tl.float32) + b_Akk31 = tl.zeros([BC, BC], dtype=tl.float32) + b_Aqk32 = tl.zeros([BC, BC], dtype=tl.float32) + b_Akk32 = tl.zeros([BC, BC], dtype=tl.float32) + + ################################################################################ + # off-diagonal blocks + ################################################################################ + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + m_ck0 = m_tc0[:, None] & m_k[None, :] + p_k0 = k + o_c0[:, None] * (H*K) + o_k[None, :] + p_g0 = g + o_c0[:, None] * (HV*K) + o_k[None, :] + b_k0 = tl.load(p_k0, mask=m_ck0, other=0.0).to(tl.float32) + b_g0 = tl.load(p_g0, mask=m_ck0, other=0.0).to(tl.float32) + + if i_tc1 < T: + m_ck1 = m_tc1[:, None] & m_k[None, :] + p_q1 = q + o_c1[:, None] * (H*K) + o_k[None, :] + p_k1 = k + o_c1[:, None] * (H*K) + o_k[None, :] + p_g1 = g + o_c1[:, None] * (HV*K) + o_k[None, :] + # [BC, BK] + b_q1 = tl.load(p_q1, mask=m_ck1, other=0.0).to(tl.float32) + b_k1 = tl.load(p_k1, mask=m_ck1, other=0.0).to(tl.float32) + b_g1 = tl.load(p_g1, mask=m_ck1, other=0.0).to(tl.float32) + # [BK] + b_gn1 = tl.load(g + i_tc1 * HV*K + o_k, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + b_gqn = tl.where(m_tc1[:, None], exp2(b_g1 - b_gn1[None, :]), 0) + # [BK, BC] + b_kgt = tl.trans(b_k0 * exp2(b_gn1[None, :] - b_g0)) + # [BC, BC] + b_Aqk10 += tl.dot(b_q1 * b_gqn, b_kgt) + b_Akk10 += tl.dot(b_k1 * b_gqn, b_kgt) + + if NC >= 3 and i_tc2 < T: + m_ck2 = m_tc2[:, None] & m_k[None, :] + p_q2 = q + o_c2[:, None] * (H*K) + o_k[None, :] + p_k2 = k + o_c2[:, None] * (H*K) + o_k[None, :] + p_g2 = g + o_c2[:, None] * (HV*K) + o_k[None, :] + # [BC, BK] + b_q2 = tl.load(p_q2, mask=m_ck2, other=0.0).to(tl.float32) + b_k2 = tl.load(p_k2, mask=m_ck2, other=0.0).to(tl.float32) + b_g2 = tl.load(p_g2, mask=m_ck2, other=0.0).to(tl.float32) + # [BK] + b_gn2 = tl.load(g + i_tc2 * HV*K + o_k, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + b_gqn2 = tl.where(m_tc2[:, None], exp2(b_g2 - b_gn2[None, :]), 0) + b_qg2 = b_q2 * b_gqn2 + b_kg2 = b_k2 * b_gqn2 + # [BK, BC] + b_kgt = tl.trans(b_k0 * exp2(b_gn2[None, :] - b_g0)) + b_Aqk20 += tl.dot(b_qg2, b_kgt) + b_Akk20 += tl.dot(b_kg2, b_kgt) + # [BC, BC] + b_kgt = tl.trans(b_k1 * exp2(b_gn2[None, :] - b_g1)) + # [BC, BC] + b_Aqk21 += tl.dot(b_qg2, b_kgt) + b_Akk21 += tl.dot(b_kg2, b_kgt) + + if NC >= 4 and i_tc3 < T: + m_ck3 = m_tc3[:, None] & m_k[None, :] + p_q3 = q + o_c3[:, None] * (H*K) + o_k[None, :] + p_k3 = k + o_c3[:, None] * (H*K) + o_k[None, :] + p_g3 = g + o_c3[:, None] * (HV*K) + o_k[None, :] + # [BC, BK] + b_q3 = tl.load(p_q3, mask=m_ck3, other=0.0).to(tl.float32) + b_k3 = tl.load(p_k3, mask=m_ck3, other=0.0).to(tl.float32) + b_g3 = tl.load(p_g3, mask=m_ck3, other=0.0).to(tl.float32) + # [BK] + b_gn3 = tl.load(g + i_tc3 * HV*K + o_k, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + b_gqn3 = tl.where(m_tc3[:, None], exp2(b_g3 - b_gn3[None, :]), 0) + b_qg3 = b_q3 * b_gqn3 + b_kg3 = b_k3 * b_gqn3 + # [BK, BC] + b_kgt = tl.trans(b_k0 * exp2(b_gn3[None, :] - b_g0)) + # [BC, BC] + b_Aqk30 += tl.dot(b_qg3, b_kgt) + b_Akk30 += tl.dot(b_kg3, b_kgt) + # [BK, BC] + b_kgt = tl.trans(b_k1 * exp2(b_gn3[None, :] - b_g1)) + # [BC, BC] + b_Aqk31 += tl.dot(b_qg3, b_kgt) + b_Akk31 += tl.dot(b_kg3, b_kgt) + # [BK, BC] + b_kgt = tl.trans(b_k2 * exp2(b_gn3[None, :] - b_g2)) + # [BC, BC] + b_Aqk32 += tl.dot(b_qg3, b_kgt) + b_Akk32 += tl.dot(b_kg3, b_kgt) + + ################################################################################ + # save off-diagonal Aqk blocks and prepare Akk + ################################################################################ + if i_tc1 < T: + p_Aqk10 = Aqk + o_c1[:, None] * (HV*BT) + o_i[None, :] + tl.store(p_Aqk10, (b_Aqk10 * scale).to(Aqk.dtype.element_ty), mask=m_A1) + + p_b1 = beta + bos * HV + i_hv + o_c1 * HV + b_b1 = tl.load(p_b1, mask=m_tc1, other=0.0).to(tl.float32) + b_Akk10 = b_Akk10 * b_b1[:, None] + if NC >= 3 and i_tc2 < T: + p_Aqk20 = Aqk + o_c2[:, None] * (HV*BT) + o_i[None, :] + p_Aqk21 = Aqk + o_c2[:, None] * (HV*BT) + (o_i + BC)[None, :] + tl.store(p_Aqk20, (b_Aqk20 * scale).to(Aqk.dtype.element_ty), mask=m_A2) + tl.store(p_Aqk21, (b_Aqk21 * scale).to(Aqk.dtype.element_ty), mask=m_A2) + + p_b2 = beta + bos * HV + i_hv + o_c2 * HV + b_b2 = tl.load(p_b2, mask=m_tc2, other=0.0).to(tl.float32) + b_Akk20 = b_Akk20 * b_b2[:, None] + b_Akk21 = b_Akk21 * b_b2[:, None] + if NC >= 4 and i_tc3 < T: + p_Aqk30 = Aqk + o_c3[:, None] * (HV*BT) + o_i[None, :] + p_Aqk31 = Aqk + o_c3[:, None] * (HV*BT) + (o_i + BC)[None, :] + p_Aqk32 = Aqk + o_c3[:, None] * (HV*BT) + (o_i + 2*BC)[None, :] + tl.store(p_Aqk30, (b_Aqk30 * scale).to(Aqk.dtype.element_ty), mask=m_A3) + tl.store(p_Aqk31, (b_Aqk31 * scale).to(Aqk.dtype.element_ty), mask=m_A3) + tl.store(p_Aqk32, (b_Aqk32 * scale).to(Aqk.dtype.element_ty), mask=m_A3) + + p_b3 = beta + bos * HV + i_hv + o_c3 * HV + b_b3 = tl.load(p_b3, mask=m_tc3, other=0.0).to(tl.float32) + b_Akk30 = b_Akk30 * b_b3[:, None] + b_Akk31 = b_Akk31 * b_b3[:, None] + b_Akk32 = b_Akk32 * b_b3[:, None] + + p_Akk00 = Akkd + o_c0[:, None] * (HV*BC) + o_i[None, :] + p_Akk11 = Akkd + o_c1[:, None] * (HV*BC) + o_i[None, :] + b_Ai00 = tl.load(p_Akk00, mask=m_A0, other=0.0).to(tl.float32) + b_Ai11 = tl.load(p_Akk11, mask=m_A1, other=0.0).to(tl.float32) + if NC >= 3: + p_Akk22 = Akkd + o_c2[:, None] * (HV*BC) + o_i[None, :] + b_Ai22 = tl.load(p_Akk22, mask=m_A2, other=0.0).to(tl.float32) + if NC >= 4: + p_Akk33 = Akkd + o_c3[:, None] * (HV*BC) + o_i[None, :] + b_Ai33 = tl.load(p_Akk33, mask=m_A3, other=0.0).to(tl.float32) + + ################################################################################ + # forward substitution on diagonals + ################################################################################ + + if not USE_SAFE_GATE: + m_A = o_i[:, None] > o_i[None, :] + m_I = o_i[:, None] == o_i[None, :] + + b_Ai00 = -tl.where(m_A, b_Ai00, 0) + b_Ai11 = -tl.where(m_A, b_Ai11, 0) + if NC >= 3: + b_Ai22 = -tl.where(m_A, b_Ai22, 0) + if NC >= 4: + b_Ai33 = -tl.where(m_A, b_Ai33, 0) + + for i in range(2, min(BC, T - i_tc0)): + b_a00 = -tl.load(Akkd + (i_tc0 + i) * HV*BC + o_i) + b_a00 = tl.where(o_i < i, b_a00, 0.) + b_a00 += tl.sum(b_a00[:, None] * b_Ai00, 0) + b_Ai00 = tl.where((o_i == i)[:, None], b_a00, b_Ai00) + for i in range(BC + 2, min(2*BC, T - i_tc0)): + b_a11 = -tl.load(Akkd + (i_tc0 + i) * HV*BC + o_i) + b_a11 = tl.where(o_i < i - BC, b_a11, 0.) + b_a11 += tl.sum(b_a11[:, None] * b_Ai11, 0) + b_Ai11 = tl.where((o_i == i - BC)[:, None], b_a11, b_Ai11) + if NC >= 3: + for i in range(2*BC + 2, min(3*BC, T - i_tc0)): + b_a22 = -tl.load(Akkd + (i_tc0 + i) * HV*BC + o_i) + b_a22 = tl.where(o_i < i - 2*BC, b_a22, 0.) + b_a22 += tl.sum(b_a22[:, None] * b_Ai22, 0) + b_Ai22 = tl.where((o_i == i - 2*BC)[:, None], b_a22, b_Ai22) + if NC >= 4: + for i in range(3*BC + 2, min(4*BC, T - i_tc0)): + b_a33 = -tl.load(Akkd + (i_tc0 + i) * HV*BC + o_i) + b_a33 = tl.where(o_i < i - 3*BC, b_a33, 0.) + b_a33 += tl.sum(b_a33[:, None] * b_Ai33, 0) + b_Ai33 = tl.where((o_i == i - 3*BC)[:, None], b_a33, b_Ai33) + + b_Ai00 += m_I + b_Ai11 += m_I + if NC >= 3: + b_Ai22 += m_I + if NC >= 4: + b_Ai33 += m_I + + ################################################################################ + # compute merged inverse using off-diagonals + ################################################################################ + + # we used tf32 to maintain matrix inverse's precision whenever possible. + b_Ai10 = -tl.dot( + tl.dot(b_Ai11, b_Akk10, input_precision=SOLVE_TRIL_DOT_PRECISION), + b_Ai00, + input_precision=SOLVE_TRIL_DOT_PRECISION + ) + + if NC >= 3: + b_Ai21 = -tl.dot( + tl.dot(b_Ai22, b_Akk21, input_precision=SOLVE_TRIL_DOT_PRECISION), + b_Ai11, + input_precision=SOLVE_TRIL_DOT_PRECISION + ) + b_Ai20 = -tl.dot( + b_Ai22, + tl.dot(b_Akk20, b_Ai00, input_precision=SOLVE_TRIL_DOT_PRECISION) + + tl.dot(b_Akk21, b_Ai10, input_precision=SOLVE_TRIL_DOT_PRECISION), + input_precision=SOLVE_TRIL_DOT_PRECISION + ) + if NC >= 4: + b_Ai32 = -tl.dot( + tl.dot(b_Ai33, b_Akk32, input_precision=SOLVE_TRIL_DOT_PRECISION), + b_Ai22, + input_precision=SOLVE_TRIL_DOT_PRECISION + ) + b_Ai31 = -tl.dot( + b_Ai33, + tl.dot(b_Akk31, b_Ai11, input_precision=SOLVE_TRIL_DOT_PRECISION) + + tl.dot(b_Akk32, b_Ai21, input_precision=SOLVE_TRIL_DOT_PRECISION), + input_precision=SOLVE_TRIL_DOT_PRECISION + ) + b_Ai30 = -tl.dot( + b_Ai33, + tl.dot(b_Akk30, b_Ai00, input_precision=SOLVE_TRIL_DOT_PRECISION) + + tl.dot(b_Akk31, b_Ai10, input_precision=SOLVE_TRIL_DOT_PRECISION) + + tl.dot(b_Akk32, b_Ai20, input_precision=SOLVE_TRIL_DOT_PRECISION), + input_precision=SOLVE_TRIL_DOT_PRECISION + ) + + ################################################################################ + # store full Akk_inv to Akk + ################################################################################ + + p_Akk00 = Akk + o_c0[:, None] * (HV*BT) + o_i[None, :] + p_Akk10 = Akk + o_c1[:, None] * (HV*BT) + o_i[None, :] + p_Akk11 = Akk + o_c1[:, None] * (HV*BT) + (o_i + BC)[None, :] + + tl.store(p_Akk00, b_Ai00.to(Akk.dtype.element_ty), mask=m_A0) + tl.store(p_Akk10, b_Ai10.to(Akk.dtype.element_ty), mask=m_A1) + tl.store(p_Akk11, b_Ai11.to(Akk.dtype.element_ty), mask=m_A1) + if NC >= 3: + p_Akk20 = Akk + o_c2[:, None] * (HV*BT) + o_i[None, :] + p_Akk21 = Akk + o_c2[:, None] * (HV*BT) + (o_i + BC)[None, :] + p_Akk22 = Akk + o_c2[:, None] * (HV*BT) + (o_i + 2*BC)[None, :] + tl.store(p_Akk20, b_Ai20.to(Akk.dtype.element_ty), mask=m_A2) + tl.store(p_Akk21, b_Ai21.to(Akk.dtype.element_ty), mask=m_A2) + tl.store(p_Akk22, b_Ai22.to(Akk.dtype.element_ty), mask=m_A2) + if NC >= 4: + p_Akk30 = Akk + o_c3[:, None] * (HV*BT) + o_i[None, :] + p_Akk31 = Akk + o_c3[:, None] * (HV*BT) + (o_i + BC)[None, :] + p_Akk32 = Akk + o_c3[:, None] * (HV*BT) + (o_i + 2*BC)[None, :] + p_Akk33 = Akk + o_c3[:, None] * (HV*BT) + (o_i + 3*BC)[None, :] + tl.store(p_Akk30, b_Ai30.to(Akk.dtype.element_ty), mask=m_A3) + tl.store(p_Akk31, b_Ai31.to(Akk.dtype.element_ty), mask=m_A3) + tl.store(p_Akk32, b_Ai32.to(Akk.dtype.element_ty), mask=m_A3) + tl.store(p_Akk33, b_Ai33.to(Akk.dtype.element_ty), mask=m_A3) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BK', 'NC', 'BT', 'HV'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['B', 'T']) +def chunk_kda_bwd_kernel_intra( + q, + k, + g, + beta, + dAqk, + dAkk, + dq, + dq2, + dk, + dk2, + dg, + dg2, + db, + cu_seqlens, + chunk_indices, + B, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, + SAFE_GATE: tl.constexpr, + USE_GATHER: tl.constexpr, +): + i_kc, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + i_k, i_i = i_kc // NC, i_kc % NC + + all = B * T + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + + i_ti = i_t * BT + i_i * BC + if i_ti >= T: + return + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + q += (bos * H + i_h) * K + k += (bos * H + i_h) * K + g += (bos * HV + i_hv) * K + beta += bos * HV + i_hv + + dAqk += (bos * HV + i_hv) * BT + dAkk += (bos * HV + i_hv) * BT + dq += (bos * HV + i_hv) * K + dq2 += (bos * HV + i_hv) * K + dk += (bos * HV + i_hv) * K + dk2 += (bos * HV + i_hv) * K + dg += (bos * HV + i_hv) * K + dg2 += (bos * HV + i_hv) * K + db += (i_k * all + bos) * HV + i_hv + + o_i = tl.arange(0, BC) + o_c = i_ti + o_i + m_c = o_c < T + m_ck = m_c[:, None] & m_k[None, :] + m_dAf = m_c[:, None] & (o_i[None, :] < BT) + m_dAt = (o_i[:, None] < BT) & m_c[None, :] + p_g = g + o_c[:, None] * (HV*K) + o_k[None, :] + b_g = tl.load(p_g, mask=m_ck, other=0.0).to(tl.float32) + + p_b = beta + o_c * HV + b_b = tl.load(p_b, mask=m_c, other=0.0) + + b_dq2 = tl.zeros([BC, BK], dtype=tl.float32) + b_dk2 = tl.zeros([BC, BK], dtype=tl.float32) + if i_i > 0: + p_gn = g + i_ti * HV*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0).to(tl.float32)[None, :] + for i_j in range(0, i_i): + o_j = i_t * BT + i_j * BC + o_i + m_jk = (o_j < T)[:, None] & m_k[None, :] + p_k = k + o_j[:, None] * (H*K) + o_k[None, :] + p_gk = g + o_j[:, None] * (HV*K) + o_k[None, :] + p_dAqk = dAqk + o_c[:, None] * (HV*BT) + (i_j * BC + o_i)[None, :] + p_dAkk = dAkk + o_c[:, None] * (HV*BT) + (i_j * BC + o_i)[None, :] + # [BC, BK] + b_k = tl.load(p_k, mask=m_jk, other=0.0) + b_gk = tl.load(p_gk, mask=m_jk, other=0.0) + b_kg = b_k * exp2(b_gn - b_gk) + # [BC, BC] + b_dAqk = tl.load(p_dAqk, mask=m_dAf, other=0.0) + b_dAkk = tl.load(p_dAkk, mask=m_dAf, other=0.0) + # [BC, BK] + b_dq2 += tl.dot(b_dAqk, b_kg) + b_dk2 += tl.dot(b_dAkk, b_kg) + b_gqn = exp2(b_g - b_gn) + b_dq2 *= b_gqn + b_dk2 *= b_gqn + + o_i = tl.arange(0, BC) + m_dA = (i_ti + o_i) < T + o_dA = (i_ti + o_i) * HV*BT + i_i * BC + p_kj = k + i_ti * H*K + o_k + p_gkj = g + i_ti * HV*K + o_k + + p_q = q + o_c[:, None] * (H*K) + o_k[None, :] + p_k = k + o_c[:, None] * (H*K) + o_k[None, :] + b_q = tl.load(p_q, mask=m_ck, other=0.0) + b_k = tl.load(p_k, mask=m_ck, other=0.0) + + if SAFE_GATE: + if USE_GATHER: + b_gn = gather(b_g, tl.full([1, BK], min(BC//2, T - i_ti - 1), dtype=tl.int16), axis=0) + else: + p_gn = g + (i_ti + min(BC // 2, T - i_ti - 1)) * HV*K + o_k + b_gn = tl.load(p_gn, mask=m_k, other=0)[None, :] + + p_dAqk = dAqk + o_c[:, None] * (HV*BT) + (i_i * BC + o_i)[None, :] + p_dAkk = dAkk + o_c[:, None] * (HV*BT) + (i_i * BC + o_i)[None, :] + b_dAqk_diag_qk = tl.load(p_dAqk, mask=m_dAf, other=0.0).to(tl.float32) + b_dAkk_diag_qk = tl.load(p_dAkk, mask=m_dAf, other=0.0).to(tl.float32) + + m_i_diag_qk = (o_i[:, None] >= o_i[None, :]) & ((i_ti + o_i[:, None]) < T) & ((i_ti + o_i[None, :]) < T) + m_j_diag_qk = (i_ti + o_i[:, None]) < T + + b_dAqk_diag_qk = tl.where(m_i_diag_qk, b_dAqk_diag_qk, 0.) + b_dAkk_diag_qk = tl.where(m_i_diag_qk, b_dAkk_diag_qk, 0.) + b_g_diag_qk = tl.where(m_j_diag_qk, b_g - b_gn, 0.) + exp_b_g_diag_qk = tl.where(m_j_diag_qk, exp2(b_g_diag_qk), 0.) + exp_neg_b_g_diag_qk = tl.where(m_j_diag_qk, exp2(-b_g_diag_qk), 0.) + + b_k_exp_diag_qk = b_k * exp_neg_b_g_diag_qk + b_dq2 += tl.dot(b_dAqk_diag_qk, b_k_exp_diag_qk) * exp_b_g_diag_qk + b_dk2 += tl.dot(b_dAkk_diag_qk, b_k_exp_diag_qk) * exp_b_g_diag_qk + else: + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC] + b_dAqk = tl.load(dAqk + o_dA + j, mask=m_dA, other=0) + b_dAkk = tl.load(dAkk + o_dA + j, mask=m_dA, other=0) + # [BK] + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] >= j + # [BC, BK] + b_gqk = exp2(b_g - b_gkj[None, :]) + b_dq2 += tl.where(m_i, b_dAqk[:, None] * b_kj[None, :] * b_gqk, 0.) + b_dk2 += tl.where(m_i, b_dAkk[:, None] * b_kj[None, :] * b_gqk, 0.) + + p_kj += H*K + p_gkj += HV*K + + b_db = tl.sum(b_dk2 * b_k, 1) + b_dk2 *= b_b[:, None] + + p_dq = dq + o_c[:, None] * (HV*K) + o_k[None, :] + p_dq2 = dq2 + o_c[:, None] * (HV*K) + o_k[None, :] + p_db = db + o_c * HV + + b_dg2 = b_q * b_dq2 + b_dq2 = b_dq2 + tl.load(p_dq, mask=m_ck, other=0.0) + tl.store(p_dq2, b_dq2.to(p_dq2.dtype.element_ty), mask=m_ck) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), mask=m_c) + + tl.debug_barrier() + b_dkt = tl.zeros([BC, BK], dtype=tl.float32) + + NC = min(NC, tl.cdiv(T - i_t * BT, BC)) + if i_i < NC - 1: + p_gn = g + (min(i_ti + BC, T) - 1) * HV*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0).to(tl.float32)[None, :] + for i_j in range(i_i + 1, NC): + o_j = i_t * BT + i_j * BC + o_i + m_j = o_j < T + m_jk = m_j[:, None] & m_k[None, :] + m_dAj = (o_i[:, None] < BT) & m_j[None, :] + p_q = q + o_j[:, None] * (H*K) + o_k[None, :] + p_k = k + o_j[:, None] * (H*K) + o_k[None, :] + p_gk = g + o_j[:, None] * (HV*K) + o_k[None, :] + p_b = beta + o_j * HV + p_dAqk = dAqk + (i_i * BC + o_i)[:, None] + o_j[None, :] * (HV*BT) + p_dAkk = dAkk + (i_i * BC + o_i)[:, None] + o_j[None, :] * (HV*BT) + # [BC] + b_b = tl.load(p_b, mask=m_j, other=0.0) + # [BC, BK] + b_q = tl.load(p_q, mask=m_jk, other=0.0) + b_kb = tl.load(p_k, mask=m_jk, other=0.0) * b_b[:, None] + b_gk = tl.load(p_gk, mask=m_jk, other=0.0).to(tl.float32) + # [BC, BC] + b_dAqk = tl.load(p_dAqk, mask=m_dAj, other=0.0) + b_dAkk = tl.load(p_dAkk, mask=m_dAj, other=0.0) + + # [BC, BK] + b_gkn = exp2(b_gk - b_gn) + b_qg = b_q * tl.where(m_j[:, None], b_gkn, 0) + b_kbg = b_kb * tl.where(m_j[:, None], b_gkn, 0) + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dkt += tl.dot(b_dAqk, b_qg) + b_dkt += tl.dot(b_dAkk, b_kbg) + b_dkt *= exp2(b_gn - b_g) + o_dA = i_ti * HV*BT + i_i * BC + o_i + p_qj = q + i_ti * H*K + o_k + p_kj = k + i_ti * H*K + o_k + p_gkj = g + i_ti * HV*K + o_k + p_bj = beta + i_ti * HV + + if SAFE_GATE: + if USE_GATHER: + b_gn = gather(b_g, tl.full([1, BK], min(BC//2, T - i_ti - 1), dtype=tl.int16), axis=0) + else: + p_gn = g + (i_ti + min(BC // 2, T - i_ti - 1)) * HV*K + o_k + b_gn = tl.load(p_gn, mask=m_k, other=0).to(tl.float32)[None, :] + p_q = q + o_c[:, None] * (H*K) + o_k[None, :] + b_q = tl.load(p_q, mask=m_ck, other=0.0) + p_b = beta + o_c * HV + b_b = tl.load(p_b, mask=m_c, other=0.0) + + p_dAqk = dAqk + (i_i * BC + o_i)[:, None] + o_c[None, :] * (HV*BT) + p_dAkk = dAkk + (i_i * BC + o_i)[:, None] + o_c[None, :] * (HV*BT) + b_dAqk_diag_kk = tl.load(p_dAqk, mask=m_dAt, other=0.0).to(tl.float32) + b_dAkk_diag_kk = tl.load(p_dAkk, mask=m_dAt, other=0.0).to(tl.float32) + + m_i_diag_kk = (o_i[:, None] <= o_i[None, :]) & ((i_ti + o_i[:, None]) < T) & ((i_ti + o_i[None, :]) < T) + m_j_diag_kk = (i_ti + o_i[:, None]) < T + + b_dAqk_diag_kk = tl.where(m_i_diag_kk, b_dAqk_diag_kk, 0.) + b_dAkk_diag_kk = tl.where(m_i_diag_kk, b_dAkk_diag_kk, 0.) + # ensure numerical stability + b_g_diag_kk = tl.where(m_j_diag_kk, b_g - b_gn, 0.) + exp_b_g_diag_kk = tl.where(m_j_diag_kk, exp2(b_g_diag_kk), 0.) + exp_neg_b_g_diag_kk = tl.where(m_j_diag_kk, exp2(-b_g_diag_kk), 0.) + + b_q_exp = b_q * exp_b_g_diag_kk + b_kb_exp = b_k * b_b[:, None] * exp_b_g_diag_kk + + b_dkt += tl.dot(b_dAqk_diag_kk, b_q_exp) * exp_neg_b_g_diag_kk + b_dkt += tl.dot(b_dAkk_diag_kk, b_kb_exp) * exp_neg_b_g_diag_kk + else: + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dAqk = tl.load(dAqk + o_dA + j * HV*BT) + b_dAkk = tl.load(dAkk + o_dA + j * HV*BT) + # [BK,] + b_qj = tl.load(p_qj, mask=m_k, other=0).to(tl.float32) + b_kbj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) * tl.load(p_bj) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] <= j + b_gkq = exp2(b_gkj[None, :] - b_g) + b_dkt += tl.where(m_i, b_dAqk[:, None] * b_qj[None, :] * b_gkq, 0.) + b_dkt += tl.where(m_i, b_dAkk[:, None] * b_kbj[None, :] * b_gkq, 0.) + + p_qj += H*K + p_kj += H*K + p_gkj += HV*K + p_bj += HV + p_dk = dk + o_c[:, None] * (HV*K) + o_k[None, :] + p_dk2 = dk2 + o_c[:, None] * (HV*K) + o_k[None, :] + p_dg = dg + o_c[:, None] * (HV*K) + o_k[None, :] + p_dg2 = dg2 + o_c[:, None] * (HV*K) + o_k[None, :] + + b_dg2 += (b_dk2 - b_dkt) * b_k + tl.load(p_dg, mask=m_ck, other=0.0) + b_dk2 += tl.load(p_dk, mask=m_ck, other=0.0) + b_dk2 += b_dkt + + tl.store(p_dk2, b_dk2.to(p_dk2.dtype.element_ty), mask=m_ck) + tl.store(p_dg2, b_dg2.to(p_dg2.dtype.element_ty), mask=m_ck) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=["BT", "BC", "HV"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_kda_fwd_kernel_intra_sub_chunk( + q, + k, + g, + beta, + Aqk, + Akk, + scale, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_GATHER: tl.constexpr, +): + i_t, i_i, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1), tl.program_id(2).to(tl.int64) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + i_ti = i_t * BT + i_i * BC + if i_ti >= T: + return + + o_c = i_ti + tl.arange(0, BC) + m_c = o_c < T + + q = q + (bos * H + i_h) * K + k = k + (bos * H + i_h) * K + g = g + (bos * HV + i_hv) * K + beta = beta + bos * HV + i_hv + Aqk = Aqk + (bos * HV + i_hv) * BT + Akk = Akk + (bos * HV + i_hv) * BC + + o_k = tl.arange(0, BK) + m_k = o_k < K + m_ck = m_c[:, None] & m_k[None, :] + p_q = q + o_c[:, None] * (H*K) + o_k[None, :] + p_k = k + o_c[:, None] * (H*K) + o_k[None, :] + p_g = g + o_c[:, None] * (HV*K) + o_k[None, :] + + p_beta = beta + o_c * HV + + b_q = tl.load(p_q, mask=m_ck, other=0.0) + b_k = tl.load(p_k, mask=m_ck, other=0.0) + b_g = tl.load(p_g, mask=m_ck, other=0.0) + b_beta = tl.load(p_beta, mask=m_c, other=0.0) + + if USE_GATHER: + b_gn = gather(b_g, tl.full([1, BK], min(BC//2, T - i_ti - 1), dtype=tl.int16), axis=0) + else: + # caculate offset + p_gn = g + (i_ti + min(BC // 2, T - i_ti - 1)) * HV*K + tl.arange(0, BK) + b_gn = tl.load(p_gn, mask=tl.arange(0, BK) < K, other=0.0) + b_gn = b_gn[None, :] + + # current block, keep numerical stability by subtracting the left boundary + # less than 85 to avoid overflow in exp2 + b_gm = (b_g - b_gn).to(tl.float32) + + b_gq = tl.where(m_c[:, None], exp2(b_gm), 0.) + b_gk = tl.where(m_c[:, None], exp2(-b_gm), 0.) + + b_kgt = tl.trans(b_k * b_gk) + + b_Aqk = tl.dot(b_q * b_gq, b_kgt) * scale + b_Akk = tl.dot(b_k * b_gq, b_kgt) * b_beta[:, None] + + o_i = tl.arange(0, BC) + m_Aqk = o_i[:, None] >= o_i[None, :] + m_Akk = o_i[:, None] > o_i[None, :] + m_I = o_i[:, None] == o_i[None, :] + + b_Aqk = tl.where(m_Aqk, b_Aqk, 0.0) + b_Akk = tl.where(m_Akk, b_Akk, 0.0) + + m_Aqk_st = m_c[:, None] & (o_i[None, :] < BT) + m_Akk_st = m_c[:, None] & (o_i[None, :] < BC) + p_Aqk = Aqk + o_c[:, None] * (HV*BT) + (i_i * BC + o_i)[None, :] + p_Akk = Akk + o_c[:, None] * (HV*BC) + o_i[None, :] + tl.store(p_Aqk, b_Aqk.to(Aqk.dtype.element_ty), mask=m_Aqk_st) + tl.store(p_Akk, b_Akk.to(Akk.dtype.element_ty), mask=m_Akk_st) + + tl.debug_barrier() + + ################################################################################ + # forward substitution + ################################################################################ + + b_Ai = -b_Akk + for i in range(2, min(BC, T - i_ti)): + b_a = -tl.load(Akk + (i_ti + i) * HV*BC + o_i) + b_a = tl.where(o_i < i, b_a, 0.) + b_a += tl.sum(b_a[:, None] * b_Ai, 0) + b_Ai = tl.where((o_i == i)[:, None], b_a, b_Ai) + b_Ai += m_I + tl.store(p_Akk, b_Ai.to(Akk.dtype.element_ty), mask=m_Akk_st) + + +@dispatch('kda') +def chunk_kda_fwd_intra( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gk: torch.Tensor | None = None, + beta: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + chunk_indices: torch.LongTensor | None = None, + safe_gate: bool = False, + disable_recompute: bool = False, +): + B, T, H, K, HV = *k.shape, gk.shape[2] + BT = chunk_size + if BT not in (32, 64): + raise ValueError(f"KDA intra chunk kernel only supports chunk_size 32 or 64, got {BT}.") + BC = 16 + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + + Aqk = torch.empty(B, T, HV, BT, device=k.device, dtype=k.dtype) + # Akk must be zero-initialized - kernel only writes lower triangular + Akk = torch.zeros(B, T, HV, BT, device=k.device, dtype=k.dtype) + # Separate fp32 buffer for diagonal 16x16 blocks (for precision in solve_tril) + Akkd = torch.empty(B, T, HV, BC, device=k.device, dtype=torch.float32) + + # Step 1: Run token_parallel first to compute diagonal blocks into Akkd (fp32) + # Step 1: compute diagonal blocks into Akk_diag (fp32) + if safe_gate: + grid = (NT, NC, B * HV) + BK = triton.next_power_of_2(K) + chunk_kda_fwd_kernel_intra_sub_chunk[grid]( + q=q, + k=k, + g=gk, + beta=beta, + Aqk=Aqk, + Akk=Akkd, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + HV=HV, + K=K, + BT=BT, + BC=BC, + BK=BK, + USE_GATHER=IS_GATHER_SUPPORTED, + ) + else: + Aqk, Akkd = chunk_kda_fwd_intra_token_parallel( + q=q, + k=k, + gk=gk, + beta=beta, + Aqk=Aqk, + Akk=Akkd, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_size=BT, + sub_chunk_size=BC, + ) + + # Step 2: Fused inter + solve_tril (works for both fixed-len and varlen) + grid = (NT, B * HV) + chunk_kda_fwd_kernel_inter_solve_fused[grid]( + q=q, + k=k, + g=gk, + beta=beta, + Aqk=Aqk, + Akkd=Akkd, + Akk=Akk, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + HV=HV, + K=K, + BT=BT, + BC=BC, + NC=NC, + USE_SAFE_GATE=safe_gate, + ) + w, u, qg, kg = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=Akk, + q=q if disable_recompute else None, + gk=gk, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + ) + return w, u, qg, kg, Aqk, Akk + + +@dispatch('kda') +def chunk_kda_bwd_intra( + q: torch.Tensor, + k: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + dAqk: torch.Tensor, + dAkk: torch.Tensor, + dq: torch.Tensor, + dk: torch.Tensor, + db: torch.Tensor, + dg: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_indices: torch.LongTensor | None = None, + chunk_size: int = 64, + safe_gate: bool = False, +): + B, T, H, K, HV = *k.shape, g.shape[2] + BT = chunk_size + BC = min(16, BT) + BK = min(32, triton.next_power_of_2(K)) + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + NC = triton.cdiv(BT, BC) + NK = triton.cdiv(K, BK) + + dq2 = torch.empty_like(dq) + dk2 = torch.empty_like(dk) + db2 = beta.new_empty(NK, *beta.shape, dtype=torch.float) + dg2 = torch.empty_like(dg, dtype=torch.float) + grid = (NK * NC, NT, B * HV) + chunk_kda_bwd_kernel_intra[grid]( + q=q, + k=k, + g=g, + beta=beta, + dAqk=dAqk, + dAkk=dAkk, + dq=dq, + dq2=dq2, + dk=dk, + dk2=dk2, + dg=dg, + dg2=dg2, + db=db2, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + HV=HV, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + SAFE_GATE=safe_gate, + USE_GATHER=IS_GATHER_SUPPORTED, + ) + dq = dq2 + dk = dk2 + db = db2.sum(0).add_(db) + dg = dg2 + + return dq, dk, db, dg diff --git a/kda/_fla/ops/kda/chunk_intra_token_parallel.py b/kda/_fla/ops/kda/chunk_intra_token_parallel.py new file mode 100644 index 0000000..573f51b --- /dev/null +++ b/kda/_fla/ops/kda/chunk_intra_token_parallel.py @@ -0,0 +1,182 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +# Token-parallel implementation of KDA intra chunk kernel + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.op import exp2 +from kda._fla.utils import autotune_cache_kwargs + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BH': BH}, num_warps=num_warps) + for BH in [1, 2, 4, 8] + for num_warps in [1, 2, 4, 8] + ], + key=["K", "H", "HV"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T', 'N']) +def chunk_kda_fwd_kernel_intra_token_parallel( + q, + k, + g, + beta, + Aqk, + Akk, + scale, + cu_seqlens, + N, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + BK: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BH: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_tg, i_hg = tl.program_id(0).to(tl.int64), tl.program_id(1) + + if IS_VARLEN: + i_n = 0 + left, right = 0, N + + # Unrolled binary search (max B=2^32) + # We can limit iterations based on expected max batch size if needed + # 20 iterations covers B=1M, usually enough + for _ in range(20): + if left < right: + mid = (left + right) // 2 + if i_tg < tl.load(cu_seqlens + mid + 1).to(tl.int32): + right = mid + else: + left = mid + 1 + i_n = left + + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + i_t = i_tg - bos + else: + bos = (i_tg // T) * T + i_t = i_tg % T + + if i_t >= T: + return + + i_c = i_t // BT + i_s = (i_t % BT) // BC + i_tc = i_c * BT + i_ts = i_tc + i_s * BC + + G: tl.constexpr = HV // H + + q += bos * H*K + k += bos * H*K + g += bos * HV*K + Aqk += bos * HV*BT + Akk += bos * HV*BC + beta += bos * HV + + o_hv = i_hg * BH + tl.arange(0, BH) + o_h = o_hv // G + o_k = tl.arange(0, BK) + m_hv = o_hv < HV + m_k = o_k < K + m_hk = m_hv[:, None] & m_k[None, :] + + # q/k: [B, T, H, K], manual load via mapped qk head index + p_qk = o_h[:, None] * K + o_k[None, :] + b_q = tl.load(q + i_t * H * K + p_qk, mask=m_hk, other=0).to(tl.float32) + b_k = tl.load(k + i_t * H * K + p_qk, mask=m_hk, other=0).to(tl.float32) + + # g: [B, T, HV, K], beta: [B, T, HV] + p_g = g + i_t * HV * K + o_hv[:, None] * K + o_k[None, :] + p_beta = beta + i_t * HV + o_hv + b_g = tl.load(p_g, mask=m_hk, other=0.0).to(tl.float32) + b_k = b_k * tl.load(p_beta, mask=m_hv, other=0.0).to(tl.float32)[:, None] + + for j in range(i_ts, min(i_t + 1, min(T, i_ts + BC))): + b_kj = tl.load(k + j * H * K + p_qk, mask=m_hk, other=0).to(tl.float32) + p_gj = g + j * HV * K + o_hv[:, None] * K + o_k[None, :] + b_gj = tl.load(p_gj, mask=m_hk, other=0.0).to(tl.float32) + + b_kgj = tl.where(m_k[None, :], b_kj * exp2(b_g - b_gj), 0.0) + b_Aqk = tl.sum(b_q * b_kgj, axis=1) * scale + b_Akk = tl.sum(b_k * b_kgj, axis=1) * tl.where(j < i_t, 1.0, 0.0) + + tl.store(Aqk + i_t * HV * BT + o_hv * BT + j % BT, b_Aqk.to(Aqk.dtype.element_ty), mask=m_hv) + tl.store(Akk + i_t * HV * BC + o_hv * BC + j - i_ts, b_Akk.to(Akk.dtype.element_ty), mask=m_hv) + + +@dispatch('kda') +def chunk_kda_fwd_intra_token_parallel( + q: torch.Tensor, + k: torch.Tensor, + gk: torch.Tensor, + beta: torch.Tensor, + Aqk: torch.Tensor, + Akk: torch.Tensor, + scale: float, + cu_seqlens: torch.LongTensor | None = None, + chunk_size: int = 64, + sub_chunk_size: int = 16, +) -> None: + """ + Token-parallel implementation: each token gets its own thread block. + Supports both fixed-length and variable-length sequences. + Reduces wasted computation on padding. + + Writes directly to Aqk and Akk tensors (in-place). + + Args: + q: [B, T, H, K] + k: [B, T, H, K] + gk: [B, T, HV, K] cumsum of gates (HV >= H for GVA) + beta: [B, T, HV] + Aqk: [B, T, HV, BT] output tensor to write to + Akk: [B, T, HV, BC] output tensor for diagonal blocks (fp32) + scale: attention scale + chunk_size: BT (default 64) + sub_chunk_size: BC (default 16) + """ + B, T, H, K, HV = *q.shape, gk.shape[2] + N = len(cu_seqlens) - 1 if cu_seqlens is not None else B + BT = chunk_size + BC = sub_chunk_size + BK = triton.next_power_of_2(K) + + def grid(meta): return (B * T, triton.cdiv(HV, meta['BH'])) + chunk_kda_fwd_kernel_intra_token_parallel[grid]( + q=q, + k=k, + g=gk, + beta=beta, + Aqk=Aqk, + Akk=Akk, + scale=scale, + cu_seqlens=cu_seqlens, + N=N, + T=T, + H=H, + HV=HV, + K=K, + BK=BK, + BT=BT, + BC=BC, + ) + return Aqk, Akk diff --git a/kda/_fla/ops/kda/fused_recurrent.py b/kda/_fla/ops/kda/fused_recurrent.py new file mode 100644 index 0000000..d98ab89 --- /dev/null +++ b/kda/_fla/ops/kda/fused_recurrent.py @@ -0,0 +1,491 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +# This kernel is modified from the Decode kernel of the vllm gdn/kda model. + +import warnings + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.utils.op import exp +from kda._fla.ops.utils.softplus import softplus +from kda._fla.utils import input_guard + + +@triton.heuristics( + { + "USE_INITIAL_STATE": lambda args: args["h0"] is not None, + "STORE_FINAL_STATE": lambda args: args["ht"] is not None, + "IS_VARLEN": lambda args: args["cu_seqlens"] is not None, + "IS_CONTINUOUS_BATCHING": lambda args: args["ssm_state_indices"] is not None, + "IS_SPEC_DECODING": lambda args: args["num_accepted_tokens"] is not None, + "HAS_A": lambda args: args["A_log"] is not None, + "HAS_BIAS": lambda args: args["dt_bias"] is not None, + "USE_LOWER_BOUND": lambda args: args["lower_bound"] is not None, + } +) +@triton.jit(do_not_specialize=["N", "T"]) +def fused_recurrent_kda_fwd_kernel( + q, + k, + v, + g, + beta, + A_log, + dt_bias, + o, + h0, + ht, + cu_seqlens, + ssm_state_indices, + num_accepted_tokens, + lower_bound, + scale: tl.constexpr, + N: tl.int64, # num of sequences + T: tl.int64, # num of tokens + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + stride_init_state_token: tl.constexpr, + stride_final_state_token: tl.constexpr, + stride_indices_seq: tl.constexpr, + stride_indices_tok: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, # whether to use initial state + INPLACE_FINAL_STATE: tl.constexpr, # whether to store final state inplace + IS_BETA_HEADWISE: tl.constexpr, # whether beta is headwise vector or scalar, + USE_QK_L2NORM_IN_KERNEL: tl.constexpr, + IS_VARLEN: tl.constexpr, + IS_CONTINUOUS_BATCHING: tl.constexpr, + IS_SPEC_DECODING: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + HAS_A: tl.constexpr, + HAS_BIAS: tl.constexpr, + USE_GATE_IN_KERNEL: tl.constexpr, + USE_LOWER_BOUND: tl.constexpr, + APPLY_BETA_SIGMOID: tl.constexpr, + ALLOW_NEG_EIGVAL: tl.constexpr, + STATE_V_FIRST: tl.constexpr, + num_stages: tl.constexpr, +): + pid = tl.program_id(0).to(tl.int64) + NV = tl.cdiv(V, BV) + NK = tl.cdiv(K, BK) + i_k = pid % NK + pid_rest = pid // NK + + i_v = pid_rest % NV + i_nh = pid_rest // NV + i_n, i_hv = i_nh // HV, i_nh % HV + i_h = i_hv // (HV // H) + if IS_VARLEN: + bos, eos = ( + tl.load(cu_seqlens + i_n).to(tl.int64), + tl.load(cu_seqlens + i_n + 1).to(tl.int64), + ) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + + if T == 0: + # no tokens to process for this sequence + return + + o_k = i_k * BK + tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + + p_q = q + (bos * H + i_h) * K + o_k + p_k = k + (bos * H + i_h) * K + o_k + p_v = v + (bos * HV + i_hv) * V + o_v + if IS_BETA_HEADWISE: + p_beta = beta + (bos * HV + i_hv) * V + o_v + else: + p_beta = beta + bos * HV + i_hv + + p_g = g + (bos * HV + i_hv) * K + o_k + p_o = o + (bos * HV + i_hv) * V + o_v + + mask_k = o_k < K + mask_v = o_v < V + if STATE_V_FIRST: + mask_h = mask_v[:, None] & mask_k[None, :] + else: + mask_h = mask_k[:, None] & mask_v[None, :] + + if STATE_V_FIRST: + b_h = tl.zeros([BV, BK], dtype=tl.float32) + else: + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + if IS_CONTINUOUS_BATCHING: + if IS_SPEC_DECODING: + i_t = tl.load(num_accepted_tokens + i_n).to(tl.int64) - 1 + else: + i_t = 0 + p_h0 = ( + h0 + + tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to( + tl.int64 + ) + * stride_init_state_token + ) + if STATE_V_FIRST: + p_h0 = p_h0 + i_hv * K * V + o_v[:, None] * K + o_k[None, :] + else: + p_h0 = p_h0 + i_hv * K * V + o_k[:, None] * V + o_v[None, :] + else: + if STATE_V_FIRST: + p_h0 = h0 + (i_n * HV + i_hv) * K * V + o_v[:, None] * K + o_k[None, :] + else: + p_h0 = h0 + (i_n * HV + i_hv) * K * V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for i_t in tl.range(0, T, num_stages=num_stages): + b_q = tl.load(p_q, mask=mask_k, other=0, eviction_policy='evict_last').to(tl.float32) + b_k = tl.load(p_k, mask=mask_k, other=0, eviction_policy='evict_last').to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0, eviction_policy='evict_first').to(tl.float32) + + if USE_QK_L2NORM_IN_KERNEL: + b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6) + b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6) + b_q = b_q * scale + b_g = tl.load(p_g, mask=mask_k, other=0, eviction_policy='evict_last').to(tl.float32) + + if USE_GATE_IN_KERNEL: + b_A = tl.load(A_log + i_hv).to(tl.float32) if HAS_A else 1.0 + + if HAS_BIAS: + b_bias = tl.load(dt_bias + i_hv * K + o_k, mask=mask_k, other=0).to(tl.float32) + b_g = b_g + b_bias + + if USE_LOWER_BOUND: + b_gk = lower_bound * tl.sigmoid((exp(b_A) if HAS_A else b_A) * b_g) + else: + b_gk = -exp(b_A) * softplus(b_g) + else: + b_gk = b_g + + if STATE_V_FIRST: + b_h *= exp(b_gk[None, :]) + else: + b_h *= exp(b_gk[:, None]) + + if STATE_V_FIRST: + b_v -= tl.sum(b_h * b_k[None, :], 1) + else: + b_v -= tl.sum(b_h * b_k[:, None], 0) + if IS_BETA_HEADWISE: + b_beta = tl.load(p_beta, mask=mask_v, other=0, eviction_policy='evict_first').to(tl.float32) + else: + b_beta = tl.load(p_beta, eviction_policy='evict_last').to(tl.float32) + if APPLY_BETA_SIGMOID: + b_beta = tl.sigmoid(b_beta) + if ALLOW_NEG_EIGVAL: + b_beta = b_beta * 2 + b_v *= b_beta + if STATE_V_FIRST: + b_h += b_v[:, None] * b_k[None, :] + b_o = tl.sum(b_h * b_q[None, :], 1) + else: + b_h += b_k[:, None] * b_v[None, :] + b_o = tl.sum(b_h * b_q[:, None], 0) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v, eviction_policy='evict_first') + + if IS_CONTINUOUS_BATCHING: + if INPLACE_FINAL_STATE: + p_ht = ( + ht + + tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to( + tl.int64 + ) + * stride_final_state_token + ) + else: + p_ht = ht + (bos + i_t) * stride_final_state_token + if STATE_V_FIRST: + p_ht = p_ht + i_hv * K * V + o_v[:, None] * K + o_k[None, :] + else: + p_ht = p_ht + i_hv * K * V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + p_q += H * K + p_k += H * K + p_o += HV * V + p_v += HV * V + p_g += HV * K + p_beta += HV * (V if IS_BETA_HEADWISE else 1) + + if not IS_CONTINUOUS_BATCHING: + if STORE_FINAL_STATE: + if STATE_V_FIRST: + p_ht = ht + (i_n * HV + i_hv) * K * V + o_v[:, None] * K + o_k[None, :] + else: + p_ht = ht + (i_n * HV + i_hv) * K * V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +@dispatch("kda") +def fused_recurrent_kda_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, + scale: float | None = None, + output_final_state: bool = False, + inplace_final_state: bool = True, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + ssm_state_indices: torch.Tensor | None = None, + num_accepted_tokens: torch.Tensor | None = None, + use_qk_l2norm_in_kernel: bool = False, + use_gate_in_kernel: bool = False, + use_beta_sigmoid_in_kernel: bool = False, + allow_neg_eigval: bool = False, + lower_bound: float | None = None, + out: torch.Tensor | None = None, + **kwargs, +) -> tuple[torch.Tensor, torch.Tensor]: + if scale is None: + scale = k.shape[-1] ** -0.5 + + B, T, H, K, V = *k.shape, v.shape[-1] + HV = v.shape[2] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK = triton.next_power_of_2(K) + BV = 32 + + if out is None: + out = torch.zeros_like(v) + else: + assert out.shape == v.shape + if inplace_final_state: + assert initial_state is not None + final_state = initial_state + elif output_final_state: + if state_v_first: + final_state = q.new_empty(N, HV, V, K, dtype=torch.float32) + else: + final_state = q.new_empty(N, HV, K, V, dtype=torch.float32) + else: + final_state = None + + stride_init_state_token = initial_state.stride(0) if initial_state is not None else 1 + stride_final_state_token = final_state.stride(0) if final_state is not None else 1 + + if ssm_state_indices is None: + stride_indices_seq, stride_indices_tok = 1, 1 + elif ssm_state_indices.ndim == 1: + stride_indices_seq, stride_indices_tok = ssm_state_indices.stride(0), 1 + else: + stride_indices_seq, stride_indices_tok = ssm_state_indices.stride() + + grid = (triton.cdiv(V, BV) * N * HV, ) + fused_recurrent_kda_fwd_kernel[grid]( + q=q, + k=k, + v=v, + g=g, + beta=beta, + A_log=A_log, + dt_bias=dt_bias, + o=out, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + ssm_state_indices=ssm_state_indices, + num_accepted_tokens=num_accepted_tokens, + lower_bound=lower_bound, + scale=scale, + N=N, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BK=BK, + BV=BV, + stride_init_state_token=stride_init_state_token, + stride_final_state_token=stride_final_state_token, + stride_indices_seq=stride_indices_seq, + stride_indices_tok=stride_indices_tok, + IS_BETA_HEADWISE=beta.ndim == v.ndim, + USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel, + INPLACE_FINAL_STATE=inplace_final_state, + USE_GATE_IN_KERNEL=use_gate_in_kernel, + APPLY_BETA_SIGMOID=use_beta_sigmoid_in_kernel, + ALLOW_NEG_EIGVAL=allow_neg_eigval, + STATE_V_FIRST=state_v_first, + num_warps=4, + num_stages=2, + ) + + return out, final_state + + +@input_guard +def fused_recurrent_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + use_gate_in_kernel: bool = False, + use_beta_sigmoid_in_kernel: bool = False, + allow_neg_eigval: bool = False, + lower_bound: float | None = None, + state_v_first: bool = False, + cu_seqlens: torch.LongTensor | None = None, + **kwargs, +) -> tuple[torch.Tensor, torch.Tensor]: + r""" + Args: + q (torch.Tensor): + queries of shape `[B, T, H, K]`. + k (torch.Tensor): + keys of shape `[B, T, H, K]`. + v (torch.Tensor): + values of shape `[B, T, HV, V]`. + GVA is applied if `HV > H`. + g (torch.Tensor): + g (decays) of shape `[B, T, HV, K]`. + beta (torch.Tensor): + betas of shape `[B, T, HV]`. + A_log (Optional[torch.Tensor]): + Decay parameter of shape `[HV]`. + When `use_gate_in_kernel=True` together with `lower_bound`, + may be `None` to use `lower_bound * sigmoid(g + dt_bias)`. + dt_bias (Optional[torch.Tensor]): + Bias added to `g` before activation, of shape `[HV]`. Only used when `use_gate_in_kernel=True`. + scale (Optional[float]): + Scale factor for the RetNet attention scores. + If not provided, it will default to `1 / sqrt(K)`. Default: `None`. + initial_state (Optional[torch.Tensor]): + Initial state of shape `[N, HV, K, V]` for `N` input sequences. + For equal-length input sequences, `N` equals the batch size `B`. + Default: `None`. + output_final_state (Optional[bool]): + Whether to output the final state of shape `[N, HV, K, V]`. Default: `False`. + use_qk_l2norm_in_kernel (Optional[bool]): + Whether to use L2 normalization in the kernel. Default: `False`. + use_gate_in_kernel (Optional[bool]): + Whether to compute the log-space KDA decay internally. + When `True`, `g` is the raw input and the kernel fuses gate activation into the recurrence. + Default: `False`. + use_beta_sigmoid_in_kernel (Optional[bool]): + Whether to apply `torch.sigmoid(beta)` inside the kernel. + - If `True`, the passed `beta` acts as the raw beta logits. + - If `False`, `beta` is expected to already be in post-sigmoid space. + Default: `False`. + allow_neg_eigval (Optional[bool]): + Whether to allow negative eigenvalues by scaling `beta` to `[0, 2)`. + Only takes effect together with `use_beta_sigmoid_in_kernel=True`, in which case + the kernel computes `2 * sigmoid(beta)` instead of `sigmoid(beta)`. Default: `False`. + lower_bound (Optional[float]): + Lower bound for the forget gate (in log space). Only used when `use_gate_in_kernel=True`. Default: `None`. + state_v_first (Optional[bool]): + Store the recurrent state in V-first ``[V, K]`` layout instead of the default ``[K, V]``. Default: ``False``. + cu_seqlens (torch.LongTensor): + Cumulative sequence lengths of shape `[N+1]` used for variable-length training, + consistent with the FlashAttention API. + + Returns: + o (torch.Tensor): + Outputs of shape `[B, T, HV, V]`. + final_state (torch.Tensor): + Final state of shape `[N, HV, K, V]` if `output_final_state=True` else `None`. + + Examples:: + >>> import torch + >>> import torch.nn.functional as F + >>> from einops import rearrange + >>> from fla.ops.kda import fused_recurrent_kda + # inputs with equal lengths + >>> B, T, H, HV, K, V = 4, 2048, 4, 8, 512, 512 + >>> q = torch.randn(B, T, H, K, device='cuda') + >>> k = F.normalize(torch.randn(B, T, H, K, device='cuda'), p=2, dim=-1) + >>> v = torch.randn(B, T, HV, V, device='cuda') + >>> g = F.logsigmoid(torch.rand(B, T, HV, K, device='cuda')) + >>> beta = torch.rand(B, T, HV, device='cuda').sigmoid() + >>> h0 = torch.randn(B, HV, K, V, device='cuda') + >>> o, ht = fused_recurrent_kda( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True + ) + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required + >>> q, k, v, g, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, g, beta)) + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) + >>> o_var, ht_var = fused_recurrent_kda( + q, k, v, g, beta, + initial_state=h0, + output_final_state=True, + cu_seqlens=cu_seqlens + ) + """ + if 'transpose_state_layout' in kwargs: + if state_v_first: + raise ValueError("Cannot pass both `state_v_first` and the deprecated `transpose_state_layout`.") + warnings.warn( + "`transpose_state_layout` is deprecated and renamed to `state_v_first`.", + DeprecationWarning, + stacklevel=2, + ) + state_v_first = kwargs.pop('transpose_state_layout') + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing.", + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}.", + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + if allow_neg_eigval and not use_beta_sigmoid_in_kernel: + raise ValueError("`allow_neg_eigval=True` requires `use_beta_sigmoid_in_kernel=True`.") + + o, final_state = fused_recurrent_kda_fwd( + q=q, + k=k, + v=v, + g=g, + beta=beta, + A_log=A_log, + dt_bias=dt_bias, + scale=scale, + initial_state=initial_state, + inplace_final_state=False, + output_final_state=output_final_state, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + use_gate_in_kernel=use_gate_in_kernel, + use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, + allow_neg_eigval=allow_neg_eigval, + lower_bound=lower_bound, + cu_seqlens=cu_seqlens, + state_v_first=state_v_first, + ) + return o, final_state diff --git a/kda/_fla/ops/kda/gate.py b/kda/_fla/ops/kda/gate.py new file mode 100644 index 0000000..a71b7fd --- /dev/null +++ b/kda/_fla/ops/kda/gate.py @@ -0,0 +1,514 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +# This file is modified and supported by the Moonshot AI Team + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.index import prepare_chunk_indices +from kda._fla.ops.utils.op import exp +from kda._fla.ops.utils.softplus import softplus +from kda._fla.utils import IS_AMD, autocast_custom_bwd, autocast_custom_fwd, autotune_cache_kwargs, check_shared_mem, input_guard + +BS_LIST = [32, 64] if check_shared_mem() else [16, 32] +BT_LIST_AUTOTUNE = [32, 64, 128] +NUM_WARPS_AUTOTUNE = [2, 4, 8, 16] if IS_AMD else [4, 8, 16, 32] + + +def naive_kda_gate( + g: torch.Tensor, + A_log: torch.Tensor, + dt_bias: torch.Tensor | None = None, + output_dtype: torch.dtype = torch.float32, +) -> torch.Tensor: + """ + Torch reference implementation for KDA gate computation. + + Computes: g = -A_log.exp().unsqueeze(-1) * softplus(g + dt_bias.view(g.shape[-2:])) + + Args: + g (torch.Tensor): + Input tensor of shape `[..., H, K]`. + A_log (torch.Tensor): + Parameter tensor with `H` elements. + dt_bias (torch.Tensor | None): + Optional bias tensor added to `g` before activation, shape `[H * K]`. + + Returns: + Output tensor of shape `[..., H, K]` . + """ + H, _ = g.shape[-2:] + g = g.float() + if dt_bias is not None: + g = g + dt_bias.view(H, -1) + + g = (-A_log.view(H, 1).float().exp() * F.softplus(g.float())).to(output_dtype) + return g + + +def naive_kda_lowerbound_gate( + g: torch.Tensor, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + lower_bound: float = -5.0, + output_dtype: torch.dtype = torch.float32, +) -> torch.Tensor: + """ + Torch reference implementation for KDA lowerbound gate computation. + + Computes: ``g = lower_bound * sigmoid(exp(A_log) * (g + dt_bias))``. + When ``A_log`` is ``None``: ``g = lower_bound * sigmoid(g + dt_bias)``. + + Args: + g (torch.Tensor): + Input tensor of shape `[..., H, K]`. + A_log (torch.Tensor | None): + Optional parameter tensor with `H` elements. + dt_bias (torch.Tensor | None): + Optional bias tensor added to `g` before activation, shape `[H * K]`. + lower_bound (float): + Lower bound for the gate output. Default: `-5.0`. + output_dtype (torch.dtype): + The dtype of the output tensor. Default: `torch.float32`. + + Returns: + Output tensor of shape `[..., H, K]`. + """ + H, _ = g.shape[-2:] + g = g.float() + if dt_bias is not None: + g = g + dt_bias.view(H, -1) + if A_log is not None: + g = A_log.view(H, 1).float().exp() * g + g = lower_bound * F.sigmoid(g) + return g.to(output_dtype) + + +@triton.heuristics({ + "HAS_A": lambda args: args["A_log"] is not None, + "HAS_BIAS": lambda args: args["dt_bias"] is not None, + "HAS_BETA": lambda args: args["beta"] is not None, + 'USE_LOWER_BOUND': lambda args: args['lower_bound'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({"BT": BT}, num_warps=num_warps, num_stages=num_stages) + for BT in BT_LIST_AUTOTUNE + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3] + ], + key=["H", "D"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def kda_gate_fwd_kernel( + g, + A_log, + dt_bias, + beta, + yg, + yb, + lower_bound, + T, + H: tl.constexpr, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + HAS_A: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_BETA: tl.constexpr, + USE_LOWER_BOUND: tl.constexpr, +): + i_t, i_h = tl.program_id(0).to(tl.int64), tl.program_id(1) + + b_A = tl.load(A_log + i_h).to(tl.float32) if HAS_A else 1.0 + + o_t = i_t * BT + tl.arange(0, BT) + o_d = tl.arange(0, BD) + m_t = o_t < T + m_g = m_t[:, None] & (o_d[None, :] < D) + p_g = g + i_h * D + o_t[:, None] * (H * D) + o_d[None, :] + p_yg = yg + i_h * D + o_t[:, None] * (H * D) + o_d[None, :] + # [BT, BD] + b_g = tl.load(p_g, mask=m_g, other=0.0).to(tl.float32) + if HAS_BIAS: + o_b = i_h * D + tl.arange(0, BD) + b_g = b_g + tl.load(dt_bias + o_b, mask=o_b < H * D, other=0.0).to(tl.float32) + if not USE_LOWER_BOUND: + b_yg = -exp(b_A) * softplus(b_g) + else: + b_yg = lower_bound * tl.sigmoid((exp(b_A) if HAS_A else b_A) * b_g) + tl.store(p_yg, b_yg.to(p_yg.dtype.element_ty), mask=m_g) + + if HAS_BETA: + p_b = beta + i_h + o_t * H + p_yb = yb + i_h + o_t * H + b_yb = tl.sigmoid(tl.load(p_b, mask=m_t, other=0.0).to(tl.float32)) + tl.store(p_yb, b_yb.to(p_yb.dtype.element_ty), mask=m_t) + + +@triton.heuristics({ + "HAS_A": lambda args: args["A_log"] is not None, + "HAS_BIAS": lambda args: args["dt_bias"] is not None, + "HAS_BETA": lambda args: args["beta"] is not None, + 'USE_LOWER_BOUND': lambda args: args['lower_bound'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in NUM_WARPS_AUTOTUNE + for num_stages in [2, 3] + ], + key=["H", "D"], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def kda_gate_bwd_kernel( + g, + A_log, + dt_bias, + beta, + dyg, + dyb, + dg, + dA, + dbeta, + lower_bound, + T, + H: tl.constexpr, + D: tl.constexpr, + BT: tl.constexpr, + BD: tl.constexpr, + HAS_A: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_BETA: tl.constexpr, + USE_LOWER_BOUND: tl.constexpr, +): + i_t, i_h = tl.program_id(0).to(tl.int64), tl.program_id(1) + + b_A = tl.load(A_log + i_h).to(tl.float32) if HAS_A else 1.0 + + o_t = i_t * BT + tl.arange(0, BT) + o_d = tl.arange(0, BD) + m_t = o_t < T + m_g = m_t[:, None] & (o_d[None, :] < D) + p_g = g + i_h * D + o_t[:, None] * (H * D) + o_d[None, :] + p_dg = dg + i_h * D + o_t[:, None] * (H * D) + o_d[None, :] + p_dyg = dyg + i_h * D + o_t[:, None] * (H * D) + o_d[None, :] + + # [BT, BD] + b_g = tl.load(p_g, mask=m_g, other=0.0).to(tl.float32) + b_dyg = tl.load(p_dyg, mask=m_g, other=0.0).to(tl.float32) + + if HAS_BIAS: + o_b = i_h * D + tl.arange(0, BD) + b_g = b_g + tl.load(dt_bias + o_b, mask=o_b < H * D, other=0.0).to(tl.float32) + + # [BT, BD] + if not USE_LOWER_BOUND: + b_A = -exp(b_A) + b_yg = b_A * softplus(b_g) + b_dg = b_A * (b_dyg * tl.sigmoid(b_g)) + b_dA = tl.sum(tl.sum(b_dyg * b_yg, 1), 0) + else: + b_A = exp(b_A) if HAS_A else b_A + b_inner = b_A * b_g + b_sig = tl.sigmoid(b_inner) + b_dsig = b_sig * (1.0 - b_sig) + # Common term: dy * (LB * dsig) + b_d_inner_term = b_dyg * (lower_bound * b_dsig) + # dg = d_inner_term * A + b_dg = b_d_inner_term * b_A + b_dA = tl.sum(tl.sum(b_dg * b_g, 1), 0) if HAS_A else 0.0 + + tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), mask=m_g) + if HAS_A: + tl.store(dA + i_t * H + i_h, b_dA) + + if HAS_BETA: + p_b = beta + i_h + o_t * H + p_db = dbeta + i_h + o_t * H + p_dyb = dyb + i_h + o_t * H + + b_b = tl.load(p_b, mask=m_t, other=0.0).to(tl.float32) + b_db = tl.load(p_dyb, mask=m_t, other=0.0).to(tl.float32) * b_b * (1.0 - b_b) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), mask=m_t) + + +@dispatch('kda') +def kda_gate_fwd( + g: torch.Tensor, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + lower_bound: float | None = None, + output_dtype: torch.dtype = torch.float32, +) -> torch.Tensor: + H, K = g.shape[-2:] + T = g.numel() // (H * K) + + yg = torch.empty_like(g, dtype=output_dtype) + + def grid(meta): + return (triton.cdiv(T, meta["BT"]), H) + + kda_gate_fwd_kernel[grid]( + g=g, + A_log=A_log, + dt_bias=dt_bias, + beta=None, + yg=yg, + yb=None, + T=T, + H=H, + D=K, + BD=triton.next_power_of_2(K), + lower_bound=lower_bound, + ) + return yg + + +@dispatch('kda') +def kda_gate_bwd( + g: torch.Tensor, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + dyg: torch.Tensor | None = None, + lower_bound: float | None = None, +) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]: + H, K = g.shape[-2:] + T = g.numel() // (H * K) + BT = 32 + NT = triton.cdiv(T, BT) + + dg = torch.empty_like(g, dtype=torch.float32) + dA = g.new_empty(NT, H, dtype=torch.float32) if A_log is not None else None + + grid = (triton.cdiv(T, BT), H) + kda_gate_bwd_kernel[grid]( + g=g, + A_log=A_log, + dt_bias=dt_bias, + beta=None, + dyg=dyg, + dyb=None, + dg=dg, + dA=dA, + dbeta=None, + T=T, + H=H, + D=K, + BT=BT, + BD=triton.next_power_of_2(K), + lower_bound=lower_bound, + ) + + dg = dg.view_as(g).type_as(g) + dA = dA.sum(0).view_as(A_log).type_as(A_log) if A_log is not None else None + # dt_bias is [HV, K] in KDAAttention and [HV*K] in some FLA call sites. + dbias = ( + dg.view(-1, H * K).sum(0).reshape_as(dt_bias).type_as(dt_bias) + if dt_bias is not None + else None + ) + + return dg, dA, dbias + + +class KDAGateFunction(torch.autograd.Function): + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + g: torch.Tensor, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + lower_bound: float | None = None, + output_dtype: torch.dtype = torch.float32, + ) -> torch.Tensor: + yg = kda_gate_fwd( + g=g, + A_log=A_log, + dt_bias=dt_bias, + lower_bound=lower_bound, + output_dtype=output_dtype + ) + ctx.save_for_backward(g, A_log, dt_bias) + ctx.lower_bound = lower_bound + return yg + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward(ctx, dyg: torch.Tensor): + g, A_log, dt_bias = ctx.saved_tensors + dg, dA, dbias = kda_gate_bwd( + g=g, + A_log=A_log, + dt_bias=dt_bias, + dyg=dyg, + lower_bound=ctx.lower_bound + ) + return dg, dA, dbias, None, None + + +@dispatch('kda') +@torch.compiler.disable +def fused_kda_gate( + g: torch.Tensor, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + lower_bound: float | None = None, + output_dtype: torch.dtype = torch.float32, +) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: + """ + Fused KDA gate computation with autograd support. + + Computes: g = -A_log.exp().unsqueeze(-1) * softplus(g + dt_bias.view(g.shape[-2:])) + When ``lower_bound`` is set: g = lower_bound * sigmoid(exp(A_log) * (g + dt_bias)). + When ``A_log`` is ``None`` (requires ``lower_bound``): g = lower_bound * sigmoid(g + dt_bias). + + Args: + g (torch.Tensor): + Input tensor of shape `[..., H, K]`. + A_log (torch.Tensor | None): + Optional parameter tensor with `H` elements. + When ``None``, the gate reduces to ``lower_bound * sigmoid(g + dt_bias)`` (requires ``lower_bound``). + dt_bias (torch.Tensor | None): + Optional bias tensor added to `g` before activation, shape `[H * K]`. + + Returns: + Output tensor of shape `[..., H, K]`. + """ + return KDAGateFunction.apply(g, A_log, dt_bias, lower_bound, output_dtype) + + +@triton.heuristics({ + "HAS_A": lambda args: args["A_log"] is not None, + "HAS_BIAS": lambda args: args["dt_bias"] is not None, + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, + 'USE_LOWER_BOUND': lambda args: args['lower_bound'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BS': BS}, num_warps=num_warps) + for BS in BS_LIST + for num_warps in [2, 4, 8] + ], + key=['H', 'S', 'BT', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def kda_gate_chunk_cumsum_vector_kernel( + s, + A_log, + dt_bias, + o, + scale, + cu_seqlens, + chunk_indices, + lower_bound, + T, + H: tl.constexpr, + S: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + REVERSE: tl.constexpr, + HAS_A: tl.constexpr, + HAS_BIAS: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_LOWER_BOUND: tl.constexpr, +): + i_s, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + o_s = i_s * BS + tl.arange(0, BS) + m_s = (o_t[:, None] < T) & (o_s[None, :] < S) + p_s = s + (bos * H + i_h) * S + o_t[:, None] * (H*S) + o_s[None, :] + p_o = o + (bos * H + i_h) * S + o_t[:, None] * (H*S) + o_s[None, :] + # [BT, BS] + b_s = tl.load(p_s, mask=m_s, other=0.0).to(tl.float32) + + # Apply dt_bias if exists + if HAS_BIAS: + b_bias = tl.load(dt_bias + i_h * S + o_s, mask=o_s < S, other=0.0).to(tl.float32) + b_s = b_s + b_bias[None, :] + + b_A = tl.load(A_log + i_h).to(tl.float32) if HAS_A else 1.0 + if not USE_LOWER_BOUND: + # Apply gate: -exp(A_log) * softplus(g + bias) + b_gate = -exp(b_A) * softplus(b_s) + else: + b_gate = lower_bound * tl.sigmoid((exp(b_A) if HAS_A else b_A) * b_s) + + # Apply chunk local cumsum + if REVERSE: + b_o = tl.cumsum(b_gate, axis=0, reverse=True) + else: + b_o = tl.cumsum(b_gate, axis=0) + + if HAS_SCALE: + b_o *= scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=m_s) + + +@input_guard +@dispatch('kda') +def kda_gate_chunk_cumsum( + g: torch.Tensor, + A_log: torch.Tensor | None, + chunk_size: int, + scale: float = None, + dt_bias: torch.Tensor | None = None, + cu_seqlens: torch.Tensor | None = None, + output_dtype: torch.dtype | None = torch.float, + chunk_indices: torch.LongTensor | None = None, + lower_bound: float | None = None, + **kwargs, +) -> torch.Tensor: + if cu_seqlens is not None: + assert g.shape[0] == 1, "Only batch size 1 is supported when cu_seqlens are provided" + assert len(g.shape) == 4 + B, T, H, S = g.shape + BT = chunk_size + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + assert chunk_size == 2**(chunk_size.bit_length()-1), "chunk_size must be a power of 2" + + g_org, g = g, torch.empty_like(g, dtype=output_dtype or g.dtype) + def grid(meta): return (triton.cdiv(meta['S'], meta['BS']), NT, B * H) + kda_gate_chunk_cumsum_vector_kernel[grid]( + s=g_org, + A_log=A_log, + dt_bias=dt_bias, + o=g, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + lower_bound=lower_bound, + T=T, + H=H, + S=S, + BT=BT, + REVERSE=False, + ) + return g diff --git a/kda/_fla/ops/kda/wy_fast.py b/kda/_fla/ops/kda/wy_fast.py new file mode 100644 index 0000000..e23b1ab --- /dev/null +++ b/kda/_fla/ops/kda/wy_fast.py @@ -0,0 +1,369 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.utils import prepare_chunk_indices +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.op import exp2 +from kda._fla.utils import autotune_cache_kwargs, check_shared_mem + + +@triton.heuristics({ + 'STORE_QG': lambda args: args['qg'] is not None, + 'STORE_KG': lambda args: args['kg'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'HV', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def recompute_w_u_fwd_kda_kernel( + q, + k, + qg, + kg, + v, + beta, + w, + u, + A, + gk, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + STORE_QG: tl.constexpr, + STORE_KG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + k += (bos * H + i_h) * K + v += (bos * HV + i_hv) * V + u += (bos * HV + i_hv) * V + w += (bos * HV + i_hv) * K + gk += (bos * HV + i_hv) * K + beta += bos * HV + i_hv + A += (bos * HV + i_hv) * BT + if STORE_QG: + q += (bos * H + i_h) * K + qg += (bos * HV + i_hv) * K + if STORE_KG: + kg += (bos * HV + i_hv) * K + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + p_b = beta + o_t * HV + b_b = tl.load(p_b, mask=m_t, other=0.0) + + o_A = tl.arange(0, BT) + m_A = m_t[:, None] & (o_A[None, :] < BT) + p_A = A + o_t[:, None] * (HV*BT) + o_A[None, :] + b_A = tl.load(p_A, mask=m_A, other=0.0) + + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + m_v = m_t[:, None] & (o_v[None, :] < V) + p_v = v + o_t[:, None] * (HV*V) + o_v[None, :] + p_u = u + o_t[:, None] * (HV*V) + o_v[None, :] + b_v = tl.load(p_v, mask=m_v, other=0.0) + b_vb = (b_v * b_b[:, None]).to(b_v.dtype) + b_u = tl.dot(b_A, b_vb) + tl.store(p_u, b_u.to(p_u.dtype.element_ty), mask=m_v) + + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + m_tk = m_t[:, None] & m_k[None, :] + p_w = w + o_t[:, None] * (HV*K) + o_k[None, :] + p_k = k + o_t[:, None] * (H*K) + o_k[None, :] + b_k = tl.load(p_k, mask=m_tk, other=0.0) + b_kb = b_k * b_b[:, None] + + p_gk = gk + o_t[:, None] * (HV*K) + o_k[None, :] + b_gk = tl.load(p_gk, mask=m_tk, other=0.0).to(tl.float32) + b_kb *= exp2(b_gk) + if STORE_QG: + p_q = q + o_t[:, None] * (H*K) + o_k[None, :] + p_qg = qg + o_t[:, None] * (HV*K) + o_k[None, :] + b_q = tl.load(p_q, mask=m_tk, other=0.0) + b_qg = b_q * exp2(b_gk) + tl.store(p_qg, b_qg.to(p_qg.dtype.element_ty), mask=m_tk) + if STORE_KG: + last_idx = min(i_t * BT + BT, T) - 1 + b_gn = tl.load(gk + last_idx * HV*K + o_k, mask=m_k, other=0.).to(tl.float32) + b_kg = b_k * tl.where((i_t * BT + tl.arange(0, BT) < T)[:, None], exp2(b_gn[None, :] - b_gk), 0) + p_kg = kg + o_t[:, None] * (HV*K) + o_k[None, :] + tl.store(p_kg, b_kg.to(p_kg.dtype.element_ty), mask=m_tk) + + b_w = tl.dot(b_A, b_kb.to(b_k.dtype)) + tl.store(p_w, b_w.to(p_w.dtype.element_ty), mask=m_tk) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + ], + key=['H', 'HV', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_bwd_kda_kernel( + k, + v, + beta, + gk, + A, + dA, + dw, + du, + dk, + dk2, + dv, + db, + dg, + dg2, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_b, i_hv = i_bh // HV, i_bh % HV + i_h = i_hv // (HV // H) + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + k += (bos * H + i_h) * K + v += (bos * HV + i_hv) * V + beta += bos * HV + i_hv + gk += (bos * HV + i_hv) * K + A += (bos * HV + i_hv) * BT + dA += (bos * HV + i_hv) * BT + dk += (bos * HV + i_hv) * K + dk2 += (bos * HV + i_hv) * K + dw += (bos * HV + i_hv) * K + du += (bos * HV + i_hv) * V + dv += (bos * HV + i_hv) * V + db += bos * HV + i_hv + dg += (bos * HV + i_hv) * K + dg2 += (bos * HV + i_hv) * K + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + p_b = beta + o_t * HV + p_db = db + o_t * HV + o_A = tl.arange(0, BT) + m_AT = (o_A[:, None] < BT) & m_t[None, :] + p_A = A + o_A[:, None] + o_t[None, :] * (HV*BT) + + b_b = tl.load(p_b, mask=m_t, other=0.0) + b_db = tl.zeros([BT], dtype=tl.float32) + b_A = tl.load(p_A, mask=m_AT, other=0.0) + b_dA = tl.zeros([BT, BT], dtype=tl.float32) + + for i_k in range(tl.cdiv(K, BK)): + o_k = i_k * BK + tl.arange(0, BK) + m_k = m_t[:, None] & (o_k[None, :] < K) + p_k = k + o_t[:, None] * (H*K) + o_k[None, :] + p_dk = dk + o_t[:, None] * (HV*K) + o_k[None, :] + p_dk2 = dk2 + o_t[:, None] * (HV*K) + o_k[None, :] + p_dw = dw + o_t[:, None] * (HV*K) + o_k[None, :] + p_dg = dg + o_t[:, None] * (HV*K) + o_k[None, :] + p_dg2 = dg2 + o_t[:, None] * (HV*K) + o_k[None, :] + + # [BT, BK] + b_k = tl.load(p_k, mask=m_k, other=0.0) + p_gk = gk + o_t[:, None] * (HV*K) + o_k[None, :] + b_gk_exp = exp2(tl.load(p_gk, mask=m_k, other=0.0)) + b_kbg = b_k * b_b[:, None] * b_gk_exp + b_dw = tl.load(p_dw, mask=m_k, other=0.0) + + b_dA += tl.dot(b_dw, tl.trans(b_kbg).to(b_dw.dtype)) + b_dkbg = tl.dot(b_A, b_dw) + b_dk = b_dkbg * b_gk_exp * b_b[:, None] + tl.load(p_dk, mask=m_k, other=0.0) + b_db += tl.sum(b_dkbg * b_k * b_gk_exp, 1) + b_dg = b_kbg * b_dkbg + tl.load(p_dg, mask=m_k, other=0.0) + + tl.store(p_dk2, b_dk.to(p_dk2.dtype.element_ty), mask=m_k) + tl.store(p_dg2, b_dg.to(p_dg2.dtype.element_ty), mask=m_k) + + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, BV) + m_v = m_t[:, None] & (o_v[None, :] < V) + p_v = v + o_t[:, None] * (HV*V) + o_v[None, :] + p_dv = dv + o_t[:, None] * (HV*V) + o_v[None, :] + p_du = du + o_t[:, None] * (HV*V) + o_v[None, :] + b_v = tl.load(p_v, mask=m_v, other=0.0) + b_vb = (b_v * b_b[:, None]).to(b_v.dtype) + b_du = tl.load(p_du, mask=m_v, other=0.0) + b_dA += tl.dot(b_du, tl.trans(b_vb)) + b_dvb = tl.dot(b_A, b_du) + b_dv = b_dvb * b_b[:, None] + b_db += tl.sum(b_dvb * b_v, 1) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), mask=m_v) + + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_dA = tl.where(m_A, b_dA, 0) + b_dA = tl.dot(b_dA.to(b_A.dtype), b_A) + b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) + + b_dA = tl.where(m_A, -b_dA, 0) + + m_dA = m_t[:, None] & (o_A[None, :] < BT) + p_dA = dA + o_t[:, None] * (HV*BT) + o_A[None, :] + tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), mask=m_dA) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), mask=m_t) + + +@dispatch('kda') +def recompute_w_u_fwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + gk: torch.Tensor, + q: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, + chunk_indices: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None, torch.Tensor | None]: + B, T, H, K, V = *k.shape, v.shape[-1] + HV = v.shape[2] + BT = A.shape[-1] + BK = 64 + BV = 64 + + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + + w = torch.empty(B, T, HV, K, device=k.device, dtype=k.dtype) + u = torch.empty_like(v) + qg = torch.empty(B, T, HV, K, device=k.device, dtype=k.dtype) if q is not None else None + kg = torch.empty(B, T, HV, K, device=k.device, dtype=k.dtype) + recompute_w_u_fwd_kda_kernel[(NT, B*HV)]( + q=q, + k=k, + qg=qg, + kg=kg, + v=v, + beta=beta, + w=w, + u=u, + A=A, + gk=gk, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return w, u, qg, kg + + +def prepare_wy_repr_bwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + gk: torch.Tensor, + A: torch.Tensor, + dk: torch.Tensor, + dw: torch.Tensor, + du: torch.Tensor, + dg: torch.Tensor, + cu_seqlens: torch.LongTensor | None = None, + chunk_indices: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + HV = v.shape[2] + BT = A.shape[-1] + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + CONST_TILING = 64 if check_shared_mem() else 32 + BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) + BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) + + dk2 = torch.empty_like(dk, dtype=torch.float) + dv = torch.empty_like(v) + dg2 = torch.empty_like(gk, dtype=torch.float) + dA = torch.empty_like(A, dtype=torch.float) + db = torch.empty_like(beta, dtype=torch.float) + prepare_wy_repr_bwd_kda_kernel[(NT, B * HV)]( + k=k, + v=v, + beta=beta, + gk=gk, + A=A, + dA=dA, + dw=dw, + du=du, + dk=dk, + dk2=dk2, + dv=dv, + db=db, + dg=dg, + dg2=dg2, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + HV=HV, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + dk = dk2 + dg = dg2 + return dk, dv, db, dg, dA diff --git a/kda/_fla/ops/utils/__init__.py b/kda/_fla/ops/utils/__init__.py new file mode 100644 index 0000000..ebe88c4 --- /dev/null +++ b/kda/_fla/ops/utils/__init__.py @@ -0,0 +1,14 @@ +from .cumsum import ( + chunk_local_cumsum, + chunk_local_cumsum_scalar, + chunk_local_cumsum_vector, +) +from .index import prepare_chunk_indices, prepare_chunk_offsets + +__all__ = [ + "chunk_local_cumsum", + "chunk_local_cumsum_scalar", + "chunk_local_cumsum_vector", + "prepare_chunk_indices", + "prepare_chunk_offsets", +] diff --git a/kda/_fla/ops/utils/cache.py b/kda/_fla/ops/utils/cache.py new file mode 100644 index 0000000..40ab2db --- /dev/null +++ b/kda/_fla/ops/utils/cache.py @@ -0,0 +1,449 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import dataclasses +import enum +import json +import logging +import os +import re +from functools import cache, lru_cache +from pathlib import Path +from typing import Any + +import torch +import triton +from packaging import version +from triton.runtime.autotuner import Autotuner + +TRITON_ABOVE_3_5_1 = version.parse(triton.__version__) >= version.parse("3.5.1") +TRITON_ABOVE_3_4_0 = version.parse(triton.__version__) >= version.parse("3.4.0") + + +class FlaCacheMode(enum.Enum): + """Controls how FLA loads kernel configs from its config cache (FLA_CACHE_MODE env var). + + DISABLED — skip all cache lookups, always fall back to Triton autotune (default when FLA_CACHE_MODE is unset) + STRICT — exact key match only; falls back to Triton autotune if no match + FUZZY — exact key match → fuzzy key match; falls back to Triton autotune if no match + FULL — exact key match → fuzzy key match → default_config fallback + DEFAULT — use only the top-level default_config field, skip key-based lookup + ALWAYS — like DEFAULT, but re-reads config files on every kernel call; + useful for debugging: edit default_config in a JSON file and the next + kernel call picks it up without restarting the process + """ + DISABLED = "disabled" + STRICT = "strict" + FUZZY = "fuzzy" + FULL = "full" + DEFAULT = "default" + ALWAYS = "always" + + def uses_default_config(self) -> bool: + """Return True for modes that may fall back to default_config (FULL, DEFAULT, ALWAYS).""" + return self in (FlaCacheMode.FULL, FlaCacheMode.DEFAULT, FlaCacheMode.ALWAYS) + + @classmethod + def from_env(cls) -> "FlaCacheMode": + mode_str = os.environ.get("FLA_CACHE_MODE", cls.DISABLED.value) + try: + return cls(mode_str) + except ValueError: + valid = [m.value for m in cls] + raise ValueError( + f"Invalid FLA_CACHE_MODE={mode_str!r}. Valid values: {valid}" + ) from None + + +FLA_CACHE_MODE: FlaCacheMode = FlaCacheMode.from_env() +logger = logging.getLogger(__name__) + + +def sanitize_gpu_name(gpu_name: str) -> str: + sanitized = re.sub(r"[^0-9A-Za-z]+", "_", gpu_name) + sanitized = sanitized.strip("_") + return sanitized or "unknown_gpu" + + +@lru_cache(maxsize=1) +def get_gpu_info(): + """Get GPU model information. + + This function detects the GPU model and returns a sanitized string identifier. + It prioritizes FLA_GPU_NAME environment variable if set, then detects from + available hardware (CUDA, ROCm, Intel GPU, or CPU). + """ + # Check if GPU name is overridden via environment variable + gpu_name = None + # Check if GPU name is overridden via environment variable + if "FLA_GPU_NAME" in os.environ: + gpu_name = os.environ["FLA_GPU_NAME"] + # Try to get device name based on availability + elif torch.cuda.is_available(): + # Works for both NVIDIA and AMD GPUs (ROCm) + gpu_name = torch.cuda.get_device_name(0) + elif hasattr(torch, 'xpu') and torch.xpu.is_available(): + gpu_name = torch.xpu.get_device_name(0) + + if gpu_name: + return sanitize_gpu_name(gpu_name) + + # Default to CPU if no GPU available + return "cpu" + + +def get_fla_config_dir() -> Path: + """Get FLA's configs directory. + + The directory can be overridden by setting the FLA_CONFIG_DIR environment variable. + If set, configs will be loaded directly from $FLA_CONFIG_DIR/. Otherwise FLA + falls back to the default fla/configs/{GPU}/ directory in the project. + """ + # Check if custom config dir is set via environment variable + if "FLA_CONFIG_DIR" in os.environ: + return Path(os.environ["FLA_CONFIG_DIR"]) + + # Default: project_dir/fla/configs/{GPU}/ + project_dir = Path(__file__).parent.parent.parent + return project_dir / "configs" / get_gpu_info() + + +@dataclasses.dataclass(frozen=True) +class AutotuneKey: + """Autotune key with exact/fuzzy matching, serialization, and construction helpers.""" + autotune_key: tuple[Any, ...] + + @staticmethod + def normalize_autotune_key(value: Any) -> Any: + if isinstance(value, (list, tuple)): + return [AutotuneKey.normalize_autotune_key(v) for v in value] + if isinstance(value, dict): + return {k: AutotuneKey.normalize_autotune_key(v) for k, v in value.items()} + return value + + @staticmethod + def serialize(key: Any) -> str: + return json.dumps(AutotuneKey.normalize_autotune_key(key), separators=(",", ":"), sort_keys=True) + + @staticmethod + def key_hash(key: Any) -> str: + import hashlib + return hashlib.md5(AutotuneKey.serialize(key).encode()).hexdigest() + + @staticmethod + def is_numeric(value: Any) -> bool: + return isinstance(value, (int, float)) and not isinstance(value, bool) + + @staticmethod + def keys_fuzzy_match(cached_key: Any, requested_key: Any) -> bool: + # Fuzzy match: numeric leaves are compatible regardless of their actual numeric values + # (e.g. a config tuned for seq_len=1024 can apply to seq_len=2048). + # Structure (type, length, dict keys) must still match exactly. + if AutotuneKey.is_numeric(cached_key) and AutotuneKey.is_numeric(requested_key): + return True + if isinstance(cached_key, (list, tuple)) and isinstance(requested_key, (list, tuple)): + return len(cached_key) == len(requested_key) and all( + AutotuneKey.keys_fuzzy_match(c, r) for c, r in zip(cached_key, requested_key) + ) + if isinstance(cached_key, dict) and isinstance(requested_key, dict): + return cached_key.keys() == requested_key.keys() and all( + AutotuneKey.keys_fuzzy_match(cached_key[k], requested_key[k]) for k in cached_key + ) + return cached_key == requested_key + + @classmethod + def build( + cls, + arg_names: list[str], + key_names: list[str], + positional_args: tuple[Any, ...], + runtime_kwargs: dict[str, Any], + ) -> "AutotuneKey": + named_args = dict(zip(arg_names, positional_args)) + all_args = {**named_args, **runtime_kwargs} + tracked_args = {k: v for (k, v) in all_args.items() if k in arg_names} + tuning_key = [tracked_args[name] for name in key_names if name in tracked_args] + for arg in tracked_args.values(): + if hasattr(arg, "dtype"): + tuning_key.append(str(arg.dtype)) + return cls(autotune_key=tuple(tuning_key)) + + def exact_matches(self, entry_key: Any) -> bool: + return self.serialize(self.autotune_key) == self.serialize(entry_key) + + def fuzzy_matches(self, entry_key: Any) -> bool: + self_normalized = self.normalize_autotune_key(self.autotune_key) + entry_normalized = self.normalize_autotune_key(entry_key) + return ( + isinstance(self_normalized, list) + and isinstance(entry_normalized, list) + and len(self_normalized) == len(entry_normalized) + and AutotuneKey.keys_fuzzy_match(self_normalized, entry_normalized) + ) + + +@dataclasses.dataclass(frozen=True) +class KernelConfigFile: + """Validated in-memory representation of a {kernel_name}.json config file.""" + kernel_name: str | None + triton_version: str | None + autotune_entries: dict[str, dict[str, Any]] | None + default_config: dict[str, Any] | None + + @classmethod + def from_dict(cls, config_file: Path, data: Any) -> "KernelConfigFile | None": + """Parse and validate a raw JSON dict. Returns None (with a warning) if malformed.""" + def fail(msg, *args): + logger.warning(msg, *args) + raise ValueError + + try: + if not isinstance(data, dict): + fail("Malformed config %s: root is %s, expected dict", config_file, type(data).__name__) + raw_entries = data.get("autotune_entries") + entries: dict[str, dict[str, Any]] | None = None + if raw_entries is not None: + if not isinstance(raw_entries, dict): + fail("Malformed config %s: 'autotune_entries' is %s, expected dict", + config_file, type(raw_entries).__name__) + for h, entry in raw_entries.items(): + if not isinstance(entry, dict): + fail("Malformed config %s: autotune_entries[%r] is %s, expected dict", + config_file, h, type(entry).__name__) + if not isinstance(entry.get("config"), dict): + fail("Malformed config %s: autotune_entries[%r] missing valid 'config' field", config_file, h) + entries = raw_entries + default_config = data.get("default_config") + if default_config is not None and not isinstance(default_config, dict): + fail("Malformed config %s: 'default_config' is %s, expected dict", config_file, type(default_config).__name__) + return cls( + kernel_name=data.get("kernel_name"), + triton_version=data.get("triton_version"), + autotune_entries=entries, + default_config=default_config, + ) + except ValueError: + return None + + @classmethod + def from_file(cls, config_file: Path) -> "KernelConfigFile | None": + """Read and validate a config file. Returns None if the file is missing or malformed.""" + config_data = read_config_file(config_file) + if config_data is None: + return None + return cls.from_dict(config_file, config_data) + + def lookup_exact(self, key: AutotuneKey) -> dict[str, Any] | None: + if self.autotune_entries is None: + return None + return self.autotune_entries.get(AutotuneKey.key_hash(key.autotune_key)) + + def lookup_fuzzy(self, key: AutotuneKey) -> dict[str, Any] | None: + if self.autotune_entries is None: + return None + for entry in self.autotune_entries.values(): + if key.fuzzy_matches(entry.get("autotune_key")): + return entry + return None + + +@cache +def load_config_file(config_file: Path) -> dict[str, Any] | None: + try: + with open(config_file) as f: + return json.load(f) + except Exception as e: + logger.warning("Error reading config file %s: %s", config_file, e) + return None + + +def read_config_file(config_file: Path) -> dict[str, Any] | None: + """Read a config file, bypassing the in-process cache in ALWAYS mode.""" + if FLA_CACHE_MODE is FlaCacheMode.ALWAYS: + return load_config_file.__wrapped__(config_file) + return load_config_file(config_file) + + +def load_cached_config(kernel_name: str, autotune_key: AutotuneKey | None = None) -> dict[str, Any] | None: + """ + Load cached best config for a kernel from FLA configs directory. + + This function loads the cached best configuration for a given kernel name + from get_fla_config_dir()/{kernel_name}.json. + + Cache files may contain multiple autotune entries keyed by Triton's + runtime tuning key plus a top-level default config. + + If the config file is not found or cannot be loaded, a warning is printed + and None is returned, allowing fallback to Triton's autotune. + + The lookup mode is controlled by the FLA_CACHE_MODE environment variable (see FlaCacheMode). + + Args: + kernel_name: Name of the kernel (e.g., "causal_conv1d_fwd_kernel") + autotune_key: Triton autotune key for the current invocation + + Returns: + Best config dictionary or None if not found or disabled + """ + if FLA_CACHE_MODE is FlaCacheMode.DISABLED: + return None + + config_dir = get_fla_config_dir() + config_file = config_dir / f"{kernel_name}.json" + + if not config_file.exists(): + return None + + config_data = read_config_file(config_file) + if config_data is None: + return None + config = KernelConfigFile.from_dict(config_file, config_data) + if config is None: + return None + + if FLA_CACHE_MODE is FlaCacheMode.DEFAULT or FLA_CACHE_MODE is FlaCacheMode.ALWAYS: + return config.default_config + + # STRICT mode: exact match only, no fuzzy fallback + if FLA_CACHE_MODE is FlaCacheMode.STRICT: + if autotune_key is not None: + entry = config.lookup_exact(autotune_key) + if entry is not None: + return entry["config"] + return None + + # FULL and FUZZY modes: try exact key match first, then fuzzy match + if autotune_key is not None: + entry = config.lookup_exact(autotune_key) or config.lookup_fuzzy(autotune_key) + if entry is not None: + return entry["config"] + + if FLA_CACHE_MODE is FlaCacheMode.FUZZY: + return None + + # FULL mode: fall back to default_config, then legacy raw config (no autotune_entries) + if config.default_config is not None: + return config.default_config + if config.autotune_entries is not None: + return None + return config_data + + +class CachedAutotuner(Autotuner): + """ + A modified autotuner that loads best config from FLA's config directory. + + This class extends Triton's Autotuner but overrides the run method to + try loading cached configuration first before falling back to autotune. + """ + + def __init__(self, fn, arg_names, configs, key, reset_to_zero, restore_value, **kwargs): + super().__init__(fn, arg_names, configs, key, reset_to_zero, restore_value, **kwargs) + self.kernel_name = fn.fn.__name__ if hasattr(fn, 'fn') else fn.__name__ + + # None-safe pre/post hooks: Triton's defaults crash when a restore_value / reset_to_zero arg + # is None (idiomatic for optional pointers gated by a tl.constexpr flag). + # Fixed upstream in triton-lang/triton#10295 — remove this override once FLA's minimum Triton version has it. + if not self.user_defined_pre_hook and (self.reset_to_zero or self.restore_value): + def _pre_hook(kw, reset_only=False): + for n in self.reset_to_zero: + if kw[n] is not None: + kw[n].zero_() + if not reset_only: + self.restore_copies = {n: kw[n].clone() for n in self.restore_value if kw[n] is not None} + self.pre_hook = _pre_hook + if not self.user_defined_post_hook and self.restore_value: + def _post_hook(kw, exception): + for n, copy in self.restore_copies.items(): + kw[n].copy_(copy) + self.restore_copies = {} + self.post_hook = _post_hook + + def should_check_fla_cache(self, key: AutotuneKey) -> bool: + if FLA_CACHE_MODE is FlaCacheMode.DISABLED: + return False + if FLA_CACHE_MODE is FlaCacheMode.ALWAYS: + return True + return key.autotune_key not in self.cache + + def run(self, *args, **kwargs): + key = AutotuneKey.build(self.arg_names, self.keys, args, kwargs) + if self.should_check_fla_cache(key): + self.maybe_load_cached_config(key) + return super().run(*args, **kwargs) + + def maybe_load_cached_config(self, key: AutotuneKey): + best_config = load_cached_config(self.kernel_name, key) + + if best_config is not None: + kw = best_config["kwargs"] + num_warps = best_config["num_warps"] + num_stages = best_config["num_stages"] + + extra = { + "num_ctas": best_config["num_ctas"], + "maxnreg": best_config.get("maxnreg"), + "pre_hook": None, + "ir_override": best_config.get("ir_override"), + } if TRITON_ABOVE_3_5_1 else {} + cfg = triton.Config(kw, num_warps=num_warps, num_stages=num_stages, **extra) + + self.cache[key.autotune_key] = cfg + else: + logger.debug( + "No cached config found for kernel %s and key %s; falling back to Triton autotune", + self.kernel_name, + list(key.autotune_key), + ) + + +def fla_cache_autotune(configs, key=None, prune_configs_by=None, reset_to_zero=None, restore_value=None, + pre_hook=None, post_hook=None, warmup=None, rep=None, use_cuda_graph=False, + do_bench=None, cache_results=False): + """ + Decorator for auto-tuning a :code:`triton.jit`'d function with FLA config support. + + Extends Triton's autotune to load best configurations from FLA's config directory + (default: fla/configs/{GPU}/, or FLA_CONFIG_DIR/ when overridden), keyed by kernel + name from {kernel_name}.json. Lookup behaviour is controlled by FLA_CACHE_MODE. + Falls back to normal Triton autotuning when no cached config is found. + """ + # key can be None when we want to use cache only (no fallback autotune) + if key is None: + key = [] + + def decorator(fn): + kwargs = {} + if TRITON_ABOVE_3_4_0: + kwargs = {"cache_results": cache_results} + + return CachedAutotuner(fn, fn.arg_names, configs, key, reset_to_zero, restore_value, + pre_hook=pre_hook, post_hook=post_hook, + prune_configs_by=prune_configs_by, warmup=warmup, rep=rep, + use_cuda_graph=use_cuda_graph, do_bench=do_bench, + **kwargs, + ) + + return decorator + + +def configure_fla_cache_autotune(): + triton.autotune = fla_cache_autotune + logger.info( + "configure_fla_cache_autotune() is enabling FLA fla_cache_autotune; " + "triton.autotune will be replaced with fla_cache_autotune." + ) + + +def restore_autotune_backend(): + from triton.runtime.autotuner import autotune as original_autotune + triton.autotune = original_autotune + logger.info( + "restore_autotune_backend() is restoring Triton's original autotune; " + "triton.autotune will be replaced with triton.runtime.autotuner.autotune." + ) diff --git a/kda/_fla/ops/utils/constant.py b/kda/_fla/ops/utils/constant.py new file mode 100644 index 0000000..3a6c837 --- /dev/null +++ b/kda/_fla/ops/utils/constant.py @@ -0,0 +1,10 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +# Approximate value of 1/ln(2), used for log/exp base conversion +# Best FP32 approximation: 1.4426950216 (hex 0x3FB8AA3B) +RCP_LN2 = 1.4426950216 diff --git a/kda/_fla/ops/utils/cumsum.py b/kda/_fla/ops/utils/cumsum.py new file mode 100644 index 0000000..fb31cf5 --- /dev/null +++ b/kda/_fla/ops/utils/cumsum.py @@ -0,0 +1,468 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import triton +import triton.language as tl + +from kda._fla.ops.backends import dispatch +from kda._fla.ops.utils.cache import fla_cache_autotune +from kda._fla.ops.utils.index import prepare_chunk_indices +from kda._fla.utils import autotune_cache_kwargs, check_shared_mem, input_guard + +BS_LIST = [32, 64] if check_shared_mem() else [16, 32] + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['B', 'H', 'BT', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_local_cumsum_scalar_kernel( + s, + o, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_bh = tl.program_id(0).to(tl.int64), tl.program_id(1).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + p_s = s + bos*H + i_h + o_t * H + p_o = o + bos*H + i_h + o_t * H + # [BT] + b_s = tl.load(p_s, mask=m_t, other=0.0).to(tl.float32) + if REVERSE: + b_o = tl.cumsum(b_s, axis=0, reverse=True) + else: + b_o = tl.cumsum(b_s, axis=0) + if HAS_SCALE: + b_o *= scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=m_t) + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@fla_cache_autotune( + configs=[ + triton.Config({'BS': BS}, num_warps=num_warps) + for BS in BS_LIST + for num_warps in [2, 4, 8] + ], + key=['B', 'H', 'S', 'BT', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_local_cumsum_vector_kernel( + s, + o, + scale, + cu_seqlens, + chunk_indices, + T, + B: tl.constexpr, + H: tl.constexpr, + S: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_s, i_t, i_bh = tl.program_id(0), tl.program_id(1).to(tl.int64), tl.program_id(2).to(tl.int64) + i_b, i_h = i_bh // H, i_bh % H + if IS_VARLEN: + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int64) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + + o_t = i_t * BT + tl.arange(0, BT) + o_s = i_s * BS + tl.arange(0, BS) + m_s = (o_t[:, None] < T) & (o_s[None, :] < S) + p_s = s + (bos * H + i_h) * S + o_t[:, None] * (H*S) + o_s[None, :] + p_o = o + (bos * H + i_h) * S + o_t[:, None] * (H*S) + o_s[None, :] + # [BT, BS] + b_s = tl.load(p_s, mask=m_s, other=0.0).to(tl.float32) + if REVERSE: + b_o = tl.cumsum(b_s, axis=0, reverse=True) + else: + b_o = tl.cumsum(b_s, axis=0) + if HAS_SCALE: + b_o *= scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=m_s) + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps, num_stages=num_stages) + for BT in [32, 64, 128, 256] + for num_warps in [2, 4, 8] + for num_stages in [1, 2, 3, 4] + ], + key=['B', 'H', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_global_cumsum_scalar_kernel( + s, + o, + scale, + cu_seqlens, + T, + B: tl.constexpr, + H: tl.constexpr, + BT: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_nh = tl.program_id(0).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + else: + bos, eos = i_n * T, i_n * T + T + T = eos - bos + + b_z = tl.zeros([], dtype=tl.float32) + NT = tl.cdiv(T, BT) + for i_c in range(NT): + i_t = NT - 1 - i_c if REVERSE else i_c + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_t < T + p_s = s + bos*H + i_h + o_t * H + p_o = o + bos*H + i_h + o_t * H + b_s = tl.load(p_s, mask=m_t, other=0.0).to(tl.float32) + if REVERSE: + b_o = tl.cumsum(b_s, axis=0, reverse=True) + else: + b_o = tl.cumsum(b_s, axis=0) + b_ss = tl.sum(b_s, 0) + b_o += b_z + if i_c >= 0: + b_z += b_ss + if HAS_SCALE: + b_o *= scale + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=m_t) + + +@triton.heuristics({ + 'HAS_SCALE': lambda args: args['scale'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BT': BT}, num_warps=num_warps, num_stages=num_stages) + for BT in [16, 32, 64, 128] + for num_warps in [2, 4, 8] + for num_stages in [1, 2, 3, 4] + ], + key=['B', 'H', 'S', 'IS_VARLEN', 'REVERSE'], + **autotune_cache_kwargs, +) +@triton.jit(do_not_specialize=['T']) +def chunk_global_cumsum_vector_kernel( + s, + o, + scale, + cu_seqlens, + T, + B: tl.constexpr, + H: tl.constexpr, + S: tl.constexpr, + BT: tl.constexpr, + BS: tl.constexpr, + REVERSE: tl.constexpr, + HAS_SCALE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_s, i_nh = tl.program_id(0), tl.program_id(1).to(tl.int64) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + else: + bos, eos = i_n * T, i_n * T + T + T = eos - bos + + b_z = tl.zeros([BS], dtype=tl.float32) + NT = tl.cdiv(T, BT) + for i_c in range(NT): + i_t = NT - 1 - i_c if REVERSE else i_c + o_t = i_t * BT + tl.arange(0, BT) + o_s = i_s * BS + tl.arange(0, BS) + m_s = (o_t[:, None] < T) & (o_s[None, :] < S) + p_s = s + (bos * H + i_h) * S + o_t[:, None] * (H*S) + o_s[None, :] + p_o = o + (bos * H + i_h) * S + o_t[:, None] * (H*S) + o_s[None, :] + # [BT, BS] + b_s = tl.load(p_s, mask=m_s, other=0.0).to(tl.float32) + if REVERSE: + b_c = b_z[None, :] + tl.cumsum(b_s, axis=0, reverse=True) + else: + b_c = b_z[None, :] + tl.cumsum(b_s, axis=0) + if HAS_SCALE: + b_c *= scale + tl.store(p_o, b_c.to(p_o.dtype.element_ty), mask=m_s) + b_z += tl.sum(b_s, 0) + + +def chunk_local_cumsum_scalar( + g: torch.Tensor, + chunk_size: int, + reverse: bool = False, + scale: float = None, + cu_seqlens: torch.Tensor | None = None, + output_dtype: torch.dtype | None = torch.float, + chunk_indices: torch.LongTensor | None = None, + **kwargs, +) -> torch.Tensor: + if 'head_first' in kwargs: + raise DeprecationWarning( + "head_first has been removed. Inputs must be in `[B, T, H, ...]` format.", + ) + B, T, H = g.shape + assert chunk_size == 2**(chunk_size.bit_length()-1), "chunk_size must be a power of 2" + BT = chunk_size + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + g_org, g = g, torch.empty_like(g, dtype=output_dtype or g.dtype) + grid = (NT, B * H) + chunk_local_cumsum_scalar_kernel[grid]( + s=g_org, + o=g, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + B=B, + H=H, + BT=BT, + REVERSE=reverse, + ) + return g + + +def chunk_local_cumsum_vector( + g: torch.Tensor, + chunk_size: int, + reverse: bool = False, + scale: float = None, + cu_seqlens: torch.Tensor | None = None, + output_dtype: torch.dtype | None = torch.float, + chunk_indices: torch.LongTensor | None = None, + **kwargs, +) -> torch.Tensor: + if 'head_first' in kwargs: + raise DeprecationWarning( + "head_first has been removed. Inputs must be in `[B, T, H, ...]` format.", + ) + B, T, H, S = g.shape + BT = chunk_size + if chunk_indices is None and cu_seqlens is not None: + chunk_indices = prepare_chunk_indices(cu_seqlens, BT) + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) + assert chunk_size == 2**(chunk_size.bit_length()-1), "chunk_size must be a power of 2" + + g_org, g = g, torch.empty_like(g, dtype=output_dtype or g.dtype) + def grid(meta): return (triton.cdiv(meta['S'], meta['BS']), NT, B * H) + # keep cummulative normalizer in fp32 + # this kernel is equivalent to + # g = g.view(B, H, NT, BT, -1).cumsum(-2).view(B, H, T, -1) + chunk_local_cumsum_vector_kernel[grid]( + s=g_org, + o=g, + scale=scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + B=B, + H=H, + S=S, + BT=BT, + REVERSE=reverse, + ) + return g + + +@input_guard +def chunk_global_cumsum_scalar( + s: torch.Tensor, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, + scale: float = None, + output_dtype: torch.dtype | None = torch.float, + **kwargs, +) -> torch.Tensor: + if 'head_first' in kwargs: + raise DeprecationWarning( + "head_first has been removed. Inputs must be in `[B, T, H, ...]` format.", + ) + B, T, H = s.shape + N = len(cu_seqlens) - 1 if cu_seqlens is not None else B + + z = torch.empty_like(s, dtype=output_dtype or s.dtype) + grid = (N * H,) + chunk_global_cumsum_scalar_kernel[grid]( + s=s, + o=z, + scale=scale, + cu_seqlens=cu_seqlens, + T=T, + B=B, + H=H, + REVERSE=reverse, + ) + return z + + +@input_guard +def chunk_global_cumsum_vector( + s: torch.Tensor, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, + scale: float = None, + output_dtype: torch.dtype | None = torch.float, + **kwargs, +) -> torch.Tensor: + if 'head_first' in kwargs: + raise DeprecationWarning( + "head_first has been removed. Inputs must be in `[B, T, H, ...]` format.", + ) + B, T, H, S = s.shape + N = len(cu_seqlens) - 1 if cu_seqlens is not None else B + BS = min(32, triton.next_power_of_2(S)) + + z = torch.empty_like(s, dtype=output_dtype or s.dtype) + grid = (triton.cdiv(S, BS), N * H) + chunk_global_cumsum_vector_kernel[grid]( + s=s, + o=z, + scale=scale, + cu_seqlens=cu_seqlens, + T=T, + B=B, + H=H, + S=S, + BS=BS, + REVERSE=reverse, + ) + return z + + +@input_guard +@dispatch('utils') +def chunk_global_cumsum( + s: torch.Tensor, + reverse: bool = False, + cu_seqlens: torch.Tensor | None = None, + scale: float = None, + output_dtype: torch.dtype | None = torch.float, + **kwargs, +) -> torch.Tensor: + if 'head_first' in kwargs: + raise DeprecationWarning( + "head_first has been removed. Inputs must be in `[B, T, H, ...]` format.", + ) + if cu_seqlens is not None: + assert s.shape[0] == 1, "Only batch size 1 is supported when cu_seqlens are provided" + if len(s.shape) == 3: + return chunk_global_cumsum_scalar( + s=s, + reverse=reverse, + cu_seqlens=cu_seqlens, + scale=scale, + output_dtype=output_dtype, + ) + elif len(s.shape) == 4: + return chunk_global_cumsum_vector( + s=s, + reverse=reverse, + cu_seqlens=cu_seqlens, + scale=scale, + output_dtype=output_dtype, + ) + else: + raise ValueError( + f"Unsupported input shape {s.shape}, " + f"which should be [B, T, H] or [B, T, H, D]", + ) + + +@input_guard +@dispatch('utils') +def chunk_local_cumsum( + g: torch.Tensor, + chunk_size: int, + reverse: bool = False, + scale: float = None, + cu_seqlens: torch.Tensor | None = None, + output_dtype: torch.dtype | None = torch.float, + chunk_indices: torch.LongTensor | None = None, + **kwargs, +) -> torch.Tensor: + if 'head_first' in kwargs: + raise DeprecationWarning( + "head_first has been removed. Inputs must be in `[B, T, H, ...]` format.", + ) + if cu_seqlens is not None: + assert g.shape[0] == 1, "Only batch size 1 is supported when cu_seqlens are provided" + if len(g.shape) == 3: + return chunk_local_cumsum_scalar( + g=g, + chunk_size=chunk_size, + reverse=reverse, + scale=scale, + cu_seqlens=cu_seqlens, + output_dtype=output_dtype, + chunk_indices=chunk_indices, + ) + elif len(g.shape) == 4: + return chunk_local_cumsum_vector( + g=g, + chunk_size=chunk_size, + reverse=reverse, + scale=scale, + cu_seqlens=cu_seqlens, + output_dtype=output_dtype, + chunk_indices=chunk_indices, + ) + else: + raise ValueError( + f"Unsupported input shape {g.shape}, " + f"which should be (B, T, H) or (B, T, H, D)", + ) diff --git a/kda/_fla/ops/utils/index.py b/kda/_fla/ops/utils/index.py new file mode 100644 index 0000000..c5bb8f8 --- /dev/null +++ b/kda/_fla/ops/utils/index.py @@ -0,0 +1,183 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import torch +import torch.nn.functional as F +import triton +import triton.language as tl + +from kda._fla.utils import autotune_cache_kwargs, tensor_cache + + +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [4, 8, 16, 32] + ], + key=['B'], + **autotune_cache_kwargs, +) +@triton.jit +def prepare_position_ids_kernel( + y, + cu_seqlens, + B: tl.constexpr, +): + i_n = tl.program_id(0) + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) + T = eos - bos + + o = tl.arange(0, B) + for i in range(0, tl.cdiv(T, B) * B, B): + o_i = o + i + tl.store(y + bos + o_i, o_i, o_i < T) + + +@tensor_cache +def prepare_lens(cu_seqlens: torch.LongTensor) -> torch.LongTensor: + return torch.diff(cu_seqlens) + + +@tensor_cache +def prepare_lens_from_mask(mask: torch.BoolTensor) -> torch.LongTensor: + return mask.sum(dim=-1, dtype=torch.int32) + + +@tensor_cache +def prepare_cu_seqlens_from_lens( + lens: torch.LongTensor, + dtype: torch.dtype | None = torch.int32, +) -> torch.LongTensor: + return F.pad(lens.cumsum(dim=0, dtype=dtype), (1, 0)) + + +@tensor_cache +def prepare_cu_seqlens_from_mask( + mask: torch.BoolTensor, + dtype: torch.dtype | None = torch.int32, +) -> torch.LongTensor: + return prepare_cu_seqlens_from_lens(prepare_lens_from_mask(mask), dtype) + + +@tensor_cache +def prepare_split_cu_seqlens( + batch_size: int | None = None, + seq_len: int | None = None, + split_size: int | None = None, + cu_seqlens: torch.LongTensor | None = None, + dtype: torch.dtype | None = torch.int32, + device: torch.device | None = torch.device('cpu'), +) -> torch.LongTensor: + """Sub-split a (optionally packed) batch along the token axis. + + Two calling modes: + - **Rectangular batch**: pass `batch_size` and `seq_len`, leave + `cu_seqlens=None`. Internally synthesizes `[0, L, 2L, ..., B*L]`. + - **Packed varlen**: pass `cu_seqlens`. `batch_size` and `seq_len` are + ignored (kept as optional kwargs for backward-compat with callers + that used to pass dummies). + + `split_size` is always required. + + The legacy positional signature `(batch_size, seq_len, split_size, ...)` + continues to work — the first two args retain their position but may now + be omitted when `cu_seqlens` is supplied. + """ + if split_size is None: + raise TypeError("prepare_split_cu_seqlens() requires `split_size`") + if cu_seqlens is None: + if batch_size is None or seq_len is None: + raise TypeError( + "prepare_split_cu_seqlens() requires either `cu_seqlens`, " + "or both `batch_size` and `seq_len`" + ) + total_tokens = batch_size * seq_len + cu_seqlens = list(range(0, total_tokens, seq_len)) + [total_tokens] + else: + cu_seqlens = cu_seqlens.tolist() + return torch.tensor( + [ + i + for bos, eos in zip(cu_seqlens[:-1], cu_seqlens[1:], strict=False) + for i in range(bos, eos, split_size) + ] + [cu_seqlens[-1]], + dtype=dtype, + device=device, + ) + + +def _segmented_arange(counts: torch.LongTensor) -> tuple[torch.LongTensor, torch.LongTensor]: + """Expand per-segment counts into flat per-slot index tensors. + + Given segment sizes ``counts = [c0, c1, ...]``, return two 1-D tensors of + length ``counts.sum()`` that together label every slot with its segment and + its position within that segment. + + Example -- ``counts = [2, 3]`` (segment 0 spans 2 slots, segment 1 spans 3):: + + seg_id = [0, 0, 1, 1, 1] # which segment each slot belongs to + intra_idx = [0, 1, 0, 1, 2] # running index within that segment + + With CUDA ``counts``, ``repeat_interleave`` reads ``counts.sum()`` on the + host (one device sync). Pass host-side counts to avoid it. + """ + seg_id = torch.repeat_interleave( + torch.arange(counts.numel(), device=counts.device, dtype=counts.dtype), + counts, + ) + seg_start = F.pad(counts.cumsum(0), (1, 0))[:-1] + intra_idx = torch.arange(seg_id.shape[0], device=counts.device, dtype=counts.dtype) - seg_start[seg_id] + return seg_id, intra_idx + + +@tensor_cache +def prepare_position_ids(cu_seqlens: torch.LongTensor, cu_seqlens_cpu: torch.LongTensor | None = None) -> torch.LongTensor: + src = cu_seqlens_cpu if cu_seqlens_cpu is not None else cu_seqlens + _, position_ids = _segmented_arange(prepare_lens(src)) + return position_ids.to(cu_seqlens) + + +@tensor_cache +def prepare_sequence_ids(cu_seqlens: torch.LongTensor, cu_seqlens_cpu: torch.LongTensor | None = None) -> torch.LongTensor: + return prepare_position_ids(cu_seqlens, cu_seqlens_cpu).eq(0).cumsum(0) - 1 + + +@tensor_cache +def prepare_token_indices(cu_seqlens: torch.LongTensor, cu_seqlens_cpu: torch.LongTensor | None = None) -> torch.LongTensor: + position_ids = prepare_position_ids(cu_seqlens, cu_seqlens_cpu) + return torch.stack([prepare_sequence_ids(cu_seqlens, cu_seqlens_cpu), position_ids], 1).to(cu_seqlens) + + +@tensor_cache +def prepare_chunk_indices( + cu_seqlens: torch.LongTensor, + chunk_size: int, + cu_seqlens_cpu: torch.LongTensor | None = None, +) -> torch.LongTensor: + src = cu_seqlens_cpu if cu_seqlens_cpu is not None else cu_seqlens + chunk_counts = (prepare_lens(src) + (chunk_size - 1)).div(chunk_size, rounding_mode='floor') + seg_id, intra_chunk_idx = _segmented_arange(chunk_counts) + return torch.stack([seg_id, intra_chunk_idx], 1).to(cu_seqlens) + + +@tensor_cache +def prepare_chunk_offsets( + cu_seqlens: torch.LongTensor, + chunk_size: int, +) -> torch.LongTensor: + return F.pad(triton.cdiv(prepare_lens(cu_seqlens), chunk_size), (1, 0), value=0).cumsum(-1) + + +@tensor_cache +def get_max_num_splits( + cu_seqlens: torch.LongTensor, + chunk_size: int, + cu_seqlens_cpu: torch.LongTensor | None = None +) -> int: + if cu_seqlens_cpu is not None: + return triton.cdiv(int(max(prepare_lens(cu_seqlens_cpu))), chunk_size) + return triton.cdiv(int(max(prepare_lens(cu_seqlens))), chunk_size) diff --git a/kda/_fla/ops/utils/op.py b/kda/_fla/ops/utils/op.py new file mode 100644 index 0000000..882b605 --- /dev/null +++ b/kda/_fla/ops/utils/op.py @@ -0,0 +1,101 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import os + +import triton +import triton.language as tl +import triton.language.extra.libdevice as tldevice + +from kda._fla.utils import IS_GATHER_SUPPORTED, IS_NVIDIA_BLACKWELL + +if os.environ.get('FLA_USE_FAST_OPS', '0') == '1': + @triton.jit + def exp(x): return tldevice.fast_expf(x.to(tl.float32)) + @triton.jit + def exp2(x): return tldevice.exp2(x.to(tl.float32)) + @triton.jit + def log(x): return tldevice.fast_logf(x.to(tl.float32)) + @triton.jit + def log2(x): return tldevice.fast_log2f(x.to(tl.float32)) + @triton.jit + def tanh(x): return tldevice.fast_tanhf(x.to(tl.float32)) +else: + @triton.jit + def exp(x): return tl.exp(x.to(tl.float32)) + @triton.jit + def exp2(x): return tl.math.exp2(x.to(tl.float32)) + @triton.jit + def log(x): return tl.log(x.to(tl.float32)) + @triton.jit + def log2(x): return tl.log2(x.to(tl.float32)) + @triton.jit + def tanh(x): return tldevice.tanh(x.to(tl.float32)) + + +if IS_NVIDIA_BLACKWELL: + """ + Compute tl.dot with Blackwell workaround. + + On SM100 datacenter and SM120 consumer Blackwell GPUs, wraps the result in + inline assembly to prevent the TritonGPUHoistTMEMAlloc pass from incorrectly + fusing add and dot operations. + See: https://github.com/fla-org/flash-linear-attention/issues/638 + + TODO: Remove this workaround once the Triton compiler bug is fixed. + Track upstream issue at: https://github.com/triton-lang/triton/issues/8695 + """ + @triton.jit + def safe_dot(a, b, allow_tf32: tl.constexpr = None): + return tl.inline_asm_elementwise( + asm="mov.f32 $0, $1;", + constraints="=r,r", + args=[tl.dot(a, b, allow_tf32=allow_tf32)], + dtype=tl.float32, + is_pure=True, + pack=1, + ) +else: + @triton.jit + def safe_dot(a, b, allow_tf32: tl.constexpr = None): + return tl.dot(a, b, allow_tf32=allow_tf32) + + +if not IS_GATHER_SUPPORTED: + @triton.jit + def gather(src, index, axis, _builder=None): + """ + Gather operation that works when tl.gather is not supported. + This is a fallback implementation that returns None. + Just to make triton compiler happy. + """ + return None +else: + gather = tl.gather + + +if hasattr(triton.language, '_experimental_make_tensor_descriptor'): + # For Triton 3.3.x + make_tensor_descriptor = triton.language._experimental_make_tensor_descriptor +elif hasattr(triton.language, 'make_tensor_descriptor'): + # For Triton 3.4.x and later + make_tensor_descriptor = triton.language.make_tensor_descriptor +else: + """ + Fallback implementation when TMA is not supported. + Returns None to indicate TMA descriptors are unavailable. + Just make triton compiler happy. + """ + @triton.jit + def make_tensor_descriptor( + base, + shape, + strides, + block_shape, + _builder=None, + ): + return None diff --git a/kda/_fla/ops/utils/softplus.py b/kda/_fla/ops/utils/softplus.py new file mode 100644 index 0000000..e6f9d0a --- /dev/null +++ b/kda/_fla/ops/utils/softplus.py @@ -0,0 +1,115 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +# REVISED FROM +# https://github.com/shawntan/stickbreaking-attention/blob/main/stickbreaking_attention/sb_varlen/softplus.py + +import triton +from triton import language as tl + +from kda._fla.utils import IS_NVIDIA + + +def _generate_softplus(num_pack): + template = """ + .reg .pred p; + setp.gt.f32 p, ${in_reg}, 20.; + @p mov.f32 ${out_reg}, ${in_reg}; + @!p mul.f32 ${out_reg}, ${in_reg}, 1.4426950408889634; + @!p ex2.approx.ftz.f32 ${out_reg}, ${out_reg}; + @!p add.f32 ${out_reg}, ${out_reg}, 1.0; + @!p lg2.approx.ftz.f32 ${out_reg}, ${out_reg}; + @!p mul.f32 ${out_reg}, ${out_reg}, 0.6931471805599453; + """ + out_str = "" + + for i in range(num_pack): + inner_str = template.format(out_reg=i, in_reg=i + num_pack) + out_str += "{" + inner_str + "}\n" + # flatten out because torch.compile doesn't like newlines + out_str = " ".join(out_str.split("\n")) + return out_str + + +def _generate_softplus2(num_pack): + template = """ + .reg .pred p; + setp.gt.f32 p, ${in_reg}, 15.; + @p mov.f32 ${out_reg}, ${in_reg}; + @!p ex2.approx.ftz.f32 ${out_reg}, ${in_reg}; + @!p add.f32 ${out_reg}, ${out_reg}, 1.0; + @!p lg2.approx.ftz.f32 ${out_reg}, ${out_reg}; + """ + out_str = "" + + for i in range(num_pack): + inner_str = template.format(out_reg=i, in_reg=i + num_pack) + out_str += "{" + inner_str + "}\n" + # flatten out because torch.compile doesn't like newlines + out_str = " ".join(out_str.split("\n")) + return out_str + + +def _generate_constraints(num_pack): + return ",".join("=r" for i in range(num_pack)) + "," + ",".join("r" for i in range(num_pack)) + + +_NUM_REG = 1 +s_softplus: tl.constexpr = tl.constexpr(_generate_softplus(_NUM_REG)) +s_softplus2: tl.constexpr = tl.constexpr(_generate_softplus2(_NUM_REG)) +s_constraints: tl.constexpr = tl.constexpr(_generate_constraints(_NUM_REG)) +NUM_REG: tl.constexpr = tl.constexpr(_NUM_REG) + + +@triton.jit +def softplus_nv(x): + # equivalent to: + # return tl.where(x < 20.0, tl.math.log(1 + tl.math.exp(x)), x) + return tl.inline_asm_elementwise( + asm=s_softplus, + constraints=s_constraints, + pack=NUM_REG, + args=[ + x, + ], + dtype=tl.float32, + is_pure=True, + ) + + +@triton.jit +def softplus_triton(x): + return tl.where(x < 20.0, tl.math.log(1 + tl.math.exp(x)), x) + + +@triton.jit +def softplus2_nv(x): + # equivalent to: + # return tl.where(x < 15.0, tl.math.log2(1 + tl.math.exp2(x)), x) + return tl.inline_asm_elementwise( + asm=s_softplus2, + constraints=s_constraints, + pack=NUM_REG, + args=[ + x, + ], + dtype=tl.float32, + is_pure=True, + ) + + +@triton.jit +def softplus2_triton(x): + return tl.where(x < 15.0, tl.math.log2(1 + tl.math.exp2(x)), x) + + +if IS_NVIDIA: + softplus = softplus_nv + softplus2 = softplus2_nv +else: + softplus = softplus_triton + softplus2 = softplus2_triton diff --git a/kda/_fla/utils/__init__.py b/kda/_fla/utils/__init__.py new file mode 100644 index 0000000..8272e83 --- /dev/null +++ b/kda/_fla/utils/__init__.py @@ -0,0 +1,92 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import sys + +from ._compat import ( # noqa: F401 + SUPPORTS_AUTOTUNE_CACHE, + TRITON_ABOVE_3_4_0, + TRITON_ABOVE_3_5_1, + TRITON_ABOVE_3_7_1, + autotune_cache_kwargs, + find_spec_cached, + has_usable_nvcc, +) +from ._config import ( # noqa: F401 + FLA_CACHE_RESULTS, + FLA_CI_ENV, + FLA_DISABLE_TENSOR_CACHE, + FLA_TENSOR_CACHE_SIZE, +) +from ._decorators import ( # noqa: F401 + Action, + checkpoint, + contiguous, + deprecate_kwarg, + input_guard, + require_version, + tensor_cache, +) +from ._device import ( # noqa: F401 + IS_AMD, + IS_ARM, + IS_GATHER_SUPPORTED, + IS_INTEL, + IS_INTEL_ALCHEMIST, + IS_NPU, + IS_NVIDIA, + IS_NVIDIA_BLACKWELL, + IS_NVIDIA_HOPPER, + IS_NVIDIA_SM100, + IS_NVIDIA_SM120, + IS_TF32_SUPPORTED, + IS_TMA_SUPPORTED, + Backend, + autocast_custom_bwd, + autocast_custom_fwd, + check_environments, + check_pytorch_version, + check_shared_mem, + custom_device_ctx, + device, + device_name, + device_platform, + device_torch_lib, + get_all_max_shared_mem, + get_available_device, + get_device_capability, + get_device_smem_optin, + get_multiprocessor_count, + map_triton_backend_to_torch_device, +) +from ._testing import assert_close, get_abs_err, get_err_ratio # noqa: F401 + + +def _register_aliases(): + current_module = sys.modules[__name__] + for key in ( + 'IS_AMD', + 'IS_ARM', + 'IS_INTEL', + 'IS_INTEL_ALCHEMIST', + 'IS_NVIDIA', + 'IS_NPU', + 'IS_NVIDIA_BLACKWELL', + 'IS_NVIDIA_HOPPER', + 'IS_NVIDIA_SM100', + 'IS_NVIDIA_SM120', + 'IS_TF32_SUPPORTED', + 'IS_GATHER_SUPPORTED', + 'IS_TMA_SUPPORTED', + ): + if hasattr(current_module, key): + setattr(current_module, key.lower(), getattr(current_module, key)) + + +_register_aliases() + +del _register_aliases diff --git a/kda/_fla/utils/_compat.py b/kda/_fla/utils/_compat.py new file mode 100644 index 0000000..24b463a --- /dev/null +++ b/kda/_fla/utils/_compat.py @@ -0,0 +1,65 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import functools +import importlib.metadata +import inspect +import logging +import os +import shutil +from importlib.util import find_spec +from pathlib import Path + +import triton +from packaging import version as package_version + +from ._config import FLA_CACHE_RESULTS + +logger = logging.getLogger(__name__) + +TRITON_ABOVE_3_4_0 = package_version.parse(triton.__version__) >= package_version.parse("3.4.0") +TRITON_ABOVE_3_5_1 = package_version.parse(triton.__version__) >= package_version.parse("3.5.1") +TRITON_ABOVE_3_7_1 = package_version.parse(triton.__version__) >= package_version.parse("3.7.1") + +SUPPORTS_AUTOTUNE_CACHE = "cache_results" in inspect.signature(triton.autotune).parameters +autotune_cache_kwargs = {"cache_results": FLA_CACHE_RESULTS} if SUPPORTS_AUTOTUNE_CACHE else {} + + +@functools.cache +def find_spec_cached(name): + return find_spec(name) + + +@functools.cache +def has_usable_nvcc() -> bool: + """Whether a usable nvcc compiler is available for TileLang's JIT. + + Mirrors the guesses in ``tilelang.env._find_cuda_home`` (env + CUDA_HOME/CUDA_PATH, nvcc on PATH, the ``nvidia-cuda-nvcc`` wheel, + /usr/local/cuda), but verifies the nvcc binary actually exists — + only ``nvidia-cuda-nvcc`` >= 13.0 ships it, the ``-cu12`` variant + installs just ptxas. + """ + cuda_home = os.environ.get("CUDA_HOME") or os.environ.get("CUDA_PATH") + if cuda_home is not None and (Path(cuda_home) / "bin" / "nvcc").exists(): + return True + if shutil.which("nvcc") is not None: + return True + try: + files = importlib.metadata.files("nvidia-cuda-nvcc") or [] + except importlib.metadata.PackageNotFoundError: + files = [] + if any(f.name in ("nvcc", "nvcc.exe") for f in files): + return True + if (Path("/usr/local/cuda") / "bin" / "nvcc").exists(): + return True + + logger.info( + "[FLA Backend] TileLang is installed but no usable nvcc compiler was found; falling back to Triton. " + "Install a CUDA toolkit or nvidia-cuda-nvcc, or set FLA_TILELANG=0 to disable TileLang explicitly." + ) + return False diff --git a/kda/_fla/utils/_config.py b/kda/_fla/utils/_config.py new file mode 100644 index 0000000..79b6ce8 --- /dev/null +++ b/kda/_fla/utils/_config.py @@ -0,0 +1,17 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import os + +FLA_CI_ENV = os.getenv("FLA_CI_ENV") == "1" +FLA_CACHE_RESULTS = os.getenv('FLA_CACHE_RESULTS', '1') == '1' + +FLA_DISABLE_TENSOR_CACHE = os.getenv('FLA_DISABLE_TENSOR_CACHE', '0') == '1' +try: + FLA_TENSOR_CACHE_SIZE = int(os.getenv('FLA_TENSOR_CACHE_SIZE', "4")) +except ValueError: + FLA_TENSOR_CACHE_SIZE = 4 diff --git a/kda/_fla/utils/_decorators.py b/kda/_fla/utils/_decorators.py new file mode 100644 index 0000000..ca40129 --- /dev/null +++ b/kda/_fla/utils/_decorators.py @@ -0,0 +1,336 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import contextlib +import functools +import inspect +import sys +import warnings +from collections import deque +from collections.abc import Callable +from enum import Enum +from typing import Any + +import torch +from packaging import version as package_version + +from .. import __version__ +from ._config import FLA_DISABLE_TENSOR_CACHE, FLA_TENSOR_CACHE_SIZE +from ._device import custom_device_ctx + + +class Action(Enum): + NONE = "none" + NOTIFY = "notify" + NOTIFY_ALWAYS = "notify_always" + RAISE = "raise" + + +def tensor_cache( + fn: Callable[..., torch.Tensor], +) -> Callable[..., torch.Tensor]: + """ + A decorator that memoizes the most recent results of a function call by argument identity. + + The decorator keeps a bounded queue of up to ``FLA_TENSOR_CACHE_SIZE`` (default 4) + recent ``(args, kwargs, result)`` triples. On each call, every cached entry is checked + in order; an entry is considered a hit when the positional arg count and kwarg key set + match and every argument is the *same object* (``is`` identity) as the cached one. On a + hit the cached result is returned and ``fn`` is skipped; on a miss ``fn`` is invoked and + the new triple is appended (evicting the oldest when the queue is full). + + Caching is fully bypassed when the ``FLA_DISABLE_TENSOR_CACHE`` environment variable is + set to ``'1'``. + + Args: + fn (Callable[..., torch.Tensor]): + The function to be decorated. Intended for functions whose inputs are tensors + (or other objects compared by identity) and whose output is a tensor. + + Returns: + Callable[..., torch.Tensor]: + A wrapped version of ``fn`` backed by an identity-based bounded cache. + """ + cached: deque = deque(maxlen=FLA_TENSOR_CACHE_SIZE) + + def cache_disabled() -> bool: + utils_module = sys.modules.get('kda._fla.utils') + return getattr(utils_module, 'FLA_DISABLE_TENSOR_CACHE', FLA_DISABLE_TENSOR_CACHE) + + @functools.wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> Any: + if cache_disabled(): + return fn(*args, **kwargs) + + for cached_args, cached_kwargs, cached_result in cached: + if len(args) != len(cached_args) or len(kwargs) != len(cached_kwargs): + continue + if all(a is b for a, b in zip(args, cached_args, strict=False)) and \ + all(k in cached_kwargs and v is cached_kwargs[k] for k, v in kwargs.items()): + return cached_result + + result = fn(*args, **kwargs) + cached.append((args, kwargs, result)) + return result + + return wrapper + + +def _skip_contiguous( + no_guard_contiguous: bool | list[str] | tuple[str, ...] | set[str], + param_name: str, + skip_params: set[str], +) -> bool: + return no_guard_contiguous is True or param_name in skip_params + + +def _contiguous_if_needed(arg: Any, skip: bool) -> Any: + if isinstance(arg, torch.Tensor) and not skip: + return arg.contiguous() + return arg + + +def input_guard( + fn: Callable[..., torch.Tensor] | None = None, + *, + no_guard_contiguous: bool | list[str] | tuple[str, ...] | set[str] = False, +) -> Callable[[Callable[..., torch.Tensor]], Callable[..., torch.Tensor]] | Callable[..., torch.Tensor]: + """ + A decorator to make sure all input tensors are contiguous and set the device based on input tensors. + + Args: + no_guard_contiguous (bool | list[str] | tuple[str, ...] | set[str]): + If True, skip all contiguous checks. If a list/tuple/set of parameter names, skip contiguous check for those parameters. + """ + + def decorator(fn: Callable[..., torch.Tensor]) -> Callable[..., torch.Tensor]: + # Get function signature for parameter name mapping + sig = inspect.signature(fn) + param_names = list(sig.parameters.keys()) + skip_params = set(no_guard_contiguous) if isinstance(no_guard_contiguous, (list, tuple, set)) else set() + + @functools.wraps(fn) + def wrapper(*args, **kwargs): + # Process args with parameter name mapping + processed_args = [] + for i, arg in enumerate(args): + if i < len(param_names): + param_name = param_names[i] + else: + # For *args beyond signature, use position as name + param_name = f"__arg_{i}" + + processed_args.append(_contiguous_if_needed( + arg, _skip_contiguous(no_guard_contiguous, param_name, skip_params))) + + # Process kwargs + processed_kwargs = {} + for k, v in kwargs.items(): + processed_kwargs[k] = _contiguous_if_needed(v, _skip_contiguous(no_guard_contiguous, k, skip_params)) + + tensor = None + for arg in args: + if isinstance(arg, torch.Tensor): + tensor = arg + break + if tensor is None: + for value in kwargs.values(): + if isinstance(value, torch.Tensor): + tensor = value + break + + if tensor is not None: + ctx = custom_device_ctx(tensor.device.index) + else: + ctx = contextlib.nullcontext() + + with ctx: + return fn(*processed_args, **processed_kwargs) + + return wrapper + + # Handle direct usage without parentheses: @input_guard + if fn is not None: + return decorator(fn) + + return decorator + + +def contiguous(fn: Callable[..., torch.Tensor]) -> Callable[..., torch.Tensor]: + """Alias for input_guard() without parameters.""" + return input_guard(fn) + + +def require_version(version, hint): + """ + Perform a runtime check of the dependency versions, using the exact same syntax used by pip. + """ + def decorator(fn): + @functools.wraps(fn) + def wrapper(ctx, *args, **kwargs): + from transformers.utils.versions import require_version + require_version(version, hint) + return fn( + ctx, + *(i if not isinstance(i, torch.Tensor) else i.contiguous() for i in args), + **{k: (v if not isinstance(v, torch.Tensor) else v.contiguous()) for k, v in kwargs.items()}, + ) + return wrapper + return decorator + + +def deprecate_kwarg( + old_name: str, + version: str, + new_name: str | None = None, + warn_if_greater_or_equal_version: bool = False, + raise_if_greater_or_equal_version: bool = False, + raise_if_both_names: bool = False, + additional_message: str | None = None, +): + """ + Decorator to notify users about deprecated keyword arguments, replacing them with a new name if specified. + + This decorator allows you to: + - Notify users when a keyword argument is deprecated. + - Automatically replace deprecated keyword arguments with new ones. + - Raise an error if deprecated arguments are used, depending on the specified conditions. + + By default, the decorator notifies the user about the deprecated argument while the `fla.__version__` < specified `version` + in the decorator. To keep notifications with any version `warn_if_greater_or_equal_version=True` can be set. + + Args: + old_name (`str`): + Name of the deprecated keyword argument. + version (`str`): + The version in which the keyword argument was (or will be) deprecated. + new_name (`Optional[str]`, *optional*): + The new name for the deprecated keyword argument. + If specified, the deprecated keyword argument will be replaced with this new name. + warn_if_greater_or_equal_version (`bool`, *optional*, defaults to `False`): + Whether to show warning if current `fla` version is greater or equal to the deprecated version. + raise_if_greater_or_equal_version (`bool`, *optional*, defaults to `False`): + Whether to raise `ValueError` if current `fla` version is greater or equal to the deprecated version. + raise_if_both_names (`bool`, *optional*, defaults to `False`): + Whether to raise `ValueError` if both deprecated and new keyword arguments are set. + additional_message (`Optional[str]`, *optional*): + An additional message to append to the default deprecation message. + + Raises: + ValueError: + If `raise_if_greater_or_equal_version` is `True` and the current version >= the deprecated one, + or if `raise_if_both_names` is `True` and both old and new keyword arguments are provided. + + Returns: + Callable: + A wrapped function that handles the deprecated keyword arguments according to the specified parameters. + + Example usage with renaming argument: + + ```python + @deprecate_kwarg("reduce_labels", new_name="do_reduce_labels", version="6.0.0") + def my_function(do_reduce_labels): + print(do_reduce_labels) + + my_function(reduce_labels=True) # Will show a deprecation warning and use do_reduce_labels=True + ``` + + Example usage without renaming argument: + + ```python + @deprecate_kwarg("max_size", version="6.0.0") + def my_function(max_size): + print(max_size) + + my_function(max_size=1333) # Will show a deprecation warning + ``` + + """ + deprecated_version = package_version.parse(version) + current_version = package_version.parse(__version__) + is_greater_or_equal_version = current_version >= deprecated_version + + if is_greater_or_equal_version: + version_message = f"and removed starting from version {version}" + else: + version_message = f"and will be removed in version {version}" + + def wrapper(func): + # Required for better warning message + sig = inspect.signature(func) + function_named_args = set(sig.parameters.keys()) + is_instance_method = "self" in function_named_args + is_class_method = "cls" in function_named_args + + @functools.wraps(func) + def wrapped_func(*args, **kwargs): + # Get class + function name (just for better warning message) + func_name = func.__name__ + if is_instance_method: + func_name = f"{args[0].__class__.__name__}.{func_name}" + elif is_class_method: + func_name = f"{args[0].__name__}.{func_name}" + + minimum_action = Action.NONE + message = None + + # deprecated kwarg and its new version are set for function call -> replace it with new name + if old_name in kwargs and new_name in kwargs: + minimum_action = Action.RAISE if raise_if_both_names else Action.NOTIFY_ALWAYS + message = ( + f"Both `{old_name}` and `{new_name}` are set for `{func_name}`. " + f"Using `{new_name}={kwargs[new_name]}` and ignoring deprecated `{old_name}={kwargs[old_name]}`." + ) + kwargs.pop(old_name) + + # only deprecated kwarg is set for function call -> replace it with new name + elif old_name in kwargs and new_name is not None and new_name not in kwargs: + minimum_action = Action.NOTIFY + message = ( + f"`{old_name}` is deprecated {version_message} for `{func_name}`. " + f"Use `{new_name}` instead." + ) + kwargs[new_name] = kwargs.pop(old_name) + + # deprecated kwarg is not set for function call and new name is not specified -> just notify + elif old_name in kwargs: + minimum_action = Action.NOTIFY + message = f"`{old_name}` is deprecated {version_message} for `{func_name}`." + + if message is not None and additional_message is not None: + message = f"{message} {additional_message}" + + # update minimum_action if argument is ALREADY deprecated (current version >= deprecated version) + if is_greater_or_equal_version: + # change to (NOTIFY, NOTIFY_ALWAYS) -> RAISE if specified + # in case we want to raise error for already deprecated arguments + if raise_if_greater_or_equal_version and minimum_action != Action.NONE: + minimum_action = Action.RAISE + + # change to NOTIFY -> NONE if specified (NOTIFY_ALWAYS can't be changed to NONE) + elif not warn_if_greater_or_equal_version and minimum_action == Action.NOTIFY: + minimum_action = Action.NONE + + # raise error or notify user + if minimum_action == Action.RAISE: + raise ValueError(message) + elif minimum_action in (Action.NOTIFY, Action.NOTIFY_ALWAYS): + # DeprecationWarning is ignored by default, so we use FutureWarning instead + warnings.warn(message, FutureWarning, stacklevel=2) + + return func(*args, **kwargs) + + return wrapped_func + + return wrapper + + +def checkpoint(fn): + @functools.wraps(fn) + def wrapper(*args, **kwargs): + return torch.utils.checkpoint.checkpoint(fn, *args, **kwargs) + return wrapper diff --git a/kda/_fla/utils/_device.py b/kda/_fla/utils/_device.py new file mode 100644 index 0000000..42d15ad --- /dev/null +++ b/kda/_fla/utils/_device.py @@ -0,0 +1,245 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import contextlib +import functools +import logging +import os +import platform +import sys +import warnings +from enum import Enum +from functools import cache, lru_cache + +import torch +import triton +from packaging import version as package_version + +logger = logging.getLogger(__name__) + + +@lru_cache(maxsize=1) +def check_environments(): + """ + Checks the current operating system, Triton version, and Python version, + issuing warnings if they don't meet recommendations. + This function's body only runs once due to lru_cache. + """ + # Check Operating System + if sys.platform == 'win32': + # Check if triton-windows is installed + try: + from importlib.metadata import PackageNotFoundError, metadata + metadata('triton-windows') + # triton-windows is installed, no warning needed + except PackageNotFoundError: + logger.warning( + "Detected Windows operating system. Consider installing triton-windows " + "(https://github.com/triton-lang/triton-windows) for better compatibility. " + "Without it, some features may not work correctly.", + ) + + triton_version = package_version.parse(triton.__version__) + required_triton_version = package_version.parse("3.3.0") + + if triton_version < required_triton_version: + logger.warning( + f"Current Triton version {triton_version} is below the recommended 3.3.0 version. " + "Errors may occur and these issues will not be fixed. " + "Please consider upgrading Triton.", + ) + + # Check Python version + py_version = package_version.parse(f"{sys.version_info.major}.{sys.version_info.minor}") + required_py_version = package_version.parse("3.11") + + if py_version < required_py_version: + logger.warning( + f"Current Python version {py_version} is below the recommended 3.11 version. " + "It is recommended to upgrade to Python 3.11 or higher for the best experience.", + ) + + return None + + +check_environments() + + +def _cpu_device_warning(): + warnings.warn(('Triton is not supported on current platform, roll back to CPU.'), stacklevel=2) + + +@cache +def check_pytorch_version(version_s: str = '2.4') -> bool: + return package_version.parse(torch.__version__) >= package_version.parse(version_s) + + +@cache +def get_multiprocessor_count(tensor_idx: int = 0, *, use_aicore: bool = False) -> int: + try: + return triton.runtime.driver.active.utils.get_device_properties(tensor_idx)['multiprocessor_count'] + except Exception: + # Maybe we use a NPU device. + try: + if triton.runtime.driver.active.get_current_target().backend == 'npu': + props = triton.runtime.driver.active.utils.get_device_properties(tensor_idx) + return props['num_aicore'] if use_aicore else props['num_vectorcore'] + except Exception: + logger.debug('Failed to get NPU multiprocessor count, falling back to 1.', exc_info=True) + return 1 + + +@cache +def get_device_capability(device_index: int = 0) -> tuple[int, int]: + major, minor = torch.cuda.get_device_capability(device_index) + return int(major), int(minor) + + +@cache +def get_device_smem_optin(device_index: int = 0) -> int: + props = torch.cuda.get_device_properties(device_index) + return int(getattr(props, 'shared_memory_per_block_optin', props.shared_memory_per_block)) + + +@cache +def get_available_device() -> str: + try: + return triton.runtime.driver.active.get_current_target().backend + except Exception: + _cpu_device_warning() + return 'cpu' + + +def map_triton_backend_to_torch_device() -> str: + backend = get_available_device() # 'cuda' | 'hip' | 'xpu' | 'cpu' | ... + return {'cuda': 'cuda', 'hip': 'cuda', 'xpu': 'xpu'}.get(backend, backend) + + +# For AMD GPUs, the triton backend is 'hip', while for Nvidia GPUs, the triton backend is 'cuda'. +# However, the torch backend is 'cuda' for both Nvidia and AMD GPUs. +# Therefore, we need to check the triton backend to determine the actual GPU vendor. +device = get_available_device() if get_available_device() != 'hip' else 'cuda' +device_torch_lib = getattr(torch, device) +device_platform = get_available_device() +device_name = map_triton_backend_to_torch_device() + +IS_AMD = (device_platform == 'hip') + +IS_ARM = platform.machine().lower() in ('aarch64', 'arm64') + +IS_INTEL = (device_platform == 'xpu') +IS_INTEL_ALCHEMIST = (IS_INTEL and 'Intel(R) Arc(TM) A' in torch.xpu.get_device_name(0)) + +IS_NPU = (device_platform == 'npu') + +IS_NVIDIA = (device_platform == 'cuda') +IS_NVIDIA_HOPPER = ( + IS_NVIDIA and ( + 'NVIDIA H' in torch.cuda.get_device_name(0) + or torch.cuda.get_device_capability()[0] == 9 + ) +) +IS_NVIDIA_SM100 = (IS_NVIDIA and torch.cuda.get_device_capability()[0] == 10) +# NOTE: exactly 12.0 — 12.1 (GB10) is a different target that FlashQLA rejects at import time. +IS_NVIDIA_SM120 = (IS_NVIDIA and torch.cuda.get_device_capability() == (12, 0)) +IS_NVIDIA_BLACKWELL = (IS_NVIDIA and torch.cuda.get_device_capability()[0] in (10, 12)) + +# Nvidia Ampere or newer, haven't check AMD and intel yet. +IS_TF32_SUPPORTED = (IS_NVIDIA and torch.cuda.get_device_capability(0)[0] >= 8) +IS_GATHER_SUPPORTED = hasattr(triton.language, 'gather') +IS_TMA_SUPPORTED = ( + IS_NVIDIA + and torch.cuda.get_device_capability(0)[0] >= 9 + and os.environ.get('FLA_USE_TMA', '0') == '1' + and ( + hasattr(triton.language, '_experimental_make_tensor_descriptor') + or hasattr(triton.language, 'make_tensor_descriptor') + ) +) + +if IS_NVIDIA and not IS_TF32_SUPPORTED: + # Make old card happy, since triton will use tf32 by default. + # This is a workaround for old nvidia card. + os.environ['TRITON_F32_DEFAULT'] = 'ieee' + + +def _default_alloc_fn(size: int, alignment: int, stream: int | None): + return torch.empty(size, device=torch.device(device_name, device_torch_lib.current_device()), dtype=torch.int8) + + +if IS_TMA_SUPPORTED: + logger.info('TMA is supported, using TMA by default.') + triton.set_allocator(_default_alloc_fn) +elif IS_NVIDIA_BLACKWELL: + # Blackwell (SM100 datacenter / SM120 consumer): Triton compiler may emit global_scratch for + # autotuned kernels even without TMA. Register a default allocator to + # prevent NullAllocator crashes. See triton-lang/triton#10002. + logger.info('Blackwell detected: registering default global_scratch allocator.') + triton.set_allocator(_default_alloc_fn) + + +def get_all_max_shared_mem(): + try: + return [ + triton.runtime.driver.active.utils.get_device_properties(i)['max_shared_mem'] + for i in range(device_torch_lib.device_count()) + ] + except Exception: + _cpu_device_warning() + return [-1] + + +class Backend(Enum): + ADA = 101376 # RTX 4090 + AMPERE = 166912 # A100 + HOPPER = 232448 # H100 + DEFAULT = 102400 # Default + + @classmethod + def get_shared_memory(cls, arch: str) -> int: + try: + return cls[arch.upper()].value + except KeyError: + return cls.DEFAULT.value + + +@cache +def check_shared_mem(arch: str = "none", tensor_idx: int = 0) -> bool: + try: + device_shared_mem_list = get_all_max_shared_mem() + max_shared_memory = device_shared_mem_list[tensor_idx] + return max_shared_memory >= Backend.get_shared_memory(arch) + except Exception: + return False + + +if check_pytorch_version('2.4'): + if device == 'cpu': + device = 'cuda' + device_torch_lib = getattr(torch, device) + autocast_custom_fwd = functools.partial(torch.amp.custom_fwd, device_type=device) + autocast_custom_bwd = functools.partial(torch.amp.custom_bwd, device_type=device) + + def custom_device_ctx(index: int): + if index is None: + return contextlib.nullcontext() + try: + return device_torch_lib.device(index) + except (AttributeError, AssertionError, RuntimeError): + return contextlib.nullcontext() +else: + assert device == 'cuda', 'Only cuda device is supported for PyTorch version < 2.4.0.' + autocast_custom_fwd = device_torch_lib.amp.custom_fwd + autocast_custom_bwd = device_torch_lib.amp.custom_bwd + + def custom_device_ctx(index: int): + if index is None: + return contextlib.nullcontext() + try: + return torch.cuda.device(index) + except (AttributeError, AssertionError, RuntimeError): + return contextlib.nullcontext() diff --git a/kda/_fla/utils/_testing.py b/kda/_fla/utils/_testing.py new file mode 100644 index 0000000..12064dc --- /dev/null +++ b/kda/_fla/utils/_testing.py @@ -0,0 +1,41 @@ +# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +# For a list of all contributors, visit: +# https://github.com/fla-org/flash-linear-attention/graphs/contributors + +import logging +import warnings + +import torch + +from ._config import FLA_CI_ENV + +logger = logging.getLogger(__name__) + + +def get_abs_err(x, y): + return (x.detach() - y.detach()).flatten().abs().max().item() + + +def get_err_ratio(x, y): + err = (x.detach() - y.detach()).flatten().square().mean().sqrt().item() + base = (x.detach()).flatten().square().mean().sqrt().item() + return err / (base + 1e-8) + + +def assert_close(prefix, ref, tri, ratio, warning=False, err_atol=1e-6): + abs_atol = get_abs_err(ref, tri) + error_rate = get_err_ratio(ref, tri) + msg = f"{prefix:>16} diff: {abs_atol:.6f} ratio: {error_rate:.6f}" + logger.info(msg) + if abs_atol <= err_atol: + return + assert not torch.isnan(ref).any(), f"{prefix}: NaN detected in ref" + assert not torch.isnan(tri).any(), f"{prefix}: NaN detected in tri" + if warning or (FLA_CI_ENV and (error_rate < 0.01 or abs_atol <= 0.3)): + if error_rate > ratio: + warnings.warn(msg) + else: + assert error_rate < ratio, msg diff --git a/kda/layers/__init__.py b/kda/layers/__init__.py new file mode 100644 index 0000000..f67d340 --- /dev/null +++ b/kda/layers/__init__.py @@ -0,0 +1,23 @@ +"""Composable mixing layers: attn and ffn both map [B,T,D] -> [B,T,D]. + +Depth mixing (AttnRes) is not a layer_specs kind. CausalLM reads +``config.attnres`` (off | full | block) and wraps DecoderBlock sublayers. +""" + +from .block import DecoderBlock, build_attn, build_ffn +from .kda_attn import KDAAttention +from .latent_moe import LatentMoE +from .mla import GatedMLA +from .rmsnorm import RMSNorm +from .swiglu import SwiGLUMLP + +__all__ = [ + "DecoderBlock", + "GatedMLA", + "KDAAttention", + "LatentMoE", + "RMSNorm", + "SwiGLUMLP", + "build_attn", + "build_ffn", +] diff --git a/kda/layers/attn_res.py b/kda/layers/attn_res.py new file mode 100644 index 0000000..f70d11b --- /dev/null +++ b/kda/layers/attn_res.py @@ -0,0 +1,519 @@ +""" +Attention Residual in one file + +Reference: + Kimi Team, Guangyu Chen, Yu Zhang, Jianlin Su, Weixin Xu, Siyuan Pan, + Yaoyu Wang, Yucheng Wang, Guanduo Chen, et al. + "Attention Residuals." arXiv:2603.15031, 2026. + https://arxiv.org/abs/2603.15031 + +This module is a compact PyTorch reference implementation of: + - Full AttnRes + - Block AttnRes + - two-phase inter/intra-block computation from the paper + +CausalLM wires Full/Block stacks when ``config.attnres`` is ``full`` or +``block``. Standard residual (``x += attn; x += ffn``) is ``attnres="off"``. +""" + +import torch +import torch.nn.functional as F +from einops import rearrange +from torch import Tensor, nn + + +ATTNRES_MODES = ("off", "full", "block") + + +def exists(x): + return x is not None + + +def validate_attnres(mode: str, block_size: int | None) -> None: + if mode not in ATTNRES_MODES: + raise ValueError(f"attnres must be one of {ATTNRES_MODES}, got {mode!r}") + if block_size is not None and block_size < 1: + raise ValueError(f"attnres_block_size must be >= 1, got {block_size}") + + +def atomic_block_size(num_hidden_layers: int, attnres_block_size: int | None) -> int: + """DecoderBlocks per AttnRes block, converted to attn|ffn atomic layers. + + ``None`` targets about 8 blocks: ``max(1, ceil(L / 8))`` DecoderBlocks. + """ + layers_per_block = ( + attnres_block_size + if attnres_block_size is not None + else max(1, (num_hidden_layers + 7) // 8) + ) + if layers_per_block < 1: + raise ValueError(f"attnres_block_size must be >= 1, got {layers_per_block}") + return layers_per_block * 2 + + +class BorrowedSubLayer(nn.Module): + """``fn(norm(x))`` without registering ``norm``/``fn`` (owned by DecoderBlock).""" + + def __init__(self, norm: nn.Module, fn: nn.Module): + super().__init__() + self._borrowed = (norm, fn) + + def forward(self, x: Tensor) -> Tensor: + norm, fn = self._borrowed + return fn(norm(x)) + + +def rms(x: Tensor, eps: float): + return x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + eps) + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float): + super().__init__() + self.eps = eps + self.weight = nn.Parameter(torch.ones(dim)) + + def forward(self, x: Tensor) -> Tensor: + return rms(x, self.eps) * self.weight + + +class DepthResidual(nn.Module): + """ + h_l = sum_i softmax_i(w_l^T RMSNorm(v_i))*v_i + + Keep query and RMSNorm gain separate + Since q^T (gamma * RMS(v)) == (q * gamma)^T RMS(v), + we can fold gamma into q for scoring. + """ + + def __init__(self, dim: int, eps: float = 1e-8, zero_init: bool = True): + super().__init__() + self.query = nn.Parameter(torch.zeros(dim)) + self.norm = RMSNorm(dim, eps=eps) + + if not zero_init: + nn.init.normal_(self.query, std=0.02) + + def effective_query(self) -> Tensor: + return (self.query * self.norm.weight).float() + + def logits(self, sources: Tensor | list[Tensor] | tuple[Tensor, ...]) -> Tensor: + sources = stack_layers(sources) # [n, b, t, d] + q = self.effective_query() # [d] + k = rms(sources.float(), self.norm.eps) # [n, b, t, d] + return torch.einsum("d, n b t d -> n b t", q, k) + + def forward(self, sources: Tensor | list[Tensor] | tuple[Tensor, ...]) -> Tensor: + sources = stack_layers(sources) + weights = self.logits(sources).softmax(dim=0) + out = torch.einsum("n b t, n b t d -> b t d", weights, sources.float()) + return out.to(sources.dtype) + + +class DepthResidualList(nn.Module): + def __init__(self, dim: int, depth: int, eps: float, zero_init: bool = True): + super().__init__() + # for L layers (depth), create depth residual modules + self.layers = nn.ModuleList( + [DepthResidual(dim, eps=eps, zero_init=zero_init) for _ in range(depth)] + ) + + def __getitem__(self, idx: int) -> DepthResidual: + return self.layers[idx] + + def __iter__(self): + return iter(self.layers) + + def __len__(self): + return len(self.layers) + + +# attnres stacks + + +class FullAttnResStack(nn.Module): + """ + Full AttnRes over atomic layers + eg: f_1,...,f_L + Each entry in `layers` should already be a full atomic layer fxn + x -> f_l(x) + """ + + def __init__( + self, + dim: int, + layers, + *, + eps: float = 1e-8, + zero_init_queries: bool = True, + is_final_aggregate: bool = True, + ): + super().__init__() + self.layers = nn.ModuleList(list(layers)) + self.eps = eps + + depth = len(self.layers) + self.residuals = DepthResidualList(dim, depth, eps, zero_init_queries) + self.final_residual = ( + DepthResidual(dim, eps, zero_init_queries) if is_final_aggregate else None + ) + + def forward_naive(self, x: Tensor) -> Tensor: + sources = [x] + for layer, residual in zip(self.layers, self.residuals): + h = residual(sources) + out = layer(h) + sources.append(out) + + return ( + self.final_residual(sources) if exists(self.final_residual) else sources[-1] + ) + + def forward_two_phase(self, x: Tensor, schedule_block_size: int) -> Tensor: + assert schedule_block_size > 0 + sources = [x] + depth = len(self.layers) + + start = 0 + while start < depth: + end = min(start + schedule_block_size, depth) + queries = torch.stack( + [self.residuals[i].effective_query() for i in range(start, end)], dim=0 + ) + inter_sources = stack_layers(sources) + inter_stats = attn_with_stats(queries, inter_sources, self.eps) + + local_outputs = [] # outputs of intra-block + for local_idx, layer_idx in enumerate(range(start, end)): + stats = inter_stats.select(local_idx) + if len(local_outputs) > 0: + intra_sources = stack_layers(local_outputs) + intra = attn_with_stats( + queries[local_idx : local_idx + 1], intra_sources, self.eps + ).select(0) + stats = merge_attn_stats(stats, intra) + h = stats.normalized() + out = self.layers[layer_idx](h) + local_outputs.append(out) + sources.append(out) + + start = end + + return ( + self.final_residual(sources) if exists(self.final_residual) else sources[-1] + ) + + def forward(self, x: Tensor, schedule_block_size: int | None = None) -> Tensor: + if schedule_block_size is None: + return self.forward_naive(x) + return self.forward_two_phase(x, schedule_block_size) + + +class BlockAttnResStack(nn.Module): + """ + Block AttnRes over atomic layers + + `block_size` is in atomic layers, not Transformer blocks. + Eg: block_size=8 -> 4 transformer blocks when layers alternate attn/MLP + + The default forward path is the two-phase algorithm from the paper: + phase 1: batch inter-block attn from all queries in the block + phase 2: merge the evolving intra-block partial sum with online softmax + """ + + def __init__( + self, + dim: int, + layers, + *, + block_size: int, + eps: float = 1e-8, + zero_init_queries: bool = True, + is_final_aggregate: bool = True, + ): + super().__init__() + self.layers = nn.ModuleList(list(layers)) + assert len(self.layers) > 0 + assert block_size > 0 + self.block_size = block_size + self.eps = eps + + depth = len(self.layers) + self.residuals = DepthResidualList( + dim, depth, eps=eps, zero_init=zero_init_queries + ) + self.final_residual = ( + DepthResidual(dim, eps=eps, zero_init=zero_init_queries) + if is_final_aggregate + else None + ) + + def forward_naive(self, x: Tensor) -> Tensor: + blocks = [x] # b_0=embedding/input representation + partial = None + + for layer_idx, (layer, residual) in enumerate( + zip(self.layers, self.residuals), start=1 + ): + sources = blocks if partial is None else blocks + [partial] + h = residual(sources) + out = layer(h) + partial = out if partial is None else (partial + out) + + if (layer_idx % self.block_size == 0) or (layer_idx == len(self.layers)): + blocks.append(partial) + partial = None + + return ( + self.final_residual(blocks) if exists(self.final_residual) else blocks[-1] + ) + + def _run_block_two_phase( + self, blocks: list[Tensor], start: int, end: int + ) -> Tensor: + queries = torch.stack( + [self.residuals[i].effective_query() for i in range(start, end)], dim=0 + ) + inter_sources = stack_layers(blocks) + inter = attn_with_stats(queries, inter_sources, self.eps) + partial = None + for local_idx, layer_idx in enumerate(range(start, end)): + stats = inter.select(local_idx) + + if partial is not None: + intra = single_source_stats(queries[local_idx], partial, self.eps) + stats = merge_attn_stats(stats, intra) + + h = stats.normalized() + out = self.layers[layer_idx](h) + partial = out if partial is None else (partial + out) + + return partial + + def forward(self, x: Tensor) -> Tensor: + blocks = [x] + depth = len(self.layers) + start = 0 + while start < depth: + end = min(start + self.block_size, depth) + blocks.append(self._run_block_two_phase(blocks, start, end)) + start = end + + return ( + self.final_residual(blocks) if exists(self.final_residual) else blocks[-1] + ) + + +# helpers + + +def stack_layers(sources: Tensor | list[Tensor] | tuple[Tensor, ...]) -> Tensor: + if isinstance(sources, Tensor): + assert sources.ndim == 4, f"expected [n, b, t, d] got {tuple(sources.shape)}" + return sources + assert len(sources) > 0, "needs at least one source" + return torch.stack(tuple(sources), dim=0) + + +class SingleAttnStats: + def __init__(self, numer: Tensor, denom: Tensor, max: Tensor): + self.numer = numer # [b,t,d] + self.max = max # [b,t] + self.denom = denom # [b,t] + + def normalized(self) -> Tensor: + return self.numer / self.denom[..., None] + + +class AttnStats: + # store the numerator => e^{s_{j}-m} * v_j where m is the max score so far + # store the max m = max(s_j) + # store the denominator sum_j e^{s_{j}-m} + def __init__(self, numer: Tensor, denom: Tensor, max: Tensor): + self.numer = numer # [q,b,t,d] + self.max = max # [q,b,t] + self.denom = denom # [q,b,t] + + def select(self, idx: int) -> "SingleAttnStats": + return SingleAttnStats(self.numer[idx], self.denom[idx], self.max[idx]) + + +def attn_with_stats(queries: Tensor, sources: Tensor, eps: float = 1e-8) -> AttnStats: + """ + queries: [q, d] + sources: [n, b, t, d] + + Returns the following for online softmax: + numer = sum_i exp(logit_i - m)*v_i + m = max_i logit_i + denom = sum_i exp(logit_i - m) + """ + normed = rms(sources, eps) + logits = torch.einsum("q d, n b t d -> q n b t", queries, normed) + m = logits.amax(dim=1) + weights = torch.exp(logits - m[:, None]) + numer = torch.einsum("q n b t, n b t d -> q b t d", weights, sources) + denom = weights.sum(dim=1) + return AttnStats(numer, denom, m) + + +def single_source_stats( + query: Tensor, source: Tensor, eps: float = 1e-8 +) -> SingleAttnStats: + score = torch.einsum("d, b t d -> b t", query, rms(source, eps)) + denom = torch.ones_like(score) + return SingleAttnStats(source, denom, score) + + +def merge_attn_stats(a: SingleAttnStats, b: SingleAttnStats) -> SingleAttnStats: + m = torch.maximum(a.max, b.max) + wa = torch.exp(a.max - m) + wb = torch.exp(b.max - m) + numer = wa[..., None] * a.numer + wb[..., None] * b.numer + denom = wa * a.denom + wb * b.denom + return SingleAttnStats(numer, denom, m) + + +# transformer +class PreNorm(nn.Module): + def __init__(self, dim: int, fn: nn.Module, eps: float = 1e-8): + super().__init__() + self.norm = RMSNorm(dim, eps=eps) + self.fn = fn + + def forward(self, x: Tensor) -> Tensor: + return self.fn(self.norm(x)) + + +class CausalAttention(nn.Module): + def __init__( + self, dim: int, heads: int = 8, dim_head: int = 64, dropout: float = 0.0 + ): + super().__init__() + inner_dim = heads * dim_head + self.heads = heads + self.dim_head = dim_head + self.dropout = dropout + + self.to_qkv = nn.Linear(dim, inner_dim * 3, bias=False) + self.to_out = nn.Linear(inner_dim, dim, bias=False) + + def forward(self, x: Tensor) -> Tensor: + q, k, v = self.to_qkv(x).chunk(3, dim=-1) + + def split_heads(y: Tensor) -> Tensor: + return rearrange(y, "b t (h d) -> b h t d", h=self.heads) + + q, k, v = map(split_heads, (q, k, v)) + + out = F.scaled_dot_product_attention( + q, k, v, is_causal=True, dropout_p=self.dropout if self.training else 0.0 + ) + out = rearrange(out, "b h t d -> b t (h d)") + return self.to_out(out) + + +class SwiGLU(nn.Module): + def __init__(self, dim: int, mult: int = 4, dropout: float = 0.0): + # dropout not needed unless training on a smaller training data + super().__init__() + inner_dim = dim * mult + self.to_hidden = nn.Linear(dim, inner_dim * 2, bias=False) + self.to_out = nn.Linear(inner_dim, dim, bias=False) + self.dropout = nn.Dropout(dropout) + + def forward(self, x: Tensor) -> Tensor: + gate, value = self.to_hidden(x).chunk(2, dim=-1) + x = F.silu(gate) * value + x = self.dropout(x) + return self.to_out(x) + + +class AttnResTransformer(nn.Module): + """ + Small GPT-style reference model using AttnRes + + Using plain PyTorch: tok/pos embedding, alternating + causal attn, SwiGLU MLP layers, final norm, output head. + """ + + def __init__( + self, + *, + num_tokens: int, + dim: int, + depth: int, + max_seq_len: int, + heads: int = 8, + dim_head: int = 64, + ff_mult: int = 4, + attn_dropout: float = 0.0, + ff_dropout: float = 0.0, + attnres: str = "block", # full or block + block_size: int = 8, + zero_init_queries: bool = True, + is_final_aggregate: bool = True, + eps: float = 1e-8, + ): + super().__init__() + assert attnres in {"full", "block"} + self.max_seq_len = max_seq_len + self.attnres = attnres + self.token_emb = nn.Embedding(num_tokens, dim) + self.pos_emb = nn.Embedding(max_seq_len, dim) + + atomic_layers = [] + for _ in range(depth): + atomic_layers.append( + PreNorm(dim, CausalAttention(dim, heads, dim_head, attn_dropout), eps) + ) + atomic_layers.append(PreNorm(dim, SwiGLU(dim, ff_mult, ff_dropout), eps)) + if attnres == "full": + self.backbone = FullAttnResStack( + dim, + atomic_layers, + eps=eps, + zero_init_queries=zero_init_queries, + is_final_aggregate=is_final_aggregate, + ) + else: + self.backbone = BlockAttnResStack( + dim, + atomic_layers, + block_size=block_size, + eps=eps, + zero_init_queries=zero_init_queries, + is_final_aggregate=is_final_aggregate, + ) + + self.final_norm = RMSNorm(dim, eps) + self.to_logits = nn.Linear(dim, num_tokens, bias=False) + + def forward(self, ids: Tensor, schedule_block_size: int | None = None) -> Tensor: + b, t = ids.shape + assert t <= self.max_seq_len + pos = torch.arange(t, device=ids.device) + x = self.token_emb(ids) + self.pos_emb(pos)[None, :, :] + if self.attnres == "full": + x = self.backbone(x, schedule_block_size=schedule_block_size) + else: + x = self.backbone(x) + x = self.final_norm(x) + return self.to_logits(x) + + +__all__ = [ + "ATTNRES_MODES", + "RMSNorm", + "DepthResidual", + "DepthResidualList", + "FullAttnResStack", + "BlockAttnResStack", + "BorrowedSubLayer", + "PreNorm", + "CausalAttention", + "SwiGLU", + "AttnResTransformer", + "atomic_block_size", + "validate_attnres", +] diff --git a/kda/layers/block.py b/kda/layers/block.py new file mode 100644 index 0000000..d5f7ff5 --- /dev/null +++ b/kda/layers/block.py @@ -0,0 +1,51 @@ +"""Decoder block: x += attn(norm(x)); x += ffn(norm(x)). + +attn/ffn are any modules with forward: [B,T,D] -> [B,T,D]. +""" +from __future__ import annotations + +from torch import nn + +from .kda_attn import KDAAttention +from .latent_moe import LatentMoE +from .mla import GatedMLA +from .rmsnorm import RMSNorm +from .swiglu import SwiGLUMLP + + +def build_attn(config, kind: str) -> nn.Module: + if kind == "kda": + return KDAAttention.from_config(config) + if kind == "mla": + return GatedMLA.from_config(config) + raise ValueError(f"unknown attn kind: {kind}") + + +def build_ffn(config, kind: str) -> nn.Module: + if kind == "swiglu": + return SwiGLUMLP.from_config(config) + if kind == "moe": + return LatentMoE.from_config(config) + raise ValueError(f"unknown ffn kind: {kind}") + + +class DecoderBlock(nn.Module): + def __init__(self, hidden_size: int, norm_eps: float, attn: nn.Module, ffn: nn.Module): + super().__init__() + self.attn_norm = RMSNorm(hidden_size, norm_eps) + self.attn = attn + self.ffn_norm = RMSNorm(hidden_size, norm_eps) + self.ffn = ffn + + @classmethod + def from_spec(cls, config, attn_kind: str, ffn_kind: str) -> DecoderBlock: + return cls( + config.hidden_size, + config.norm_eps, + build_attn(config, attn_kind), + build_ffn(config, ffn_kind), + ) + + def forward(self, x): + x = x + self.attn(self.attn_norm(x)) + return x + self.ffn(self.ffn_norm(x)) diff --git a/kda/layers/kda_attn.py b/kda/layers/kda_attn.py new file mode 100644 index 0000000..141aa0c --- /dev/null +++ b/kda/layers/kda_attn.py @@ -0,0 +1,99 @@ +"""KDA attention: project q/k/v/g/beta, run chunk_kda, project back to D.""" +from __future__ import annotations + +import torch +from torch import nn + +from ..ops.api import chunk_kda + + +class KDAAttention(nn.Module): + """Mixing module: x [B,T,D] -> y [B,T,D].""" + + def __init__( + self, + hidden_size: int, + num_heads: int, + num_value_heads: int, + head_dim: int, + *, + chunk_size: int = 16, + initializer_range: float = 0.02, + use_gate_in_kernel: bool = True, + use_qk_l2norm_in_kernel: bool = True, + use_beta_sigmoid_in_kernel: bool = True, + lower_bound: float | None = -5.0, + kda_backend: str = "reference", + ): + super().__init__() + if num_value_heads % num_heads: + raise ValueError("num_value_heads must be divisible by num_heads") + self.hidden_size = hidden_size + self.num_heads = num_heads + self.num_value_heads = num_value_heads + self.head_dim = head_dim + self.chunk_size = chunk_size + self.initializer_range = initializer_range + self.use_gate_in_kernel = use_gate_in_kernel + self.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel + self.use_beta_sigmoid_in_kernel = use_beta_sigmoid_in_kernel + self.lower_bound = lower_bound + self.kda_backend = kda_backend + + H, HV, K, V = num_heads, num_value_heads, head_dim, head_dim + self.q_proj = nn.Linear(hidden_size, H * K, bias=False) + self.k_proj = nn.Linear(hidden_size, H * K, bias=False) + self.v_proj = nn.Linear(hidden_size, HV * V, bias=False) + self.g_proj = nn.Linear(hidden_size, HV * K, bias=False) + self.beta_proj = nn.Linear(hidden_size, HV, bias=False) + self.o_proj = nn.Linear(HV * V, hidden_size, bias=False) + self.A_log = nn.Parameter(torch.zeros(HV)) + # With safe_gate=-5, bias=-4 starts at g≈-0.09 (about 91% state retention). + self.dt_bias = nn.Parameter(torch.full((HV, K), -4.0)) + self.apply(self._init_weights) + + @classmethod + def from_config(cls, config) -> KDAAttention: + return cls( + hidden_size=config.hidden_size, + num_heads=config.num_heads, + num_value_heads=getattr(config, "num_value_heads", config.num_heads), + head_dim=config.head_dim, + chunk_size=config.chunk_size, + initializer_range=config.initializer_range, + use_gate_in_kernel=config.use_gate_in_kernel, + use_qk_l2norm_in_kernel=config.use_qk_l2norm_in_kernel, + use_beta_sigmoid_in_kernel=config.use_beta_sigmoid_in_kernel, + lower_bound=config.lower_bound, + kda_backend=config.kda_backend, + ) + + def _init_weights(self, module): + if isinstance(module, nn.Linear): + nn.init.normal_(module.weight, std=self.initializer_range) + + def forward(self, x: torch.Tensor): + B, T, _ = x.shape + H, HV, K, V = self.num_heads, self.num_value_heads, self.head_dim, self.head_dim + q = self.q_proj(x).view(B, T, H, K) + k = self.k_proj(x).view(B, T, H, K) + v = self.v_proj(x).view(B, T, HV, V) + g_raw = self.g_proj(x).view(B, T, HV, K) + beta_raw = self.beta_proj(x).view(B, T, HV) + o, _ = chunk_kda( + q, + k, + v, + g_raw, + beta_raw, + A_log=self.A_log, + dt_bias=self.dt_bias, + use_qk_l2norm_in_kernel=self.use_qk_l2norm_in_kernel, + use_gate_in_kernel=self.use_gate_in_kernel, + use_beta_sigmoid_in_kernel=self.use_beta_sigmoid_in_kernel, + safe_gate=self.lower_bound is not None, + lower_bound=self.lower_bound, + chunk_size=self.chunk_size, + backend=self.kda_backend, + ) + return self.o_proj(o.reshape(B, T, HV * V)) diff --git a/kda/layers/latent_moe.py b/kda/layers/latent_moe.py new file mode 100644 index 0000000..1a719d4 --- /dev/null +++ b/kda/layers/latent_moe.py @@ -0,0 +1,118 @@ +"""Stable LatentMoE (K3): shared 全宽 + routed 半宽专家 + SiTU-GLU + Top-k. + +对照 learning/kimi-k3-notes §Stable LatentMoE: + z = W_down(x) [B, T, ℓ] ℓ = d/2 latent 接口宽 + u = Σ_{i∈Top-k(x)} p_i E_i^rt(z) [B, T, ℓ] routed 专家只在 ℓ 上算 + y = Σ_j E_j^sh(x) + W_up RMSNorm(u) [B, T, d] shared 全宽 + +SiTU-GLU: gate = β1·tanh(W_g x/β1)⊙σ(W_g x); up = β2·tanh(W_u x/β2) + ||SiTU-GLU||_∞ ≤ β1·β2 (=100), 原点附近≈SwiGLU, 远端软饱和防低精度溢出. + E: R^in → R^in (内部中间维 d_ff). + +Router: Top-k logits 基于全宽 x (笔记 Topk(x)); 归一化权重取 softmax(topk). +""" +from __future__ import annotations + +import torch +import torch.nn.functional as F +from torch import nn + +from .rmsnorm import RMSNorm + + +class SiTU(nn.Module): + """SiTU-GLU expert: gate 支软上限 β1, up 支软上限 β2, 输出回到输入维.""" + + def __init__(self, dim_in: int, dim_ff: int, beta1: float = 4.0, beta2: float = 25.0): + super().__init__() + self.beta1, self.beta2 = beta1, beta2 + self.w_g = nn.Linear(dim_in, dim_ff, bias=False) + self.w_u = nn.Linear(dim_in, dim_ff, bias=False) + self.w_o = nn.Linear(dim_ff, dim_in, bias=False) + + def forward(self, x: torch.Tensor): + wg = self.w_g(x) + g = self.beta1 * torch.tanh(wg / self.beta1) * torch.sigmoid(wg) + u = self.beta2 * torch.tanh(self.w_u(x) / self.beta2) + return self.w_o(g * u) + + +class LatentMoE(nn.Module): + def __init__( + self, + hidden_size: int, + latent_size: int, + n_routed: int, + top_k: int, + n_shared: int, + d_ff: int, + beta1: float = 4.0, + beta2: float = 25.0, + ): + super().__init__() + self.latent_size = latent_size + self.n_routed = n_routed + self.top_k = top_k + + self.down = nn.Linear(hidden_size, latent_size, bias=False) # W↓ + self.router = nn.Linear(hidden_size, n_routed, bias=False) # Top-k logits + self.shared = nn.ModuleList( + [SiTU(hidden_size, d_ff, beta1, beta2) for _ in range(n_shared)] + ) + self.experts = nn.ModuleList( + [SiTU(latent_size, d_ff, beta1, beta2) for _ in range(n_routed)] + ) + self.norm = RMSNorm(latent_size) + self.up = nn.Linear(latent_size, hidden_size, bias=False) # W↑ + self.last_route_ids: torch.Tensor | None = None + + @classmethod + def from_config(cls, config) -> LatentMoE: + return cls( + config.hidden_size, + config.moe_latent_size, + config.n_routed, + config.top_k, + config.n_shared, + config.moe_d_ff, + config.situ_beta1, + config.situ_beta2, + ) + + def forward(self, x: torch.Tensor): + B, T, _ = x.shape + z = self.down(x) # [B, T, ℓ] + + logits = self.router(x) # [B, T, n_routed] + topk = torch.topk(logits, self.top_k, dim=-1) + ids = topk.indices # [B, T, k] + self.last_route_ids = ids.detach() + probs = F.softmax(topk.values, dim=-1) # [B, T, k] + + # 向量化 routed: 预计算全部专家输出, 按 token 的 Top-k id 取 + all_out = torch.stack([e(z) for e in self.experts]) # [R, B, T, ℓ] + all_out = all_out.permute(1, 2, 0, 3).reshape(B * T, self.n_routed, self.latent_size) + u = torch.zeros(B, T, self.latent_size, device=x.device, dtype=x.dtype) + for i in range(self.top_k): + idx = ids[:, :, i].reshape(B * T) # [B*T] + sel = all_out[torch.arange(B * T, device=x.device), idx] # [B*T, ℓ] + u += probs[:, :, i : i + 1] * sel.reshape(B, T, self.latent_size) + + shared_out = torch.stack([e(x) for e in self.shared]).sum(0) # [B, T, d] + return shared_out + self.up(self.norm(u)) + + +def moe_route_frac(model: nn.Module) -> torch.Tensor | None: + """Mean expert occupancy over LatentMoE layers from the last forward.""" + hists: list[torch.Tensor] = [] + n_routed: int | None = None + for module in model.modules(): + if not isinstance(module, LatentMoE) or module.last_route_ids is None: + continue + n_routed = module.n_routed + ids = module.last_route_ids.reshape(-1) + hists.append(torch.bincount(ids, minlength=n_routed).float()) + if not hists or n_routed is None: + return None + stacked = torch.stack(hists).sum(0) + return stacked / stacked.sum().clamp_min(1.0) diff --git a/kda/layers/mla.py b/kda/layers/mla.py new file mode 100644 index 0000000..5694d0f --- /dev/null +++ b/kda/layers/mla.py @@ -0,0 +1,97 @@ +"""Gated MLA (K3): NoPE, latent KV compression, matrix absorption, full-rank output gate. + +K3 相对 DeepSeek MLA 的三个改动 (对照 learning/kimi-k3-notes): +1. NoPE — 不显式 RoPE; 位置感交给夹层 KDA 的 decay/gate。 +2. 矩阵吸收 — 训练/推理都不解压 K/V: q 吸收 W_UK 后直接与 latent c 内积, + 输出先在 latent 加权再乘 W_UV 还原 (v2 吸收版)。 +3. Full-rank 输出门 — y = W_o[ σ(W_g x) ⊙ õ ]。 + +形状 (小规模 toy, d 为 hidden): + c = RMSNorm(kv_down(x)) [B, T, r] latent + q = q_up(RMSNorm(q_down(x))) [B, T, H, d_q] d_q = d_nope (NoPE) + W_UK = kv_up[.., :H*d_q].view(H,d_q,r) W_UV = kv_up[.., H*d_q:].view(H,d_v,r) + score = (q @ W_UK^T) @ c^T [B, H, T, T] causal + õ = (softmax(score) @ c) @ W_UV^T [B, T, H, d_v] + y = o_proj( σ(W_g x) ⊙ õ_head ) [B, T, d] +""" +from __future__ import annotations + +import torch +import torch.nn.functional as F +from torch import nn + +from .rmsnorm import RMSNorm + + +class GatedMLA(nn.Module): + def __init__( + self, + hidden_size: int, + num_heads: int, + kv_lora_rank: int, + q_lora_rank: int, + qk_nope_head_dim: int, + v_head_dim: int, + ): + super().__init__() + self.hidden_size = hidden_size + self.num_heads = num_heads + self.qk_nope_head_dim = qk_nope_head_dim + self.v_head_dim = v_head_dim + + # Q 低秩路径 (NoPE, 只有 nope 段) + self.q_down = nn.Linear(hidden_size, q_lora_rank, bias=False) + self.q_norm = RMSNorm(q_lora_rank) + self.q_up = nn.Linear(q_lora_rank, num_heads * qk_nope_head_dim, bias=False) + + # KV latent 压缩 + 解压 (W_UK | W_UV 拼接在同一矩阵里) + self.kv_down = nn.Linear(hidden_size, kv_lora_rank, bias=False) + self.kv_norm = RMSNorm(kv_lora_rank) + self.kv_up = nn.Linear( + kv_lora_rank, num_heads * (qk_nope_head_dim + v_head_dim), bias=False + ) + + # Full-rank 输出门: σ(W_g x) 与 õ (H*d_v 维) 逐元素相乘 + self.gate = nn.Linear(hidden_size, num_heads * v_head_dim, bias=False) + self.o_proj = nn.Linear(num_heads * v_head_dim, hidden_size, bias=False) + + @classmethod + def from_config(cls, config) -> GatedMLA: + return cls( + config.hidden_size, + config.num_heads, + config.kv_lora_rank, + config.q_lora_rank, + config.qk_nope_head_dim, + config.v_head_dim, + ) + + def forward(self, x: torch.Tensor): + B, T, _ = x.shape + H, r = self.num_heads, self.kv_up.in_features + + c = self.kv_norm(self.kv_down(x)) # [B, T, r] + q = self.q_up(self.q_norm(self.q_down(x))) # [B, T, H*d_q] + q = q.view(B, T, H, self.qk_nope_head_dim) # [B, T, H, d_q] + + w = self.kv_up.weight # [H*(d_q+d_v), r] + w_uk = w[: H * self.qk_nope_head_dim].view(H, self.qk_nope_head_dim, r) + w_uv = w[H * self.qk_nope_head_dim :].view(H, self.v_head_dim, r) + + # 吸收 W_UK 进 query: score = (q @ W_UK^T) @ c^T + q_absorb = torch.einsum("bthd,hdj->bthj", q, w_uk) # [B, T, H, r] + scores = torch.einsum("bthj,bsj->bhts", q_absorb, c) # [B, H, T, T] + + mask = torch.triu( + torch.ones(T, T, dtype=torch.bool, device=x.device), diagonal=1 + ) + scores = scores.masked_fill(mask, float("-inf")) + attn = F.softmax(scores, dim=-1) # [B, H, T, T] + + # 先在 latent 加权, 再乘 W_UV^T 还原 v —— 永不解压 + latent_out = torch.einsum("bhts,bsj->bhtj", attn, c) # [B, H, T, r] + o_heads = torch.einsum("bhtj,hvj->bhtv", latent_out, w_uv) # [B, H, T, d_v] + + o_heads = o_heads.transpose(1, 2).reshape(B, T, H * self.v_head_dim) + gate = torch.sigmoid(self.gate(x)) # [B, T, H*d_v] + return self.o_proj(gate * o_heads) # [B, T, d] diff --git a/kda/layers/rmsnorm.py b/kda/layers/rmsnorm.py new file mode 100644 index 0000000..265860d --- /dev/null +++ b/kda/layers/rmsnorm.py @@ -0,0 +1,17 @@ +"""RMSNorm used by attention, FFN, and the final LM stem.""" +from __future__ import annotations + +import torch +from torch import nn + + +class RMSNorm(nn.Module): + def __init__(self, dim: int, eps: float = 1e-6): + super().__init__() + self.weight = nn.Parameter(torch.ones(dim)) + self.eps = eps + + def forward(self, x: torch.Tensor): + dtype = x.dtype + x = x.float() + return (x * torch.rsqrt(x.square().mean(-1, keepdim=True) + self.eps)).to(dtype) * self.weight diff --git a/kda/layers/swiglu.py b/kda/layers/swiglu.py new file mode 100644 index 0000000..3c0c9e8 --- /dev/null +++ b/kda/layers/swiglu.py @@ -0,0 +1,20 @@ +"""SwiGLU FFN: x [B,T,D] -> y [B,T,D].""" +from __future__ import annotations + +import torch.nn.functional as F +from torch import nn + + +class SwiGLUMLP(nn.Module): + def __init__(self, hidden_size: int, intermediate_size: int): + super().__init__() + self.w1 = nn.Linear(hidden_size, intermediate_size, bias=False) + self.w3 = nn.Linear(hidden_size, intermediate_size, bias=False) + self.w2 = nn.Linear(intermediate_size, hidden_size, bias=False) + + @classmethod + def from_config(cls, config) -> SwiGLUMLP: + return cls(config.hidden_size, config.intermediate_size) + + def forward(self, x): + return self.w2(F.silu(self.w1(x)) * self.w3(x)) diff --git a/kda/models/__init__.py b/kda/models/__init__.py new file mode 100644 index 0000000..08150b6 --- /dev/null +++ b/kda/models/__init__.py @@ -0,0 +1,7 @@ +"""Configs and the single CausalLM entry.""" + +from .causal_lm import CausalLM +from .config import KDAConfig +from .k3_config import K3Config + +__all__ = ["CausalLM", "K3Config", "KDAConfig"] diff --git a/kda/models/causal_lm.py b/kda/models/causal_lm.py new file mode 100644 index 0000000..9d1fd45 --- /dev/null +++ b/kda/models/causal_lm.py @@ -0,0 +1,123 @@ +"""Causal LM stem: embed -> DecoderBlock* -> norm -> lm_head. + +KDA-only and K3-like both use this class. Config.layer_specs() chooses +attn/ffn per layer: ("kda"|"mla", "swiglu"|"moe"). + +``config.attnres`` selects the depth mixer: + off — standard residual inside each DecoderBlock (default) + full — Full AttnRes over attn|ffn sublayers + block — Block AttnRes (K3); block size from ``attnres_block_size`` +""" + +from __future__ import annotations + +import torch +import torch.nn.functional as F +from torch import nn +from torch.utils.checkpoint import checkpoint as activation_checkpoint + +from ..layers.attn_res import ( + BlockAttnResStack, + BorrowedSubLayer, + FullAttnResStack, + atomic_block_size, +) +from ..layers.block import DecoderBlock +from ..layers.rmsnorm import RMSNorm + + +def _build_mixer(config, blocks: nn.ModuleList): + mode = getattr(config, "attnres", "off") + if mode == "off": + return None + atomics = [] + for block in blocks: + atomics.append(BorrowedSubLayer(block.attn_norm, block.attn)) + atomics.append(BorrowedSubLayer(block.ffn_norm, block.ffn)) + kwargs = dict( + eps=config.norm_eps, + zero_init_queries=getattr(config, "attnres_zero_init_queries", True), + is_final_aggregate=getattr(config, "attnres_final_aggregate", True), + ) + if mode == "full": + return FullAttnResStack(config.hidden_size, atomics, **kwargs) + if mode == "block": + return BlockAttnResStack( + config.hidden_size, + atomics, + block_size=atomic_block_size( + config.num_hidden_layers, getattr(config, "attnres_block_size", None) + ), + **kwargs, + ) + raise ValueError(f"unknown attnres mode: {mode!r}") + + +class CausalLM(nn.Module): + def __init__(self, config): + super().__init__() + self.config = config + self.attnres = getattr(config, "attnres", "off") + self.embedding = nn.Embedding(config.vocab_size, config.hidden_size) + self.blocks = nn.ModuleList( + [ + DecoderBlock.from_spec(config, attn, ffn) + for attn, ffn in config.layer_specs() + ] + ) + self.mixer = _build_mixer(config, self.blocks) + self.gradient_checkpointing = bool( + getattr(config, "gradient_checkpointing", False) + ) + self.norm = RMSNorm(config.hidden_size, config.norm_eps) + self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) + nn.init.normal_(self.embedding.weight, std=config.initializer_range) + nn.init.normal_(self.lm_head.weight, std=config.initializer_range) + if config.tie_word_embeddings: + self.lm_head.weight = self.embedding.weight + + def forward( + self, + input_ids: torch.Tensor, + labels: torch.Tensor | None = None, + ignore_index: int = -100, + ): + x = self.embedding(input_ids) + if self.mixer is None: + for block in self.blocks: + if self.gradient_checkpointing and self.training: + x = activation_checkpoint(block, x, use_reentrant=False) + else: + x = block(x) + elif self.gradient_checkpointing and self.training: + x = activation_checkpoint(self.mixer, x, use_reentrant=False) + else: + x = self.mixer(x) + logits = self.lm_head(self.norm(x)) + if labels is None: + return logits + return F.cross_entropy( + logits[:, :-1].reshape(-1, logits.size(-1)), + labels[:, 1:].reshape(-1), + ignore_index=ignore_index, + ) + + @torch.inference_mode() + def generate( + self, + input_ids: torch.Tensor, + max_new_tokens: int, + temperature: float = 0.0, + eos_token_id: int | None = None, + ): + for _ in range(max_new_tokens): + logits = self(input_ids)[:, -1] + if temperature > 0: + probs = F.softmax(logits / temperature, dim=-1) + next_token = torch.multinomial(probs, 1) + else: + next_token = logits.argmax(-1, keepdim=True) + input_ids = torch.cat((input_ids, next_token), dim=1) + if eos_token_id is not None and (next_token.squeeze(-1) == eos_token_id).all(): + break + return input_ids diff --git a/kda/models/config.py b/kda/models/config.py new file mode 100644 index 0000000..4dae7c6 --- /dev/null +++ b/kda/models/config.py @@ -0,0 +1,62 @@ +"""KDAConfig — toy Causal LM hyperparameters. + +Defaults match the working reference-backend model: GVA with G=2, +safe gate (lower_bound=-5), q/k L2-norm and beta sigmoid inside the op. +""" +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass +class KDAConfig: + hidden_size: int = 64 + num_hidden_layers: int = 2 + num_heads: int = 4 + num_value_heads: int = 8 # G = num_value_heads // num_heads + head_dim: int = 16 + chunk_size: int = 16 + vocab_size: int = 256 + intermediate_size: int = 128 + max_position_embeddings: int = 128 + initializer_range: float = 0.02 + norm_eps: float = 1e-6 + use_gate_in_kernel: bool = True + use_qk_l2norm_in_kernel: bool = True + use_beta_sigmoid_in_kernel: bool = True + lower_bound: float | None = -5.0 + tie_word_embeddings: bool = False + kda_backend: str = "reference" # reference | triton | fla + attnres: str = "off" # off | full | block + attnres_block_size: int | None = None # DecoderBlocks / block; None ≈ L/8 + attnres_zero_init_queries: bool = True + attnres_final_aggregate: bool = True + gradient_checkpointing: bool = False + + @property + def H(self) -> int: return self.num_heads + + @property + def G(self) -> int: return self.num_value_heads // self.num_heads + + @property + def HV(self) -> int: return self.num_value_heads + + @property + def K(self) -> int: return self.head_dim + + @property + def V(self) -> int: return self.head_dim + + def __post_init__(self): + from ..layers.attn_res import validate_attnres + + if self.num_value_heads % self.num_heads: + raise ValueError("num_value_heads must be divisible by num_heads") + supported = {"reference", "triton", "fla", "torch", "auto"} + if self.kda_backend not in supported: + raise ValueError(f"kda_backend must be one of {sorted(supported)}") + validate_attnres(self.attnres, self.attnres_block_size) + + def layer_specs(self) -> list[tuple[str, str]]: + return [("kda", "swiglu")] * self.num_hidden_layers diff --git a/kda/models/k3_config.py b/kda/models/k3_config.py new file mode 100644 index 0000000..ce09eea --- /dev/null +++ b/kda/models/k3_config.py @@ -0,0 +1,128 @@ +"""K3Config — Kimi K3 架构的小规模复现配置 (KDA + Gated MLA + Stable LatentMoE). + +对照 learning/kimi-k3-notes §尺寸速查 (真实 K3 → 本 toy 缩比): + hidden 7168 → 256; L 93 → 4; H=HV 96 → 8; K=V 128 → 16; + MLA kv_lora 512 → 32, q_lora 1536 → 64, nope/v 128 → 16; + MoE ℓ=d/2=3584 → 128, 896/16 → 16/2, shared 2, d_ff 3072 → 96. + +Hybrid Attention (K3): 每 4 层 1 次 Gated MLA, 末层强制 MLA. + +Presets: + toy — ~8M, 自训 8k SP, 本地过拟合 + 0.5b — ~482M, Qwen3 词表, 32–40GB bf16;默认 step 是冒烟,翻译前置用 --max-tokens +""" +from __future__ import annotations + +from dataclasses import dataclass + +# Qwen3 config.json; train_k3 overrides with len(tokenizer). +QWEN3_VOCAB_SIZE = 151936 + + +@dataclass +class K3Config: + # 主干 + hidden_size: int = 256 + num_hidden_layers: int = 4 + vocab_size: int = 8192 # toy: data/spm_4k; 0.5b: Qwen3 + initializer_range: float = 0.02 + norm_eps: float = 1e-6 + tie_word_embeddings: bool = False + max_position_embeddings: int = 2048 # NoPE, 仅语义保留 + + # KDA (K3: H = HV = 96, 无 GVA) + num_heads: int = 8 + head_dim: int = 16 + chunk_size: int = 16 + lower_bound: float | None = -5.0 + use_gate_in_kernel: bool = True + use_qk_l2norm_in_kernel: bool = True + use_beta_sigmoid_in_kernel: bool = True + + # Gated MLA (NoPE) + kv_lora_rank: int = 32 + q_lora_rank: int = 64 + qk_nope_head_dim: int = 16 + v_head_dim: int = 16 + + # Stable LatentMoE + moe_latent_size: int = 128 # ℓ = d/2 + n_routed: int = 16 + top_k: int = 2 + n_shared: int = 2 + moe_d_ff: int = 96 + situ_beta1: float = 4.0 + situ_beta2: float = 25.0 + + kda_backend: str = "reference" + + # Depth mixer. off = DecoderBlock residual; block matches K3. + attnres: str = "off" # off | full | block + attnres_block_size: int | None = None # DecoderBlocks / AttnRes block; None ≈ L/8 + attnres_zero_init_queries: bool = True + attnres_final_aggregate: bool = True + gradient_checkpointing: bool = False + + def __post_init__(self): + from ..layers.attn_res import validate_attnres + + validate_attnres(self.attnres, self.attnres_block_size) + + @classmethod + def preset(cls, name: str) -> K3Config: + if name == "toy": + return cls() + if name in {"0.5b", "500m"}: + # H * head_dim == hidden. Routed 16: LatentMoE still runs every expert. + # ~482M with tied Qwen3 embeddings. 6×(3 KDA + 1 MLA). + return cls( + hidden_size=768, + num_hidden_layers=24, + vocab_size=QWEN3_VOCAB_SIZE, + tie_word_embeddings=True, + max_position_embeddings=2048, + num_heads=12, + head_dim=64, + chunk_size=64, + kv_lora_rank=192, + q_lora_rank=512, + qk_nope_head_dim=64, + v_head_dim=64, + moe_latent_size=384, + n_routed=16, + top_k=2, + n_shared=2, + moe_d_ff=512, + # The pure-PyTorch reference is far too slow at this size. + kda_backend="triton", + gradient_checkpointing=True, + ) + raise ValueError(f"unknown preset: {name}") + + @property + def H(self) -> int: + return self.num_heads + + @property + def HV(self) -> int: + return self.num_heads + + @property + def K(self) -> int: + return self.head_dim + + @property + def V(self) -> int: + return self.head_dim + + def layer_types(self) -> list[str]: + """Hybrid pattern: 每 4 层 1 次 MLA (0-based 层 3,7,...), 末层强制 MLA.""" + types = ["kda"] * self.num_hidden_layers + for i in range(self.num_hidden_layers): + if i % 4 == 3: + types[i] = "mla" + types[-1] = "mla" + return types + + def layer_specs(self) -> list[tuple[str, str]]: + return [(kind, "moe") for kind in self.layer_types()] diff --git a/kda/ops/__init__.py b/kda/ops/__init__.py new file mode 100644 index 0000000..6095684 --- /dev/null +++ b/kda/ops/__init__.py @@ -0,0 +1,5 @@ +"""KDA operator API and implementation backends.""" + +from .api import chunk_kda + +__all__ = ["chunk_kda"] diff --git a/kda/ops/api.py b/kda/ops/api.py new file mode 100644 index 0000000..3608b33 --- /dev/null +++ b/kda/ops/api.py @@ -0,0 +1,167 @@ +"""Training-facing KDA operator with the same boundary as FLA's ``chunk_kda``.""" + +from __future__ import annotations + +import warnings +from functools import lru_cache + +import torch +import torch.nn.functional as F + +from .reference.chunkwise import DECAY_BLOCK, _EXP_LIMIT, naive_chunk_kda + + +@lru_cache(maxsize=1) +def _fla_chunk_kda(): + try: + from fla.ops.kda import chunk_kda + except ImportError: + return None + return chunk_kda + + +def _reference_chunk_size(T: int, requested: int) -> int: + size = min(T, requested) + while T % size: + size -= 1 + return size + + +def chunk_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + *, + A_log: torch.Tensor | None = None, + dt_bias: torch.Tensor | None = None, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + use_qk_l2norm_in_kernel: bool = False, + use_gate_in_kernel: bool = False, + use_beta_sigmoid_in_kernel: bool = False, + safe_gate: bool = False, + lower_bound: float | None = None, + chunk_size: int = 64, + backend: str = "reference", +): + """Run an explicitly selected KDA implementation. + + ``reference`` and its legacy alias ``torch`` use this repository's + differentiable PyTorch implementation. ``triton`` uses the vendored + FLA NVIDIA Triton kernels in ``kda._fla`` (chunk_size 32 or 64, + CUDA). ``fla`` is reserved for explicit upstream parity runs. + """ + supported = {"reference", "triton", "fla", "torch", "auto"} + if backend not in supported: + raise ValueError(f"backend must be one of {sorted(supported)}") + if not use_qk_l2norm_in_kernel: + # Backend-independent: this is a property of the recurrence, not of any + # one implementation. + warnings.warn( + "use_qk_l2norm_in_kernel=False: KDA's chunkwise form assumes " + "||k||=1 so that I + tril(A_kk*beta) has a convergent Neumann " + "series. Unnormalised k makes the exact output grow like " + "||k||^chunk_size and can reach inf on any backend.", + RuntimeWarning, + stacklevel=2, + ) + if backend == "auto": + warnings.warn( + "backend='auto' is deprecated and now selects the local reference backend; " + "use backend='fla' explicitly for upstream FLA", + DeprecationWarning, + stacklevel=2, + ) + backend = "reference" + if backend == "torch": + backend = "reference" + if backend == "triton": + from .triton.chunk import chunk_kda as triton_chunk_kda + + fla_chunk = 32 if chunk_size <= 32 else 64 + return triton_chunk_kda( + q, + k, + v, + g, + beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + chunk_size=fla_chunk, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + use_gate_in_kernel=use_gate_in_kernel, + use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, + A_log=A_log, + dt_bias=dt_bias, + safe_gate=safe_gate, + lower_bound=lower_bound, + ) + if backend == "fla": + fused_op = _fla_chunk_kda() + if fused_op is None: + raise RuntimeError( + "backend='fla' requires a complete flash-linear-attention installation" + ) + return fused_op( + q, + k, + v, + g, + beta, + A_log=A_log, + dt_bias=dt_bias, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel, + use_gate_in_kernel=use_gate_in_kernel, + use_beta_sigmoid_in_kernel=use_beta_sigmoid_in_kernel, + safe_gate=safe_gate, + lower_bound=lower_bound, + chunk_size=32 if chunk_size <= 32 else 64, + ) + + if safe_gate and lower_bound is not None: + # _decayed_dot exponentiates at most DECAY_BLOCK steps of gate decay, + # and safe_gate bounds each step by |lower_bound|. + budget = DECAY_BLOCK * abs(lower_bound) + if budget > _EXP_LIMIT: + raise ValueError( + f"lower_bound={lower_bound} allows a gate span of {budget:.1f} " + f"per {DECAY_BLOCK}-row block, which overflows exp() " + f"(limit {_EXP_LIMIT:.1f}) and yields NaN. Use " + f"|lower_bound| < {_EXP_LIMIT / DECAY_BLOCK:.2f} or " + "backend='triton'." + ) + if use_qk_l2norm_in_kernel: + q, k = F.normalize(q, dim=-1), F.normalize(k, dim=-1) + if use_beta_sigmoid_in_kernel: + beta = beta.sigmoid() + if use_gate_in_kernel: + if A_log is None: + raise ValueError("A_log is required when use_gate_in_kernel=True") + bias = 0 if dt_bias is None else dt_bias.view(g.shape[-2:]) + gate_input = g + bias + rate = A_log.exp().view(1, 1, -1, 1) + if safe_gate: + if lower_bound is None: + raise ValueError("lower_bound is required when safe_gate=True") + g = lower_bound * torch.sigmoid(rate * gate_input) + else: + g = -rate * F.softplus(gate_input) + + return naive_chunk_kda( + q, + k, + v, + g, + beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + chunk_size=_reference_chunk_size(q.shape[1], chunk_size), + ) diff --git a/kda/ops/recurrent/__init__.py b/kda/ops/recurrent/__init__.py new file mode 100644 index 0000000..fc1c45f --- /dev/null +++ b/kda/ops/recurrent/__init__.py @@ -0,0 +1,5 @@ +"""Incremental recurrent KDA implementations and state containers.""" + +from .fused import KDAState, fused_recurrent_kda, fused_recurrent_kda_step + +__all__ = ["KDAState", "fused_recurrent_kda", "fused_recurrent_kda_step"] diff --git a/kda/ops/recurrent/fused.py b/kda/ops/recurrent/fused.py new file mode 100644 index 0000000..f92c13e --- /dev/null +++ b/kda/ops/recurrent/fused.py @@ -0,0 +1,73 @@ +"""L6: FLA fused recurrent KDA decode with optional step cache.""" + +from __future__ import annotations + +from dataclasses import dataclass + +import torch + +from kda._fla.ops.kda.fused_recurrent import fused_recurrent_kda as _fused_recurrent_kda + + +@dataclass +class KDAState: + """Mutable recurrent state cache: ``S`` is ``[B, HV, K, V]``.""" + + S: torch.Tensor + pos: int = 0 + + def reset(self): + self.S.zero_() + self.pos = 0 + + +def fused_recurrent_kda_step( + state: KDAState, + q_t: torch.Tensor, + k_t: torch.Tensor, + v_t: torch.Tensor, + g_t: torch.Tensor, + beta_t: torch.Tensor, + scale: float | None = None, +): + """Single-token step. Inputs are ``[B, H|HV, ...]`` (no time dim).""" + o, ht = _fused_recurrent_kda( + q_t.unsqueeze(1), + k_t.unsqueeze(1), + v_t.unsqueeze(1), + g_t.unsqueeze(1), + beta_t.unsqueeze(1), + scale=scale, + initial_state=state.S, + output_final_state=True, + ) + state.S = ht + state.pos += 1 + return o.squeeze(1) + + +def fused_recurrent_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + **kwargs, +): + return _fused_recurrent_kda( + q, + k, + v, + g, + beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + **kwargs, + ) + + +__all__ = ["KDAState", "fused_recurrent_kda", "fused_recurrent_kda_step"] diff --git a/kda/ops/reference/__init__.py b/kda/ops/reference/__init__.py new file mode 100644 index 0000000..b2d5d82 --- /dev/null +++ b/kda/ops/reference/__init__.py @@ -0,0 +1,13 @@ +"""Readable PyTorch implementations used as correctness references.""" + +from .chunkwise import naive_chunk_kda +from .gate import kda_gate_naive, kda_gate_reference +from .recurrent import naive_kda, naive_kda_fwd + +__all__ = [ + "kda_gate_naive", + "kda_gate_reference", + "naive_chunk_kda", + "naive_kda", + "naive_kda_fwd", +] diff --git a/kda/ops/reference/chunkwise.py b/kda/ops/reference/chunkwise.py new file mode 100644 index 0000000..7577fb9 --- /dev/null +++ b/kda/ops/reference/chunkwise.py @@ -0,0 +1,155 @@ +"""Pure-PyTorch chunked reference implementation of KDA.""" +from __future__ import annotations + +import math +import warnings + +import torch +from einops import rearrange + +#: ``exp`` overflows past this exponent in fp32 and bf16 (both top out at 3.4e38). +_EXP_LIMIT = math.log(torch.finfo(torch.float32).max) + + +#: Row-block size for :func:`_decayed_dot`. +#: +#: The g_ref GEMM exponentiates the gate span between the reference row and the +#: rows/columns it covers, so the block size caps that exponent at +#: ``DECAY_BLOCK * max|g|``. With the default ``lower_bound=-5`` gate that is +#: ``16 * 5 = 80 < ln(3.4e38) = 88.7``, i.e. fp32/bf16-safe for any chunk size. +#: Referencing a whole 64-row chunk instead would allow ``64 * 5 = 320`` and +#: overflow to NaN once the gate saturates. +DECAY_BLOCK = 16 + + +def _decayed_dot(x: torch.Tensor, k: torch.Tensor, g: torch.Tensor) -> torch.Tensor: + """Return ``A[..., i, j] = `` (FLA g_ref GEMM). + + Only the causal part (``j <= i``) is exact; callers mask the rest, which is + left at zero. Rows are processed in blocks of :data:`DECAY_BLOCK` against + the block's own first row, which is what bounds the exponent: for a row + block starting at ``r``, ``exp(g_i - g_ref)`` spans at most ``DECAY_BLOCK`` + steps, and ``exp(g_ref - g_j)`` is ``<= 1`` for ``j < r`` and likewise spans + at most ``DECAY_BLOCK`` steps for ``j >= r``. + """ + C = g.shape[-2] + out = g.new_zeros(*g.shape[:-1], C) + for r in range(0, C, DECAY_BLOCK): + end = min(r + DECAY_BLOCK, C) + g_ref = g[..., r : r + 1, :] + rows = x[..., r:end, :] * (g[..., r:end, :] - g_ref).exp() + cols = k[..., :end, :] * (g_ref - g[..., :end, :]).exp() + out[..., r:end, :end] = rows @ cols.transpose(-1, -2) + return out + + +#: Whether :func:`naive_chunk_kda` checks the gate span against the ``exp`` +#: budget. The check costs one device sync per call; set it to ``False`` if that +#: matters more than diagnosing a NaN. +CHECK_DECAY_SPAN = True + + +def _max_decay_span(g_cumsum: torch.Tensor) -> torch.Tensor: + """Largest ``|g_ref - g_j|`` any row block will exponentiate.""" + C = g_cumsum.shape[-2] + if C % DECAY_BLOCK == 0: + blocks = g_cumsum.unflatten(-2, (C // DECAY_BLOCK, DECAY_BLOCK)) + return (blocks[..., :1, :] - blocks).abs().amax() + return torch.stack( + [ + (g_cumsum[..., r : r + 1, :] - g_cumsum[..., r : r + DECAY_BLOCK, :]) + .abs() + .amax() + for r in range(0, C, DECAY_BLOCK) + ] + ).amax() + + +def _warn_if_decay_span_overflows(g_cumsum: torch.Tensor) -> None: + """Warn when a row block's gate span is about to overflow ``exp``. + + ``DECAY_BLOCK`` bounds this for the default ``safe_gate`` path, but an + unbounded gate (``-A.exp() * softplus(x)``) can still exceed it. + """ + span = _max_decay_span(g_cumsum).item() + if span > _EXP_LIMIT: + warnings.warn( + f"gate span within a {DECAY_BLOCK}-row block is {span:.1f} > " + f"{_EXP_LIMIT:.1f}; exp() will overflow to inf and the output will " + "be NaN. Reduce the gate magnitude (e.g. safe_gate with a smaller " + "|lower_bound|) or use backend='triton'.", + RuntimeWarning, + stacklevel=3, + ) + + +def naive_chunk_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + chunk_size: int = 64, +): + """Chunk-parallel, inter-chunk recurrent KDA reference. + + Shapes are ``q/k: [B,T,H,K]``, ``v: [B,T,HV,V]``, + ``g: [B,T,HV,K]`` and ``beta: [B,T,HV]``. + """ + dtype = v.dtype + B, T, H, K = q.shape + HV, V = v.shape[2], v.shape[-1] + C = chunk_size + assert HV % H == 0, f"HV={HV} must be divisible by H={H}" + assert T % C == 0, f"T={T} must be divisible by chunk_size={C}" + scale = K**-0.5 if scale is None else scale + + q, k = [ + rearrange(x, "b (n c) h d -> b h n c d", c=C) + .repeat_interleave(HV // H, dim=1) + for x in (q, k) + ] + v, g = [rearrange(x, "b (n c) h d -> b h n c d", c=C) for x in (v, g)] + beta = rearrange(beta, "b (n c) h -> b h n c", c=C) + q = q * scale + g = g.cumsum(dim=-2) + if CHECK_DECAY_SPAN: + _warn_if_decay_span_overflows(g) + + # r_i + sum_{j r_j + # = v_i - . + mask_upper = torch.triu(torch.ones(C, C, dtype=torch.bool, device=q.device)) + mask_strict_upper = torch.triu(mask_upper, diagonal=1) + eye = torch.eye(C, dtype=q.dtype, device=q.device) + A_kk = _decayed_dot(k, k, g) + M = eye + (A_kk * beta[..., None, :]).masked_fill(mask_upper, 0) + W = torch.linalg.solve_triangular(M, g.exp() * k, upper=False) + U = torch.linalg.solve_triangular(M, v, upper=False) + + # Output includes the current token, hence the diagonal is retained. + A_qk = (_decayed_dot(q, k, g) * beta[..., None, :]).masked_fill(mask_strict_upper, 0) + + S = q.new_zeros(B, HV, K, V) + if initial_state is not None: + S = S + initial_state + o = v.new_empty(B, HV, T // C, C, V) + + for n in range(T // C): + q_n, k_n, g_n = q[:, :, n], k[:, :, n], g[:, :, n] + r = U[:, :, n] - W[:, :, n] @ S + o[:, :, n] = (q_n * g_n.exp()) @ S + A_qk[:, :, n] @ r + + decay = (g_n[:, :, -1:, :] - g_n).exp() + S = S * g_n[:, :, -1, :, None].exp() + S = S + (decay * k_n).transpose(-1, -2) @ (r * beta[:, :, n, :, None]) + + if not output_final_state: + S = None + return rearrange(o, "b h n c d -> b (n c) h d").to(dtype), S + + +# Backward-compatible name used by earlier notes/scripts. +naive_chunk_kda_fwd = naive_chunk_kda diff --git a/kda/ops/reference/gate.py b/kda/ops/reference/gate.py new file mode 100644 index 0000000..46bb926 --- /dev/null +++ b/kda/ops/reference/gate.py @@ -0,0 +1,47 @@ +"""PyTorch references for the two KDA gate activations.""" +from __future__ import annotations + +import torch +import torch.nn.functional as F + + +def kda_gate_reference( + g: torch.Tensor, + A_log: torch.Tensor, + dt_bias: torch.Tensor | None = None, + *, + safe_gate: bool = False, + lower_bound: float | None = None, +) -> torch.Tensor: + """Compute the official KDA gate semantics in PyTorch. + + ``A_log`` is head-wise with shape ``[HV]`` and ``dt_bias`` is + per-dimension with shape ``[HV, K]`` (or flattened to ``[HV*K]``). + """ + HV, K = g.shape[-2:] + gate_input = g if dt_bias is None else g + dt_bias.view(HV, K) + rate = A_log.view(HV, 1).exp() + if safe_gate: + if lower_bound is None: + raise ValueError("lower_bound is required when safe_gate=True") + return lower_bound * torch.sigmoid(rate * gate_input) + return -rate * F.softplus(gate_input) + + +def kda_gate_naive( + g: torch.Tensor, + A_log: torch.Tensor, + dt_bias: torch.Tensor | None = None, + lower_bound: float | None = None, +) -> torch.Tensor: + """Compatibility name matching FLA's reference gate convention.""" + return kda_gate_reference( + g, + A_log, + dt_bias, + safe_gate=lower_bound is not None, + lower_bound=lower_bound, + ) + + +__all__ = ["kda_gate_naive", "kda_gate_reference"] diff --git a/kda/ops/reference/recurrent.py b/kda/ops/reference/recurrent.py new file mode 100644 index 0000000..301c1ff --- /dev/null +++ b/kda/ops/reference/recurrent.py @@ -0,0 +1,298 @@ +"""L1: Naive recurrent KDA fwd+bwd (torch only). + +公式 (per timestep t, log-space gate; q/k 入口 H 维, 内部 repeat_interleave 到 HV): + S_t = exp(g_t) * S_{t-1} + (beta_t * k_t) outer (v_t - k_t . (exp(g_t) * S_{t-1})) + o_t = (q_t * scale) . S_t + +backward (BPTT, T -> 0): + 设 dS_t 为进入 t 步累积的反传梯度 (含 o_t 反传). + 1. o_t = q_t . S_t -> dS_t += q_t outer do_t (i.e. dS = dS + q_t·do_t) + dq_t = do_t . S_t^T -> einsum('bhv,bhkv->bhk') + 2. S_t = S_decay + a_t outer r_t, a_t = b_t k_t, r_t = v_t - k_t . S_decay + 其中 S_decay = exp(g_t) * S_{t-1} + dS_{t-1} = exp(g_t) * (dS_t - r_t outer da_t - a_t outer dr_t) via residual 反传 + 更具体: + dS_decay = dS_t - (a_t outer dr_t) - (da_t outer r_t) + dS_{t-1} += exp(g_t) * dS_decay + 其中 dr_t = -dv_t + dS_t . a_t^T (因为 r_t = v - k·S_dec, dr 来自 -dv - k·dS_decay) + da_t = -r_t outer dS_t? 让我直接推导下面. + 推导 (设 G1 = S_t, 走 a = r 反向链 通过 autograd): + o_t = q_t . G1 + dq_t = do_t . G1^T -> [B,HV,K] + dG1 = q_t outer do_t -> [B,HV,K,V] = dS_t (上游) + G1 = Sdec + a outer r -> Sdec = G1[...] (跳过) + dSdec = dG1 + da_t = r_t outer dG1 -> [B,HV,K] (因为 a outer r 是 K-V, d(a outer r) = r outer d[...,V]) + 但在 einsum 表示: dA_t.grad = einsum('bhkv,bhv->bhk', dS_t, r_t) + dr_t = a_t outer dG1 -> [B,HV,V] = einsum('bhkv,bhk->bhv', dS_t, a_t) + plus: S_t = Sdec + a outer r -> a outer r - outer product 形状是 [B,HV,K,V] = einsum('bhk,bhv->bhkv') + d(a outer r) 的雅可比: let G1_m = a_t ⊗ r_t (rank-1 matrix per (b,h)) + dG1_m[i,j] = da_t[i] * r_t[j] + a_t[i] * dr_t[j] + 在外积形式, 即 dG1_m = a_outer r 的张量积正交分解: + da_t = sum_j r_t[j] dG1_m[i,j] = einsum('bhkv,bhv->bhk', dG1_m, r_t) + dr_t = sum_i a_t[i] dG1_m[i,j] = einsum('bhkv,bhk->bhv', dG1_m, a_t) + 因为 a_t = b_t k_t -> da_t = db_t k_t + b_t dk_t (b_t 是 ...) + db_t = einsum('bhk,bhk->bh', da_t, k_t) + dk_t_a = b_t * da_t (来自 a_t 路径, 还有来自 r_t 路径和 S_dec 路径) + 因为 r_t = v_t - k_t . S_dec -> 注 rk_t grad via dg,S_dec 和 dv_t + dv_t = -dr_t (实际 dr 的负梯度) 即 dv_t = -dr_t + 这里 r_t = v_t - k_t · S_dec, 写作矩阵乘 r = v - einsum('bhk,bhkv->bhv', k, S_dec) + dr = -dv - einsum('bhk,bhkv->bhv', dk_from_r, S_dec) + eigengrad via S_dec + 更精确的反向: r_t = v_t - k_t . S_dec + dv_t += -dr_t -> dv_t = -dr_t + dk_t_r_path = -S_dec outer dr_t (即 -dS_dec 传递来自 k_t 的部分) + 具体: d(k·S) = dk·S + k·dS -> dS_dec 这层, dk 的贡献: -S_dec outer dr_t + 即 dk_t_r = einsum('bhv,bhkv->bhk', -dr_t, S_dec) + dS_dec_r = -k_t outer dr_t = -einsum('bhv,bhk->bhkv', dr_t, k_t) + 合并: dS_dec 合总 = dG1 + (-k_t outer dr_t) + = dS_t - k_t outer dr_t + (相加过的 dv, dk_r, dS_dec_r 都上面项) + Sdec = exp(g_t) * S_{t-1}: + dS_{t-1} = exp(g_t) ⊙ dS_dec (因为 Sdec = exp_g * S_prev, 微分后 exp_g 直接相乘) + dg_t = exp(g_t) * S_prev * dS_dec (微分时对 g_t (log-space) 求偏导数) + 即 dg_t = exp(g_t) * (S_{t-1} ⊙ dS_dec) -> 沿 K 维求和 + in einsum: dg_t = sum over v of (exp(g_t) * S_{t-1}) ⊙ dS_dec ...\n + = einsum('bhk, bhk, bhkv -> bhk', exp_g, S_prev, dS_dec) + 更简洁: Sdec = exp_g * S_prev (per-(b,h,k)/v), 故 dSdec/dg_t = S_prev * exp_g + 所以 dg_t = sum_v S_prev_sub_k_dim * exp_g * dS_dec -> [B, HV, K] + einsum: dg_t = einsum('bhkv,bhkv->bhk', Sdec, dS_dec) + (因为 Sdec = S_prev * exp_g, sum_v Sdec[:, :, :, v] * dS_dec[:, :, :, v] = sum_v Sdec_eachK * dSdec_eachK) + einsum上是 einsum('bhkv,bhkv->bhk', Sdec, dSdec) + dS_{t-1} = exp_g ⊙ dSdec (per (b,h,k,v) entrywise multiply exp_g with dSdec) + +GVA 反归约: + q,k 入口 [B, T, H, K] --repeat_interleave(G, dim=2)--> [B, T, HV, K] + 内部计算后, dq/dk 在 HV 维上 -> dV 拿 shape [B,T,HV,K] + bwd 通过 sum 回 H: dq_H = dq_HV.view(B,T,H,G,K).sum(dim=3) -> [B,T,H,K] + (因为 repeat_interleave 是复制, 反传是 sum 路径相同意义) + +记号对照: + a_t = b_t * k_t (a = beta * k) [B, HV, K] + r_t = v_t - k_t . S_dec (residual) [B, HV, V] + S_dec = exp(g_t) * S_{t-1} [B, HV, K, V] + S_t = S_dec + a_t outer r_t [B, HV, K, V] + o_t = q_t . S_t = (q_t_eff * scale) . S_t [B, HV, V] +""" +from __future__ import annotations + +import math + +import torch + + +def naive_kda_fwd( + q: torch.Tensor, # [B, T, H, K] + k: torch.Tensor, # [B, T, H, K] + v: torch.Tensor, # [B, T, HV, V] + g: torch.Tensor, # [B, T, HV, K] + beta: torch.Tensor, # [B, T, HV] + scale: float | None = None, + initial_state: torch.Tensor | None = None, # [B, HV, K, V] + output_final_state: bool = False, + *, + force_float32: bool = False, +): + """纯 forward, 不带 autograd. 与上游 naive_recurrent_kda 数值等价. + + force_float32=True 时强制 fp32 计算 (与上游对拍时用); + 默认保持输入 dtype (gradcheck 用 fp64). + """ + dtype = v.dtype + B, T, H, K = q.shape + HV, V = v.shape[2], v.shape[-1] + G = HV // H + if scale is None: + scale = 1.0 / math.sqrt(K) + + # 上游强制 fp32; 本实现默认保留输入 dtype 以便 gradcheck 适用 fp64 + # force_float32=True 时与上游逐位对齐 + work_dtype = torch.float if force_float32 else q.dtype + q = q.to(work_dtype) + k = k.to(work_dtype) + v = v.to(work_dtype) + g = g.to(work_dtype) + beta = beta.to(work_dtype) + + # GVA: expand q/k from H to HV + qe = q.repeat_interleave(G, dim=2) * scale # [B, T, HV, K] + ke = k.repeat_interleave(G, dim=2) # [B, T, HV, K] + + S = torch.zeros(B, HV, K, V, dtype=work_dtype, device=q.device) + if initial_state is not None: + S = S + initial_state.to(work_dtype) + + o = torch.empty(B, T, HV, V, dtype=work_dtype, device=q.device) + for t in range(T): + q_t = qe[:, t] # [B, HV, K] + k_t = ke[:, t] # [B, HV, K] + v_t = v[:, t] # [B, HV, V] + g_t = g[:, t] # [B, HV, K] + b_t = beta[:, t] # [B, HV] + + S_dec = S * g_t.exp().unsqueeze(-1) # [B, HV, K, V] + p_t = torch.einsum('b h k, b h k v -> b h v', k_t, S_dec) # [B, HV, V] + r_t = v_t - p_t # [B, HV, V] + a_t = b_t.unsqueeze(-1) * k_t # [B, HV, K] + S = S_dec + torch.einsum('b h k, b h v -> b h k v', a_t, r_t) + o[:, t] = torch.einsum('b h k, b h k v -> b h v', q_t, S) + + if not output_final_state: + S = None + return o.to(dtype), S + + +class KDAFunction(torch.autograd.Function): + """autograd Function (forward + backward). + + forward 入参顺序 (q, k, v, g, beta, scale, initial_state, output_final_state) + backward 必须返回一致: (dq, dk, dv, dg, dbeta, None, dinit_state, None) + """ + + @staticmethod + def forward(ctx, q, k, v, g, beta, scale, initial_state, output_final_state): + dtype = v.dtype + B, T, H, K = q.shape + HV, V = v.shape[2], v.shape[-1] + G = HV // H + if scale is None: + scale = 1.0 / math.sqrt(K) + + work_dtype = q.dtype + qf = q.to(work_dtype).contiguous() + kf = k.to(work_dtype).contiguous() + vf = v.to(work_dtype).contiguous() + gf = g.to(work_dtype).contiguous() + bf = beta.to(work_dtype).contiguous() + + # GVA: expand q/k from H to HV + qe = qf.repeat_interleave(G, dim=2) * scale # [B, T, HV, K] + ke = kf.repeat_interleave(G, dim=2) # [B, T, HV, K] + + S = torch.zeros(B, HV, K, V, dtype=work_dtype, device=q.device) + if initial_state is not None: + S = S + initial_state.to(work_dtype) + + o = torch.empty(B, T, HV, V, dtype=work_dtype, device=q.device) + q_ts, k_ts, b_ts, S_decs, r_ts, a_ts, exp_g_ts = [], [], [], [], [], [], [] + + for t in range(T): + q_t = qe[:, t] + k_t = ke[:, t] + v_t = vf[:, t] + g_t = gf[:, t] + b_t = bf[:, t] + exp_g_t = g_t.exp() + S_dec = S * exp_g_t.unsqueeze(-1) + p_t = torch.einsum('b h k, b h k v -> b h v', k_t, S_dec) + r_t = v_t - p_t + a_t = b_t.unsqueeze(-1) * k_t + S = S_dec + torch.einsum('b h k, b h v -> b h k v', a_t, r_t) + o[:, t] = torch.einsum('b h k, b h k v -> b h v', q_t, S) + + q_ts.append(q_t) + k_ts.append(k_t) + b_ts.append(b_t) + S_decs.append(S_dec) + r_ts.append(r_t) + a_ts.append(a_t) + exp_g_ts.append(exp_g_t) + + ctx.save_for_backward( + torch.stack(q_ts, dim=1), + torch.stack(k_ts, dim=1), + torch.stack(b_ts, dim=1), + torch.stack(S_decs, dim=1), + torch.stack(r_ts, dim=1), + torch.stack(a_ts, dim=1), + torch.stack(exp_g_ts, dim=1), + ) + ctx.G = G + ctx.H = H + ctx.HV = HV + ctx.K = K + ctx.V = V + ctx.T = T + ctx.B = B + ctx.scale = scale + ctx.dtype = dtype + ctx.has_initial_state = initial_state is not None + ctx.output_final_state = output_final_state + + final_S = S if output_final_state else None + return o.to(dtype), final_S + + @staticmethod + def backward(ctx, do, dS): + q_ts, k_ts, b_ts, S_decs, r_ts, a_ts, exp_g_ts = ctx.saved_tensors + B, T, H, HV, K, V, G = ctx.B, ctx.T, ctx.H, ctx.HV, ctx.K, ctx.V, ctx.G + + work_dtype = q_ts.dtype + device = q_ts.device + + dq_e = torch.zeros(B, T, HV, K, dtype=work_dtype, device=device) + dk_e = torch.zeros(B, T, HV, K, dtype=work_dtype, device=device) + dv = torch.zeros(B, T, HV, V, dtype=work_dtype, device=device) + dg = torch.zeros(B, T, HV, K, dtype=work_dtype, device=device) + dbeta= torch.zeros(B, T, HV, dtype=work_dtype, device=device) + + if dS is None: + dS_acc = torch.zeros(B, HV, K, V, dtype=work_dtype, device=device) + else: + dS_acc = dS.to(work_dtype).clone() + + for t in range(T - 1, -1, -1): + q_t = q_ts[:, t] + k_t = k_ts[:, t] + b_t = b_ts[:, t] + S_dec = S_decs[:, t] + r_t = r_ts[:, t] + a_t = a_ts[:, t] + exp_g_t = exp_g_ts[:, t] + do_t = do[:, t].to(work_dtype) + + S_t = S_dec + torch.einsum('b h k, b h v -> b h k v', a_t, r_t) + dS_acc = dS_acc + torch.einsum('b h k, b h v -> b h k v', q_t, do_t) + dq_e[:, t] = torch.einsum('b h v, b h k v -> b h k', do_t, S_t) + + da_t = torch.einsum('b h v, b h k v -> b h k', r_t, dS_acc) + dr_t = torch.einsum('b h k, b h k v -> b h v', a_t, dS_acc) + + dbeta[:, t] = torch.einsum('b h k, b h k -> b h', k_t, da_t) + dk_t_a = b_t.unsqueeze(-1) * da_t + + dv[:, t] = dr_t + dS_dec_from_r = -torch.einsum('b h v, b h k -> b h k v', dr_t, k_t) + dk_t_r = -torch.einsum('b h v, b h k v -> b h k', dr_t, S_dec) + + dS_dec_total = dS_acc + dS_dec_from_r + dk_e[:, t] = dk_t_a + dk_t_r + + dg[:, t] = torch.einsum('b h k v, b h k v -> b h k', S_dec, dS_dec_total) + dS_acc = exp_g_t.unsqueeze(-1) * dS_dec_total + + if HV > H: + dq_H = dq_e.view(B, T, H, G, K).sum(dim=3) + dk_H = dk_e.view(B, T, H, G, K).sum(dim=3) + else: + dq_H = dq_e + dk_H = dk_e + + # q 在 forward 内被乘过 scale (qe = q * scale), chain rule: dq_orig = dq_e * scale + dq_H = dq_H * ctx.scale + + return (dq_H.to(ctx.dtype), dk_H.to(ctx.dtype), dv.to(ctx.dtype), + dg.to(ctx.dtype), dbeta.to(ctx.dtype), None, None, None) + + +def naive_kda( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, +): + """对外入口: 调 KDAFunction.apply.""" + return KDAFunction.apply(q, k, v, g, beta, scale, initial_state, output_final_state) diff --git a/kda/ops/triton/__init__.py b/kda/ops/triton/__init__.py new file mode 100644 index 0000000..7af7d52 --- /dev/null +++ b/kda/ops/triton/__init__.py @@ -0,0 +1,7 @@ +"""Local Triton KDA kernels vendored from FLA chunk_{fwd,intra,bwd,wy,gate}.""" + +from .chunk import ChunkKDAFunction, chunk_kda +from .chunk_fwd import chunk_kda_fwd +from .gate import kda_gate_fwd + +__all__ = ["ChunkKDAFunction", "chunk_kda", "chunk_kda_fwd", "kda_gate_fwd"] diff --git a/kda/ops/triton/chunk.py b/kda/ops/triton/chunk.py new file mode 100644 index 0000000..c14409e --- /dev/null +++ b/kda/ops/triton/chunk.py @@ -0,0 +1,5 @@ +"""FLA ``chunk_kda`` surface used by ``ops.api`` backend='triton'.""" + +from kda._fla.ops.kda.chunk import ChunkKDAFunction, chunk_kda + +__all__ = ["ChunkKDAFunction", "chunk_kda"] diff --git a/kda/ops/triton/chunk_bwd.py b/kda/ops/triton/chunk_bwd.py new file mode 100644 index 0000000..9d195d7 --- /dev/null +++ b/kda/ops/triton/chunk_bwd.py @@ -0,0 +1,5 @@ +"""Vendored FLA chunk KDA backward.""" + +from kda._fla.ops.kda.chunk_bwd import chunk_kda_bwd + +__all__ = ["chunk_kda_bwd"] diff --git a/kda/ops/triton/chunk_fwd.py b/kda/ops/triton/chunk_fwd.py new file mode 100644 index 0000000..d60a29e --- /dev/null +++ b/kda/ops/triton/chunk_fwd.py @@ -0,0 +1,37 @@ +"""Vendored FLA chunk KDA forward, returning ``(o, ht)`` like the public op.""" + +from __future__ import annotations + +import torch + +from kda._fla.ops.kda.chunk import chunk_kda +from kda._fla.ops.kda.chunk_fwd import chunk_kda_fwd as fla_chunk_kda_fwd + +__all__ = ["chunk_kda_fwd", "fla_chunk_kda_fwd"] + + +def chunk_kda_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + scale: float | None = None, + initial_state: torch.Tensor | None = None, + output_final_state: bool = False, + chunk_size: int = 64, + **kwargs, +): + """Chunked KDA forward with FLA kernels. Returns ``(o, ht)``.""" + return chunk_kda( + q, + k, + v, + g, + beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + chunk_size=chunk_size, + **kwargs, + ) diff --git a/kda/ops/triton/gate.py b/kda/ops/triton/gate.py new file mode 100644 index 0000000..91d5b83 --- /dev/null +++ b/kda/ops/triton/gate.py @@ -0,0 +1,36 @@ +"""Vendored FLA KDA gate fusion (standard + safe gate + chunk cumsum).""" + +from __future__ import annotations + +import torch + +from kda._fla.ops.kda.gate import ( + kda_gate_bwd, + kda_gate_chunk_cumsum, + kda_gate_fwd as _kda_gate_fwd, +) + +DEFAULT_LOWER_BOUND = -5.0 + + +def kda_gate_fwd( + g: torch.Tensor, + A_log: torch.Tensor, + dt_bias: torch.Tensor | None = None, + lower_bound: float | None = DEFAULT_LOWER_BOUND, +): + return _kda_gate_fwd( + g, + A_log=A_log, + dt_bias=dt_bias, + lower_bound=lower_bound, + output_dtype=g.dtype, + ) + + +__all__ = [ + "DEFAULT_LOWER_BOUND", + "kda_gate_bwd", + "kda_gate_chunk_cumsum", + "kda_gate_fwd", +] diff --git a/kda/ops/triton/wy_fast.py b/kda/ops/triton/wy_fast.py new file mode 100644 index 0000000..607949e --- /dev/null +++ b/kda/ops/triton/wy_fast.py @@ -0,0 +1,5 @@ +"""Vendored FLA WY recompute used by the chunk KDA backward.""" + +from kda._fla.ops.kda.wy_fast import recompute_w_u_fwd + +__all__ = ["recompute_w_u_fwd"] diff --git a/kda/training/__init__.py b/kda/training/__init__.py new file mode 100644 index 0000000..64c6bde --- /dev/null +++ b/kda/training/__init__.py @@ -0,0 +1,5 @@ +"""Training and checkpoint helpers.""" + +from .toy import load_ckpt, make_toy_data, save_ckpt, train_one_batch + +__all__ = ["load_ckpt", "make_toy_data", "save_ckpt", "train_one_batch"] diff --git a/kda/training/data.py b/kda/training/data.py new file mode 100644 index 0000000..cedbf69 --- /dev/null +++ b/kda/training/data.py @@ -0,0 +1,355 @@ +"""Pretrain / SFT sample construction. + +Pretrain: Wikipedia parquet → tokenize → pack (B, T). Languages mix 1:1 by +token via seq_len-sized blocks so each training chunk is monolingual. + +SFT: instruction-parallel rows → prompt-masked labels. Template lives in +``prompts.instruction_prompt`` (same string as eval_mt). +""" +from __future__ import annotations + +import json +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Protocol + +import torch + +from .prompts import instruction_prompt + +WIKI_SHARD_TOTAL = {"zh": 6, "en": 41} +WIKI_BASE = ( + "https://huggingface.co/datasets/wikimedia/wikipedia/resolve/main/20231101.{lang}" +) +IGNORE_INDEX = -100 + + +class Tokenizer(Protocol): + vocab_size: int + + def encode(self, text: str) -> list[int]: ... + + def decode(self, ids: list[int]) -> str: ... + + +@dataclass +class SentencePieceTokenizer: + _sp: object + + @property + def vocab_size(self) -> int: + return int(self._sp.vocab_size()) + + def encode(self, text: str) -> list[int]: + return list(self._sp.encode(text, out_type=int)) + + def decode(self, ids: list[int]) -> str: + return str(self._sp.decode(ids)) + + +@dataclass +class HuggingFaceTokenizer: + _tok: object + + @property + def vocab_size(self) -> int: + return int(len(self._tok)) + + def encode(self, text: str) -> list[int]: + return list(self._tok.encode(text, add_special_tokens=False)) + + def decode(self, ids: list[int]) -> str: + return str(self._tok.decode(ids, skip_special_tokens=True)) + + +def load_tokenizer(source: str) -> Tokenizer: + """`.model` 走 SentencePiece, 其它当作 HuggingFace 名或本地目录.""" + if source.endswith(".model"): + from sentencepiece import SentencePieceProcessor + + return SentencePieceTokenizer(SentencePieceProcessor(model_file=source)) + from transformers import AutoTokenizer + + tok = AutoTokenizer.from_pretrained(source, trust_remote_code=True) + return HuggingFaceTokenizer(tok) + + +def pretrain_dir() -> Path: + for candidate in ( + os.environ.get("KDA_PRETRAIN_DIR"), + "/data/pretrain", + "data/pretrain", + ): + if candidate and Path(candidate).is_dir(): + return Path(candidate) + return Path("data/pretrain") + + +def _wiki_files(lang: str, n_shards: int) -> list[str]: + if lang not in WIKI_SHARD_TOTAL: + raise ValueError(f"unsupported wiki lang {lang!r}; expected zh or en") + total = WIKI_SHARD_TOTAL[lang] + n = min(max(n_shards, 1), total) + base = WIKI_BASE.format(lang=lang) + return [f"{base}/train-{i:05d}-of-{total:05d}.parquet" for i in range(n)] + + +def _cache_path(cache_dir: Path, lang: str, n_shards: int, limit: int) -> Path: + return cache_dir / f"wiki-{lang}-n{n_shards}-limit{limit}.jsonl" + + +def fetch_wiki_texts( + limit: int, + lang: str = "zh", + n_shards: int = 2, + cache_dir: str | Path | None = None, +) -> list[str]: + """Load up to ``limit`` article bodies, caching jsonl under pretrain_dir.""" + cache = Path(cache_dir) if cache_dir is not None else pretrain_dir() + cache.mkdir(parents=True, exist_ok=True) + path = _cache_path(cache, lang, n_shards, limit) + if path.exists(): + texts: list[str] = [] + with path.open(encoding="utf-8") as fh: + for line in fh: + line = line.strip() + if not line: + continue + texts.append(json.loads(line)["text"]) + if len(texts) >= limit: + break + if texts: + return texts + + from datasets import load_dataset + + files = _wiki_files(lang, n_shards) + ds = load_dataset("parquet", data_files=files, split="train", streaming=True) + texts = [] + for i, row in enumerate(ds): + if i >= limit: + break + texts.append(row["text"]) + tmp = path.with_suffix(path.suffix + ".tmp") + with tmp.open("w", encoding="utf-8") as fh: + for text in texts: + fh.write(json.dumps({"text": text}, ensure_ascii=False) + "\n") + tmp.replace(path) + return texts + + +def tokenize_corpus(texts: list[str], tok: Tokenizer) -> list[int]: + ids: list[int] = [] + for text in texts: + ids.extend(tok.encode(text)) + return ids + + +def interleave_balanced(ids_a: list[int], ids_b: list[int], block: int) -> list[int]: + """1:1 by token: seq_len-sized monolingual blocks, drop the longer tail.""" + if block < 1: + raise ValueError(f"block must be >= 1, got {block}") + n = min(len(ids_a), len(ids_b)) + n = (n // block) * block + out: list[int] = [] + a, b = ids_a, ids_b + for i in range(0, n, block): + out.extend(a[i : i + block]) + out.extend(b[i : i + block]) + return out + + +def chunk_ids(ids: list[int], batch: int, seq_len: int) -> torch.Tensor: + """切成 (num_chunks, B, T); 末尾不足部分丢弃.""" + n = (len(ids) // (batch * seq_len)) * (batch * seq_len) + t = torch.tensor(ids[:n], dtype=torch.long) + if n == 0: + return t.view(0, batch, seq_len) + return t.view(batch, -1, seq_len).transpose(0, 1) + + +def split_heldout( + chunks: torch.Tensor, + frac: float = 0.01, + min_heldout: int = 1, +) -> tuple[torch.Tensor, torch.Tensor]: + """Last ``frac`` of packed chunks for CE only. Empty held-out if too few.""" + n = int(chunks.size(0)) + if n <= 1 or frac <= 0: + return chunks, chunks[:0] + h = max(min_heldout, int(n * frac)) + h = min(h, n - 1) + return chunks[:-h], chunks[-h:] + + +def iter_chunks(chunks: torch.Tensor): + """逐块产出 (input_ids, labels), labels 右移 (模型内 CE shift).""" + for chunk in chunks: + yield chunk, chunk.clone() + + +def iter_indexed(chunks: torch.Tensor, start: int = 0): + """Infinite cycle with a global index (for --resume).""" + n = int(chunks.size(0)) + if n == 0: + raise ValueError("no training chunks") + i = start + while True: + x = chunks[i % n] + yield i, x, x.clone() + i += 1 + + +def load_pretrain_chunks( + tok: Tokenizer, + *, + langs: Iterable[str], + limit: int, + batch: int, + seq_len: int, + heldout_frac: float = 0.01, + n_shards: int = 2, + cache_dir: str | Path | None = None, +) -> tuple[torch.Tensor, torch.Tensor, int]: + """Fetch / cache / tokenize / pack. Returns train chunks, held-out, token count.""" + lang_list = [lang.strip() for lang in langs if lang.strip()] + if not lang_list: + raise ValueError("langs must contain at least one of zh, en") + streams: list[list[int]] = [] + for lang in lang_list: + print(f"loading {limit} wiki articles ({lang}) ...") + texts = fetch_wiki_texts(limit, lang=lang, n_shards=n_shards, cache_dir=cache_dir) + streams.append(tokenize_corpus(texts, tok)) + print(f" {lang}: {len(streams[-1]):,} tokens from {len(texts)} articles") + if len(streams) == 1: + ids = streams[0] + else: + ids = streams[0] + for extra in streams[1:]: + ids = interleave_balanced(ids, extra, seq_len) + chunks = chunk_ids(ids, batch, seq_len) + train, held = split_heldout(chunks, heldout_frac) + return train, held, len(ids) + + +def pad_id(tok: Tokenizer) -> int: + inner = getattr(tok, "_tok", None) + if inner is not None: + pid = getattr(inner, "pad_token_id", None) + if pid is not None: + return int(pid) + eid = getattr(inner, "eos_token_id", None) + if eid is not None: + return int(eid) + return 0 + + +def eos_id(tok: Tokenizer) -> int | None: + inner = getattr(tok, "_tok", None) + if inner is not None: + eid = getattr(inner, "eos_token_id", None) + if eid is not None: + return int(eid) + convert = getattr(inner, "convert_tokens_to_ids", None) + if convert is not None: + tid = convert("<|im_end|>") + if isinstance(tid, int) and tid >= 0: + return tid + return None + + +def encode_sft_row( + tok: Tokenizer, + src: str, + tgt: str, + target_lang: str, + max_len: int, + eos: int | None = None, +) -> tuple[list[int], list[int]]: + prompt_ids = tok.encode(instruction_prompt(src, target_lang)) + tgt_ids = tok.encode(tgt) + if eos is not None: + tgt_ids = tgt_ids + [eos] + ids = prompt_ids + tgt_ids + labels = [IGNORE_INDEX] * len(prompt_ids) + list(tgt_ids) + if len(ids) > max_len: + overflow = len(ids) - max_len + cut = min(overflow, max(len(prompt_ids) - 1, 0)) + ids = ids[cut:] + labels = labels[cut:] + if len(ids) > max_len: + ids = ids[:max_len] + labels = labels[:max_len] + return ids, labels + + +def load_sft_rows(path: str | Path) -> list[dict]: + """jsonl ``{src,tgt,target_lang}`` or TSV ``src\\ttgt\\ttarget_lang``.""" + p = Path(path) + rows: list[dict] = [] + text = p.read_text(encoding="utf-8") + if p.suffix == ".jsonl" or p.suffix == ".json": + for line in text.splitlines(): + line = line.strip() + if not line: + continue + obj = json.loads(line) + rows.append( + { + "src": obj["src"], + "tgt": obj["tgt"], + "target_lang": obj.get("target_lang", "en"), + } + ) + return rows + for line in text.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + parts = line.split("\t") + if len(parts) < 2: + raise ValueError(f"SFT TSV needs src, tgt [, target_lang]: {line[:80]!r}") + lang = parts[2] if len(parts) > 2 else "en" + rows.append({"src": parts[0], "tgt": parts[1], "target_lang": lang}) + return rows + + +def collate_sft( + rows: list[dict], + tok: Tokenizer, + max_len: int, +) -> tuple[torch.Tensor, torch.Tensor]: + pad = pad_id(tok) + eos = eos_id(tok) + encoded = [ + encode_sft_row(tok, r["src"], r["tgt"], r["target_lang"], max_len, eos) + for r in rows + ] + width = min(max(len(ids) for ids, _ in encoded), max_len) + width = max(width, 2) + bsz = len(encoded) + input_ids = torch.full((bsz, width), pad, dtype=torch.long) + labels = torch.full((bsz, width), IGNORE_INDEX, dtype=torch.long) + for i, (ids, lab) in enumerate(encoded): + n = min(len(ids), width) + input_ids[i, :n] = torch.tensor(ids[:n], dtype=torch.long) + labels[i, :n] = torch.tensor(lab[:n], dtype=torch.long) + return input_ids, labels + + +def iter_sft_batches( + rows: list[dict], + tok: Tokenizer, + batch: int, + max_len: int, + start: int = 0, +): + n = len(rows) + if n == 0: + raise ValueError("no SFT rows") + i = start + while True: + sl = [rows[j % n] for j in range(i, i + batch)] + yield i, *collate_sft(sl, tok, max_len) + i += batch diff --git a/kda/training/eval_mt.py b/kda/training/eval_mt.py new file mode 100644 index 0000000..2f7f22e --- /dev/null +++ b/kda/training/eval_mt.py @@ -0,0 +1,139 @@ +"""Greedy translation eval on line-aligned src/ref files. + + python -m kda.training.eval_mt \\ + --ckpt ckpts/k3_wiki.pt --src /data/eval/zh2en.src.txt \\ + --ref /data/eval/zh2en.ref.txt --target-lang en +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import torch + +from kda.training.data import eos_id, load_tokenizer +from kda.training.prompts import instruction_prompt +from kda.training.success import _chrf, _detect_lang, translation_success +from kda.training.toy import load_ckpt + + +def _read_lines(path: str) -> list[str]: + return [ln.strip() for ln in Path(path).read_text(encoding="utf-8").splitlines() if ln.strip()] + + +def _instruction(src: str, target_lang: str) -> str: + return instruction_prompt(src, target_lang) + + +@torch.inference_mode() +def decode_one(model, tok, prompt: str, device: str, max_new: int) -> str: + ids = tok.encode(prompt) + if not ids: + return "" + inp = torch.tensor([ids], dtype=torch.long, device=device) + out = model.generate(inp, max_new, eos_token_id=eos_id(tok)) + gen = out[0, inp.size(1) :].tolist() + return tok.decode(gen).strip() + + +def evaluate_pairs( + model, + tok, + srcs: list[str], + refs: list[str], + *, + target_lang: str, + device: str, + max_new: int, + limit: int | None, +) -> dict: + n = len(srcs) + if limit is not None: + n = min(n, limit) + hyps: list[str] = [] + wins = 0 + copies = 0 + lang_ok = 0 + chrf_sum = 0.0 + for i in range(n): + src, ref = srcs[i], refs[i] + hyp = decode_one(model, tok, _instruction(src, target_lang), device, max_new) + hyps.append(hyp) + ok = translation_success(src, hyp, ref, target_lang=target_lang) + wins += int(ok) + copies += int(_chrf(hyp, src) >= 80.0 or hyp == src) + want = "zh" if target_lang.startswith("zh") else "en" + lang_ok += int(_detect_lang(hyp) == want) + chrf_sum += _chrf(hyp, ref) + corpus = {} + try: + from sacrebleu.metrics import BLEU, CHRF + + corpus["chrf"] = float(CHRF(word_order=2).corpus_score(hyps, [refs[:n]]).score) + corpus["bleu"] = float(BLEU().corpus_score(hyps, [refs[:n]]).score) + except Exception: + corpus["chrf"] = chrf_sum / max(n, 1) + corpus["bleu"] = None + return { + "n": n, + "success_rate": wins / max(n, 1), + "copy_rate": copies / max(n, 1), + "lang_ok": lang_ok / max(n, 1), + "chrf": corpus["chrf"], + "bleu": corpus["bleu"], + "hyps": hyps, + } + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--ckpt", required=True) + p.add_argument("--tokenizer", default=None, help="override ckpt tokenizer field") + p.add_argument("--src", default=None, help="one source sentence per line") + p.add_argument("--ref", default=None, help="one reference sentence per line") + p.add_argument("--target-lang", default="en", choices=["en", "zh"]) + p.add_argument("--max-new", type=int, default=64) + p.add_argument("--limit", type=int, default=None) + p.add_argument("--prefix", default=None, help="single-prompt smoke decode") + p.add_argument("--device", default="auto") + args = p.parse_args() + + device = args.device + if device == "auto": + device = "cuda" if torch.cuda.is_available() else "cpu" + + model, _config = load_ckpt(args.ckpt) + model.to(device).eval() + payload = torch.load(args.ckpt, map_location="cpu", weights_only=False) + tok_src = args.tokenizer or payload.get("tokenizer") + if not tok_src: + raise SystemExit("need --tokenizer or a 'tokenizer' field in the checkpoint") + tok = load_tokenizer(tok_src) + + if args.prefix: + print(decode_one(model, tok, args.prefix, device, args.max_new)) + + if args.src and args.ref: + srcs, refs = _read_lines(args.src), _read_lines(args.ref) + if len(srcs) != len(refs): + raise SystemExit(f"src/ref length mismatch: {len(srcs)} vs {len(refs)}") + out = evaluate_pairs( + model, + tok, + srcs, + refs, + target_lang=args.target_lang, + device=device, + max_new=args.max_new, + limit=args.limit, + ) + printable = {k: v for k, v in out.items() if k != "hyps"} + print(json.dumps(printable, ensure_ascii=False, indent=2)) + elif not args.prefix: + raise SystemExit("pass --prefix and/or --src + --ref") + + +if __name__ == "__main__": + main() diff --git a/kda/training/prompts.py b/kda/training/prompts.py new file mode 100644 index 0000000..c7cd59f --- /dev/null +++ b/kda/training/prompts.py @@ -0,0 +1,7 @@ +"""Instruction strings shared by SFT and eval. Do not drift.""" + + +def instruction_prompt(src: str, target_lang: str) -> str: + if target_lang.startswith("zh"): + return f"Translate to Chinese:\n{src}" + return f"Translate to English:\n{src}" diff --git a/kda/training/schedule.py b/kda/training/schedule.py new file mode 100644 index 0000000..f56e3c5 --- /dev/null +++ b/kda/training/schedule.py @@ -0,0 +1,47 @@ +"""LR scale and token-horizon helpers for train_k3 / train_sft.""" +from __future__ import annotations + +import math + + +def lr_scale( + opt_step: int, + warmup: int, + total_opt: int, + min_ratio: float = 0.1, +) -> float: + """Linear warmup (optimizer steps) then cosine down to ``min_ratio``. + + ``opt_step`` is 0-indexed at the optimizer update that is about to run. + """ + if warmup > 0 and opt_step < warmup: + return (opt_step + 1) / warmup + denom = max(total_opt - warmup - 1, 1) + progress = min(max(opt_step - warmup, 0) / denom, 1.0) + cosine = 0.5 * (1.0 + math.cos(math.pi * progress)) + return min_ratio + (1.0 - min_ratio) * cosine + + +def tokens_per_micro(batch: int, seq_len: int) -> int: + return batch * seq_len + + +def total_opt_steps( + *, + max_tokens: int | None, + max_micro: int | None, + batch: int, + seq_len: int, + grad_acc: int, +) -> int: + """Optimizer-step horizon used by cosine. At least 1.""" + acc = max(grad_acc, 1) + candidates: list[int] = [] + if max_tokens is not None and max_tokens > 0: + tpm = max(tokens_per_micro(batch, seq_len), 1) + candidates.append(math.ceil(max_tokens / (tpm * acc))) + if max_micro is not None and max_micro > 0: + candidates.append(math.ceil(max_micro / acc)) + if not candidates: + return 1 + return max(min(candidates), 1) diff --git a/kda/training/success.py b/kda/training/success.py new file mode 100644 index 0000000..4422687 --- /dev/null +++ b/kda/training/success.py @@ -0,0 +1,86 @@ +"""Frozen translation success() — SFT eval and RL reward must call this.""" + +from __future__ import annotations + +import re + +CHRF_MIN = 40.0 +COPY_CHRF_MAX = 80.0 +_CJK = re.compile(r"[\u4e00-\u9fff]") + + +def _detect_lang(text: str) -> str | None: + sample = text.strip() + if not sample: + return None + try: + from langdetect import detect + + tag = detect(sample) + except Exception: + if _CJK.search(sample): + return "zh" + if any(c.isascii() and c.isalpha() for c in sample): + return "en" + return None + if tag.startswith("zh"): + return "zh" + return tag[:2] + + +def _chrf(hyp: str, ref: str) -> float: + """chrF++ in 0–100. Falls back to char unigram F if sacrebleu is missing.""" + if not hyp or not ref: + return 0.0 + try: + from sacrebleu.metrics import CHRF + + return float(CHRF(word_order=2).sentence_score(hyp, [ref]).score) + except Exception: + hyp_c, ref_c = list(hyp), list(ref) + if not hyp_c: + return 0.0 + ref_set = set(ref_c) + overlap = sum(1 for c in hyp_c if c in ref_set) + prec = overlap / len(hyp_c) + rec = overlap / max(len(ref_c), 1) + if prec + rec == 0: + return 0.0 + return 100.0 * 2 * prec * rec / (prec + rec) + + +def translation_success( + src: str, + hyp: str, + ref: str | None = None, + *, + target_lang: str, + chrf_min: float = CHRF_MIN, + copy_chrf_max: float = COPY_CHRF_MAX, +) -> bool: + """Binary task success for zh↔en instruction translation. + + 1. non-empty hyp, no instruction leak prefix + 2. langid(hyp) matches target_lang (zh / en) + 3. hyp is not a copy of src + 4. if ref is given, chrF(hyp, ref) >= chrf_min + """ + hyp = hyp.strip() + src = src.strip() + if not hyp: + return False + leak = ("翻译如下", "translate to", "translation:", "译文:") + head = hyp[:40].lower() + if any(p in head or p in hyp[:20] for p in leak): + return False + want = "zh" if target_lang.startswith("zh") else "en" + got = _detect_lang(hyp) + if got != want: + return False + if src and _chrf(hyp, src) >= copy_chrf_max: + return False + if hyp == src: + return False + if ref is not None and _chrf(hyp, ref.strip()) < chrf_min: + return False + return True diff --git a/kda/training/toy.py b/kda/training/toy.py new file mode 100644 index 0000000..f1b785f --- /dev/null +++ b/kda/training/toy.py @@ -0,0 +1,110 @@ +"""L7: toy training loop — overfit 起步. + +target: + 端到端验证模型 + 数据流 + optimizer + ckpt + generate. + +toy data: + 建一份 256-token vocab 的小数据集: e.g. 1000 个长度 32 随机 token 序列 + 起步只取 batch=4, 看能否在 ~320 steps 内把 loss 压到 < 0.1 (overfit 单 batch). + +step: + optimizer = AdamW(lr=1e-3, wd=0.01) + loss.backward(); optimizer.step(); optimizer.zero_grad() + every N steps: 打印 loss + end: 保存 ckpt to ckpts/kda_toy.pt + +ckpt: + save: + torch.save({"model_state": model.state_dict(), "config": asdict(config)}, path) + load: + torch.load -> model.load_state_dict +""" +from __future__ import annotations + +import os +from dataclasses import asdict, fields + +import torch + +from ..models.causal_lm import CausalLM +from ..models.config import KDAConfig +from ..models.k3_config import K3Config + + +def make_toy_data(batch: int = 4, seq_len: int = 32, vocab: int = 256, seed: int = 42): + """单 batch overfit 数据: 同一组序列循环.""" + torch.manual_seed(seed) + seq = torch.randint(0, vocab, (batch, seq_len), dtype=torch.long) + return seq # 用作 input_ids 和 labels (shift one inside forward) + + +def train_one_batch(model, optimizer, input_ids, labels): + optimizer.zero_grad(set_to_none=True) + loss = model(input_ids, labels=labels) + loss.backward() + optimizer.step() + return loss.detach() + + +def save_ckpt(model, config, path: str): + os.makedirs(os.path.dirname(path) or ".", exist_ok=True) + torch.save({"model_state": model.state_dict(), "config": asdict(config)}, path) + + +#: The feed-forward submodule was named after its contents (``mlp`` in the +#: dense config, ``moe`` in K3) before both were unified under ``ffn``. +#: Checkpoints saved before that rename still carry the old prefixes. +_LEGACY_PREFIXES = { + ".mlp.": ".ffn.", + ".mlp_norm.": ".ffn_norm.", + ".moe.": ".ffn.", + ".moe_norm.": ".ffn_norm.", +} + + +def _rename_legacy_keys(state: dict) -> dict: + def fix(key: str) -> str: + for old, new in _LEGACY_PREFIXES.items(): + if old in key: + return key.replace(old, new) + return key + + return {fix(k): v for k, v in state.items()} + + +def _config_from(payload_config: dict) -> K3Config | KDAConfig: + """Pick the config class the checkpoint was written with. + + ``moe_latent_size`` is a K3-only field, so its presence identifies the + hybrid K3 architecture; anything else is the dense KDA config. + """ + cls = K3Config if "moe_latent_size" in payload_config else KDAConfig + known = {item.name for item in fields(cls)} + return cls(**{k: v for k, v in payload_config.items() if k in known}) + + +def load_ckpt(path: str, model: CausalLM | None = None) -> tuple[CausalLM, K3Config | KDAConfig]: + payload = torch.load(path, map_location="cpu", weights_only=False) + config = _config_from(payload["config"]) + if model is None: + model = CausalLM(config) + model.load_state_dict(_rename_legacy_keys(payload["model_state"])) + return model, config + + +def main(): + """主入口: overfit 起步. 320 steps 期望 loss < 0.1.""" + device = "cuda" if torch.cuda.is_available() else "cpu" + config = KDAConfig() + model = CausalLM(config).to(device) + tokens = make_toy_data(seq_len=32, vocab=config.vocab_size).to(device) + optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01) + for step in range(320): + loss = train_one_batch(model, optimizer, tokens, tokens) + if step % 64 == 0 or step == 319: + print(f"step {step:3d} loss {loss.item():.4f}") + save_ckpt(model, config, "ckpts/kda_toy.pt") + + +if __name__ == "__main__": + main() diff --git a/kda/training/train_tokenizer.py b/kda/training/train_tokenizer.py new file mode 100644 index 0000000..33121ca --- /dev/null +++ b/kda/training/train_tokenizer.py @@ -0,0 +1,51 @@ +"""Train a SentencePiece tokenizer on a Chinese Wikipedia subset. + +用法: + uv run python kda/training/train_tokenizer.py \ + --out data/spm_4k --vocab-size 4096 --limit 20000 + +产出: + data/spm_4k.model / data/spm_4k.vocab (BPE/unigram, 中文小语料) +""" +from __future__ import annotations + +import argparse + +import sentencepiece as spm + +from .data import fetch_wiki_texts + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--out", default="data/spm_4k", help="输出前缀 (model/vocab 文件)") + p.add_argument("--vocab-size", type=int, default=8192) + p.add_argument("--limit", type=int, default=20000, help="用于训练的 wiki 文章数") + p.add_argument("--model-type", default="unigram", choices=["unigram", "bpe"]) + p.add_argument("--character-coverage", type=float, default=0.9995) + args = p.parse_args() + + texts = fetch_wiki_texts(args.limit) + corpus = "".join(texts) + tmp = args.out + ".corpus.txt" + with open(tmp, "w", encoding="utf-8") as f: + f.write(corpus) + print(f"corpus: {len(corpus):,} chars from {len(texts)} articles") + + spm.SentencePieceTrainer.train( + input=tmp, + model_prefix=args.out, + vocab_size=args.vocab_size, + model_type=args.model_type, + character_coverage=args.character_coverage, + unk_id=0, + pad_id=1, + bos_id=-1, + eos_id=-1, + num_threads=4, + ) + print(f"tokenizer saved: {args.out}.model / {args.out}.vocab") + + +if __name__ == "__main__": + main() diff --git a/notes/ledger.yaml b/notes/ledger.yaml new file mode 100644 index 0000000..a2e213b --- /dev/null +++ b/notes/ledger.yaml @@ -0,0 +1,187 @@ +schema: superpaper.ledger/v1 +retired_ids: [] +paper: + id: "kda-project" + title: "KDA 训练→推理 手写实现 — 完整笔记" + authors: ["dela"] + notes_language: zh + source: + kind: markdown + +coverage: + mode: full + sections_in: + - "KDA 递归核心" + - "Gate 激活" + - "分块并行计算" + - "GVA 分组值注意力" + - "KDAAttention 层" + - "Gated MLA 矩阵吸收版" + - "SiTU-GLU 与 Stable LatentMoE" + - "K3 混合架构" + - "Attention Residual 深度残差" + - "反向传播推导" + sections_skipped: + - "Triton kernel 细节" + - "Docker 部署" + - "AttnRes 论文的 kernel 级调度与 pipeline 重叠" + +questions: + - id: Q1 + text: "KDA 的状态更新如何避免 softmax、实现线性复杂度?" + - id: Q2 + text: "safe gate 与 standard gate 的区别是什么?" + - id: Q3 + text: "分块并行如何在保持递归等价的同时利用 GPU 并行?" + - id: Q4 + text: "GVA 的 repeat_interleave + sum 反向是怎么回事?" + - id: Q5 + text: "MLA 矩阵吸收如何避免解压 K/V?" + - id: Q6 + text: "SiTU-GLU 为什么比 SwiGLU 更稳定?" + - id: Q7 + text: "AttnRes 如何把残差流从等权累加换成按内容选择?" + - id: Q8 + text: "Block AttnRes 的两阶段算法为什么和 naive 逐层实现数值等价?" + - id: Q9 + text: "深度残差接入 CausalLM 时怎样避免参数被重复注册?" + +claims: + - id: C1 + text: "KDA 用 delta rule 更新 KV 状态矩阵,不需要 softmax,复杂度 O(T·K·V)" + kind: methodological + status: core + - id: C2 + text: "safe gate = lower_bound · σ(rate · input),保证 gate 值在 [lower_bound, 0] 范围内" + kind: methodological + status: core + - id: C3 + text: "分块计算:chunk 内用下三角解,chunk 间用状态递推,数值等价于 naive recurrent" + kind: methodological + status: core + - id: C4 + text: "MLA 矩阵吸收:q 吸收 W_UK 后直接与 latent c 内积,永不解压 K/V" + kind: methodological + status: core + - id: C5 + text: "LatentMoE 通过 latent 接口把 routed 专家限制在半宽空间 ℓ=d/2" + kind: methodological + status: core + - id: C6 + text: "AttnRes 用逐 token 的深度维 softmax 代替等权残差累加:打分在 RMS 归一化后做,加权和在原始张量上做" + kind: methodological + status: core + - id: C7 + text: "Block AttnRes 块内退化为普通求和、只让块输出进入源列表,源数从 O(N) 降到 O(N/S)" + kind: methodological + status: core + - id: C8 + text: "两阶段算法 = inter 块间批量 einsum + intra online-softmax 增量合并,与 naive 逐层实现数值等价 (atol 1e-5)" + kind: methodological + status: core + - id: C9 + text: "BorrowedSubLayer 用普通 tuple 持有 norm/fn,不注册为子模块,保证参数与 state_dict 键不重复" + kind: methodological + status: supporting + +symbols: + - {name: B, latex: "B", meaning: "batch size", kind: "shape parameter"} + - {name: T, latex: "T", meaning: "序列长度", kind: "shape parameter"} + - {name: H, latex: "H", meaning: "query/key 头数", kind: "shape parameter"} + - {name: HV, latex: "H_V", meaning: "value 头数 (GVA)", kind: "shape parameter"} + - {name: G, latex: "G", meaning: "GVA 组数 = HV/H", kind: "shape parameter"} + - {name: K, latex: "K", meaning: "key/query 头维度", kind: "shape parameter"} + - {name: V, latex: "V", meaning: "value 头维度 (= K)", kind: "shape parameter"} + - {name: D, latex: "D", meaning: "hidden_size", kind: "shape parameter"} + - {name: C, latex: "C", meaning: "chunk_size", kind: "shape parameter"} + - {name: r, latex: "r", meaning: "KV latent rank (kv_lora_rank)", kind: "shape parameter"} + - {name: ell, latex: "\\ell", meaning: "MoE latent 宽度 = d/2", kind: "shape parameter"} + - {name: S, latex: "S", meaning: "KV 状态矩阵", domain: "[B, HV, K, V]", kind: value} + - {name: q, latex: "q", meaning: "query", domain: "[B, T, H, K]", kind: value} + - {name: k, latex: "k", meaning: "key", domain: "[B, T, H, K]", kind: value} + - {name: v, latex: "v", meaning: "value", domain: "[B, T, HV, V]", kind: value} + - {name: g, latex: "g", meaning: "gate (log-space decay)", domain: "[B, T, HV, K]", kind: value} + - {name: beta, latex: "\\beta", meaning: "学习率/写入强度", domain: "[B, T, HV]", kind: value} + - {name: A_log, latex: "A_{\\log}", meaning: "head-wise 衰减参数 (log-space)", domain: "[HV]", kind: value} + - {name: dt_bias, latex: "\\Delta_b", meaning: "per-dim gate bias", domain: "[HV, K]", kind: value} + - {name: c, latex: "c", meaning: "KV latent 向量", domain: "[B, T, r]", kind: value} + - {name: W_UK, latex: "W_{UK}", meaning: "Key 解压矩阵 (MLA)", domain: "[H, d_q, r]", kind: value} + - {name: W_UV, latex: "W_{UV}", meaning: "Value 解压矩阵 (MLA)", domain: "[H, d_v, r]", kind: value} + - {name: N, latex: "N", meaning: "AttnRes 原子层数 = 2L", kind: "shape parameter"} + - {name: S, latex: "S", meaning: "AttnRes 块大小(原子层)", kind: "shape parameter"} + - {name: v_i, latex: "v_i", meaning: "AttnRes 第 i 个源(v_0 = embedding 输出)", domain: "[B, T, D]", kind: value} + - {name: w_l, latex: "w_l", meaning: "第 l 层 depth query(零初始化)", domain: "[D]", kind: value} + - {name: alpha, latex: "\\alpha_{l,i}", meaning: "深度维 softmax 权重", domain: "[n, B, T]", kind: value} + - {name: h_l, latex: "h_l", meaning: "深度注意力聚合出的层输入", domain: "[B, T, D]", kind: value} + - {name: b_j, latex: "b_j", meaning: "Block AttnRes 第 j 块的输出", domain: "[B, T, D]", kind: value} + - {name: p, latex: "p", meaning: "块内 running partial", domain: "[B, T, D]", kind: value} + +terms: + - {canonical: "KDA", aliases: ["Key-Decayed Attention", "键衰减注意力"]} + - {canonical: "GVA", aliases: ["Grouped Value Attention", "分组值注意力"]} + - {canonical: "MLA", aliases: ["Multi-head Latent Attention", "多头隐变量注意力"]} + - {canonical: "MoE", aliases: ["Mixture of Experts", "混合专家"]} + - {canonical: "SiTU-GLU", aliases: ["Sigmoid Tanh Unit GLU"]} + - {canonical: "delta rule", aliases: ["δ 规则"]} + - {canonical: "safe gate", aliases: ["安全门控"]} + - {canonical: "matrix absorption", aliases: ["矩阵吸收"]} + - {canonical: "AttnRes", aliases: ["Attention Residual", "注意力残差", "深度残差"]} + - {canonical: "depth residual", aliases: ["DepthResidual", "深度维残差"]} + - {canonical: "online softmax", aliases: ["在线 softmax", "增量 softmax"]} + - {canonical: "atomic layer", aliases: ["原子层", "atomic sublayer"]} + +derivations: + - id: DER1 + claim: C1 + title: "KDA 递归状态更新推导" + expand: true + figure: null + steps: + - {id: "1", from: "S_{t-1}", to: "S_{\\mathrm{dec}} = \\exp(g_t) \\odot S_{t-1}", rule: scale} + - {id: "2", from: "S_{\\mathrm{dec}}", to: "r_t = v_t - k_t \\cdot S_{\\mathrm{dec}}", rule: definition} + - {id: "3", from: "r_t", to: "S_t = S_{\\mathrm{dec}} + (\\beta_t k_t) \\otimes r_t", rule: definition} + - {id: "4", from: "S_t", to: "o_t = (q_t \\cdot \\text{scale}) \\cdot S_t", rule: definition} + - id: DER2 + claim: C4 + title: "MLA 矩阵吸收推导" + expand: true + figure: null + steps: + - {id: "1", from: "q \\in [B,T,H,d_q]", to: "q_{\\mathrm{abs}} = q \\cdot W_{UK} \\in [B,T,H,r]", rule: substitute} + - {id: "2", from: "q_{\\mathrm{abs}}, c", to: "\\text{score} = q_{\\mathrm{abs}} \\cdot c^T \\in [B,H,T,T]", rule: definition} + - {id: "3", from: "\\text{attn}, c", to: "\\tilde{o}_{\\mathrm{lat}} = \\text{attn} \\cdot c \\in [B,H,T,r]", rule: definition} + - {id: "4", from: "\\tilde{o}_{\\mathrm{lat}}", to: "\\tilde{o} = \\tilde{o}_{\\mathrm{lat}} \\cdot W_{UV}^T \\in [B,H,T,d_v]", rule: substitute} + - id: DER3 + claim: C8 + title: "AttnRes 两阶段 online softmax 合并推导" + expand: true + figure: null + steps: + - {id: "1", from: "s_{l,i} = \\tilde{w}_l^T \\mathrm{RMS}(v_i)", to: "(m, n, d) = (\\max_i s_i, \\sum_i e^{s_i - m} v_i, \\sum_i e^{s_i - m})", rule: definition} + - {id: "2", from: "inter sources b_0..b_{j-1} 固定", to: "一次批量 einsum 'q d, n b t d -> q n b t' 得块内全部 query 的 (m,n,d)", rule: substitute} + - {id: "3", from: "单源 partial p", to: "(m, n, d) = (s_p, p, 1),因为 e^{s_p - m} = 1", rule: definition} + - {id: "4", from: "(m_a,n_a,d_a), (m_b,n_b,d_b)", to: "m = \\max(m_a,m_b);\\ n = e^{m_a-m} n_a + e^{m_b-m} n_b;\\ d = e^{m_a-m} d_a + e^{m_b-m} d_b", rule: scale} + - {id: "5", from: "(m, n, d)", to: "h_l = n / d,与 forward_naive 逐位一致", rule: definition} + +figures: + - id: F1 + claim: C1 + title: "KDA 递归状态更新张量图" + grammar: tensor-face + toolkit: supertensor + signals: [shape, contraction, broadcast] + status: planned + - id: F2 + claim: C4 + title: "MLA 矩阵吸收计算流" + grammar: tensor-face + toolkit: supertensor + signals: [shape, contraction, transpose] + status: planned + - id: F3 + claim: C7 + title: "Full vs Block AttnRes 的源列表增长" + grammar: tensor-face + toolkit: supertensor + signals: [shape, contraction] + status: planned diff --git a/notes/notes-macros.tex b/notes/notes-macros.tex new file mode 100644 index 0000000..36a23c3 --- /dev/null +++ b/notes/notes-macros.tex @@ -0,0 +1,94 @@ +% KDA 笔记 preamble — 基于 superpaper/assets/notes-macros.tex +\usepackage[fontset=fandol]{ctex} +\usepackage{amsmath,amssymb} +\usepackage{graphicx} +\usepackage[margin=2.2cm]{geometry} +\usepackage[most]{tcolorbox} +\usepackage{etoolbox} +\usepackage{listings} +\usepackage{booktabs} +\usepackage{subcaption} +\usepackage{float} +\usepackage{tikz} +\usepackage{hyperref} +\usepackage{xcolor} +\usepackage{multicol} +\usepackage{tabularx} +\usepackage{array} +\usepackage{enumitem} + +% ---------- 颜色 ---------- +\definecolor{codebg}{HTML}{F7F7F7} +\definecolor{codeframe}{HTML}{CCCCCC} +\definecolor{shapecolor}{HTML}{2E86C1} +\definecolor{einsumcolor}{HTML}{884EA0} +\definecolor{notegreen}{HTML}{27AE60} +\definecolor{warnorange}{HTML}{E67E22} + +% ---------- 代码样式 ---------- +\lstdefinestyle{pycode}{ + language=Python, + backgroundcolor=\color{codebg}, + frame=single, + rulecolor=\color{codeframe}, + basicstyle=\ttfamily\small, + keywordstyle=\color{blue!70!black}\bfseries, + commentstyle=\color{gray}, + stringstyle=\color{red!60!black}, + showstringspaces=false, + breaklines=true, + tabsize=4, + columns=flexible, + xleftmargin=4pt, + xrightmargin=4pt, + aboveskip=6pt, + belowskip=6pt, +} +\lstset{style=pycode} + +% ---------- 形状标注命令 ---------- +\newcommand{\shape}[1]{{\color{shapecolor}\ensuremath{[#1]}}} +\newcommand{\einsum}[1]{{\color{einsumcolor}\texttt{einsum(#1)}}} +\newcommand{\note}[1]{{\color{notegreen}\textit{#1}}} + +% ---------- 盒子 ---------- +\newtcolorbox{knowledgebox}[1]{ + enhanced, colback=blue!5!white, colframe=blue!75!black, colbacktitle=blue!75!black, + coltitle=white, fonttitle=\bfseries, title=#1, + attach boxed title to top left={yshift=-2mm, xshift=2mm}, + boxrule=1pt, sharp corners +} +\newtcolorbox{importantbox}[1]{ + enhanced, colback=yellow!10!white, colframe=yellow!80!black, colbacktitle=yellow!80!black, + coltitle=black, fonttitle=\bfseries, title=#1, sharp corners +} +\newtcolorbox{warningbox}[1]{ + enhanced, colback=red!5!white, colframe=red!75!black, colbacktitle=red!75!black, + coltitle=white, fonttitle=\bfseries, title=#1, sharp corners +} +\newtcolorbox{tensorbox}[1]{ + enhanced, colback=blue!3!white, colframe=blue!40!black, colbacktitle=blue!50!black, + coltitle=white, fonttitle=\bfseries, title=#1, sharp corners, + boxrule=0.8pt +} + +% 代码-公式并行盒子 +\newtcolorbox{codemathtop}[1]{ + enhanced, colback=codebg, colframe=codeframe, + fonttitle=\bfseries\ttfamily\small, title=#1, + sharp corners, boxrule=0.6pt, + left=4pt, right=4pt, top=2pt, bottom=2pt, +} +\newtcolorbox{codemathbot}{ + enhanced, colback=white, colframe=blue!30!black, + sharp corners, boxrule=0.6pt, + left=4pt, right=4pt, top=4pt, bottom=4pt, +} + +% ---------- 文档元信息 ---------- +\newcommand{\notetitle}{KDA 训练→推理 完整笔记} +\newcommand{\notesubtitle}{递归 · 分块 · Gate · MLA · MoE · AttnRes · K3 架构} +\newcommand{\notedate}{\today} + +\newcommand{\splabel}[1]{\hypertarget{sp:#1}{}\label{sp:#1}} +\newcommand{\spref}[1]{\hyperlink{sp:#1}{\texttt{#1}}} diff --git a/notes/notes.pdf b/notes/notes.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5f65fda02f947e2e84c8a2d8dbb397a8e1271437 GIT binary patch literal 572085 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
(2%=%GN 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

jZjc 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�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%0DC3iJBh+Yz$@Hg8qe*y$u@m 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�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 literal 0 HcmV?d00001 diff --git a/notes/notes.tex b/notes/notes.tex new file mode 100644 index 0000000..282b2b0 --- /dev/null +++ b/notes/notes.tex @@ -0,0 +1,39 @@ +\documentclass[a4paper,11pt]{article} +\input{notes-macros} + +\begin{document} + +% ---------- 封面 ---------- +\begin{titlepage} +\centering +\vspace{2cm} +{\huge\bfseries \notetitle\par} +\vspace{0.6cm} +{\Large \notesubtitle\par} +\vspace{0.8cm} +{\large \notedate\par} +\vspace{1.5cm} +\begin{tcolorbox}[width=0.88\textwidth, colback=black!2!white, colframe=black!60, sharp corners] +\textbf{项目}:\texttt{projects/kda/} — KDA 手写实现(naive recurrent → chunked → Triton)\\ +\textbf{架构}:KDA + Gated MLA + Stable LatentMoE + AttnRes 深度残差 (K3-like)\\ +\textbf{参考}:KDA arXiv:2510.26692; AttnRes arXiv:2603.15031; Kimi K3 architecture notes\\ +\textbf{代码}:\texttt{kda/ops/}, \texttt{kda/layers/}, \texttt{kda/models/} +\end{tcolorbox} +\end{titlepage} + +\tableofcontents +\newpage + +\input{sections/sec-01} % KDA 递归核心 +\input{sections/sec-02} % Gate 激活 +\input{sections/sec-03} % 分块并行计算 +\input{sections/sec-04} % GVA 分组值注意力 +\input{sections/sec-05} % KDAAttention 层 +\input{sections/sec-06} % Gated MLA 矩阵吸收版 +\input{sections/sec-07} % SiTU-GLU 与 Stable LatentMoE +\input{sections/sec-08} % K3 混合架构 +\input{sections/sec-09} % Attention Residual 深度残差 +\input{sections/sec-10} % 反向传播推导 +\input{sections/sec-11} % 符号表 + +\end{document} diff --git a/notes/sections/sec-01.tex b/notes/sections/sec-01.tex new file mode 100644 index 0000000..a8f16e0 --- /dev/null +++ b/notes/sections/sec-01.tex @@ -0,0 +1,127 @@ +% teach: +% gap: 读者知道 softmax attention 但不知道线性注意力怎么维护状态矩阵 +% takeaway: KDA 用 delta rule 逐步更新 [K,V] 状态矩阵, 写入=擦旧写新, 每步 O(KV) +% jump: 为什么 r_t = v - k·S 而不是直接用 v?delta rule 的"先擦再写" +% omit: KDA 论文的 related work、实验细节 + +\section{KDA 递归核心} +\splabel{C1} + +\subsection{动机:从 softmax 到状态矩阵} + +标准 attention 每个 token 都要回看所有历史,复杂度 $O(T^2)$。 +线性注意力换掉 softmax,把 $\sum_j v_j k_j^T$ 压成一个 $K \times V$ 的状态矩阵 $S$, +每步只做 $o_t = q_t \cdot S$,复杂度降到 $O(T \cdot K \cdot V)$。 + +但裸线性注意力的问题是:$S$ 只能加,不能改。写进去的信息永远在那里。 +KDA 的核心想法是给 $S$ 加两个操作:\textbf{衰减}(逐渐忘记旧信息)和 +\textbf{delta rule}(先擦旧的,再写新的)。 + +\begin{importantbox}{如果你只记一件事} +KDA 的状态更新 = 衰减旧状态 + $\beta_t k_t$ 写入 $(v_t - k_t \cdot S_{\mathrm{dec}})$。 +减去 $k_t \cdot S_{\mathrm{dec}}$ 就是"先把 $k_t$ 方向的旧预测擦掉"。 +\end{importantbox} + +\subsection{逐步公式} + +\noindent\textbf{输入张量:} + +\begin{center} +\begin{tabular}{lll} +\toprule +符号 & 形状 & 含义 \\ +\midrule +$q_t$ & \shape{B, HV, K} & query(已经 repeat\_interleave 到 HV) \\ +$k_t$ & \shape{B, HV, K} & key(同上) \\ +$v_t$ & \shape{B, HV, V} & value \\ +$g_t$ & \shape{B, HV, K} & gate(log-space 衰减率,逐维) \\ +$\beta_t$ & \shape{B, HV} & 写入强度标量 \\ +$S_{t-1}$ & \shape{B, HV, K, V} & 上一步的 KV 状态 \\ +\bottomrule +\end{tabular} +\end{center} + +\noindent\textbf{四步更新:} + +\begin{enumerate}[leftmargin=2em] +\item \textbf{衰减旧状态}(逐元素,$g_t$ 是 log-space 所以取 exp): +\[ + S_{\mathrm{dec}} = \exp(g_t) \odot S_{t-1} + \qquad \shape{B, HV, K, V} +\] + +\item \textbf{计算残差}(先用 $k_t$ 查旧状态,得到"旧预测",再减掉): +\[ + p_t = \sum_k k_{t,k} \cdot S_{\mathrm{dec},k,\cdot} + = \texttt{einsum('bhk, bhkv -> bhv')} + \qquad \shape{B, HV, V} +\] +\[ + r_t = v_t - p_t \qquad \shape{B, HV, V} +\] + +\item \textbf{写入状态}(外积 rank-1 更新): +\[ + a_t = \beta_t \cdot k_t \qquad \shape{B, HV, K} +\] +\[ + S_t = S_{\mathrm{dec}} + a_t \otimes r_t + = S_{\mathrm{dec}} + \texttt{einsum('bhk, bhv -> bhkv')} + \qquad \shape{B, HV, K, V} +\] + +\item \textbf{读出}: +\[ + o_t = \frac{1}{\sqrt{K}} \cdot q_t \cdot S_t + = \texttt{einsum('bhk, bhkv -> bhv')} + \qquad \shape{B, HV, V} +\] +\end{enumerate} + +\subsection{代码对照} + +\begin{codemathtop}{ops/reference/recurrent.py — naive\_kda\_fwd (核心循环)} +\begin{lstlisting} +for t in range(T): + q_t = qe[:, t] # [B, HV, K] + k_t = ke[:, t] # [B, HV, K] + v_t = v[:, t] # [B, HV, V] + g_t = g[:, t] # [B, HV, K] + b_t = beta[:, t] # [B, HV] + + # Step 1: decay + S_dec = S * g_t.exp().unsqueeze(-1) # [B,HV,K,V] + + # Step 2: residual (delta rule) + p_t = einsum('bhk, bhkv -> bhv', k_t, S_dec) + r_t = v_t - p_t # [B,HV,V] + + # Step 3: write (rank-1 update) + a_t = b_t.unsqueeze(-1) * k_t # [B,HV,K] + S = S_dec + einsum('bhk, bhv -> bhkv', a_t, r_t) + + # Step 4: read + o[:, t] = einsum('bhk, bhkv -> bhv', q_t, S) +\end{lstlisting} +\end{codemathtop} + +\begin{warningbox}{为什么 exp(g\_t) 要 unsqueeze(-1)?} +$g_t$ 的形状是 \shape{B, HV, K},而 $S$ 是 \shape{B, HV, K, V}。 +衰减是在 $K$ 维上逐元素(同一 $k$ 索引的所有 $v$ 维度共享同一个衰减率), +所以 \texttt{exp(g\_t).unsqueeze(-1)} 把 K 维 broadcast 到 $K \times V$。 +\end{warningbox} + +\subsection{Delta rule 的直觉} + +\begin{knowledgebox}{为什么减去 $k_t \cdot S_{\mathrm{dec}}$?} +把 $S$ 想象成一个 $K \to V$ 的线性映射。用 $k_t$ 去查它,得到的 $p_t = k_t^T S$ +就是``旧状态对 $k_t$ 方向的预测''。如果 $p_t$ 已经很接近 $v_t$,说明这个方向的信息 +已经写好了,不需要再写。$r_t = v_t - p_t$ 就是``需要修正的量''。 + +这就是 Widrow-Hoff delta rule:不是盲目地加,而是只修正误差。 +\end{knowledgebox} + +\subsection{本章小结} + +KDA 的状态更新 = 衰减旧状态 + $\beta_t k_t$ 写入残差 $(v_t - k_t \cdot S_{\mathrm{dec}})$。 +每步复杂度 $O(K \cdot V)$(两次矩阵-向量乘 + 一次外积),不需要 softmax。 diff --git a/notes/sections/sec-02.tex b/notes/sections/sec-02.tex new file mode 100644 index 0000000..dd1356c --- /dev/null +++ b/notes/sections/sec-02.tex @@ -0,0 +1,111 @@ +% teach: +% gap: 读者知道 g_t 是 gate 但不知道它怎么从 raw projection 变成一个负的 log-space 衰减 +% takeaway: safe gate 用 sigmoid 把值夹在 [lower_bound, 0], standard gate 用 -softplus 保证负 +% jump: 论文没解释为什么需要 A_log 和 dt_bias 两层 +% omit: Triton gate kernel 的 fused 实现细节 + +\section{Gate 激活} +\splabel{C2} + +\subsection{Gate 的角色} + +回顾 §1:$S_{\mathrm{dec}} = \exp(g_t) \odot S_{t-1}$。$g_t$ 必须 $\leq 0$ +才是衰减($\exp(g_t) \leq 1$),否则状态会指数增长爆炸。 + +\texttt{g\_raw} 是从 \texttt{g\_proj(x)} 出来的 raw 值,没有约束。 +Gate 激活函数的任务是:把 raw 值映射到一个保证 $\leq 0$ 的范围。 + +\subsection{两种 Gate} + +\begin{center} +\begin{tabular}{p{3cm}p{5.5cm}p{5cm}} +\toprule +& \textbf{Standard gate} & \textbf{Safe gate} \\ +\midrule +公式 & +$g = -\mathrm{rate} \cdot \mathrm{softplus}(\mathrm{input})$ & +$g = L \cdot \sigma(\mathrm{rate} \cdot \mathrm{input})$ \\ +值域 & +$(-\infty, 0]$ & +$[L, 0]$($L$ 是 lower\_bound,如 $-5$) \\ +衰减范围 & +$\exp(g) \in (0, 1]$ & +$\exp(g) \in [\exp(L), 1]$ \\ +稳定性 & +衰减可以任意快 & +衰减有下限,不会瞬间清零 \\ +\bottomrule +\end{tabular} +\end{center} + +\noindent 其中: +\begin{itemize}[nosep] +\item $\mathrm{input} = g_{\mathrm{raw}} + \Delta_b$ \quad($\Delta_b$ + 是 \texttt{dt\_bias} \shape{HV, K}) +\item $\mathrm{rate} = \exp(A_{\log})$ \quad($A_{\log}$ 是 + \texttt{A\_log} \shape{HV},head-wise 可学习) +\end{itemize} + +\begin{importantbox}{如果你只记一件事} +Safe gate = $L \cdot \sigma(\mathrm{rate} \cdot \mathrm{input})$, +$L=-5$ 时 $\exp(g) \geq \exp(-5) \approx 0.0067$, +状态永远不会被``一次性清零''。 +\end{importantbox} + +\subsection{代码对照} + +\begin{codemathtop}{ops/reference/gate.py — kda\_gate\_reference} +\begin{lstlisting} +def kda_gate_reference(g, A_log, dt_bias=None, *, + safe_gate=False, lower_bound=None): + HV, K = g.shape[-2:] + gate_input = g if dt_bias is None else g + dt_bias.view(HV, K) + rate = A_log.view(HV, 1).exp() + + if safe_gate: + # safe: g in [lower_bound, 0] + return lower_bound * torch.sigmoid(rate * gate_input) + # standard: g in (-inf, 0] + return -rate * F.softplus(gate_input) +\end{lstlisting} +\end{codemathtop} + +\subsection{初始化与默认值} + +\begin{center} +\begin{tabular}{llp{7cm}} +\toprule +参数 & 初始值 & 效果 \\ +\midrule +\texttt{A\_log} & $\mathbf{0}$ \shape{HV} & $\mathrm{rate} = \exp(0) = 1$,不缩放 \\ +\texttt{dt\_bias} & $-4.0$ \shape{HV, K} & 初始时 $\mathrm{input} \approx g_{\mathrm{raw}} - 4$, + 配合 safe gate ($L=-5$) 得到 $g \approx -5 \cdot \sigma(-4) \approx -0.09$, + 即 $\exp(g) \approx 0.91$(约 91\% 状态保留) \\ +\texttt{lower\_bound} & $-5.0$ & safe gate 的下限 \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{Gate 在 API 中的位置} + +Gate 激活在 \texttt{ops/api.py} 的 \texttt{chunk\_kda} 中调用, +在进入 chunkwise 或 recurrent 核心之前完成。 +当 \texttt{use\_gate\_in\_kernel=True} 时,\texttt{g\_raw} 进入 API, +API 内部完成 gate 激活;否则调用方自己完成。 + +\begin{lstlisting} +# ops/api.py (simplified) +if use_gate_in_kernel: + gate_input = g + dt_bias.view(g.shape[-2:]) + rate = A_log.exp().view(1, 1, -1, 1) + if safe_gate: + g = lower_bound * torch.sigmoid(rate * gate_input) + else: + g = -rate * F.softplus(gate_input) +\end{lstlisting} + +\subsection{本章小结} + +Gate 把 raw projection 映射到 $\leq 0$ 的 log-space 衰减率。 +Safe gate 用 sigmoid 限制在 $[L, 0]$,防止瞬间清零; +standard gate 用 softplus 不限制下限。默认配置下初始状态保留约 91\%。 diff --git a/notes/sections/sec-03.tex b/notes/sections/sec-03.tex new file mode 100644 index 0000000..b2b302c --- /dev/null +++ b/notes/sections/sec-03.tex @@ -0,0 +1,148 @@ +% teach: +% gap: 读者知道递归形式但不知道怎么在 GPU 上并行, 以为只能一步步算 +% takeaway: chunk 内用下三角线性系统并行求解, chunk 间递推状态, 数值等价于 naive recurrent +% jump: 论文直接写了 triangular solve 但没解释为什么要 solve 而不是直接矩阵乘 +% omit: Triton 实现细节 + +\section{分块并行计算(Chunkwise)} +\splabel{C3} + +\subsection{为什么需要分块?} + +Naive recurrent 一步一步算,$T$ 步串行,GPU 利用率低。 +分块的想法是把序列切成 $T/C$ 个长度为 $C$ 的 chunk: +\begin{itemize}[nosep] +\item \textbf{chunk 内}:$C$ 个 token 之间的依赖可以用矩阵运算并行处理 +\item \textbf{chunk 间}:状态 $S$ 从上一个 chunk 传到下一个,仍然是递推 +\end{itemize} + +\begin{importantbox}{如果你只记一件事} +Chunkwise = chunk 内并行 + chunk 间递推。数值结果与 naive recurrent 逐位一致。 +\end{importantbox} + +\subsection{chunk 内的 cumsum 与下三角解} + +在每个 chunk 内,先对 $g$ 做 cumsum(前缀和),这样衰减就变成了相对距离的函数: +\[ + g_{\mathrm{cum},i} = \sum_{j=0}^{i} g_j, \qquad + \text{token } i \text{ 对 token } j \text{ 的衰减} = \exp(g_{\mathrm{cum},i} - g_{\mathrm{cum},j}) +\] + +定义 \textbf{decayed dot} 矩阵(chunk 内 $C \times C$): +\[ + A_{ij} = \langle x_i, \exp(g_{\mathrm{cum},i} - g_{\mathrm{cum},j}) \cdot k_j \rangle + \qquad \shape{..., C, C} +\] + +这个矩阵的构造是 chunk 内计算的核心。用它可以构造一个下三角线性系统: +\[ + M = I + \mathrm{tril}(A_{kk} \cdot \beta, \text{diagonal}=-1) + \qquad \shape{..., C, C} +\] +\[ + M \cdot W = \exp(g_{\mathrm{cum}}) \cdot k \qquad \Rightarrow \qquad + W = M^{-1} (\exp(g_{\mathrm{cum}}) \cdot k) +\] +\[ + M \cdot U = v \qquad \Rightarrow \qquad U = M^{-1} v +\] + +\subsection{代码对照} + +\begin{codemathtop}{ops/reference/chunkwise.py — naive\_chunk\_kda (核心)} +\begin{lstlisting} +# Rearrange: [B,T,H,K] -> [B,H,N,C,K] where N=T/C +q, k = [rearrange(x, 'b (n c) h d -> b h n c d', c=C) + .repeat_interleave(HV//H, dim=1) for x in (q, k)] +v, g = [rearrange(x, 'b (n c) h d -> b h n c d', c=C) + for x in (v, g)] +beta = rearrange(beta, 'b (n c) h -> b h n c', c=C) +q = q * scale +g = g.cumsum(dim=-2) # chunk 内 cumsum + +# Construct triangular system +A_kk = _decayed_dot(k, k, g) # [B,HV,N,C,C] +M = eye + (A_kk * beta[...,None,:]).masked_fill(mask_upper, 0) +W = solve_triangular(M, g.exp() * k, upper=False) +U = solve_triangular(M, v, upper=False) + +# A_qk: query 对 key 的 decayed dot (含对角线) +A_qk = (_decayed_dot(q, k, g) * beta[...,None,:]) + .masked_fill(mask_strict_upper, 0) +\end{lstlisting} +\end{codemathtop} + +\subsection{chunk 间递推} + +每个 chunk 内算完后,用 $W$ 和 $U$ 来处理跨 chunk 的状态: + +\begin{codemathtop}{ops/reference/chunkwise.py — chunk 间循环} +\begin{lstlisting} +S = zeros(B, HV, K, V) # inter-chunk state +for n in range(T // C): + # r = "local residual, adjusted by cross-chunk state" + r = U[:,:,n] - W[:,:,n] @ S # [B,HV,C,V] + + # output: cross-chunk part + intra-chunk part + o[:,:,n] = (q_n * g_n.exp()) @ S + A_qk[:,:,n] @ r + + # update cross-chunk state + decay = (g_n[:,:,-1:,:] - g_n).exp() # decay to chunk end + S = S * g_n[:,:,-1,:,None].exp() # decay old state + S = S + (decay * k_n).T @ (r * beta_n) # write new +\end{lstlisting} +\end{codemathtop} + +\subsection{形状流水线} + +\begin{center} +\begin{tabular}{lll} +\toprule +变量 & 形状 & 说明 \\ +\midrule +\texttt{q, k} (chunked) & \shape{B, HV, N, C, K} & $N = T/C$ 个 chunk \\ +\texttt{v, g} (chunked) & \shape{B, HV, N, C, V/K} & \\ +\texttt{beta} (chunked) & \shape{B, HV, N, C} & \\ +\texttt{A\_kk} & \shape{B, HV, N, C, C} & key-key decayed dot \\ +\texttt{M} & \shape{B, HV, N, C, C} & 下三角系统 \\ +\texttt{W} & \shape{B, HV, N, C, K} & $M^{-1}(\exp(g) \cdot k)$ \\ +\texttt{U} & \shape{B, HV, N, C, V} & $M^{-1} v$ \\ +\texttt{A\_qk} & \shape{B, HV, N, C, C} & query-key decayed dot \\ +\texttt{S} & \shape{B, HV, K, V} & 跨 chunk 状态 \\ +\texttt{r} & \shape{B, HV, C, V} & 调整后的残差 \\ +\texttt{o (chunk n)} & \shape{B, HV, C, V} & 本 chunk 输出 \\ +\bottomrule +\end{tabular} +\end{center} + +\begin{warningbox}{为什么用 triangular solve 而不是直接矩阵乘?} +Delta rule 的"先擦再写"引入了 chunk 内 token 之间的递归依赖: +token $i$ 的写入依赖 token $j < i$ 的写入结果。 +这个依赖关系恰好形成一个下三角线性系统 $M \cdot x = b$, +用 \texttt{solve\_triangular} 可以在 $O(C^2)$ 内并行求解, +而展开递归需要 $O(C)$ 步串行。 +\end{warningbox} + +\subsection{Decayed dot 函数} + +\begin{codemathtop}{ops/reference/chunkwise.py — \_decayed\_dot} +\begin{lstlisting} +def _decayed_dot(x, k, g): + """A[..., i, j] = """ + C = x.shape[-2] + A = empty(*x.shape[:-2], C, C) + for i in range(C): + decay = (g[..., i:i+1, :] - g).exp() # [.., 1, K] - [.., C, K] + A[..., i, :] = einsum('...jk,...jk->...j', + x[..., i, None, :] * decay, k) + return A +\end{lstlisting} +\end{codemathtop} + +\noindent 这是一个 $C \times C$ 的矩阵,每个元素 $(i,j)$ 是 +$x_i$ 和 $\exp(g_i - g_j) \cdot k_j$ 的内积。Triton 实现会把这个双循环融合成一个 kernel。 + +\subsection{本章小结} + +分块把 $T$ 步串行拆成 $T/C$ 个 chunk,chunk 内用下三角 solve 并行处理 delta rule 依赖, +chunk 间递推状态 $S$。最终输出与 naive recurrent 逐位相同。 diff --git a/notes/sections/sec-04.tex b/notes/sections/sec-04.tex new file mode 100644 index 0000000..97ec7c2 --- /dev/null +++ b/notes/sections/sec-04.tex @@ -0,0 +1,114 @@ +% teach: +% gap: 读者不知道 q/k 和 v 为什么可以有不同的头数, 以及 repeat_interleave 的反传怎么做 +% takeaway: GVA 让 G 组 value heads 共享一组 q/k, forward repeat_interleave, backward sum +% jump: 论文没解释为什么反传是 sum 而不是 mean +% omit: GQA 的历史 + +\section{GVA(分组值注意力)} +\splabel{GVA} + +\subsection{为什么头数不一样?} + +标准 MHA 里 $H_q = H_k = H_v$。GQA(Grouped Query Attention)让多组 q/k 共享同一组 v/k, +减少 KV cache。KDA 反过来做:$H$ 组 q/k 对应 $H_V = G \cdot H$ 组 value heads。 + +直觉:value 维度决定表达能力,多一点 value head 增加容量; +q/k 主要负责路由(``看哪里''),可以共享。 + +\begin{center} +\begin{tabular}{lll} +\toprule +& 标准头数 & GVA \\ +\midrule +$q, k$ & \shape{B, T, H, K} & \shape{B, T, H, K}(不变)\\ +$v$ & \shape{B, T, H, V} & \shape{B, T, HV, V}($H_V = G \cdot H$) \\ +$g, \beta$ & \shape{B, T, H, K/1} & \shape{B, T, HV, K/1} \\ +$S$ & \shape{B, H, K, V} & \shape{B, HV, K, V} \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{Forward: repeat\_interleave} + +进入 KDA 核心前,$q$ 和 $k$ 从 $H$ 维复制到 $H_V$ 维: + +\begin{lstlisting} +G = HV // H +qe = q.repeat_interleave(G, dim=2) * scale # [B,T,H,K] -> [B,T,HV,K] +ke = k.repeat_interleave(G, dim=2) # [B,T,H,K] -> [B,T,HV,K] +\end{lstlisting} + +\noindent 例如 $H=4, G=2, H_V=8$:head 0 的 q/k 复制到 value head 0 和 1, +head 1 复制到 value head 2 和 3,依此类推。 + +\subsection{Backward: view + sum} + +反传时,$dq_e$ 和 $dk_e$ 的形状是 \shape{B, T, HV, K}(在 $H_V$ 维上计算的梯度)。 +因为 forward 是复制,反传就是求和: + +\begin{lstlisting} +# Backward: HV -> H +dq_H = dq_e.view(B, T, H, G, K).sum(dim=3) # [B,T,HV,K] -> [B,T,H,K] +dk_H = dk_e.view(B, T, H, G, K).sum(dim=3) +\end{lstlisting} + +\begin{warningbox}{为什么是 sum 不是 mean?} +\texttt{repeat\_interleave} 是\textbf{复制}:$y_0 = x_0, y_1 = x_0, y_2 = x_1, \ldots$ + +对 $x_0$ 的梯度 = $\frac{\partial L}{\partial y_0} + \frac{\partial L}{\partial y_1}$ += \textbf{sum}(不是 mean)。 + +这和 \texttt{.expand()} 的反传一样:复制的反传是求和。 +\end{warningbox} + +\subsection{scale 的处理} + +$q$ 在 repeat\_interleave 之后乘了 \texttt{scale = $1/\sqrt{K}$}。 +反传时 chain rule 要求 $dq_{\mathrm{orig}} = dq_e \cdot \texttt{scale}$: + +\begin{lstlisting} +# q 在 forward 内被乘过 scale, chain rule: +dq_H = dq_H * scale +\end{lstlisting} + +\subsection{KDAAttention 层中的投影} + +\begin{codemathtop}{layers/kda\_attn.py — forward} +\begin{lstlisting} +def forward(self, x): # x: [B, T, D] + B, T, _ = x.shape + H, HV, K, V = self.num_heads, self.num_value_heads, ... + + q = self.q_proj(x).view(B, T, H, K) # [B,T,D] -> [B,T,H*K] -> [B,T,H,K] + k = self.k_proj(x).view(B, T, H, K) # 同上 + v = self.v_proj(x).view(B, T, HV, V) # [B,T,D] -> [B,T,HV*V] -> [B,T,HV,V] + g_raw = self.g_proj(x).view(B, T, HV, K) + beta_raw = self.beta_proj(x).view(B, T, HV) + + o, _ = chunk_kda(q, k, v, g_raw, beta_raw, ...) + return self.o_proj(o.reshape(B, T, HV * V)) # [B,T,HV,V] -> [B,T,D] +\end{lstlisting} +\end{codemathtop} + +\subsection{投影矩阵形状总览} + +\begin{center} +\begin{tabular}{llll} +\toprule +投影 & 权重形状 & 输入 & 输出 \\ +\midrule +\texttt{q\_proj} & \shape{H \cdot K, D} & \shape{B,T,D} & \shape{B,T,H,K} \\ +\texttt{k\_proj} & \shape{H \cdot K, D} & \shape{B,T,D} & \shape{B,T,H,K} \\ +\texttt{v\_proj} & \shape{HV \cdot V, D} & \shape{B,T,D} & \shape{B,T,HV,V} \\ +\texttt{g\_proj} & \shape{HV \cdot K, D} & \shape{B,T,D} & \shape{B,T,HV,K} \\ +\texttt{beta\_proj} & \shape{HV, D} & \shape{B,T,D} & \shape{B,T,HV} \\ +\texttt{o\_proj} & \shape{D, HV \cdot V} & \shape{B,T,HV \cdot V} & \shape{B,T,D} \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{本章小结} + +GVA 让 $H_V = G \cdot H$ 组 value heads 共享 $H$ 组 q/k。 +Forward 用 \texttt{repeat\_interleave} 复制,backward 用 \texttt{view+sum} 归约。 +$v, g, \beta$ 直接在 $H_V$ 维投影,q/k 在 $H$ 维投影。 diff --git a/notes/sections/sec-05.tex b/notes/sections/sec-05.tex new file mode 100644 index 0000000..49fc36d --- /dev/null +++ b/notes/sections/sec-05.tex @@ -0,0 +1,75 @@ +% teach: +% gap: 读者已知各组件, 但不清楚它们怎么黏在一起成为一个层 +% takeaway: KDAAttention = 投影 → gate+norm → chunk_kda → output 投影, 整个层就是 x → y [B,T,D] +% jump: none +% omit: from_config 工厂方法细节 + +\section{KDAAttention 层} + +\subsection{完整数据流} + +\texttt{KDAAttention} 把投影、gate 激活、KDA 核心计算和输出投影封装成一个 +\texttt{[B,T,D] $\to$ [B,T,D]} 的模块。 + +\begin{center} +\begin{tabular}{rlll} +\toprule +步骤 & 操作 & 输入形状 & 输出形状 \\ +\midrule +1 & \texttt{q\_proj(x)} & \shape{B,T,D} & \shape{B,T,H,K} \\ +2 & \texttt{k\_proj(x)} & \shape{B,T,D} & \shape{B,T,H,K} \\ +3 & \texttt{v\_proj(x)} & \shape{B,T,D} & \shape{B,T,HV,V} \\ +4 & \texttt{g\_proj(x)} & \shape{B,T,D} & \shape{B,T,HV,K} \\ +5 & \texttt{beta\_proj(x)} & \shape{B,T,D} & \shape{B,T,HV} \\ +6 & \texttt{chunk\_kda(...)} & 上述 5 项 + 参数 & \shape{B,T,HV,V} \\ +7 & \texttt{o.reshape(...)} & \shape{B,T,HV,V} & \shape{B,T,HV \cdot V} \\ +8 & \texttt{o\_proj(...)} & \shape{B,T,HV \cdot V} & \shape{B,T,D} \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{chunk\_kda 内部做了什么} + +\texttt{chunk\_kda}(\texttt{ops/api.py})是统一入口,按 \texttt{backend} 分发: + +\begin{enumerate}[nosep] +\item 如果 \texttt{use\_qk\_l2norm\_in\_kernel}:$q, k \leftarrow \text{L2-normalize}(q), \text{L2-normalize}(k)$ +\item 如果 \texttt{use\_beta\_sigmoid\_in\_kernel}:$\beta \leftarrow \sigma(\beta_{\mathrm{raw}})$ +\item 如果 \texttt{use\_gate\_in\_kernel}:应用 gate 激活(§2) +\item 调用 \texttt{naive\_chunk\_kda}(或 triton/fla 版本) +\end{enumerate} + +\begin{knowledgebox}{三个 ``in\_kernel'' 开关} +\begin{itemize}[nosep] +\item \texttt{use\_qk\_l2norm}:L2-norm 让 $\langle q, k \rangle$ 变成余弦相似度, + 稳定训练 +\item \texttt{use\_beta\_sigmoid}:sigmoid 把 $\beta$ 限制在 $(0,1)$, + 控制写入强度 +\item \texttt{use\_gate\_in\_kernel}:gate 激活在 API 内部完成(vs 调用方自己做) +\end{itemize} +默认三个都是 \texttt{True}。 +\end{knowledgebox} + +\subsection{可学习参数清单} + +\begin{center} +\begin{tabular}{lll} +\toprule +参数 & 形状 & 说明 \\ +\midrule +\texttt{q\_proj.weight} & \shape{H \cdot K, D} & query 投影 \\ +\texttt{k\_proj.weight} & \shape{H \cdot K, D} & key 投影 \\ +\texttt{v\_proj.weight} & \shape{HV \cdot V, D} & value 投影 \\ +\texttt{g\_proj.weight} & \shape{HV \cdot K, D} & gate 投影 \\ +\texttt{beta\_proj.weight} & \shape{HV, D} & beta 投影 \\ +\texttt{o\_proj.weight} & \shape{D, HV \cdot V} & 输出投影 \\ +\texttt{A\_log} & \shape{HV} & head-wise 衰减率(log-space)\\ +\texttt{dt\_bias} & \shape{HV, K} & per-dim gate bias \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{本章小结} + +KDAAttention 是一个完整的 mixing 模块:5 个线性投影 + gate 激活 + KDA 核心 + 输出投影。 +三个 ``in\_kernel'' 开关控制 L2-norm、sigmoid、gate 是否在 API 内部完成。 diff --git a/notes/sections/sec-06.tex b/notes/sections/sec-06.tex new file mode 100644 index 0000000..c7a8be5 --- /dev/null +++ b/notes/sections/sec-06.tex @@ -0,0 +1,173 @@ +% teach: +% gap: 读者知道标准 MHA 但不知道 MLA 怎么压缩 KV、矩阵吸收怎么避免解压 +% takeaway: MLA 把 KV 压成低秩 latent c, 通过吸收 W_UK 进 q 直接在 latent 空间算 attention +% jump: 为什么可以先在 latent 加权再乘 W_UV?因为矩阵乘和加权求和可交换 +% omit: RoPE (K3 用 NoPE) + +\section{Gated MLA(矩阵吸收版)} +\splabel{C4} + +\subsection{标准 MHA 的 KV cache 问题} + +标准 MHA 推理时需要缓存所有历史 token 的 $K, V$,cache 大小 $\propto T \cdot H \cdot d$。 +MLA 的想法:把 $K, V$ 压缩成一个低秩 latent $c$,cache 大小 $\propto T \cdot r$, +其中 $r \ll H \cdot d$。 + +\subsection{低秩压缩} + +\[ + c = \mathrm{RMSNorm}(W_{\downarrow} \cdot x) \qquad \shape{B, T, r} +\] + +推理时只缓存 $c$,不缓存解压后的 $K, V$。 +解压矩阵 $W_{\mathrm{KV}\uparrow}$ 包含两部分: +\[ + W_{\mathrm{KV}\uparrow} = \begin{bmatrix} W_{UK} \\ W_{UV} \end{bmatrix} + \qquad \shape{H \cdot (d_q + d_v), r} +\] +拆开:$W_{UK} \in \mathbb{R}^{H \times d_q \times r}$(key 解压), +$W_{UV} \in \mathbb{R}^{H \times d_v \times r}$(value 解压)。 + +\subsection{矩阵吸收的核心思路} + +\textbf{不解压} $K$ 和 $V$。标准做法会先解压再算 attention: + +\begin{center} +\textit{标准}:$k_h = c \cdot W_{UK,h}^T$ \shape{B,T,d_q}, +$\mathrm{score} = q_h \cdot k_h^T$ +\end{center} + +矩阵吸收反过来:把 $W_{UK}$ 吸收进 $q$: + +\begin{center} +\textit{吸收}:$q_{\mathrm{abs},h} = q_h \cdot W_{UK,h}$ \shape{B,T,r}, +$\mathrm{score} = q_{\mathrm{abs},h} \cdot c^T$ +\end{center} + +\begin{importantbox}{如果你只记一件事} +$(q \cdot W_{UK}^T) \cdot c^T = q \cdot (W_{UK}^T \cdot c^T) = q_{\mathrm{abs}} \cdot c^T$ + +吸收后,attention 直接在 latent 空间 $r$ 维上算,永不解压到 $H \cdot d_q$ 维。 +\end{importantbox} + +\subsection{完整计算流(四步)} + +\begin{enumerate}[leftmargin=2em] +\item \textbf{Q 低秩路径}(NoPE,只有 nope 段): +\[ + q = W_{q\uparrow} \cdot \mathrm{RMSNorm}(W_{q\downarrow} \cdot x) + \qquad \shape{B, T, H, d_q} +\] + +\item \textbf{吸收 $W_{UK}$ + 打分}: +\[ + q_{\mathrm{abs}} = q \cdot W_{UK} \quad + \xrightarrow{\texttt{einsum('bthd,hdj->bthj')}} \quad \shape{B, T, H, r} +\] +\[ + \mathrm{score} = q_{\mathrm{abs}} \cdot c^T \quad + \xrightarrow{\texttt{einsum('bthj,bsj->bhts')}} \quad \shape{B, H, T, T} +\] +\[ + \mathrm{attn} = \mathrm{softmax}(\mathrm{causal\_mask}(\mathrm{score})) + \qquad \shape{B, H, T, T} +\] + +\item \textbf{先在 latent 加权,再乘 $W_{UV}^T$}: +\[ + \tilde{o}_{\mathrm{lat}} = \mathrm{attn} \cdot c \quad + \xrightarrow{\texttt{einsum('bhts,bsj->bhtj')}} \quad \shape{B, H, T, r} +\] +\[ + \tilde{o} = \tilde{o}_{\mathrm{lat}} \cdot W_{UV}^T \quad + \xrightarrow{\texttt{einsum('bhtj,hvj->bhtv')}} \quad \shape{B, H, T, d_v} +\] + +\item \textbf{输出门 + 投影}: +\[ + y = W_o \big[ \sigma(W_g \cdot x) \odot \tilde{o}_{\mathrm{flat}} \big] + \qquad \shape{B, T, D} +\] +\end{enumerate} + +\subsection{代码对照} + +\begin{codemathtop}{layers/mla.py — GatedMLA.forward} +\begin{lstlisting} +def forward(self, x): # x: [B, T, D] + B, T, _ = x.shape + H, r = self.num_heads, self.kv_up.in_features + + # Step 1: latent + query + c = self.kv_norm(self.kv_down(x)) # [B, T, r] + q = self.q_up(self.q_norm(self.q_down(x))) # [B, T, H*d_q] + q = q.view(B, T, H, self.qk_nope_head_dim) # [B, T, H, d_q] + + # Split W_UK, W_UV from kv_up.weight + w = self.kv_up.weight # [H*(d_q+d_v), r] + w_uk = w[:H*d_q].view(H, d_q, r) # [H, d_q, r] + w_uv = w[H*d_q:].view(H, d_v, r) # [H, d_v, r] + + # Step 2: absorb W_UK, score + q_absorb = einsum('bthd,hdj->bthj', q, w_uk) # [B,T,H,r] + scores = einsum('bthj,bsj->bhts', q_absorb, c) # [B,H,T,T] + scores = scores.masked_fill(causal_mask, -inf) + attn = softmax(scores, dim=-1) # [B,H,T,T] + + # Step 3: latent-space weighted sum, then W_UV + latent_out = einsum('bhts,bsj->bhtj', attn, c) # [B,H,T,r] + o_heads = einsum('bhtj,hvj->bhtv', latent_out, w_uv) # [B,H,T,d_v] + + # Step 4: output gate + o_heads = o_heads.transpose(1,2).reshape(B,T, H*d_v) + gate = sigmoid(self.gate(x)) # [B,T,H*d_v] + return self.o_proj(gate * o_heads) # [B,T,D] +\end{lstlisting} +\end{codemathtop} + +\begin{warningbox}{为什么可以先加权再乘 $W_{UV}$?} +标准做法:$o = \mathrm{attn} \cdot V = \mathrm{attn} \cdot (c \cdot W_{UV}^T)$ + +交换顺序:$o = (\mathrm{attn} \cdot c) \cdot W_{UV}^T$ + +这能成立是因为矩阵乘法的结合律:$A(BC) = (AB)C$。 +$\mathrm{attn} \cdot c$ 先在 latent 空间 $r$ 维上加权求和, +得到的 \shape{B,H,T,r} 再乘 $W_{UV}^T$ 还原到 $d_v$ 维。 +全程不需要显式构造 $H \cdot T$ 大小的 $V$ 矩阵。 +\end{warningbox} + +\subsection{形状与参数对比} + +\begin{center} +\begin{tabular}{lll} +\toprule +参数 & 形状 & 说明 \\ +\midrule +\texttt{kv\_down.weight} & \shape{r, D} & KV latent 压缩 \\ +\texttt{kv\_up.weight} & \shape{H \cdot (d_q+d_v), r} & 包含 $W_{UK}$ 和 $W_{UV}$ \\ +\texttt{q\_down.weight} & \shape{r_q, D} & Q 低秩 \\ +\texttt{q\_up.weight} & \shape{H \cdot d_q, r_q} & Q 解压 \\ +\texttt{gate.weight} & \shape{H \cdot d_v, D} & 输出门 \\ +\texttt{o\_proj.weight} & \shape{D, H \cdot d_v} & 输出投影 \\ +\bottomrule +\end{tabular} +\end{center} + +\noindent KV cache 大小对比(推理时): + +\begin{center} +\begin{tabular}{ll} +\toprule +方法 & Cache 大小 per token \\ +\midrule +标准 MHA & $2 \times H \times d = 2 H d$ \\ +MLA (latent) & $r$(只存 $c$) \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{本章小结} + +Gated MLA 把 KV 压缩到低秩 latent $c$ \shape{B,T,r},通过矩阵吸收 +($q_{\mathrm{abs}} = q \cdot W_{UK}$)直接在 latent 空间打分和加权, +永不解压 K/V。输出通过 sigmoid 门控。NoPE:不使用 RoPE,位置感交给夹层 KDA 的 decay/gate。 diff --git a/notes/sections/sec-07.tex b/notes/sections/sec-07.tex new file mode 100644 index 0000000..169ad74 --- /dev/null +++ b/notes/sections/sec-07.tex @@ -0,0 +1,158 @@ +% teach: +% gap: 读者知道 MoE 的 top-k 路由但不知道 LatentMoE 的 latent 接口和 SiTU-GLU +% takeaway: LatentMoE 通过 latent 接口把 routed 专家限制在 ℓ=d/2 上算, SiTU-GLU 用软上限防溢出 +% jump: 为什么 routed 专家在 latent 空间而 shared 在全宽?省参数 +% omit: load balancing loss + +\section{SiTU-GLU 与 Stable LatentMoE} +\splabel{C5} + +\subsection{SiTU-GLU:带软上限的激活} + +SwiGLU 在低精度(fp16/bf16)训练时可能溢出:$\mathrm{silu}(x) \cdot x$ 没有上限。 +SiTU-GLU 用 $\tanh$ 给门控和上投影加软上限: + +\[ + \mathrm{SiTU}(x) = W_o \big[\underbrace{\beta_1 \tanh\!\left(\frac{W_g x}{\beta_1}\right) \cdot \sigma(W_g x)}_{\text{gate}} \;\cdot\; \underbrace{\beta_2 \tanh\!\left(\frac{W_u x}{\beta_2}\right)}_{\text{up}}\big] +\] + +\begin{center} +\begin{tabular}{lp{8cm}} +\toprule +性质 & 说明 \\ +\midrule +输出上限 & $\|\mathrm{SiTU}\|_\infty \leq \beta_1 \cdot \beta_2 = 4 \times 25 = 100$ \\ +原点附近 & $\tanh(x/\beta) \approx x/\beta$,所以 $\beta \cdot \tanh(x/\beta) \approx x$,退化为 SwiGLU \\ +远端 & 软饱和,防 fp16 溢出 \\ +\bottomrule +\end{tabular} +\end{center} + +\begin{codemathtop}{layers/latent\_moe.py — SiTU} +\begin{lstlisting} +class SiTU(nn.Module): + def __init__(self, dim_in, dim_ff, beta1=4.0, beta2=25.0): + self.w_g = nn.Linear(dim_in, dim_ff, bias=False) + self.w_u = nn.Linear(dim_in, dim_ff, bias=False) + self.w_o = nn.Linear(dim_ff, dim_in, bias=False) + + def forward(self, x): # [*, dim_in] + wg = self.w_g(x) + g = self.beta1 * tanh(wg / self.beta1) * sigmoid(wg) # gate + u = self.beta2 * tanh(self.w_u(x) / self.beta2) # up + return self.w_o(g * u) # [*, dim_in] +\end{lstlisting} +\end{codemathtop} + +\subsection{LatentMoE 架构} + +\begin{center} +\begin{tabular}{rl} +\toprule +组件 & 说明 \\ +\midrule +\textbf{Shared 专家} & $n_{\mathrm{shared}}$ 个 SiTU,全宽 $d \to d$,所有 token 都经过 \\ +\textbf{Routed 专家} & $n_{\mathrm{routed}}$ 个 SiTU,半宽 $\ell \to \ell$($\ell = d/2$) \\ +\textbf{Latent 接口} & $W_\downarrow: d \to \ell$, $W_\uparrow: \ell \to d$(压缩/还原) \\ +\textbf{Router} & $W_r: d \to n_{\mathrm{routed}}$,Top-k 选择 + softmax 归一化 \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{计算流(五步)} + +\begin{enumerate}[leftmargin=2em] +\item \textbf{Latent 压缩}: +\[ + z = W_\downarrow \cdot x \qquad \shape{B, T, \ell} +\] + +\item \textbf{Routing}: +\[ + \mathrm{logits} = W_r \cdot x \qquad \shape{B, T, n_{\mathrm{routed}}} +\] +\[ + \mathrm{ids}, \mathrm{probs} = \mathrm{TopK}(\mathrm{logits}, k) + \qquad \mathrm{ids}: \shape{B, T, k}, \;\; \mathrm{probs}: \shape{B, T, k} +\] + +\item \textbf{Routed 专家}(在 latent 空间 $\ell$ 上): +\[ + u = \sum_{i \in \mathrm{Top\text{-}k}} p_i \cdot E_i^{\mathrm{rt}}(z) + \qquad \shape{B, T, \ell} +\] + +\item \textbf{Shared 专家}(全宽 $d$): +\[ + s = \sum_j E_j^{\mathrm{sh}}(x) \qquad \shape{B, T, d} +\] + +\item \textbf{合并}: +\[ + y = s + W_\uparrow \cdot \mathrm{RMSNorm}(u) \qquad \shape{B, T, d} +\] +\end{enumerate} + +\begin{importantbox}{如果你只记一件事} +Routed 专家只在 $\ell = d/2$ 的 latent 空间操作, +参数量是全宽专家的 $1/4$($\ell^2$ vs $d^2$)。 +Shared 专家保持全宽 $d$,提供基础表达能力。 +\end{importantbox} + +\subsection{代码对照} + +\begin{codemathtop}{layers/latent\_moe.py — LatentMoE.forward} +\begin{lstlisting} +def forward(self, x): # [B, T, d] + z = self.down(x) # [B, T, ell] + + logits = self.router(x) # [B, T, n_routed] + topk = torch.topk(logits, self.top_k, dim=-1) + ids = topk.indices # [B, T, k] + probs = F.softmax(topk.values, dim=-1) # [B, T, k] + + # All expert outputs (vectorized) + all_out = stack([e(z) for e in self.experts]) # [R, B, T, ell] + # Gather top-k and weighted sum + u = zeros(B, T, ell) + for i in range(self.top_k): + idx = ids[:,:,i].reshape(B*T) + sel = all_out[arange, idx] + u += probs[:,:,i:i+1] * sel.reshape(B, T, ell) + + shared_out = stack([e(x) for e in self.shared]).sum(0) # [B, T, d] + return shared_out + self.up(self.norm(u)) # [B, T, d] +\end{lstlisting} +\end{codemathtop} + +\begin{warningbox}{为什么 router 用 $x$(全宽)而不是 $z$(latent)?} +路由需要看到 token 的完整表示才能做好选择。 +如果用 $z$ 路由,压缩过程可能丢失路由需要的信息。 +K3 论文里也是用全宽 $x$ 做 Top-k,然后在 $\ell$ 空间计算。 +\end{warningbox} + +\subsection{形状总览} + +\begin{center} +\begin{tabular}{llll} +\toprule +变量 & 形状 & 说明 \\ +\midrule +$x$ & \shape{B, T, d} & 输入 \\ +$z$ & \shape{B, T, \ell} & latent($\ell = d/2$)\\ +logits & \shape{B, T, n_r} & router 输出 \\ +ids & \shape{B, T, k} & Top-k 专家索引 \\ +probs & \shape{B, T, k} & Top-k softmax 权重 \\ +\texttt{all\_out} & \shape{n_r, B, T, \ell} & 所有 routed 专家输出 \\ +$u$ & \shape{B, T, \ell} & 加权求和后的 routed 输出 \\ +\texttt{shared\_out} & \shape{B, T, d} & shared 专家求和 \\ +$y$ & \shape{B, T, d} & 最终输出 \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{本章小结} + +LatentMoE 把 routed 专家限制在 $\ell = d/2$ 的 latent 空间,省参数。 +SiTU-GLU 给 gate 和 up 加 $\tanh$ 软上限($\beta_1=4, \beta_2=25$), +防止低精度溢出。Shared 专家全宽,提供基础能力;routed 专家通过 Top-k 路由提供专业化能力。 diff --git a/notes/sections/sec-08.tex b/notes/sections/sec-08.tex new file mode 100644 index 0000000..e1ef771 --- /dev/null +++ b/notes/sections/sec-08.tex @@ -0,0 +1,162 @@ +% teach: +% gap: 读者已知各组件但不知道怎么组装成完整模型 +% takeaway: K3 = Hybrid(3 KDA + 1 MLA) × DecoderBlock(attn + MoE), 末层强制 MLA +% jump: 为什么每 4 层才放一次 MLA?位置感知只需要周期性提供 +% omit: 0.5b preset 的训练超参 + +\section{K3 混合架构} + +\subsection{整体结构} + +\begin{center} +\texttt{Embedding} $\to$ \texttt{DecoderBlock} $\times L$ $\to$ \texttt{RMSNorm} $\to$ \texttt{LM Head} +\end{center} + +每个 \texttt{DecoderBlock} 是 Pre-Norm 残差: + +\begin{lstlisting} +def forward(self, x): + x = x + self.attn(self.attn_norm(x)) # mixing + return x + self.ffn(self.ffn_norm(x)) # channel +\end{lstlisting} + +\subsection{Hybrid Attention Pattern} + +K3 用两种 attention 层交替: + +\begin{center} +\begin{tabular}{ccccccccc} +\toprule +层 & 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 \\ +\midrule +Attn & KDA & KDA & KDA & \textbf{MLA} & KDA & KDA & KDA & \textbf{MLA} \\ +FFN & MoE & MoE & MoE & MoE & MoE & MoE & MoE & MoE \\ +\bottomrule +\end{tabular} +\end{center} + +\noindent 规则:每 4 层放 1 次 MLA(0-based 层 3, 7, 11, ...),\textbf{末层强制 MLA}。 + +\begin{codemathtop}{models/k3\_config.py — layer\_types} +\begin{lstlisting} +def layer_types(self) -> list[str]: + """Hybrid: 3 KDA + 1 MLA per group, last always MLA.""" + types = ["kda"] * self.num_hidden_layers + for i in range(self.num_hidden_layers): + if i % 4 == 3: + types[i] = "mla" + types[-1] = "mla" # last layer forced + return types + +def layer_specs(self): + return [(kind, "moe") for kind in self.layer_types()] +\end{lstlisting} +\end{codemathtop} + +\begin{knowledgebox}{为什么 KDA 不需要 RoPE?} +KDA 的 gate/decay 机制天然提供位置感知: +远的 token 衰减更多,近的保留更多。 +但 softmax attention(MLA)没有这个机制,所以真实 K3 用 NoPE +(本复现的 MLA 也是 NoPE)。 +位置感知从 KDA 层``渗透''到 MLA 层——3:1 的比例足够了。 +\end{knowledgebox} + +\subsection{CausalLM 完整数据流} + +\begin{codemathtop}{models/causal\_lm.py — CausalLM} +\begin{lstlisting} +class CausalLM(nn.Module): + def __init__(self, config): + self.embedding = nn.Embedding(vocab_size, D) # [V, D] + self.blocks = ModuleList([ + DecoderBlock.from_spec(config, attn, ffn) + for attn, ffn in config.layer_specs() + ]) + self.norm = RMSNorm(D) + self.lm_head = nn.Linear(D, vocab_size) # [V, D] + if config.tie_word_embeddings: + self.lm_head.weight = self.embedding.weight + + def forward(self, input_ids, labels=None): + x = self.embedding(input_ids) # [B,T] -> [B,T,D] + if self.mixer is None: # attnres="off" + for block in self.blocks: + x = block(x) # [B,T,D] -> [B,T,D] + else: + x = self.mixer(x) # AttnRes 深度残差, 见 §9 + logits = self.lm_head(self.norm(x)) # [B,T,D] -> [B,T,V] + if labels is None: + return logits + # Shifted CE: predict next token + return cross_entropy(logits[:,:-1], labels[:,1:]) +\end{lstlisting} +\end{codemathtop} + +\begin{knowledgebox}{残差流是可替换的} +上面的 \texttt{DecoderBlock} 逐层堆叠(\texttt{x = x + sublayer(norm(x))}) +是 \texttt{config.attnres="off"} 时的默认路径。 +置为 \texttt{"full"} / \texttt{"block"} 时,\texttt{CausalLM} 会把每个 block +拆成 attn / ffn 两个原子子层交给 \texttt{mixer},用\textbf{深度维注意力} +代替等权残差加法——见 \S9。真实 K3 用的是 \texttt{block} 模式。 +\end{knowledgebox} + +\subsection{两种配置} + +\begin{center} +\begin{tabular}{lll} +\toprule +& \textbf{KDAConfig}(纯 KDA) & \textbf{K3Config}(混合) \\ +\midrule +Attn & KDA only & 3 KDA + 1 MLA \\ +FFN & SwiGLU & LatentMoE \\ +典型规模 & \textasciitilde8M (toy) & \textasciitilde8M (toy) / \textasciitilde500M (0.5b) \\ +\texttt{layer\_specs()} & \texttt{[("kda","swiglu")] * L} & \texttt{[(kind,"moe") for kind in ...]} \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{K3 toy 尺寸} + +\begin{center} +\begin{tabular}{llll} +\toprule +参数 & 真实 K3 & toy 复现 & 缩比 \\ +\midrule +$D$ & 7168 & 256 & 28$\times$ \\ +$L$ & 93 & 4 & 23$\times$ \\ +$H = H_V$ & 96 & 8 & 12$\times$ \\ +$K = V$ & 128 & 16 & 8$\times$ \\ +kv\_lora\_rank & 512 & 32 & 16$\times$ \\ +q\_lora\_rank & 1536 & 64 & 24$\times$ \\ +$\ell$ (MoE latent) & 3584 & 128 & 28$\times$ \\ +$n_{\mathrm{routed}}$ / Top-$k$ & 896/16 & 16/2 & 56$\times$ / 8$\times$ \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{DecoderBlock 构建} + +\begin{codemathtop}{layers/block.py — build\_attn / build\_ffn} +\begin{lstlisting} +def build_attn(config, kind: str) -> nn.Module: + if kind == "kda": return KDAAttention.from_config(config) + if kind == "mla": return GatedMLA.from_config(config) + +def build_ffn(config, kind: str) -> nn.Module: + if kind == "swiglu": return SwiGLUMLP.from_config(config) + if kind == "moe": return LatentMoE.from_config(config) + +class DecoderBlock(nn.Module): + def forward(self, x): + x = x + self.attn(self.attn_norm(x)) + return x + self.ffn(self.ffn_norm(x)) +\end{lstlisting} +\end{codemathtop} + +\subsection{本章小结} + +K3 架构 = Hybrid Attention(3 KDA + 1 MLA,末层强制 MLA)+ LatentMoE。 +KDA 层提供线性复杂度的序列混合和位置感知(通过 decay), +MLA 层提供全局 softmax attention(NoPE,利用 KDA 渗透的位置信息)。 +每层默认是 Pre-Norm 残差 DecoderBlock;\texttt{config.attnres} 可以把这条 +等权残差流换成 AttnRes 深度注意力(\S9)。 diff --git a/notes/sections/sec-09.tex b/notes/sections/sec-09.tex new file mode 100644 index 0000000..65837a1 --- /dev/null +++ b/notes/sections/sec-09.tex @@ -0,0 +1,379 @@ +% teach: +% gap: 读者知道 Pre-Norm 残差是"无条件等权累加", 但不知道怎么把它换成"按内容选择读哪一层" +% takeaway: AttnRes = 深度维 softmax 注意力残差; Block 版把 O(N^2) 源数压到 O(N/S); 两阶段 = inter 批量 + intra online-softmax 合并 +% jump: 为什么打分用 RMSNorm 后的 v, 加权和却用原始 v +% omit: 论文里的 kernel 级调度与 pipeline 重叠 + +\section{Attention Residual 深度残差} + +\subsection{从"等权累加"到"按内容选择"} + +标准 Pre-Norm 残差把每层输出\textbf{无条件加}进残差流: + +\[ + x_l = x_{l-1} + f_l(x_{l-1}), + \qquad + x_N = x_0 + \sum_{l=1}^{N} f_l(x_{l-1}) +\] + +\noindent 展开后每一项权重恒为 1:第 3 层的输出和第 80 层的输出对最终表示的 +"名义"贡献一样大,深层无法表达"我这一步应该主要读第 12 层的结果"。 + +AttnRes(\texttt{arXiv:2603.15031})把这个加法换成\textbf{深度维上的 softmax 注意力}: +第 $l$ 层持有一个可学习 query 向量 $w_l \in \mathbb{R}^D$, +把此前所有层的输出当成"可读的记忆": + +\begin{align} + s_{l,i} &= w_l^{\top}\,\mathrm{RMS}(v_i), + & i = 0,1,\dots,l-1 \tag{A1} \\ + \alpha_{l,i} &= \frac{\exp(s_{l,i})}{\sum_{j} \exp(s_{l,j})} + & \shape{n, B, T} \tag{A2} \\ + h_l &= \sum_{i} \alpha_{l,i}\, v_i + & \shape{B, T, D} \tag{A3} \\ + v_l &= f_l(h_l) \tag{A4} +\end{align} + +\noindent 其中 $v_0 = x$(embedding 输出),$f_l$ 是已经含 Pre-Norm 的原子子层, +$\mathrm{RMS}(\cdot)$ 是不带 gain 的 RMS 归一化。 +最后(\texttt{is\_final\_aggregate=True})再用一个独立 query 聚合所有源得到 $y$。 + +\begin{importantbox}{注意力权重是逐 token 的} +$s_{l,i}$ 的形状是 \shape{n, B, T}——每个 batch、每个位置 $t$ 都有自己的一套深度权重。 +所以同一个位置在不同深度可以读不同的层,但\textbf{不跨时间混合}, +因果性完全不受影响(\texttt{test\_attnres\_is\_still\_causal})。 +\end{importantbox} + +\subsection{DepthResidual:三个实现细节} + +\begin{codemathtop}{layers/attn\_res.py — DepthResidual} +\begin{lstlisting} +class DepthResidual(nn.Module): + def __init__(self, dim, eps=1e-8, zero_init=True): + self.query = nn.Parameter(torch.zeros(dim)) # [D] + self.norm = RMSNorm(dim, eps=eps) # gain gamma + + def effective_query(self): + return (self.query * self.norm.weight).float() # 折叠 gain + + def forward(self, sources): + sources = stack_layers(sources) # [n,B,T,D] + q = self.effective_query() # [D] + k = rms(sources.float(), self.norm.eps) # 只用于打分 + logits = einsum('d, n b t d -> n b t', q, k) + w = logits.softmax(dim=0) # 在深度维 softmax + out = einsum('n b t, n b t d -> b t d', w, sources.float()) + return out.to(sources.dtype) +\end{lstlisting} +\end{codemathtop} + +\paragraph{(1) gain 折叠} +RMSNorm 的可学习 gain $\gamma$ 本该作用在 key 上,但 +$w^{\top}(\gamma \odot \mathrm{RMS}(v)) = (w \odot \gamma)^{\top}\mathrm{RMS}(v)$, +所以直接把 $\gamma$ 折进 query:$\tilde{w}_l = w_l \odot \gamma_l$。 +少一次 \shape{n,B,T,D} 的逐元素乘法,两阶段算法里也只需要传一个向量。 + +\paragraph{(2) 打分用归一化的 $v$,加权和用原始 $v$} +注意 \texttt{logits} 用 \texttt{k = rms(sources)},而 \texttt{out} 用的是 +\texttt{sources} 本身。 + +\begin{knowledgebox}{为什么这样不对称?} +打分要的是\textbf{方向}:$\mathrm{RMS}$ 之后 $s_{l,i}$ 与 $\|v_i\|$ 无关, +一层输出幅度大不会自动抢到高权重,softmax 只按"内容像不像我要读的东西"分配。\\ +加权和要的是\textbf{原始信息}:如果对归一化后的 $v$ 求和,每层输出的模长 +(承载着"这层贡献多大"的信息)就被抹掉了,深层的小幅修正会被放大到和主干同量级。 +\end{knowledgebox} + +\paragraph{(3) zero-init query} +\texttt{query} 默认初始化为 $0$ $\Rightarrow$ 所有 logits 为 $0$ +$\Rightarrow$ softmax 均匀 $\Rightarrow$ +\[ + h_l = \frac{1}{l}\sum_{i=0}^{l-1} v_i +\] +训练起步就是\textbf{等权深度平均}(已实测:零初始化时 \texttt{forward} 输出与 +\texttt{sources.mean(0)} 逐位相同),行为接近标准残差但自带 $1/l$ 缩放, +之后由梯度慢慢学出偏好。设 \texttt{zero\_init\_queries=False} 则用 $\mathcal{N}(0, 0.02^2)$。 + +\subsection{Full 与 Block:源数量的差别} + +两种堆叠方式的区别只在\textbf{谁有资格进入源列表}: + +\begin{itemize}[nosep, leftmargin=2em] +\item \texttt{FullAttnResStack}\\ + 保留\textbf{每一个原子层}的输出作为源,第 $l$ 层在 $l+1$ 个源上做注意力。 +\item \texttt{BlockAttnResStack}\\ + 把 $N$ 个原子层切成大小为 $S$ 的块,\textbf{块内退化成普通求和} + (running partial $p \leftarrow p + v$), + 只有\textbf{块的输出} $b_j$ 才进入源列表。 +\end{itemize} + +\begin{center} +\begin{tabular}{lccc} +\toprule + & \textbf{Full} & \textbf{Block ($S$)} & 标准残差 \\ +\midrule +注意力源数 & 最多 $N+1$ & 最多 $N/S + 2$ & 1 \\ +需保留的 \shape{B,T,D} 激活 & $O(N)$ & $O(N/S)$ & $O(1)$ \\ +深度注意力 FLOPs & $O(N^2 BTD)$ & $O(N^2 BTD / S)$ & 0 \\ +新增参数 & $2(N{+}1)D$ & $2(N{+}1)D$ & 0 \\ +\bottomrule +\end{tabular} +\end{center} + +\noindent 参数量不变(每个原子层都有自己的 query),变的是\textbf{显存与带宽}。 +真实 K3($L=93$,$N=186$)取 $S=24$ 个原子层(12 个 DecoderBlock), +源数从 187 降到 $\le 10$。 + +\begin{codemathtop}{layers/attn\_res.py — BlockAttnResStack.forward\_naive(语义参考实现)} +\begin{lstlisting} +blocks = [x] # b_0 = embedding +partial = None +for layer_idx, (layer, residual) in enumerate(zip(self.layers, self.residuals), 1): + sources = blocks if partial is None else blocks + [partial] + h = residual(sources) # 深度注意力 + out = layer(h) + partial = out if partial is None else (partial + out) # 块内: 普通累加 + if (layer_idx % self.block_size == 0) or (layer_idx == len(self.layers)): + blocks.append(partial) # 块边界: 定型成一个新源 + partial = None +return self.final_residual(blocks) +\end{lstlisting} +\end{codemathtop} + +\subsection{两阶段算法(inter / intra)} + +块内逐层跑上面的 naive 版本有个浪费:块内每一层的 query 面对的 +\textbf{块间源 $b_0 \dots b_{j-1}$ 是完全相同且固定的}, +唯一在变的只有 running partial $p$。于是拆成两个阶段: + +\begin{enumerate}[nosep] +\item \textbf{inter(批量)}:把块内 $S$ 个 query 堆成 \shape{S, D}, + 对固定源做\textbf{一次}批量 einsum,拿到每个 query 的 online-softmax 三元组 + $(m,\ \text{numer},\ \text{denom})$; +\item \textbf{intra(串行)}:逐层把新出现的 $p$ 作为\textbf{单个源}合并进去, + 用 online softmax 的 merge 规则更新三元组,再 \texttt{normalized()} 出 $h$。 +\end{enumerate} + +\noindent online softmax 的三元组定义与合并规则(和 FlashAttention 同构, +只是"序列维"换成了"深度维"): + +\begin{align} + m = \max_i s_i, + \quad + n = \sum_i e^{s_i - m} v_i, + \quad + d = \sum_i e^{s_i - m}, + \quad + h = n / d + \tag{OS1} +\end{align} + +\noindent 合并两组统计量 $(m_a, n_a, d_a)$ 与 $(m_b, n_b, d_b)$, +令 $m = \max(m_a, m_b)$、$w_a = e^{m_a - m}$、$w_b = e^{m_b - m}$: + +\begin{align} + n = w_a\, n_a + w_b\, n_b, + \quad + d = w_a\, d_a + w_b\, d_b + \tag{OS2} +\end{align} + +\noindent 单个源 $p$ 的三元组是 $(\,m = s_p,\ \text{numer} = p,\ \text{denom} = 1\,)$ +——因为 $e^{s_p - m} = 1$,不需要真的算指数(\texttt{single\_source\_stats})。 + +\begin{codemathtop}{layers/attn\_res.py — \_run\_block\_two\_phase} +\begin{lstlisting} +queries = torch.stack([self.residuals[i].effective_query() + for i in range(start, end)], dim=0) # [S, D] +inter = attn_with_stats(queries, stack_layers(blocks), self.eps) # phase 1: 一次算完 + +partial = None +for local_idx, layer_idx in enumerate(range(start, end)): # phase 2: 串行 + stats = inter.select(local_idx) + if partial is not None: + intra = single_source_stats(queries[local_idx], partial, self.eps) + stats = merge_attn_stats(stats, intra) # online softmax merge + h = stats.normalized() + out = self.layers[layer_idx](h) + partial = out if partial is None else (partial + out) +return partial +\end{lstlisting} +\end{codemathtop} + +\begin{importantbox}{等价性是被测出来的,不是假设的} +\texttt{test\_block\_two\_phase\_matches\_naive} 直接对拍 +\texttt{mixer.forward\_naive(emb)} 与 \texttt{mixer(emb)}, +\texttt{atol=rtol=1e-5} 通过。Full 版同理:不传 \texttt{schedule\_block\_size} +走 naive,传了走两阶段,两者一致。 +\end{importantbox} + +\subsection{接入 CausalLM} + +\subsubsection*{原子层 = 半个 DecoderBlock} + +深度注意力的粒度是\textbf{原子层}而不是 DecoderBlock: +每个 block 拆成"norm + attn"和"norm + ffn"两个 Pre-Norm 原子层, +所以原子层数 $N = 2L$。 + +\begin{codemathtop}{models/causal\_lm.py — \_build\_mixer} +\begin{lstlisting} +atomics = [] +for block in blocks: + atomics.append(BorrowedSubLayer(block.attn_norm, block.attn)) + atomics.append(BorrowedSubLayer(block.ffn_norm, block.ffn)) + +if mode == "full": + return FullAttnResStack(D, atomics, eps=..., zero_init_queries=..., ...) +if mode == "block": + return BlockAttnResStack(D, atomics, + block_size=atomic_block_size(config.num_hidden_layers, + config.attnres_block_size), ...) +\end{lstlisting} +\end{codemathtop} + +\subsubsection*{BorrowedSubLayer:借用而不注册} + +\begin{codemathtop}{layers/attn\_res.py — BorrowedSubLayer} +\begin{lstlisting} +class BorrowedSubLayer(nn.Module): + def __init__(self, norm, fn): + self._borrowed = (norm, fn) # 普通 tuple, 不是 self.norm = norm + + def forward(self, x): + norm, fn = self._borrowed + return fn(norm(x)) +\end{lstlisting} +\end{codemathtop} + +\begin{warningbox}{为什么必须用 tuple 藏起来?} +如果写成 \texttt{self.norm = norm},\texttt{nn.Module} 会把它\textbf{注册成子模块}, +于是同一份权重同时挂在 \texttt{blocks.0.attn.*} 和 \texttt{mixer.layers.0.fn.*} 下: +\begin{itemize}[nosep] +\item \texttt{model.parameters()} 出现重复 $\Rightarrow$ 优化器对同一参数更新两次 +\item \texttt{state\_dict()} 多出一份镜像键 $\Rightarrow$ 旧 checkpoint 加载不上 +\end{itemize} +放进普通 tuple 后 \texttt{blocks.*} 仍是唯一属主, +\texttt{mixer} 下只多出 depth query 与 gain(\texttt{test\_no\_duplicate\_parameter\_ids} 守这条)。 +\end{warningbox} + +\subsubsection*{forward:mixer 接管整条残差流} + +\begin{codemathtop}{models/causal\_lm.py — CausalLM.forward} +\begin{lstlisting} +x = self.embedding(input_ids) +if self.mixer is None: + for block in self.blocks: # attnres="off": 老路径 + x = block(x) +else: + x = self.mixer(x) # full / block: DecoderBlock.forward 被完全绕过 +logits = self.lm_head(self.norm(x)) +\end{lstlisting} +\end{codemathtop} + +\noindent 注意 \texttt{mixer} 打开后 \texttt{DecoderBlock.forward} +(\S8 里的 \texttt{x = x + attn(...)})\textbf{一次都不会被调用}—— +残差加法整个交给深度注意力,DecoderBlock 退化成"两个子层的容器"。 + +\subsubsection*{新增参数量:可忽略} + +每个 DepthResidual 只有 query \shape{D} 和 gain \shape{D},共 $N+1$ 个: + +\begin{center} +\begin{tabular}{lrrr} +\toprule +配置 & $D$ / $L$ & 原子层 $N$ & 新增参数 \\ +\midrule +toy (K3Config) & 256 / 4 & 8 & 4{,}608 \\ +0.5b preset & 768 / 24 & 48 & 75{,}264 \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{配置与命令行} + +\begin{center} +\begin{tabular}{lll} +\toprule +字段 & 默认 & 含义 \\ +\midrule +\texttt{attnres} & \texttt{"off"} & \texttt{off} / \texttt{full} / \texttt{block} \\ +\texttt{attnres\_block\_size} & \texttt{None} & 每块几个 \textbf{DecoderBlock};\texttt{None} $\to \lceil L/8 \rceil$ \\ +\texttt{attnres\_zero\_init\_queries} & \texttt{True} & query 零初始化(等权起步)\\ +\texttt{attnres\_final\_aggregate} & \texttt{True} & 末尾再做一次全源聚合 \\ +\bottomrule +\end{tabular} +\end{center} + +\begin{codemathtop}{layers/attn\_res.py — atomic\_block\_size} +\begin{lstlisting} +def atomic_block_size(num_hidden_layers, attnres_block_size): + """DecoderBlock 数 -> 原子层数。None 时目标约 8 块。""" + layers_per_block = (attnres_block_size if attnres_block_size is not None + else max(1, (num_hidden_layers + 7) // 8)) + return layers_per_block * 2 # 每个 DecoderBlock = attn|ffn 两个原子层 +\end{lstlisting} +\end{codemathtop} + +\noindent 单位换算是最容易踩的一处: +配置字段的单位是 \textbf{DecoderBlock 数}, +而堆叠类收到的 \texttt{block\_size} 是\textbf{原子层数}($\times 2$)。 +例如 $L = 24$、块大小留 \texttt{None}: +\[ + \lceil 24/8 \rceil = 3 \text{ 个 DecoderBlock} + \;\to\; S = 6 \text{ 个原子层} + \;\to\; N/S = 48/6 = 8 \text{ 块} +\] + +\begin{lstlisting} +uv run python train_k3.py --preset toy --attnres block --attnres-block-size 2 +uv run python train_k3.py --preset 0.5b --attnres block # 块大小自动 ~L/8 +\end{lstlisting} + +\noindent \texttt{KDAConfig} 与 \texttt{K3Config} 都在 +\texttt{\_\_post\_init\_\_} 里调 \texttt{validate\_attnres}, +非法模式 / 块大小在构造时就报错。 +旧 checkpoint 的 config 里没有这几个字段,加载时回落到 +\texttt{off}(见 \S 9.7 验证清单最后两行)。 + +\subsection{验证清单} + +\texttt{tests/integration/test\_attn\_res.py},14 项全过: + +\begin{center} +\begin{tabular}{ll} +\toprule +测试 & 守住的性质 \\ +\midrule +\texttt{default\_attnres\_is\_off} & 默认不改变任何既有行为,\texttt{mixer is None} \\ +\texttt{invalid\_attnres\_rejected} & 非法 mode / \texttt{block\_size=0} 构造期报错 \\ +\texttt{mixer\_kind\_and\_atomic\_count} & 原子层数 $= 2L$,块大小 $\times 2$ 换算 \\ +\texttt{auto\_block\_size\_targets\_eight\_blocks} & $L=93 \to 24$(K3 $S=12$ 个 block)\\ +\texttt{no\_duplicate\_parameter\_ids} & 借用不注册,参数 id / 名字均无重复 \\ +\texttt{off\_and\_block\_differ\_at\_same\_seed} & 同种子下确实换了计算图 \\ +\texttt{block\_two\_phase\_matches\_naive} & 两阶段 $\equiv$ naive,\texttt{atol 1e-5} \\ +\texttt{attnres\_is\_still\_causal} & 改末位 token 不影响前缀 logits \\ +\texttt{kda\_config\_block\_runs} & 纯 KDA 配置也能开 \\ +\texttt{attnres\_ckpt\_roundtrip} & 存取后逐位一致,config 字段保真 \\ +\texttt{old\_ckpt\_without\_attnres\_stays\_off} & 向后兼容 \\ +\texttt{attnres\_block\_overfits\_single\_batch} & 200 步 loss $< 0.5$,能训 \\ +\bottomrule +\end{tabular} +\end{center} + +\begin{warningbox}{混合精度} +\texttt{DepthResidual.forward}(naive 路径)显式 \texttt{.float()} 后再算 softmax +与加权和,最后 cast 回原 dtype;两阶段路径的 query 是 fp32、源保持原 dtype, +靠 einsum 的类型提升处理。bf16 autocast 下前向实测正常。 +和 \S1 的结论一致:\textbf{指数/累和一律不要放进 fp16}。 +\end{warningbox} + +\subsection{本章小结} + +AttnRes 把残差流从"等权累加"升级成"深度维 softmax 注意力": +每层用自己的 query 决定读此前哪些层的输出,打分在 RMS 归一化后做(方向)、 +加权和在原始张量上做(保留模长),query 零初始化让训练从等权平均起步。 +Full 版源数随深度线性增长,Block 版把块内退化成普通求和、只让块输出进入源列表, +把源数压到 $O(N/S)$;两阶段算法进一步把块间注意力批量化, +块内用 online softmax 增量合并,与 naive 实现数值等价。 +接入 \texttt{CausalLM} 时每个 DecoderBlock 拆成 attn / ffn 两个原子层, +\texttt{BorrowedSubLayer} 用普通 tuple 借用权重以免重复注册, +\texttt{attnres="off"} 保持旧路径不变。 diff --git a/notes/sections/sec-10.tex b/notes/sections/sec-10.tex new file mode 100644 index 0000000..db200dc --- /dev/null +++ b/notes/sections/sec-10.tex @@ -0,0 +1,157 @@ +% teach: +% gap: 读者会用 autograd 但不知道手写 KDA backward 的具体展开 +% takeaway: backward = 逆序遍历时间步, 每步求 dq/dk/dv/dg/dbeta + 累积 dS; GVA 反传 = view+sum +% jump: 为什么 dS_dec 要加 dS_acc 和 -k⊗dr 两项 +% omit: Triton backward 优化 + +\section{反向传播推导} + +\subsection{Forward 回顾} + +逐步写下 forward(省略 batch/head 下标): + +\begin{align} + S_{\mathrm{dec}} &= \exp(g_t) \odot S_{t-1} \tag{F1} \\ + p_t &= k_t^T S_{\mathrm{dec}} \tag{F2} \\ + r_t &= v_t - p_t \tag{F3} \\ + a_t &= \beta_t \cdot k_t \tag{F4} \\ + S_t &= S_{\mathrm{dec}} + a_t \otimes r_t \tag{F5} \\ + o_t &= q_t^T S_t \tag{F6} +\end{align} + +\subsection{反传公式(BPTT,$T \to 0$)} + +设 $dS_{\mathrm{acc}}$ 是从时间步 $t$ 开始累积到 $S_t$ 上的梯度。逆序遍历: + +\paragraph{Step 1: $o_t = q_t^T S_t$} + +\begin{align} + dS_{\mathrm{acc}} &\mathrel{+}= q_t \otimes do_t + & \xrightarrow{\texttt{einsum('bhk,bhv->bhkv')}} + & \quad \shape{B, HV, K, V} \\ + dq_t &= do_t^T S_t + & \xrightarrow{\texttt{einsum('bhv,bhkv->bhk')}} + & \quad \shape{B, HV, K} +\end{align} + +\paragraph{Step 2: $S_t = S_{\mathrm{dec}} + a_t \otimes r_t$} + +外积的反传:$d(a \otimes r) = (\cdot)$,分解为: + +\begin{align} + da_t &= \sum_v r_{t,v} \cdot dS_{\mathrm{acc},\cdot,v} + & \xrightarrow{\texttt{einsum('bhv,bhkv->bhk')}} + & \quad \shape{B, HV, K} \\ + dr_t &= \sum_k a_{t,k} \cdot dS_{\mathrm{acc},k,\cdot} + & \xrightarrow{\texttt{einsum('bhk,bhkv->bhv')}} + & \quad \shape{B, HV, V} +\end{align} + +\paragraph{Step 3: $a_t = \beta_t \cdot k_t$} + +\begin{align} + d\beta_t &= \sum_k k_{t,k} \cdot da_{t,k} + & \xrightarrow{\texttt{einsum('bhk,bhk->bh')}} + & \quad \shape{B, HV} \\ + dk_t^{(a)} &= \beta_t \cdot da_t + & & \quad \shape{B, HV, K} +\end{align} + +\paragraph{Step 4: $r_t = v_t - p_t = v_t - k_t^T S_{\mathrm{dec}}$} + +\begin{align} + dv_t &= dr_t & & \shape{B, HV, V} \\ + dk_t^{(r)} &= -S_{\mathrm{dec}}^T \cdot dr_t + & \xrightarrow{\texttt{einsum('bhv,bhkv->bhk')}} + & \quad \shape{B, HV, K} \\ + dS_{\mathrm{dec}}^{(r)} &= -k_t \otimes dr_t + & \xrightarrow{\texttt{einsum('bhv,bhk->bhkv')}} + & \quad \shape{B, HV, K, V} +\end{align} + +\paragraph{Step 5: 合并 $dS_{\mathrm{dec}}$ 并传递 $dg_t$, $dS_{t-1}$} + +\[ + dS_{\mathrm{dec}}^{\mathrm{total}} = dS_{\mathrm{acc}} + dS_{\mathrm{dec}}^{(r)} + = dS_{\mathrm{acc}} - k_t \otimes dr_t +\] + +因为 $S_{\mathrm{dec}} = \exp(g_t) \odot S_{t-1}$: + +\begin{align} + dg_t &= S_{\mathrm{dec}} \odot dS_{\mathrm{dec}}^{\mathrm{total}} + & \xrightarrow{\texttt{einsum('bhkv,bhkv->bhk')}} + & \quad \shape{B, HV, K} \\ + dS_{t-1} &= \exp(g_t) \odot dS_{\mathrm{dec}}^{\mathrm{total}} + & & \quad \shape{B, HV, K, V} +\end{align} + +\paragraph{Step 6: 合并 $dk_t$ 和 GVA 归约} + +\[ + dk_t = dk_t^{(a)} + dk_t^{(r)} + = \beta_t \cdot da_t - S_{\mathrm{dec}}^T \cdot dr_t +\] + +GVA 反传($H_V \to H$): +\[ + dq_H = dq_{H_V}.\texttt{view}(B, T, H, G, K).\texttt{sum}(\text{dim}=3) \cdot \mathrm{scale} +\] +\[ + dk_H = dk_{H_V}.\texttt{view}(B, T, H, G, K).\texttt{sum}(\text{dim}=3) +\] + +\subsection{代码对照} + +\begin{codemathtop}{ops/reference/recurrent.py — KDAFunction.backward} +\begin{lstlisting} +for t in range(T - 1, -1, -1): + q_t, k_t, b_t = q_ts[:,t], k_ts[:,t], b_ts[:,t] + S_dec, r_t, a_t = S_decs[:,t], r_ts[:,t], a_ts[:,t] + exp_g_t, do_t = exp_g_ts[:,t], do[:,t] + + # Step 1: o_t = q_t . S_t + S_t = S_dec + einsum('bhk,bhv->bhkv', a_t, r_t) + dS_acc += einsum('bhk,bhv->bhkv', q_t, do_t) + dq_e[:,t] = einsum('bhv,bhkv->bhk', do_t, S_t) + + # Step 2: outer product grads + da_t = einsum('bhv,bhkv->bhk', r_t, dS_acc) + dr_t = einsum('bhk,bhkv->bhv', a_t, dS_acc) + + # Step 3: a_t = beta_t * k_t + dbeta[:,t] = einsum('bhk,bhk->bh', k_t, da_t) + dk_t_a = b_t.unsqueeze(-1) * da_t + + # Step 4: r_t = v_t - k_t . S_dec + dv[:,t] = dr_t + dS_dec_from_r = -einsum('bhv,bhk->bhkv', dr_t, k_t) + dk_t_r = -einsum('bhv,bhkv->bhk', dr_t, S_dec) + + # Step 5: S_dec = exp(g) * S_{t-1} + dS_dec_total = dS_acc + dS_dec_from_r + dk_e[:,t] = dk_t_a + dk_t_r + dg[:,t] = einsum('bhkv,bhkv->bhk', S_dec, dS_dec_total) + dS_acc = exp_g_t.unsqueeze(-1) * dS_dec_total + +# Step 6: GVA reduce +dq_H = dq_e.view(B,T,H,G,K).sum(dim=3) * scale +dk_H = dk_e.view(B,T,H,G,K).sum(dim=3) +\end{lstlisting} +\end{codemathtop} + +\begin{warningbox}{$dS_{\mathrm{dec}}^{\mathrm{total}}$ 为什么包含两项?} +$S_t = S_{\mathrm{dec}} + a_t \otimes r_t$,$S_{\mathrm{dec}}$ 同时参与了: +\begin{enumerate}[nosep] +\item 直接传递到 $dS_{\mathrm{acc}}$(作为 $S_t$ 的一部分被读出) +\item 通过 $r_t = v_t - k_t \cdot S_{\mathrm{dec}}$ 参与 delta rule +\end{enumerate} +所以 $dS_{\mathrm{dec}}^{\mathrm{total}} = dS_{\mathrm{acc}} + dS_{\mathrm{dec}}^{(r)}$, +两条路径的梯度要\textbf{加}起来(chain rule 分叉处求和)。 +\end{warningbox} + +\subsection{本章小结} + +KDA backward 是 BPTT 展开:逆序遍历时间步,每步 6 个 einsum + 一次 $dS$ 累积更新。 +GVA 反传在最后做 \texttt{view+sum}。手写 backward 的关键是正确处理 +$dS_{\mathrm{dec}}$ 的两条梯度路径(直接传递 + 通过 $r_t$ 的 delta rule 路径)。 diff --git a/notes/sections/sec-11.tex b/notes/sections/sec-11.tex new file mode 100644 index 0000000..452233e --- /dev/null +++ b/notes/sections/sec-11.tex @@ -0,0 +1,183 @@ +% teach: +% gap: none — this is a reference appendix +% takeaway: 一表查所有符号 +% jump: none +% omit: none + +\section{符号表} + +\subsection{形状参数} + +\begin{center} +\begin{tabular}{lll} +\toprule +符号 & 含义 & 典型值 (toy) \\ +\midrule +$B$ & batch size & 2--4 \\ +$T$ & 序列长度 & 128--2048 \\ +$D$ & hidden\_size & 64 / 256 \\ +$H$ & query/key 头数 & 4 / 8 \\ +$H_V$ & value 头数(GVA) & $G \cdot H$ \\ +$G$ & GVA 组数 & $H_V / H$ \\ +$K$ & key/query 头维度 & 16 \\ +$V$ & value 头维度($= K$) & 16 \\ +$C$ & chunk\_size & 16 / 64 \\ +$r$ & KV latent rank (MLA) & 32 \\ +$d_q$ & MLA query head dim & 16 \\ +$d_v$ & MLA value head dim & 16 \\ +$\ell$ & MoE latent width ($= D/2$) & 128 \\ +$n_r$ & routed 专家数 & 16 \\ +$k$ & Top-$k$ & 2 \\ +$n_s$ & shared 专家数 & 2 \\ +$d_{\mathrm{ff}}$ & 专家中间维度 & 96 \\ +$N$ & AttnRes 原子层数 ($= 2L$) & 8 \\ +$S$ & AttnRes 块大小(原子层) & 2--24 \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{KDA 变量} + +\begin{center} +\begin{tabular}{llp{7cm}} +\toprule +符号 & 形状 & 含义 \\ +\midrule +$q_t$ & \shape{B, HV, K} & query(已 GVA 展开 + scale) \\ +$k_t$ & \shape{B, HV, K} & key(已 GVA 展开) \\ +$v_t$ & \shape{B, HV, V} & value \\ +$g_t$ & \shape{B, HV, K} & gate(log-space 衰减,逐维逐头) \\ +$\beta_t$ & \shape{B, HV} & 写入强度 \\ +$S_t$ & \shape{B, HV, K, V} & KV 状态矩阵 \\ +$S_{\mathrm{dec}}$ & \shape{B, HV, K, V} & 衰减后的状态 \\ +$p_t$ & \shape{B, HV, V} & 旧状态对 $k_t$ 的预测 \\ +$r_t$ & \shape{B, HV, V} & delta rule 残差 = $v_t - p_t$ \\ +$a_t$ & \shape{B, HV, K} & 写入向量 = $\beta_t \cdot k_t$ \\ +$o_t$ & \shape{B, HV, V} & 读出 = $q_t \cdot S_t$ \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{Gate 变量} + +\begin{center} +\begin{tabular}{llp{6cm}} +\toprule +符号 & 形状 & 含义 \\ +\midrule +$g_{\mathrm{raw}}$ & \shape{B, T, HV, K} & gate 投影原始输出 \\ +$A_{\log}$ & \shape{HV} & head-wise 衰减参数 (log-space) \\ +$\Delta_b$ & \shape{HV, K} & per-dim gate bias \\ +$\mathrm{rate}$ & \shape{HV, 1} & $\exp(A_{\log})$ \\ +$\mathrm{input}$ & \shape{B, T, HV, K} & $g_{\mathrm{raw}} + \Delta_b$ \\ +$L$ & 标量 & lower\_bound ($-5.0$) \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{MLA 变量} + +\begin{center} +\begin{tabular}{llp{6cm}} +\toprule +符号 & 形状 & 含义 \\ +\midrule +$c$ & \shape{B, T, r} & KV latent(推理时缓存这个) \\ +$q$ & \shape{B, T, H, d_q} & query(低秩路径输出) \\ +$W_{UK}$ & \shape{H, d_q, r} & key 解压矩阵(吸收进 $q$) \\ +$W_{UV}$ & \shape{H, d_v, r} & value 解压矩阵 \\ +$q_{\mathrm{abs}}$ & \shape{B, T, H, r} & 吸收后的 query \\ +score & \shape{B, H, T, T} & $q_{\mathrm{abs}} \cdot c^T$ \\ +attn & \shape{B, H, T, T} & causal softmax \\ +$\tilde{o}_{\mathrm{lat}}$ & \shape{B, H, T, r} & latent 加权输出 \\ +$\tilde{o}$ & \shape{B, H, T, d_v} & 解压后的输出 \\ +gate & \shape{B, T, H \cdot d_v} & $\sigma(W_g x)$ \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{LatentMoE 变量} + +\begin{center} +\begin{tabular}{llp{6cm}} +\toprule +符号 & 形状 & 含义 \\ +\midrule +$x$ & \shape{B, T, D} & 输入 \\ +$z$ & \shape{B, T, \ell} & latent ($\ell = D/2$) \\ +logits & \shape{B, T, n_r} & router logits \\ +ids & \shape{B, T, k} & Top-$k$ 专家索引 \\ +probs & \shape{B, T, k} & softmax 权重 \\ +$u$ & \shape{B, T, \ell} & routed 加权输出 \\ +$s$ & \shape{B, T, D} & shared 专家求和 \\ +$y$ & \shape{B, T, D} & $s + W_\uparrow \mathrm{RMSNorm}(u)$ \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{AttnRes 变量} + +\begin{center} +\begin{tabular}{llp{6.4cm}} +\toprule +符号 & 形状 & 含义 \\ +\midrule +$v_i$ & \shape{B, T, D} & 第 $i$ 个源($v_0 =$ embedding 输出) \\ +$w_l$ & \shape{D} & 第 $l$ 层的 depth query(零初始化) \\ +$\gamma_l$ & \shape{D} & DepthResidual 的 RMSNorm gain \\ +$\tilde{w}_l$ & \shape{D} & 折叠后的 query $= w_l \odot \gamma_l$ \\ +$s_{l,i}$ & \shape{n, B, T} & 深度打分 $= \tilde{w}_l^{\top}\mathrm{RMS}(v_i)$ \\ +$\alpha_{l,i}$ & \shape{n, B, T} & 深度维 softmax 权重 \\ +$h_l$ & \shape{B, T, D} & 第 $l$ 层的输入 $= \sum_i \alpha_{l,i} v_i$ \\ +$b_j$ & \shape{B, T, D} & 第 $j$ 个块的输出(Block 版的源) \\ +$p$ & \shape{B, T, D} & 块内 running partial \\ +$m, n, d$ & \shape{B, T} / \shape{B,T,D} / \shape{B,T} & online softmax 三元组 \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{Einsum 速查} + +\begin{center} +\small +\begin{tabular}{p{6cm}lp{3.5cm}} +\toprule +操作 & einsum & 结果形状 \\ +\midrule +key 查状态 & \texttt{'bhk,bhkv->bhv'} & $p_t$ \shape{B,HV,V} \\ +外积写入 & \texttt{'bhk,bhv->bhkv'} & $a_t \otimes r_t$ \shape{B,HV,K,V} \\ +读出 & \texttt{'bhk,bhkv->bhv'} & $o_t$ \shape{B,HV,V} \\ +MLA 吸收 $W_{UK}$ & \texttt{'bthd,hdj->bthj'} & $q_{\mathrm{abs}}$ \shape{B,T,H,r} \\ +MLA 打分 & \texttt{'bthj,bsj->bhts'} & score \shape{B,H,T,T} \\ +MLA latent 加权 & \texttt{'bhts,bsj->bhtj'} & $\tilde{o}_{\mathrm{lat}}$ \shape{B,H,T,r} \\ +MLA 解压 & \texttt{'bhtj,hvj->bhtv'} & $\tilde{o}$ \shape{B,H,T,d_v} \\ +AttnRes 深度打分 & \texttt{'d,nbtd->nbt'} & $s_{l,i}$ \shape{n,B,T} \\ +AttnRes 深度加权和 & \texttt{'nbt,nbtd->btd'} & $h_l$ \shape{B,T,D} \\ +AttnRes 批量打分(inter) & \texttt{'qd,nbtd->qnbt'} & logits \shape{S,n,B,T} \\ +\bottomrule +\end{tabular} +\end{center} + +\subsection{总结与延伸} + +\subsubsection*{核心要点回顾} + +\begin{enumerate}[nosep] +\item \textbf{KDA} = delta rule 状态更新 + gate 衰减,线性复杂度 +\item \textbf{分块} = chunk 内下三角解 + chunk 间状态递推,等价于 naive recurrent +\item \textbf{GVA} = $H_V = G \cdot H$,forward repeat\_interleave / backward view+sum +\item \textbf{MLA} = 低秩 latent + 矩阵吸收,KV cache 从 $2Hd$ 降到 $r$ +\item \textbf{LatentMoE} = shared 全宽 + routed 半宽 latent + SiTU-GLU 防溢出 +\item \textbf{K3 Hybrid} = 3 KDA + 1 MLA,KDA 提供位置感知 +\item \textbf{AttnRes} = 深度维 softmax 残差,Block 版把源数压到 $O(N/S)$, + 两阶段 = inter 批量 + intra online-softmax 合并 +\end{enumerate} + +\subsubsection*{未完成项} + +\begin{itemize}[nosep] +\item L5 — 项目内自研 fused gate Triton kernel +\item L6 — recurrent decode cache(推理加速) +\item AttnRes 与 recurrent decode 的组合(增量解码时的深度源缓存) +\item AttnRes 开 / 关的收敛质量对比实验(目前只验证了等价性与可训练性) +\end{itemize} diff --git a/notes/verify_gref_overflow.py b/notes/verify_gref_overflow.py new file mode 100644 index 0000000..59949d6 --- /dev/null +++ b/notes/verify_gref_overflow.py @@ -0,0 +1,106 @@ +"""验证 reference `_decayed_dot` 的 g_ref 因子在真实训练门控量级下是否溢出。 + +_decayed_dot 把 exp(g_i-g_j) 拆成 (x*exp(g_i-g_ref)) @ (k*exp(g_ref-g_j)), +g_ref = g_cumsum[..., 0, :]。因 g<0,最大中间因子为 + exp(g_ref - g_{C-1}) = exp(sum_{t=1}^{C-1} |g_t|) +溢出阈值: sum|g| > ln(3.39e38)=88.7 (fp32/bf16) ; > ln(65504)=11.09 (fp16) + +门控: safe_gate, g = lower_bound * sigmoid(exp(A_log) * (g_raw + dt_bias)) + lower_bound=-5 => |g| ∈ (0, 5) 逐步硬上界 +""" +import math +import sys + +import torch + +import kda.ops.api as kapi +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig +from kda.models.k3_config import K3Config + +dev = "cuda" +LIM = {"fp32/bf16": math.log(3.39e38), "fp16": math.log(65504.0)} + +# ---------- 1) 解析上界: 每个 chunk_size 需要的平均 |g| ---------- +print("=== 解析: 溢出所需的 chunk 内平均 |g|/step (硬上界 |g|<5) ===") +print(f"{'C':>4} | {'fp32/bf16 阈值':>15} | {'可达?':>6} | {'fp16 阈值':>10} | {'可达?':>6}") +for C in (16, 32, 64, 128): + a, b = LIM["fp32/bf16"] / (C - 1), LIM["fp16"] / (C - 1) + print(f"{C:4d} | {a:15.2f} | {'YES' if a < 5 else 'no':>6} | " + f"{b:10.2f} | {'YES' if b < 5 else 'no':>6}") + +# ---------- 2) 实测: 真实 checkpoint 上的 chunk 内 sum|g| ---------- +_orig = kapi.naive_chunk_kda # api.py 在 import 时绑定, 必须 patch 这里 +stats = [] + + +def max_sum_abs_g(g, C): + """chunk 内最大 sum_{t=1..C-1}|g_t| (= max exp(g_ref-g_j) 的指数)。""" + T = g.shape[1] + gg = g[:, : T - T % C].reshape(g.shape[0], -1, C, *g.shape[2:]) + return gg[:, :, 1:].abs().sum(dim=2).max().item() + + +def patched(q, k, v, g, beta, **kw): + stats.append(g.detach().float()) + return _orig(q, k, v, g, beta, **kw) + + +kapi.naive_chunk_kda = patched + + +def build(cfg_dict): + """checkpoint 的 config 是 dict, 按字段集合判断是 K3 还是纯 KDA。""" + cls = K3Config if "moe_latent_size" in cfg_dict else KDAConfig + return cls(**{k: v for k, v in cfg_dict.items() + if k in cls.__dataclass_fields__}) + + +for path in ("ckpts/k3_wiki.pt", "ckpts/kda_toy.pt"): + ck = torch.load(path, map_location="cpu", weights_only=False) + cfg = build(ck["config"]) + model = CausalLM(cfg).to(dev).eval() + # 旧 checkpoint 用 moe/moe_norm 命名, 现已重命名为 ffn/ffn_norm + sd = {k.replace(".moe_norm.", ".ffn_norm.").replace(".moe.", ".ffn.") + .replace(".mlp_norm.", ".ffn_norm.").replace(".mlp.", ".ffn."): v + for k, v in ck["model_state"].items()} + missing, unexpected = model.load_state_dict(sd, strict=False) + assert not missing and not unexpected, (path, missing[:5], unexpected[:5]) + + stats.clear() + ids = torch.randint(0, cfg.vocab_size, (2, 512), device=dev) + with torch.no_grad(): + model(ids) + if not stats: + print(f"\n{path}: 未走 reference 路径 (backend={cfg.kda_backend})") + continue + gs = list(stats) + print(f"\n=== {path} (训练用 C={cfg.chunk_size}, KDA 层数 {len(gs)}) ===") + print(f" |g| mean/step: {sum(g.abs().mean().item() for g in gs)/len(gs):.4f} " + f"|g| max/step: {max(g.abs().max().item() for g in gs):.4f} " + f"(硬上界 {abs(cfg.lower_bound)})") + print(f" {'C':>4} | {'max chunk sum|g|':>16} | {'max exp 因子':>12} | " + f"{'fp32/bf16':>18} | {'fp16':>12}") + for C in (16, 32, 64, 128): + mg = max(max_sum_abs_g(g, C) for g in gs) + fac = math.exp(mg) if mg < 709 else float("inf") + f32 = f"{LIM['fp32/bf16']/mg:.2f}x OK" if mg < LIM["fp32/bf16"] else "OVERFLOW" + f16 = f"{LIM['fp16']/mg:.2f}x OK" if mg < LIM["fp16"] else "OVERFLOW" + mark = " <- 训练配置" if C == cfg.chunk_size else "" + print(f" {C:4d} | {mg:16.2f} | {fac:12.3e} | {f32:>18} | {f16:>12}{mark}") + +# ---------- 3) 随机初始化模型 (未训练) 同样测一遍 ---------- +for preset in ("toy", "0.5b"): + cfg = K3Config.preset(preset) + cfg.vocab_size = 2048 + cfg.kda_backend = "reference" + model = CausalLM(cfg).to(dev).eval() + stats.clear() + with torch.no_grad(): + model(torch.randint(0, cfg.vocab_size, (2, 512), device=dev)) + if stats: + gs = list(stats) + mg = max(max_sum_abs_g(g, cfg.chunk_size) for g in gs) + print(f"\n=== init preset={preset} (C={cfg.chunk_size}) ===") + print(f" |g| mean/step {sum(g.abs().mean().item() for g in gs)/len(gs):.4f} " + f"max chunk sum|g| {mg:.3f} fp32 余量 {LIM['fp32/bf16']/max(mg,1e-9):.0f}x") diff --git a/notes/verify_l2norm_stability.py b/notes/verify_l2norm_stability.py new file mode 100644 index 0000000..38ba085 --- /dev/null +++ b/notes/verify_l2norm_stability.py @@ -0,0 +1,83 @@ +"""验证: 不做 L2-norm 时 KDA 的爆炸是"数学上真实"还是"浮点误差"。 + +判据: 逐步递推 (naive_kda_fwd, 无三角求解) 在 float64 下的输出。 + - 若 fp64 递推也 ~1e32 => 爆炸是 KDA 递推本身的数学性质 + - 若 fp64 递推 O(1) 而 chunkwise 爆炸 => 是 solve 的浮点失效 +""" +import torch +import torch.nn.functional as F + +from kda.ops.reference.chunkwise import naive_chunk_kda +from kda.ops.reference.recurrent import naive_kda_fwd + +torch.manual_seed(0) +dev = "cuda" +B, T, H, K, V = 1, 64, 1, 16, 32 +C = 64 + + +def make(norm: bool, dtype): + g0 = torch.Generator(device=dev).manual_seed(0) + q = torch.randn(B, T, H, K, device=dev, dtype=dtype, generator=g0) + k = torch.randn(B, T, H, K, device=dev, dtype=dtype, generator=g0) + v = torch.randn(B, T, H, V, device=dev, dtype=dtype, generator=g0) + # 训练里 g = -A.exp()*softplus(...) < 0,量级温和 + g = -F.softplus(torch.randn(B, T, H, K, device=dev, dtype=dtype, generator=g0)) * 0.1 + beta = torch.rand(B, T, H, device=dev, dtype=dtype, generator=g0) + if norm: + q, k = F.normalize(q, dim=-1), F.normalize(k, dim=-1) + return q, k, v, g, beta + + +def amax(x): + return x.abs().max().item() + + +print(f"{'norm':>5} | {'‖k‖':>6} | {'rec fp64':>10} | {'rec fp32':>10} | " + f"{'chunk fp64':>10} | {'chunk fp32':>10} | {'rel(chunk64,rec64)':>18}") +print("-" * 100) + +for norm in (True, False): + row = {} + for dt in (torch.float64, torch.float32): + q, k, v, g, beta = make(norm, dt) + o_rec, _ = naive_kda_fwd(q, k, v, g, beta) + o_chk, _ = naive_chunk_kda(q, k, v, g, beta, chunk_size=C) + row[dt] = (amax(o_rec), amax(o_chk), o_chk, o_rec) + knorm = make(norm, torch.float64)[1].norm(dim=-1).mean().item() + r64, c64, oc64, or64 = row[torch.float64] + r32, c32, oc32, _ = row[torch.float32] + rel = ((oc64 - or64).abs().max() / (or64.abs().max() + 1e-30)).item() + print(f"{str(norm):>5} | {knorm:6.2f} | {r64:10.3e} | {r32:10.3e} | " + f"{c64:10.3e} | {c32:10.3e} | {rel:18.3e}") + +# ---- M 的结构诊断 ---- +print("\n=== M = I + tril(A_kk*beta, -1) 诊断 (float64) ===") +print(f"{'norm':>5} | {'max|N|':>9} | {'‖M⁻¹‖∞':>10} | {'cond2(M)':>10} | {'|S_final|max':>12}") +for norm in (True, False): + q, k, v, g, beta = make(norm, torch.float64) + gc = g.cumsum(dim=1)[0, :, 0] # [T,K] + kk = k[0, :, 0] # [T,K] + gref = gc[:1] + A = (kk * (gc - gref).exp()) @ (kk * (gref - gc).exp()).T + N = (A * beta[0, :, 0][None, :]).tril(-1) + M = torch.eye(T, dtype=torch.float64, device=dev) + N + Minv = torch.linalg.inv(M) + _, Sf = naive_kda_fwd(q, k, v, g, beta, output_final_state=True) + print(f"{str(norm):>5} | {amax(N):9.3e} | {Minv.abs().sum(1).max():10.3e} | " + f"{torch.linalg.cond(M).item():10.3e} | {amax(Sf):12.3e}") + +# ---- ‖M⁻¹‖ 随 chunk 长度的增长 ---- +print("\n=== ‖M⁻¹‖∞ vs chunk 长度 C (float64) ===") +for norm in (True, False): + q, k, v, g, beta = make(norm, torch.float64) + gc = g.cumsum(dim=1)[0, :, 0] + kk = k[0, :, 0] + out = [] + for C_ in (4, 8, 16, 32, 64): + gs, ks, bs = gc[:C_], kk[:C_], beta[0, :C_, 0] + gref = gs[:1] + A = (ks * (gs - gref).exp()) @ (ks * (gref - gs).exp()).T + M = torch.eye(C_, dtype=torch.float64, device=dev) + (A * bs[None, :]).tril(-1) + out.append(f"C={C_:2d}:{torch.linalg.inv(M).abs().sum(1).max():.2e}") + print(f" norm={str(norm):>5} " + " ".join(out)) diff --git a/notes/verify_severity_sweep.py b/notes/verify_severity_sweep.py new file mode 100644 index 0000000..0013d0b --- /dev/null +++ b/notes/verify_severity_sweep.py @@ -0,0 +1,72 @@ +"""严重度扫描: 把"数学爆炸"和"两条路径分道扬镳"分开测。 + +对每个 k 缩放系数 s: + rec64 = 逐步递推 float64 (无三角求解) -> 数学真值 + chk64 = chunkwise float64 (全局 solve) + chk32 = chunkwise float32 + tri32 = FLA/vendored triton 16x16 分块路径 (float32 in/out) +""" +import torch +import torch.nn.functional as F + +from kda.ops.reference.chunkwise import naive_chunk_kda +from kda.ops.reference.recurrent import naive_kda_fwd + +dev = "cuda" +B, T, H, K, V = 1, 64, 1, 16, 32 + +try: + from kda.ops.triton.chunk import chunk_kda as triton_chunk_kda +except Exception as e: # pragma: no cover + triton_chunk_kda = None + print("triton backend unavailable:", e) + + +def make(scale_k, dtype): + gen = torch.Generator(device=dev).manual_seed(0) + q = torch.randn(B, T, H, K, device=dev, dtype=dtype, generator=gen) + k = torch.randn(B, T, H, K, device=dev, dtype=dtype, generator=gen) + v = torch.randn(B, T, H, V, device=dev, dtype=dtype, generator=gen) + g = -F.softplus(torch.randn(B, T, H, K, device=dev, dtype=dtype, generator=gen)) * 0.1 + beta = torch.rand(B, T, H, device=dev, dtype=dtype, generator=gen) + return q, F.normalize(k, dim=-1) * scale_k, v, g, beta + + +def amax(x): + return x.abs().max().item() + + +hdr = (f"{'‖k‖':>6} | {'max|N|':>9} | {'‖M⁻¹‖∞':>10} | {'rec fp64':>10} | " + f"{'chk fp64':>10} | {'chk fp32':>10} | {'tri fp32':>10} | " + f"{'rel(chk64/rec64)':>16} | {'rel(chk32/chk64)':>16} | {'rel(tri32/chk32)':>16}") +print(hdr) +print("-" * len(hdr)) + +for s in (1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0): + q, k, v, g, beta = make(s, torch.float64) + o_rec, _ = naive_kda_fwd(q, k, v, g, beta) + o_c64, _ = naive_chunk_kda(q, k, v, g, beta, chunk_size=64) + + q3, k3, v3, g3, b3 = [x.float() for x in (q, k, v, g, beta)] + o_c32, _ = naive_chunk_kda(q3, k3, v3, g3, b3, chunk_size=64) + if triton_chunk_kda is not None: + o_t32, _ = triton_chunk_kda(q3, k3, v3, g3, b3, chunk_size=64) + o_t32 = o_t32.double() + else: + o_t32 = torch.full_like(o_c64, float("nan")) + + gc = g.cumsum(dim=1)[0, :, 0] + kk = k[0, :, 0] + gref = gc[:1] + A = (kk * (gc - gref).exp()) @ (kk * (gref - gc).exp()).T + M = torch.eye(T, dtype=torch.float64, device=dev) + (A * beta[0, :, 0][None, :]).tril(-1) + minv = torch.linalg.inv(M).abs().sum(1).max().item() + + def rel(a, b): + return ((a - b).abs().max() / (b.abs().max() + 1e-300)).item() + + print(f"{k.norm(dim=-1).mean():6.2f} | {amax((A * beta[0, :, 0][None, :]).tril(-1)):9.2e} | " + f"{minv:10.2e} | {amax(o_rec):10.2e} | {amax(o_c64):10.2e} | " + f"{amax(o_c32):10.2e} | {amax(o_t32):10.2e} | " + f"{rel(o_c64, o_rec):16.2e} | {rel(o_c32.double(), o_c64):16.2e} | " + f"{rel(o_t32, o_c32.double()):16.2e}") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..afe1d05 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,43 @@ +[project] +name = "kda" +version = "0.0.1" +description = "Hand-written KDA implementation from naive recurrent to fused Triton + training + inference" +requires-python = ">=3.10" + +# kda 是独立 uv 项目 (自带 uv.lock + .venv), 自声明运行依赖, 不依赖仓库根环境 +dependencies = [ + "torch>=2.9.0", # uv 解析最新满足版 (含 CUDA 构建); 与根环境 2.9.0+cu128 仅下限一致 + "einops>=0.7.0", # kda/ops/reference/chunkwise.py 的 rearrange + "packaging>=23.0", # vendored FLA utils version checks + "sentencepiece>=0.2.0", # toy SentencePiece + "datasets>=3.0.0", # 中文 wiki 语料加载 + "transformers>=4.51.0", # Qwen3 tokenizer for 0.5b preset + "swanlab>=0.9.7", +] + +# 训练机 / Docker 镜像: uv sync --extra train +[project.optional-dependencies] +train = [ + "swanlab>=0.6.0", + "sacrebleu>=2.4.0", + "langdetect>=1.0.9", +] + +# uv run / uv sync 默认安装 dev group +[dependency-groups] +dev = [ + "pytest>=7.0", + "torchlens>=2.34", # 计算图展开集成测试; 未装时测试模块自动 skip + "tensorlens>=0.0.3", # Flask viewer 集成测试; 未装时测试模块自动 skip +] + +[tool.uv] +# 公共入口: `from kda import CausalLM, KDAConfig, K3Config, chunk_kda` +package = true + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["kda"] diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000..6b6ad4e --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,17 @@ +{ + "include": ["kda", "tests", "train.py", "train_k3.py", "train_sft.py"], + "exclude": ["**/__pycache__", ".venv", "ckpts", ".pytest_cache"], + "venvPath": ".", + "venv": ".venv", + "pythonVersion": "3.12", + "pythonPlatform": "Linux", + "typeCheckingMode": "standard", + "reportUnknownMemberType": false, + "reportUnknownArgumentType": false, + "reportUnknownVariableType": false, + "reportUnknownParameterType": false, + "reportUnknownLambdaType": false, + "reportMissingTypeStubs": false, + "reportMissingModuleSource": "none", + "reportAny": false +} diff --git a/scripts/container_help.py b/scripts/container_help.py new file mode 100644 index 0000000..6e04ecf --- /dev/null +++ b/scripts/container_help.py @@ -0,0 +1,43 @@ +"""Default container command: print usage and refuse to start a silent toy run.""" + +from __future__ import annotations + +HELP = """kda image: one GPU runtime for train / monitor / eval. + +Do not docker run without a command — toy overfit is not the default. + +Volumes (host → container): + ./ckpts → /workspace/kda/ckpts + pretrain corpus → /data/pretrain (ro) + SFT bitext → /data/sft (ro) + frozen eval → /data/eval (ro) + HF tokenizer cache → /cache/huggingface + SWANLAB_API_KEY via -e (never COPY into the image) + +Examples: + docker run --rm --gpus all kda: python train.py + docker run --rm --gpus all -e SWANLAB_API_KEY \\ + -v "$PWD/ckpts:/workspace/kda/ckpts" \\ + kda: python train_k3.py --preset toy --attnres off + docker run --rm --gpus all kda: swanlab ping + docker run --rm --gpus all \\ + -v "$PWD/ckpts:/workspace/kda/ckpts" -v "$PWD/data/eval:/data/eval:ro" \\ + kda: python -m kda.training.eval_mt \\ + --ckpt /workspace/kda/ckpts/k3_wiki.pt \\ + --src /data/eval/zh2en.src.txt --ref /data/eval/zh2en.ref.txt \\ + --target-lang en + docker run --rm --gpus all \\ + -v "$PWD/ckpts:/workspace/kda/ckpts" -v "$PWD/data/sft:/data/sft:ro" \\ + kda: python train_sft.py --ckpt /workspace/kda/ckpts/k3_wiki.pt \\ + --data /data/sft/toy.jsonl + docker run --rm --gpus all kda: python -m pytest -q +""" + + +def main() -> None: + print(HELP) + raise SystemExit(2) + + +if __name__ == "__main__": + main() diff --git a/scripts/export_flores.py b/scripts/export_flores.py new file mode 100644 index 0000000..376a2d6 --- /dev/null +++ b/scripts/export_flores.py @@ -0,0 +1,61 @@ +"""Export FLORES-200 zh↔en into line-aligned src/ref files for eval_mt. + +Corpus is not committed. Typical: + + uv run python scripts/export_flores.py --out data/eval +""" + +from __future__ import annotations + +import argparse +from pathlib import Path + + +def _write(path: Path, lines: list[str]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--out", default="data/eval") + p.add_argument("--split", default="devtest", choices=["dev", "devtest"]) + args = p.parse_args() + + from datasets import load_dataset + + ds = None + err: Exception | None = None + for config in ("eng_Latn-zho_Hans", "default"): + try: + ds = load_dataset("facebook/flores", config, split=args.split) + break + except Exception as exc: # noqa: BLE001 — try the next config name + err = exc + if ds is None: + raise SystemExit(f"could not load facebook/flores ({err})") + + cols = set(ds.column_names) + en_key = next( + (c for c in ("sentence_eng_Latn", "eng_Latn", "sentence_en") if c in cols), + None, + ) + zh_key = next( + (c for c in ("sentence_zho_Hans", "zho_Hans", "sentence_zh") if c in cols), + None, + ) + if en_key is None or zh_key is None: + raise SystemExit(f"FLORES columns not found: {sorted(cols)}") + + en = [row[en_key].strip() for row in ds] + zh = [row[zh_key].strip() for row in ds] + out = Path(args.out) + _write(out / "flores.zh2en.src.txt", zh) + _write(out / "flores.zh2en.ref.txt", en) + _write(out / "flores.en2zh.src.txt", en) + _write(out / "flores.en2zh.ref.txt", zh) + print(f"wrote {len(zh)} pairs under {out}/flores.*.txt") + + +if __name__ == "__main__": + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..c5bab3c --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# allow running tests from package root diff --git a/tests/correctness/__init__.py b/tests/correctness/__init__.py new file mode 100644 index 0000000..b6827a0 --- /dev/null +++ b/tests/correctness/__init__.py @@ -0,0 +1 @@ +"""Reference correctness tests.""" diff --git a/tests/correctness/test_api.py b/tests/correctness/test_api.py new file mode 100644 index 0000000..431c77d --- /dev/null +++ b/tests/correctness/test_api.py @@ -0,0 +1,107 @@ +"""Public backend selection must be explicit and reproducible.""" +import pytest +import torch + +from kda.models.config import KDAConfig +from kda.ops.api import chunk_kda + + +def _inputs(): + torch.manual_seed(41) + B, T, H, HV, K, V = 1, 4, 1, 2, 2, 2 + return ( + torch.randn(B, T, H, K), + torch.randn(B, T, H, K), + torch.randn(B, T, HV, V), + -torch.rand(B, T, HV, K), + torch.rand(B, T, HV), + ) + + +def test_default_config_enables_kernel_side_transforms(): + config = KDAConfig() + assert config.use_gate_in_kernel + assert config.use_qk_l2norm_in_kernel + assert config.use_beta_sigmoid_in_kernel + assert config.lower_bound == -5.0 + + +def test_default_backend_is_local_reference(): + inputs = _inputs() + default_output, _ = chunk_kda(*inputs, chunk_size=4, use_qk_l2norm_in_kernel=True) + reference_output, _ = chunk_kda(*inputs, chunk_size=4, backend="reference", use_qk_l2norm_in_kernel=True) + torch.testing.assert_close(default_output, reference_output) + assert KDAConfig().kda_backend == "reference" + + +def test_auto_is_deprecated_reference_alias(): + inputs = _inputs() + with pytest.warns(DeprecationWarning, match="selects the local reference backend"): + auto_output, _ = chunk_kda(*inputs, chunk_size=4, backend="auto", use_qk_l2norm_in_kernel=True) + reference_output, _ = chunk_kda(*inputs, chunk_size=4, backend="reference", use_qk_l2norm_in_kernel=True) + torch.testing.assert_close(auto_output, reference_output) + + +@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required") +def test_triton_backend_matches_reference(): + torch.manual_seed(41) + B, T, H, HV, K, V = 2, 64, 4, 8, 32, 32 + device = "cuda" + q = torch.randn(B, T, H, K, device=device) + k = torch.randn(B, T, H, K, device=device) + v = torch.randn(B, T, HV, V, device=device) + g = -torch.rand(B, T, HV, K, device=device) + beta = torch.rand(B, T, HV, device=device) + triton_out, _ = chunk_kda( + q, k, v, g, beta, chunk_size=64, backend="triton", use_qk_l2norm_in_kernel=True + ) + reference_out, _ = chunk_kda( + q, k, v, g, beta, chunk_size=64, backend="reference", use_qk_l2norm_in_kernel=True + ) + torch.testing.assert_close( + triton_out.float(), reference_out.float(), atol=2e-3, rtol=2e-3 + ) + + +@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required") +def test_unnormalized_parity_is_not_meaningful(): + """对拍必须用与训练一致的 q/k L2-norm (守卫测试). + + M = I + tril(A_kk*beta, -1) 是单位下三角, 行列式恒为 1、特征值全为 1, + 不存在"失去对角占优"一说. 真正的机制是 M = I + N 中 N 严格下三角 (幂零), + 故 M^-1 = sum_{m1, 精确解本身就以 ‖k‖^chunk_size 增长 (实测输出 1e31 量级 + 乃至 inf). 这不是某个后端的缺陷: fp64 逐步递推 (完全不含三角求解) 与分块 + 形式吻合到 8e-15, 说明爆炸就是该递推的真值. 两个后端的相对误差其实很接近 + (1.3e-3 → 2.6e-2), 但绝对差随输出量级一起走, 任何一致性容差都必然失败. + """ + torch.manual_seed(41) + B, T, H, HV, K, V = 2, 64, 4, 8, 32, 32 + device = "cuda" + q = torch.randn(B, T, H, K, device=device) # 故意不归一化 + k = torch.randn(B, T, H, K, device=device) + v = torch.randn(B, T, HV, V, device=device) + g = -torch.rand(B, T, HV, K, device=device) + beta = torch.rand(B, T, HV, device=device) + + with pytest.warns(RuntimeWarning, match="use_qk_l2norm_in_kernel=False"): + triton_out, _ = chunk_kda(q, k, v, g, beta, chunk_size=64, backend="triton") + reference_out, _ = chunk_kda(q, k, v, g, beta, chunk_size=64, backend="reference") + + diff = (triton_out.float() - reference_out.float()).abs().max() + assert not torch.isfinite(diff).item() or diff.item() > 1e-2, ( + f"unnormalised parity diff {diff.item():.3e} should be pathological" + ) + + +def test_triton_backend_does_not_import_upstream_fla(): + import sys + + for name in list(sys.modules): + if name == "fla" or name.startswith("fla."): + sys.modules.pop(name) + from kda.ops.triton.chunk import chunk_kda as _triton_chunk_kda # noqa: F401 + + assert not any(n == "fla" or n.startswith("fla.") for n in sys.modules) diff --git a/tests/correctness/test_chunkwise.py b/tests/correctness/test_chunkwise.py new file mode 100644 index 0000000..4fe64a3 --- /dev/null +++ b/tests/correctness/test_chunkwise.py @@ -0,0 +1,47 @@ +"""L2: chunked naive vs L1 naive (fwd) + gradcheck.""" +import torch + +from kda.ops.reference.chunkwise import naive_chunk_kda +from kda.ops.reference.recurrent import naive_kda + + +def test_fwd_matches_naive(): + """chunked vs naive fwd, atol=1e-4.""" + B, T, H, HV, K, V = 2, 32, 2, 4, 8, 8 + torch.manual_seed(1) + q = torch.randn(B, T, H, K, dtype=torch.float64) + k = torch.randn(B, T, H, K, dtype=torch.float64) + v = torch.randn(B, T, HV, V, dtype=torch.float64) + g = torch.randn(B, T, HV, K, dtype=torch.float64) * 0.1 + beta = torch.rand(B, T, HV, dtype=torch.float64) + + o_ref, _ = naive_kda(q, k, v, g, beta, output_final_state=True) + o_chk, _ = naive_chunk_kda(q, k, v, g, beta, + output_final_state=True, chunk_size=8) + + diff = (o_ref - o_chk).abs().max().item() + assert diff < 1e-4, f"chunk vs naive fwd max diff {diff:.2e} > 1e-4" + print(f"L2 fwd-vs-naive: PASSED (max diff {diff:.2e})") + + +def test_gradcheck(): + """L2 chunked gradcheck atol=1e-4 (allowing rtol 1e-3 for triangular path).""" + B, T, H, HV, K, V = 2, 16, 2, 4, 4, 4 + torch.manual_seed(2) + q = torch.randn(B, T, H, K, dtype=torch.float64, requires_grad=True) + k = torch.randn(B, T, H, K, dtype=torch.float64, requires_grad=True) + v = torch.randn(B, T, HV, V, dtype=torch.float64, requires_grad=True) + g = torch.randn(B, T, HV, K, dtype=torch.float64, requires_grad=True) * 0.1 + beta = torch.rand(B, T, HV, dtype=torch.float64, requires_grad=True) + + assert torch.autograd.gradcheck( + lambda q, k, v, g, b: naive_chunk_kda(q, k, v, g, b, chunk_size=4)[0], + (q, k, v, g, beta), + eps=1e-6, atol=1e-4, rtol=1e-3, + ), "L2 gradcheck 失败" + print("L2 gradcheck: PASSED") + + +if __name__ == "__main__": + test_fwd_matches_naive() + test_gradcheck() diff --git a/tests/correctness/test_decay_span.py b/tests/correctness/test_decay_span.py new file mode 100644 index 0000000..2136164 --- /dev/null +++ b/tests/correctness/test_decay_span.py @@ -0,0 +1,99 @@ +"""Saturated gates must not overflow the reference ``exp``. + +The existing kernel tests draw ``g = -rand(...)``, which keeps the per-chunk +gate span near 1 per step and never exercises the exponent budget. A trained +``safe_gate`` model pins whole channels at ``|g| = |lower_bound|`` for a full +chunk, which used to overflow ``_decayed_dot`` and produce NaN for any +``chunk_size > 16``. +""" +import pytest +import torch + +from kda.ops.api import chunk_kda +from kda.ops.reference.chunkwise import DECAY_BLOCK, _EXP_LIMIT + +LOWER_BOUND = -5.0 + + +def _saturated_inputs(T, device="cpu", seed=0): + """Gates pinned at ``lower_bound`` on one channel, mild elsewhere.""" + torch.manual_seed(seed) + B, H, K, V = 1, 2, 8, 8 + q = torch.randn(B, T, H, K, device=device) + k = torch.randn(B, T, H, K, device=device) + v = torch.randn(B, T, H, V, device=device) + g = -torch.rand(B, T, H, K, device=device) * 0.1 + g[..., 0] = LOWER_BOUND # fully saturated channel, the worst case + beta = torch.rand(B, T, H, device=device) + return q, k, v, g, beta + + +def _run(inputs, **kwargs): + kwargs.setdefault("use_qk_l2norm_in_kernel", True) + return chunk_kda(*inputs, **kwargs)[0] + + +@pytest.mark.parametrize("chunk_size", [16, 32, 64]) +def test_saturated_gate_does_not_overflow(chunk_size): + out = _run(_saturated_inputs(128), chunk_size=chunk_size) + assert torch.isfinite(out).all(), f"NaN/inf at chunk_size={chunk_size}" + + +def test_chunk_size_does_not_change_the_result(): + """Chunking is exact algebra, so every chunk_size must agree.""" + inputs = _saturated_inputs(128) + base = _run(inputs, chunk_size=16) + for chunk_size in (32, 64): + other = _run(inputs, chunk_size=chunk_size) + torch.testing.assert_close(other, base, atol=1e-5, rtol=1e-5) + + +def test_gradients_flow_through_the_row_blocks(): + """_decayed_dot writes its row blocks into a preallocated buffer.""" + q, k, v, g, beta = _saturated_inputs(64) + for t in (q, k, v, g, beta): + t.requires_grad_(True) + _run((q, k, v, g, beta), chunk_size=64).sum().backward() + for name, t in zip("qkvgb", (q, k, v, g, beta)): + assert t.grad is not None and torch.isfinite(t.grad).all(), name + + +def test_decay_block_fits_the_exp_budget(): + assert DECAY_BLOCK * abs(LOWER_BOUND) < _EXP_LIMIT + + +def test_lower_bound_beyond_the_budget_is_rejected(): + too_deep = -(_EXP_LIMIT / DECAY_BLOCK) - 1.0 + with pytest.raises(ValueError, match="overflows exp"): + _run( + _saturated_inputs(32), + chunk_size=32, + safe_gate=True, + lower_bound=too_deep, + use_gate_in_kernel=True, + A_log=torch.zeros(2), + ) + + +@pytest.mark.parametrize("chunk_size", [32, 50, 64]) +def test_unbounded_gate_past_the_budget_warns(chunk_size): + """safe_gate is bounded, but ``-A.exp() * softplus(x)`` is not.""" + q, k, v, g, beta = _saturated_inputs(100) + g[...] = -(_EXP_LIMIT / DECAY_BLOCK) - 0.5 # just over the per-block budget + with pytest.warns(RuntimeWarning, match="gate span within a"): + _run((q, k, v, g, beta), chunk_size=chunk_size) + + +def test_unnormalised_qk_warns(): + with pytest.warns(RuntimeWarning, match="Neumann series"): + _run(_saturated_inputs(32), chunk_size=32, use_qk_l2norm_in_kernel=False) + + +@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required") +def test_saturated_gate_matches_triton(): + inputs = _saturated_inputs(128, device="cuda") + reference = _run(inputs, chunk_size=64, backend="reference") + triton = _run(inputs, chunk_size=64, backend="triton") + torch.testing.assert_close( + triton.float(), reference.float(), atol=2e-3, rtol=2e-3 + ) diff --git a/tests/correctness/test_gate.py b/tests/correctness/test_gate.py new file mode 100644 index 0000000..d359abe --- /dev/null +++ b/tests/correctness/test_gate.py @@ -0,0 +1,52 @@ +"""KDA gate reference semantics and autograd coverage.""" +import torch +import torch.nn.functional as F + +from kda.ops.reference.gate import kda_gate_reference + + +def test_standard_gate_matches_formula(): + B, T, HV, K = 1, 32, 4, 8 + torch.manual_seed(10) + g = torch.randn(B, T, HV, K, dtype=torch.float64) + A_log = torch.randn(HV, dtype=torch.float64) * 0.5 + dt_bias = torch.randn(HV, K, dtype=torch.float64) * 0.1 + expected = -A_log[:, None].exp() * F.softplus(g + dt_bias) + actual = kda_gate_reference(g, A_log, dt_bias) + torch.testing.assert_close(actual, expected) + + +def test_safe_gate_matches_formula(): + B, T, HV, K = 1, 8, 2, 4 + torch.manual_seed(12) + g = torch.randn(B, T, HV, K, dtype=torch.float64) + A_log = torch.randn(HV, dtype=torch.float64) + dt_bias = torch.randn(HV, K, dtype=torch.float64) + lower_bound = -5.0 + expected = lower_bound * torch.sigmoid(A_log[:, None].exp() * (g + dt_bias)) + actual = kda_gate_reference( + g, A_log, dt_bias, safe_gate=True, lower_bound=lower_bound + ) + torch.testing.assert_close(actual, expected) + + +def test_bwd_gradcheck(): + B, T, HV, K = 1, 8, 2, 4 + torch.manual_seed(11) + g = torch.randn(B, T, HV, K, dtype=torch.float64, requires_grad=True) + A_log = torch.randn(HV, dtype=torch.float64, requires_grad=True) * 0.5 + dt_bias = torch.randn(HV, K, dtype=torch.float64, requires_grad=True) * 0.1 + A_log.requires_grad_(True) + dt_bias.requires_grad_(True) + + def fn(g, A_log, dt_bias): + return kda_gate_reference(g, A_log, dt_bias).sum() + + assert torch.autograd.gradcheck(fn, (g, A_log, dt_bias), eps=1e-6, atol=1e-4) + print("L5 bwd-gradcheck: PASSED") + + +if __name__ == "__main__": + test_standard_gate_matches_formula() + test_safe_gate_matches_formula() + test_bwd_gradcheck() diff --git a/tests/correctness/test_recurrent.py b/tests/correctness/test_recurrent.py new file mode 100644 index 0000000..e1de6dd --- /dev/null +++ b/tests/correctness/test_recurrent.py @@ -0,0 +1,34 @@ +"""L1: gradcheck for naive_recurrent_kda. + +验证策略: torch.autograd.gradcheck 走 forward+backward 五个梯度. +强制 dtype=float64; eps=1e-6, atol=1e-4. + +shape (small): + B=2, T=8, H=2, HV=4, K=4, V=4 +""" +import torch + +from kda.ops.reference.recurrent import naive_kda + + +def test_gradcheck(): + B, T, H, HV, K, V = 2, 8, 2, 4, 4, 4 + torch.manual_seed(0) + + # 所有输入都需要 requires_grad=True + q = torch.randn(B, T, H, K, dtype=torch.float64, requires_grad=True) + k = torch.randn(B, T, H, K, dtype=torch.float64, requires_grad=True) + v = torch.randn(B, T, HV, V, dtype=torch.float64, requires_grad=True) + g = torch.randn(B, T, HV, K, dtype=torch.float64, requires_grad=True) * 0.1 + beta = torch.rand(B, T, HV, dtype=torch.float64, requires_grad=True) + + assert torch.autograd.gradcheck( + lambda q, k, v, g, b: naive_kda(q, k, v, g, b, output_final_state=True), + (q, k, v, g, beta), + eps=1e-6, atol=1e-4, rtol=1e-3, + ), "L1 gradcheck 失败" + print("L1 gradcheck: PASSED") + + +if __name__ == "__main__": + test_gradcheck() diff --git a/tests/inference/__init__.py b/tests/inference/__init__.py new file mode 100644 index 0000000..7645a90 --- /dev/null +++ b/tests/inference/__init__.py @@ -0,0 +1 @@ +"""Incremental inference tests.""" diff --git a/tests/inference/test_recurrent_decode.py b/tests/inference/test_recurrent_decode.py new file mode 100644 index 0000000..9e2f068 --- /dev/null +++ b/tests/inference/test_recurrent_decode.py @@ -0,0 +1,30 @@ +"""L6: fused_recurrent decode matches naive recurrent.""" +import pytest +import torch + +from kda.ops.recurrent.fused import fused_recurrent_kda +from kda.ops.reference.recurrent import naive_kda + +pytestmark = pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required") + + +def test_step_matches_naive(): + B, T, H, HV, K, V = 2, 32, 2, 4, 8, 8 + torch.manual_seed(20) + device = "cuda" + q = torch.randn(B, T, H, K, device=device) + k = torch.randn(B, T, H, K, device=device) + v = torch.randn(B, T, HV, V, device=device) + g = -torch.rand(B, T, HV, K, device=device) * 2 + beta = torch.rand(B, T, HV, device=device) + + o_naive, _ = naive_kda( + q.double(), k.double(), v.double(), g.double(), + beta.double(), output_final_state=False, + ) + o_step, _ = fused_recurrent_kda(q, k, v, g, beta, output_final_state=False) + torch.testing.assert_close(o_step.float(), o_naive.float(), rtol=2e-3, atol=2e-3) + + +if __name__ == "__main__": + test_step_matches_naive() diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..c12baa2 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1 @@ +"""Model and training integration tests.""" diff --git a/tests/integration/test_attn_res.py b/tests/integration/test_attn_res.py new file mode 100644 index 0000000..4ca9271 --- /dev/null +++ b/tests/integration/test_attn_res.py @@ -0,0 +1,176 @@ +"""AttnRes depth mixer: switch, no double-register, causality, two-phase match.""" +from dataclasses import asdict + +import pytest +import torch + +from kda.layers.attn_res import BlockAttnResStack, FullAttnResStack, atomic_block_size +from kda.layers.kda_attn import KDAAttention +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig +from kda.models.k3_config import K3Config +from kda.training.toy import load_ckpt, save_ckpt + + +def _tiny_k3(**kwargs): + defaults = dict( + hidden_size=32, + num_hidden_layers=4, + num_heads=4, + head_dim=8, + chunk_size=4, + vocab_size=64, + moe_latent_size=16, + moe_d_ff=16, + n_routed=4, + top_k=2, + n_shared=1, + kv_lora_rank=8, + q_lora_rank=16, + qk_nope_head_dim=8, + v_head_dim=8, + ) + defaults.update(kwargs) + return K3Config(**defaults) + + +def test_default_attnres_is_off(): + cfg = K3Config(num_hidden_layers=2) + assert cfg.attnres == "off" + model = CausalLM(cfg) + assert model.mixer is None + assert isinstance(model.blocks[0].attn, KDAAttention) + + +def test_invalid_attnres_rejected(): + with pytest.raises(ValueError, match="attnres"): + K3Config(attnres="yes") + with pytest.raises(ValueError, match="attnres_block_size"): + K3Config(attnres="block", attnres_block_size=0) + + +@pytest.mark.parametrize("mode, stack_cls", [("block", BlockAttnResStack), ("full", FullAttnResStack)]) +def test_mixer_kind_and_atomic_count(mode, stack_cls): + cfg = _tiny_k3(attnres=mode, attnres_block_size=2) + model = CausalLM(cfg) + assert model.attnres == mode + assert isinstance(model.mixer, stack_cls) + assert len(model.mixer.layers) == 2 * cfg.num_hidden_layers + if mode == "block": + assert model.mixer.block_size == 4 # 2 DecoderBlocks × attn|ffn + + +def test_auto_block_size_targets_eight_blocks(): + assert atomic_block_size(24, None) == 6 # 3 DecoderBlocks × 2 + assert atomic_block_size(4, None) == 2 + assert atomic_block_size(93, None) == 24 # 12 DecoderBlocks × 2, K3 S=12 + + +def test_no_duplicate_parameter_ids(): + model = CausalLM(_tiny_k3(attnres="block")) + ids = [id(p) for p in model.parameters()] + assert len(ids) == len(set(ids)) + names = [n for n, _ in model.named_parameters()] + assert len(names) == len(set(names)) + residual_names = [n for n in names if "residuals" in n or "final_residual" in n] + assert residual_names + block_names = [n for n in names if n.startswith("blocks.")] + mixer_weight_names = [ + n for n in names if n.startswith("mixer.layers.") and "query" not in n and "norm" not in n + ] + assert block_names + assert mixer_weight_names == [] + + +def test_off_and_block_differ_at_same_seed(): + torch.manual_seed(0) + off = CausalLM(_tiny_k3(attnres="off")) + torch.manual_seed(0) + on = CausalLM(_tiny_k3(attnres="block")) + x = torch.tensor([[1, 2, 3, 4]]) + with torch.no_grad(): + assert not torch.allclose(off(x), on(x)) + + +def test_block_two_phase_matches_naive(): + torch.manual_seed(4) + model = CausalLM(_tiny_k3(attnres="block", attnres_block_size=2)).eval() + x = torch.randint(0, 64, (2, 8)) + with torch.no_grad(): + emb = model.embedding(x) + naive = model.mixer.forward_naive(emb) + two_phase = model.mixer(emb) + torch.testing.assert_close(naive, two_phase, atol=1e-5, rtol=1e-5) + + +@pytest.mark.parametrize("mode", ["block", "full"]) +def test_attnres_is_still_causal(mode): + torch.manual_seed(51) + model = CausalLM(_tiny_k3(attnres=mode, num_hidden_layers=2)).eval() + with torch.no_grad(): + a = model(torch.tensor([[1, 2, 3, 4]])) + b = model(torch.tensor([[1, 2, 3, 9]])) + torch.testing.assert_close(a[:, :3], b[:, :3], atol=1e-5, rtol=1e-5) + + +def test_kda_config_block_runs(): + cfg = KDAConfig( + hidden_size=16, + num_hidden_layers=2, + num_heads=2, + num_value_heads=2, + head_dim=4, + chunk_size=4, + vocab_size=32, + intermediate_size=32, + attnres="block", + attnres_block_size=1, + ) + model = CausalLM(cfg) + logits = model(torch.tensor([[1, 2, 3, 4]])) + assert logits.shape == (1, 4, 32) + + +def test_attnres_ckpt_roundtrip(tmp_path): + cfg = _tiny_k3(attnres="block", attnres_block_size=2) + model = CausalLM(cfg) + path = str(tmp_path / "attnres.pt") + save_ckpt(model, cfg, path) + loaded, loaded_cfg = load_ckpt(path) + assert loaded_cfg.attnres == "block" + assert loaded_cfg.attnres_block_size == 2 + torch.manual_seed(1) + x = torch.randint(0, cfg.vocab_size, (2, 8)) + with torch.no_grad(): + torch.testing.assert_close(model(x), loaded(x), atol=1e-5, rtol=1e-5) + + +def test_old_ckpt_without_attnres_stays_off(tmp_path): + cfg = _tiny_k3() + payload = asdict(cfg) + payload.pop("attnres") + payload.pop("attnres_block_size") + payload.pop("attnres_zero_init_queries") + payload.pop("attnres_final_aggregate") + model = CausalLM(cfg) + path = str(tmp_path / "legacy.pt") + torch.save({"model_state": model.state_dict(), "config": payload}, path) + _, loaded_cfg = load_ckpt(path) + assert loaded_cfg.attnres == "off" + assert loaded_cfg.attnres_block_size is None + + +def test_attnres_block_overfits_single_batch(): + torch.manual_seed(30) + cfg = _tiny_k3(attnres="block", num_hidden_layers=2, attnres_block_size=1) + model = CausalLM(cfg) + x = torch.randint(0, cfg.vocab_size, (2, 8)) + optim = torch.optim.AdamW(model.parameters(), lr=3e-3) + final = None + for _ in range(200): + optim.zero_grad() + loss = model(x, labels=x) + loss.backward() + optim.step() + final = loss.item() + assert final < 0.5, f"final loss {final:.4f} >= 0.5" diff --git a/tests/integration/test_causal_checkpoint.py b/tests/integration/test_causal_checkpoint.py new file mode 100644 index 0000000..78c8527 --- /dev/null +++ b/tests/integration/test_causal_checkpoint.py @@ -0,0 +1,64 @@ +import torch +import torch.nn.functional as F + +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig +from kda.models.k3_config import K3Config + + +def _tiny(): + torch.manual_seed(4) + cfg = KDAConfig( + hidden_size=16, + num_hidden_layers=2, + num_heads=2, + num_value_heads=2, + head_dim=4, + chunk_size=4, + vocab_size=32, + intermediate_size=32, + kda_backend="reference", + ) + return CausalLM(cfg), cfg + + +def test_ignore_index_skips_masked_positions(): + model, _ = _tiny() + tokens = torch.tensor([[1, 2, 3, 4]]) + labels = tokens.clone() + labels[:, 1:3] = -100 + with torch.no_grad(): + logits = model(tokens) + actual = model(tokens, labels=labels) + expected = F.cross_entropy( + logits[:, :-1].reshape(-1, logits.size(-1)), + labels[:, 1:].reshape(-1), + ignore_index=-100, + ) + torch.testing.assert_close(actual, expected) + + +def test_gradient_checkpointing_matches_eager_grad(): + torch.manual_seed(8) + tokens = torch.randint(0, 32, (2, 8)) + m1, cfg = _tiny() + m2 = CausalLM(cfg) + m2.load_state_dict(m1.state_dict()) + m2.gradient_checkpointing = True + m1.train() + m2.train() + l1 = m1(tokens, labels=tokens) + l2 = m2(tokens, labels=tokens) + torch.testing.assert_close(l1, l2, atol=1e-5, rtol=1e-5) + l1.backward() + l2.backward() + for p1, p2 in zip(m1.parameters(), m2.parameters()): + if p1.grad is None: + assert p2.grad is None + continue + torch.testing.assert_close(p1.grad, p2.grad, atol=1e-4, rtol=1e-4) + + +def test_0_5b_preset_enables_checkpointing(): + assert K3Config.preset("0.5b").gradient_checkpointing is True + assert K3Config.preset("toy").gradient_checkpointing is False diff --git a/tests/integration/test_causal_lm.py b/tests/integration/test_causal_lm.py new file mode 100644 index 0000000..2c5ecf4 --- /dev/null +++ b/tests/integration/test_causal_lm.py @@ -0,0 +1,78 @@ +"""Causal-language-model behavior independent of toy memorization.""" +import torch +import torch.nn.functional as F + +from kda.layers.kda_attn import KDAAttention +from kda.layers.swiglu import SwiGLUMLP +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig + + +def _model(): + torch.manual_seed(51) + config = KDAConfig( + hidden_size=16, + num_hidden_layers=1, + num_heads=2, + num_value_heads=2, + head_dim=4, + chunk_size=4, + vocab_size=32, + intermediate_size=32, + kda_backend="reference", + ) + return CausalLM(config).eval() + + +def test_kda_schedule_and_unified_stem(): + config = KDAConfig(num_hidden_layers=2) + assert config.layer_specs() == [("kda", "swiglu"), ("kda", "swiglu")] + model = CausalLM(config) + assert isinstance(model.blocks[0].attn, KDAAttention) + assert isinstance(model.blocks[0].ffn, SwiGLUMLP) + + +def test_future_token_does_not_change_past_logits(): + model = _model() + first = torch.tensor([[1, 2, 3, 4]]) + second = torch.tensor([[1, 2, 3, 9]]) + with torch.no_grad(): + first_logits = model(first) + second_logits = model(second) + torch.testing.assert_close(first_logits[:, :3], second_logits[:, :3]) + + +def test_attention_reads_operator_flags_from_config(): + torch.manual_seed(52) + config = KDAConfig( + hidden_size=16, + num_hidden_layers=1, + num_heads=2, + num_value_heads=2, + head_dim=4, + chunk_size=4, + vocab_size=32, + intermediate_size=32, + use_gate_in_kernel=False, + use_qk_l2norm_in_kernel=False, + use_beta_sigmoid_in_kernel=False, + lower_bound=None, + kda_backend="reference", + ) + model = CausalLM(config).eval() + with torch.no_grad(): + logits = model(torch.tensor([[1, 2, 3, 4]])) + assert logits.shape == (1, 4, 32) + + +def test_loss_is_shifted_next_token_cross_entropy(): + model = _model() + tokens = torch.tensor([[1, 2, 3, 4]]) + with torch.no_grad(): + logits = model(tokens) + actual = model(tokens, labels=tokens) + expected = F.cross_entropy( + logits[:, :-1].reshape(-1, logits.size(-1)), + tokens[:, 1:].reshape(-1), + ) + torch.testing.assert_close(actual, expected) diff --git a/tests/integration/test_ckpt_compat.py b/tests/integration/test_ckpt_compat.py new file mode 100644 index 0000000..bfe16d4 --- /dev/null +++ b/tests/integration/test_ckpt_compat.py @@ -0,0 +1,56 @@ +"""load_ckpt must read checkpoints written before the ffn/config renames.""" +from dataclasses import asdict + +import pytest +import torch + +from kda.models.config import KDAConfig +from kda.models.k3_config import K3Config +from kda.training.toy import load_ckpt, save_ckpt + + +def _legacy(state, new, old): + """Undo the ffn rename, reproducing a pre-rename checkpoint.""" + renamed = { + k.replace(f".{new}.", f".{old}.").replace(f".{new}_norm.", f".{old}_norm."): v + for k, v in state.items() + } + assert any(f".{old}." in k for k in renamed), "fixture renamed nothing" + return renamed + + +@pytest.mark.parametrize( + ("config", "old"), + [ + (KDAConfig(num_hidden_layers=2), "mlp"), # dense: was named .mlp + (K3Config(num_hidden_layers=2), "moe"), # K3: was named .moe + ], + ids=["kda-mlp", "k3-moe"], +) +def test_legacy_ffn_names_still_load(tmp_path, config, old): + from kda.models.causal_lm import CausalLM + + model = CausalLM(config) + path = str(tmp_path / "legacy.pt") + torch.save( + { + "model_state": _legacy(model.state_dict(), "ffn", old), + "config": asdict(config), + }, + path, + ) + + loaded, loaded_config = load_ckpt(path) + assert type(loaded_config) is type(config) + for name, want in model.state_dict().items(): + torch.testing.assert_close(loaded.state_dict()[name], want) + + +@pytest.mark.parametrize("config", [KDAConfig(num_hidden_layers=2), K3Config(num_hidden_layers=2)]) +def test_roundtrip_picks_the_right_config_class(tmp_path, config): + from kda.models.causal_lm import CausalLM + + path = str(tmp_path / "ckpt.pt") + save_ckpt(CausalLM(config), config, path) + _, loaded_config = load_ckpt(path) + assert loaded_config == config diff --git a/tests/integration/test_eval_files.py b/tests/integration/test_eval_files.py new file mode 100644 index 0000000..d752989 --- /dev/null +++ b/tests/integration/test_eval_files.py @@ -0,0 +1,23 @@ +from pathlib import Path + +from kda.training.eval_mt import _instruction +from kda.training.prompts import instruction_prompt + +_ROOT = Path(__file__).resolve().parents[2] / "data" / "eval" + + +def _lines(name: str) -> list[str]: + return [ln.strip() for ln in (_ROOT / name).read_text(encoding="utf-8").splitlines() if ln.strip()] + + +def test_frozen_eval_files_are_aligned(): + zh_src, zh_ref = _lines("zh2en.src.txt"), _lines("zh2en.ref.txt") + en_src, en_ref = _lines("en2zh.src.txt"), _lines("en2zh.ref.txt") + assert len(zh_src) == len(zh_ref) >= 16 + assert len(en_src) == len(en_ref) >= 16 + assert all("\t" not in s for s in zh_src + en_src) + + +def test_eval_instruction_is_the_sft_template(): + assert _instruction("q", "en") == instruction_prompt("q", "en") + assert _instruction("q", "zh") == instruction_prompt("q", "zh") diff --git a/tests/integration/test_eval_mt.py b/tests/integration/test_eval_mt.py new file mode 100644 index 0000000..1c2a795 --- /dev/null +++ b/tests/integration/test_eval_mt.py @@ -0,0 +1,44 @@ +"""translation_success and eval helpers (no GPU, no FLORES download).""" +from kda.training.success import translation_success + + +def test_empty_and_copy_fail(): + src = "人工智能的发展改变了世界。" + assert translation_success(src, "", ref="The development of AI changed the world.", target_lang="en") is False + assert translation_success(src, src, ref="The development of AI changed the world.", target_lang="en") is False + + +def test_wrong_language_fails(): + src = "The cat sat on the mat." + hyp = "The cat sat on the mat and smiled." + ref = "猫坐在垫子上。" + assert translation_success(src, hyp, ref, target_lang="zh") is False + + +def test_instruction_leak_fails(): + src = "Hello" + hyp = "翻译如下:你好" + assert translation_success(src, hyp, ref="你好", target_lang="zh") is False + + +def test_good_zh2en_passes(): + src = "今天天气很好。" + hyp = "The weather is very nice today." + ref = "The weather is very nice today." + assert translation_success(src, hyp, ref, target_lang="en") is True + + +def test_container_help_exits_2(): + import importlib.util + from pathlib import Path + + import pytest + + path = Path(__file__).resolve().parents[2] / "scripts" / "container_help.py" + spec = importlib.util.spec_from_file_location("container_help", path) + mod = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(mod) + with pytest.raises(SystemExit) as ei: + mod.main() + assert ei.value.code == 2 diff --git a/tests/integration/test_k3_arch.py b/tests/integration/test_k3_arch.py new file mode 100644 index 0000000..d80f895 --- /dev/null +++ b/tests/integration/test_k3_arch.py @@ -0,0 +1,145 @@ +"""K3 架构复现测试: MLA 吸收等价, LatentMoE 路由, hybrid pattern, 因果性, overfit.""" +import torch +import torch.nn.functional as F +import pytest + +from kda.layers.kda_attn import KDAAttention +from kda.layers.latent_moe import LatentMoE +from kda.layers.mla import GatedMLA +from kda.models.causal_lm import CausalLM +from kda.models.k3_config import K3Config + + +def _mla(d=64, H=4, r=16, q_r=32, d_q=16, d_v=16): + torch.manual_seed(7) + return GatedMLA(d, H, r, q_r, d_q, d_v) + + +def _naive_mla(x, module: GatedMLA): + """解压版参考: 标准 attention (吸收版数学上应与它逐位一致).""" + B, T, _ = x.shape + H, r = module.num_heads, module.kv_up.in_features + c = module.kv_norm(module.kv_down(x)) # [B,T,r] + q = module.q_up(module.q_norm(module.q_down(x))).view(B, T, H, module.qk_nope_head_dim) + w = module.kv_up.weight + w_uk = w[: H * module.qk_nope_head_dim].view(H, module.qk_nope_head_dim, r) + w_uv = w[H * module.qk_nope_head_dim :].view(H, module.v_head_dim, r) + k = torch.einsum("btj,hvj->bthv", c, w_uk) # 解压 K + v = torch.einsum("btj,hvj->bthv", c, w_uv) # 解压 V + scores = torch.einsum("bthv,bshv->bhts", q, k) # [B,H,T,T] + mask = torch.triu(torch.ones(T, T, dtype=torch.bool), diagonal=1) + scores = scores.masked_fill(mask, float("-inf")) + attn = F.softmax(scores, dim=-1) + o = torch.einsum("bhts,bshv->bthv", attn, v) # [B,T,H,d_v] + o = o.reshape(B, T, H * module.v_head_dim) + gate = torch.sigmoid(module.gate(x)) + return module.o_proj(gate * o) + + +def test_mla_absorption_matches_unrolled(): + m = _mla().eval() + x = torch.randn(3, 12, 64) + with torch.no_grad(): + absorbed = m(x) + unrolled = _naive_mla(x, m) + torch.testing.assert_close(absorbed, unrolled, atol=1e-5, rtol=1e-5) + + +def test_mla_absorption_matches_unrolled_grad(): + """吸收版与解压版的梯度也应一致 (fwd+bwd 双重验证).""" + m1, m2 = _mla(), _mla() + m2.load_state_dict(m1.state_dict()) + x = torch.randn(2, 8, 64) + l1 = m1(x).square().mean() + l2 = _naive_mla(x, m2).square().mean() + l1.backward() + l2.backward() + for (n1, p1), (n2, p2) in zip(m1.named_parameters(), m2.named_parameters()): + torch.testing.assert_close(p1.grad, p2.grad, atol=1e-5, rtol=1e-5) + + +def test_hybrid_layer_pattern(): + cfg = K3Config(num_hidden_layers=4) + assert cfg.layer_types() == ["kda", "kda", "kda", "mla"] + cfg8 = K3Config(num_hidden_layers=8) + assert cfg8.layer_types() == ["kda", "kda", "kda", "mla"] * 2 + # 末层强制 MLA: L=5 → 层 3 MLA + 层 4 (末层) MLA + cfg5 = K3Config(num_hidden_layers=5) + assert cfg5.layer_types() == ["kda", "kda", "kda", "mla", "mla"] + assert cfg.layer_specs() == [("kda", "moe"), ("kda", "moe"), ("kda", "moe"), ("mla", "moe")] + model = CausalLM(K3Config(num_hidden_layers=4, hidden_size=32, moe_d_ff=16, moe_latent_size=16)) + assert isinstance(model.blocks[0].attn, KDAAttention) + assert isinstance(model.blocks[3].attn, GatedMLA) + assert isinstance(model.blocks[0].ffn, LatentMoE) + + +def test_preset_0_5b_schedule(): + cfg = K3Config.preset("0.5b") + assert cfg.hidden_size == 768 + assert cfg.num_heads * cfg.head_dim == cfg.hidden_size + assert cfg.num_hidden_layers == 24 + assert cfg.tie_word_embeddings + assert cfg.chunk_size == 64 + assert cfg.gradient_checkpointing is True + assert cfg.moe_latent_size == cfg.hidden_size // 2 + types = cfg.layer_types() + assert types.count("mla") == 6 + assert types[-1] == "mla" + assert cfg.layer_specs()[3] == ("mla", "moe") + + +def test_moe_router_activates_topk_only(): + from kda.layers.latent_moe import LatentMoE + torch.manual_seed(3) + moe = LatentMoE(hidden_size=32, latent_size=16, n_routed=8, top_k=2, n_shared=1, d_ff=24) + x = torch.randn(2, 6, 32) + with torch.no_grad(): + y = moe(x) + logits = moe.router(x) + topk = torch.topk(logits, moe.top_k, dim=-1) + z = moe.down(x) + # 手算: 只有 top-k 专家输出被加权, 再经 shared + up(norm(u)) + expected_u = torch.zeros(2, 6, moe.latent_size) + all_out = torch.stack([e(z) for e in moe.experts]) # [R,B,T,ℓ] + probs = F.softmax(topk.values, dim=-1) + for i in range(moe.top_k): + idx = topk.indices[:, :, i] + for b in range(2): + for t in range(6): + expected_u[b, t] += probs[b, t, i] * all_out[idx[b, t], b, t] + shared = torch.stack([e(x) for e in moe.shared]).sum(0) + expected_y = shared + moe.up(moe.norm(expected_u)) + torch.testing.assert_close(y, expected_y, atol=1e-5, rtol=1e-5) + assert moe.last_route_ids is not None + assert moe.last_route_ids.shape[-1] == moe.top_k + + +def test_k3_causal_future_does_not_change_past_logits(): + torch.manual_seed(51) + cfg = K3Config(hidden_size=64, num_hidden_layers=4, num_heads=4, head_dim=8, + chunk_size=4, vocab_size=64, moe_latent_size=32, moe_d_ff=24, + n_routed=8, kv_lora_rank=16, q_lora_rank=32, qk_nope_head_dim=8, v_head_dim=8) + m = CausalLM(cfg).eval() + with torch.no_grad(): + a = m(torch.tensor([[1, 2, 3, 4]])) + b = m(torch.tensor([[1, 2, 3, 9]])) + torch.testing.assert_close(a[:, :3], b[:, :3], atol=1e-6, rtol=0) + + +def test_k3_small_model_overfits_single_batch(): + """K3 混合架构单 batch overfit 冒烟: loss < 0.5 (收敛即架构可训).""" + torch.manual_seed(30) + cfg = K3Config(hidden_size=64, num_hidden_layers=2, num_heads=4, head_dim=8, + chunk_size=4, vocab_size=64, moe_latent_size=32, moe_d_ff=24, + n_routed=8, kv_lora_rank=16, q_lora_rank=32, qk_nope_head_dim=8, v_head_dim=8) + m = CausalLM(cfg) + x = torch.randint(0, cfg.vocab_size, (2, 16)) + optim = torch.optim.AdamW(m.parameters(), lr=3e-3) + final = None + for step in range(200): + optim.zero_grad() + loss = m(x, labels=x) + loss.backward() + optim.step() + final = loss.item() + assert final < 0.5, f"final loss {final:.4f} >= 0.5" diff --git a/tests/integration/test_pretrain_data.py b/tests/integration/test_pretrain_data.py new file mode 100644 index 0000000..8c05198 --- /dev/null +++ b/tests/integration/test_pretrain_data.py @@ -0,0 +1,53 @@ +import json +import sys + +import torch + +from kda.training.data import ( + chunk_ids, + fetch_wiki_texts, + interleave_balanced, + split_heldout, +) + + +def test_interleave_is_one_to_one_monolingual_blocks(): + a = list(range(10)) + b = list(range(100, 112)) + out = interleave_balanced(a, b, block=4) + assert out == [0, 1, 2, 3, 100, 101, 102, 103, 4, 5, 6, 7, 104, 105, 106, 107] + + +def test_split_heldout_keeps_at_least_one_train(): + chunks = torch.arange(10).view(10, 1, 1) + train, held = split_heldout(chunks, frac=0.01, min_heldout=1) + assert train.size(0) == 9 + assert held.size(0) == 1 + empty_train, empty_held = split_heldout(chunks[:1], frac=0.5) + assert empty_train.size(0) == 1 + assert empty_held.size(0) == 0 + + +def test_chunk_ids_drops_tail(): + ids = list(range(10)) + chunks = chunk_ids(ids, batch=2, seq_len=4) + assert chunks.shape == (1, 2, 4) + + +def test_wiki_cache_roundtrip(tmp_path, monkeypatch): + cache = tmp_path / "pretrain" + cache.mkdir() + path = cache / "wiki-zh-n2-limit3.jsonl" + path.write_text( + "\n".join(json.dumps({"text": f"article {i}"}) for i in range(3)) + "\n", + encoding="utf-8", + ) + + def _boom(*_a, **_k): + raise AssertionError("must not hit the network") + + fake = type(sys)("datasets") + fake.load_dataset = _boom + monkeypatch.setitem(sys.modules, "datasets", fake) + texts = fetch_wiki_texts(3, lang="zh", cache_dir=cache) + assert texts == ["article 0", "article 1", "article 2"] diff --git a/tests/integration/test_schedule.py b/tests/integration/test_schedule.py new file mode 100644 index 0000000..fe52ba9 --- /dev/null +++ b/tests/integration/test_schedule.py @@ -0,0 +1,21 @@ +from kda.training.schedule import lr_scale, total_opt_steps + + +def test_warmup_then_cosine_floor(): + assert abs(lr_scale(0, warmup=10, total_opt=100) - 0.1) < 1e-9 + assert abs(lr_scale(9, warmup=10, total_opt=100) - 1.0) < 1e-9 + assert abs(lr_scale(10, warmup=10, total_opt=100) - 1.0) < 1e-6 + end = lr_scale(99, warmup=10, total_opt=100) + assert abs(end - 0.1) < 1e-6 + + +def test_horizon_prefers_the_earlier_stop(): + # 8.2M tokens @ batch 2 seq 2048 acc 8 -> 250 opt + opt_from_tokens = total_opt_steps( + max_tokens=8_192_000, max_micro=10_000, batch=2, seq_len=2048, grad_acc=8 + ) + assert opt_from_tokens == 250 + opt_from_micro = total_opt_steps( + max_tokens=10**12, max_micro=2000, batch=2, seq_len=2048, grad_acc=8 + ) + assert opt_from_micro == 250 diff --git a/tests/integration/test_sft_data.py b/tests/integration/test_sft_data.py new file mode 100644 index 0000000..c7b1897 --- /dev/null +++ b/tests/integration/test_sft_data.py @@ -0,0 +1,52 @@ +from kda.training.data import IGNORE_INDEX, collate_sft, encode_sft_row, load_sft_rows +from kda.training.prompts import instruction_prompt + + +class _Tok: + vocab_size = 32 + + def encode(self, text: str) -> list[int]: + return [min((ord(c) % 30) + 1, 31) for c in text[:12]] or [1] + + def decode(self, ids: list[int]) -> str: + return "x" * len(ids) + + +def test_instruction_matches_eval_template(): + assert instruction_prompt("你好", "en") == "Translate to English:\n你好" + assert instruction_prompt("Hello", "zh") == "Translate to Chinese:\nHello" + + +def test_prompt_tokens_are_ignored(): + tok = _Tok() + src, tgt = "ab", "cd" + ids, labels = encode_sft_row(tok, src, tgt, "en", max_len=64) + prompt_n = len(tok.encode(instruction_prompt(src, "en"))) + assert labels[:prompt_n] == [IGNORE_INDEX] * prompt_n + assert all(v != IGNORE_INDEX for v in labels[prompt_n:]) + assert ids[prompt_n:] == tok.encode(tgt) + + +def test_collate_and_jsonl(tmp_path): + path = tmp_path / "tiny.jsonl" + path.write_text( + '{"src": "a", "tgt": "b", "target_lang": "en"}\n' + '{"src": "c", "tgt": "d", "target_lang": "zh"}\n', + encoding="utf-8", + ) + rows = load_sft_rows(path) + assert len(rows) == 2 + x, y = collate_sft(rows, _Tok(), max_len=32) + assert x.shape == y.shape + assert x.size(0) == 2 + assert (y == IGNORE_INDEX).any() + + +def test_toy_sft_file_parses(): + from pathlib import Path + + path = Path(__file__).resolve().parents[2] / "data" / "sft" / "toy.jsonl" + rows = load_sft_rows(path) + assert len(rows) >= 20 + langs = {r["target_lang"] for r in rows} + assert langs == {"en", "zh"} diff --git a/tests/integration/test_tensorlens.py b/tests/integration/test_tensorlens.py new file mode 100644 index 0000000..450128b --- /dev/null +++ b/tests/integration/test_tensorlens.py @@ -0,0 +1,193 @@ +"""TensorLens 集成测试: KDA 模型张量 -> trace -> 全局 store -> Flask 端点. + +依赖: tensorlens (未安装时整个模块 skip, 用 `pip install tensorlens` 启用). + +测三层: +1. trace — KDA 前向的真实 1D/2D/3D 张量 (embed/block 输出/logits/权重) + 规范化成 int8 存入 tensorlens 全局 store +2. normalize — 四种策略 (clip/minmax/zscore/none) 的边界行为 +3. HTTP — 用 Flask test_client 验证 /api/list_tensors 与 /api/get_tensor, + 不启动阻塞的 gunicorn server (viewer() 为交互式入口, 不做自动化) +""" +import numpy as np +import pytest +import torch + +pytest.importorskip("tensorlens") + +from tensorlens.core import global_store +from tensorlens.tensorlens import normalize_to_int8, trace +from tensorlens.web.server import app + +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig + + +def _model(): + torch.manual_seed(51) + config = KDAConfig( + hidden_size=16, + num_hidden_layers=1, + num_heads=2, + num_value_heads=2, + head_dim=4, + chunk_size=4, + vocab_size=32, + intermediate_size=32, + kda_backend="reference", + ) + return CausalLM(config).eval() + + +@pytest.fixture(autouse=True) +def _clean_store(): + """global_store 是模块级单例, 每个测试前后清空, 避免互相污染.""" + global_store.INMEMORY_TENSORS.clear() + yield + global_store.INMEMORY_TENSORS.clear() + + +def _trace_kda_tensors(model): + """前向一次, 把 KDA 的 1D/2D/3D 张量全部 trace 进 store, 返回 logits numpy.""" + x = torch.tensor([[1, 2, 3, 4]]) + hidden = {} + + def hook_fn(name): + def hook(module, inp, out): + hidden[name] = out.detach() + + return hook + + model.embedding.register_forward_hook(hook_fn("embed")) + model.blocks[0].register_forward_hook(hook_fn("block0")) + + with torch.no_grad(): + logits = model(x) + + logits_np = logits.detach().numpy() # [1, T, vocab] 3D + trace("lm_head.weight", model.lm_head.weight.detach().numpy()) # [vocab, hidden] 2D + trace("embed", hidden["embed"].numpy()) # [1, T, hidden] 3D + trace("block0.out", hidden["block0"].numpy()) # [1, T, hidden] 3D + trace("logits", logits_np) # [1, T, vocab] 3D + trace("logits.row0", logits_np[0, 0]) # [vocab] 1D + return logits_np + + +# --------------------------------------------------------------------------- +# Layer 1: trace — KDA 张量进 store +# --------------------------------------------------------------------------- + + +def test_trace_stores_int8_with_expected_shape(): + model = _model() + logits_np = _trace_kda_tensors(model) + store = global_store.INMEMORY_TENSORS + + assert set(store) == {"lm_head.weight", "embed", "block0.out", "logits", "logits.row0"} + assert store["logits"].dtype == np.int8 + assert store["logits"].shape == logits_np.shape + assert store["lm_head.weight"].shape == (32, 16) + assert store["logits.row0"].ndim == 1 + assert store["logits"].min() >= -128 and store["logits"].max() <= 127 + + +# --------------------------------------------------------------------------- +# Layer 2: normalize_to_int8 — 四种策略边界行为 +# --------------------------------------------------------------------------- + + +def test_clip_normalization_bounds(): + t = np.array([[-100.0, 0.0, 100.0]]) + out = normalize_to_int8(t, (-1.0, 1.0), "clip") + assert out.dtype == np.int8 + assert out[0, 0] == -127 and out[0, 2] == 127 + assert out[0, 1] == 0 + + +def test_minmax_constant_tensor_returns_zeros(): + t = np.full((2, 3), 0.5) + out = normalize_to_int8(t, (-1.0, 1.0), "minmax") + assert (out == 0).all() + + +def test_zscore_zero_std_returns_zeros(): + t = np.ones((4, 4)) + out = normalize_to_int8(t, (-1.0, 1.0), "zscore") + assert (out == 0).all() + + +def test_none_strategy_scales_by_127(): + t = np.array([[0.5, -0.5]]) + out = normalize_to_int8(t, (-1.0, 1.0), "none") + assert out[0, 0] == 63 and out[0, 1] == -63 # int8 cast 向零截断 + + +def test_unsupported_normalization_raises(): + with pytest.raises(ValueError): + normalize_to_int8(np.zeros(3), (-1.0, 1.0), "bogus") + + +# --------------------------------------------------------------------------- +# Layer 2.5: trace 输入校验 +# --------------------------------------------------------------------------- + + +def test_trace_rejects_non_ndarray(): + with pytest.raises(TypeError): + trace("bad", torch.zeros(3)) + + +def test_trace_rejects_empty_key(): + with pytest.raises(ValueError): + trace("", np.zeros(3)) + + +# --------------------------------------------------------------------------- +# Layer 3: Flask 端点 (test_client, 不起真实 server) +# --------------------------------------------------------------------------- + + +def test_list_tensors_endpoint_reports_kda_tensors(): + model = _model() + _trace_kda_tensors(model) + resp = app.test_client().get("/api/list_tensors") + assert resp.status_code == 200 + body = resp.get_json() + assert body["count"] == 5 + keys = [t["key"] for t in body["available_tensors"]] + assert "logits" in keys and "lm_head.weight" in keys + + +def test_get_tensor_endpoint_returns_data(): + model = _model() + logits_np = _trace_kda_tensors(model) + resp = app.test_client().get("/api/get_tensor?tensor_key=logits") + assert resp.status_code == 200 + body = resp.get_json() + assert body["shape"] == list(logits_np.shape) + assert len(body["data"]) == logits_np.shape[0] + + +def test_get_tensor_missing_key_returns_400(): + model = _model() + _trace_kda_tensors(model) + resp = app.test_client().get("/api/get_tensor") + assert resp.status_code == 400 + assert "tensor_key" in resp.get_json()["error"] + + +def test_get_tensor_unknown_key_returns_404(): + resp = app.test_client().get("/api/get_tensor?tensor_key=nope") + assert resp.status_code == 404 + + +def test_config_endpoint(): + resp = app.test_client().get("/api/config") + assert resp.status_code == 200 + assert resp.get_json()["status"] == "ok" + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main([__file__, "-v"])) diff --git a/tests/integration/test_torchlens.py b/tests/integration/test_torchlens.py new file mode 100644 index 0000000..7582a25 --- /dev/null +++ b/tests/integration/test_torchlens.py @@ -0,0 +1,83 @@ +"""TorchLens 集成测试: 计算图展开 KDA 模型并提取逐层激活. + +依赖: torchlens (未安装时整个模块 skip, 用 `pip install torchlens` 启用). + +验证三点: +1. trace 能展开 KDA 模型 —— 关键子模块 (embedding / attention 各投影 / + block 输出 / norm / lm_head 输出) 的激活被捕获且形状正确 +2. 展开不改变模型行为 —— trace 记录的输出与直接 forward 严格一致 +3. extract 便捷接口 —— 按模块名批量取激活 + +torchlens 2.34 的 trace[key] 返回 Op 对象, 取原始 tensor 用 `.tensor`. +""" +import pytest +import torch + +pytest.importorskip("torchlens") + +import torchlens as tl + +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig + + +def _model(): + torch.manual_seed(51) + config = KDAConfig( + hidden_size=16, + num_hidden_layers=1, + num_heads=2, + num_value_heads=2, + head_dim=4, + chunk_size=4, + vocab_size=32, + intermediate_size=32, + kda_backend="reference", + ) + return CausalLM(config).eval() + + +def test_trace_captures_kda_submodule_activations(): + model = _model() + x = torch.tensor([[1, 2, 3, 4]]) + with torch.no_grad(): + trace = tl.trace(model, x, capture=tl.options.CaptureOptions(verbose=False)) + + # 模块激活被捕获, 形状正确 + assert tuple(trace["embedding"].tensor.shape) == (1, 4, 16) + assert tuple(trace["blocks.0.attn.q_proj"].tensor.shape) == (1, 4, 8) # H*K = 2*4 + assert tuple(trace["blocks.0.attn.v_proj"].tensor.shape) == (1, 4, 8) # HV*V = 2*4 + assert tuple(trace["blocks.0.attn"].tensor.shape) == (1, 4, 16) + assert tuple(trace["blocks.0.ffn"].tensor.shape) == (1, 4, 16) + assert tuple(trace["norm"].tensor.shape) == (1, 4, 16) + assert tuple(trace["output"].tensor.shape) == (1, 4, 32) # vocab_size + + +def test_trace_does_not_change_model_behavior(): + model = _model() + x = torch.tensor([[1, 2, 3, 4]]) + with torch.no_grad(): + trace = tl.trace(model, x, capture=tl.options.CaptureOptions(verbose=False)) + traced_logits = trace["output"].tensor + direct_logits = model(x) + torch.testing.assert_close(traced_logits, direct_logits) + + +def test_extract_returns_activations_by_module_name(): + model = _model() + x = torch.tensor([[1, 2, 3, 4]]) + with torch.no_grad(): + acts = tl.extract( + model, x, ["embedding", "blocks.0.attn.q_proj", "blocks.0", "output"] + ) + assert set(acts) == {"embedding", "blocks.0.attn.q_proj", "blocks.0", "output"} + assert tuple(acts["embedding"].shape) == (1, 4, 16) + assert tuple(acts["blocks.0.attn.q_proj"].shape) == (1, 4, 8) + assert tuple(acts["blocks.0"].shape) == (1, 4, 16) + assert tuple(acts["output"].shape) == (1, 4, 32) + + +if __name__ == "__main__": + import sys + + sys.exit(pytest.main([__file__, "-v"])) diff --git a/tests/integration/test_train_overfit.py b/tests/integration/test_train_overfit.py new file mode 100644 index 0000000..5a03a03 --- /dev/null +++ b/tests/integration/test_train_overfit.py @@ -0,0 +1,32 @@ +"""L7: toy overfit smoke test. 320 steps loss < 0.1.""" +import torch + +from kda.models.causal_lm import CausalLM +from kda.models.config import KDAConfig + + +def test_overfit(): + cfg = KDAConfig() # 起步默认 toy 配置 + torch.manual_seed(30) + model = CausalLM(cfg).cuda() + + x = torch.randint(0, cfg.vocab_size, (4, 16), device="cuda") + labels = x.clone() + # A single repeated batch is an optimizer/dataflow smoke test, so converge it quickly. + optim = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01) + + for step in range(320): + optim.zero_grad() + loss = model(x, labels=labels) + loss.backward() + optim.step() + if step % 64 == 0 or step == 319: + print(f" step {step:3d} loss {loss.item():.4f}") + + final = loss.item() + assert final < 0.1, f"final loss {final:.4f} > 0.1" + print(f"L7 overfit: PASSED (final loss {final:.4f})") + + +if __name__ == "__main__": + test_overfit() diff --git a/tests/kernels/__init__.py b/tests/kernels/__init__.py new file mode 100644 index 0000000..6913d67 --- /dev/null +++ b/tests/kernels/__init__.py @@ -0,0 +1 @@ +"""Local kernel implementation tests.""" diff --git a/tests/kernels/test_gate.py b/tests/kernels/test_gate.py new file mode 100644 index 0000000..92b7868 --- /dev/null +++ b/tests/kernels/test_gate.py @@ -0,0 +1,20 @@ +"""The vendored FLA fused gate must match the PyTorch reference.""" +import pytest +import torch + +from kda.ops.reference.gate import kda_gate_reference +from kda.ops.triton.gate import kda_gate_fwd + +pytestmark = pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required") + + +def test_triton_gate_matches_reference(): + B, T, HV, K = 1, 32, 4, 8 + torch.manual_seed(10) + device = "cuda" + g = torch.randn(B, T, HV, K, device=device, dtype=torch.float32) + A_log = torch.randn(HV, device=device, dtype=torch.float32) * 0.5 + dt_bias = torch.randn(HV, K, device=device, dtype=torch.float32) * 0.1 + expected = kda_gate_reference(g, A_log, dt_bias) + actual = kda_gate_fwd(g, A_log, dt_bias, lower_bound=None) + torch.testing.assert_close(actual, expected, rtol=1e-4, atol=1e-4) diff --git a/tests/kernels/test_triton_bwd.py b/tests/kernels/test_triton_bwd.py new file mode 100644 index 0000000..73839b4 --- /dev/null +++ b/tests/kernels/test_triton_bwd.py @@ -0,0 +1,67 @@ +"""L4: vendored FLA Triton bwd vs naive chunked. + +Triton kernels run in fp32, so this checks VJP vs L2 rather than fp64 gradcheck. +Inputs L2-normalize q/k like the trained KDA path. +""" +import pytest +import torch +import torch.nn.functional as F + +from kda.ops.reference.chunkwise import naive_chunk_kda +from kda.ops.triton.chunk_fwd import chunk_kda_fwd + +pytestmark = pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required") + + +def test_bwd_matches_naive(): + B, T, H, HV, K, V = 2, 64, 4, 8, 32, 32 + torch.manual_seed(5) + dev = "cuda" + q = F.normalize(torch.randn(B, T, H, K, device=dev), dim=-1).requires_grad_() + k = F.normalize(torch.randn(B, T, H, K, device=dev), dim=-1).requires_grad_() + v = torch.randn(B, T, HV, V, device=dev, requires_grad=True) + g = (-torch.rand(B, T, HV, K, device=dev) * 2).requires_grad_() + beta = torch.rand(B, T, HV, device=dev, requires_grad=True) + + o_na, _ = naive_chunk_kda(q, k, v, g, beta, chunk_size=64) + o_na.sum().backward() + dq_na, dk_na = q.grad.clone(), k.grad.clone() + dv_na, dg_na, db_na = v.grad.clone(), g.grad.clone(), beta.grad.clone() + + q.grad = k.grad = v.grad = g.grad = beta.grad = None + o_tr, _ = chunk_kda_fwd(q, k, v, g, beta, chunk_size=64) + o_tr.sum().backward() + dq_tr, dk_tr = q.grad.clone(), k.grad.clone() + dv_tr, dg_tr, db_tr = v.grad.clone(), g.grad.clone(), beta.grad.clone() + + torch.testing.assert_close(dq_tr, dq_na, rtol=2e-2, atol=2e-3) + torch.testing.assert_close(dk_tr, dk_na, rtol=2e-2, atol=2e-3) + torch.testing.assert_close(dv_tr, dv_na, rtol=2e-2, atol=2e-3) + torch.testing.assert_close(dg_tr, dg_na, rtol=2e-2, atol=2e-3) + torch.testing.assert_close(db_tr, db_na, rtol=2e-2, atol=2e-3) + + +def test_triton_kda_attention_backward_dt_bias_rank2(): + """Layer stores dt_bias as [HV, K]; Triton bwd used to return a flat [HV*K].""" + from kda.layers.kda_attn import KDAAttention + + torch.manual_seed(0) + model = KDAAttention( + hidden_size=64, + num_heads=4, + num_value_heads=8, + head_dim=16, + chunk_size=64, + kda_backend="triton", + ).cuda() + x = torch.randn(2, 64, 64, device="cuda") + model(x).sum().backward() + assert model.dt_bias.grad is not None + assert model.dt_bias.grad.shape == model.dt_bias.shape + assert model.A_log.grad is not None + assert torch.isfinite(model.dt_bias.grad).all() + + +if __name__ == "__main__": + test_bwd_matches_naive() + test_triton_kda_attention_backward_dt_bias_rank2() diff --git a/tests/kernels/test_triton_fwd.py b/tests/kernels/test_triton_fwd.py new file mode 100644 index 0000000..0c562b4 --- /dev/null +++ b/tests/kernels/test_triton_fwd.py @@ -0,0 +1,29 @@ +"""L3: vendored FLA Triton fwd vs naive chunked.""" +import pytest +import torch +import torch.nn.functional as F + +from kda.ops.reference.chunkwise import naive_chunk_kda +from kda.ops.triton.chunk_fwd import chunk_kda_fwd + +pytestmark = pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required") + + +def test_triton_fwd_matches_naive(): + B, T, H, HV, K, V = 2, 64, 4, 8, 32, 32 + torch.manual_seed(3) + device = "cuda" + q = F.normalize(torch.randn(B, T, H, K, device=device), dim=-1) + k = F.normalize(torch.randn(B, T, H, K, device=device), dim=-1) + v = torch.randn(B, T, HV, V, device=device) + g = -torch.rand(B, T, HV, K, device=device) * 2 + beta = torch.rand(B, T, HV, device=device) + + o_ref, S_ref = naive_chunk_kda( + q, k, v, g, beta, output_final_state=True, chunk_size=64 + ) + o_tr, S_tr = chunk_kda_fwd( + q, k, v, g, beta, output_final_state=True, chunk_size=64 + ) + torch.testing.assert_close(o_tr.float(), o_ref.float(), rtol=2e-3, atol=2e-3) + torch.testing.assert_close(S_tr.float(), S_ref.float(), rtol=2e-3, atol=2e-3) diff --git a/train.py b/train.py new file mode 100644 index 0000000..a5fe747 --- /dev/null +++ b/train.py @@ -0,0 +1,6 @@ +"""Toy causal-language-model training loop.""" + +from kda.training.toy import main + +if __name__ == "__main__": + main() diff --git a/train_k3.py b/train_k3.py new file mode 100644 index 0000000..5b8a266 --- /dev/null +++ b/train_k3.py @@ -0,0 +1,461 @@ +"""Train a Kimi-K3-like model (KDA + Gated MLA + LatentMoE) on bilingual wiki. + +用法: + uv run python train_k3.py # 8M toy, zh+en wiki + uv run python train_k3.py --preset 0.5b # ~0.5B smoke (8M tokens) + # 32–40GB Ampere, 1B-token 翻译前置预训练: + uv run python train_k3.py --preset 0.5b --attnres block \\ + --max-tokens 1000000000 --warmup 2000 +""" + +from __future__ import annotations + +import argparse +import os +import time +from dataclasses import asdict + +import torch + +from kda.layers.latent_moe import moe_route_frac +from kda.models.causal_lm import CausalLM +from kda.models.k3_config import K3Config +from kda.training.data import iter_indexed, load_pretrain_chunks, load_tokenizer +from kda.training.schedule import lr_scale, tokens_per_micro, total_opt_steps +from kda.training.toy import load_ckpt + +_TOY_TRAIN = { + "tokenizer": "data/spm_4k.model", + "out": "ckpts/k3_wiki.pt", + "limit": 8000, + "batch": 4, + "seq_len": 256, + "steps": 500, + "lr": 2e-3, + "warmup": 50, + "grad_acc": 1, + "eval_every": 100, +} +_B500M_TRAIN = { + "tokenizer": "Qwen/Qwen3-8B", + "out": "ckpts/k3_0.5b.pt", + "limit": 20000, + "batch": 2, + "seq_len": 2048, + "steps": 2000, + "lr": 3e-4, + "warmup": 64, + "grad_acc": 8, + "eval_every": 100, +} + + +def _sibling(path: str, suffix: str) -> str: + root, ext = os.path.splitext(path) + return f"{root}{suffix}{ext or '.pt'}" + + +def _set_lr(optim: torch.optim.Optimizer, lr: float) -> None: + for group in optim.param_groups: + group["lr"] = lr + + +def _init_swanlab(cfg: K3Config, args: argparse.Namespace): + """Cloud monitor if SWANLAB_API_KEY is set; otherwise no-op.""" + key = os.environ.get("SWANLAB_API_KEY") + if not key: + return None + try: + import swanlab + except ImportError: + print("SWANLAB_API_KEY set but swanlab is not installed") + return None + try: + swanlab.login(api_key=key, save=False) + # swanlab 0.9 Settings.project is nested; a string SWANLAB_PROJECT env crashes init. + project = os.environ.pop("SWANLAB_PROJECT", None) or "kda" + return swanlab.init( + project=project, + name=f"{args.preset}-{cfg.attnres}", + config={ + "preset": args.preset, + "attnres": cfg.attnres, + "attnres_block_size": cfg.attnres_block_size, + "lr": args.lr, + "batch": args.batch, + "seq_len": args.seq_len, + "steps": args.steps, + "max_tokens": args.max_tokens, + "grad_acc": args.grad_acc, + "warmup": args.warmup, + "langs": args.langs, + "kda_backend": cfg.kda_backend, + "gradient_checkpointing": cfg.gradient_checkpointing, + }, + ) + except Exception as exc: + print(f"swanlab init failed ({exc}); continuing without cloud monitor") + return None + + +def _payload( + cfg: K3Config, + model: CausalLM, + optim: torch.optim.Optimizer, + args: argparse.Namespace, + *, + micro_step: int, + opt_step: int, + tokens: int, + chunk_index: int, + best_heldout: float, +): + return { + "config": asdict(cfg), + "model_state": model.state_dict(), + "optimizer_state": optim.state_dict(), + "tokenizer": args.tokenizer, + "preset": args.preset, + "micro_step": micro_step, + "opt_step": opt_step, + "tokens": tokens, + "chunk_index": chunk_index, + "best_heldout": best_heldout, + } + + +def _save(path: str, payload: dict) -> None: + os.makedirs(os.path.dirname(path) or ".", exist_ok=True) + tmp = path + ".tmp" + torch.save(payload, tmp) + os.replace(tmp, path) + + +@torch.no_grad() +def _heldout_loss( + model, chunks, device, use_bf16, max_batches: int = 4 +) -> float | None: + if chunks is None or chunks.numel() == 0: + return None + model.eval() + losses = [] + for x, y in zip(chunks[:max_batches], chunks[:max_batches]): + x = x.to(device) + y = y.to(device) + with torch.autocast("cuda", dtype=torch.bfloat16, enabled=use_bf16): + losses.append(float(model(x, labels=y).item())) + model.train() + return sum(losses) / max(len(losses), 1) + + +def _moe_log(model) -> dict: + frac = moe_route_frac(model) + if frac is None: + return {} + n = frac.numel() + dead = float((frac < 1.0 / (2 * n)).sum().item()) + return { + "moe/max_frac": float(frac.max()), + "moe/min_frac": float(frac.min()), + "moe/n_dead": dead, + } + + +def main() -> None: + pre = argparse.ArgumentParser(add_help=False) + pre.add_argument("--preset", default="toy", choices=["toy", "0.5b"]) + pre_args, _ = pre.parse_known_args() + train_defaults = _B500M_TRAIN if pre_args.preset == "0.5b" else _TOY_TRAIN + + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--preset", default="toy", choices=["toy", "0.5b"]) + p.add_argument( + "--tokenizer", "--sp", dest="tokenizer", default=train_defaults["tokenizer"] + ) + p.add_argument("--out", default=train_defaults["out"]) + p.add_argument("--limit", type=int, default=train_defaults["limit"]) + p.add_argument("--batch", type=int, default=train_defaults["batch"]) + p.add_argument("--seq-len", type=int, default=train_defaults["seq_len"]) + p.add_argument("--steps", type=int, default=train_defaults["steps"]) + p.add_argument( + "--max-tokens", + type=int, + default=None, + help="stop after this many tokens (primary budget). --steps is a micro-step cap", + ) + p.add_argument("--lr", type=float, default=train_defaults["lr"]) + p.add_argument( + "--warmup", + type=int, + default=train_defaults["warmup"], + help="linear warmup in optimizer steps, then cosine to 0.1× lr", + ) + p.add_argument("--grad-acc", type=int, default=train_defaults["grad_acc"]) + p.add_argument("--eval-every", type=int, default=train_defaults["eval_every"]) + p.add_argument( + "--langs", + default="zh,en", + help="comma-separated wiki languages mixed 1:1 by token (default zh,en)", + ) + p.add_argument("--heldout-frac", type=float, default=0.01) + p.add_argument("--resume", default=None, help="checkpoint to continue from") + p.add_argument("--gen-prefix", action="append", default=None) + p.add_argument("--device", default="auto") + p.add_argument( + "--attnres", + default="off", + choices=["off", "full", "block"], + help="depth mixer: off=standard residual, block=K3 AttnRes, full=per-layer AttnRes", + ) + p.add_argument( + "--attnres-block-size", + type=int, + default=None, + help="DecoderBlocks per AttnRes block (block mode). Default ≈ L/8", + ) + p.add_argument( + "--grad-checkpoint", + dest="grad_checkpoint", + action="store_true", + default=None, + help="activation checkpointing (0.5b preset default on)", + ) + p.add_argument( + "--no-grad-checkpoint", + dest="grad_checkpoint", + action="store_false", + ) + args = p.parse_args() + if args.gen_prefix is None: + args.gen_prefix = ["人工智能的发展", "The history of computing"] + + if args.seq_len % K3Config.preset(args.preset).chunk_size: + raise SystemExit( + f"seq-len {args.seq_len} must be divisible by chunk_size " + f"{K3Config.preset(args.preset).chunk_size}" + ) + + device = args.device + if device == "auto": + device = "cuda" if torch.cuda.is_available() else "cpu" + use_bf16 = device == "cuda" and torch.cuda.is_bf16_supported() + if device == "cuda" and not use_bf16: + raise SystemExit( + "KDA training needs bf16; this GPU does not support it (avoid V100 fp16)" + ) + + print(f"loading tokenizer {args.tokenizer} ...") + tok = load_tokenizer(args.tokenizer) + cfg = K3Config.preset(args.preset) + cfg.vocab_size = tok.vocab_size + cfg.attnres = args.attnres + cfg.attnres_block_size = args.attnres_block_size + if args.grad_checkpoint is not None: + cfg.gradient_checkpointing = args.grad_checkpoint + + langs = [part.strip() for part in args.langs.split(",") if part.strip()] + tpm = tokens_per_micro(args.batch, args.seq_len) + horizon = total_opt_steps( + max_tokens=args.max_tokens, + max_micro=args.steps, + batch=args.batch, + seq_len=args.seq_len, + grad_acc=args.grad_acc, + ) + + micro_step = 0 + opt_step = 0 + tokens = 0 + chunk_index = 0 + best_heldout = float("inf") + best_train = float("inf") + + if args.resume: + print(f"resume {args.resume}") + model, loaded_cfg = load_ckpt(args.resume) + if not isinstance(loaded_cfg, K3Config): + raise SystemExit( + f"train_k3.py requires a K3 checkpoint; {args.resume} has " + f"{type(loaded_cfg).__name__}" + ) + cfg = loaded_cfg + cfg.attnres = args.attnres + cfg.attnres_block_size = args.attnres_block_size + if args.grad_checkpoint is not None: + cfg.gradient_checkpointing = args.grad_checkpoint + model.gradient_checkpointing = cfg.gradient_checkpointing + model.to(device) + payload = torch.load(args.resume, map_location="cpu", weights_only=False) + if payload.get("tokenizer") and payload["tokenizer"] != args.tokenizer: + print(f"warning: ckpt tokenizer {payload['tokenizer']} != {args.tokenizer}") + micro_step = int(payload.get("micro_step", 0)) + opt_step = int(payload.get("opt_step", 0)) + tokens = int(payload.get("tokens", 0)) + chunk_index = int(payload.get("chunk_index", 0)) + best_heldout = float(payload.get("best_heldout", best_heldout)) + else: + model = CausalLM(cfg).to(device) + + tracker = _init_swanlab(cfg, args) + n = sum(p.numel() for p in model.parameters()) + print( + f"preset={args.preset} model={n:,} params ({n / 1e6:.1f}M) on {device} " + f"bf16={use_bf16} checkpoint={cfg.gradient_checkpointing}" + ) + print( + f"vocab={cfg.vocab_size} tied={cfg.tie_word_embeddings} " + f"layers={cfg.layer_types()} attnres={cfg.attnres} langs={langs}" + ) + if args.max_tokens is None: + print( + f"token budget: --steps {args.steps} micro " + f"({args.steps * tpm:,} tokens); pass --max-tokens for a real run" + ) + else: + print(f"token budget: {args.max_tokens:,} cosine horizon {horizon} opt steps") + + train_chunks, held_chunks, n_ids = load_pretrain_chunks( + tok, + langs=langs, + limit=args.limit, + batch=args.batch, + seq_len=args.seq_len, + heldout_frac=args.heldout_frac, + ) + print( + f"packed tokens {n_ids:,} -> {train_chunks.size(0)} train / " + f"{held_chunks.size(0)} held-out chunks of [{args.batch}, {args.seq_len}]" + ) + if train_chunks.size(0) == 0: + raise SystemExit("no training chunks; raise --limit or lower --batch/--seq-len") + + optim = torch.optim.AdamW( + model.parameters(), + lr=args.lr, + weight_decay=0.1 if args.preset == "0.5b" else 0.01, + ) + if args.resume: + payload = torch.load(args.resume, map_location="cpu", weights_only=False) + if payload.get("optimizer_state"): + optim.load_state_dict(payload["optimizer_state"]) + + def gen_sample(prefix: str, max_new: int = 24) -> str: + ids_ = tok.encode(prefix) + if not ids_: + return "" + inp = torch.tensor([ids_], dtype=torch.long, device=device) + was_training = model.training + model.eval() + with torch.inference_mode(): + out = model.generate(inp, max_new) + if was_training: + model.train() + return tok.decode(out[0].tolist()) + + model.train() + t0 = time.perf_counter() + tokens_at_t0 = tokens + for chunk_index, x, y in iter_indexed(train_chunks, start=chunk_index): + if args.max_tokens is not None and tokens >= args.max_tokens: + break + if micro_step >= args.steps: + break + x, y = x.to(device), y.to(device) + scale = lr_scale(opt_step, args.warmup, horizon) + _set_lr(optim, args.lr * scale) + with torch.autocast("cuda", dtype=torch.bfloat16, enabled=use_bf16): + loss = model(x, labels=y) / args.grad_acc + loss.backward() + do_step = (micro_step + 1) % args.grad_acc == 0 + grad_norm = None + if do_step: + grad_norm = float(torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)) + optim.step() + optim.zero_grad(set_to_none=True) + opt_step += 1 + + raw_loss = loss.item() * args.grad_acc + tokens += tpm + micro_step += 1 + lr_now = optim.param_groups[0]["lr"] + if raw_loss < best_train: + best_train = raw_loss + + metrics = {"train/loss": raw_loss, "train/lr": lr_now, "train/tokens": tokens} + if grad_norm is not None: + metrics["train/grad_norm"] = grad_norm + elapsed = time.perf_counter() - t0 + if elapsed > 0: + metrics["train/tok_s"] = (tokens - tokens_at_t0) / elapsed + + log_now = ( + micro_step % args.eval_every == 0 + or micro_step == 1 + or (args.max_tokens is not None and tokens >= args.max_tokens) + or micro_step >= args.steps + ) + if log_now: + held = _heldout_loss(model, held_chunks, device, use_bf16) + if held is not None: + metrics["heldout/loss"] = held + metrics.update(_moe_log(model)) + print( + f"micro {micro_step:6d} opt {opt_step:6d} tok {tokens:,} " + f"loss {raw_loss:.4f} lr {lr_now:.2e}" + + (f" held {held:.4f}" if held is not None else "") + ) + if micro_step % (args.eval_every * 2) == 0 or micro_step <= args.eval_every: + for prefix in args.gen_prefix: + sample = gen_sample(prefix) + print(f" gen[{prefix[:16]}]: {sample}") + if tracker is not None: + import swanlab + + tracker.log( + {f"gen/{prefix[:24]}": swanlab.Text(sample)}, + step=micro_step, + ) + payload = _payload( + cfg, + model, + optim, + args, + micro_step=micro_step, + opt_step=opt_step, + tokens=tokens, + chunk_index=chunk_index + 1, + best_heldout=best_heldout, + ) + _save(_sibling(args.out, "_last"), payload) + if held is not None and held < best_heldout: + best_heldout = held + payload["best_heldout"] = best_heldout + _save(_sibling(args.out, "_best"), payload) + print( + f" best held-out {best_heldout:.4f} -> {_sibling(args.out, '_best')}" + ) + if tracker is not None: + tracker.log(metrics, step=micro_step) + + payload = _payload( + cfg, + model, + optim, + args, + micro_step=micro_step, + opt_step=opt_step, + tokens=tokens, + chunk_index=chunk_index + 1, + best_heldout=best_heldout, + ) + _save(args.out, payload) + print( + f"best train {best_train:.4f} best held-out {best_heldout:.4f}; " + f"tokens {tokens:,} -> {args.out}" + ) + if tracker is not None: + tracker.finish() + + +if __name__ == "__main__": + main() diff --git a/train_sft.py b/train_sft.py new file mode 100644 index 0000000..a6da519 --- /dev/null +++ b/train_sft.py @@ -0,0 +1,196 @@ +"""Instruction SFT for zh↔en translation. Prompt template matches eval_mt. + +用法: + uv run python train_sft.py --ckpt ckpts/k3_wiki.pt --data data/sft/train.jsonl + uv run python train_sft.py --ckpt ckpts/k3_0.5b_best.pt --data data/sft/opus.jsonl \\ + --seq-len 512 --batch 4 --lr 5e-5 --epochs 2 +""" + +from __future__ import annotations + +import argparse +import os +from dataclasses import asdict + +import torch + +from kda.training.data import ( + IGNORE_INDEX, + iter_sft_batches, + load_sft_rows, + load_tokenizer, +) +from kda.training.eval_mt import evaluate_pairs +from kda.training.schedule import lr_scale, total_opt_steps +from kda.training.toy import load_ckpt + + +def _set_lr(optim: torch.optim.Optimizer, lr: float) -> None: + for group in optim.param_groups: + group["lr"] = lr + + +def _init_swanlab(args: argparse.Namespace): + key = os.environ.get("SWANLAB_API_KEY") + if not key: + return None + try: + import swanlab + except ImportError: + return None + try: + swanlab.login(api_key=key, save=False) + project = os.environ.pop("SWANLAB_PROJECT", None) or "kda" + return swanlab.init( + project=project, + name=f"sft-{os.path.basename(args.ckpt)}", + config={ + "ckpt": args.ckpt, + "data": args.data, + "lr": args.lr, + "batch": args.batch, + "seq_len": args.seq_len, + "epochs": args.epochs, + }, + ) + except Exception as exc: + print(f"swanlab init failed ({exc}); continuing without cloud monitor") + return None + + +def _read_lines(path: str) -> list[str]: + from pathlib import Path + + return [ + ln.strip() + for ln in Path(path).read_text(encoding="utf-8").splitlines() + if ln.strip() + ] + + +def main() -> None: + p = argparse.ArgumentParser(description=__doc__) + p.add_argument("--ckpt", required=True) + p.add_argument("--data", required=True, help="jsonl {src,tgt,target_lang} or TSV") + p.add_argument("--out", default="ckpts/k3_sft.pt") + p.add_argument("--tokenizer", default=None) + p.add_argument("--batch", type=int, default=4) + p.add_argument("--seq-len", type=int, default=256) + p.add_argument("--lr", type=float, default=1e-3) + p.add_argument("--warmup", type=int, default=20) + p.add_argument("--epochs", type=int, default=2) + p.add_argument("--max-steps", type=int, default=None) + p.add_argument("--grad-acc", type=int, default=1) + p.add_argument("--eval-every", type=int, default=50) + p.add_argument("--src", default=None, help="frozen eval src (not used as train)") + p.add_argument("--ref", default=None) + p.add_argument("--target-lang", default="en", choices=["en", "zh"]) + p.add_argument("--device", default="auto") + args = p.parse_args() + + device = args.device + if device == "auto": + device = "cuda" if torch.cuda.is_available() else "cpu" + use_bf16 = device == "cuda" and torch.cuda.is_bf16_supported() + if device == "cuda" and not use_bf16: + raise SystemExit("KDA training needs bf16") + + model, cfg = load_ckpt(args.ckpt) + model.to(device) + payload = torch.load(args.ckpt, map_location="cpu", weights_only=False) + tok_src = args.tokenizer or payload.get("tokenizer") + if not tok_src: + raise SystemExit("need --tokenizer or a tokenizer field in the checkpoint") + tok = load_tokenizer(tok_src) + rows = load_sft_rows(args.data) + if not rows: + raise SystemExit(f"no SFT rows in {args.data}") + print(f"SFT {len(rows)} rows from {args.data}; model {cfg.__class__.__name__}") + + steps_per_epoch = max((len(rows) + args.batch - 1) // args.batch, 1) + max_micro = args.max_steps + if max_micro is None: + max_micro = steps_per_epoch * args.epochs + horizon = total_opt_steps( + max_tokens=None, + max_micro=max_micro, + batch=args.batch, + seq_len=args.seq_len, + grad_acc=args.grad_acc, + ) + optim = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=0.01) + tracker = _init_swanlab(args) + model.train() + step = 0 + opt_step = 0 + best = float("inf") + + for _, x, y in iter_sft_batches(rows, tok, args.batch, args.seq_len): + if step >= max_micro: + break + x, y = x.to(device), y.to(device) + _set_lr(optim, args.lr * lr_scale(opt_step, args.warmup, horizon)) + with torch.autocast("cuda", dtype=torch.bfloat16, enabled=use_bf16): + loss = model(x, labels=y, ignore_index=IGNORE_INDEX) / args.grad_acc + loss.backward() + if (step + 1) % args.grad_acc == 0: + torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) + optim.step() + optim.zero_grad(set_to_none=True) + opt_step += 1 + raw = loss.item() * args.grad_acc + if raw < best: + best = raw + if tracker is not None: + tracker.log( + {"sft/loss": raw, "sft/lr": optim.param_groups[0]["lr"]}, + step=step, + ) + if step % args.eval_every == 0 or step == max_micro - 1: + print(f"step {step:4d} sft loss {raw:.4f} lr {optim.param_groups[0]['lr']:.2e}") + if args.src and args.ref: + model.eval() + srcs, refs = _read_lines(args.src), _read_lines(args.ref) + out = evaluate_pairs( + model, + tok, + srcs, + refs, + target_lang=args.target_lang, + device=device, + max_new=64, + limit=None, + ) + printable = {k: v for k, v in out.items() if k != "hyps"} + print(printable) + if tracker is not None: + tracker.log( + { + "eval/success_rate": printable["success_rate"], + "eval/chrf": printable["chrf"], + "eval/copy_rate": printable["copy_rate"], + }, + step=step, + ) + model.train() + step += 1 + + os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True) + torch.save( + { + "config": asdict(cfg), + "model_state": model.state_dict(), + "optimizer_state": optim.state_dict(), + "tokenizer": tok_src, + "sft_data": args.data, + "pretrained_ckpt": args.ckpt, + }, + args.out, + ) + print(f"best sft loss {best:.4f}; checkpoint -> {args.out}") + if tracker is not None: + tracker.finish() + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..2a26f52 --- /dev/null +++ b/uv.lock @@ -0,0 +1,4133 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11'" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/4d/4a99fb425c5e0cad715eea7bd190aff46f38b959a0a2dadb993705d34b26/aiohttp-3.14.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b", size = 765848, upload-time = "2026-07-23T01:52:08.217Z" }, + { url = "https://files.pythonhosted.org/packages/74/e8/43b85dc55b8e950dc644babe762add781319ea881b57b33d2cce12017d12/aiohttp-3.14.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a", size = 517476, upload-time = "2026-07-23T01:52:10.846Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9e/73b582c4dbbc3c12ef4473822475effaabf1f934b56f14f5b03fe5d3a2af/aiohttp-3.14.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5", size = 515334, upload-time = "2026-07-23T01:52:12.636Z" }, + { url = "https://files.pythonhosted.org/packages/79/03/e98c3c9e05a5bdf97defe5ff9169baba4f0ec9a901f2d60e0f060c2f051e/aiohttp-3.14.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f", size = 1708830, upload-time = "2026-07-23T01:52:14.538Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2c/26e60b694844dfd2176c57f913a22d0cd6a16f9ff202cbda7580d0328b98/aiohttp-3.14.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43", size = 1674012, upload-time = "2026-07-23T01:52:16.486Z" }, + { url = "https://files.pythonhosted.org/packages/38/65/672df92e3172cd876aacfa97a952ac560877eb169384b2991ac5b273de4c/aiohttp-3.14.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9", size = 1767015, upload-time = "2026-07-23T01:52:18.28Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c5/228dec7bfec1c373cc2217cdeb47d6456dcd7a13a4c55144930a75ae3851/aiohttp-3.14.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8", size = 1858700, upload-time = "2026-07-23T01:52:20.08Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ff/cb36724e8c8d17f90ada567a9ff3efe1d6e9b549fba697a242aece180f21/aiohttp-3.14.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479", size = 1714075, upload-time = "2026-07-23T01:52:22.071Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3a/296a4135c6366376263aeef54b15caca1f07676c2ae0c525d7832f2f808a/aiohttp-3.14.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b", size = 1588234, upload-time = "2026-07-23T01:52:23.757Z" }, + { url = "https://files.pythonhosted.org/packages/7d/81/9d5d853ef892dc066d1eb6db0e87a47348b920c1c879aa554612fdbd9d79/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d", size = 1677300, upload-time = "2026-07-23T01:52:25.861Z" }, + { url = "https://files.pythonhosted.org/packages/68/96/021d386ae32d9b26d4b88df2e794546232ff56bb6be952bf6be227c0bbc7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d", size = 1691501, upload-time = "2026-07-23T01:52:28Z" }, + { url = "https://files.pythonhosted.org/packages/29/9f/af66adce26a14af135c003cbd0f44ccaa68cebd30ff8ac99ca47fb4958f7/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2", size = 1735113, upload-time = "2026-07-23T01:52:29.995Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/28c390d4c9851effe52ac25b5a2e1d92246acd00728b4fc7975dafb67484/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48", size = 1577486, upload-time = "2026-07-23T01:52:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/db/c2/00e23a1bf2abb70dd353f6987db7e7f2491d0261f7363997738c71c98f95/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f", size = 1751353, upload-time = "2026-07-23T01:52:33.688Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7d/d51a706a8cbfa57f0611127daf61ab3ae02ab8420b0407412079227d1c65/aiohttp-3.14.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32", size = 1698681, upload-time = "2026-07-23T01:52:38.167Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b0/90bd5cd9fdd9787cb4211d284d1fb8401339a933cb0227a15b71e789232f/aiohttp-3.14.3-cp310-cp310-win32.whl", hash = "sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e", size = 456733, upload-time = "2026-07-23T01:52:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d8/15/fe5b8f6a71ae112bc677163d0b0701bda5dc15005249582258ede0eb88c7/aiohttp-3.14.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c", size = 480460, upload-time = "2026-07-23T01:52:43.905Z" }, + { url = "https://files.pythonhosted.org/packages/54/00/45e98b6645cd7f00a4b78b749ebd309094b0eaeb2d2e96157eadbc0d0050/aiohttp-3.14.3-cp310-cp310-win_arm64.whl", hash = "sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb", size = 453479, upload-time = "2026-07-23T01:52:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5c/b3e4ff8ad43a8afef9602c5e90285936da1beaea8b029016b793891f03c3/aiohttp-3.14.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3", size = 764250, upload-time = "2026-07-23T01:52:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/0e/da/f1b384465e51449d844056b75070461da03a9a23e6c1747003695bf4172a/aiohttp-3.14.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a", size = 516281, upload-time = "2026-07-23T01:52:51.047Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3f/01264f820ee2e3712a827892b1cd6ff80f3300c1fcbffbb45714a915d47a/aiohttp-3.14.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8", size = 514742, upload-time = "2026-07-23T01:52:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8d/a71c6f2db52ac1ed142b133f7feddaa6b70539c3f4de24d7e226c95b794c/aiohttp-3.14.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239", size = 1780613, upload-time = "2026-07-23T01:52:56.948Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/3dd9b3fb3a170f6ec9011b5291d876a6fab4086714c9e158600edf01b4fd/aiohttp-3.14.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f", size = 1737688, upload-time = "2026-07-23T01:52:59.294Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3e/834c26918be7d88068822b40e0db30fca50b5f4fe79104aa16a93f1d74e6/aiohttp-3.14.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06", size = 1845742, upload-time = "2026-07-23T01:53:01.641Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/49ab8572df7d66bc13d11e31f781292badb04180dd87ba98733066c6aed7/aiohttp-3.14.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929", size = 1928412, upload-time = "2026-07-23T01:53:04.018Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b9/2b8f0c0ce09c87a1daf80fd483431b56b1435d3f62789bc86f572e1245de/aiohttp-3.14.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db", size = 1786220, upload-time = "2026-07-23T01:53:06.481Z" }, + { url = "https://files.pythonhosted.org/packages/85/00/9c45f81de11710460edfa1dc81317b6e882703b160926c879a9d20da9fcc/aiohttp-3.14.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce", size = 1637231, upload-time = "2026-07-23T01:53:10.258Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/967d628e910756f3539c6107cb7844a1b69440dcb3029a5ee7871b09ab63/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c", size = 1753161, upload-time = "2026-07-23T01:53:13.817Z" }, + { url = "https://files.pythonhosted.org/packages/11/b2/0c3d4114f0aee4f580f5b3b4eb71b24d7a23b834ea506a4dfebe76513f35/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15", size = 1756356, upload-time = "2026-07-23T01:53:16.211Z" }, + { url = "https://files.pythonhosted.org/packages/63/5d/99e7d91c82f1399d1ae2a854e080bd1493fbc31e5e959dbc4ec33dac3bec/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c", size = 1819846, upload-time = "2026-07-23T01:53:18.289Z" }, + { url = "https://files.pythonhosted.org/packages/ad/05/d5e1cb6480eeffd3f901d40a2c5e2d1e7effdc797837da3b490272699f13/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae", size = 1628531, upload-time = "2026-07-23T01:53:23.86Z" }, + { url = "https://files.pythonhosted.org/packages/c9/90/b934682bcaefae18a9e04f3dff5b68522ba810906358ae5029b68110ea3b/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910", size = 1832712, upload-time = "2026-07-23T01:53:27.551Z" }, + { url = "https://files.pythonhosted.org/packages/21/df/6061679faaf81fac746e7307c7adb71e858071a5d34c27583afefc64f543/aiohttp-3.14.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7", size = 1775014, upload-time = "2026-07-23T01:53:30.223Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1d/f854878bbc69b88faefe924b619a34a6f59ec05fd387c77690667eaa75eb/aiohttp-3.14.3-cp311-cp311-win32.whl", hash = "sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa", size = 456006, upload-time = "2026-07-23T01:53:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/73/0c/2af9d1674baccd1dbd47282a93d660a22e57ef6167c856deb24b4214fbab/aiohttp-3.14.3-cp311-cp311-win_amd64.whl", hash = "sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d", size = 481069, upload-time = "2026-07-23T01:53:39.673Z" }, + { url = "https://files.pythonhosted.org/packages/8e/76/88401ff3fc95e85c5fc38d588f36f55e61ecb64343b2bc8d69326f453cc0/aiohttp-3.14.3-cp311-cp311-win_arm64.whl", hash = "sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39", size = 453021, upload-time = "2026-07-23T01:53:43.749Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/eb96299230e20acf2efae207cb8d69051f1f68e357e5ea5e479bf6fb097a/aiohttp-3.14.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5", size = 754690, upload-time = "2026-07-23T01:53:47.332Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/e7a70a209eb9a067c0d3212b518a0134e3484f5178c7533878b6b514d469/aiohttp-3.14.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228", size = 509484, upload-time = "2026-07-23T01:53:51.159Z" }, + { url = "https://files.pythonhosted.org/packages/30/07/4bbc222cc8dbe31d4c3e8a5baad2286e4d42026ac0c570027b89afce6344/aiohttp-3.14.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee", size = 511949, upload-time = "2026-07-23T01:53:55.083Z" }, + { url = "https://files.pythonhosted.org/packages/54/b9/42e74c46b7b7c794b995bbc1f573fb48950c38b19d8600c62a6804ee2d67/aiohttp-3.14.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a", size = 1765282, upload-time = "2026-07-23T01:53:59.662Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/62bc4d74363ad346d518e0720363a949f63e2e23439a79eb5813d4d29bb3/aiohttp-3.14.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b", size = 1741511, upload-time = "2026-07-23T01:54:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/181e8a8bc79e47d13c7fc4540bd7a3b729d9505609c61f392a8dd2fbfe55/aiohttp-3.14.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529", size = 1810680, upload-time = "2026-07-23T01:54:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/dec94d6ad694552fe3424e3f1928d7a606a5d9d9433a04e7ecdd9d38ae7f/aiohttp-3.14.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787", size = 1905646, upload-time = "2026-07-23T01:54:13.475Z" }, + { url = "https://files.pythonhosted.org/packages/52/b7/7cd31f29d6055bd711ae6e669367fba6f5ae9de463910a793e30556a8db7/aiohttp-3.14.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42", size = 1792122, upload-time = "2026-07-23T01:54:15.752Z" }, + { url = "https://files.pythonhosted.org/packages/66/73/10b1ef93afa61f4963c746257b70ced619cf31a4798671de5fdb2608501d/aiohttp-3.14.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b", size = 1591127, upload-time = "2026-07-23T01:54:19.489Z" }, + { url = "https://files.pythonhosted.org/packages/49/ed/3b203fa6de1b338c14acdc06bf6ca9b043b7944f005966958c2ced932cde/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043", size = 1725210, upload-time = "2026-07-23T01:54:24.129Z" }, + { url = "https://files.pythonhosted.org/packages/28/b7/1c2aab8c706436dcc28598452488ac9cd7c409da815237c28c27d58993e6/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427", size = 1764848, upload-time = "2026-07-23T01:54:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/54/50/94c28f08b131c4bf10984ea2c7a536c9920608bb2d6e7f95642c30cc87b7/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d", size = 1777102, upload-time = "2026-07-23T01:54:31.775Z" }, + { url = "https://files.pythonhosted.org/packages/13/d4/e7d09ba7d345fb2d74440fd2fa033c5e079fac05552927705986f41a364f/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0", size = 1580205, upload-time = "2026-07-23T01:54:34.518Z" }, + { url = "https://files.pythonhosted.org/packages/a3/84/072a91d68e1e1eb587985b54baab94221277f877e8ef274fc213a0ceae28/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d", size = 1797219, upload-time = "2026-07-23T01:54:36.995Z" }, + { url = "https://files.pythonhosted.org/packages/e0/eb/aad34e897e668424d6e995da5dff8a4a09af93363d3392488772957a63aa/aiohttp-3.14.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19", size = 1768629, upload-time = "2026-07-23T01:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/6bb88ddba0fecd9122aa3ebcad25996cf6c083a4a7040dbb3a4f97972af6/aiohttp-3.14.3-cp312-cp312-win32.whl", hash = "sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559", size = 451481, upload-time = "2026-07-23T01:54:42.547Z" }, + { url = "https://files.pythonhosted.org/packages/76/9b/f2f8f108da17ecef2cc3efc424e8b7ad3782b1a8360f7b8eae8ced84f6ea/aiohttp-3.14.3-cp312-cp312-win_amd64.whl", hash = "sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a", size = 476845, upload-time = "2026-07-23T01:54:44.853Z" }, + { url = "https://files.pythonhosted.org/packages/3e/44/28dac80a8941b604f4da10ce21097614ca1bf905ce93dca28d8d7de9c1e7/aiohttp-3.14.3-cp312-cp312-win_arm64.whl", hash = "sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c", size = 448050, upload-time = "2026-07-23T01:54:47.087Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" }, + { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" }, + { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" }, + { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" }, + { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" }, + { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" }, + { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" }, + { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" }, + { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/c8/20/887fdcf832326571b370ffc347b3e70abe101096f3720126aac161b1d872/aiohttp-3.14.3-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062", size = 509067, upload-time = "2026-07-23T01:55:42.618Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a3/92cec936f78cc4bf0fa5554ebe593b73459d94e3c62303e1902a4cccb6f7/aiohttp-3.14.3-cp314-cp314-android_24_x86_64.whl", hash = "sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6", size = 514774, upload-time = "2026-07-23T01:55:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/29/ba/2a0c38df3fc557620b6a5acd98364af050053b6285b4dc7ee74100c63c18/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919", size = 488134, upload-time = "2026-07-23T01:55:47.135Z" }, + { url = "https://files.pythonhosted.org/packages/48/d6/d51b7d4bf309af3693940d8ffd2b9ed0b682434ef85959b7c9c137f60cf8/aiohttp-3.14.3-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7", size = 494201, upload-time = "2026-07-23T01:55:49.451Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5a/8f624384e5f1efabb5229b94157eb966b021e97bdb188c62860c2ae243c2/aiohttp-3.14.3-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0", size = 502766, upload-time = "2026-07-23T01:55:51.656Z" }, + { url = "https://files.pythonhosted.org/packages/a6/26/4ff0164370deec18fb19254ee4ab10b7a73304ac0c860b13f5f84663759b/aiohttp-3.14.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924", size = 756557, upload-time = "2026-07-23T01:55:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/97/a3/7056b86dc0d9ec709ea9777eae3b0161428f943372f8b98c01c11593b682/aiohttp-3.14.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646", size = 510168, upload-time = "2026-07-23T01:55:56.22Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/0357a015892fd68058bf2d39d3fd1958e459b997a7db30aaa6aaa434ae96/aiohttp-3.14.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b", size = 512957, upload-time = "2026-07-23T01:55:58.437Z" }, + { url = "https://files.pythonhosted.org/packages/47/d1/8aba53f15ccb2238405f5e9d30e2a8ca44f93878c26e7165ade00d374b1c/aiohttp-3.14.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30", size = 1750149, upload-time = "2026-07-23T01:56:00.856Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/40c3fee327529284375c6701cbb0fa4600cc2e8432af1378f897e2ef7d3a/aiohttp-3.14.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9", size = 1707685, upload-time = "2026-07-23T01:56:03.371Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a3/ca0cc6724cca8114b05694abd916060758c79894c3aa5b012cdadc1bc28e/aiohttp-3.14.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f", size = 1803911, upload-time = "2026-07-23T01:56:05.817Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/85b099c299c3ffd38ad9b3e43694c8a346934e4a30c88c4fd5a841234f77/aiohttp-3.14.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d", size = 1876929, upload-time = "2026-07-23T01:56:08.413Z" }, + { url = "https://files.pythonhosted.org/packages/d5/b7/1da684a04175473fa4cddbf9a2f572e79514c3fd27a74597f43057d4f3da/aiohttp-3.14.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147", size = 1761112, upload-time = "2026-07-23T01:56:10.918Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/bc4b55e3e5cb175fd69c53c90d60d2f47797cb343da5106e23863dc4dba4/aiohttp-3.14.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c", size = 1583500, upload-time = "2026-07-23T01:56:13.613Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e8/13a9d957a1ee40837f46aa30f0f4c657e673ad86a2e6362a9f9be20d26d9/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a", size = 1713940, upload-time = "2026-07-23T01:56:15.969Z" }, + { url = "https://files.pythonhosted.org/packages/38/05/d33c680c1bcf1c7e130f9cbfc1fc02fe8bb0c4af2a94a53dd5fb56131e5c/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0", size = 1724413, upload-time = "2026-07-23T01:56:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/1d/af798d306f7a74b6a632dbcabcf62a4c91391b7582d2a8c6d7712e2cc54e/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661", size = 1770748, upload-time = "2026-07-23T01:56:21.074Z" }, + { url = "https://files.pythonhosted.org/packages/a8/92/ad720d472556a995049206867765e9410969684f86ee09423ff9969044c1/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22", size = 1577564, upload-time = "2026-07-23T01:56:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/60/ad/0ed7586cbef7a884e23a752fa2bb987a122e6a5dd50dab109258d0a95193/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41", size = 1782080, upload-time = "2026-07-23T01:56:25.994Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/dbaed0d73e8a69aad653b045dab451c67c2454bb731a37b45a86593e9422/aiohttp-3.14.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf", size = 1745813, upload-time = "2026-07-23T01:56:28.604Z" }, + { url = "https://files.pythonhosted.org/packages/81/1b/6893d4bc57e434fc93a6c9217c637d967a0b651d989f6e3265179375754a/aiohttp-3.14.3-cp314-cp314-win32.whl", hash = "sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da", size = 455872, upload-time = "2026-07-23T01:56:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8b/c7baa1ba1eda4db6989baefe5de6d99834921b84ebd7918624febcb9f290/aiohttp-3.14.3-cp314-cp314-win_amd64.whl", hash = "sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100", size = 481030, upload-time = "2026-07-23T01:56:33.365Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/c29d067df825a2df88ca432db848aa2fe8199598359cc06c12b09320cac9/aiohttp-3.14.3-cp314-cp314-win_arm64.whl", hash = "sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc", size = 453669, upload-time = "2026-07-23T01:56:35.731Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a4/9c033beb355d39b6147980597ec9645e4729243f686ee4dc73945de72030/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b", size = 791403, upload-time = "2026-07-23T01:56:37.972Z" }, + { url = "https://files.pythonhosted.org/packages/80/ca/87c32a0a7704583cfc49660bd817889bae5b830bf53b5dcb4e92145ac2da/aiohttp-3.14.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0", size = 526413, upload-time = "2026-07-23T01:56:40.523Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d8/8ec0e471248c500acdce2be3f46db8fb62b5eb60efef072529cc85ee1d26/aiohttp-3.14.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e", size = 532135, upload-time = "2026-07-23T01:56:42.876Z" }, + { url = "https://files.pythonhosted.org/packages/fe/45/f8919fd936e8b79fcd9bda7b6d8e62613462a713f4f17987fd7c34399142/aiohttp-3.14.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716", size = 1922742, upload-time = "2026-07-23T01:56:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/9ca76b28a27525b0cc53e20842e0228b022f301ce1f436b7d814b4aaf2df/aiohttp-3.14.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f", size = 1787371, upload-time = "2026-07-23T01:56:48.045Z" }, + { url = "https://files.pythonhosted.org/packages/b1/04/6acdbf17315f7b55f1937e3387acb89a3cddeb4995689553d064af8e92ab/aiohttp-3.14.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553", size = 1912623, upload-time = "2026-07-23T01:56:50.605Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/438b0c79ca6f45eb9fd9817dd4c01a91919a38c0de5ee9e05e2b4dc0ece7/aiohttp-3.14.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100", size = 2005515, upload-time = "2026-07-23T01:56:53.153Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/62cbd6577758699525f5c712d1ddef57d9875fbab0ae8d5f5a202fd598f8/aiohttp-3.14.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85", size = 1879906, upload-time = "2026-07-23T01:56:55.818Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/18bcbf830a21dc3aae24d8f6b6feaf3db1d2090242d00a7868db2ffb0b67/aiohttp-3.14.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33", size = 1675849, upload-time = "2026-07-23T01:56:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/47f4968659c5e23606c3790c80fc624e691c153d036148449ee84d31b287/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f", size = 1843496, upload-time = "2026-07-23T01:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/64/af/38c33c4dd82fddcb4e56c4653b6f1072a8edbc6b7fa15809f14932c41e2d/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0", size = 1827746, upload-time = "2026-07-23T01:57:05.131Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/0537cda4885ac8f5b7053d164dd06312f4c483a4edcb8ee5b8aaf2a989bf/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098", size = 1853810, upload-time = "2026-07-23T01:57:08.043Z" }, + { url = "https://files.pythonhosted.org/packages/19/fe/26f9c5e6458385aa86497836b0dea6fb2f027827d63f37c7856cce9286ee/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25", size = 1668895, upload-time = "2026-07-23T01:57:10.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4c/618b1db9b9ba079b8875d2cdf78e7c4a3bf72903bd5850fee7dd9544600a/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9", size = 1883833, upload-time = "2026-07-23T01:57:13.672Z" }, + { url = "https://files.pythonhosted.org/packages/94/c6/bd959bd1e4771f9fd944e9e436224c48c77b018b73b519b5aad346335bcc/aiohttp-3.14.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb", size = 1844251, upload-time = "2026-07-23T01:57:16.593Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/08d41839658bdd44a0ed2480f3891705ecb487ce28c0dde62c9040c997e0/aiohttp-3.14.3-cp314-cp314t-win32.whl", hash = "sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963", size = 474180, upload-time = "2026-07-23T01:57:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/3cd6ef0a2b2851f7ab913b5b079334781bd50ff56a323e4454063377a080/aiohttp-3.14.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b", size = 500528, upload-time = "2026-07-23T01:57:21.762Z" }, + { url = "https://files.pythonhosted.org/packages/a4/37/cfd1ed540a4d318da025590d96b728e63713c09e9377950fc655dadeb856/aiohttp-3.14.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7", size = 469280, upload-time = "2026-07-23T01:57:24.241Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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-doc" +version = "0.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/aa/554e2614f38fc34c58ff1d0911ae8535ad2516440d5482d76fe59f1088b0/charset_normalizer-3.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1ee1e296209fdce05b81b663250eefa02213a2da7b41bf26f7829b8ba3545aa", size = 369072, upload-time = "2026-08-15T08:16:22.964Z" }, + { url = "https://files.pythonhosted.org/packages/03/6d/439231dfc3ccfa6f8c06477b7da2219cbd41a2de3d49084df8ec7b5100f2/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9fbdce1e47394b09bc9f26ab117dfc8d6491977a11d86f592bb42c779db2fda", size = 251142, upload-time = "2026-08-15T08:16:24.81Z" }, + { url = "https://files.pythonhosted.org/packages/55/53/7d819bd23a00ef45039146fa2cce1daa2f0771e758c5653ee1f6edac91ed/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:00668ebb0609751758682eb0b5857e7c35b9f00e84dfdef062e103244ec94d45", size = 240714, upload-time = "2026-08-15T08:16:26.392Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2c/45847198c16f4b38090cc7423b2b6a9008e438704d8ab413211832498d31/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba2f37ee79e6338845261a3c5b1784e5d1acdff2c0785b284f1b633033d136ab", size = 279637, upload-time = "2026-08-15T08:16:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/69/2b/d8be3523ddf9f0b0f3e56d1359034aa10653a4d11564c697f802b4775766/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ce854f5f478050ade5a238731c4ca985a7d3b3cb53ff600a9b5c3b689b5f0a7a", size = 276543, upload-time = "2026-08-15T08:16:29.399Z" }, + { url = "https://files.pythonhosted.org/packages/32/cd/4f564b8f132de25db594efc706897069f016790cea63a5669c9df2675f64/charset_normalizer-3.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96eefc178f8636b9c760c5829345307fd81cfae9ab1e80997dbddeb0f54ee9a3", size = 261644, upload-time = "2026-08-15T08:16:30.722Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e3/38b975422534a608f98c360e79c2f07c763d66dd4272300d45fb1fee54b0/charset_normalizer-3.5.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:366ec70f5547c640d3ce1985722490f23faf4eb5216a7eeba78277490e78dacb", size = 259609, upload-time = "2026-08-15T08:16:32.248Z" }, + { url = "https://files.pythonhosted.org/packages/87/bd/fbc24d825c66f1c74f6ccdea3742c3d8354a4888e86d1315a197fee69061/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:950f23cb393f85543777b0433f082cddd25b51ab398eac7971146495679efe5f", size = 252457, upload-time = "2026-08-15T08:16:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/2d/918d0e98a0e679469ed05bb2d90c2088b4d315bb612969d8499f76fb5210/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1dcc36dcb96abc02236e182d17e0f71430152a6c2c7447421da2d2dc144edea", size = 242240, upload-time = "2026-08-15T08:16:35.396Z" }, + { url = "https://files.pythonhosted.org/packages/20/c8/c36f6e0b2dfec351bd38cbc05362697e58bcd073d7dbd95154290c9714ce/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:07ffd07412fc5d5e84cd8952acf9ff7e4ed7a708e69d1bada19d8ba91711353f", size = 280308, upload-time = "2026-08-15T08:16:36.825Z" }, + { url = "https://files.pythonhosted.org/packages/ca/7b/311b3e02e8c4092400c449c850a760d8c45d900983c83a70cc07208c551d/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:f5542f9b941279d82d41eb0aa9f98eba36fe4df5c7086c651df7944935b37182", size = 258679, upload-time = "2026-08-15T08:16:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b9/90/082cc45599c392f28c036a497f49e0634041a785fc3849c80ccf396d096f/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a545775cfe815855ea32d7c27731d79da358ef2055b4a25830231b1622dd18aa", size = 277221, upload-time = "2026-08-15T08:16:39.62Z" }, + { url = "https://files.pythonhosted.org/packages/58/ad/b9aecf38d805cbcf84fa94f14c5d972a16561e20296a11dc799a5dcf3763/charset_normalizer-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:494b70049a4d69aec6e8137c13af4cf8db8c9f9820a1392ac293b0dd2987a818", size = 263799, upload-time = "2026-08-15T08:16:40.885Z" }, + { url = "https://files.pythonhosted.org/packages/b7/23/b38a20598d5a825f85d9d7636860e56ff0db1479f86497a6e485aa9326f7/charset_normalizer-3.5.1-cp310-cp310-win32.whl", hash = "sha256:94fbf1c0c6cc0d3d5e50f9a9313a8cdca90dd696d34b381cd1704f8c9e939f20", size = 182037, upload-time = "2026-08-15T08:16:42.198Z" }, + { url = "https://files.pythonhosted.org/packages/d2/21/83fffb77864408b8bf0fe1ca603926401d6f8775a8e150b39aacc9958f8a/charset_normalizer-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:be47f99644b208bff7766314013f9acf57b056b04191d570d68ad14022cf5b1d", size = 206030, upload-time = "2026-08-15T08:16:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/86/2e/b93135b5034b1157fb29554b0d06d4844ce62282f0e0a14036f93d7ee2e7/charset_normalizer-3.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:a6d095662e73e74f0a49988e0593373e243e3a52e27bfeea0a859e88acf4a0f5", size = 185092, upload-time = "2026-08-15T08:16:45.177Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048, upload-time = "2026-08-15T08:19:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997, upload-time = "2026-08-15T08:19:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014, upload-time = "2026-08-15T08:19:03.943Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174, upload-time = "2026-08-15T08:19:05.598Z" }, + { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361, upload-time = "2026-08-15T08:19:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143, upload-time = "2026-08-15T08:19:08.816Z" }, + { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086, upload-time = "2026-08-15T08:19:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231, upload-time = "2026-08-15T08:19:11.807Z" }, + { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546, upload-time = "2026-08-15T08:19:13.416Z" }, + { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033, upload-time = "2026-08-15T08:19:14.981Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045, upload-time = "2026-08-15T08:19:16.618Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866, upload-time = "2026-08-15T08:19:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932, upload-time = "2026-08-15T08:19:19.734Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320, upload-time = "2026-08-15T08:19:21.37Z" }, + { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174, upload-time = "2026-08-15T08:19:23.088Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126, upload-time = "2026-08-15T08:19:24.471Z" }, + { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441, upload-time = "2026-08-15T08:19:26.038Z" }, + { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742, upload-time = "2026-08-15T08:19:27.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298, upload-time = "2026-08-15T08:19:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500, upload-time = "2026-08-15T08:19:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888, upload-time = "2026-08-15T08:19:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243, upload-time = "2026-08-15T08:19:33.94Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871, upload-time = "2026-08-15T08:19:35.413Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580, upload-time = "2026-08-15T08:19:36.886Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807, upload-time = "2026-08-15T08:19:38.392Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083, upload-time = "2026-08-15T08:19:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317, upload-time = "2026-08-15T08:19:41.53Z" }, + { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173, upload-time = "2026-08-15T08:19:43.07Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960, upload-time = "2026-08-15T08:19:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186, upload-time = "2026-08-15T08:19:46.293Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947, upload-time = "2026-08-15T08:19:47.853Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909, upload-time = "2026-08-15T08:19:49.383Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[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, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/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 = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, + { url = "https://files.pythonhosted.org/packages/a8/1f/5ef51f5fbaa5d4d3201bb3d7555af028ec1aa4416275ccbf73c9e34e3d2d/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9851b0caa8bfd3bc6fa054eaf57bea7c8e9c3a62db2d2621224677f49f3c53d0", size = 6675244, upload-time = "2026-05-29T23:11:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/1e/2942a0d6d52240d439f44384a40c65d359d9ca70325b65b265c020113121/cuda_pathfinder-1.6.1-py3-none-any.whl", hash = "sha256:cc8ec4cb0881fa5bcbf96b6dd75d50e55352b74dd33d4f3d884e192e7c2b6ef8", size = 60238, upload-time = "2026-08-18T02:57:50.087Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +curand = [ + { name = "nvidia-curand", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusolver", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, +] + +[[package]] +name = "datasets" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "pandas", version = "3.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/5b/836516269d4f618efe621661cfb6f9acc57e6f95265db3efaee48a5ffe04/datasets-5.0.1.tar.gz", hash = "sha256:ce22bb851efd7494f08aad33b940803784434f6e77763d00679a0dc45fcf686a", size = 641498, upload-time = "2026-07-28T11:09:12.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/0b/98fc6eb83333508ca5f44c52b3e287ea8137a0ad582714e2cbc67a02154b/datasets-5.0.1-py3-none-any.whl", hash = "sha256:9fbf73688f8c18f7529b4fe592abd04015f81d1e58001e4bac73ffb2b39d7cc4", size = 559079, upload-time = "2026-07-28T11:09:10.266Z" }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/30/03b03951873a1a0ffc7e8ca0e10c15597b59e8d0e39260704cd2ea087bc4/filelock-3.32.4.tar.gz", hash = "sha256:2bde2e4cf732e0153406d8a7bc80620ecf5e621fe0d25e41143c4e3b4733ff30", size = 222126, upload-time = "2026-08-23T17:37:55.363Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl", hash = "sha256:22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd", size = 99864, upload-time = "2026-08-23T17:37:53.913Z" }, +] + +[[package]] +name = "flask" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "gunicorn" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b8/ec4ba3f6cace4091c34e27478b576bb80f2f06fab80fd42c0ecc785b308f/gunicorn-26.1.0.tar.gz", hash = "sha256:1413d777bf99d31ebeb08acd354b01f1ecc44db0aa7b811ae7b86c669232e4f7", size = 755923, upload-time = "2026-08-18T11:49:39.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/dc/7a55fc605543fd5cb11c003fbbb21a1911d5e88a582cce6c5e063bf5c176/gunicorn-26.1.0-py3-none-any.whl", hash = "sha256:9f45bcddec5e9dc7a25a3bdccb0c6832f11fd5d4739b1ee36c8d2fec25f1dc86", size = 216237, upload-time = "2026-08-18T11:49:38.001Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/ab/522a2ab67f27971a9d48ca666d4fca85ef7d5282d142e31fd087e27b1bbe/hf_xet-1.6.0.tar.gz", hash = "sha256:2e58454a340b3556dfa4972d5451aff4fba8dd42a236600ba1a1d2b1514f0fef", size = 920527, upload-time = "2026-08-03T22:33:13.243Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/62/3c062f593bd92ef4e77a0ef39541e3d82a0a1d3947c8a777a02a13a27828/hf_xet-1.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:70cbb9c896901600128cb9b6f06e132954fbede1db30f31f7c6c63f84cb7c31d", size = 4074584, upload-time = "2026-08-03T22:32:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1e/c0ad437dd267a8e435bef594acf781bbc3874ff0b6435b4962d03ecf7cc4/hf_xet-1.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:23379c2f9ec8696d952b16414a2bae72cad86a52df869b050698ba60f538c675", size = 3867381, upload-time = "2026-08-03T22:32:49.049Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ee/7c0d7b6ab336167531b1c30af2af003f054af4c749becbd7209ae33a77c3/hf_xet-1.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f2f7278c05c22fd60cb436cda1269649b3e81db65ecdc8496e5e164aa4143e7b", size = 4453982, upload-time = "2026-08-03T22:32:50.568Z" }, + { url = "https://files.pythonhosted.org/packages/63/06/ad8eab1c9525246650cbaa821caa3cdbaca734ab1a5b8c91bea09cbd8d69/hf_xet-1.6.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:948f15d3a9545cfe5932f6bd8b440f6ae630aee108f14b7bd6c561f7c2dcc522", size = 4249445, upload-time = "2026-08-03T22:32:52.391Z" }, + { url = "https://files.pythonhosted.org/packages/d8/26/1eee8aedb0dafc1ab9717dc9ac602cde33361b232dc06803f1f6ed18b58c/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5153e6bb103ad49d6ea9f1b2e230db5a2ea32551ad09a706d2f61d7c7c80d80e", size = 4451099, upload-time = "2026-08-03T22:32:54.114Z" }, + { url = "https://files.pythonhosted.org/packages/67/57/0b88af1f194ab6c9c650547d9cc06bfeaab836ae4dcdb331676bfb8be95a/hf_xet-1.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:35cec30d75c6f9eb9c16a77cef68e85a103b72e24d4b473714ec9ff06428bab9", size = 4664712, upload-time = "2026-08-03T22:32:55.547Z" }, + { url = "https://files.pythonhosted.org/packages/53/a0/26b717a9d1840e8abf48dcec64b5ed8fbe472671d38ad28d30e147132b33/hf_xet-1.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5789835d7c6bc9436962853192082374297fb72d7eff7e7762ec25ceb7e25338", size = 4025906, upload-time = "2026-08-03T22:32:57.391Z" }, + { url = "https://files.pythonhosted.org/packages/49/f6/4a9966633c6fef83af997e2cff68ec1963676d412bdfd096df2a93b8e185/hf_xet-1.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:75765820ce4700db3750c94acc8fe27c5fae4c9ec000a0dbac3ca082acf97765", size = 3849221, upload-time = "2026-08-03T22:32:59.123Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/7afa2c9c787405864fc47a0d1bbc02c62e9101947ed43c1f43899fc7d91d/hf_xet-1.6.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:633dc0cd71d32da58ab8c03ad38e2fac452c15c2b0a2866ebf6ededfe0a5061d", size = 4071729, upload-time = "2026-08-03T22:33:00.721Z" }, + { url = "https://files.pythonhosted.org/packages/4b/69/55b8dcf636142ae660fec1869fcac14c4da2e8412e14d6eee1523be77e9f/hf_xet-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:f0906082d9932ae0c0057fa194041c22b4e2cdb46b2592ef3b91f020d62a081a", size = 3876287, upload-time = "2026-08-03T22:33:02.251Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/a28359bf1c1ecf11eba22123168c138698f7cb576ac678f5a2e16cd5da08/hf_xet-1.6.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d62671bb130879cef0ee4c9ebe47a14af6c66ec53e6d84dc15936e5ffdfac82f", size = 4464663, upload-time = "2026-08-03T22:33:03.802Z" }, + { url = "https://files.pythonhosted.org/packages/9a/69/1f0cbc2fb22ae6082d094f743d1b8945a3f36f6089cb95f42b7ee348cda7/hf_xet-1.6.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0e6e21fa3cdfcdcd76748564bf593870a5e013f47d97cf10aed63aa222cff5b7", size = 4262538, upload-time = "2026-08-03T22:33:05.287Z" }, + { url = "https://files.pythonhosted.org/packages/d1/3a/4f4f2301ade26e404462d3336fa11f7958d914cabbabdd6e03c3c5d5658c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4fc74352a17015bd0ee90038bc9efe38db894cde45f268b6712b04fce8cd0acb", size = 4460520, upload-time = "2026-08-03T22:33:06.81Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/311725e2a905534dfee2dcb5b08414f249147f1f12252bfc2bd24caa075c/hf_xet-1.6.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4f71cba6129110c3374a33f919001ff130488fc23553698e34cc1c2a1198c", size = 4675937, upload-time = "2026-08-03T22:33:08.616Z" }, + { url = "https://files.pythonhosted.org/packages/98/b7/8c59a66d15205024662f1d66968136f13893f96df1ddc5087e2e281fc95f/hf_xet-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:fb4fadde1b2b70bf4c0c14a6dccbe7194b1c28947fefd5bbe3fed9d940676c3b", size = 4033128, upload-time = "2026-08-03T22:33:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/73/63/ca511b6f802f28cf3489b280fe77475bcca8de85e81a6299d7916b5b5555/hf_xet-1.6.0-cp38-abi3-win_arm64.whl", hash = "sha256:3dc3e35441ba395006af5aaacc40ef2e603c51ef46c3530b9156185f00935ea3", size = 3859359, upload-time = "2026-08-03T22:33:11.725Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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://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, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/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 = "1.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/ae/222a91937ebee7f62c0ca8f5ee0afd97577caf24c0abb927d1f5c7e9f6d2/huggingface_hub-1.28.0.tar.gz", hash = "sha256:46a2e950c09234de54093d587d1675382f0d08dbd600d9fb599b5932f5b2c6cb", size = 959609, upload-time = "2026-08-18T12:27:15.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/0e/eafef18f1a75e125e68395db21131db0cf868a128ecd2fce69b4df6c584b/huggingface_hub-1.28.0-py3-none-any.whl", hash = "sha256:58a8bacb03072edfc38067065e9dc24bbb34805410fcd36a1632de0b329660bb", size = 793202, upload-time = "2026-08-18T12:27:12.719Z" }, +] + +[[package]] +name = "idna" +version = "3.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, + { name = "decorator", marker = "python_full_version < '3.11'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi", marker = "python_full_version < '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version < '3.11'" }, + { name = "pexpect", marker = "python_full_version < '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version < '3.11'" }, + { name = "pygments", marker = "python_full_version < '3.11'" }, + { name = "stack-data", marker = "python_full_version < '3.11'" }, + { name = "traitlets", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + +[[package]] +name = "ipython" +version = "9.16.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.11' and sys_platform == 'win32'" }, + { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.11'" }, + { name = "jedi", marker = "python_full_version >= '3.11'" }, + { name = "matplotlib-inline", marker = "python_full_version >= '3.11'" }, + { name = "pexpect", marker = "python_full_version >= '3.11' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit", marker = "python_full_version >= '3.11'" }, + { name = "psutil", marker = "python_full_version >= '3.11' and sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments", marker = "python_full_version >= '3.11'" }, + { name = "stack-data", marker = "python_full_version >= '3.11'" }, + { name = "traitlets", marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version == '3.11.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/96/b150fe7e25a5a29ae9ac1374e71488639605d39a1ea4abb74c9ce33af235/ipython-9.16.1.tar.gz", hash = "sha256:5a3d1f9a47ff216d6cf9cf863124f6a2c1a198d1354c546a4d24a370a283b64c", size = 4515302, upload-time = "2026-08-03T08:36:15.571Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8e/1239df488393d61076653bfb29f759d0f60cab8e030abdf7c17c31539b51/ipython-9.16.1-py3-none-any.whl", hash = "sha256:4acae635506f6d352d94c4899a19d5f85f8bc4d230932342dca556fdab1c69b4", size = 625974, upload-time = "2026-08-03T08:36:13.654Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +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, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[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, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/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 = "kda" +version = "0.0.1" +source = { editable = "." } +dependencies = [ + { name = "datasets" }, + { name = "einops" }, + { name = "packaging" }, + { name = "sentencepiece" }, + { name = "swanlab" }, + { name = "torch" }, + { name = "transformers" }, +] + +[package.optional-dependencies] +train = [ + { name = "langdetect" }, + { name = "sacrebleu" }, + { name = "swanlab" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "tensorlens" }, + { name = "torchlens" }, +] + +[package.metadata] +requires-dist = [ + { name = "datasets", specifier = ">=3.0.0" }, + { name = "einops", specifier = ">=0.7.0" }, + { name = "langdetect", marker = "extra == 'train'", specifier = ">=1.0.9" }, + { name = "packaging", specifier = ">=23.0" }, + { name = "sacrebleu", marker = "extra == 'train'", specifier = ">=2.4.0" }, + { name = "sentencepiece", specifier = ">=0.2.0" }, + { name = "swanlab", specifier = ">=0.9.7" }, + { name = "swanlab", marker = "extra == 'train'", specifier = ">=0.6.0" }, + { name = "torch", specifier = ">=2.9.0" }, + { name = "transformers", specifier = ">=4.51.0" }, +] +provides-extras = ["train"] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=7.0" }, + { name = "tensorlens", specifier = ">=0.0.3" }, + { name = "torchlens", specifier = ">=2.34" }, +] + +[[package]] +name = "langdetect" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/72/a3add0e4eec4eb9e2569554f7c70f4a3c27712f40e3284d483e88094cc0e/langdetect-1.0.9.tar.gz", hash = "sha256:cbc1fef89f8d062739774bd51eda3da3274006b3661d199c2655f6b3f6d605a0", size = 981474, upload-time = "2021-05-07T07:54:13.562Z" } + +[[package]] +name = "lxml" +version = "6.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/a9/970b8fa0ecc4fbf1dfaed0d89bbc1fc1421b25ec26a2038c91e872dc6c8e/lxml-6.1.2.tar.gz", hash = "sha256:1055241852f2b02068af4a625a5d32c087db193c12251928af2562ecd2239f18", size = 4210626, upload-time = "2026-08-19T04:58:15.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/37/6f7d794e93c62cf40025426a6222c08a6ee620b605c3f10f1537dac491ff/lxml-6.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:522387e05cd015a81d1dc621fb167fb42b8f629ccd2e8b39de583828f165aae6", size = 8575493, upload-time = "2026-08-19T04:58:23.411Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/6867418bf427a6b3e9595ac157eb21c850e7e5d8b9d74bead2cc0d3fc6b2/lxml-6.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d86130d70a2557cdf825dffc56255f1f16b83a7bbeab677b4cd040c4c53d8c52", size = 4619345, upload-time = "2026-08-19T04:58:26.355Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f6/23281da5470fbad09c34936df331dcb5eb81edfa21428ea4ebeabfccba67/lxml-6.1.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08cd52e6487435c75f2da0a5b276beef7fed161681b93ab766e66b954f0c349a", size = 5015466, upload-time = "2026-08-19T04:58:28.794Z" }, + { url = "https://files.pythonhosted.org/packages/a7/47/6aa8bb3c1b365f02bcd03a2618695906cd08e989fc3cb8f958476dd6e3bf/lxml-6.1.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:785761d5123f222cd97f2263a510107226fe32ce7aa7824a90616a41c574ace1", size = 5168503, upload-time = "2026-08-19T04:58:44.125Z" }, + { url = "https://files.pythonhosted.org/packages/62/87/298b9e3cd647ad027af6d6e0d57e92313c1fa427ad46cb8fac38d013e451/lxml-6.1.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae520f189895c5dd7eeb2b7a372d464da6f4a1ba1d0ecb741b1d4fe4c1f699ac", size = 5067860, upload-time = "2026-08-19T04:59:16.65Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5d/7770088b7323c595770a61cd9b87cf40f3c9ef763eba6622ad681e9a2d89/lxml-6.1.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83e7510a6dda8df41d1b68b783de2953b3feb55a11dcebf693201ebaa5cc0c4a", size = 5296802, upload-time = "2026-08-19T04:59:22.759Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/818ed8caa380d47a24743260a74a010bd6215f7fb8099736562cc4dd9bde/lxml-6.1.2-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:c20fa05d128c463209ef5323ebf33ee1cac6d87cdc3933fd789fd3c101017c8e", size = 5424636, upload-time = "2026-08-19T04:59:29.235Z" }, + { url = "https://files.pythonhosted.org/packages/4b/0d/5012ab60d5789da55d9fe1cf42326ac9c959948db6d58430932f039ef8aa/lxml-6.1.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:e7269cc410f3cdf84a66914fc0ef54b1618115c87fb4f9a59a05c5dfc23bece1", size = 4783670, upload-time = "2026-08-19T04:59:38.887Z" }, + { url = "https://files.pythonhosted.org/packages/3d/72/9b60a579b4e6cadfabe73137a408b3b5ed4e66d9f4de1c65ba2d720c7f0e/lxml-6.1.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7233a987a101bdf79059014130262a01339094a0a709f175162542f33b55d4e", size = 5373226, upload-time = "2026-08-19T04:59:57.71Z" }, + { url = "https://files.pythonhosted.org/packages/da/dc/2a2029e4207b9cf7c40a2034b1f23e0a7405fb299aa607ddda1c556972b3/lxml-6.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ee23f6599682bd4d48bb757c0633e78774eedfb65a7e52851f9ad182eeeb625e", size = 5116503, upload-time = "2026-08-19T05:00:06.23Z" }, + { url = "https://files.pythonhosted.org/packages/a5/63/fea5bdb16cf6fa99e44b23f5ac864f8f6e292e254973322f04efe2808958/lxml-6.1.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e062f5ac1255dfa6c98e3e3863ec18bc79d0947d22d08921a3ca60cee40559fd", size = 4814057, upload-time = "2026-08-19T05:00:18.145Z" }, + { url = "https://files.pythonhosted.org/packages/14/40/7129ace48559c4b9d2c23be4acca07d5763e0ecc01f8d4ff2f07a325989a/lxml-6.1.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:cb0cf498efa3204621b3c5576f0accd80ad2ee85575f1cae5d2f98de32c8d9cc", size = 5361605, upload-time = "2026-08-19T05:00:23.91Z" }, + { url = "https://files.pythonhosted.org/packages/58/52/d14161a2be7eff8525214060a2350d76422f1697839a5fcc933e272a08aa/lxml-6.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ee7410c98222070fd717ad881ee2a80cc11826b7001b9a5a807155d8918bfc7a", size = 5321744, upload-time = "2026-08-19T05:00:40.187Z" }, + { url = "https://files.pythonhosted.org/packages/36/53/46849b3250063701ec30a9905a5c928f016f09dba3743bb69ed433433db4/lxml-6.1.2-cp310-cp310-win32.whl", hash = "sha256:aa224ecc613d411690aa650dbf01daafbe385cd6c67145e80bc5fc01b3a71469", size = 3604437, upload-time = "2026-08-19T05:00:50.125Z" }, + { url = "https://files.pythonhosted.org/packages/1e/83/0e506436ef9fd7ec9e25bd31a3f18eb8103f5c8e639892ef2fedf3f4dc40/lxml-6.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:1c0173595dc1c25768f42681a1517dcfc74bb18a34695f127931cbd05f4dead6", size = 4029081, upload-time = "2026-08-19T05:00:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2a/a3e037aa6f24d3addfbc62875dc44d5a007127a2cf519c1c40fdc110536d/lxml-6.1.2-cp310-cp310-win_arm64.whl", hash = "sha256:adbecbfe44a497c742792457b1c27300617967c18c3934d2416023eba8d8c553", size = 3674566, upload-time = "2026-08-19T05:01:04.572Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2d/c292b75049d8b919a515a439646307b971a5f72cd99aaf77d59c9a99e7c4/lxml-6.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6a4f55f0e3308c07354b1ee239c5550afc212f81629a6067db505ace3b667a", size = 8563059, upload-time = "2026-08-19T04:58:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/69/55/16395f232cb28182c72a1fb4d9d187163fd05a581a98c37f33e945b77a6d/lxml-6.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4f4d2c36fd5997d30ff19c29fb93293401d0daaf87512297d47610e6883964b5", size = 4613599, upload-time = "2026-08-19T04:58:30.589Z" }, + { url = "https://files.pythonhosted.org/packages/08/20/a65a084596ccd7fd1ed0668b4cf3b68e700da4eac830a0f22ac569f19a73/lxml-6.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1d55a614d2f0457b1f7511c1b7bec0db0dcdd4af4d09d226829eb054c647527c", size = 4935619, upload-time = "2026-08-19T04:58:45.181Z" }, + { url = "https://files.pythonhosted.org/packages/e1/35/008bf5a5f8809a90a3e62909d8d4458f09b7c034c365b508990bdc38b5b7/lxml-6.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:575fef7f30048b744dffb3e4ff64a18cac7dba3fd26efdea5730ade9d1bdeb33", size = 5078913, upload-time = "2026-08-19T04:58:53.376Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cf/041b4c15ba3b0421ed828af60993f23cf6e5ea8801efb773b19e248fc6a5/lxml-6.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79b428c3242e63bdacf3b526a34e0b8b26583846fc597da84b8f0c3d5ea446b2", size = 5012236, upload-time = "2026-08-19T04:59:06.663Z" }, + { url = "https://files.pythonhosted.org/packages/06/42/89a2760cd2f2cda28ef5b9591ec775a6a5183d193e7b62ddb936b1565167/lxml-6.1.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12ecfea07d767f6accbf30b014e1c477b5eabb13eb4e8c748215efb52c0e314a", size = 5211283, upload-time = "2026-08-19T04:59:31.561Z" }, + { url = "https://files.pythonhosted.org/packages/5d/0d/f5607ff466d0d8874d7b778c3ccb64f65ccc0ac430e1961969fd450b899c/lxml-6.1.2-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:bfcbee8ffff4188f4c6d97eceeff36d8eb983cf838933cbc12ce5f5dd51476c6", size = 5343352, upload-time = "2026-08-19T04:59:41.056Z" }, + { url = "https://files.pythonhosted.org/packages/63/6a/77713b73265d043a513d9e7df2458f07b2a14709f95e3a35a34834785fde/lxml-6.1.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:822d9397033edbe530a13bb1e0091c0e817536b6aba87a9b4ad626ed779ca0bd", size = 4673191, upload-time = "2026-08-19T05:00:01.85Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/e4179e0b9f71859bf9a56b3da91db4c7e85c47072018e7b63e019ff65c9f/lxml-6.1.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4303f904fb6c41b58dc70743b1d8a470aba6c9897427c48324cff1a95673ddb4", size = 5281079, upload-time = "2026-08-19T05:00:20.59Z" }, + { url = "https://files.pythonhosted.org/packages/22/f4/358200b95081db4fd02c4d81938a07080ae7636f9149befda1c0e5189c40/lxml-6.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdd35422de747237f451e821766e2b6be3dd2c31955c1ecd7f17984c5b9bb62d", size = 5055515, upload-time = "2026-08-19T05:00:29.28Z" }, + { url = "https://files.pythonhosted.org/packages/fe/06/8fe708d90022bd13122c359d38f3f751e4fa71b871eace7fa81212dadfa5/lxml-6.1.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b3ca02ef3b5920b88119c82eb6badfb2d082b1f681d528a856dcce17c8706da8", size = 4722745, upload-time = "2026-08-19T05:00:49.132Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1d/9d374182c2ee79a5097d4950bfca9e28011eeacdf614db022b9905266b5c/lxml-6.1.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:4bf14db2f0214003ec7f46c4300e2065668fc93e20448c1c95bac2e952072168", size = 5268962, upload-time = "2026-08-19T05:01:15.762Z" }, + { url = "https://files.pythonhosted.org/packages/72/89/d0835e464b84d92c43d838bbeaef02f9ac374ab2bb6972411e4c3e80975d/lxml-6.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2afd1688e372d8eafaa6f56c589399e0a87d086a0c110f6346b0b50f42e67e25", size = 5235564, upload-time = "2026-08-19T05:03:11.298Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ea/0b8acc86d702b9fa1a0194fc7e653087912d340cb10507f4a5bc369d04b3/lxml-6.1.2-cp311-cp311-win32.whl", hash = "sha256:aea814342f6afd20d832937ff8b333cd6506428a39c0c4c70c2380aab1887bfb", size = 3600342, upload-time = "2026-08-19T05:03:14.238Z" }, + { url = "https://files.pythonhosted.org/packages/65/5c/04480497142794bfb2d98c01ea9972e9b3d0f6b1f017073cabb74ab0b8c1/lxml-6.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b3db5497af55f7a557c95265dd3b91c75dc56364a7b59f258c45fa5576dce058", size = 4032771, upload-time = "2026-08-19T05:03:16.934Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/4c5ca0f808a80b7eaad073269f1fc53992c5c7c905df13d3953d886834b1/lxml-6.1.2-cp311-cp311-win_arm64.whl", hash = "sha256:e8dc3d29f2ed2bbf24c205a86326d6681230ace55abfb3f9d5230f42078ad63d", size = 3674380, upload-time = "2026-08-19T05:03:19.158Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/55eb54507073089ab27743c5da2113c84f0d0b1715b33175fdd943c9652d/lxml-6.1.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d506bdba580ecb1a6ad2e2b5c49445e66d3e1f95894885739094393a1aad237", size = 8602111, upload-time = "2026-08-19T04:58:28.017Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bf/6332f45d78da385bb01d5cac3fe4acda19f025d1307cbc7ad538355fecbb/lxml-6.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12acd337d2821cb8b9247dfe4b7aa2f2769a3df5ae8511b7e550df42b8f4d3c3", size = 4638376, upload-time = "2026-08-19T04:58:41.181Z" }, + { url = "https://files.pythonhosted.org/packages/68/e0/21fba0fe74d417fbe976903ae6bc77e92cdce01aae7b636abd87756f4588/lxml-6.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5078ff51e6316c0f75ea8127c2cd24374747fb351f62fb93d1761f8ae5a04a40", size = 4939689, upload-time = "2026-08-19T04:58:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/ce3e885264fdd0bdcb6b49c1ea1842f94281b39e4ff956099e8d57532c60/lxml-6.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9477e14217c212e6023c994a71a1a349db19b0e10fd5bf189666b281ae63b1fd", size = 5105185, upload-time = "2026-08-19T04:59:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b6/990a8446c488c70fa25681e150de94b7bf2eaaf387e374d195ab3c8faafb/lxml-6.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261d98065326676d7253882db0198d0aa06748d7ee0443367acf10b148273f99", size = 5011863, upload-time = "2026-08-19T04:59:50.58Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6a/f70f41363dae27e3bfd6224b128f5ba150874bd32ca4938552930ffa33b0/lxml-6.1.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0666943ee1576fa890a6dc6316ef42e8241b5dd56f67bc5475acb2ac298c6ca9", size = 5638234, upload-time = "2026-08-19T05:00:00.802Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/a65b64f34d556925faef2c4f14167d58c571bc15a3e1f2bba71138830562/lxml-6.1.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04cf9e3f4ee9cab9d9ba05401bef8668840fa9620fcd4d8e85a2d2fd0b0fa960", size = 5244532, upload-time = "2026-08-19T05:00:07.516Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a9/471552e015e954fc9d960aa27c3d67ebf489683d03f033399a790417c67c/lxml-6.1.2-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:9429d2371d406344ed1da5b5686d9412e74137c07b0171278368ff704f470ed5", size = 5358194, upload-time = "2026-08-19T05:00:22.747Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0f/bc6248fbec2cc416f102b1267f1567e07510f6fa909bbe8cd2a22d6fb78e/lxml-6.1.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:eff128ffdc093cc6317955934ad9751105d37ed8dbca3ff4ccd751af6be37185", size = 4704432, upload-time = "2026-08-19T05:00:51.115Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3f/cec859f50e63f1fa338fab43d2362d7543e1237f2475960d8ab0769de0eb/lxml-6.1.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ba58574d710b82ead7cbedea01cac3e110bc3ef82d4731519b74a2c11f7cf5e9", size = 5255038, upload-time = "2026-08-19T05:00:58.895Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d9/2ced0cf2967115f92a1b8b3ae6bd18763abc3ebef88c98cf25145fda396c/lxml-6.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:52f6d4dff133c9778a24e9a2cfc1608930b15869866171aacc5131b5a418a003", size = 5054481, upload-time = "2026-08-19T05:01:10.096Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f5/4f07386d3c88673daeec3b8cc09a2a4d39fa01c1fc49009791b0746d97fa/lxml-6.1.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8807998c1023d1e9d60e02500f90e85a0752dbc0b670989806bba87b82dd5b42", size = 4785535, upload-time = "2026-08-19T05:01:18.909Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5a/f4fe3ecbc189f48fba2547c5db5c940a10151d3e86b856a60a533a77e816/lxml-6.1.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2170d0a280c877b6e2dc6738217db947be35dd8cf09ca458b355aa1bab2a9e70", size = 5655337, upload-time = "2026-08-19T05:01:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/f586aa1bf27bfbace2dfdbb704da5c52f0bdece8ee440c8fb4946c940b2e/lxml-6.1.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c67f3c1278f942e97d8665c2a690324aaea5137de16f056583a21f0ac706177f", size = 5245778, upload-time = "2026-08-19T05:01:45.227Z" }, + { url = "https://files.pythonhosted.org/packages/18/a1/677494bbaef4d6db5e4633af817414f478865850b55c03ae4bf70fa7b8ca/lxml-6.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:093fbf547d0f3ca02705381f795a050fbb58988be4aac7f79f99f280c4082313", size = 5267274, upload-time = "2026-08-19T05:01:57.687Z" }, + { url = "https://files.pythonhosted.org/packages/5a/71/b71425b8764d4cb7c92eb970483be7d5610dce2a6316242b5aaae7d260be/lxml-6.1.2-cp312-cp312-win32.whl", hash = "sha256:be365ce8d2d411cf2fb573747684b4fd470fa6224e0094d9d5a21155acc369d3", size = 3602563, upload-time = "2026-08-19T05:02:01.837Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fb/909584e16d2148c1a252cc2c32dd99fe0e2682459c586d3d7a192e74a0ae/lxml-6.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:b97153ca609b434b712ddfb92cd6af101a7045a7724c542258bd4727a344472f", size = 4005965, upload-time = "2026-08-19T05:02:07.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8d/41207c9212caad0b52749e34739fb9bfab67486729f52a8fe9bd9266fee6/lxml-6.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:7feb72424f19a893ae4f3373c7aae821b1aacb6076b708915c651f0683a97c49", size = 3666641, upload-time = "2026-08-19T05:02:11.3Z" }, + { url = "https://files.pythonhosted.org/packages/61/2a/e9651f47a31a60b5cae031abc23391ed9aa30c8fc07571d1a38f58d6d770/lxml-6.1.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:351318f5c0eb7fcab5b4fdb507c6f88fb2c4b5e67784c7e5911448c91fffb5d4", size = 8590165, upload-time = "2026-08-19T04:58:40.489Z" }, + { url = "https://files.pythonhosted.org/packages/61/87/a8098abaf35118767d1703b84c98940a5d833064e0eca39a00ecfe9840ab/lxml-6.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0edde95e4b4278dcc0175eda06dc8aa2631ad9f83ae5dbdbc4f0925e200b0b0", size = 4632474, upload-time = "2026-08-19T04:58:47.465Z" }, + { url = "https://files.pythonhosted.org/packages/93/cc/fe74d1def7f4fb967c4a825608a074d4dbdbb871b0d6bd59c6ed07d67868/lxml-6.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8326e24ae6c3a6bfb03fa8b4793f9a5d804c125228aa067f652b0428e31b87c", size = 4936196, upload-time = "2026-08-19T04:59:03.477Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ad/b96e6ca926e26726a99aa643602aac7411ecc1731ddb1b25af8cc57edfcd/lxml-6.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7c534ed898413f439b048130011e99a4245ee13d62d431f6b4f7f2484d02a93a", size = 5093290, upload-time = "2026-08-19T04:59:17.498Z" }, + { url = "https://files.pythonhosted.org/packages/d2/84/616f5d3b7cd086fcfba3e5add6fccda67f976c1c753ae9ed7bbd317cb9be/lxml-6.1.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e37fe49fe2d5aa40a2cb1cc8176673ad7de0d124e6f4a509d9318f5979c7871", size = 4998767, upload-time = "2026-08-19T04:59:28.385Z" }, + { url = "https://files.pythonhosted.org/packages/80/88/d5b453a8d083483c9442ad7f5ac5c560796022eb5c80d60b65d75e449236/lxml-6.1.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9b52ea73a37fc64aa3357ff8607801d46dd170506d3cf8253a91a1d91639d4f9", size = 5626717, upload-time = "2026-08-19T04:59:40.045Z" }, + { url = "https://files.pythonhosted.org/packages/71/45/31e5aa4d4bae024908ba1d03480c7425cf027a28b7e5c88d1b7202bd80cc/lxml-6.1.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8b9a92652e75e7731309ea51db5dee892eef414ce70a6ec3441e5d36bf5189f", size = 5232330, upload-time = "2026-08-19T04:59:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/2627912420df8b2d31ba3014da5539f15ec85add01d42048864ffefda516/lxml-6.1.2-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:9088da25ecd609965f838d89fda0465a905b48f4dd90331db9845518f2177372", size = 5347054, upload-time = "2026-08-19T04:59:52.762Z" }, + { url = "https://files.pythonhosted.org/packages/16/86/54ac0f529b22a8f12313726dd49e12961bb46471d9028cc28d2a29408f0b/lxml-6.1.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:0349321a0537d4fdbebb2af06dd1b64676132c72e2ae250de8cdb58f8c43019c", size = 4707275, upload-time = "2026-08-19T05:00:04.836Z" }, + { url = "https://files.pythonhosted.org/packages/3a/42/ffcdc6e4519be90df907cdae7e88409efb25d823ae4de8846f737dae1884/lxml-6.1.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b20440e578d269c5e8a722ab602ddd0f0cedb8b080006b3f936da9991a593d3b", size = 5240071, upload-time = "2026-08-19T05:00:19.604Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/5b1d7ab35f013f1127ec48f3108319f58b65b00d5cb26f215adbe86eadfb/lxml-6.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7766e525282dd38fd89567311323e441996eb958e8e816d16b38f782e3aecd2a", size = 5050356, upload-time = "2026-08-19T05:00:27.968Z" }, + { url = "https://files.pythonhosted.org/packages/b0/57/1cf049d054189b55c8fe8012269234f6602256949b69cd3ba80608a88219/lxml-6.1.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9221442682c27417f10fe11184ea4cce174b25ab52465570b1f3ee3f85f320fa", size = 4780394, upload-time = "2026-08-19T05:00:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ad/064488a8fa60e639fd773e421a18bf17541d02a95fbf36238ad7c65f69d4/lxml-6.1.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75530642d8471327e691ab9b0513a5f9c77f38871014ceda40f51bb51765c0a1", size = 5645854, upload-time = "2026-08-19T05:03:42.697Z" }, + { url = "https://files.pythonhosted.org/packages/85/bb/120e56f3cf1c149bb3b014278fb86d0a6dd552403981081f0ee0a0a57be7/lxml-6.1.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:678e35f1cbca98f55107511ee21a60568535c950f3c2371819bd64504c980d20", size = 5231132, upload-time = "2026-08-19T05:03:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2c/7d49aab893c128671a3276580074cce4c002896145b8dd2893da79633bca/lxml-6.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c2bae42b3a09f977330a08f4a8fe72aec58c4bdb89069d3fe7272a71d885881", size = 5256076, upload-time = "2026-08-19T05:03:48.092Z" }, + { url = "https://files.pythonhosted.org/packages/72/28/ddea3aa1fa9acfd384fe34d4a2a93eecc07541dd2d922fa9b140c60d8014/lxml-6.1.2-cp313-cp313-win32.whl", hash = "sha256:5848f3de6a8de8a93cff9f068134393ff5fa69ac2a04399f7d49cd67c61c348c", size = 3602177, upload-time = "2026-08-19T05:03:50.571Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7a/96bac167538748cae2544335855f812fa33e49a9a67bc8b8520dcbd592bd/lxml-6.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:6cb0c87421946030b92b558be416852780a912454e3dcba0998e4497c9c588d5", size = 4004117, upload-time = "2026-08-19T05:03:53.074Z" }, + { url = "https://files.pythonhosted.org/packages/0a/24/9498fa3c84135956e5ef55ea4d8bd11e999e381f7f210fb6f8c6a980ef03/lxml-6.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:648861c19b775b89ebefa14586f85090b10163367476d77f242c4131c835ce73", size = 3665412, upload-time = "2026-08-19T05:03:55.621Z" }, + { url = "https://files.pythonhosted.org/packages/27/b5/728b0578791b397ace8d1b101c8b3fe10f36043542f7bb85f82d8bdc3f50/lxml-6.1.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d50a44113fe6800dcc8a859332b823a4735b1e6ae1b0063882e4cca569ec3e29", size = 8609651, upload-time = "2026-08-19T04:58:42.42Z" }, + { url = "https://files.pythonhosted.org/packages/3c/6b/49209fa6225c15c48a30061f03d3aba75e3c19634813b88bf83b88c525ed/lxml-6.1.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fa813b0247d0543a563b993ac3dba6168eef59e3a61448432cf5453300c2412b", size = 4639588, upload-time = "2026-08-19T04:59:01.501Z" }, + { url = "https://files.pythonhosted.org/packages/20/86/80bae4e8bc2eed9d6f017701a3d86fdea56936218efa738911d0b76aa7f4/lxml-6.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d858e718b94033ab4b67e4a58fe3114c65bae01ae2314a62fb39ae8897ed4324", size = 4964846, upload-time = "2026-08-19T04:59:08.59Z" }, + { url = "https://files.pythonhosted.org/packages/70/ce/4782caee7a22959c1ac67cb46495e03912c22a4ba7d20c163496a519e815/lxml-6.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e3b666f57a5d81562f38c766c762416b0f6eb58a00590546911514b48412abd", size = 5099288, upload-time = "2026-08-19T04:59:18.649Z" }, + { url = "https://files.pythonhosted.org/packages/32/21/f120967cc43b54e05512dff0c39726b832c836195d30f41f88733ef36ac8/lxml-6.1.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26ff164c6629e5c4d11c9e55d5ea3d6eed0be2a420eee1f55cbce6e2c23e231a", size = 5036837, upload-time = "2026-08-19T04:59:47.217Z" }, + { url = "https://files.pythonhosted.org/packages/61/ba/8005e9f47598e3ec5c18312c77f94e889580027616678848405c6aeba5de/lxml-6.1.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:962c12b51d0b164f12569af225dea57568477e24a845b96eaccbef6c07e4cc03", size = 5658569, upload-time = "2026-08-19T04:59:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ba/add33b3c7ce51462cf7a4637bcfec2eaa258364d6015b989dd7d1216e6a6/lxml-6.1.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47e367dfe341521426692819803e260d0673899c0ff611f14af978d725e2c999", size = 5246003, upload-time = "2026-08-19T04:59:59.764Z" }, + { url = "https://files.pythonhosted.org/packages/05/b3/a43012748fb861c914c5eac1c1a3bad44282e767499cd02280d4d1edf092/lxml-6.1.2-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:92c2b366028ac01e90399e6d17734ce6e4f4aeddd8ba75fbaf80ea11d6c6d645", size = 5354047, upload-time = "2026-08-19T05:00:21.657Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cb/813021d9a445713b8d758b9e5eae2ed392cd598d9f119d9b053b37c2ab93/lxml-6.1.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:7e81fc065ede5d58dd0bf0912025aee1bd04c52c2affd61fdb93226a97ce2fc6", size = 4704382, upload-time = "2026-08-19T05:00:47.067Z" }, + { url = "https://files.pythonhosted.org/packages/17/c9/1155299f4577bebf3c280497534a73e4b8ad8cab3b96074731ad10949d4e/lxml-6.1.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:633ac039cb32366dd5935868e041e385875c017b8cd54ea56aeee3fe29ca5935", size = 5258530, upload-time = "2026-08-19T05:01:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/25/6e/d76e58384b378b877e140e25b9a9835da00035f81ff70cbe943a3749bf27/lxml-6.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f3194777c0d05945ac91d8594be25d2679d1d826e01e1fc90bae568ff3a547b", size = 5089919, upload-time = "2026-08-19T05:01:33.602Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b7/898013c0f8891481d0624ab3bd5dd8c8ff827232dfee2a5d1f8bf970a7cc/lxml-6.1.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1133bd969f2bfcc6b0c0cf7cdf5f2631e62b23fa2471ee8bd44f6ab73554ee9a", size = 4741972, upload-time = "2026-08-19T05:01:38.18Z" }, + { url = "https://files.pythonhosted.org/packages/c3/47/efb53c4d7b655831c03317a450d9da439b0829c61f34d9d4fe7c863445d6/lxml-6.1.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1edca8f4a92b94e873093df959f141d388f2141fcad0c47598442fb4730ef57a", size = 5683241, upload-time = "2026-08-19T05:02:00.731Z" }, + { url = "https://files.pythonhosted.org/packages/da/0a/0ff36a584cbba14a71326ee8a5300694400f0b97927d1f90a87d95b17d4a/lxml-6.1.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8512b3775d68994dd1d6d533161e0a214f2ad9c634659d34a99c98e86c6c3d68", size = 5245892, upload-time = "2026-08-19T05:02:06.108Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9e/303717a1aa56d4bd775c91936717d3c9e8d999a8e8b68b00979c4c1f93d0/lxml-6.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5005c0c9e4d749a76a2ff8bd5918a8bb248df8e08e73a55654b9f79c9cd1e2b", size = 5269528, upload-time = "2026-08-19T05:02:09.883Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c2/2ae7cb97089eb86bf0689516db3cf280a007b6145853d2a0235a1f01683d/lxml-6.1.2-cp314-cp314-win32.whl", hash = "sha256:e17e2c30e27f56da5551e7a425888b45f013e940b99ab07d125a1c33f77a4605", size = 3662743, upload-time = "2026-08-19T05:03:02.513Z" }, + { url = "https://files.pythonhosted.org/packages/77/13/a3d483230a09201e211ceb1aa208b1374d27d23b8b180d74dba14b30f6b3/lxml-6.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:87e9673cd8a3445024fe38e7f91b55fa3428437eec9b7a7ff7d81979520c0d2d", size = 4073942, upload-time = "2026-08-19T05:03:04.864Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f1/c1445d4b6ad7c51e39d4e2ebbf015a4880f5b297a4ab0e77e4d0e5b70110/lxml-6.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:878e7c8ada8f92c52f13f35a2ab98ef0adf7fd0211d164fc2af589e4c3cfed63", size = 3749235, upload-time = "2026-08-19T05:03:07.239Z" }, + { url = "https://files.pythonhosted.org/packages/9d/eb/598c76f4ce19a67c635e86a46d880cc854f308f39a6f1fdf13bbb01813ec/lxml-6.1.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:94162456ed0a64fb1c06915df5bd06af4675ae3966d6048fcb73b0906e0e0222", size = 8860315, upload-time = "2026-08-19T05:02:14.39Z" }, + { url = "https://files.pythonhosted.org/packages/da/c7/1f9fac7b566a86ad0da13dcc0259164266469c0ad86744c740ccd5c2a081/lxml-6.1.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4b0fa7109b1d0bc1747d8241a0853e135eefb1c978685241b544c46937383efd", size = 4755176, upload-time = "2026-08-19T05:02:18.705Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1b/cfda9307388d496e7eeb7493d9455896b8137ed95f51f3d6ae6ddcc14a47/lxml-6.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:604f4778632588d7c000e7e19430639dc12fca58b5b6e99edffba7631725ef0e", size = 4979444, upload-time = "2026-08-19T05:02:21.262Z" }, + { url = "https://files.pythonhosted.org/packages/e5/71/f732c8919c45b7f29acf443288c6e90036877a67bfeeb1acceb0fffa011b/lxml-6.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a096d6a5f96b776a5b020cb45c17c545effd2a3b6639e6fa97bc95537600923", size = 5115887, upload-time = "2026-08-19T05:02:23.62Z" }, + { url = "https://files.pythonhosted.org/packages/30/00/121d52b944f41e33ea86c62875f902d24982842dc7231ab154ac5a6c6593/lxml-6.1.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6454d184d556eaf4cb3d6f69e405d21602d6fdcf08b8d57796824275986c6595", size = 5032418, upload-time = "2026-08-19T05:02:26.114Z" }, + { url = "https://files.pythonhosted.org/packages/70/19/cadb73c7fe48c7563dc8ab62ea53d5b920c8911bfb808507a6daa82e78d2/lxml-6.1.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b68f2548259bb04e0b3d5df0c397abe8b0080f5e1ffe4019fb7a8bf01a9339e", size = 5603304, upload-time = "2026-08-19T05:02:28.694Z" }, + { url = "https://files.pythonhosted.org/packages/13/32/9de126a14d5a5db8c371c5ec869178417db226707b62a47273a95ae6df7f/lxml-6.1.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c9cc4b6532abe154dbdebb42aaba8d52c852919591e45067f5b7d46a0405e88", size = 5228938, upload-time = "2026-08-19T05:02:30.99Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9b/22dd9e843629ed04652591fb220eb2bf2394d97be3be377d60d8083405d7/lxml-6.1.2-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:57188e441ab24f906bd5a5c14eb55363ab51aa6c0de549f3dd320043721cc118", size = 5317790, upload-time = "2026-08-19T05:02:33.301Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2c/b12a1dc121f81c280635c721c7bcaa341441fcbe37397f60b8915048aece/lxml-6.1.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d0bfd719c254bbe60ea022cff0e6ffb799a6fa7d4d72852cebe0257957b32d68", size = 4646468, upload-time = "2026-08-19T05:02:35.504Z" }, + { url = "https://files.pythonhosted.org/packages/57/41/fd87a41edc531e7969c25ab1d6b52b5b041eb108b88f6394d6afb4374396/lxml-6.1.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:be6f87cd224254a8f81324e34cc655508b83f1d70458a1a39857ad2aa9925852", size = 5240607, upload-time = "2026-08-19T05:02:37.805Z" }, + { url = "https://files.pythonhosted.org/packages/6e/30/713ba813b6e6673c6dc34733746516017efcd17949b767b154cc50bccf20/lxml-6.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:074a88f70a7360a4a0c5be5d898062cd26f898c25b459efb1bdd43ae700c5a1a", size = 5086495, upload-time = "2026-08-19T05:02:40.099Z" }, + { url = "https://files.pythonhosted.org/packages/33/f8/6532ce0fecd9c326d06b08274ee075cc28dbc9f5e9285355db8504689114/lxml-6.1.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:9031f5f01452681abf39fdd65f84a70cb01a7572a1bbf570042e826b1232d07b", size = 4758801, upload-time = "2026-08-19T05:02:45.434Z" }, + { url = "https://files.pythonhosted.org/packages/74/b6/5a1f7833ebaa0dd33c28f6f9755ec6ff3891bf63f097634b44e6da1bb65e/lxml-6.1.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:cfeac14425fc7a6fca7864b774d4ee63547926158f4a18c67d77b2c9a948acf1", size = 5626977, upload-time = "2026-08-19T05:02:48.092Z" }, + { url = "https://files.pythonhosted.org/packages/e6/20/6ae0fc1b45e20877cdcfb1168ceeaf9abb0fba5ed36bd639a260e7b2101e/lxml-6.1.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8ec111ff8067325f85c08aa9c2b26179ec0537bb89c003fde31127139f85f82d", size = 5235036, upload-time = "2026-08-19T05:02:50.726Z" }, + { url = "https://files.pythonhosted.org/packages/47/b4/2bc7b37fbb990ccfb7d30393660741592177224a94e07d842c8da70638e8/lxml-6.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:48e912f37c99a297175ba955f55a47c0e1c834b506ef162e52a6e4fe276e6e45", size = 5252270, upload-time = "2026-08-19T05:02:53.454Z" }, + { url = "https://files.pythonhosted.org/packages/4d/0b/07fb8e1dee29a78e2c5fa5c6c914218be76a6406baff27907429566e90ec/lxml-6.1.2-cp314-cp314t-win32.whl", hash = "sha256:7c444c3a6e8e75334879980eed96568f0e12064c8b1913424eac1805e976736b", size = 3902666, upload-time = "2026-08-19T05:02:55.607Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/3371527bd9820aae6f511697c93032ed197b0d8dab0f17818f18d3099637/lxml-6.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:7f35ba7667004ecdafebbe08da7c9fa06ee6195275bb7ef7a29ee1901e69519c", size = 4401011, upload-time = "2026-08-19T05:02:57.899Z" }, + { url = "https://files.pythonhosted.org/packages/e6/bb/e6de9b2546a4e6df4fb52fb18921906a8b7a041aba06570995759a4d6d8b/lxml-6.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d117f39b28ab8a330a74abdbe61c2255b51973b238db25fd6c2448de1eb2a02d", size = 3823384, upload-time = "2026-08-19T05:03:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/0e/83/7ff98683e14a148191278728d11ba782c3d5137886d49fd95ab4036efa1b/lxml-6.1.2-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:1e3c67b817867c484794d7fe0d73045d7d0c67460c78a0a1249a9e92266e6a0e", size = 8609183, upload-time = "2026-08-19T04:58:32.19Z" }, + { url = "https://files.pythonhosted.org/packages/24/39/c39f05e8240e98009dd3d4ceb248319d0f36467babc5f90a909ed0c5b68a/lxml-6.1.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:d3e97ac4353cca3fbbfa829bc0c6a913771573d1c6d46932d4335c46f2b7796a", size = 4639898, upload-time = "2026-08-19T04:58:39.017Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bf/25e26b089510940a0777ab334357874569255e50930224c8159cd649e754/lxml-6.1.2-cp315-cp315-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:827438bf6c8292d22a409bb7990d7cffce410f33e7664e46ca74d2ecc26975ef", size = 5037527, upload-time = "2026-08-19T04:58:46.224Z" }, + { url = "https://files.pythonhosted.org/packages/65/6d/aed3a58a3d662f7367a537fabe8c549f1446dbd043719e0ae8cd53f47819/lxml-6.1.2-cp315-cp315-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c470d192e27f97842a068cf12a1c1296b20ca716c56a9249715c6654bc192d19", size = 5661918, upload-time = "2026-08-19T04:59:02.534Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ca/706d32b6957c0c2e005a9833e8fc528449196b38d5cfcf9e0fd86a96fb00/lxml-6.1.2-cp315-cp315-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef0b8ba6e13597f681b2b4924ca9c4e8c88420bf0e21d9a9006c757f2fc39d1f", size = 5249359, upload-time = "2026-08-19T05:04:01.956Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e9/445ff43f56fcffa06f6f3a7189920c216f3eacef68ef834d4111cdbd86ba/lxml-6.1.2-cp315-cp315-manylinux_2_31_armv7l.whl", hash = "sha256:65c32ddc5d0750129c7b119fb57d48192b76d334c21e6b690d19dfb06b34af79", size = 4704548, upload-time = "2026-08-19T05:04:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/69/78/20b8b7e79a1b1d9cd4465c332d62962858562b446692f16a27068fa54b85/lxml-6.1.2-cp315-cp315-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0aa07065497f191ad26c4b587ce5dbb5a7105285a3789aafd0661750e8bac537", size = 5261170, upload-time = "2026-08-19T05:04:07.336Z" }, + { url = "https://files.pythonhosted.org/packages/54/ca/84a0e1148bf511e12e0d99732a4e136a3bf1b91622f0a1b197796e2ff984/lxml-6.1.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cde6b8db7d2e5135129eb5e74b7b44dd2053aa767cd5023541fccedddc262453", size = 5090576, upload-time = "2026-08-19T05:04:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f8/1ef6fc7070bed8753315f2e4ea66bc0d37620e1444d014db7f0267b8faaf/lxml-6.1.2-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:b28842b30c4bc2e6afe137d98a5d2071a62589471e76d053bea55b0e53298af9", size = 4744614, upload-time = "2026-08-19T05:04:12.717Z" }, + { url = "https://files.pythonhosted.org/packages/87/f6/3a4824cd1c1b81d996d2d75bbd176ba13fbe9b5d89489290d93ff9558486/lxml-6.1.2-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:11f529062255209a421ae4de5b1bb36b2f0a2e1a700745e675a4bf4084d13c00", size = 5685792, upload-time = "2026-08-19T05:04:15.367Z" }, + { url = "https://files.pythonhosted.org/packages/64/9a/f133bf16a67149e00ca5d8a8f1ae662c30a86c303aa242693b67f8e19856/lxml-6.1.2-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:f8b89b3be75a37509602b03f9cfa1a28298d4eed4625748148307aeb907901b7", size = 5248972, upload-time = "2026-08-19T05:04:18.491Z" }, + { url = "https://files.pythonhosted.org/packages/50/63/273e7e8a73a5d183d8552dfdaa131dfda0292ddab7bcddc5a66a0ae525d8/lxml-6.1.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1a2331da06dd55a8184985306eb2afd72d708283ce7e85d67bba77317b785060", size = 5271809, upload-time = "2026-08-19T05:04:21.448Z" }, + { url = "https://files.pythonhosted.org/packages/49/eb/614117c36a28909e79ff7cdec87008f0bd996478f35cf72309189cf398b1/lxml-6.1.2-cp315-cp315-win32.whl", hash = "sha256:442766b326d9892585a64e8c6c4b5ab81d0e6c0538c9f0fc11a84dc101a5d97f", size = 3662854, upload-time = "2026-08-19T05:05:07.141Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e9/06aee6107cf8e7b870f10f82539f366cba10dc6053144cca80e838caf8c8/lxml-6.1.2-cp315-cp315-win_amd64.whl", hash = "sha256:a7fd1dd6faa3df9dcd8f1765237362cd885ca62cdf77a7c5f5ea383ae5b6048b", size = 4074590, upload-time = "2026-08-19T05:05:09.697Z" }, + { url = "https://files.pythonhosted.org/packages/84/bf/dad9b6baf9b26d79584834e15cef2a5dd0a13c7b1df08831e8f18244b494/lxml-6.1.2-cp315-cp315-win_arm64.whl", hash = "sha256:054175250531a5fb102d485743ff16412279c93add12385b3b1c3d7b16d8deaa", size = 3749336, upload-time = "2026-08-19T05:05:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9d/cd0c43d45e2eb52df7735c6558f24054ca633499191899b0cb9040fbbc3c/lxml-6.1.2-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:84a2a46b93b789d8acb44cfcb3d967ce9dbe29884ddb93fbb1a33f0e0c8fcd86", size = 8857688, upload-time = "2026-08-19T05:04:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/0b/26/27093dc1a9edbdd8a54652f237a387f7e63ec0192efe708bc2576d8a1383/lxml-6.1.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:4aced3284e0353c798b060fe2c175eb81410e99b9a7e2ae6951be5333732b111", size = 4754422, upload-time = "2026-08-19T05:04:27.645Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ee/502f7c93507f57eb496744a64da8f4ca86855cf88e48d14584342f1bfd92/lxml-6.1.2-cp315-cp315t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47c92dc5167de16e27ace8332454f12ba172dcab04f7a78a9eae14e2e41b6a41", size = 5033396, upload-time = "2026-08-19T05:04:30.054Z" }, + { url = "https://files.pythonhosted.org/packages/bf/72/c4cbbe72f951650f2afe43a70e51687e111d82b9bec46e3310ea76419d46/lxml-6.1.2-cp315-cp315t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40366c23a938008a3bedfcfd80709b3a857c188b4d710b083e978ef5d2c1c715", size = 5615298, upload-time = "2026-08-19T05:04:32.752Z" }, + { url = "https://files.pythonhosted.org/packages/14/83/a3df966d6d7b6513e9dfb6fbfb041c0619642170359c1b36ab20a83e59eb/lxml-6.1.2-cp315-cp315t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c4c6dc1b2485aaa4adfb6ed754f90dddcb2b96a66bbebc9e1ac242b5ce5e818", size = 5236282, upload-time = "2026-08-19T05:04:35.762Z" }, + { url = "https://files.pythonhosted.org/packages/4f/85/8692ec8173c9f8d295735b9bf410d202317e7b3ed11141e80a30f421f409/lxml-6.1.2-cp315-cp315t-manylinux_2_31_armv7l.whl", hash = "sha256:3a698fad6f122a9b3e2dc2fb598c1de7329c74a67c7a334c9109a440de2508e5", size = 4650647, upload-time = "2026-08-19T05:04:38.396Z" }, + { url = "https://files.pythonhosted.org/packages/11/e7/dbe3cece28a5bf82997a091d9dbb0fc49e725a5fa86550897ee2cf6412e6/lxml-6.1.2-cp315-cp315t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:14879fa5eb2b793c040bbfcb62011aa3015c65d6c9875e063ea98ce2029d51fb", size = 5243387, upload-time = "2026-08-19T05:04:41.247Z" }, + { url = "https://files.pythonhosted.org/packages/99/a9/81a2d27640db0d27200b2f32339a54e74c36d58feb5ad528b87d52a59ecc/lxml-6.1.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b631174cd2e4d9f8a94ef17f911c6ded10ede93b5e7860dee7bbf85961d321e9", size = 5092624, upload-time = "2026-08-19T05:04:43.919Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f4/0b0304c70c087f618d95b0306738b070bd556afd09c2c92589b78dbe5eb0/lxml-6.1.2-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:ceafa5e0536c62a5cd9f65327fa0b57d6f0b0e3435daf2c98a78d0dde7ecbae1", size = 4758742, upload-time = "2026-08-19T05:04:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/f9fc45f1d01b632b673e11880e75292dff9953db9f426d1a38201b8eb5f5/lxml-6.1.2-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:7c482e87cc86bed78a50462560675bc2c348ef72c47596f9b933346d5a8e920e", size = 5649540, upload-time = "2026-08-19T05:04:49.777Z" }, + { url = "https://files.pythonhosted.org/packages/6a/0b/d65e0458c2bcce0df68d5cc29ad0006e76446f02d9e50caf188fd1fb8bae/lxml-6.1.2-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:b1c0d2dde8a50520efc51644587f0fc4810e3af7d3e029d7af0be93bf39e2b5c", size = 5234869, upload-time = "2026-08-19T05:04:52.972Z" }, + { url = "https://files.pythonhosted.org/packages/ee/62/1fee828238badd3bfe9544f5cc9ce6ded421ef38e9634030445dedd78b36/lxml-6.1.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:dd7ea3fa47154b9fff90591b961e41b3718bd7fcd5bc2d9bb47e9845c8ace088", size = 5259992, upload-time = "2026-08-19T05:04:56.028Z" }, + { url = "https://files.pythonhosted.org/packages/20/18/35fb14dd6baccbffa6daeb2369802f04a94e3f73db3c7bb405dbab009729/lxml-6.1.2-cp315-cp315t-win32.whl", hash = "sha256:87534cec6ea325435e4adf2326b0cf3110eee9a47abf73652eb155db639c08c6", size = 3901151, upload-time = "2026-08-19T05:04:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b6/07530896ca062bc3d2f09d5cb8a48e799c05b12c496205db03159ba13b6c/lxml-6.1.2-cp315-cp315t-win_amd64.whl", hash = "sha256:4e220a9c297e5d36895d489a08c9a3f1f6193b6414e702c5fb751e4a3767f8d0", size = 4395355, upload-time = "2026-08-19T05:05:01.651Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/237d8de1d77085cfd41d0c6049a044d8d01886f3afb7f1eda2f43d900a96/lxml-6.1.2-cp315-cp315t-win_arm64.whl", hash = "sha256:f16a407766bac51c65d605b06d900821751a79aa20e12185f273f14a17180e7b", size = 3822823, upload-time = "2026-08-19T05:05:04.63Z" }, + { url = "https://files.pythonhosted.org/packages/82/e0/efb40756ca9499992d2f751668920660c466522a6b14ea1024d71b480338/lxml-6.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:feda2ef68c339987dfb370af3a4b785dbc40f925723fe2365e68e43c2640f85a", size = 3947525, upload-time = "2026-08-19T05:01:16.637Z" }, + { url = "https://files.pythonhosted.org/packages/3e/00/54f317630c238418c9541cdce7f4e9998c28913ca23159f54cc09e62dabe/lxml-6.1.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9bdc2db9e04538f917bba0242920764dd740649d8df58700d6d687ead4429429", size = 4220190, upload-time = "2026-08-19T05:01:26.615Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7d/3a35ff7e08461f4aef320e5f95b6f6ff80a1a1b4b871a2e4ca09425f9c91/lxml-6.1.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4a16457e330b7099aa5a8e8bfa5d53a33a1672a819fa656157e9e6dc433ac7a4", size = 4329292, upload-time = "2026-08-19T05:01:32.516Z" }, + { url = "https://files.pythonhosted.org/packages/85/89/55a6b1f1bc45c65779c23e99c95f179e91ffe833410a9c58a26013660ca2/lxml-6.1.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:614d4c5a34556e369b86cfcc8d0cf71cd0759a3444a464a07a9427ab0f5e3a99", size = 4261996, upload-time = "2026-08-19T05:01:36.882Z" }, + { url = "https://files.pythonhosted.org/packages/62/08/d88e0ae9ad2cf261143142d9633269d1bf90dc24454132d3c758c147d4fd/lxml-6.1.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18467b0e9f7f0bc477df69e99829a59ae17fb37d34e5f68399371c7c67be9002", size = 4409999, upload-time = "2026-08-19T05:01:50.389Z" }, + { url = "https://files.pythonhosted.org/packages/cc/ec/3677ba99141f215dc9dbeffbfab868b9c0a117abaac8e4d85654246e02b6/lxml-6.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:351855814dec4ad55ca5f24d0f4b1cdaca7927fe48023a2965351845f3b60cff", size = 3510608, upload-time = "2026-08-19T05:01:58.454Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c9/11bfea1b3afc7a27ce74222b2e12b97005f3b81aa0011313769a14afd60a/lxml-6.1.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4622c5616683faf63791b349e6c8dad7717412dc5f29f4febe7575f110609a86", size = 3942892, upload-time = "2026-08-19T05:03:21.567Z" }, + { url = "https://files.pythonhosted.org/packages/c4/98/9885a4505758885c113af2bc2335a9fced99cb01e07e42895a62f1eb97fb/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:733dfb492ec3dfef8350a5cc896e90d202c5171e791e1609e77563751d69a15d", size = 4213061, upload-time = "2026-08-19T05:03:24.259Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/e80d9e7d6e54b0693df60c7eeeed4aa19e2e3936dadf0676e6a3e8ac1ee1/lxml-6.1.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4618b20f43dc98b49569b1dc822176140ea0f2598d672a6989187ba49bcbfec1", size = 4322013, upload-time = "2026-08-19T05:03:26.764Z" }, + { url = "https://files.pythonhosted.org/packages/52/22/2e896cfba4e86b805eb8a3259cbdc1601971dc8fda5b1db2044ec2a3e6f0/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f93bc5e25992f5545709000d840c6cafdbd022781a7a0ed79d58a5633733a4e8", size = 4257333, upload-time = "2026-08-19T05:03:29.355Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1d/9dbdbfa284ea96aee7c368e0ac73994f7e1375281070c355bcd85d4f7a77/lxml-6.1.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:662432a6103e671d971e06e75ed146d9ff67f39d2c98c2f26613b6057f54eafc", size = 4410828, upload-time = "2026-08-19T05:03:31.948Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f2/fea24b044219458c252e0a0a08074a27dc9e28edb85f83533e36e3ddb57d/lxml-6.1.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ba0dfead73be5be9ad0b7fbf9f31ff29c1b1eae858816dfc8d85099d6e4af0d6", size = 3511278, upload-time = "2026-08-19T05:03:34.597Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "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, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/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.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/b6/10832f96b499690854e574360be342a282f5f7dba58eff791299ff6c0637/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e", size = 135131, upload-time = "2026-01-19T06:47:20.479Z" }, + { url = "https://files.pythonhosted.org/packages/99/50/faef2d8106534b0dc4a0b772668a1a99682696ebf17d3c0f13f2ed6a656a/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa", size = 135131, upload-time = "2026-01-19T06:47:21.879Z" }, + { url = "https://files.pythonhosted.org/packages/94/b1/0b71d18b76bf423c2e8ee00b31db37d17297ab3b4db44e188692afdca628/multiprocess-0.70.19-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6b6d78d43a03b68014ca1f0b7937d965393a670c5de7c29026beb2258f2f896", size = 135134, upload-time = "2026-01-19T06:47:23.262Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743, upload-time = "2026-01-19T06:47:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738, upload-time = "2026-01-19T06:47:26.636Z" }, + { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741, upload-time = "2026-01-19T06:47:27.985Z" }, + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414, upload-time = "2026-01-19T06:47:35.915Z" }, + { url = "https://files.pythonhosted.org/packages/a0/61/af9115673a5870fd885247e2f1b68c4f1197737da315b520a91c757a861a/multiprocess-0.70.19-py314-none-any.whl", hash = "sha256:e8cc7fbdff15c0613f0a1f1f8744bef961b0a164c0ca29bdff53e9d2d93c5e5f", size = 160318, upload-time = "2026-01-19T06:47:37.497Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/c6/28de0191c5f82b7d42a0a51390ba98587048aa93a39fafb05bdbe6e8d00c/numpy-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f", size = 16885274, upload-time = "2026-08-09T13:47:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/973ca116000d244897e468ea1aff30b589e5022e3c8744b71706fe33bd57/numpy-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842", size = 11907846, upload-time = "2026-08-09T13:47:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/78/d9/8c4b3937ef204cb2fd88d389ccd0f265a2ffb11f35a01d2064cf46714bd6/numpy-2.5.2-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6", size = 5354892, upload-time = "2026-08-09T13:47:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/b6ee65ea2999fdb7023935e108e6fb776ee4082aa15f159acfa857e578c8/numpy-2.5.2-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8", size = 6679309, upload-time = "2026-08-09T13:47:20.456Z" }, + { url = "https://files.pythonhosted.org/packages/43/f3/acb18d8b137a393c8e7803a8c994c9e64bde3930692a69d826993113a159/numpy-2.5.2-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69", size = 15625850, upload-time = "2026-08-09T13:47:24.365Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bf/a8e9bb0db815a0e265b5744ebedd3af0bd5faad8604e5b50a1cd012f3c91/numpy-2.5.2-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab", size = 16713664, upload-time = "2026-08-09T13:47:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/6e913736b3dd6582344af32418b5fb9dab34282e8a8174ae1d54ceb0fc13/numpy-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514", size = 16986749, upload-time = "2026-08-09T13:47:31.541Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/7d3b23eff5c7428ef6c01e6f7052bb60d504c4d33e317b36b8959c24ad97/numpy-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710", size = 18470495, upload-time = "2026-08-09T13:47:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a4/68a321d825374f6eb677ffe8ef8c6b9a328304e6fd2e39d9530822776607/numpy-2.5.2-cp315-cp315-win32.whl", hash = "sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617", size = 6120696, upload-time = "2026-08-09T13:47:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/deafbb1700f79fae9cd1e91220f133d124cc267de1b584da3fbf6db2f6cd/numpy-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7", size = 12597324, upload-time = "2026-08-09T13:47:41.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/3272ba105e3bbbdaeb11357eda31e7a6825ffe159e8171665660299a948f/numpy-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788", size = 10680466, upload-time = "2026-08-09T13:47:44.873Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/58370637b1bb70a5c9ce2b43f4b521ccb224e36ccb76a6596b17ae4b447c/numpy-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b", size = 16993947, upload-time = "2026-08-09T13:47:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/93/2abcb807712b289d6d60fe4cf30532f98974a8396d885650f3ba5a13026e/numpy-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e", size = 12025331, upload-time = "2026-08-09T13:47:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3a/2898e003a5fbaf87e76c039b4ee1f5eb390471b4ffe74887c1f34c4e791e/numpy-2.5.2-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a", size = 5472336, upload-time = "2026-08-09T13:47:55.403Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/23f69d07c544597b29758b31b55c27dc9d541012a2c1496189fef702aec2/numpy-2.5.2-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8", size = 6788387, upload-time = "2026-08-09T13:47:58.192Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/c0dbdbcf22f43782510a3e492dd3da73c6112b69cac8929d16d127536fc4/numpy-2.5.2-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc", size = 15667096, upload-time = "2026-08-09T13:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/29c73c31748cdb0f7566642125ba17fd5b56780cddf891b085dab27e4466/numpy-2.5.2-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec", size = 16751730, upload-time = "2026-08-09T13:48:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/47/95/02501e8454796bb58dadf7a99d3181e0b464bf264e1003039572f9779fac/numpy-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0", size = 17038686, upload-time = "2026-08-09T13:48:09.627Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b5/53a681d91b5c82687067d8ea5035e02d917b5509d6f334cb06484a954714/numpy-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2", size = 18507727, upload-time = "2026-08-09T13:48:13.744Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/6e11443f7b64ee376c860506091103bf68f92d2cab9e8d96d4501babf07c/numpy-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251", size = 6269775, upload-time = "2026-08-09T13:48:17.543Z" }, + { url = "https://files.pythonhosted.org/packages/f1/18/195d6b86cd72dbbc501edfa778005fa6b87afd34c153e46028cd3a0938f4/numpy-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12", size = 12782559, upload-time = "2026-08-09T13:48:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/458c344f0f0c178f4481dad5cca790626ffe4c34eabf9467069d06ee4999/numpy-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e", size = 10748103, upload-time = "2026-08-09T13:48:24.21Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparse", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, +] + +[[package]] +name = "nvidia-ml-py" +version = "13.610.43" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/b5/a8fbc356f768fa5c9cfd646668fd7d34bf55bdd1c6e20754642a64d930d4/nvidia_ml_py-13.610.43.tar.gz", hash = "sha256:65437eb73d68d0c62c931ca4d45038472faff03bd0b8729abba4b899f70d60f2", size = 52109, upload-time = "2026-06-01T18:54:08.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/45/caa600acfab94560807a20a64b5830d2cd3c3202b7f1328644d70b7d6bd8/nvidia_ml_py-13.610.43-py3-none-any.whl", hash = "sha256:f13c72698edef492f985cc225f14faafe68ae065a2e407f45bdf6f4b9b43fde8", size = 53163, upload-time = "2026-06-01T18:54:07.704Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.3.33" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423, upload-time = "2026-05-26T16:54:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635, upload-time = "2026-05-26T16:54:13.906Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + +[[package]] +name = "orjson" +version = "3.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/742fb1f62b825f2c010697eaf4e828004bc2a81e7e806666989c132c7c42/orjson-3.12.0.tar.gz", hash = "sha256:d14203fb1aae2ad9b3d52f8a0e82aeb10197ef1c9bc61da7f358bd70b00123d5", size = 4142915, upload-time = "2026-08-14T16:13:30.607Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/35/819eeb4fa8ee676d38fdbb8213a76fd496f7dbbfdfafa89d34e02b22dfac/orjson-3.12.0-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:747843254519dd43b93eee3153a19e5a509334320c4d2f823ec879232db5c796", size = 224133, upload-time = "2026-08-14T16:12:00.607Z" }, + { url = "https://files.pythonhosted.org/packages/58/ab/d9221d4a2b085b073fcddc91728d490f20b9cf010c62c2f42371ab997695/orjson-3.12.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:7c2ad193c8004254f34b499f3bd2c80f043d10754aff2b38f93da574f4883f98", size = 113669, upload-time = "2026-08-14T16:12:02.126Z" }, + { url = "https://files.pythonhosted.org/packages/15/12/644cbbcabb26df61d9ef0c66e6f2bf8b687cc7b66137597f2858951f1952/orjson-3.12.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:bc7a872f03522d90e0429e6c0c5cd23084f767bedcb4c58048eec19294613344", size = 130410, upload-time = "2026-08-14T16:12:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/14/6d/e3a8c34d687895aecd8b267a01c46106eb98d8424a83bfa7bacb723854f6/orjson-3.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18a87929f31d94a77f7dc93cf527e91f39ce7fe7813d588a4de2507efd32a387", size = 131101, upload-time = "2026-08-14T16:12:04.918Z" }, + { url = "https://files.pythonhosted.org/packages/75/20/930824c07685c22af23f26818ed3853b0270488a412b6ab757904b7f787b/orjson-3.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9683ee9ea0659da64f36574ef675b8a86330c34c19ea75db1fb93c3ff99e0ef", size = 131479, upload-time = "2026-08-14T16:12:06.11Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a6/22e863bbbe8917aa292e33e0db597000f9a07eb5e6f52efed623fa16bae1/orjson-3.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:103b5db66aa53c1f9e88c2524be4f383e831ba7dfd5f9f5af6336a177c622f11", size = 135865, upload-time = "2026-08-14T16:12:07.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/a0/ceb5008914a65e9a19a46a09d94bc67a74d120209fdfa772750023ceb377/orjson-3.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bd57d79aefa3f84eec851d6de7a366795b9345cfaf17f82b4820430a7a5fa241", size = 127843, upload-time = "2026-08-14T16:12:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/3d/61c6b3b84c250cb09cb7229701ff77e4d763773ad7f577d0b6abf2892664/orjson-3.12.0-cp310-cp310-win32.whl", hash = "sha256:3dbce9b6b3074b31a5d5dd322a9c4e5b16f206091ece4194c2e36952847a105e", size = 128293, upload-time = "2026-08-14T16:12:09.819Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/ea0f4a563253b6363195a4f704123c6bfbf156641bd3be5a75de81c5e917/orjson-3.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:3bb17a06f9bd15237b3216c044209fe92597379124018cfc196fbb846cde64df", size = 122216, upload-time = "2026-08-14T16:12:11.261Z" }, + { url = "https://files.pythonhosted.org/packages/75/1a/a7075a8e8b0d3f5097d17ac3099017104b6b7b42012041147995d5b2da05/orjson-3.12.0-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a94f0f0c6fcbb2b5bd9734c57a489c7584a732bbdf04a39e8c83b861e9d03e92", size = 223409, upload-time = "2026-08-14T16:12:12.654Z" }, + { url = "https://files.pythonhosted.org/packages/05/34/c2eb3b2900e5597db7841a4c6416ac2d90081bd956b02d4dd1833fa2b96b/orjson-3.12.0-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:a696529ec96a90d9a5f9570207efe403c8b08f8e4aa2783ee3403511e2fdfa10", size = 124015, upload-time = "2026-08-14T16:12:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/1c/df/b49081766a75b6a37b3d33bdc0a39e492abab8441dd25e3e1998e7b83fcb/orjson-3.12.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:e4ac5059baab4b3acbd99485de019ff8cda0fdf34b61fa74f7197a53db78bfe8", size = 113471, upload-time = "2026-08-14T16:12:15.81Z" }, + { url = "https://files.pythonhosted.org/packages/48/d4/58ea28eeef95c2a27358ed927380a621162cf20bd740bbccf9c3f09a200a/orjson-3.12.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8e29957429c35bbb5a185a119c523aa2428b7bbf1a293724c7b9375ed8f892a3", size = 129998, upload-time = "2026-08-14T16:12:17.503Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f4/1e82aa2efc9916422d804697876ce433c907a1abd7c7e5c6d3d48565e5f9/orjson-3.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dce0166feb0a737ab84f598c9a338cbc0b764a036617aa686194f53c7eba0c3e", size = 130891, upload-time = "2026-08-14T16:12:18.762Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/15169e9d22b59a406264f99d6db387c0b0b12b6357a8a0169917c2a713eb/orjson-3.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9caf3d09f47c3c70c4451ada20ef9bc4a4cdffa26f49862cf0a253b329aae2d5", size = 131285, upload-time = "2026-08-14T16:12:20.251Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3a/763dbd426290d044ec3e615a05e70adb6d8b6f95bf17dc355c0081a5e8b6/orjson-3.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9dca132b1fda5565088e65a6b6e742285e0aeceb6fae549fa8863e16c7d3998", size = 135707, upload-time = "2026-08-14T16:12:21.652Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/3b2038ed168d22e14182ed715d6963f9c073a83a2ba43cfe918a4fc43c64/orjson-3.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a791f793b287bbc135b8e87c34e35c8bfc693e2a8a620fab1ae682b925f9a32e", size = 127669, upload-time = "2026-08-14T16:12:22.926Z" }, + { url = "https://files.pythonhosted.org/packages/88/ae/b84b3d3e65f5629ada0edcb1d2bccc55d7c5f89d8b981537ecdc3d6f31ec/orjson-3.12.0-cp311-cp311-win32.whl", hash = "sha256:31ed278a36304390adc3eec5d7f6fd593a7c3e99e5a06cd07866396c4b1b4710", size = 128043, upload-time = "2026-08-14T16:12:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/35/24/2ed0e6f51ea3d0af45d807233a851175af75bec83ef5fd0d6a2601904ec0/orjson-3.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb2539159dfe8d371914f354360fa50e4a577cc89222a3828b9650a5e5040252", size = 122084, upload-time = "2026-08-14T16:12:25.813Z" }, + { url = "https://files.pythonhosted.org/packages/21/dd/95d25fcfbc9471799ef6bb01c552d64ee5cde93ee40ba2f423dd3442c708/orjson-3.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:61318b6de893c7a9d9f3e5ecbadccbfc26a7eb417ccc7bbf0771de3b4d72f868", size = 127035, upload-time = "2026-08-14T16:12:27.201Z" }, + { url = "https://files.pythonhosted.org/packages/be/4a/295da39c651c2faac8bd351a2a346f0fdedd9d50b847ee9dfc27d2207ef6/orjson-3.12.0-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:aa3e43a6846e91d7bde3d5a9c66090fcd8744f569a9b6cffc5e1ca38f6a461c0", size = 223427, upload-time = "2026-08-14T16:12:28.525Z" }, + { url = "https://files.pythonhosted.org/packages/29/98/758cf90fbeaaafb7f8141bfac75a432099959f3a2f5db93a412e876415d8/orjson-3.12.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:11edb4660a6680abee9788a3a9072208a2c96538cc1322bd79542065229d8e54", size = 123725, upload-time = "2026-08-14T16:12:30.013Z" }, + { url = "https://files.pythonhosted.org/packages/32/b5/5b934d251f8651f7e41df180ad0c57a6e1cabe15c7bd331638413a50ebc9/orjson-3.12.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:2d3a9da945a4d96ae758fdaaca56742e6b73b6fd554c5d8876f252a6dad70b83", size = 113375, upload-time = "2026-08-14T16:12:31.209Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d2/37efb5b12a176ce3ced29f4144f20da57d02757f78ce549637dc1b4e1fc8/orjson-3.12.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:92ffc09e07233a6ab6d4e067f7841edcbcc134cb4812155cf171ea5255a421d7", size = 129983, upload-time = "2026-08-14T16:12:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/50/22/0644b87c73f13e0092df8f35a1fe280d991e5e90072087411e0dd7e44e0c/orjson-3.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf44e374aadde77b1f6109f1030be51433eb61984379852766b6f4e187db7b1e", size = 130629, upload-time = "2026-08-14T16:12:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/8c/57/80b986ebfecd9c6a177ddf1c2319717f0cd8feffb2b78946595a18a2fc88/orjson-3.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1192a7021b6d071aaf909864f6e924d6a2675ca360485b972b8401749311750b", size = 131245, upload-time = "2026-08-14T16:12:35.713Z" }, + { url = "https://files.pythonhosted.org/packages/80/3d/75c5ac5a69161f44492a68fbdde66f4cc4ce48cd5e1fb05918e46f0c8848/orjson-3.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:53c0c474a9d9aff9aebfc0c88de1f28f843d940e6e3a80729abdf6a20274356f", size = 135397, upload-time = "2026-08-14T16:12:37.128Z" }, + { url = "https://files.pythonhosted.org/packages/71/93/4d71f2df314a97ff0d27a4559bf5888fc8406e3c6dec90e92291e3511215/orjson-3.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:532ff8cd4bd59a327a953a7dcde922c7fc25b85e29721bb8633265430d3a3873", size = 127693, upload-time = "2026-08-14T16:12:38.627Z" }, + { url = "https://files.pythonhosted.org/packages/bc/1d/0dbc6be5adfd1730491072fb60beb6bcdf5d7b2596ee41b7fc2e298bfc09/orjson-3.12.0-cp312-cp312-win32.whl", hash = "sha256:a6cf4b18e7de173f209f2084ffbd736dd72389a396326ee80a7022168be232e5", size = 128000, upload-time = "2026-08-14T16:12:39.954Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c9/97b1ce0112ebf5e949c775ed5b1755e562233179f3584579673cc24d6378/orjson-3.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:010811c1b69773450a01cef97727a67b223242f350b77d4ca000e59a9ef2155a", size = 122106, upload-time = "2026-08-14T16:12:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6a/facd8b312e4a0d3a7fa978c7e15821f74a336adf1d65529faec33b48e18b/orjson-3.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:ad29eece0c601737f2a60edc2752a84e7a0785df3efb62e3012834700a5afe0d", size = 126869, upload-time = "2026-08-14T16:12:42.651Z" }, + { url = "https://files.pythonhosted.org/packages/54/cb/d7b78218a987eb8a8ce4eeae0286b1bb679333eb631ea0eeaf6371680bfc/orjson-3.12.0-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9a36ec60f1796f9a3f13e3b98390295e17a1c7c10155b448d264098bf9ee5900", size = 223397, upload-time = "2026-08-14T16:12:44.003Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4a/bc87c45e7ec639d35ebefd62618e01939531ac8e171426606a01bda05914/orjson-3.12.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ad0422b92d5195443a39f80c3bcf731cc2e00f153bd32063a47b73b057bd0f03", size = 123662, upload-time = "2026-08-14T16:12:45.433Z" }, + { url = "https://files.pythonhosted.org/packages/94/ee/c9a4ff3f2dbedbbe9e635d0fa72c8866adede09b6335ef9644f53752f0d8/orjson-3.12.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:5a0fdbc216388f653d3752ff310e710f59253bd4ed6a2bfb3f4f06b84714bbd8", size = 113374, upload-time = "2026-08-14T16:12:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/75/09/3f330a026a796c8b4c97a6f429652a5e912e7065039bf96ed25e42aa7b25/orjson-3.12.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2eb5c56e534127b2b8fa38d2363c8b1b8190367ee0d1d16c041517d880843b94", size = 130029, upload-time = "2026-08-14T16:12:48.06Z" }, + { url = "https://files.pythonhosted.org/packages/7d/40/094cc53126a3d22f76cdf83b6ea67338bed01d774037621a785aa8e6e5ea/orjson-3.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:784106539f4b9d4b930e0b4eb8d45168507dae001945e71b4675a367f1e5e806", size = 130528, upload-time = "2026-08-14T16:12:49.362Z" }, + { url = "https://files.pythonhosted.org/packages/bc/74/89bb236deb9565f99434b13052bb40ddfcce4adf3afbfa3132ee7e421468/orjson-3.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c680706fc8396d95e7c4c1f9482563f552137aef91b57237a3ad5aaf64629df", size = 131075, upload-time = "2026-08-14T16:12:50.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ac/1176360d762c01b5bd34acd56fc098e936c491363d8b6b397ad4aa475547/orjson-3.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:83445adc40cba26d6d621185a45128ce455b766af368cad2ab64b970603a7978", size = 135321, upload-time = "2026-08-14T16:12:52.114Z" }, + { url = "https://files.pythonhosted.org/packages/7a/02/bbd881c8b9276d50b998de38b4e97de8ace1aac940b0ee545aedbf65ed00/orjson-3.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:644d005bc82f917337a95ce270c9f6f92f9834c2bed7b1477572f8db00784222", size = 127472, upload-time = "2026-08-14T16:12:53.517Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/a0934d7503e6dcbedd6afac3e7f3f8597fd09389949ad94d0f7540e9dbca/orjson-3.12.0-cp313-cp313-win32.whl", hash = "sha256:d8e78d3d93705e3d27cc17cdb209e44d7a8ea203010cac6ce9c7ffc1ae1996f1", size = 128000, upload-time = "2026-08-14T16:12:55.14Z" }, + { url = "https://files.pythonhosted.org/packages/52/87/69f98f8d40faff103a965a5fbb83f08241b01beaf92badb5413fbc9358cc/orjson-3.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b85931be5b6763c31283805c9bdaae1ca03ad9f6f12a15f1cbf6745b907932c2", size = 121841, upload-time = "2026-08-14T16:12:56.507Z" }, + { url = "https://files.pythonhosted.org/packages/e6/07/b83046a4e3cadcc0987d0f160696107c4af706a619b56e4ad01940cadadf/orjson-3.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:6a31348d7dfa64cd9c78bd1f510ff44c48fe64d71094e6b90e364dba3b55949e", size = 126765, upload-time = "2026-08-14T16:12:57.806Z" }, + { url = "https://files.pythonhosted.org/packages/12/9d/3931253e6f3148abf2cbe14830367042a4806b362ea520df2303db188fb9/orjson-3.12.0-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9e6fee342a48760e854d743e7a81534d8e2925a6f46e09f750cf56b50fd1de5d", size = 223391, upload-time = "2026-08-14T16:12:59.184Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0e/b4a4f1e305367245877b967a0bad70fcf001d77c54ac4339a120b66fdae4/orjson-3.12.0-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:8c3bb86dd10f39b3fbf434b7d5dc7cac77d6fc8ac572ae30a10731ede2c4b647", size = 123659, upload-time = "2026-08-14T16:13:00.548Z" }, + { url = "https://files.pythonhosted.org/packages/96/f3/6782c6fa85e2702bc66be183c3b421486167dcf266ee4dc1403fe3824870/orjson-3.12.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:2bb3ce43203936072dd8b4917b01d3aecfc02329bfb42510cb7cfb24708adc9c", size = 113337, upload-time = "2026-08-14T16:13:02.009Z" }, + { url = "https://files.pythonhosted.org/packages/bf/79/b32ab64bacda9d0fa4942ef483bd03cabf0eaf2be819ca9fb7ff610c559d/orjson-3.12.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:6a2a79c89984dc719817d388c8709e0efc2a2795a934eaa746b4882eb6045adc", size = 130112, upload-time = "2026-08-14T16:13:03.404Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/6e6142999ca01509219be5e5a9c338a3e5ea011f63e91ff473fbbf3734ed/orjson-3.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f06dd838d1e07d9b1de0932ec0485ec92c4d5f5d1ad4817a656268c3e88be1e1", size = 130520, upload-time = "2026-08-14T16:13:04.798Z" }, + { url = "https://files.pythonhosted.org/packages/49/d0/3745af0a4cc9867784f29722929cec4d10bd1c877cd754b01ba6d96eb21a/orjson-3.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6b11be792c3d2c6a4be2af4ebf97a68d0bf5f580aca6e86a418a354f6cc846a", size = 131053, upload-time = "2026-08-14T16:13:06.14Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f4/6fe5a22fa478fffb190e65c338c84df5c311ef597b363150a17cc57063c0/orjson-3.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:477ecaf6b9f88f873341b91fcc736119ca81b5e002a9f7f308ff5b4f2ce2a70e", size = 135321, upload-time = "2026-08-14T16:13:07.544Z" }, + { url = "https://files.pythonhosted.org/packages/ff/41/b1b0ec30289646a81a76e2dbaae2686b96fcccb7cb0323dc1dd78cbc7875/orjson-3.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f3c0683136acdc29afdf88a5bc2f7d3d0e34087788d1d63c0144b805a87a196f", size = 127485, upload-time = "2026-08-14T16:13:08.88Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2b/277404bdcc21c93b112b963655b76443ebfe828f8a3ff1de7d90f8850eb3/orjson-3.12.0-cp314-cp314-win32.whl", hash = "sha256:d39f3f5c3927e2dc0913fe5bbc1a2f6b1b9d1bba1de6358340d0ad0d0c00ca92", size = 128048, upload-time = "2026-08-14T16:13:10.305Z" }, + { url = "https://files.pythonhosted.org/packages/41/2b/395b36fa2b4ce7af70b651d715e88f80d884b2c2b14a6b53e84d554fb5f0/orjson-3.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:0b1ac5bf6609b2716c7954011c5fef6254922df029f45d032ee4ebf5d363cbed", size = 121858, upload-time = "2026-08-14T16:13:11.634Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a3/833e895ff452859eebe75093d26691fe9108f1a7a6a08435d7a5780ea652/orjson-3.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:50fae885cb073eac7556353ff3df93312b0d5137b0a5056b2bb63f97ed9a93c7", size = 126749, upload-time = "2026-08-14T16:13:13.117Z" }, + { url = "https://files.pythonhosted.org/packages/58/64/99c8947ece10c17176af9aae85c4948f1d109da77440ec14d87239efaf73/orjson-3.12.0-cp315-cp315-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:01efac2074fffb4cb1ea3fab7861e9d0f2a26913854a972f5ac760525dbdaf6e", size = 223398, upload-time = "2026-08-14T16:13:14.694Z" }, + { url = "https://files.pythonhosted.org/packages/3e/30/cf983fe09f2731420fda097a9f7ef4343f47fa216c228961ad8f6da44f3d/orjson-3.12.0-cp315-cp315-macosx_15_0_arm64.whl", hash = "sha256:ed4ca42bd55955aa34deedcfdfd0e0c31abf51143aae158ae2bc3520b626e517", size = 123655, upload-time = "2026-08-14T16:13:16.221Z" }, + { url = "https://files.pythonhosted.org/packages/11/50/9cb8ae73fa4749dbbc20f617004213b5ff01c20aaeec34c3f31124f2c1d8/orjson-3.12.0-cp315-cp315-manylinux_2_39_aarch64.whl", hash = "sha256:40f92192227505acca4e2533ce565f8e6b9535f7d0d09b0968452f18b7376b38", size = 130515, upload-time = "2026-08-14T16:13:17.601Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0a/adb6ce1a5b5fbf9cb1790f9961bb668a0dd5429aadaf6cee044724681795/orjson-3.12.0-cp315-cp315-manylinux_2_39_armv7l.whl", hash = "sha256:33efefcf5d88eaf400b47e2eba02f91f319bb9951be61ca500b7d536d3f2079d", size = 113327, upload-time = "2026-08-14T16:13:18.927Z" }, + { url = "https://files.pythonhosted.org/packages/51/5c/d17f61581d8dbdde7048f87a330fa24915edec38db4d72b381fec14fbb56/orjson-3.12.0-cp315-cp315-manylinux_2_39_i686.whl", hash = "sha256:8e386b0bc0ddd7cd2056f884b5a0af33592bd01ac66a7ca4b42a65a7e7774a13", size = 130105, upload-time = "2026-08-14T16:13:20.317Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b7/938befcf33bee4704a92ecec6a2731224c539d939bf9429fd39396d28931/orjson-3.12.0-cp315-cp315-manylinux_2_39_x86_64.whl", hash = "sha256:58c58e1de0006ffb580368d6793c36c7b0b021db066479cf281bf5061e732328", size = 131049, upload-time = "2026-08-14T16:13:21.719Z" }, + { url = "https://files.pythonhosted.org/packages/b0/15/cfa2021d64d5aa8bb5c9f604ef375e00ec8b657651b5dd650b1b7ad13df1/orjson-3.12.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:08231552159be266a7269555bd9f7c016aee7d9ad6dab06eb58796c5ccb7101c", size = 135320, upload-time = "2026-08-14T16:13:23.415Z" }, + { url = "https://files.pythonhosted.org/packages/1a/50/3e75dfe357c1e8f9e287c7a5740260ef15bd23a5299eae8d0835dcad5375/orjson-3.12.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:a15f9a891bce5f5cc5d210e3ad8614d4d1b489a56448c099d6d2a7168b2d954a", size = 127488, upload-time = "2026-08-14T16:13:24.791Z" }, + { url = "https://files.pythonhosted.org/packages/11/a6/79aed402eb3ab284dc5b4791a7ad62c5875127de01b8e3f04bd92d551298/orjson-3.12.0-cp315-cp315-win32.whl", hash = "sha256:03091c8a64db4be38746597ceea68f33c238e27acd9bfe99fb59420224ae7a55", size = 128048, upload-time = "2026-08-14T16:13:26.217Z" }, + { url = "https://files.pythonhosted.org/packages/64/f7/2723e264aab7248c1ed6ecaad8e5d0cb866c0cffde75442102ffa7491aba/orjson-3.12.0-cp315-cp315-win_amd64.whl", hash = "sha256:2b7bcefb9f40fa242fa6b06377232c048e655747790829609168c01162f60578", size = 121860, upload-time = "2026-08-14T16:13:27.577Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/630c9113ec8996778f1f0304b364b091b9a9db5fef5fdc17cca622f5ea24/orjson-3.12.0-cp315-cp315-win_arm64.whl", hash = "sha256:859fc4196855890150bb08e649b30d2c93b249b3e3edd0d3bb2231abf8aa8adc", size = 126754, upload-time = "2026-08-14T16:13:28.962Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "python-dateutil", marker = "python_full_version < '3.11'" }, + { name = "pytz", marker = "python_full_version < '3.11'" }, + { name = "tzdata", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11'" }, + { name = "tzdata", marker = "(python_full_version >= '3.11' and sys_platform == 'emscripten') or (python_full_version >= '3.11' and sys_platform == 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ef/f1fd7431d635bf20015489bf0bd69c17fff1018de773540f651455a3916b/pandas-3.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2946e77e4a53cd248cbde631a12f0e51c8324ce354c3eba4d20147c1ad6f4282", size = 10397178, upload-time = "2026-07-22T22:17:48.274Z" }, + { url = "https://files.pythonhosted.org/packages/31/b4/0eafac990a431561187694126de01f9b12559549b4d86360c0c4bd870fde/pandas-3.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71ecc8fb7ed1a7aa4392316b5309a6347e8e7f832f38fd897846b3a1457a9298", size = 9990736, upload-time = "2026-07-22T22:17:52.388Z" }, + { url = "https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b173f5951ff6b8b0ec7675e20dff3c97b7e7a57dfcce387c2d7c5afe87cb7899", size = 10814438, upload-time = "2026-07-22T22:17:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/d6cc4d7e508bbccf5d6027314a8312bc7ac73d0ec7f195f53838daafab40/pandas-3.0.5-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c0cf1dd9b55a22d105fc46c1b489af3bd42264fcba7c66297bf47a9a1d9c78a", size = 11323634, upload-time = "2026-07-22T22:17:56.858Z" }, + { url = "https://files.pythonhosted.org/packages/70/2b/d5f0a8c90dd0ae04e64ba53b871afb796ec026b615086d382ddc2ade729b/pandas-3.0.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0fac0010c75e4efb6b99e249c183a8993ce0dc95c240f9b120a5e67c727b7928", size = 11850860, upload-time = "2026-07-22T22:17:59.1Z" }, + { url = "https://files.pythonhosted.org/packages/5c/30/183aec2e19adf778a98d29b5729a0a68f4cc4ebf9b9c3b70d0297355bcb1/pandas-3.0.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:08d24fe11a17dc33bd6e937dc9c665f9cba08fbdc9f657f405713515febe300d", size = 12411100, upload-time = "2026-07-22T22:18:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/fa/9a/31f4983f191af51ab2a8f2d0c7b33dff3a84da26533f982fff02c2f9e28b/pandas-3.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b1261758dfb6cf12c3cff8300e21cefad30e7ec709abb4c24ac7318e6a52462a", size = 9968804, upload-time = "2026-07-22T22:18:03.903Z" }, + { url = "https://files.pythonhosted.org/packages/49/97/7886c89a39045c69ad82cbceaf3343810480c8ef49a216319ce8183860a6/pandas-3.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:679f4e85b30ddb1515458ab1e788d3e260eae369b1f78da7a3aa4cac8ebf4a2a", size = 9205447, upload-time = "2026-07-22T22:18:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/1c/54/1dc810ea558d1320b597aa140a514f2fdf1d2ea09c38cf556f13ea712ec9/pandas-3.0.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa290c16964d4963fbfbc358928239cf3bd755b20e988ce944877def2f44471d", size = 10411717, upload-time = "2026-07-22T22:18:08.307Z" }, + { url = "https://files.pythonhosted.org/packages/68/56/fbe81c09195924d8b7b8d4461a20458fe80a6a5ed6b24f0314da684277e1/pandas-3.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2e26bb46934b8a2ca0c3de1d3d606fc5f6746584791b2db264d58cf370e08dc", size = 9957095, upload-time = "2026-07-22T22:18:10.6Z" }, + { url = "https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73fa87b08a7ef706f8aafda39ddaccf2a99047bea62d8c88a0361bcafb2237bc", size = 10485458, upload-time = "2026-07-22T22:18:12.834Z" }, + { url = "https://files.pythonhosted.org/packages/12/98/e976540c1addf70442be7842a18cf70884a964abbf69442504f4d2939989/pandas-3.0.5-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d373ce03ffd84010ed9839fa73672a9c8256990532e158440c0085db7d914b34", size = 10998091, upload-time = "2026-07-22T22:18:15.209Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8c/1f29b5be8d3fc47dd7567eb167fabba2085879b31e0287ce7cba6d3d2ff4/pandas-3.0.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2a29c53d85ea98c5e792c59ef82ee9fbe6ca902c0d0adb6b23f45ef894cd7bf6", size = 11499501, upload-time = "2026-07-22T22:18:17.689Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e2/bd9c98ad2df7b38bde002adde4cdf353519da51881634323b126c55997f9/pandas-3.0.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5ad3b02ed6bc7d7ae9b70804b2c6aa31827489d150f8e623ce82491b82085d7", size = 12060559, upload-time = "2026-07-22T22:18:20.147Z" }, + { url = "https://files.pythonhosted.org/packages/f3/9a/ffbd852d58bd74a617fe2f8ee6a58a96982271ce41cf981eab22190b4a4b/pandas-3.0.5-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:b2acb4650527eec6822c3dadb2b771277b65e7dae7a267d4bccf65fd1bb3fbce", size = 7197652, upload-time = "2026-07-22T22:18:22.502Z" }, + { url = "https://files.pythonhosted.org/packages/70/b5/d2d3e9ae73362ba4229651b0ee1455cf78073a1ce585f6ff693782ce263e/pandas-3.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:80a611068e8a3ac23f7398c6c14eb46dc974e5cc9997f653e2dcfd1da74edd41", size = 9831691, upload-time = "2026-07-22T22:18:24.534Z" }, + { url = "https://files.pythonhosted.org/packages/52/51/dea1e89d6a6796b9c43f85a09b484ee03edb8a4c4842e73e200a8c11301c/pandas-3.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:25ff585b972a18ef1fe9ffa3ac6544d9950508aa76832e5147640b6022821e49", size = 9105796, upload-time = "2026-07-22T22:18:27.064Z" }, + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742, upload-time = "2026-07-22T22:18:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067, upload-time = "2026-07-22T22:18:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756, upload-time = "2026-07-22T22:18:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525, upload-time = "2026-07-22T22:18:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303, upload-time = "2026-07-22T22:18:39.287Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004, upload-time = "2026-07-22T22:18:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896, upload-time = "2026-07-22T22:18:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790, upload-time = "2026-07-22T22:18:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.11.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/bb/ebc6636e1ae41314f796ebb7215fd28febb45f9aac72f2b04cb74b5071dc/platformdirs-4.11.4.tar.gz", hash = "sha256:f3373be828247211d0febabea97e238c3dfde8a60b3c90c32756fb52cb21556d", size = 34079, upload-time = "2026-08-24T14:53:49.676Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/be/0ff05fcd2938fb58ad9219bd54135968342d214737e012d62d43f06a2dd6/platformdirs-4.11.4-py3-none-any.whl", hash = "sha256:e34ff91a24bcddc6d939b878bdf3f5c437c9c46fe9e212b1bf455fdf1ee57586", size = 23741, upload-time = "2026-08-24T14:53:48.406Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "portalocker" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/92/d15473a60e4cb7bc450e10d3160d7d531d6fa8cb144e31395afd76cf4be2/portalocker-4.2.0.tar.gz", hash = "sha256:f4c5024903126159c4798e6aec6c32e30a0759db39bf795c0dbeb180a2e8c843", size = 259245, upload-time = "2026-08-22T00:22:15.62Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/49/abd8a4154c911cc6c247edb7195d14846b614314c58ae09cf710d7d56098/portalocker-4.2.0-py3-none-any.whl", hash = "sha256:b33a8ce7d9b74922123a6fe64c2d785c015e5e9049ce7f1db5bd44e288731460", size = 128016, upload-time = "2026-08-22T00:22:14.254Z" }, +] + +[[package]] +name = "prettytable" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/74/ba08d81e668ccfe8658d7520a307e63c19862c08eb4ccb26f356c5239a7a/prettytable-3.18.0.tar.gz", hash = "sha256:439217116152244369caf3d9f1caf2f9fe29b03bd79e88d2928c8e718c95d680", size = 76373, upload-time = "2026-06-22T16:07:50.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl", hash = "sha256:b3346e0e6f79180833aebaac088ae926340586cf6d7d991b9eb125b65f72313a", size = 37357, upload-time = "2026-06-22T16:07:48.595Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.53" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ea/39b988c938f75cb75d7045b5c69f8bfed47ee2152c8837fb403de29d6fb8/prompt_toolkit-3.0.53.tar.gz", hash = "sha256:9ec8a0ad96d5c56148b3f914aa79c1564c3fde5d2e6b876e7bc327e353cf8fa6", size = 435492, upload-time = "2026-07-26T20:56:14.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/6f/84908cad2d6aa5144abcf7b42709fe4fdb459bc640ec7ac5786e7693dabc/prompt_toolkit-3.0.53-py3-none-any.whl", hash = "sha256:01c0891d7f9237d5e339f7d3e42cdae80b7534abb1c7c0e3352efba6231492f2", size = 392288, upload-time = "2026-07-26T20:56:12.512Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/56/030b7b4719d53085722893e0009dffb9236aa10bca1b12121bdc5626ef16/propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b", size = 93417, upload-time = "2026-05-08T20:59:15.597Z" }, + { url = "https://files.pythonhosted.org/packages/1a/55/1140a8e067b8ec093a18a4ae7bb0045d9db65da38a08618ddc5e2f1994aa/propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c", size = 53847, upload-time = "2026-05-08T20:59:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/0e7443c90310498561addf346e7d57fe3c6ba1914e1ba938b5464c7bbfd2/propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb", size = 53512, upload-time = "2026-05-08T20:59:18.64Z" }, + { url = "https://files.pythonhosted.org/packages/b7/db/cf51a71bab2009517d1a7f0ee07657e3bd446c4d69f67e6966cf17bcf956/propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e", size = 58068, upload-time = "2026-05-08T20:59:20.683Z" }, + { url = "https://files.pythonhosted.org/packages/b7/43/39b6bdee9699fa1e1641c519feeb64a67e2a9f93bb465c70776b37a7333f/propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e", size = 61020, upload-time = "2026-05-08T20:59:22.112Z" }, + { url = "https://files.pythonhosted.org/packages/26/0b/843726fbb0a29a8c5684fdb25971823638399f31e52e9d1f06a02dc9aa6b/propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b", size = 62732, upload-time = "2026-05-08T20:59:23.805Z" }, + { url = "https://files.pythonhosted.org/packages/39/6e/899fed76dc1942b8a64193a4f059d7f1a2c7ef65085e8a9366ed8ec0d199/propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d", size = 60140, upload-time = "2026-05-08T20:59:25.389Z" }, + { url = "https://files.pythonhosted.org/packages/ab/09/3da4be9b5b879219ad234aa535b3dd4a080ed1ad48d3a73ca07a9e798f22/propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d", size = 60400, upload-time = "2026-05-08T20:59:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/60/2f/09b72b874a9aa0044faf52a69807a6ed618e267ceaa9ec4a63195fa5b504/propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0", size = 58155, upload-time = "2026-05-08T20:59:28.48Z" }, + { url = "https://files.pythonhosted.org/packages/8a/37/97489848c54c95578045473954f10956d619ce6a09e7ac137b71cdcb698b/propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b", size = 57037, upload-time = "2026-05-08T20:59:30.146Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/6c695285ccfc49012743ee9c98212b8c5dd0aed7b63cfd816d4a0f7a1601/propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf", size = 61103, upload-time = "2026-05-08T20:59:31.626Z" }, + { url = "https://files.pythonhosted.org/packages/98/a9/1e500401ca593b0bdb6bf75a70bc2d723835fd53360edff6af70692c7546/propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf", size = 60394, upload-time = "2026-05-08T20:59:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/1f/87/f638b6e375eae0f30a1a2325d8b34fd85fdc785bb9960cf805f3bf1ec69a/propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e", size = 63084, upload-time = "2026-05-08T20:59:35.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/18/884573f5d97b6d9eba68de759a82c901b7e39d7904d30f7b8d58d42d2a12/propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274", size = 60999, upload-time = "2026-05-08T20:59:38.481Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1a/c3915eb059ceec9e758a56e4cfd955292bc0f201be2176a46b76d94b303a/propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe", size = 39036, upload-time = "2026-05-08T20:59:40.323Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/1dfd5607501a602d19c1c449d2d193b7d1c611f9246b4059026a1189a80e/propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d", size = 42190, upload-time = "2026-05-08T20:59:42.232Z" }, + { url = "https://files.pythonhosted.org/packages/57/93/f71588ad08b3e6f4b555b5ef215808a3c02b042d0151ad82fa6f15be677a/propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5", size = 38545, upload-time = "2026-05-08T20:59:44.087Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[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, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[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, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pwinput" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/c9/f6d03c2214c282251baa38c6be08f5b9f81f1ea488716cd2335e7e190b58/pwinput-1.0.3.tar.gz", hash = "sha256:ca1a8bd06e28872d751dbd4132d8637127c25b408ea3a349377314a5491426f3", size = 4433, upload-time = "2023-02-16T17:04:14.592Z" } + +[[package]] +name = "pyarrow" +version = "25.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/e3/27f57f80141379d60defe6703eb50a707325706f07fedfd1312c7a751995/pyarrow-25.0.1.tar.gz", hash = "sha256:9150a83248bfed9813ea3c3af74c3856c1984d444aa28e58bf7733b9750ddf6a", size = 1201653, upload-time = "2026-08-10T12:40:53.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/3e/5cd70becb51e1d044c54ba5e627424a6e87df5b98008cbd22cc6abd409ca/pyarrow-25.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0b1edbb2f385a6a65e9711b62ba86ac54a7816a3f8d17bb3e8a5929d65fb2485", size = 35954271, upload-time = "2026-08-10T12:36:33.857Z" }, + { url = "https://files.pythonhosted.org/packages/64/be/17599e086df264ea7dc221d1101e3131e181e00da428a2f9bd0358f0d06b/pyarrow-25.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:a4dd8bf99a8fac133efc0ed6a92f5fddbe2adba0d0f6dd720e39ba9855cea85c", size = 37647543, upload-time = "2026-08-10T12:36:39.486Z" }, + { url = "https://files.pythonhosted.org/packages/42/34/e138b451fd3970a6eda4599f68ae3b2b32b661bc958de3239d54a0bf6575/pyarrow-25.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bddd0c4f7630c2a3ddf6347c1bdaa79d97bcf6bd445f9e60c816b7d77c85a5ae", size = 46837120, upload-time = "2026-08-10T12:36:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/57/5c/f8fc0eb2de03464a557d5a4d0c15e972d73362414696618833b771f7eddd/pyarrow-25.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a4d6d5e9a3d1879a97c08ded0c797579b7965eafd0f0c26c30b45ccc06db939b", size = 50066460, upload-time = "2026-08-10T12:36:53.702Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d1/0dd64fd06de0333b808a02f60981635f067b71aad3a30698a9a104fae778/pyarrow-25.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:514ddb60285631af068875550c90eddc181db3e8e63a032b1559be189e82f056", size = 49937892, upload-time = "2026-08-10T12:37:00.349Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3c/f89d1bd76d5f3284c2a44d7d7ebbd8204535e5ae2b41f4077069b4ff2ec6/pyarrow-25.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cab40b1edfef0262e0e5251aa2c58d75630f24d06dd7794480243acc001a1d7d", size = 53107240, upload-time = "2026-08-10T12:37:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/67/67/b554a8e09f3f3decccf405eb8fbe86696321cbcb5b62d18b4a5057a4c113/pyarrow-25.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:60e89d8f13861a1f7f8d950fa54aebb8023b30734d0ac51ffa80beabe2df4bba", size = 27848683, upload-time = "2026-08-10T12:37:12.058Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8b/0d23b47702fcfe8b3618d5292035099675c5a1c48258932350c08020f7b5/pyarrow-25.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:51093dd9e10325fbdb3c10a2ae7c4806e5c822d94e74ae4938b26524a3323fee", size = 35946180, upload-time = "2026-08-10T12:37:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/d8/17/707d17a5476c55a9541fde0db8213ac30979a792864d72415f176ba50c45/pyarrow-25.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:eb6203482ff3746a5632303a7279ae0b5a304c46985b49ed1378cb350ea6728d", size = 37644787, upload-time = "2026-08-10T12:37:25.795Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b2/cdc98ecf1a6408280bc3a6a07054cdd99a3f4670acc0545d383ce113e87d/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:880523be3d29efcf83d3998835d206118ccf35e3871dbd2fb60408cf6b007a80", size = 46834633, upload-time = "2026-08-10T12:37:33.604Z" }, + { url = "https://files.pythonhosted.org/packages/c8/6e/d3fafc41f378b2c65be43b827798c0fae42049a641c8526633ed3eb573e2/pyarrow-25.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:25f8720bf6387d5dc2ebd2622112de630760419e4b66134405dd24110d15f37e", size = 50065507, upload-time = "2026-08-10T12:37:40.565Z" }, + { url = "https://files.pythonhosted.org/packages/d5/12/8d0698954b8c3001844a898e0a6900bebe83d7ee40c11195174c5122f324/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4facd65742a024a4a366328a1d2292062d72d6e023c1b7dda8d4c37544933a25", size = 49955690, upload-time = "2026-08-10T12:37:46.644Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0b/1ecb936ac6409e90a34d58eea1c7cec09a9ae6d2141b9e49ad01a2b1ea47/pyarrow-25.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa0559502e1cd6254d6814614085dd9c5a3dd0419362978a936a3f68a9e5c3df", size = 53128198, upload-time = "2026-08-10T12:37:52.531Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1c/5236033550633c9b7377b2a53660b2bbb06cb06dc09c4356332d67643ca1/pyarrow-25.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:62cd0d785b8aa6675ee355f9fc02252a340f4441257c42674937826fd7594325", size = 27857263, upload-time = "2026-08-10T12:37:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e2/9ab15b88cbfac28e16419ce5439ec29234c5172cb8259301b4ba639bdec0/pyarrow-25.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:df961f2e7ae9cf496459259d798652c70625f6c080650d6952f8c04053c58ee9", size = 35861559, upload-time = "2026-08-10T12:38:02.567Z" }, + { url = "https://files.pythonhosted.org/packages/58/79/a0036dbe1eabe1f73127427342f1d99982584c4a2cde2651d6c93499c6f6/pyarrow-25.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:cc4aa407fde9fc660be3939e49ea31f50f3e9fec17c0ec63159f7711edd3efc9", size = 37628383, upload-time = "2026-08-10T12:38:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/13/49/d93a57d375f4bf0cf82913dd6bb54acafde83dd993be2282c81ac5616cad/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4340f0ba6c1d2e13f21658de1d7c662ca2545018568d0030a1e9afca159d87e3", size = 46820190, upload-time = "2026-08-10T12:38:15.458Z" }, + { url = "https://files.pythonhosted.org/packages/60/c9/711ca85d79f1ec98f29a5eae2b051e25b4ecec5de3e3c0e2d5c5dcb15664/pyarrow-25.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5389cdf79447ed1515c9e31620e6e1e2302249564d603f2ad727d4f6d313e4c3", size = 50102437, upload-time = "2026-08-10T12:38:22.487Z" }, + { url = "https://files.pythonhosted.org/packages/80/53/8fb8359ff17cfb6263a1cf3ebf7caec9fe197de118719e84fcb1d0618026/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d51592cb7561e87877c506113e7adbf1342ab579e6c21f0ef44b8ba41cb74c80", size = 49942424, upload-time = "2026-08-10T12:38:28.755Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/4e5ae02a9341571b18a6fca380ac7a58ce6ddae7ab3c060208c0a1e79f02/pyarrow-25.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6109c94d8b9f3b17a041daca16cacb2f651ad8f1ef70a4232c2c0f37a23da2a8", size = 53144206, upload-time = "2026-08-10T12:38:34.862Z" }, + { url = "https://files.pythonhosted.org/packages/65/ee/197cbf47e49f83e6ebeb946a5259a48a638dea27ac774db42fe78022179d/pyarrow-25.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8858d7bfc22e3f51529aeaa4077225029724623e4595dc9eff8c793935c34140", size = 27953934, upload-time = "2026-08-10T12:38:39.808Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8d/8f271a7a034c834910ec925d56fa4b29733b1380f5289419f5aaa3b02777/pyarrow-25.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c7c534ec03c358a76ea3e505e74c1b6aef290af90c444dfd092dbfe23e755b85", size = 35855328, upload-time = "2026-08-10T12:38:45.489Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cd/5bac242f4e841b9971d5eb94fdfe2577e2b70be983e27401e72055786037/pyarrow-25.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:dda9470024204d7bbf2042b47c6e8a0e47a3eeb8e34405882dfaea6577e0c153", size = 37622415, upload-time = "2026-08-10T12:38:51.107Z" }, + { url = "https://files.pythonhosted.org/packages/63/1f/96d03b4e1506524f7087adb0fd6b2f69f0c9c7aaff1ec36d8030082e15a5/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:44a9120ce5bd81936b8ab9a88076e3fd47c2c6838e0e43630fed83626aca81d9", size = 46813813, upload-time = "2026-08-10T12:38:57.773Z" }, + { url = "https://files.pythonhosted.org/packages/98/d6/33a411115b61dbfc16ad6ad73e71730f6fea654ee3667673bc53ab0e2fe7/pyarrow-25.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0befcf816e45a1af33ac775a9970b749e4868a230c7372f0ae5e932bee27039f", size = 50104452, upload-time = "2026-08-10T12:39:04.579Z" }, + { url = "https://files.pythonhosted.org/packages/33/ae/b1b97c9ca87f9f9ddbb5230c798df94eccce61bd79b9b45458c69a478588/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f89685964f46e4216103c75483aac0c0692a5f72212d7ca835adba5ede56ce3", size = 49951343, upload-time = "2026-08-10T12:39:11.8Z" }, + { url = "https://files.pythonhosted.org/packages/98/9e/a112df5cfd5a68cb1d9fc31cfe38c28d5aec9f10865ce37ecef2e4450873/pyarrow-25.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6943e2fe7954d29d84de45d29d34c8dc36ce96570e67d89aa9976e650a4a9138", size = 53144784, upload-time = "2026-08-10T12:39:20.503Z" }, + { url = "https://files.pythonhosted.org/packages/31/24/97e8bd98f1e3b07e2ba08bcdff690674fbe16d69a7d2712cc3884665e615/pyarrow-25.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:31e49a7888fcdf3a835da33ae777f6bb9a866334e5a789282fc26dcf426f7f15", size = 27870159, upload-time = "2026-08-10T12:39:26.161Z" }, + { url = "https://files.pythonhosted.org/packages/36/4c/b525824ad3094076919273cd97db61fb3d78252dee76fa3b8dc8f76774aa/pyarrow-25.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bf0b672390cdcb640d7288f96b826d71ff4e9abb254a86c89890baf51a29cee6", size = 35885255, upload-time = "2026-08-10T12:39:32.366Z" }, + { url = "https://files.pythonhosted.org/packages/08/62/448bb0e940de41aec31d1a956e63ad9c54afdf122a103cc3ab20c2a3ce33/pyarrow-25.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:38a9a4b4b9613380e200641891495a56c3d5a98a092db4a870af9975e220471d", size = 37644461, upload-time = "2026-08-10T12:39:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9a/13587e38bd4806fd218f50fd13b8903fab60588a699ff0c406372e5b4043/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b726ad7e7b669be982b0c71c07fe4b037d654354130da79a7902a669e93a66b", size = 46877146, upload-time = "2026-08-10T12:39:43.722Z" }, + { url = "https://files.pythonhosted.org/packages/8d/61/1c5d1229fa21da4cff5365e41e57177aaac57c563c727f35419b8513d1c1/pyarrow-25.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:9171748cdf796972d85a4b60157c279913e242992e350c90c7450182a9838b2a", size = 50131616, upload-time = "2026-08-10T12:39:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/43/20/291e1d65cc0b09aa19f03cf25cf51a2f5fa94b5db315178f2d254ed5cad4/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b7a296aac7a71fa0886c08e155ddb6c636a50013f801f6178daafa0f9e726188", size = 50008879, upload-time = "2026-08-10T12:39:56.891Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7c/1b7c9ec28e76576337e4f97b31141c9a181b89b6d1d6221e9d8205621a58/pyarrow-25.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0fe7c8b6c03969b49c8c66182e4a18e3819ab92d07cfab5d8370c531b9369ef0", size = 53170864, upload-time = "2026-08-10T12:40:04.918Z" }, + { url = "https://files.pythonhosted.org/packages/b7/75/f3d789dc06011a765d14d86bda799cf72ac1d715b6a6edecaa0d73d95062/pyarrow-25.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:f729cfdbd36fd99d543b67a914d2de044c84ebe45be8b34902b299b608c15c8f", size = 28620729, upload-time = "2026-08-10T12:40:51.41Z" }, + { url = "https://files.pythonhosted.org/packages/fc/05/647a8ee6f7c2662feb6921315617bc04dcd6034763fb61b1199720bf6162/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:59a2de54c0cbd954da861eee4d1d330f8e909c45b53455baef696380f2c55033", size = 36130288, upload-time = "2026-08-10T12:40:11.014Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/c9ee997554d7bea94520667dd1933f109ac1da3ee3556d2b49381e023484/pyarrow-25.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:35935cd5de130aa5cf4dea052a63e6bf2e17006c35c3a468194242b9b2bf5956", size = 37762187, upload-time = "2026-08-10T12:40:16.592Z" }, + { url = "https://files.pythonhosted.org/packages/a2/08/a28c01c7fe9e96e8233ce2d13df1d402f4f999f848f51d2daacd6bb4c036/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f3831aaa25c67a99f99dc8b05873cb9d64560390372e2aa197ce9dd4a3f06a44", size = 46888003, upload-time = "2026-08-10T12:40:23.242Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b9/58612e977d28dc58c878448866838369ee8da2f1e7cc8ed2c84b952aafee/pyarrow-25.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6a1fdfc6659b6b19022f2e50627fb5cf7156a66c46bf4299379955cbe742382a", size = 50079036, upload-time = "2026-08-10T12:40:29.169Z" }, + { url = "https://files.pythonhosted.org/packages/72/13/66e1402dcc860e1dc2760b1e0292c9a569b62b3bccab69def1b3e907d006/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:169d3429d5be7c752125890620f75a60776d38b0035eddae939651640822332e", size = 50040226, upload-time = "2026-08-10T12:40:35.186Z" }, + { url = "https://files.pythonhosted.org/packages/78/10/3f1a5497a7ef732ab0f03ecca3e66d89d9c0f57fdc61b4794c456b781f01/pyarrow-25.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:119297a6dc197e45d9c6d4415f7814a67ffa36c180d26f68c154c58067ae782d", size = 53149035, upload-time = "2026-08-10T12:40:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/93/c0/37d4a7e8e2f7a6076283673d5298018ca26478b934c6ee369e10505ab32c/pyarrow-25.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4288f27577352d608ca08553b0865e4a9b3aa14820c5d95b53337218d609835b", size = 28753071, upload-time = "2026-08-10T12:40:46.623Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +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/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" }, +] + +[[package]] +name = "pyecharts" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "prettytable" }, + { name = "simplejson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/f4/f408dffd3ca3eeaaf385cfcf49b913883f5db83e1e56e25ecb16e2b27468/pyecharts-2.1.0.tar.gz", hash = "sha256:077f8205390aea705b8cc0be9d0c09916a2c060e9e5c8d9c9d11b7d8f4bdc1da", size = 172645, upload-time = "2026-02-10T11:53:38.377Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/4d/518d1c17575dc3a51f3c17490e75b1811873168a165c4eb585af9da093e3/pyecharts-2.1.0-py3-none-any.whl", hash = "sha256:5cba0aa06af3d9390a1fed18aa4e76acd7bdfb323cdf131874f46eaf7e78c6c7", size = 161148, upload-time = "2026-02-10T11:53:36.943Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyroaring" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/e4/975f0fa77fc3590820b4a3ac49704644b389795409bc12eb91729f845812/pyroaring-1.0.3.tar.gz", hash = "sha256:cd7392d1c010c9e41c11c62cd0610c8852e7e9698b1f7f6c2fcdefe50e7ef6da", size = 188688, upload-time = "2025-10-09T09:08:22.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/87/f19328d35f29b1d634f8f2127941927b37c6b68890912a4668e1a272d54d/pyroaring-1.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c10e4cfbe203a578c78808406af491e3615d5e46cf69a7709050243346cd68bc", size = 670434, upload-time = "2025-10-09T09:06:34.89Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fa/d933448844925728990e2256bbfa4f18cc3956d43d548270317128038015/pyroaring-1.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc329c62e504f2531c4008240f31736bcd2dee4339071f1eac0648068e6d17fa", size = 367332, upload-time = "2025-10-09T09:06:36.549Z" }, + { url = "https://files.pythonhosted.org/packages/39/e4/ab74296c3aac8f3ceb800354c2f811de2c4c518b3ca3a7a28e0599740cff/pyroaring-1.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c7fb6ddf6ef31148f0939bc5c26b681d63df301ee1e372525012dd7bfe4a30a", size = 311443, upload-time = "2025-10-09T09:06:37.617Z" }, + { url = "https://files.pythonhosted.org/packages/6b/71/13ff623f3bba340ea7cc841883d7a0eaba1bec7e2d4e0d6759b89a4ce754/pyroaring-1.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd18446832ea04a7d33bd6b78270b0be14eabcda5937af3428d6cb3d2bf98e54", size = 1853845, upload-time = "2025-10-09T09:06:38.706Z" }, + { url = "https://files.pythonhosted.org/packages/2b/05/40c0b37d78b16842c924b87b4fa491f4b20ed0e40c6255c872df45314247/pyroaring-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f0cbc766df2a24e28f23d69b66bbec64e691799219fd82c2f2236f03fc88e2e", size = 2045170, upload-time = "2025-10-09T09:06:40.229Z" }, + { url = "https://files.pythonhosted.org/packages/78/c6/6560c61d2f5c30fbb8f7b9a1c7d02068e78a7e8cd336eb294ec70896a80a/pyroaring-1.0.3-cp310-cp310-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96a51e96f8f473381615f0f852f7238ad0a47f28e4a35e9f082468c5cfe4e9c3", size = 1791073, upload-time = "2025-10-09T09:06:42.092Z" }, + { url = "https://files.pythonhosted.org/packages/33/9e/0c91d4dbc4ec7bea9dcd3c203cfb8d96ed9df3c46981c0b22e9f17e98296/pyroaring-1.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:654af38b1f1c9bdc27b4f6d331fc5d91599df96e72a6df1886f4d95eea60ab29", size = 1789048, upload-time = "2025-10-09T09:06:43.392Z" }, + { url = "https://files.pythonhosted.org/packages/e9/48/51af418321cda2a1cfa64a48397ea0b73da74afe5c53a862525476d8a42c/pyroaring-1.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6721036afa31c07bdcbb4fcafa166660cf9c2eac695dcd495f8778549fa55899", size = 2838811, upload-time = "2025-10-09T09:06:44.588Z" }, + { url = "https://files.pythonhosted.org/packages/b2/69/7bc7070b35f72706e3870b5856e73656b9065bedae90268da5d77be00b15/pyroaring-1.0.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0caa10f20329d09233fac6550b2adce4d9f173f748a9a9a5ea3b7033827dfe2d", size = 2640299, upload-time = "2025-10-09T09:06:46.159Z" }, + { url = "https://files.pythonhosted.org/packages/d0/87/5f2f590973d454e79ee8729aca888f9bb2d6018f7c9816bf66000cbc5e88/pyroaring-1.0.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f109be8af937e85c52cb920d3fd120db52b172f59460852d2e3d2e3d13a4f52a", size = 2965427, upload-time = "2025-10-09T09:06:47.558Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c8/1b425503141681db94941d9d3f41333bbd61975c3fc62d95122c372da85a/pyroaring-1.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ddc80bfcd313c7c524a2742d263e73cae088b6a611b77dcc46fa90c306f6dace", size = 3084447, upload-time = "2025-10-09T09:06:48.727Z" }, + { url = "https://files.pythonhosted.org/packages/51/b9/2ac712ea90bd1e0d7e49e5e26c0c5aad1d77f21cf520a7edf46a477f5217/pyroaring-1.0.3-cp310-cp310-win32.whl", hash = "sha256:5a183f5ec069757fe5b60e37f7c6fa8a53178eacf0d76601b739e2890edee036", size = 204956, upload-time = "2025-10-09T09:06:49.801Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/1d4859c74d05f72b86dc0b308e6221e814a178459301cea9bcd084b4a92b/pyroaring-1.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:051bd9a66ce855a1143faa2b879ea6c6ca2905209e172ce9eedf79834897c730", size = 253778, upload-time = "2025-10-09T09:06:50.68Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/b421100bd14b6a1074945af1418671630e1b8c4996ef000ac4e363785ead/pyroaring-1.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:3043ff5c85375310ca3cd3e01944e03026e0ec07885e52dfabcfcd9dc303867f", size = 219330, upload-time = "2025-10-09T09:06:52.011Z" }, + { url = "https://files.pythonhosted.org/packages/39/ed/5e555dd99b12318ea1c7666b773fc4f097aeb609eeb1c1b3da519d445f71/pyroaring-1.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:755cdac1f9a1b7b5c621e570d4f6dbcf3b8e4a1e35a66f976104ecb35dce4ed2", size = 675916, upload-time = "2025-10-09T09:06:53.174Z" }, + { url = "https://files.pythonhosted.org/packages/da/06/dd8a9a87b90c4560f8384ab1dbafcd40c2a16f6777a07334a8e341bd7383/pyroaring-1.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ebab073db620f26f0ba11e13fa2f35e3b1298209fba47b6bc8cb6f0e2c9627f9", size = 369743, upload-time = "2025-10-09T09:06:54.421Z" }, + { url = "https://files.pythonhosted.org/packages/35/aa/da882011045ddacffe818a4fcbdd7e609a15f9c83d536222ec5b17af4aa9/pyroaring-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:684fb8dffe19bdb7f91897c65eac6eee23b1e46043c47eb24288f28a1170fe04", size = 313981, upload-time = "2025-10-09T09:06:55.514Z" }, + { url = "https://files.pythonhosted.org/packages/ed/3c/f6534844b02e2505ccdc9aae461c9838ab96f72b5688c045448761735512/pyroaring-1.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:678d31fc24e82945a1bfb14816c77823983382ffea76985d494782aa2f058427", size = 1923181, upload-time = "2025-10-09T09:06:56.897Z" }, + { url = "https://files.pythonhosted.org/packages/ea/82/9f1a85ba33e3d89b9cdb8183fb2fd2f25720d10742dd8827508ccccc13ae/pyroaring-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d815f624e0285db3669f673d1725cb754b120ec70d0032d7c7166103a96c96d", size = 2113222, upload-time = "2025-10-09T09:06:58.388Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f8/4d4340971cbc1379f987c847080bcb7f9765a57e122f392c3a3485c9587e/pyroaring-1.0.3-cp311-cp311-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:57fd5b80dacb8e888402b6b7508a734c6a527063e4e24e882ff2e0fd90721ada", size = 1837385, upload-time = "2025-10-09T09:06:59.449Z" }, + { url = "https://files.pythonhosted.org/packages/c6/58/d14cc561685e4c224af26b4fdb4f6c7e643294ac5a4b29f178b5cbb71af1/pyroaring-1.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab26a7a45a0bb46c00394d1a60a9f2d57c220f84586e30d59b39784b0f94aee6", size = 1856170, upload-time = "2025-10-09T09:07:00.608Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d2/d2d9790c373f6438d4d0958bc4c79f3dc77826d8553743ff3f64acdc9ab3/pyroaring-1.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9232f3f606315d59049c128154100fd05008d5c5c211e48b21848cd41ee64d26", size = 2909282, upload-time = "2025-10-09T09:07:02.124Z" }, + { url = "https://files.pythonhosted.org/packages/bc/28/4b2277982302b5b406998064ca1eaef1a79e4ea87185f511e33e7a7e3511/pyroaring-1.0.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f34b44b3ec3df97b978799f2901fefb2a48d367496fd1cde3cc5fe8b3bc13510", size = 2701034, upload-time = "2025-10-09T09:07:03.403Z" }, + { url = "https://files.pythonhosted.org/packages/d2/91/b2340193825fa2431cf735f0ecb23206fb31f386fecca38336935a294513/pyroaring-1.0.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25a83ec6bac3106568bd3fdd316f0fee52aa0be8c72da565ad02b10ae7905924", size = 3028962, upload-time = "2025-10-09T09:07:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/07/ea/ad79073cc5d8dcca35d1a955bb886d96905e9dacc58d1971fda012a5ad18/pyroaring-1.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c17d4ec53b5b6b333d9a9515051213a691293ada785dc8c025d3641482597ed3", size = 3152109, upload-time = "2025-10-09T09:07:06.887Z" }, + { url = "https://files.pythonhosted.org/packages/9a/de/f55a1093acb16d25ff9811546823e59078e4a3e56d2eb0ff5d10f696933d/pyroaring-1.0.3-cp311-cp311-win32.whl", hash = "sha256:d54024459ace600f1d1ffbc6dc3c60eb47cca3b678701f06148f59e10f6f8d7b", size = 204246, upload-time = "2025-10-09T09:07:08.036Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e5/36bf3039733b8e00732892c9334b2f5309f38e72af0b3b40b8729b5857a3/pyroaring-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:c28750148ef579a7447a8cb60b39e5943e03f8c29bce8f2788728f6f23d1887a", size = 254637, upload-time = "2025-10-09T09:07:09.103Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e8/e2b78e595b5a82a6014af327614756a55f17ec4120a2ab197f1762641316/pyroaring-1.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:535d8deccbd8db2c6bf38629243e9646756905574a742b2a72ff51d6461d616c", size = 219597, upload-time = "2025-10-09T09:07:10.38Z" }, + { url = "https://files.pythonhosted.org/packages/dd/09/a5376d55672e0535019ba1469888909d0046cea0cfb969a4aa1f99caaf22/pyroaring-1.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:add3e4c78eb590a76526ecce8d1566eecdd5822e351c36b3697997f4a80ed808", size = 681056, upload-time = "2025-10-09T09:07:11.497Z" }, + { url = "https://files.pythonhosted.org/packages/23/dd/78f59d361bd9ebf8de3660408b0c48664ade0a057ebcf4b207d99ac1a698/pyroaring-1.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ebaffe846cf4ba4f00ce6b8a9f39613f24e2d09447e77be4fa6e898bc36451b6", size = 375111, upload-time = "2025-10-09T09:07:12.597Z" }, + { url = "https://files.pythonhosted.org/packages/bf/03/10dc93f83a5453eb40a69c79106a8385b40aa12cf4531ca72bd9d7f45cb2/pyroaring-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9459f27498f97d08031a34a5ead230b77eb0ab3cc3d85b7f54faa2fd548acd6", size = 314319, upload-time = "2025-10-09T09:07:13.579Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/b00c38a7e62a73e152055f593595c37152e61fc2896fd11538a7c71fbe4e/pyroaring-1.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2b2eb8bd1c35c772994889be9f7dda09477475d7aa1e2af9ab4ef18619326f6", size = 1869251, upload-time = "2025-10-09T09:07:14.584Z" }, + { url = "https://files.pythonhosted.org/packages/4f/33/f32d00ca105b66303deab43d027c3574c8ade8525dac0e5b50a9fb4d1b76/pyroaring-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d31f4c1c906f1af14ce61a3959d04a14a64c594f8a768399146a45bbd341f21f", size = 2071551, upload-time = "2025-10-09T09:07:15.713Z" }, + { url = "https://files.pythonhosted.org/packages/5d/89/e953cae181ba4c7523334855a1ca0ae8eeea3cee8d7cd39c56bd99709d3f/pyroaring-1.0.3-cp312-cp312-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53be988fc86698d56c11049bfe5113a2f6990adb1fa2782b29636509808b6aa7", size = 1781071, upload-time = "2025-10-09T09:07:17.19Z" }, + { url = "https://files.pythonhosted.org/packages/fa/db/65d4be532e68b62a84a9c89b24d0a1394f452f484fa29392142d9a3b9c48/pyroaring-1.0.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7df84d223424523b19a23781f4246cc247fd6d821e1bc0853c2f25669136f7d0", size = 1795670, upload-time = "2025-10-09T09:07:18.524Z" }, + { url = "https://files.pythonhosted.org/packages/f5/9e/684ea0568ce7d30fc4e01ad1c666e9ce1a5b1702fa630231f4f6bdb96539/pyroaring-1.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34a781f1f9766897f63ef18be129827340ae37764015b83fdcff1efb9e29136d", size = 2849305, upload-time = "2025-10-09T09:07:20.388Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fd/d7773a2adf91f45d8924197954c66b1694325afd2f27e02edaac07338402/pyroaring-1.0.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1f414343b4ed0756734328cdf2a91022fc54503769e3f8d79bd0b672ea815a16", size = 2692843, upload-time = "2025-10-09T09:07:22.042Z" }, + { url = "https://files.pythonhosted.org/packages/13/72/b8a99ba138eebd8ff9bf8d15f3942e9e43e8e45723e2e6b7b09e542b7448/pyroaring-1.0.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d16ae185c72dc64f76335dbe53e53a892e78115adc92194957d1b7ef74d230b9", size = 2983440, upload-time = "2025-10-09T09:07:23.419Z" }, + { url = "https://files.pythonhosted.org/packages/ca/94/e6ed1f682d850e039c71b2032bacdefc5082dc809796cf34b9e6f24c604d/pyroaring-1.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f888447bf22dde7759108bfe6dfbeb6bbb61b14948de9c4cb6843c4dd57e2215", size = 3117542, upload-time = "2025-10-09T09:07:25.104Z" }, + { url = "https://files.pythonhosted.org/packages/8f/89/d55b0ed3e098ef89c421b43b748afe3d90eb250cab50b9e53e3a3449ac58/pyroaring-1.0.3-cp312-cp312-win32.whl", hash = "sha256:fbbdc44c51a0a3efd7be3dbe04466278ce098fcd101aa1905849319042159770", size = 205118, upload-time = "2025-10-09T09:07:26.532Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e1/b71fef6a73efb50110d33d714235ff7059f4ebae98dc474b6549b322f48f/pyroaring-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:3b217c4b3ad953b4c759a0d2f9bd95316f0c345b9f7adb49e6ded7a1f5106bd4", size = 260629, upload-time = "2025-10-09T09:07:27.528Z" }, + { url = "https://files.pythonhosted.org/packages/57/33/66ee872079c9c47512d6e17d374bcad8d91350c24dc20fbe678c34b33745/pyroaring-1.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:e6bcf838564c21bab8fe6c2748b4990d4cd90612d8c470c04889def7bb5114ea", size = 219032, upload-time = "2025-10-09T09:07:28.754Z" }, + { url = "https://files.pythonhosted.org/packages/1f/95/97142ee32587ddda9e2cd614b865eeb5c0ee91006a51928f4074cd6e8e5f/pyroaring-1.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:20bc947054b197d1baa76cd05d70b8e04f95b82e698266e2f8f2f4b36d764477", size = 678813, upload-time = "2025-10-09T09:07:29.936Z" }, + { url = "https://files.pythonhosted.org/packages/70/5e/cff22be3a76a80024bdf00a9decdffedc6e80f037328a58b58c1b521442d/pyroaring-1.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ba5909b4c66bb85cab345e2f3a87e5ce671509c94b8c9823d8db64e107cbe854", size = 373661, upload-time = "2025-10-09T09:07:30.983Z" }, + { url = "https://files.pythonhosted.org/packages/86/73/fc406a67cd49e1707d1c3d08214458959dd579eff88c28587b356dfa068b/pyroaring-1.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b744746ba5da27fad760067f12633f5d384db6a1e65648d00244ceacbbd87731", size = 313559, upload-time = "2025-10-09T09:07:32.099Z" }, + { url = "https://files.pythonhosted.org/packages/f9/64/c7fe510523445f27e2cb04de6ffd3137f9d72db438b62db2bfa3dafcf4fc/pyroaring-1.0.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b16c2a2791a5a09c4b59c0e1069ac1c877d0df25cae3155579c7eac8844676e", size = 1875926, upload-time = "2025-10-09T09:07:33.701Z" }, + { url = "https://files.pythonhosted.org/packages/47/74/da9b8ad2ca9ce6af1377f2cffdad6582a51a5f5df4f26df5c41810c9de5b/pyroaring-1.0.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7f68dfcf8d01177267f4bc06c4960fe8e39577470d1b52c9af8b61a72ca8767", size = 2064377, upload-time = "2025-10-09T09:07:35.273Z" }, + { url = "https://files.pythonhosted.org/packages/99/e3/8a70c5a5f7821c63709e2769aeccda8ae87a192198374bc475cbee543a22/pyroaring-1.0.3-cp313-cp313-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dba4e4700030182a981a3c887aa73887697145fc9ffb192f908aa59b718fbbdd", size = 1778320, upload-time = "2025-10-09T09:07:36.782Z" }, + { url = "https://files.pythonhosted.org/packages/04/4c/08159a07c3723a2775064887543766b6115b4975e7baaa4d51e5580701a4/pyroaring-1.0.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e26dd1dc1edba02288902914bdb559e53e346e9155defa43c31fcab831b55342", size = 1786569, upload-time = "2025-10-09T09:07:38.473Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ff/55a18d0e7e0dc4cd9f43988b746e788234a8d660fa17367c5ed9fa799348/pyroaring-1.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6eb98d2cacfc6d51c6a69893f04075e07b3df761eac71ba162c43b9b4c4452ad", size = 2852766, upload-time = "2025-10-09T09:07:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/24/3c/419e25c51843dd40975ae37d67dea4f2f256554b5bec32237f607ec8ef21/pyroaring-1.0.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a967e9eddb9485cbdd95d6371e3dada67880844d836c0283d3b11efe9225d1b7", size = 2683904, upload-time = "2025-10-09T09:07:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/75/64/8d91f1b85b42925af632fc2c1047bb314be622dce890a4181a0a8d6e498d/pyroaring-1.0.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b12ef7f992ba7be865f91c7c098fd8ac6c413563aaa14d5b1e2bcb8cb43a4614", size = 2973884, upload-time = "2025-10-09T09:07:42.34Z" }, + { url = "https://files.pythonhosted.org/packages/61/6d/c867625549df0dc9ad675424ecf989fa2f08f0571bd46dfc4f7218737dd2/pyroaring-1.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:82ca5be174b85c40be7b00bc6bf39b2931a1b4a465f3af17ec6b9c48e9aa6fe0", size = 3103671, upload-time = "2025-10-09T09:07:44.055Z" }, + { url = "https://files.pythonhosted.org/packages/59/b1/d47c5ec2b2580d0b94f42575be8f49907a0f4aa396fdc18660f3b5060d54/pyroaring-1.0.3-cp313-cp313-win32.whl", hash = "sha256:f758c681e63ffe74b20423695e71f0410920f41b075cee679ffb5bc2bf38440b", size = 205153, upload-time = "2025-10-09T09:07:45.496Z" }, + { url = "https://files.pythonhosted.org/packages/c4/92/3600486936eebab747ae1462d231d7f87d234da24a04e82e1915c00f4427/pyroaring-1.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:428c3bb384fe4c483feb5cf7aa3aef1621fb0a5c4f3d391da67b2c4a43f08a10", size = 260349, upload-time = "2025-10-09T09:07:46.524Z" }, + { url = "https://files.pythonhosted.org/packages/77/96/8dde074f1ad2a1c3d2091b22de80d1b3007824e649e06eeeebded83f4d48/pyroaring-1.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:9c0c856e8aa5606e8aed5f30201286e404fdc9093f81fefe82d2e79e67472bb2", size = 218775, upload-time = "2025-10-09T09:07:47.558Z" }, +] + +[[package]] +name = "pyroaring" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/46/a50510d080f8cb089303ec0f7cd80736b2949ca3d148f48f1cc90c49e345/pyroaring-1.1.0.tar.gz", hash = "sha256:f02e4021397ae02a139defdc6813b9942ab163de90affddd4ce4efbac299f619", size = 200298, upload-time = "2026-04-24T21:29:25.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/b0/ab1c3b9227aadc326138cbbb973549399ece103d35b8c11952405a252a4d/pyroaring-1.1.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19c9bb8b32f17fe63e54e78f88a7a67fe2dca352bf79d7d3b43f0a901db5ea3b", size = 332486, upload-time = "2026-04-24T21:27:35.332Z" }, + { url = "https://files.pythonhosted.org/packages/b9/02/a3a916ac9b6de70ac8696f767eb3487c41ca37b7f991216abbebe528045c/pyroaring-1.1.0-cp310-cp310-macosx_14_0_universal2.whl", hash = "sha256:212f471da113eba24a0fedbd1ef078bfed34b792e8f48dba3f7301f1bca2678a", size = 707625, upload-time = "2026-04-24T21:27:36.809Z" }, + { url = "https://files.pythonhosted.org/packages/22/53/e845f27b6e6e23cb322f834fcf871e27e4e2e9284360d63e68bfcf9088dd/pyroaring-1.1.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a31276323fdd355505883162f1dca4d7acc3d6ec2159c85ddef863f1fe9e8f05", size = 382766, upload-time = "2026-04-24T21:27:38.337Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/4c9439ff556091d93c591ac466ab526991fa8745c1a15e9ca49081f215da/pyroaring-1.1.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55c9d0b708a65308eaa95c53d9de56a2b4425812395f6e57a4d9ad8efb081bf9", size = 1970617, upload-time = "2026-04-24T21:27:39.722Z" }, + { url = "https://files.pythonhosted.org/packages/b6/4a/568684cc6d5294f0ec183bacf38f5a27b2c37908d0e0ac6f4423ae7c7c5f/pyroaring-1.1.0-cp310-cp310-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5599b691e5e993f7fdd9880429d1c485eae03dc392ba5b2dbe2eef0434bea1a5", size = 1853502, upload-time = "2026-04-24T21:27:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/87/f0/82c4335651ae243eed411daa8e5acc1c62179a3e90350e4785c487e643a7/pyroaring-1.1.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8376c62e60a49e8fe51ffce569f74beed6e53fb58e5d6739f954c8c51ec0bec3", size = 2171600, upload-time = "2026-04-24T21:27:42.79Z" }, + { url = "https://files.pythonhosted.org/packages/62/b6/e6859ba41ad4c421248c2baaa4aa6c434369991200c4809cd389a4b1688f/pyroaring-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cc998e81748b72659802a7b434b39292308a5f4ab8dd4bf4739d65de1d4ed2bf", size = 2887912, upload-time = "2026-04-24T21:27:43.864Z" }, + { url = "https://files.pythonhosted.org/packages/07/c2/3f31ebc60ab094c1f2ff72f60aa0b5650d49c7653b5b575648cfb22b5441/pyroaring-1.1.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:fe0b9f0baf190663df37571cbb8a77370e7bd3a66068276d48f813c53ceba530", size = 2691997, upload-time = "2026-04-24T21:27:44.996Z" }, + { url = "https://files.pythonhosted.org/packages/00/6e/c1d09230ebe0088ca011be2879e7c2f956cbdbc530c6427f6d046658b1df/pyroaring-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:83815b3a4cb9c3b17096c70833373610d5c93e89c134548cf43436c90326bfb3", size = 3143377, upload-time = "2026-04-24T21:27:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/cd/fa/665ad8ce092e24b4d3b1f5e34b032d73c6a3f4792f229a70b702cd050408/pyroaring-1.1.0-cp310-cp310-win32.whl", hash = "sha256:e9caf67c0c05d27bab0d88cf193e73023f624c6714f1ff807c7d4b135b19d36a", size = 210064, upload-time = "2026-04-24T21:27:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/802d6edca2dfdca841ecaafb1d60c05657f2d3948b7b78eb892cf8195dac/pyroaring-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6343249ce9401dd90c3b934fd9a4a618b6fc455f27a9aa11d198eebd4743137", size = 259507, upload-time = "2026-04-24T21:27:48.788Z" }, + { url = "https://files.pythonhosted.org/packages/c9/4c/b57b59a7d7f913eb6d1ce2578fbd82a3209805a33a673f562ca1035839c1/pyroaring-1.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:8f5194c0544b08e56e108dad096c468c67f8ac60eec42763aed36ccbd3565346", size = 217549, upload-time = "2026-04-24T21:27:50.129Z" }, + { url = "https://files.pythonhosted.org/packages/4e/aa/574f153feb89856010092c33cafe4a52f4da8cea19d441cbc4cbcb8ccb1f/pyroaring-1.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:462b0952277d3100a90ae890ae641d3fb3561b10cfea542e02468f0bef7700a5", size = 332142, upload-time = "2026-04-24T21:27:51.258Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a1/938a9fbdd41699ac9419ca922fcf80eb58c0c62aa34539aa540abaee9a63/pyroaring-1.1.0-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:9cf8608c9d6cb6bff9c624744f7a2ba8ab12276f097a07930490fe0e2219e9be", size = 706441, upload-time = "2026-04-24T21:27:52.226Z" }, + { url = "https://files.pythonhosted.org/packages/06/06/5120acc9918223ccb647dfadb430da1ec08d7ada818469ad3a2f1574b8c7/pyroaring-1.1.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:44a9f9719ed18e86627286f90057f9b7e22f6ba1d952c9793f9600b5c14e8680", size = 382003, upload-time = "2026-04-24T21:27:53.322Z" }, + { url = "https://files.pythonhosted.org/packages/88/73/adc7951e0d4e65ab7a84fa5562bc28ba2dcc6f461a9156edc0d5aefda7f6/pyroaring-1.1.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c14fc6bd65e5624f76b90297b081222261476978f795f60d48745553617ddceb", size = 2034493, upload-time = "2026-04-24T21:27:54.739Z" }, + { url = "https://files.pythonhosted.org/packages/c7/28/992f2f2ef573b54559ed4bc62b5cf0ea095a59173521cfbe78dfefcc08ad/pyroaring-1.1.0-cp311-cp311-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:01212da3752d6486adcca98c9d353f8fb8e36513e05062cdd0feebc4211dbe70", size = 1901479, upload-time = "2026-04-24T21:27:57.115Z" }, + { url = "https://files.pythonhosted.org/packages/10/4b/38bded4ca17af6359c1766c25e942435332d26e2cb3321d9d081546b75f8/pyroaring-1.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:100585f438b293112e2c52e45a442835837c8a0267dd1e513bafec35628f8ecb", size = 2235338, upload-time = "2026-04-24T21:27:58.66Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ff/8e7da41468a33fc9d0fbee949e8f3f734930b5173020a478686558388f31/pyroaring-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:532e53191d8dd29dedfc5202cbb45632f7df751b207a7f6d6860fb7067c7fe11", size = 2951300, upload-time = "2026-04-24T21:28:00.12Z" }, + { url = "https://files.pythonhosted.org/packages/23/a8/f8a4b55b0817aac3caf6b3b51f2e93cf152f32784949919512bd38feb0f6/pyroaring-1.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:731a7a9e050758986d5757eea10f9ccb08f9c3ef514ce0335f4a90e126f81131", size = 2752747, upload-time = "2026-04-24T21:28:01.281Z" }, + { url = "https://files.pythonhosted.org/packages/b7/26/b5a29c0f38c581ac290245150d4d14b627f110eac208f3569d5d8739c78f/pyroaring-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9864e19109e76111befc75d799d334e7365eb4189607aa734053c12e7840fa5", size = 3203933, upload-time = "2026-04-24T21:28:02.595Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/2b3661d9a9e12dac02d2c3ef4545bd2236fcd964ba8fdd96e308ca621153/pyroaring-1.1.0-cp311-cp311-win32.whl", hash = "sha256:7caf95de39ce869ea0978068521cf6faa7350574fd1734ad6c63e5ed8cd06baa", size = 209205, upload-time = "2026-04-24T21:28:03.811Z" }, + { url = "https://files.pythonhosted.org/packages/be/1f/416a8cf29738d2e8c552fcaff7951c2a9a3bac0faffbb88b888287665834/pyroaring-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a23cd023985b5f2ba23e84e1fadaeacde3c8a59e1d2adb3fe782e99db1e22387", size = 260338, upload-time = "2026-04-24T21:28:04.752Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0d/fd3f57014e98ffd20a3db7fc07157be8abb6cc5a356ccb20e1ea2493c397/pyroaring-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:a98d1147fe1d3195053b67b474bccc0be5021506765d27f613a943c8c99f9e4c", size = 217570, upload-time = "2026-04-24T21:28:06.126Z" }, + { url = "https://files.pythonhosted.org/packages/96/e9/d8dcccfdac1657e6a53b6ade0c0c71d59244316810c82537a5d634f8b7fd/pyroaring-1.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fdf484d26016e0c016f23f2b635d2899daec034565fdcc062ed6b10f3b26a3f4", size = 334166, upload-time = "2026-04-24T21:28:07.184Z" }, + { url = "https://files.pythonhosted.org/packages/1a/16/70f8268c9bac4f6d91a82df254332edfa07020fe02e97c6d3d0295ee4db3/pyroaring-1.1.0-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:e9c2b9aa8decdcf40ed8f4c887092c20a272f8c32215c3fee65e9db92ecf418e", size = 711970, upload-time = "2026-04-24T21:28:08.929Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a6/0b88a8a8e4ffdd0bf53765c3ea17ce3b747f7de42b1f10d5c50a13ba3ecc/pyroaring-1.1.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:5eb031237e9d39cbdfc9276facacdd88e27aefb58940bd8b56b878dfd38d6022", size = 385825, upload-time = "2026-04-24T21:28:10.077Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a7/f06a899b896bb74a031201fbba707abf23e2485f44ead28d2e91977ee204/pyroaring-1.1.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:66aa6a321fbc598f26d5e66050a7f145c2253f3fe5737b589841ff0cbe5cb177", size = 2000799, upload-time = "2026-04-24T21:28:11.276Z" }, + { url = "https://files.pythonhosted.org/packages/eb/16/b13e0727ef5c91c84ac5d9b5c4af43cb3f28d8822d96d44aa4aeefc10b37/pyroaring-1.1.0-cp312-cp312-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cff06d18c9a30f8547a92757078aa345db1ba5b22e3082a05f64e50b384e27a", size = 1843405, upload-time = "2026-04-24T21:28:12.394Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4b/ff77d6eb4747c65aba49d345318059f1ccfbd206bbcd34686cea819187e9/pyroaring-1.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:043dbbaa905f7288c515ac06a96b67a3763f35e9ae06f0c0278c0d9964d16760", size = 2224307, upload-time = "2026-04-24T21:28:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/50/d1/9b4e2175a9bd07592ef1c6f4692ba0cfe3abd54f33ebd574aa2b2ab88c2c/pyroaring-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71fec09bd42f8c33ac3b762cd00c5db842eb583ffd0e361739ce1c17ad078a6a", size = 2902586, upload-time = "2026-04-24T21:28:15.485Z" }, + { url = "https://files.pythonhosted.org/packages/67/24/904952d6bfe2e4946f361958157774230cb1ac171d306e2460620274c58a/pyroaring-1.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c9f30ca28b991a920b446ed3ee19c7ecafcc49c46db592abf89cf239a7bb45f4", size = 2741581, upload-time = "2026-04-24T21:28:17.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/f5/8ad5605870fbdcb568f0b847e1fea24adea15e07c90231fb62f339c08b14/pyroaring-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67650460c65bdd7b4f5078d9c955aa38f64627d02cb48f9cfb24eae84bca2aba", size = 3182906, upload-time = "2026-04-24T21:28:18.435Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5d/264414a1b1bea72b2a7756e2b1dca709e5c695b0cd332a8f90c297ae3b33/pyroaring-1.1.0-cp312-cp312-win32.whl", hash = "sha256:61a8eabee99104ca197b6e7cce05dc4f27f503be52881800cd370eb5a5152d3f", size = 211231, upload-time = "2026-04-24T21:28:20.414Z" }, + { url = "https://files.pythonhosted.org/packages/ea/39/dd3341e235a3794c613ea32bd35618a88ff2ae067dbe9dd7c382c8c146d2/pyroaring-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:51ebe5e6f48e3dc9df91a4cb62137ef72e1469acd6f37479abd9991f6d945cc9", size = 263337, upload-time = "2026-04-24T21:28:21.371Z" }, + { url = "https://files.pythonhosted.org/packages/12/5d/bb8e93dd7412180c621086ed46014a0f09f9a71d9370ce8cf607c5a2cf00/pyroaring-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:9882a204178cc8c915e0ce30abb4bdd1668e383c571b06649d5ed272d9625877", size = 216727, upload-time = "2026-04-24T21:28:22.536Z" }, + { url = "https://files.pythonhosted.org/packages/7d/75/1d39ecb04e6cd96d191eb8884864355051df80928dd5096a9dea43fbf63b/pyroaring-1.1.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:72f68a16b00b35481d9b3bfe897ecd8a1f7da69efd92ba5b17347ca11c21cb0d", size = 333363, upload-time = "2026-04-24T21:28:23.838Z" }, + { url = "https://files.pythonhosted.org/packages/20/3e/65cd0871e86d11c5c5cfd0f5abb0ca80eb2b6b5dbe5a2433f315a9ebd90c/pyroaring-1.1.0-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:4c443e9f942b6089efe8c9b264576e9d116f90be28a315679375bba2d8a915d6", size = 710573, upload-time = "2026-04-24T21:28:24.884Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a2/f8f23515f41414332e60cd86e4957e2a6838070b2ad5fe25e80f136de635/pyroaring-1.1.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3beb40eb1220d1ce4fb3661bb019e9a21857e5bb294fe8c1c5016aeb6e82318c", size = 384880, upload-time = "2026-04-24T21:28:25.864Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5b/82dc44b5074a1ff62e702d12611272d1711a60d5518dab23f94e1f7a9b3d/pyroaring-1.1.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f1f56004e8f1c1489bf279c25f1fa4764252cd9af5fb35675774268a4a615ba", size = 1999529, upload-time = "2026-04-24T21:28:26.859Z" }, + { url = "https://files.pythonhosted.org/packages/11/40/b07bac8cdc4b709a05f5c55bb52d4f684e5ea1fadfa0b6d9decf477a9d2a/pyroaring-1.1.0-cp313-cp313-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:13660386ea8905ee4d42c21a6275463e2dc7d31e0b5d65eec210aa7043ad96f4", size = 1842927, upload-time = "2026-04-24T21:28:28.056Z" }, + { url = "https://files.pythonhosted.org/packages/0d/60/c4b511965802dfc77978a9e16f2813f47fb3083db1822019ba1bb169c685/pyroaring-1.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0dfb6cf50fd8898179e460e699a6b8326ca508c627d083f7bf62f769fe1717d5", size = 2199538, upload-time = "2026-04-24T21:28:29.425Z" }, + { url = "https://files.pythonhosted.org/packages/e8/12/38f6b50b3f3f41a8b752d3e9efcf105b18eb2c66811831059f25613734ac/pyroaring-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81ebbc0c880c8a10f13118632e5c0d59159ceada8b651bba18f2e6dc70efdeda", size = 2896904, upload-time = "2026-04-24T21:28:30.67Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b6/b5436e4b93c6bf2bd3dd6ccb88cbdc64b12084151a43e2f5c94be50eb710/pyroaring-1.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:370d191b0d1b32bbd99452ef5f0485f22fcc4bf7404d33b821d0ce2459951152", size = 2733819, upload-time = "2026-04-24T21:28:31.882Z" }, + { url = "https://files.pythonhosted.org/packages/ab/8f/f392f268de9607a5c7a95aaed6b9c8a81f00c14d85c33855e9f492095478/pyroaring-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8b3bfad0ae3ef0e67b40c193863dce8b7d79de545dadbe53c19acc3ace38f66", size = 3161730, upload-time = "2026-04-24T21:28:33.244Z" }, + { url = "https://files.pythonhosted.org/packages/9e/a1/03250fd4834b6a5c13e6600bca47ea20fda579f80bce3551d4985185d164/pyroaring-1.1.0-cp313-cp313-win32.whl", hash = "sha256:eead129046822cb0fd47c78740b81bdaffd0515c0bb0306a2318acf0f0540b58", size = 211194, upload-time = "2026-04-24T21:28:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/70/63/d9b307462cddc82fe94a67d6810e5c802818690e131ba690c1de674d8558/pyroaring-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:90ab2f00c09eed5bd986a80c8641e2dc10e7aca1a2d892d89a44b396e39c08ea", size = 263110, upload-time = "2026-04-24T21:28:35.976Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4a/aa6e9833a6ba9a630efdbec8783b63da6602f763b37a5b5fbc01d73a1af1/pyroaring-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:51dd2490a64ad4ed53c4fb58ef1ee3f84f6cbd97cdb47abd9065c9f714ab72ef", size = 216546, upload-time = "2026-04-24T21:28:37.065Z" }, + { url = "https://files.pythonhosted.org/packages/93/ab/2260fd567a2d5d957393b932ea940dc31146bd509c88164c1b786eee7836/pyroaring-1.1.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:5e337f8c5b3c2e0c27da83fc2cb702684a47eee907a960cfee964fcb5344515b", size = 335093, upload-time = "2026-04-24T21:28:38.325Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/df82a832ff3760c7c7653b80d030fb43b18eb88bfa604e7de3e84457286e/pyroaring-1.1.0-cp314-cp314-macosx_14_0_universal2.whl", hash = "sha256:53acecba8f898e96b84d4139356e30719c70358177e270055901d3ec1cb0e34c", size = 712387, upload-time = "2026-04-24T21:28:39.404Z" }, + { url = "https://files.pythonhosted.org/packages/12/b9/a94d6b2d7a1be2fa5009ecfc345bacb2ee0b536020aeb23e92c6bb7e70f2/pyroaring-1.1.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:986efb3aec7655d69c14db2309a2072dbf181bdb906091fede83ad18e316cdaf", size = 385413, upload-time = "2026-04-24T21:28:40.563Z" }, + { url = "https://files.pythonhosted.org/packages/60/6a/3658eadbe28a5a2093c27857dd21441f1ea1cede2ddbe367df76e3018859/pyroaring-1.1.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92643c9dd303de8960c3dbed93a28b8d87da5ed0a7776568979f379d7bc8a885", size = 1995135, upload-time = "2026-04-24T21:28:41.931Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/4706ec770790d3433520eb0ea98fc662ccb1533164fd00b01f3413c3425c/pyroaring-1.1.0-cp314-cp314-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6a1d4c59d5b23c01d62f86d57ceefd0c0977de0425aafa7069f2d70563fed3b8", size = 1833652, upload-time = "2026-04-24T21:28:43.381Z" }, + { url = "https://files.pythonhosted.org/packages/2e/38/b8b861738e49fd4c4a54bebe257dced603999365629b4e10cb85fac940b0/pyroaring-1.1.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8ac1bc26223befbca986551521f37f4c1670dfe26fccb2f0fc2775e75be99c1", size = 2188218, upload-time = "2026-04-24T21:28:44.487Z" }, + { url = "https://files.pythonhosted.org/packages/01/8c/96afa9b5f509a5c607deaf30538edb3bdf026447a864cfe3f2c3d7484875/pyroaring-1.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b490f2d22df30affbfdcbe4f7896f321edb72a8dc0cbe5f38adec3de5b947c25", size = 2898243, upload-time = "2026-04-24T21:28:45.9Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1e/86f8720525250fe742fc77ea5c2a2074a1ea830efe84a79112ce6fe113d3/pyroaring-1.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:56a67794188275f8897a8f1fa64d6313c48241bebbdef38833063e7281b29ef8", size = 2715091, upload-time = "2026-04-24T21:28:47.721Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/29e8f58c8af2ce016904a7e6aa61be675945224971cd70f3f698e584a23f/pyroaring-1.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d9f196007f0b15ea19c21732faacaea83cbf5946b6db4949b3b98cf871c93f0", size = 3149470, upload-time = "2026-04-24T21:28:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/ab/e1/f67ef1c9de461a80707a2f2981320b1b30632720bac426a9bfd51e4744b6/pyroaring-1.1.0-cp314-cp314-win32.whl", hash = "sha256:abc0f0ce22464864fea208315d25e999e45cb5ee646ac1ca11d314a6a51dbe4a", size = 216552, upload-time = "2026-04-24T21:28:50.652Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/a8d9fee17e6eedf2a2281b2aabcdde86930408486381ec48d1f7d3404521/pyroaring-1.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:532ae6bb1d3431d9956ef07589dd5c8dd918301a83d937c7dc6e511b1364d76a", size = 270712, upload-time = "2026-04-24T21:28:51.817Z" }, + { url = "https://files.pythonhosted.org/packages/42/50/ab2bf3fe45e4c2952690d657321a8470558f92cf93cb197fe5d31f7110d2/pyroaring-1.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:d2706a89242a347be20805147d58a38f4f4d8f6846228c4ee8dfd3587113719c", size = 224783, upload-time = "2026-04-24T21:28:53.183Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0c/9eb48ac698280170f184045814b7bd44829af37c1c6de79a4d7b5ea0c8b8/pyroaring-1.1.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:39eff7dd06c163c22d0a9f9fd72d27e671457bea8cdb71215382a10512539e1d", size = 345689, upload-time = "2026-04-24T21:28:54.494Z" }, + { url = "https://files.pythonhosted.org/packages/f6/61/66b18f8ed17e70f88a410dcfac21e5964c2ad01bd4d6a25024a87522c8a9/pyroaring-1.1.0-cp314-cp314t-macosx_14_0_universal2.whl", hash = "sha256:562fa04bbfd41144d1276ed79505007557c161371450d68a1d71fc83dc01d083", size = 732373, upload-time = "2026-04-24T21:28:55.525Z" }, + { url = "https://files.pythonhosted.org/packages/04/7a/976482874ea5e4476f9dd84e7d0274e480446b1b6ab45dfe301281814b3b/pyroaring-1.1.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:591e2ed4d60443dafd9075c1f72e9aaf359ccf5120e32a8c340c2b2ae3da45e7", size = 392985, upload-time = "2026-04-24T21:28:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/ea/22/1eed09ff3aa792865dd52ef447cbe52dbc5901ed88bf1cf4513f7220150e/pyroaring-1.1.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:381eda673442c389993f8b0db2dbf5d02ea8ea9aac6ba736f64cc1ffb6c96885", size = 2045479, upload-time = "2026-04-24T21:28:58.008Z" }, + { url = "https://files.pythonhosted.org/packages/7d/47/de43464f9b28c445868e4bc8f0e6c6dcd51103bd9a757e3dcd9af25a4a69/pyroaring-1.1.0-cp314-cp314t-manylinux_2_24_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9127feb5356ba3a92bdffa04c1bf6bcbc8d436369f78badf441018c3029dd63", size = 1853013, upload-time = "2026-04-24T21:28:59.265Z" }, + { url = "https://files.pythonhosted.org/packages/03/17/29b128a580ec43905fb766b934e7dcb1095059e99e38e941edf50152207b/pyroaring-1.1.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:650db21c10f42ff2b09ef02c10a779a3d59d0c7512552f3844738b30adbcb8a5", size = 2222628, upload-time = "2026-04-24T21:29:00.792Z" }, + { url = "https://files.pythonhosted.org/packages/bd/d8/bb7d69978a5fcac95da48bedb114554d8345b50b77f042e8cd2a8277bb4b/pyroaring-1.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a5fbcb86e44f1c0c9c052917eee67a04cbac9de7392fb4bc77c140ff4a7e471", size = 2931231, upload-time = "2026-04-24T21:29:02.275Z" }, + { url = "https://files.pythonhosted.org/packages/0b/12/d44d144352a4586544313a97b2576f8f8673b98c02ae7fed77d38751cc1f/pyroaring-1.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0f1d76ef29034017eb2cceebd5fa0504d6ced218ce6432f99da5adecbe038269", size = 2729546, upload-time = "2026-04-24T21:29:03.414Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f9/0d01c6ba01c0d01609fddb1d46138a7ae95b7db386bac4afb0ff082d5c0e/pyroaring-1.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:19d0c81c865d63791fe20e5b38733b66f4f962e677ae7e8b3d3c4947ac6e752f", size = 3177123, upload-time = "2026-04-24T21:29:04.619Z" }, + { url = "https://files.pythonhosted.org/packages/43/6d/f991526fdef3cf7739f6db0cdf12b157e840e0ddd4a7e1c2a477da9072d6/pyroaring-1.1.0-cp314-cp314t-win32.whl", hash = "sha256:1fc112b9a9890f89cc645a16604783ed7fa25299f149b0ef7b45a5e2e3c1f31f", size = 241484, upload-time = "2026-04-24T21:29:06.114Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6e/fb9876940acb50df355a473c087b9924e7b3368070403683941653b6fabc/pyroaring-1.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d92a0f4c7e6bb7deeafac68c79c92ef9340895fe825cf1a31078443753ab6756", size = 304537, upload-time = "2026-04-24T21:29:07.312Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e0/39afe4bddbed6276c54e35e310aa345fbeb00f8890e96e7f48cdc2be9c66/pyroaring-1.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99c42fe1449acfbf130da65e66b4d5b2726aba4497be359bae7672e38a15fc62", size = 234615, upload-time = "2026-04-24T21:29:08.751Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[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, upload-time = "2024-03-01T18:36:20.211Z" } +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, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, +] + +[[package]] +name = "pytz" +version = "2026.3.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/48/fb042503b6ca6cd271261dc559fd6432f7d8c713153e9ec5c591af4dfc1c/pytz-2026.3.post1.tar.gz", hash = "sha256:2211d3fcf9a797d3405cac96ac7f61d80e6a644f72a3309607282fe8a2010c5d", size = 319745, upload-time = "2026-07-25T15:12:07.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/7b/39c34ca613b0b198cb866466651b26b045e2009864c5183c979a3b83f383/pytz-2026.3.post1-py2.py3-none-any.whl", hash = "sha256:dd95840dd199baea12d9cc096a1d452caa6596a1c1e4b5f3dbd1541855d5e815", size = 508283, upload-time = "2026-07-25T15:12:05.782Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "regex" +version = "2026.7.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/13/bbf7d9d1887fe4a3693527c6caa232c197ea9da91f1212e9672eff60329d/regex-2026.7.19-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:555497390743af1a65045fa4527782d10ff5b88970359412baa4a1e628fe393b", size = 494009, upload-time = "2026-07-19T00:16:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/a3/19/783688e75a2bec15d50aec0d5e7e317d363808bc82a6eb6750b897bfcd7b/regex-2026.7.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:343a4504e3fb688c47cad451221ca5d4814f42b1e16c0065bde9cbf7f473bd52", size = 295287, upload-time = "2026-07-19T00:16:15.702Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/cefe4f051302ca298d3f3e79ed6dbd933ac84485b9515acdd6a52d70cef7/regex-2026.7.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ebee1ee89c39c953baac6924fcde08c5bb427c4057510862f9d7c7bdb3d8665", size = 290633, upload-time = "2026-07-19T00:16:17.182Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1e/1045ca2cabb12e8ec41ad0d138e9f3ff1eb079d30a6f51ccf7d709b44aad/regex-2026.7.19-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:062f8cb7a9739c4835d22bd96f370c59aba89f257adcfa53be3cc209e08d3ae0", size = 785300, upload-time = "2026-07-19T00:16:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/20e5bca184e90bf1bd187efdb53363f4a7b7b34f01d54ced5740caf104bd/regex-2026.7.19-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1123ef4211d763ee771d47916a1596e2f4915794f7aabdc1adcb20e4249a6951", size = 854079, upload-time = "2026-07-19T00:16:19.909Z" }, + { url = "https://files.pythonhosted.org/packages/09/9b/5a2e59678be3b24aa6a42b2c6d66a48daa212593e9f4096fe7ba577fa9b1/regex-2026.7.19-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e44c0e7c5664be20aee92085153150c0a7967310a73a43c0f832b7cd35d0dd3", size = 899496, upload-time = "2026-07-19T00:16:21.453Z" }, + { url = "https://files.pythonhosted.org/packages/48/9a/7317f14ed8ed9fd998d1978b4802b07bc4d79216353c435dbcc1ddd1301f/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98c6ac18480fcdb33f35439183f1d2e79760ab41930309c6d951cb1f8e46694c", size = 793541, upload-time = "2026-07-19T00:16:22.991Z" }, + { url = "https://files.pythonhosted.org/packages/6f/53/833c2db3e274d3c191f4c42fe5bfa358e4c8b617d5d7312d31334965fc46/regex-2026.7.19-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4458124d71339f505bf1fb94f69fd1bb8fa9d2481eebfef27c10ef4f2b9e12f6", size = 785515, upload-time = "2026-07-19T00:16:24.654Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b9/efb2f9fa151d71db09d4015e1fb92fee47416f01c12164836bbd23e2f3c2/regex-2026.7.19-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbf300e2070bb35038660b3be1be4b91b0024edb41517e6996320b49b92b4175", size = 769556, upload-time = "2026-07-19T00:16:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/4d/45610c263f8eadb84e4a1fabd904d81d5176226faa9104ef498bf8a8b285/regex-2026.7.19-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b2b506b1788df5fecd270a10d5e70a95fe77b87ea2b370a318043f6f5f817ee6", size = 774130, upload-time = "2026-07-19T00:16:27.786Z" }, + { url = "https://files.pythonhosted.org/packages/40/95/1b40d87c7a9e5480bec7a87bce9fd67fc3f14b5f106c8ee66d660249072f/regex-2026.7.19-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:52579c60a6078be70a0e49c81d6e56d677f34cd439af281a0083b8c7bc75c095", size = 848694, upload-time = "2026-07-19T00:16:29.412Z" }, + { url = "https://files.pythonhosted.org/packages/17/8b/bb45968addd5b394ef9cd9184bd9c65ade1a819dbb2b92b71ad52a0c7907/regex-2026.7.19-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2955907b7157a6660f27079edf7e0229e9c9c5325c77a2ef6a890cba91efa6f0", size = 758505, upload-time = "2026-07-19T00:16:31.006Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6f/33386c672fbf43e21602135a0f29a97ee251a483f007fe51d10e9b2dbc93/regex-2026.7.19-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:89dfee3319f5ae3f75ebd5c2445a809bb320252ba5529ffdafea4ef25d79cf1a", size = 836985, upload-time = "2026-07-19T00:16:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/22/f1/9112b86e9bb075619862e8e42b604794389f1958faa68fb69bde505dd90e/regex-2026.7.19-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d3143f159261b1ce5b24c261c590e5913370c3200c5e9ebbb92b5aa5e111902", size = 782610, upload-time = "2026-07-19T00:16:33.857Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f9/13d460d8a385ca0b0be9e6be80a90968b9293b3e30895543ad2d1d1653e4/regex-2026.7.19-cp310-cp310-win32.whl", hash = "sha256:64729333167c2dcaaa56a331d40ee097bd9c5617ffd51dabb09eaddafb1b532e", size = 266772, upload-time = "2026-07-19T00:16:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/9f/90/29addd7a03e1aea402c1f31467e25c80caabc8c3735b88a23cf73b0aa9c2/regex-2026.7.19-cp310-cp310-win_amd64.whl", hash = "sha256:1c398716054621aa300b3d411f467dda903806c5da0df6945ab73982b8d115db", size = 277967, upload-time = "2026-07-19T00:16:36.847Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ba/ecfce06fe66c122bc6f77ae284887a9282e4411fe1e6268c5266611ca054/regex-2026.7.19-cp310-cp310-win_arm64.whl", hash = "sha256:064f1760a5a4ade65c5419be23e782f29147528e8a66e0c42dd4cedb8d4e9fc6", size = 276963, upload-time = "2026-07-19T00:16:38.315Z" }, + { url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012, upload-time = "2026-07-19T00:16:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281, upload-time = "2026-07-19T00:16:41.433Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615, upload-time = "2026-07-19T00:16:43.058Z" }, + { url = "https://files.pythonhosted.org/packages/cf/69/b65ba4344efbc771b28fe5dde84cbbb6c8f9551165952fe78def5b9dde6a/regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20", size = 791804, upload-time = "2026-07-19T00:16:44.662Z" }, + { url = "https://files.pythonhosted.org/packages/81/b6/a40dfa0dc6224b36f620c00296eacc830489cbf8c2837b6750dfe6170375/regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a", size = 861723, upload-time = "2026-07-19T00:16:46.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/02/735991dee71abd83196a7962f7ed8bf5aa05720ff06e2d3ff896a85e2bbb/regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7", size = 905932, upload-time = "2026-07-19T00:16:47.956Z" }, + { url = "https://files.pythonhosted.org/packages/45/6c/e7098d8b846ccdbf431d8c081b61e496526a27a28094ed09e0dce21b3f54/regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc", size = 801407, upload-time = "2026-07-19T00:16:49.43Z" }, + { url = "https://files.pythonhosted.org/packages/8a/18/34b69274e2649bcc7d9b089c2b2983fb2632d8ecf667e359593be9072e79/regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864", size = 774448, upload-time = "2026-07-19T00:16:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e6/0a72247d025585fd3800b98e040b84d562a88af6303347100484849f4f01/regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af", size = 783297, upload-time = "2026-07-19T00:16:53.071Z" }, + { url = "https://files.pythonhosted.org/packages/b1/aa/c4f65ae7dd02a36b323a70c4cff326e1f3442361aaebc9311100a130d54f/regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5", size = 854736, upload-time = "2026-07-19T00:16:54.607Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/668082bcc817b9e694189b84997aeba7385b7779faa6711788679c482e35/regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82", size = 763298, upload-time = "2026-07-19T00:16:56.289Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fb/2d07ad555e7af88aa5f867fdafa47a8d945ee237c20af3ebceb46a820835/regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78", size = 844430, upload-time = "2026-07-19T00:16:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/51/15/c82a471fe3dce56f03745635b43aa456c40dc0db089e07ef148b331507d1/regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327", size = 789683, upload-time = "2026-07-19T00:16:59.583Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f4/7532a2c59d56f5398902c20de60f0c9a5d1cd364e42a051b48e1b210be7b/regex-2026.7.19-cp311-cp311-win32.whl", hash = "sha256:9be2a6647740dd3cca6acb24e87f03d7632cd280dbce9bbe40c26353a215a45d", size = 266778, upload-time = "2026-07-19T00:17:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/83/2b/cf1bc631db154eb95520d9d5dbc2371ff77a0f014bbf7d748fed8496aa63/regex-2026.7.19-cp311-cp311-win_amd64.whl", hash = "sha256:8d3469c91dd92ee41b7c95280edbd975ef1ba9195086686623a1c6e8935ce965", size = 277983, upload-time = "2026-07-19T00:17:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/8d/bd/56ceaf170e875d5a6761bf2bfd0d040f1cacc896850d5e40cb29b11bbd06/regex-2026.7.19-cp311-cp311-win_arm64.whl", hash = "sha256:36aacfb15faaff3ced55afbf35ec72f50d4aee22082c4f7fe0573a33e2fca92e", size = 276961, upload-time = "2026-07-19T00:17:04.135Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778, upload-time = "2026-07-19T00:17:05.677Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122, upload-time = "2026-07-19T00:17:07.59Z" }, + { url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009, upload-time = "2026-07-19T00:17:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/22/85/102a81b218298957d4ea7d2f084fae537a71add9d6ff93c8e67284c5f45e/regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797", size = 796708, upload-time = "2026-07-19T00:17:11.542Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/dc136af5629938a037cd2b304c12240e132ec92f38be8ff9cc89af2a1f2d/regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18", size = 865651, upload-time = "2026-07-19T00:17:13.312Z" }, + { url = "https://files.pythonhosted.org/packages/e0/75/67402ae3cd9c8c988a4c805d15ee3eef015e7ca4cb112cf3e640fc1f4153/regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511", size = 911756, upload-time = "2026-07-19T00:17:15.015Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8e/096d00c7c480ef2ff4265349b14e2261d4ab787ba1f74e2e80d1c58079c3/regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68", size = 801798, upload-time = "2026-07-19T00:17:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/41/e7ecac6edb5722417f85cc67eaf386322fbe8acf6918ec2fdc37c20dd9d0/regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11", size = 776933, upload-time = "2026-07-19T00:17:19.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/69/03c9b3f058d66403e0ca2c938696e81d51cd4c6d47ec5265f02f96948d9a/regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986", size = 784338, upload-time = "2026-07-19T00:17:21.057Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f7/b38ab3d43f284afbb618fcd15d0e77eb786ae461ce1f6bc7494619ddc0f2/regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b", size = 860452, upload-time = "2026-07-19T00:17:23.119Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/ff60ef0571121714f3cf9920bc183071e384a10b556d042e0fdb06cc07a5/regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb", size = 765958, upload-time = "2026-07-19T00:17:24.81Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0f/bd34021162c0ab47f9a315bd56cd5642e920c8e5668a75ef6c6a6fca590d/regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035", size = 851765, upload-time = "2026-07-19T00:17:26.993Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/a2ca43edade0595cccfdc98636739f536d9e26898e7dbddc2b9e98898953/regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a", size = 789714, upload-time = "2026-07-19T00:17:28.699Z" }, + { url = "https://files.pythonhosted.org/packages/5d/47/e02db4015d424fc83c00ea0ac8c5e5ec14397943de9abf909d5ce3a25931/regex-2026.7.19-cp312-cp312-win32.whl", hash = "sha256:ddd67571c10869f65a5d7dde536d1e066e306cc90de57d7de4d5f34802428bb5", size = 267157, upload-time = "2026-07-19T00:17:31.051Z" }, + { url = "https://files.pythonhosted.org/packages/08/8e/c780c131f79b42ed22d1bd7da4096c2c35f813e835acd02ef0f018bd892c/regex-2026.7.19-cp312-cp312-win_amd64.whl", hash = "sha256:e30d40268a28d54ce0437031750497004c22602b8e3ab891f759b795a003b312", size = 277777, upload-time = "2026-07-19T00:17:32.848Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4c/e4d7e086449bdf379d89774bf1f89dc4a41943f3c5a6125a03905b34b5fb/regex-2026.7.19-cp312-cp312-win_arm64.whl", hash = "sha256:de9208bb427130c82a5dbfd104f92c8876fc9559278c880b3002755bbbe9c83d", size = 277136, upload-time = "2026-07-19T00:17:34.803Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3d/84165e4299ff76f3a40fe1f2abf939e976f693383a08d2beea6af62bd2c1/regex-2026.7.19-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f035d9dc1d25eff9d361456572231c7d27b5ccd473ca7dc0adfce732bd006d40", size = 496552, upload-time = "2026-07-19T00:17:36.808Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/a65293e6e4cf28eb7ee1be5335a5386c40d6742e9f47fafc8fec785e16c7/regex-2026.7.19-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42572142ed0b9d5d261ba727157c426510da78e20828b66bbb855098b8a4e38", size = 296983, upload-time = "2026-07-19T00:17:38.816Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/2d0564e93d87bc48618360ddca232a2ca612bbdf53ce8465d45ca5ce14ee/regex-2026.7.19-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40b34dd88658e4fedd2fddbf0275ac970d00614b731357f425722a3ed1983d11", size = 291832, upload-time = "2026-07-19T00:17:40.726Z" }, + { url = "https://files.pythonhosted.org/packages/07/cd/42dfbabff3dfc9603c501c0e2e2c5adbb09d127b267bf5348de0af338c15/regex-2026.7.19-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c41c63992bf1874cebb6e7f56fd7d3c007924659a604ae3d90e427d40d4fd13", size = 796775, upload-time = "2026-07-19T00:17:42.382Z" }, + { url = "https://files.pythonhosted.org/packages/df/5d/f6a4839f2b934e3eed5973fd07f5929ee97d4c98939fb275ea23c274ee16/regex-2026.7.19-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d3372064506b94dd2c67c845f2db8062e9e9ba84d04e33cb96d7d33c11fe1ae", size = 865687, upload-time = "2026-07-19T00:17:44.185Z" }, + { url = "https://files.pythonhosted.org/packages/14/b0/b47d6c36049bc59806a50bd4c86ced70bbe058d787f80281b1d7a9b0e024/regex-2026.7.19-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fce7760bf283405b2c7999cab3da4e72f7deca6396013115e3f7a955db9760da", size = 911962, upload-time = "2026-07-19T00:17:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/ff61f28f9273658cfe23acbbac5217221f6519960ed401e61dfdab12bc35/regex-2026.7.19-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0d702548d89d572b2929879bc883bb7a4c4709efafe4512cadee56c55c9bd15", size = 801817, upload-time = "2026-07-19T00:17:48.25Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bb/8b4f7f26b333f9f79e1b453613c39bb4776f51d38ae66dd0ba31d6b354ca/regex-2026.7.19-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d446c6ac40bb6e05025ccee55b84d80fe9bf8e93010ffc4bb9484f13d498835f", size = 776908, upload-time = "2026-07-19T00:17:50.183Z" }, + { url = "https://files.pythonhosted.org/packages/09/13/610110fc5921d380516d03c26b652555f08aa0d23ea78a771231873c3638/regex-2026.7.19-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c3501bfa814ab07b5580741f9bf78dfdfe146a04057f82df9e2402d2a975939", size = 784426, upload-time = "2026-07-19T00:17:52.454Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f5/1ef9e2a83a5947c57ebff0b377cb5727c3d5ec1992317a320d035cd0dbb6/regex-2026.7.19-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c4585c3e64b4f9e583b4d2683f18f5d5d872b3d71dcf24594b74ecc23602fa96", size = 860600, upload-time = "2026-07-19T00:17:54.229Z" }, + { url = "https://files.pythonhosted.org/packages/a0/02/073af33a3ec149241d11c80acea91e722aa0adbf05addd50f251c4fe89c3/regex-2026.7.19-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:571fde9741eb0ccde23dd4e0c1d50fbae910e901fa7e629faf39b2dda740d220", size = 765950, upload-time = "2026-07-19T00:17:56.041Z" }, + { url = "https://files.pythonhosted.org/packages/81/a9/d1e9f819dc394a568ef370cd56cf25394e957a2235f8370f23b576e5a475/regex-2026.7.19-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:15b364b9b98d6d2fe1a85034c23a3180ff913f46caddc3895f6fd65186255ccc", size = 851794, upload-time = "2026-07-19T00:17:57.897Z" }, + { url = "https://files.pythonhosted.org/packages/03/3a/8ae83eda7579feacdf984e71fb9e70635fb6f832eeddca58427ec4fca926/regex-2026.7.19-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffd8893ccc1c2fce6e0d6ca402d716fe1b29db70c7132609a05955e31b2aa8f2", size = 789845, upload-time = "2026-07-19T00:17:59.97Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/c195cbfe5a75fdec64d8f6554fd15237b837919d2c61bdc141d7c807b08b/regex-2026.7.19-cp313-cp313-win32.whl", hash = "sha256:f0fa4fa9c3632d708742baf2282f2055c11d888a790362670a403cbf48a2c404", size = 267135, upload-time = "2026-07-19T00:18:01.958Z" }, + { url = "https://files.pythonhosted.org/packages/b2/80/a11de8404b7272b70acb45c1c05987cce60b45d5693da2e176f0e390d564/regex-2026.7.19-cp313-cp313-win_amd64.whl", hash = "sha256:d51ffd3427640fa2da6ade574ceba932f210ad095f65fcc450a2b0a0d454868e", size = 277747, upload-time = "2026-07-19T00:18:04.121Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/0f5c8eff1b4f1f3d83276d365fccecf666afcc7d947420943bf394d07adb/regex-2026.7.19-cp313-cp313-win_arm64.whl", hash = "sha256:c670fe7be5b6020b76bc6e8d2196074657e1327595bca93a389e1a76ab130ad8", size = 277129, upload-time = "2026-07-19T00:18:05.821Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4c/44b74742052cedda40f9ae469532a037112f7311a36669a891fba8984bb0/regex-2026.7.19-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db47b561c9afd884baa1f96f797c9ca369872c4b65912bc691cfa99e68340af2", size = 501134, upload-time = "2026-07-19T00:18:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/f0/45/bbd038b5e39ee5613a5a689290145b40058cc152c41de9cc23639d2b9734/regex-2026.7.19-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65dcd28d3eba2ab7c2fd906485cc301392b47cc2234790d27d4e4814e02cdfda", size = 299418, upload-time = "2026-07-19T00:18:09.38Z" }, + { url = "https://files.pythonhosted.org/packages/65/38/c5bde94b4cedfd5850d64c3f08222d8e1600e84f6ee71d9b44b4b8163f74/regex-2026.7.19-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f2e7f8e2ab6c2922be02c7ec45185aa5bd771e2e57b95455ee343a44d8130dff", size = 294486, upload-time = "2026-07-19T00:18:11.188Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6a/2f5e107cb26c960b781967178899daf2787a7ab151844ed3c01d6fc95474/regex-2026.7.19-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe31f28c94402043161876a258a9c6f757cb485905c7614ce8d6cd40e6b7bdc1", size = 811643, upload-time = "2026-07-19T00:18:12.975Z" }, + { url = "https://files.pythonhosted.org/packages/37/d4/a2f963406d7d73a62eed84ba05a258afb6cad1b21aa4517443ce40506b78/regex-2026.7.19-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8f6fa298bb4f7f58a33334406218ba74716e68feddf5e4e54cd5d8082705abf", size = 871081, upload-time = "2026-07-19T00:18:14.733Z" }, + { url = "https://files.pythonhosted.org/packages/45/a3/44be546340bedb15f13063f5e7fe16793ea4d9ea2e805d09bd174ac27724/regex-2026.7.19-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cc1b2440423a851fad781309dd87843868f4f66a6bcd1ddb9225cf4ec2c84732", size = 917372, upload-time = "2026-07-19T00:18:16.724Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f6/e0870b0fd2a40dba0074e4b76e514b21313d37946c9248453e34ec43923e/regex-2026.7.19-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ac59a0900474a52b7c04af8196affc22bd9842acb0950df12f7b813e983609a", size = 816089, upload-time = "2026-07-19T00:18:18.617Z" }, + { url = "https://files.pythonhosted.org/packages/ae/27/957e8e22690ad6634572b39b71f130a6105f4d0718bb16849eac00fff147/regex-2026.7.19-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4896db1f4ce0576765b8272aa922df324e0f5b9bb2c3d03044ff32a7234a9aba", size = 785206, upload-time = "2026-07-19T00:18:20.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/a4/186e410941e731037c01166069ab86da9f65e8f8110c18009ccf4bd623ee/regex-2026.7.19-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4e6883a021db30511d9fb8cfb0f222ce1f2c369f7d4d8b0448f449a93ba0bdfc", size = 800431, upload-time = "2026-07-19T00:18:22.716Z" }, + { url = "https://files.pythonhosted.org/packages/73/9f/e4e10e023d291d64a33e246610b724493bf1ce98e0e59c9b7c837e5acfb7/regex-2026.7.19-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:09523a592938aa9f587fb74467c63ff0cf88fc3df14c82ab0f0517dcf76aaa62", size = 864906, upload-time = "2026-07-19T00:18:24.772Z" }, + { url = "https://files.pythonhosted.org/packages/24/57/ccb20b6be5f1f52a053d1ba2a8f7a077edb9d918248b8490d7506c6832b3/regex-2026.7.19-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1ebac3474b8589fce2f9b225b650afd61448f7c73a5d0255a10cc6366471aed1", size = 773559, upload-time = "2026-07-19T00:18:27.008Z" }, + { url = "https://files.pythonhosted.org/packages/a3/82/f3b263cf8fad927dc102891da8502e718b7ff9d19af7a2a07c03865d7188/regex-2026.7.19-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4a0530bb1b8c1c985e7e2122e2b4d3aedd8a3c21c6bfddae6767c4405668b56e", size = 857739, upload-time = "2026-07-19T00:18:29.107Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/1687bd1b6c2aed5e672ccf845fc11557821fe7366d921b50889ea5ce57bf/regex-2026.7.19-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef7eeb108c47ce7bcc9513e51bcb1bf57e8f483d52fce68a8642e3527141ae0", size = 804522, upload-time = "2026-07-19T00:18:31.362Z" }, + { url = "https://files.pythonhosted.org/packages/76/7c/cc4e7655181b2d9235b704f2c5e19d8eff002bbc437bae59baee0e381aca/regex-2026.7.19-cp313-cp313t-win32.whl", hash = "sha256:64b6ca7391a1395c2638dd5c7456d67bea44fc6c5e8e92c5dc8aa6a8f23292b4", size = 269141, upload-time = "2026-07-19T00:18:33.479Z" }, + { url = "https://files.pythonhosted.org/packages/bb/14/961b4c7b05a2391c32dbc85e27773076671ef8f97f36cec70fe414734c02/regex-2026.7.19-cp313-cp313t-win_amd64.whl", hash = "sha256:f04b9f56b0e0614c0126be12c2c2d9f8850c1e57af302bd0a63bed379d4af974", size = 280036, upload-time = "2026-07-19T00:18:35.419Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/795644550d788ddbb6dc458c95895f8009978ea6d6ea76b005eb3f45e8c9/regex-2026.7.19-cp313-cp313t-win_arm64.whl", hash = "sha256:fcee38cd8e5089d6d4f048ba1233b3ad76e5954f545382180889112ff5cb712d", size = 279394, upload-time = "2026-07-19T00:18:37.454Z" }, + { url = "https://files.pythonhosted.org/packages/d2/25/0c4c452f8ef3efe456745b2f33195f5904b573fb4c2ff3f0cb9ec188461e/regex-2026.7.19-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a81758ed242b861b72e778ba34d41366441a2e10b16b472784c88da2dea7e2dd", size = 496750, upload-time = "2026-07-19T00:18:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/24/9e/b70ca6c1704f6c7cd32a9e143c86cc5968d10981eca284bad670c245ea7d/regex-2026.7.19-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4aa5435cdb3eb6f55fe98a171b05e3fbcd95fadaa4aa32acf62afd9b0cfdbcac", size = 297093, upload-time = "2026-07-19T00:18:41.583Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/0b692da2520d51fbff19c88b83d97e4c702909dd02386c585998b7e2dbed/regex-2026.7.19-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:60be8693a1dadc210bbcbc0db3e26da5f7d01d1d5a3da594e99b4fa42df404f5", size = 292043, upload-time = "2026-07-19T00:18:43.347Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a7/1d478e614016045a33feae57446215f9fd65b665a5ceb2f891fb3183bc52/regex-2026.7.19-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d19662dbedbe783d323196312d38f5ba53cf56296378252171985da6899887d3", size = 797214, upload-time = "2026-07-19T00:18:45.362Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ae/11b9c9411d92c30e3d2db32df5a31133e4a99a8fc397a604fd08f6c4bffb/regex-2026.7.19-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d15df07081d91b76ff20d43f94592ee110330152d617b730fdbe5ef9fb680053", size = 866433, upload-time = "2026-07-19T00:18:47.315Z" }, + { url = "https://files.pythonhosted.org/packages/b1/62/2b2efc4992f91d6d204b24c647c9f9412e85379d92b7c0ab9fdae622327e/regex-2026.7.19-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:56ad4d9f77df871a99e25c37091052a02528ec0eb059de928ee33956b854b45b", size = 911360, upload-time = "2026-07-19T00:18:49.588Z" }, + { url = "https://files.pythonhosted.org/packages/14/71/986ceea9aa3da548bf1357cad89b63915ec6d21ec957c8113b29ece567df/regex-2026.7.19-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7322ec6cc9fba9d49ab888bb82d67ac5625627aa168f0165139b17018df3fb8a", size = 801275, upload-time = "2026-07-19T00:18:51.767Z" }, + { url = "https://files.pythonhosted.org/packages/15/be/ce9d9534b2cda96eab32c548261224b9b4e220a4126f098f60f42ae7b4cd/regex-2026.7.19-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9c7472192ebfad53a6be7c4a8bfb2d64b81c0e93a1fc8c57e1dd0b638297b5d1", size = 777131, upload-time = "2026-07-19T00:18:54.053Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/58b5c710f2c3929515a25f3a1ca0dad0dcd4518d4fff3cf23bc7adb8dcd2/regex-2026.7.19-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c10b82c2634df08dfb13b1f04e38fe310d086ee092f4f69c0c8da234251e556e", size = 785020, upload-time = "2026-07-19T00:18:56.579Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/5fe091935b74f15fe0f97998c215cae418d1c0413f6258c7d4d2e83aa37f/regex-2026.7.19-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:17ed5692f6acc4183e98331101a5f9e4f64d72fe58b753da4d444a2c77d05b12", size = 861263, upload-time = "2026-07-19T00:18:58.64Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fa/d60bf82e10841eef62a9e32aac401468f05fddfbcb2942e342b1ba3d2433/regex-2026.7.19-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:22a992de9a0d91bda927bf02b94351d737a0302905432c88a53de7c4b9ce62e2", size = 766199, upload-time = "2026-07-19T00:19:00.705Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5d/11e64d151b0662b81d6bf644c74dc118d461df85bdf2577fadbbf751788a/regex-2026.7.19-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:618a0aed532be87294c4477b0481f3aa0f1520f4014a4374dd4cf789b4cd2c97", size = 851317, upload-time = "2026-07-19T00:19:03.015Z" }, + { url = "https://files.pythonhosted.org/packages/7c/34/532efb87488d90807bae6a443d357ee5e2728a478c597619c8aaa17cc0bd/regex-2026.7.19-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2ce9e679f776649746729b6c86382da519ef649c8e34cc41df0d2e5e0f6c36d4", size = 789557, upload-time = "2026-07-19T00:19:05.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/90/3a8d5ca977171ec3ae21a71207d2228b2663bde14d7f7ef0e6363ecf9290/regex-2026.7.19-cp314-cp314-win32.whl", hash = "sha256:73f272fba87b8ccfe70a137d02a54af386f6d27aa509fbffdd978f5947aae1aa", size = 272531, upload-time = "2026-07-19T00:19:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/96/e1/8862885e70409de70e8c005f57fb2e7be8d9ef0317250d60f4c9660a300d/regex-2026.7.19-cp314-cp314-win_amd64.whl", hash = "sha256:d721e53758b2cca74990185eb0671dd466d7a388a1a45d0c6f4c13cef41a68ac", size = 280831, upload-time = "2026-07-19T00:19:09.46Z" }, + { url = "https://files.pythonhosted.org/packages/08/82/2693e53e29f9104d9de95d37ce4dd826bd32d5f9c0085d3aa6ac042675c4/regex-2026.7.19-cp314-cp314-win_arm64.whl", hash = "sha256:65fa6cb38ed5e9c3637e68e544f598b39c3b86b808ed0627a67b68320384b459", size = 281099, upload-time = "2026-07-19T00:19:11.398Z" }, + { url = "https://files.pythonhosted.org/packages/92/b7/9a01aa16461a18cde9d7b9c3ab21e501db2ce33725f53014342b91df2b0a/regex-2026.7.19-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:5a2721c8720e2cb3c209925dfb9200199b4b07361c9e01d321719404b21458b3", size = 501121, upload-time = "2026-07-19T00:19:13.425Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/bbaeca815dc9191c424c94a4fdc5c87c75748a64a6271821212ebdd4e1a3/regex-2026.7.19-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:199535629f25caf89698039af3d1ad5fcae7f933e2112c73f1cdf49165c99518", size = 299415, upload-time = "2026-07-19T00:19:15.43Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d6/0dd1a321afaab95eb7ff44aa0f637301786f1dc71c6b797b9ed236ed8890/regex-2026.7.19-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9b60d7814174f059e5de4ab98271cc5ba9259cfea55273a81544dceea32dc8d9", size = 294483, upload-time = "2026-07-19T00:19:17.879Z" }, + { url = "https://files.pythonhosted.org/packages/92/5f/40bacf91d0904f812e13bbbab3864604c463eced8afdc54aeaa50492ea95/regex-2026.7.19-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dbece16025afda5e3031af0c4059207e61dcf73ef13af844964f57f387d1c435", size = 811833, upload-time = "2026-07-19T00:19:20.102Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/4902744261f775aeede8b5627314b38482da29cf49a57b66a6fb753246c5/regex-2026.7.19-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d24ecb4f5e009ea0bd275ee37ad9953b32005e2e5e60f8bbae16da0dbbf0d3a0", size = 871270, upload-time = "2026-07-19T00:19:22.365Z" }, + { url = "https://files.pythonhosted.org/packages/16/70/6980c9be6bf21c0a60ed3e0aea39cf419ecf3b08d1d9947bc56e196ef186/regex-2026.7.19-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8cae6fd77a5b72dae505084b1a2ee0360139faf72fedbab667cd7cc65aae7a6a", size = 917534, upload-time = "2026-07-19T00:19:24.529Z" }, + { url = "https://files.pythonhosted.org/packages/52/92/8b2bd872782ce8c42691e39acb38eb8efe014e5ddb78ad7d943d6f197ce9/regex-2026.7.19-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9724e6cb5e478cd7d8cabf027826178739cb18cf0e117d0e32814d479fa02276", size = 816135, upload-time = "2026-07-19T00:19:26.919Z" }, + { url = "https://files.pythonhosted.org/packages/de/2d/33a602f657bdc4041f17d79f92ab18261d255d91a06117a6e29df023e5e2/regex-2026.7.19-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:572fc57b0009c735ee56c175ea021b637a15551a312f56734277f923d6fd0f6c", size = 785492, upload-time = "2026-07-19T00:19:29.192Z" }, + { url = "https://files.pythonhosted.org/packages/9e/36/0987cf4cb271680064a70d24a475873775a151d0b7058698a006cb0cae4a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:20568e182eb82d39a6bf7cff3fd58566f14c75c6f74b2c8c96537eecf9010e3a", size = 800658, upload-time = "2026-07-19T00:19:31.392Z" }, + { url = "https://files.pythonhosted.org/packages/a8/24/c14f31c135e1ba55fa4f9a58ca98d0842512bf6188230763c31c8f449e3b/regex-2026.7.19-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1d58561843f0ff7dc78b4c28b5e2dc388f3eff94ebc8a232a3adba961fc00009", size = 865073, upload-time = "2026-07-19T00:19:33.485Z" }, + { url = "https://files.pythonhosted.org/packages/14/85/181a12211f22469f24d2de1ebddfe397d2396e2c29013b9a58134a91069a/regex-2026.7.19-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:61bb1bd45520aacd56dd80943bd34991fb5350afdd1f36f2282230fd5154a218", size = 773684, upload-time = "2026-07-19T00:19:35.599Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/bd1a0c1a62251366f8d21f41b1ea3c76994962071b8b6ea42f72d505c0f0/regex-2026.7.19-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:cd3584591ea4429026cdb931b054342c2bcf189b44ff367f8d5c15bc092a2966", size = 857769, upload-time = "2026-07-19T00:19:37.738Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4f/f7e2dad6756b2fe1fe75dd90a628c3b45f249d39f948dd90cd2476325417/regex-2026.7.19-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5cc26a66e212fa5d6c6170c3a40d99d888db3020c6fdab1523250d4341382e44", size = 804546, upload-time = "2026-07-19T00:19:40.229Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d7/01d31d5bdb09bc026fab77f59a371fdf8f9b292e4810546c56182ca70498/regex-2026.7.19-cp314-cp314t-win32.whl", hash = "sha256:2c4e61e2e1be56f63ec3cc618aa9e0de81ef6f43d177205451840022e24f5b78", size = 274526, upload-time = "2026-07-19T00:19:42.398Z" }, + { url = "https://files.pythonhosted.org/packages/52/0e/cea4ce73bc0a8247a0748228ae6669984c7e1f8134b6fa66e59c0572e0ea/regex-2026.7.19-cp314-cp314t-win_amd64.whl", hash = "sha256:c639ea314df70a7b2811e8020448c75af8c9445f5a60f8a4ced81c306a9380c2", size = 283763, upload-time = "2026-07-19T00:19:44.644Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b6/26e41975febae63b7a6e3e02f32cff6cff2e4f10d19c929082f56aebf7c6/regex-2026.7.19-cp314-cp314t-win_arm64.whl", hash = "sha256:9a15e785f244f3e07847b984ce8773fc3da10a9f3c131cc49a4c5b4d672b4547", size = 283451, upload-time = "2026-07-19T00:19:46.639Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "sacrebleu" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "lxml" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "portalocker" }, + { name = "regex" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/ed/d7acddcff74d690c56fe26a1f7828bdde548262828d0743414ea916c40c1/sacrebleu-2.6.0.tar.gz", hash = "sha256:91499b6cd46138d95154fff1e863c2f9be57e82f0c719d8dd718d0006cf6c566", size = 1893419, upload-time = "2026-01-12T17:17:20.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f2/6c90ccf3ad1d09a7d662a405b274f3c93b92df59c8d6a025d26aaf34d302/sacrebleu-2.6.0-py3-none-any.whl", hash = "sha256:3edc1531575cfe4ad04ce53491a9307e234af1c3f805a1f491cbec844229a8a8", size = 100785, upload-time = "2026-01-12T17:17:18.868Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/33/ea3cb3839607eb175da835244a798f797f478c5ddf0e8ecdf57ea85a4c70/sentencepiece-0.2.2.tar.gz", hash = "sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6", size = 8218435, upload-time = "2026-07-12T08:39:34.701Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/1b/e6c69e4c2026ed575d68dda2847a404468ca7b5fa684bb0b19f71d82d29d/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bc7b0b1da20f856bfac5f84b2673fe534b167e41980b27442ca8f78c2b7eb77e", size = 2180607, upload-time = "2026-07-12T08:38:01.018Z" }, + { url = "https://files.pythonhosted.org/packages/36/5a/2a1d84c87dc075d4f8cf1a2470a95399e59834e219ffb5f4285533e750d0/sentencepiece-0.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8b2db2056c97224e122054fd794543cde5d24b7cae28424f6e3eb79bbe08e42b", size = 1437502, upload-time = "2026-07-12T08:38:02.899Z" }, + { url = "https://files.pythonhosted.org/packages/1b/39/3d43a75dd5a22503ca5074d0d37707cabb2e4a71b4bc6e6c61be3643cc7a/sentencepiece-0.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f1f61592e7cabd45d49ce8cc0ef42ca655c091e037153754fb3fa59725b5914", size = 1345667, upload-time = "2026-07-12T08:38:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/90/d5/a69a8cc896e7de3fe2061b08c2f33e28656f243bed8af6a2df9f5d8c3124/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c798f0b327bac10dc95cdac77b9a197ab2bd7dd1e60ebd7586a12d918d4be711", size = 1322864, upload-time = "2026-07-12T08:38:06.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/79/dd1836df32971d4eb14ff5cb4a8b3fe4419adbeada8e81d09dc53c5c0ef0/sentencepiece-0.2.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44284adc6fbe9d5bdd480541431a3d93f674fa44736714d3ad4bcee8283ace7d", size = 1392757, upload-time = "2026-07-12T08:38:08.559Z" }, + { url = "https://files.pythonhosted.org/packages/26/83/c3547715c29b7e4c84a180a240267f7685dde6f9b981396f16b95405ec9d/sentencepiece-0.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:1120e0791540615e650b2e9bea835bf38a7362455d8ab62dee7968219c2d79a0", size = 1245044, upload-time = "2026-07-12T08:38:10.21Z" }, + { url = "https://files.pythonhosted.org/packages/1f/55/7da03b35582a4eb276f99051109f3e3e8f176835b6d6837422e4c3a013dd/sentencepiece-0.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:524e2a85c028a0d2f9935191fa751e5ef9d9bcc39616f70ab14b28d0369c9936", size = 1190467, upload-time = "2026-07-12T08:38:12.07Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/f23a2efaa0210b883574001b88fa64e499f798f0848a0b610fb9b384d162/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:69e9dc8078e128286ed3b975e37c837ba96e215a50c3ef9f3f8b7ab9e5a832a0", size = 2184255, upload-time = "2026-07-12T08:38:14.855Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/1ee0ccb772d71e822f625d6cb5f0ea825835e877f28a9ef299a1291df19e/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6dd76f3e5c8b2eb8a3a3efee787bbf5b9a66e52a048fe09cab85eca33fec6790", size = 1438545, upload-time = "2026-07-12T08:38:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/2a/92/3a6ea4a2c6dd9e7062698a5a33534ca0e20844883338ae9c6b9c122c1a9f/sentencepiece-0.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:443ac618c7a2a1377cf5c82581fbb849591d14e656d5e5a3e4682d4e36a34e4e", size = 1346997, upload-time = "2026-07-12T08:38:18.499Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3a/7839048997c7bc0c34c57526f539f835e20c7a57dc2a99f99579b11cdbef/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e2aae42960392d6dcb9a72d8e1e65a97294c965071b43c7b3429a42f350250e", size = 1324282, upload-time = "2026-07-12T08:38:20.342Z" }, + { url = "https://files.pythonhosted.org/packages/06/5f/9117bf854aef817ad0d0ee9310eed0308a7e529e7eaf2e80ad9cd281ef82/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1416b92f2f010333786fe6306ed2631121d5ea492219b0841e967b6765e64107", size = 1394242, upload-time = "2026-07-12T08:38:22.976Z" }, + { url = "https://files.pythonhosted.org/packages/ab/62/9e2569867e3dcff7ad6d89642a9615b9801b5cd698abe7df3b490361f66e/sentencepiece-0.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:70d4ca6f4d06df7f0ccab6fe4f49c8a712c8c8b6847b4f0af9a0e1dbb0e0337e", size = 1246268, upload-time = "2026-07-12T08:38:24.857Z" }, + { url = "https://files.pythonhosted.org/packages/96/c9/5d781d4ef1124564a45c98b9ff25d531c10cdf568ec6314a2d1946f9251c/sentencepiece-0.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:252908153eeec06c3ca3a32077e64a49d572e3d89881475b4e0f02d99d9fcc7c", size = 1190702, upload-time = "2026-07-12T08:38:26.789Z" }, + { url = "https://files.pythonhosted.org/packages/b8/13/7a562289c8d5b49ebdf3f9c1e8ab67cf14a8743b1d90c8f406bfdec36b72/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b", size = 2188384, upload-time = "2026-07-12T08:38:28.486Z" }, + { url = "https://files.pythonhosted.org/packages/85/d1/912f14fd5eae168aba726ffb6a9a2dc1c71fe7676c53da6f5c442b886d4a/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820", size = 1441553, upload-time = "2026-07-12T08:38:30.552Z" }, + { url = "https://files.pythonhosted.org/packages/bd/44/caa9cab5f261a019e2808bc5046152775dc57352ba9cbae7525e9e7a1ed4/sentencepiece-0.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838", size = 1347176, upload-time = "2026-07-12T08:38:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/cd798935668cff71d309d8ff10385844ecf216b1fe454f1993ed8bf2cb91/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a", size = 1325200, upload-time = "2026-07-12T08:38:33.689Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/37e3da037318a70066ded0d51bc2a7f35491ae6338dd993d5eb1503fc3b5/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da", size = 1397736, upload-time = "2026-07-12T08:38:35.771Z" }, + { url = "https://files.pythonhosted.org/packages/8d/11/753fca2e6b109be3ab7867abf357dfe48677fe726ae5a5363d0b54ca9450/sentencepiece-0.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7c6e7bf684dc12145bfa685d3060beaea55139134ba848289bee514ed42e7383", size = 1248030, upload-time = "2026-07-12T08:38:37.604Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0a/70efbe861ca182d7d4b6e1a20f58e043400848fa9f2915229f082e221648/sentencepiece-0.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:76ff5814db72e7462dece042d7593cdf102b8ec82c2b1cc201a2add34ee3050d", size = 1187325, upload-time = "2026-07-12T08:38:39.348Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a3/b3b05095c174d6e80d37d5ddc2f57c2c56237333e7bbd6079cf3243c2a8a/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:77c3ce990b23441e5ecfa5bce181fd6f408b564aeb6d7e1d1e7de9c5612501c8", size = 2188346, upload-time = "2026-07-12T08:38:41.089Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/72ebc4acb10a06bcf7503fbc6091c8f5db68300f6aac4356c09e6c76e0e1/sentencepiece-0.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fd523c4992041faa5c2b3cde62253d11a96c30d73a34afe48a486e8e2254cd1c", size = 1441434, upload-time = "2026-07-12T08:38:42.56Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/f9ea1a6844b4fa5dfe2312095cd866a1f724cd0905054ab9d5991778ba50/sentencepiece-0.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:201a8e0f55501a76e08dbf2c54bc45f4642b379271e89c667d517bfbc2191f2a", size = 1347267, upload-time = "2026-07-12T08:38:44.389Z" }, + { url = "https://files.pythonhosted.org/packages/32/4f/31c1073314ad94466bca37d29581761d70110237ee3d46b0efece59a8c1e/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8eed98514bffe5ecac37f493f91869c351fbb05629328bfdbc08502c6c094dc0", size = 1324980, upload-time = "2026-07-12T08:38:46.304Z" }, + { url = "https://files.pythonhosted.org/packages/59/b4/a0356fa04d6a14337a6e0e443556785a0422c53ec58baae6b9568120eb0f/sentencepiece-0.2.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64b656f025355cf8c51abe9fbe3848540756c6d7ca5e6791b1afa664bc24c7cb", size = 1397593, upload-time = "2026-07-12T08:38:48.302Z" }, + { url = "https://files.pythonhosted.org/packages/09/fa/d2d6369257fd2f0de616b1c7110b73fab409ef61b14f1b9e0010ed325914/sentencepiece-0.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:74f0ee601047c0c12a783088b51be4e6214a62ecd9e02278c477433cd16e0ed9", size = 1247987, upload-time = "2026-07-12T08:38:50.15Z" }, + { url = "https://files.pythonhosted.org/packages/17/ee/2bb594da6fd95e32f29057f1aa7fa996701b8980090923c2d8711fdc0a24/sentencepiece-0.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:b23fe17779834d3c27aaf2edac9486d04cca1a7deb8f5facda35150ac6263a91", size = 1187250, upload-time = "2026-07-12T08:38:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/58/9c/dfc82846460e7a712310f5613f23d8b553cabb4e2e648663c11d8382af56/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:72b7825b331b1b7e7c45be2e674b3e3c65af608fa376bad2d851b20aaf0cdc78", size = 2223080, upload-time = "2026-07-12T08:38:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/3ff12cebe6d31662d9ceeabfb282de20bd0d6098fa282b4a3b8305abc7e8/sentencepiece-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d795c4ac689a57f9d4ba2288126ec7901d389ad5827d2f8b8533c883974fe563", size = 1458511, upload-time = "2026-07-12T08:38:56.811Z" }, + { url = "https://files.pythonhosted.org/packages/59/5a/16d51d05360be4cee3ebfe4837c184054c4eed16cabaeb3b039524e9a000/sentencepiece-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ab3f1ae98970b5590e2209341522718900ba19bcc2c207ffaa6bd417ad960c5", size = 1361138, upload-time = "2026-07-12T08:38:58.808Z" }, + { url = "https://files.pythonhosted.org/packages/0f/af/c30ee2a9f99d51db9844acaa8fa0b611a97c2fa7116646fa43db3300b187/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec27c152a1f1b24bc9168b55a5880f3c16e2334e697da6f55a1046a22405a3d", size = 1328625, upload-time = "2026-07-12T08:39:00.849Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1a/4c6b39d03f5ba8439509adbd5a23c9538088a3cb679e7a47b911e8442bc6/sentencepiece-0.2.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59d6588712101ccfcae9b03692be3aaae1514c2078666d7b05f15ba3a702e41b", size = 1398595, upload-time = "2026-07-12T08:39:02.86Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bc/9eedddcec1fd57bc70200fa3ebf792d18fa63527a5369581cd416c81f97f/sentencepiece-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:89625fb43765cccaa1443b9adb61f283e5fe4cb1536728205d06bada730caa53", size = 1259346, upload-time = "2026-07-12T08:39:04.559Z" }, + { url = "https://files.pythonhosted.org/packages/41/15/7e74c8533848866ff560b29f7d8719921b76c4ec7149592d6d28e0deee75/sentencepiece-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:4f0603267cd15b92b68c2c0e852a441507614b70dc7773659baa6b8c214a91fd", size = 1196596, upload-time = "2026-07-12T08:39:06.454Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7e/f5df63edb6bcb46c1343cfa5d9192d73a4eb61af2e800d9402efff387523/sentencepiece-0.2.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c62bd361cec1f5b556eb8210264ecfff37486cd990c3386cc00310f26c54090a", size = 2190240, upload-time = "2026-07-12T08:39:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/52/0a/095d183b453b2a2e20b016829029c58eca90adc1c9911113e5d26fff45ed/sentencepiece-0.2.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:46ba07b543add034de0ff47ac5f907e9a06682f91d85121a972764628933be6b", size = 1442220, upload-time = "2026-07-12T08:39:09.91Z" }, + { url = "https://files.pythonhosted.org/packages/d1/18/823954c9c90e74eba09fb96752dc37a5555df00d69866cb9406d1725dc7e/sentencepiece-0.2.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79bac5a251f23a7341e28fda9ce0d5319edf45328239ce037c0682936f137906", size = 1348056, upload-time = "2026-07-12T08:39:11.744Z" }, + { url = "https://files.pythonhosted.org/packages/10/ca/1b6c251321901cbf8a2d2e48b8b70eb82a449011b766af52a228d0a90b6b/sentencepiece-0.2.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1402d8ee36f0d851cea8eee4dbb85fea14643b7503cf4d00d102eec0fe3ca719", size = 1325463, upload-time = "2026-07-12T08:39:13.413Z" }, + { url = "https://files.pythonhosted.org/packages/24/b3/718847349da7b25c8220ed86d85b89080af94740b2d87a59198104ae5c51/sentencepiece-0.2.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d44b20234905ff022b7d535f79d1f823ad7670c9851cc4f03cdc34787cdb3ab", size = 1398138, upload-time = "2026-07-12T08:39:15.564Z" }, + { url = "https://files.pythonhosted.org/packages/33/fe/4906f12c458274edd96387e4baaad7c6f064a2b7c11a1cc2401c8a7bd483/sentencepiece-0.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:63250cfab8b80a1ef82a614eb2b3cadfec2c405f870cedc139d08e2f063eb708", size = 1356144, upload-time = "2026-07-12T08:39:17.313Z" }, + { url = "https://files.pythonhosted.org/packages/d3/eb/22f89b6542aba400b0007cf0b1697cc3f99be8fb682fdb4c05eec450e33f/sentencepiece-0.2.2-cp314-cp314-win_arm64.whl", hash = "sha256:65d84ec36888de4a848eee5f910e67fbc79b064685ef1e10a502e14520ead9c9", size = 1294351, upload-time = "2026-07-12T08:39:18.967Z" }, + { url = "https://files.pythonhosted.org/packages/84/c4/7afe8c2315b76e46818851a057e50a378a0382aa00b970a1fa444181b6f6/sentencepiece-0.2.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d254c98ca6387655400b3959c33c83efd807f5edeb608e3aca45800ceaa77151", size = 2223281, upload-time = "2026-07-12T08:39:20.978Z" }, + { url = "https://files.pythonhosted.org/packages/98/42/fb678e472c554ef086be6375d20060ca610a2c4218854d4c091001fc6f91/sentencepiece-0.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:3fd9ce2ab4460c713cfdeb4aca693ca6732a11538e05fb332d5af42e3d7fde25", size = 1458779, upload-time = "2026-07-12T08:39:22.812Z" }, + { url = "https://files.pythonhosted.org/packages/78/52/ffe402b13bce1889228a98dc6cd86ae8afac1112362236be3468be784441/sentencepiece-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7fc14c1585139fa6b68775e616a6b90cf622ebf219f9558c0aeaf5d253ee6c9b", size = 1361736, upload-time = "2026-07-12T08:39:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/78/4a/2288f60e7283583ec0a0f16e72f9c8e68557d7e7a4b585d2cda4f9f47e64/sentencepiece-0.2.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df88b0c34f2fa909d322f7b06b1398e1e81af4b2f42a7b8e3556f928b25d1811", size = 1328155, upload-time = "2026-07-12T08:39:26.422Z" }, + { url = "https://files.pythonhosted.org/packages/26/31/5dd6882ebe899f741a5cfe40ff56c6efc06bc26ee287abdb723b671f409c/sentencepiece-0.2.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3f5851441ab1ef8634963a5100b733a8bbeefe623e0c5c005b1f1f3880e574cf", size = 1398307, upload-time = "2026-07-12T08:39:28.637Z" }, + { url = "https://files.pythonhosted.org/packages/da/05/7d7780fa63f4b8c1821953b916e25f89ae8f14d4da6ba91e10f6d06dc2b4/sentencepiece-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:046b15ea22d8042e2e173561d464ec3b64a9c2081324df70ebce7bf7ebb3e497", size = 1367133, upload-time = "2026-07-12T08:39:30.546Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/70007fef3f818c688de4a730f98024a671599ab67f20270f8efb03d69dcc/sentencepiece-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fa9f5ef0e2a82233dd0b8b32ea3f5710e0c44afbc07ed3620219f32601e56090", size = 1302760, upload-time = "2026-07-12T08:39:32.457Z" }, +] + +[[package]] +name = "setuptools" +version = "84.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "simplejson" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/2a/54837395a3487c725669428d513293612a48d82b95a0642c936932e5d898/simplejson-4.1.1.tar.gz", hash = "sha256:c08eb9f7a90f77ae470e19a07472e9a79ebc0d1c2315d86a72767665bd5ba79f", size = 118860, upload-time = "2026-04-24T19:24:59.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/da/3ba5e87e917094961e7b51b541c88f735f1ca37d580ac78a9302b468f64e/simplejson-4.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7f61eefab86235c800e7f4e37d977080ec424bb2bf0b74e95a2d17ecb48eac0a", size = 111675, upload-time = "2026-04-24T19:22:30.344Z" }, + { url = "https://files.pythonhosted.org/packages/fe/8a/d0c08f4b8934b64469a63d461a68a01d5cc32faf313400dda2bdc1075a29/simplejson-4.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4484960512db9c8124bfa91e0d8a9f9c302338f1c5454e74c21d7d022df10f46", size = 90544, upload-time = "2026-04-24T19:22:32.095Z" }, + { url = "https://files.pythonhosted.org/packages/c2/2d/7832ed91cf4900f86c783d589bfac53358abfccb278f1c8b55eec167b395/simplejson-4.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b75c7ef874dbb350f41827cdf3cee23f5257bdcb0df46d4c01b34badb62dcfe8", size = 90895, upload-time = "2026-04-24T19:22:34.412Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d6/a2a7a482fa43aaeaefc001491d381960f5e685ee4645343e0e037cebb57c/simplejson-4.1.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c7494c75b95171194f965ea609e97081837a26494d91dcc046ad27dd9c3503e2", size = 168660, upload-time = "2026-04-24T19:22:35.717Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/7a6482f336338dbdb6ca6d3099b2fdc1c74c47eea3c6511975751e9198df/simplejson-4.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1778e09a6e4bb4ef304627915dc4a838569d9e6b737c787925b4e98244bbbc16", size = 167264, upload-time = "2026-04-24T19:22:37.415Z" }, + { url = "https://files.pythonhosted.org/packages/c9/43/039982e956b06c6b019d48bdf9d4ec06f298adf6136552ad1979b94be0fd/simplejson-4.1.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:67e43e7c0555e10de6d83e1408035652fad28c983516e38c4e3a9a748c9af129", size = 176909, upload-time = "2026-04-24T19:22:38.872Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f5/e3ad592d089922abce2c2ea377548953ac55ffcbe061d600f01b9db2e6b6/simplejson-4.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:93bf6653420258372444de90194dab8de8ff13d74b5d4263a5fefbbe8b8d2060", size = 165930, upload-time = "2026-04-24T19:22:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/f830b648ae04601e6813306535d8e0a4c178d6453cec539b85dafdac80ed/simplejson-4.1.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0662cfe0482c9796bd097213b27f006815bfdc9b671264c3c0b7fc0e72b71d00", size = 174710, upload-time = "2026-04-24T19:22:42.437Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3e/82c8997c4ef2ef6c832fbfc3bb2ed14a212616a284100af03b552ea7e072/simplejson-4.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a9ab55d2459f6d0fdf9984a7a0fb0280dae12979f4fcc3171f5096a4fcf5fafe", size = 167685, upload-time = "2026-04-24T19:22:44.023Z" }, + { url = "https://files.pythonhosted.org/packages/4d/03/80e67a6c63fe812094c681917a5c5d403e34904d200570416863fe2e8328/simplejson-4.1.1-cp310-cp310-win32.whl", hash = "sha256:dfb84ace97acbdf1916c5a675387493fc5a7f67c2e15d4a7687143f8c73024d4", size = 88317, upload-time = "2026-04-24T19:22:45.547Z" }, + { url = "https://files.pythonhosted.org/packages/f4/05/d4fa2c024d566bddff732a2aa437faa4cbee15ee277e2a855faf91a9d906/simplejson-4.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:8eb821ef27f688f59ed4a93b17a666a7ebacf8dd65fecaa2b3c531a3aea62eaf", size = 90461, upload-time = "2026-04-24T19:22:47.447Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/39013ffe279d90093ec1c848565b3683c586906c10fa55d9000ec29d046b/simplejson-4.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2867c64d92abd1992c15666fae198203093f593e43d6b81adf176bae530d493a", size = 111538, upload-time = "2026-04-24T19:22:49.051Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ae/2c272971c8a87e2539c54a98eb6ff037bee1e2e93943c3986cf7500a4f3a/simplejson-4.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c47c46e16c8ea9e4850061e6ed5aa2b9cd2074cb2274bfd9c138cba15ce7453", size = 90594, upload-time = "2026-04-24T19:22:50.408Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a2/6eebfb99dedc139f549200f61ade6d1890ac5707c5d427bdfa6fe39c9313/simplejson-4.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e294e33dbf316a9bbdd4030d46503c9b0f19470ae7ad6af5bae6c426bc2e869f", size = 90718, upload-time = "2026-04-24T19:22:51.694Z" }, + { url = "https://files.pythonhosted.org/packages/80/7e/c9e6c0c4ad8415e64dad0c47f619b556b02680a41631b4dbc281d55dc54d/simplejson-4.1.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7ce252b28fddbdd83db5bd7d93dad2a8a591d7ada098afec9c1b23d6b722a7a4", size = 180901, upload-time = "2026-04-24T19:22:53.025Z" }, + { url = "https://files.pythonhosted.org/packages/34/09/69e331e3994b1ed9be6ce9ace4ade704e7ed503edf869929ca7bb404eda8/simplejson-4.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c44ef6b02a4eb67ed17a72342341792149b3ff46f15426c26e970e49addf327", size = 178133, upload-time = "2026-04-24T19:22:54.574Z" }, + { url = "https://files.pythonhosted.org/packages/5d/40/ed806f24afef295c1032448f5ff6f6f2979392d5645ddb9f4fed7f38194d/simplejson-4.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82bfca2b85a34178c25829c703f0a9e9f113a5af7539285bd3efb583a0bf1ba3", size = 188155, upload-time = "2026-04-24T19:22:56.044Z" }, + { url = "https://files.pythonhosted.org/packages/38/94/8d6f515b827b0f7881a49c8c1ac6920b7ae9428939ef04238c973278b42a/simplejson-4.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0e4b23f71dd781f8830f1663dc01a4944d3dbf87a1f93d78fba1cf64722d0ccf", size = 176225, upload-time = "2026-04-24T19:22:57.981Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fd/6dffb4956563d48bbe46b91ff341adae34920e94008fd6b8d728072abfc7/simplejson-4.1.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:82fee635d7b73ad801030b05a75fbd34a098da0c2ecf600667a03636d09e1e42", size = 185535, upload-time = "2026-04-24T19:22:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/de/d2/a509ee37763e79aec75d68f8521db1440306edeba3b8b4064ab4ee8bf1d9/simplejson-4.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:68e62eda21192c5ea9bb92d571ca46a4477fef48762f50d433de2b4253051551", size = 179302, upload-time = "2026-04-24T19:23:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/d8/23/5b343bfd2a79d3b6818e4db3586c405a001a090d4c89d336e31273ce7177/simplejson-4.1.1-cp311-cp311-win32.whl", hash = "sha256:ffd3d82294b47f5ec64050021ace95fd62628a0c1cc8bbf4d06d2d1fb697e055", size = 88408, upload-time = "2026-04-24T19:23:02.808Z" }, + { url = "https://files.pythonhosted.org/packages/38/04/df9b37aedbd524dca20840d25ebe01d6ae486b89792aeff5d15b9c4114f7/simplejson-4.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:78a3fe0995be42bed62a26aa78e0e0b4d87c6545785346b9cc898f3389569a35", size = 90526, upload-time = "2026-04-24T19:23:04.408Z" }, + { url = "https://files.pythonhosted.org/packages/60/25/e90998fe8e480eb43b966c09e835379887d427567ebd496563d3b1e16b19/simplejson-4.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:19040a17154dc03d289bab68d73ce0a6a0be01de30c584bbdd93490bead14b22", size = 112414, upload-time = "2026-04-24T19:23:06.084Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a0/abd4785f36c3400f1fbb21f517be39295a750a714f04b7ee175adf6ef580/simplejson-4.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a94ebaecdbaa80d9551a3ec6bf0c9302fc8b53ab6c1b2bfd498a1df4cb28158d", size = 91120, upload-time = "2026-04-24T19:23:07.877Z" }, + { url = "https://files.pythonhosted.org/packages/b8/78/fc060d2e3b13c6ec59288574b8efac64075e316b2afba4396a56b2422f78/simplejson-4.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67341c95c0a168ab4a6d1e807e50463f1c8da932c3286d81e201266c427061fa", size = 91055, upload-time = "2026-04-24T19:23:09.264Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b6/156a8de1e1b47694f0e7de6675866936608d45dc68388fd017d36f8693be/simplejson-4.1.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:45ec18e337fec538b7e902d489505c450b2454653d1290f3f50385e6fd8aa607", size = 190297, upload-time = "2026-04-24T19:23:11.226Z" }, + { url = "https://files.pythonhosted.org/packages/86/1c/e4d0eab695be3eb21d0f46bce820752031f03e7113f9c80a9b3c73ee7157/simplejson-4.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:820c69a4710400e9b248d5670647d60be58824369282d3925e516b3ff1a7cd82", size = 187002, upload-time = "2026-04-24T19:23:12.982Z" }, + { url = "https://files.pythonhosted.org/packages/76/0e/7f5a59d29426b062d5928fb88b403c3f797129d53be7102f955dbe51aa44/simplejson-4.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e708d373a10e4378ef2d59f8361850c7150fd907ed49efe49bc5492160476d1", size = 195146, upload-time = "2026-04-24T19:23:14.517Z" }, + { url = "https://files.pythonhosted.org/packages/78/18/9943db224dd4d5fa3c090c3e56a94c37b254338c83995ec5680285111c40/simplejson-4.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:980fc33353f81fd12d8c49d44f8c2760d1dc8192285e627c5180d141035b228a", size = 183931, upload-time = "2026-04-24T19:23:16.742Z" }, + { url = "https://files.pythonhosted.org/packages/c2/08/9a690da9a766161c06c627d805362cf159f1abe480969372b2897649b955/simplejson-4.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:de2ed102fff88dacf543699f53ee3a533cc11539a39baa176b7e09dd783069d6", size = 192228, upload-time = "2026-04-24T19:23:18.33Z" }, + { url = "https://files.pythonhosted.org/packages/05/88/bd8aad36b451ffb0e0a3f721d695a88befa6d1ac7d1e02ae788ca7ff4029/simplejson-4.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2785ff8edc0e28bf773a32543a6bbed46351453c997b3f6709c744e3c2f7eabb", size = 187808, upload-time = "2026-04-24T19:23:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/04/ee/14f91db0d1f481533b651dafbf8cd0da088d9817f7af30c68f7f19f9c847/simplejson-4.1.1-cp312-cp312-win32.whl", hash = "sha256:2e0d5ead6d14610467ec356ec1f6b5d8a56aa216abaad8d41c8b873b16cf313f", size = 88512, upload-time = "2026-04-24T19:23:22.764Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c4/90de06b2d8737c68c05ff9274113f854dbf6a5f28b7a955212111672cb57/simplejson-4.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:63a5451f557d6be48a231bae932458655c620902b868170b2f1c8afed496f6b4", size = 90748, upload-time = "2026-04-24T19:23:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/37/a9/47b445eeb559c9593453a0648e0fd6d08e8adff64dd5e5ced66726da8a09/simplejson-4.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dff52fc7af272e84fc21cc5a06c927c823ca6ae00af14f3b0d7707b42775ed98", size = 113160, upload-time = "2026-04-24T19:23:26.033Z" }, + { url = "https://files.pythonhosted.org/packages/4c/65/cb72db31523c164dea5dc55b02dad065a40c478856bc7534b279d2b51906/simplejson-4.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:971aed0647ad6e840a3943bec812fcda5f2d26a5497a4981d1fb49aa4f9a396c", size = 91521, upload-time = "2026-04-24T19:23:27.572Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e5/54cb7c50ad5fdc1e0a86b7df4b135c2cbd5c4623605aa94466659098e8da/simplejson-4.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:249e2e220aa6d9b9d936bde84eb7bf79d5b6c5a8273c6e411f8b1635a9073f2d", size = 91407, upload-time = "2026-04-24T19:23:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/21a3ede87f0bf82d6c7bcb90480d50a6490eb974c6ab20881188e440957c/simplejson-4.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e5cdd6a5d52299f345c15ab5678cc4249e24f383f361d986afbc3c7072a6b6b", size = 192451, upload-time = "2026-04-24T19:23:30.56Z" }, + { url = "https://files.pythonhosted.org/packages/59/df/9903edd3102bf0b5984edfcb90c88612330996efa3b4fbf8a971d6e17839/simplejson-4.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:642cec364e0676e2d5a73fa4d31d0c7c55886997caa2fde24e8292ca44d32728", size = 189015, upload-time = "2026-04-24T19:23:32.647Z" }, + { url = "https://files.pythonhosted.org/packages/98/cd/33230927a780e1398b857e3944abb914556994d252b1d765ae40d112cb25/simplejson-4.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:76fe296ca1df23d290033f10aaacf534fd1b3e3007e7f9ff8aa68b21413aaa78", size = 196658, upload-time = "2026-04-24T19:23:34.563Z" }, + { url = "https://files.pythonhosted.org/packages/cd/84/2c5a7444eb53e9a86d3738299bffddd9f53aeed799ded2f45368221fdb19/simplejson-4.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f0ad25b7dc4e0fb23858355819f2e994f1a5badcdcde8737eac7921c2f1ed2a", size = 185967, upload-time = "2026-04-24T19:23:36.191Z" }, + { url = "https://files.pythonhosted.org/packages/d3/68/454378e06d059cd412a7ed5d87fb6d29fd5b60f13a4d89fc1f764ff434df/simplejson-4.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a59ebd0533f03fd06ff0c42ba0f02d93cbcdd7944922bf3b93911327a95b901f", size = 193940, upload-time = "2026-04-24T19:23:38.151Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d5/a15bf915f623a2c5a079d6e3be8256fdb8ef06f110669493a09b9d6933e0/simplejson-4.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bccbf4419676b517939852e5aeff2af6aee4dc046881c67a1581fa6f1cb01abd", size = 189795, upload-time = "2026-04-24T19:23:40.139Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c9/37212ae7dc4b607f0978c408e8633f05c810884e054c33113184c6c2c8a2/simplejson-4.1.1-cp313-cp313-win32.whl", hash = "sha256:6c845363eb5fd166fb7c72243da38f4fcfde666ede7fdf2cc6fd7762894626f7", size = 88773, upload-time = "2026-04-24T19:23:41.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c7a0a47883a9015b54c9d8a4b62f2aba17bd4335b1787b9b8a0fc2fa6d52/simplejson-4.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:104d8324c34f25b4b90800bc5fa363780cbc3d8496aef061cba7ce1af9162270", size = 90888, upload-time = "2026-04-24T19:23:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/4a118a6a92eb33bb08c8e2fe7ec85cb96f0673491bb2b829930831ee4fbe/simplejson-4.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ed7473602b6625de793b6acba49aa949f144a475f538792067e4cf2fda2071f5", size = 110492, upload-time = "2026-04-24T19:23:44.957Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/84d160e9fa8cada1e0a9381cae4fa81eecd573577a5b34366d8ced59bdf7/simplejson-4.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:225c9caa324c5b554d009fb9cac22aee7711e71bd96f487938c659af467e828e", size = 90152, upload-time = "2026-04-24T19:23:46.355Z" }, + { url = "https://files.pythonhosted.org/packages/68/31/9a5432c433a7671107182cdc9a20ea78a70f99c4e5334aa54b6d4d0d79ed/simplejson-4.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:95407269340c7f22f09776ea7b717a52cf56cfcf119b5e45f66faa4a26445bea", size = 90115, upload-time = "2026-04-24T19:23:47.743Z" }, + { url = "https://files.pythonhosted.org/packages/78/91/3635cdb13318cb0a328abaa69e2b91251caad39d6779aa308098f341f6cb/simplejson-4.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3851658d642c1184d2023f0e6c9ce44a21eb1629e74e7c84ef956b128841fe12", size = 184036, upload-time = "2026-04-24T19:23:49.472Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/149b6ec5393f6849d98c59cadba888b710a8ef4b805ab91e11a566960d40/simplejson-4.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95a3bb0f78e85f4937f99092239f2011ce06f0f2d803df5c299cc05abbeae008", size = 180543, upload-time = "2026-04-24T19:23:51.023Z" }, + { url = "https://files.pythonhosted.org/packages/df/7c/a5d968d0b527a748b667e62bea94309ccbcb1e2b108e8f0cf8547efaa12b/simplejson-4.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbfdaa7c0603f75b7b14b211b7f2be44696d4e26833ad2d91d5c87bf5fb9a920", size = 188725, upload-time = "2026-04-24T19:23:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/db/e3/6a8d11181d587ef00e2db9112357e6832111e56dd56b01b5c11758a1965d/simplejson-4.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39e3c584071dced8c21b4689f0254303521daeb9b5bc1f4289755d71fa3cb0d3", size = 177492, upload-time = "2026-04-24T19:23:54.581Z" }, + { url = "https://files.pythonhosted.org/packages/67/e3/8b0eb8b06e8198cfbd1270487da163d0093df05cc4f557350cd65e2f7e79/simplejson-4.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:036a27bd0469b9d79557cbddb392969f876cd7f278cfbd0fba81534927a06575", size = 185281, upload-time = "2026-04-24T19:23:56.13Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5f/64990f07ec9e2cb1a814c674e2e21b5693207f74ac70eb72151b847ea4e6/simplejson-4.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b70bfd2f67f3351baba08aa3ae9233c83f21fd95ae5e6b3d0ecb8c647929112f", size = 181848, upload-time = "2026-04-24T19:23:57.92Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/bbc1bc0447f339f79f99ab8c37f7f037cb2f1f93af75d6a4d553096bb0c3/simplejson-4.1.1-cp314-cp314-win32.whl", hash = "sha256:37233c72ce88d06acb92747347742b3c07871eba6789f060c179c9302dde8efe", size = 88761, upload-time = "2026-04-24T19:23:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/18/72/ec1b5cbdcb140c132e6c7bdf99bd73e4f675439e77126c88f472fcffa09c/simplejson-4.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:cc0442dea71cd9cbf30a0b8b9929ab5aa6c02c0443a3d977351e6ec5bada4388", size = 91018, upload-time = "2026-04-24T19:24:00.85Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/4fa437f68ff72219bac3bf3d050de9c6265691f3a170e16954bd69d7cddd/simplejson-4.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c996a4d38290c515af347740659ce095b425449c164a5c9fa3977caa6eff5dbe", size = 113919, upload-time = "2026-04-24T19:24:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/c2/83/59de041d09eb4a9577f7015d7263c32095dfb7fde49717dff62145d89809/simplejson-4.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c65c763fb20d7ca113c1c14dce2fc04a0fc3a57aceff533d6fdac707c7bffb40", size = 91904, upload-time = "2026-04-24T19:24:03.812Z" }, + { url = "https://files.pythonhosted.org/packages/03/8e/46bb345d540f6eb31427d984a4e518cdb182d0621814fee4fee045e8815b/simplejson-4.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0da5c9f57206ee7ef280ff7f1d924937b0a64f9a271a5ef371a2ecdbebba7421", size = 91752, upload-time = "2026-04-24T19:24:05.622Z" }, + { url = "https://files.pythonhosted.org/packages/83/e2/1b2ce97f068835eb3d253c116a4df7a3f436b7bf2fb5ff1ba29287e8b0ec/simplejson-4.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ea3426e786425d10e9e82f8a6eda74a7d6eb10d99165ac3d0d3bbcb65c0ea343", size = 214021, upload-time = "2026-04-24T19:24:07.447Z" }, + { url = "https://files.pythonhosted.org/packages/48/70/d93e556df6a0786298644a7c08304fcbeddc248325f23f38acbebeb21165/simplejson-4.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d75cea7a1025edd7e439b2966b3d977c45b5b899e2adaf422811b3ac702ed9fb", size = 213530, upload-time = "2026-04-24T19:24:09.289Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/c93bf305b9f00d7259e09e713d60e75bd0f7f53da970f716ab90491770e7/simplejson-4.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63c2ada8e58f266491f19eed2eeeb7c25c6141e52f8f9e820f6bb94156cf8dbc", size = 218282, upload-time = "2026-04-24T19:24:10.991Z" }, + { url = "https://files.pythonhosted.org/packages/0c/20/a9b5d2e27ec44b069ee251bd55544fc76929a067107b1050001566ba86f3/simplejson-4.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d1fffb56305c5b475ee746cf9e04f97423ba5aaacd292dc1255bd75b1d3b124b", size = 209249, upload-time = "2026-04-24T19:24:12.662Z" }, + { url = "https://files.pythonhosted.org/packages/97/e4/e06ee682ed5df67592181f5ecb062e35878967e27f5b6e087237d4548d95/simplejson-4.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a6525ec733f43d0541206cffa64fd2aad5a7ae3eb76566aff49cd4db6382209a", size = 213963, upload-time = "2026-04-24T19:24:14.302Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9f/1e160e4cd8cdbf062bf6a454cdf814dc7a48eb47e566fdb8f80ccb202605/simplejson-4.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:861e393260508efa64d8805a8e49c416c3484907e3f146ce966c69552b49b9a3", size = 210474, upload-time = "2026-04-24T19:24:15.917Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e6/cecd913df322df5bbe7ebb8ba39e0708e505a165553900da8a7761026d6f/simplejson-4.1.1-cp314-cp314t-win32.whl", hash = "sha256:d083b89d30948a751d3d97476c2ed91e4caaa24a1a1459bdbadb8876242c71fe", size = 91134, upload-time = "2026-04-24T19:24:17.635Z" }, + { url = "https://files.pythonhosted.org/packages/97/73/f540dde99cc1d393bd062ab3b5735b777561a5d8f8a5f2e241164444d77a/simplejson-4.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4cbb299d0528ec0447fe366d8c9641860e28f997a62730690fef905f1f41046e", size = 94467, upload-time = "2026-04-24T19:24:19.109Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6a/8b74c52ffd33dbbde00fe7251fee6a0acdc8cea33f7a43805aed258fb79b/simplejson-4.1.1-py3-none-any.whl", hash = "sha256:2ce92b3748f02423e26d2bfb636fb9d7a8f67c8f5854dcae69d350d123b2eee2", size = 69195, upload-time = "2026-04-24T19:24:57.962Z" }, +] + +[[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, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/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 = "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, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "swanlab" +version = "0.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "nvidia-ml-py" }, + { name = "orjson" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "psutil" }, + { name = "pwinput" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyecharts" }, + { name = "pyroaring", version = "1.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "pyroaring", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "rich" }, + { name = "setuptools" }, + { name = "urllib3" }, + { name = "watchdog" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/88/a1ff4cc522f2e56d29d6ebd75e2466ad2e2b19d070539ad9c7c792a5f9fa/swanlab-0.9.7.tar.gz", hash = "sha256:aa807269af599270d3339f6d844569c4e4dd95b101c3c77b3c4714f7ec21941c", size = 14845500, upload-time = "2026-08-21T14:33:56.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/22/ced448dea9ddc2482184e2a42a1c3f8158a22224f6a2657e070db9f3c799/swanlab-0.9.7-py3-none-any.whl", hash = "sha256:05ebcfdc9cee2b177a549a59316570a8b965e6cb1da0552a4fefab20a3fd736c", size = 519493, upload-time = "2026-08-21T14:33:53.567Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + +[[package]] +name = "tensorlens" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "gunicorn" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "ipython", version = "9.16.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/c7/6aa13d98c4c3b3f600bbb068af1355bf85ad8571cf374c1c3a359d399fc1/tensorlens-0.0.3.tar.gz", hash = "sha256:b6b64070ae6da1a752e007b3a4a0df884fec8a97146559ca3e3b9a561dfd4118", size = 962890, upload-time = "2025-04-23T21:04:47.781Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/cb/0105b445a5947f5a7e48fe9cfe6e7ce9e1bb8088bb318606dda3cc84f895/tensorlens-0.0.3-py3-none-any.whl", hash = "sha256:8e60d9309d50153af6e44294c95fc719920b4fc73ac5c4a8e1b6aa710719f2cc", size = 966011, upload-time = "2025-04-23T21:04:45.255Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, + { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/e7/19894fdb51c7dbaf94f5a79bb0871da0992e8e4241e579cb006da46d2e58/torch-2.13.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:94f0de129916f77b8dc2c7a8eff644cfeddfe59e39c9f55e9f6e17543410281d", size = 111178962, upload-time = "2026-07-08T16:05:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5c/b1d5de470c54e339b30a92d96683a71bcebd78f5f2a7fc714cd6dc6bbd68/torch-2.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:0ab4b69f3ee03a62a002cfbf77b1ca5e88aceb4ea64cb4388bb28f638ddbb045", size = 427198333, upload-time = "2026-07-08T16:05:36.847Z" }, + { url = "https://files.pythonhosted.org/packages/50/c0/68a84105e1fcb8970144b388ff3d3e5dc15a3be28c1e247841f7d7247e41/torch-2.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c78b7b4d04461855a764cf01bae9a462bb88bc93defcfa11235cbc8fdf3e12c4", size = 526555154, upload-time = "2026-07-08T16:05:06.507Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c9/0bb9d097b03cbaf96bb75b15e867347b8e41bfcdfe0539452d17d9e63993/torch-2.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:2bd30b6b730d987fa386ce3898933762c5cb8cc82eb0535211d787cc3ce2dfeb", size = 122015602, upload-time = "2026-07-08T16:05:45.25Z" }, + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369, upload-time = "2026-07-08T16:04:51.054Z" }, + { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961, upload-time = "2026-07-08T16:04:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fd/0f2ce40f58aefbdb3392f9acce3c8171940943ae2d661f70558bfa73befb/torch-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0d8b11f16a48d60e2015d8213aa0390744cbebb98e58b62b3514dddc656e330", size = 122015870, upload-time = "2026-07-08T16:05:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045, upload-time = "2026-07-08T16:05:22.997Z" }, + { url = "https://files.pythonhosted.org/packages/df/a9/f6a2a4d763ff1df02e9a64c477029db614295bc9367f4131223791ccc243/torch-2.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:572df8be8ffb4599c88cbd6a0726f1f854f4da65d2e3c09f0e2c2283333cd6d4", size = 427210998, upload-time = "2026-07-08T16:04:37.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/82/fea946351658e6534db52d2cc12bc53087cbf87f9440c5f180f367c1950b/torch-2.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:796633c4cdf0fe2cdced72d8f88f22e73dbcfce83132763162f6d4bff13b820b", size = 526605292, upload-time = "2026-07-08T16:04:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/21/d6/e8f3c6f7e01f626f77259de9860d2a78bc84c40539e28e79b7e98b0bb659/torch-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:024c6cc0c1b085f2f91f20a3dc27b0471d021c31ce84b81be3afdc39f791fd9d", size = 122057313, upload-time = "2026-07-08T16:03:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743, upload-time = "2026-07-08T16:03:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008, upload-time = "2026-07-08T16:03:44.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329, upload-time = "2026-07-08T16:03:12.649Z" }, + { url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920, upload-time = "2026-07-08T16:03:01.808Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/6dcc7f0c07052102dd36f83cbc5800842a909c8c3fbf1a7f8a5844954de9/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", size = 111227066, upload-time = "2026-07-08T16:03:33.6Z" }, + { url = "https://files.pythonhosted.org/packages/e9/09/2c10e8cd0e00fa5d23c052df6ce467eaa7182399f5e0f824f1e4ff42ccae/torch-2.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a3893dc2da0a972a8ca5d698c85a9f967559ac5f8ee1797b77408aa8734d073c", size = 427226309, upload-time = "2026-07-08T16:02:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/22c2102bbef14ca6a6cb4c20e42f088e49c5f812be4e160ae57502e325f9/torch-2.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:49f1ea385c754e54919408a9bb3b5a72b0b755bbe2c916c1d6f70afbec4908a2", size = 526614507, upload-time = "2026-07-08T16:02:16.441Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0c/7d1deb6bce5bc3e6042caf39100ac768eba3b9a098e1dddd16f75bd6489b/torch-2.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f8573e3ce9ebcd53fe922f01077a6085ccdfbe5f12fd215883a9d87d7a744fd", size = 122051871, upload-time = "2026-07-08T16:03:23.521Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ce/aa8b7f9949d32e0f2f624f342bc3b48112c1b8a130288465938bc83bcbf9/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", size = 111537025, upload-time = "2026-07-08T16:02:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/69/d1/491e3a0389430946145888b0203f2b6a759ce2a61481b96a85c2da4f2ced/torch-2.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:31061ff56ed8fbf26c749806905aeb749ebeb819810fd5d52508aa5afd90dddc", size = 427219769, upload-time = "2026-07-08T16:02:31.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1d/38006e045bf0a1fc28ef01e757c554e59e59a8770c284bc4f47b14e60441/torch-2.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cc26eead4cf51d0b544e31e364dcf000846549c273bd148936fe9d24d29acb92", size = 526571320, upload-time = "2026-07-08T16:01:59.348Z" }, + { url = "https://files.pythonhosted.org/packages/56/94/655c91992a882bd5071aa0b5d22a07dbb130d801e872be97c0b627a7c693/torch-2.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a7de8a313090dc5c7d7ba4bfe5c3be222528f9a4dba1acc83bddb1157360c4b8", size = 122306773, upload-time = "2026-07-08T16:02:39.832Z" }, +] + +[[package]] +name = "torchlens" +version = "2.34.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphviz" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "safetensors" }, + { name = "torch" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/e3/1e08bef887faf48ab0116bf19f753d1b98e1725505c23a58507ca342ca63/torchlens-2.34.1.tar.gz", hash = "sha256:ab1327d16d1b1e9b88dbab01cb8a3cddd8cac3cd30446f403bce67375f181f2a", size = 12997616, upload-time = "2026-08-10T22:05:06.709Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/9e/b44c428accaaefcdbbc035ab9df510e7e193fe5a5bec791702dbc1440f6d/torchlens-2.34.1-py3-none-any.whl", hash = "sha256:5370fd2cfc0e4a9559a9041710bfc6d13558ae906918060293fb8c6ba0803cfb", size = 13232610, upload-time = "2026-08-10T22:05:03.762Z" }, +] + +[[package]] +name = "tqdm" +version = "4.70.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" }, +] + +[[package]] +name = "traitlets" +version = "5.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/2e/a7fbfe268c8a3b32546930c0297c101d65a4a14c304ad5790a9f478f0e4e/traitlets-5.16.1.tar.gz", hash = "sha256:ed900c2b631aa3a112811139fa97b8d2c3bad5e989656bba4b7e52c7852c18c1", size = 166137, upload-time = "2026-08-03T08:32:36.848Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/66/0d785f0bc5e4315a96c989bb476d0fc07ea4f85132550c7b156ca2035d52/traitlets-5.16.1-py3-none-any.whl", hash = "sha256:f775618166caa0396c8e337099240f2bd3e5e917d203b2e6fbe21a58d3cb1f6b", size = 86211, upload-time = "2026-08-03T08:32:34.48Z" }, +] + +[[package]] +name = "transformers" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/92/c50c61da7046bbb59a4d011291aeadcfb4d7980ab36fdb31e93823a3fb93/transformers-5.15.1.tar.gz", hash = "sha256:27c996bd9075ddc82d40f8590dfdc81ea45f611bfca477e0db5d7fd257a482f7", size = 9378434, upload-time = "2026-08-19T11:28:20.33Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/c4/a12e1d9b387fb0c40a57116db82b457e8c771cb419163cda29204d74a595/transformers-5.15.1-py3-none-any.whl", hash = "sha256:b7cdf238ff583e3a58dbc7fa34da1aaf091ce063141f65a30538160bd5afe93f", size = 11749582, upload-time = "2026-08-19T11:28:16.726Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/ea/629cc37436ca5df93ce98956d09cd2ca1498bfee8ef4972d2fe48b9f958c/triton-3.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3daf64305d6cea88d3334c65ebc9bcd0c64c9564a977084366aa768d57cbcf64", size = 184551013, upload-time = "2026-06-17T20:03:37.551Z" }, + { url = "https://files.pythonhosted.org/packages/15/76/c79c34311625227a288df3e483fc5cdf3d596624cbd4b4758c4cbdc14af3/triton-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee89fbf782ec2ad50391dd1cf26cbea4f4467154c37f4773026da8fc31c0f58e", size = 197596267, upload-time = "2026-06-17T19:53:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, +] + +[[package]] +name = "typer" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/40/4a3db7990d1f62a53182aa96eaef57aeb2886a27f90a195bc66713565d31/typer-0.27.1.tar.gz", hash = "sha256:a79bef8469a79c45498e7b814ecf8d603cc7644e9acbd9e19cac0334240b18df", size = 203994, upload-time = "2026-08-03T14:41:03.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/89/9518bc0c3929bee36b3a4a8e3daddd6e03f92f9961c66d4983b837160543/typer-0.27.1-py3-none-any.whl", hash = "sha256:53150287edd11baeb4e4722c8e394fcdf8181c0ae89485cba8d25c778d5edd56", size = 122874, upload-time = "2026-08-03T14:41:04.391Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] + +[[package]] +name = "wrapt" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/b0/c1f5a970721f06b85c0cd5142e0ff8fe067708abd779b0c4f4be7d61d09f/wrapt-2.3.0.tar.gz", hash = "sha256:681a2d0eefd721998f90642762b8e75c2159ec531b20ad5e437245ea7b06a107", size = 131509, upload-time = "2026-07-28T06:06:14.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/31/5822ce37ca8820c2ed35a498c67c8b37960b9cee2ba437fd32849d0a234c/wrapt-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0bb2797048db0956348cb3058c33bc4184614f13231389cfbccc16a5d32780a7", size = 81191, upload-time = "2026-07-28T06:04:04.858Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5a/3c6117938be98754578ab83f5a40d7d0ea2cd2c487dc5cd6027ee7228229/wrapt-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce9f398f868d2b3b27aa2ea4de79645ef9077aeeac8dfc2814b0d542c6a2b87f", size = 82255, upload-time = "2026-07-28T06:04:07.151Z" }, + { url = "https://files.pythonhosted.org/packages/a5/0f/94ae724c5087eb6054c0d63febd7094947dcf302fe058e2e0488102a872b/wrapt-2.3.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ad71df7a04dd3497e9302e81f4a7c91bd401ea0e15a9df9029527900f94bee43", size = 155228, upload-time = "2026-07-28T06:04:08.272Z" }, + { url = "https://files.pythonhosted.org/packages/6c/21/1f780bba935dcf697c0c59de9be3a559bbb8e31a53ca3f25422023738432/wrapt-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc82c2ccc8e234c844f5303d9f2984b346dcdd53e94823ce8420d2c75b4b9023", size = 157073, upload-time = "2026-07-28T06:04:09.459Z" }, + { url = "https://files.pythonhosted.org/packages/73/31/6c7799d7b6431fcd7e1b83245fb45258a2d2c3a2187fbaecb83572a72d7a/wrapt-2.3.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6e19531ae33c508cea7d84a7edfda01fa86e51b8d1a93a77712c55e6e469152", size = 151594, upload-time = "2026-07-28T06:04:10.784Z" }, + { url = "https://files.pythonhosted.org/packages/ce/17/42d670dbfafd49076c6eb2b7d67633d7e1c968e39bfb11a135acb6fac67b/wrapt-2.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:df4ce31150bcd5d9f36f816aac3010ab4f4bf8672ac1d3b0ac7d539ec61c7c02", size = 156069, upload-time = "2026-07-28T06:04:12.316Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d6/c66b4ba4eda49257c84d5c2df26118280f09ca7905aee20d0064db778d13/wrapt-2.3.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:e2e692bc0d63f881cf7006730a56bd4e0c2fab5dc318466942805d692b166276", size = 150930, upload-time = "2026-07-28T06:04:13.482Z" }, + { url = "https://files.pythonhosted.org/packages/c0/f2/1a3b949c0322fb27396eafd1044328c1cb0400e0b32105d75a3cd03096e7/wrapt-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c8388ba7faf5dbf9ee106bb70d66f257629b1bd98091123e19e8a4553a319199", size = 154525, upload-time = "2026-07-28T06:04:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/147563a3dfa6e830c857b93b530ebd8c0cd9d540e5914aec8f9b12880c02/wrapt-2.3.0-cp310-cp310-win32.whl", hash = "sha256:e045ff75d7d94900fc32896ed93c45ce2d2cac28c9dead582ff9a5a49d446e35", size = 77879, upload-time = "2026-07-28T06:04:16.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/eb/921405b4dc55d4f8be4c700ef120539fdd75d5fdb50d83bd257171ee18e0/wrapt-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b4fc96b159af0a3e0faa72475a69d66292bea72a5bed1e1aca1bffbddc3cb2b0", size = 80733, upload-time = "2026-07-28T06:04:17.43Z" }, + { url = "https://files.pythonhosted.org/packages/b6/13/75947450c5bb57795fa86384721cd52c5c4deb0879022f309501a8a85d44/wrapt-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:1236fa25173ca964c97422470482e9011b9e3c7ed0d75798b40b3da3b0e0e760", size = 80199, upload-time = "2026-07-28T06:04:18.761Z" }, + { url = "https://files.pythonhosted.org/packages/00/b8/9182e4c618a847be0baccb68e4602b070d0fa22c782cf058f4bc66b32709/wrapt-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ab559e1b2551d23d54db2a0001c6d73bad022a254639561c5f6c382a9d6c2fe", size = 81427, upload-time = "2026-07-28T06:04:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/613cefd9c5977366b1587e61c0b428176d382e6d75b454084c5e58503042/wrapt-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bff9a671bc00709cab5a7f745c592b5671873449db0ee2a569af994f16b29a4d", size = 82360, upload-time = "2026-07-28T06:04:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/71/71/4cd2151a236f44a6e2dd4ed8011838d7ba0be3d656c8bafdfc65a2ed1917/wrapt-2.3.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fc648a335d7e01adb3640b25f02fd0ea05886cf04d0af7f4ee902bc7b5e466e8", size = 161700, upload-time = "2026-07-28T06:04:22.723Z" }, + { url = "https://files.pythonhosted.org/packages/49/2c/bc508fee75eb2919ed69769800b09968e4aab16897f909a23f39c81e323f/wrapt-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0077f3d65541925fa83002f967b22ad6550d24813ac64cb905f717194128d9c", size = 162922, upload-time = "2026-07-28T06:04:24.177Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e5/04f34d38e66d857dfc2fc4088d60e70c0e422467822defa49b2b4a26e17b/wrapt-2.3.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9790ea25190a4e0fe4cdf4eeb868e9d75f8a024a70a5b6bf9c348a3a2b72e731", size = 156125, upload-time = "2026-07-28T06:04:25.58Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/c35940ea1c423f129ebe4361db853bc80d4def6326242e1206fa15bf94f4/wrapt-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:816877aa749253149f9ecfd2635d4d948ecfa338e1a0311d187b1acb1bb8a3eb", size = 162039, upload-time = "2026-07-28T06:04:27.154Z" }, + { url = "https://files.pythonhosted.org/packages/0e/60/9bda34c3d7d182aa703fe35339ae0ed4c4dad5e5c587f93890143e1f87fb/wrapt-2.3.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3d1c2c1b808600d2ea808e6360910a60ed5f409a4011655e10f9164ba0a414a6", size = 155110, upload-time = "2026-07-28T06:04:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ba/60bfd9b1a751f4fcb2d603668fc272d651ccdd339a56acf8c40ad21a0293/wrapt-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5ba1e5e08ddc46130e9682b2c249f2d1dd39bda9106ed4bd401b7519f18f41bd", size = 161089, upload-time = "2026-07-28T06:04:29.959Z" }, + { url = "https://files.pythonhosted.org/packages/0f/32/2bd358c6f4f1305c813479d1e9ba746bebdd794f4a20107ab2b3ee0cbd45/wrapt-2.3.0-cp311-cp311-win32.whl", hash = "sha256:45c9279b373d15649dfa2c2077cb3408ea1a6d3125afbdab9d6b809a66f68e14", size = 78030, upload-time = "2026-07-28T06:04:31.241Z" }, + { url = "https://files.pythonhosted.org/packages/4a/62/ecc969b13b141fef89b888c9760821cb01a86ac8fc953911592c8e1e1522/wrapt-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:195b1842b4122fb54e3cd3dd5b2b4aa49302a5a61da901df0481f5c97aedde84", size = 80944, upload-time = "2026-07-28T06:04:32.655Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3d/9278ada8a2b3f24372b630361e84e9a7de7abc3784634860c26d1c37785a/wrapt-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:6db604ef0c67bdb2042ecdfd7b7f037cf09733557ca42360d1018285634f7b98", size = 80074, upload-time = "2026-07-28T06:04:33.811Z" }, + { url = "https://files.pythonhosted.org/packages/5b/4a/d17a0fad1bf1c5f2c887ff71fef75654141b0880bff71d157d955b5bec3a/wrapt-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a45ffae742ce91a16e11cb6c7cd71e7f9994f3cbd283b962ab093f5c6dcf525", size = 82139, upload-time = "2026-07-28T06:04:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/6e/55/51b92daaf6defb57f4dc56bdcce985400f75c6984a03ca5e78ccac717028/wrapt-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:69e477046f2237ef0bc6547544ee73008dc764ca26eff44f09e976d221b34d5d", size = 82723, upload-time = "2026-07-28T06:04:36.502Z" }, + { url = "https://files.pythonhosted.org/packages/28/7f/cfd9bc4b1f5e424eeea83d0493e43f3b1b02707ce8e50c47945873982bd5/wrapt-2.3.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d221a6e6ddd302b8397433184e96b59f259f50024b854db1c411a881586b6b8", size = 172381, upload-time = "2026-07-28T06:04:37.674Z" }, + { url = "https://files.pythonhosted.org/packages/cb/89/ff7814f6eb6856b479946117d1138a2fbb46cdb6b1f379db359056c69743/wrapt-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:392158c9a7f2ab1b8699418bfc0fe6f83548788c418b27d7bf2019ad3405cebb", size = 174120, upload-time = "2026-07-28T06:04:38.987Z" }, + { url = "https://files.pythonhosted.org/packages/12/1e/8eded8615d39e3ce81f626937a3a87b280a2a86239a2bf14a4b4bb345034/wrapt-2.3.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e5301c35cf75655eb33498f2bd6ae8703ca19940e3167dc9cdf740c712a39c60", size = 163035, upload-time = "2026-07-28T06:04:40.361Z" }, + { url = "https://files.pythonhosted.org/packages/35/ea/a0af2d9da62897af2a055484920de05dade30d2ba2c0d65cbdea875d3d8b/wrapt-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:418f54bb09d1762db02c7009b4051149893af3153a87f92d70356703c11eea02", size = 171887, upload-time = "2026-07-28T06:04:41.614Z" }, + { url = "https://files.pythonhosted.org/packages/7e/dd/63cd4c864c65ef4906df64bd2d378f4a62b54f28063f282dfb3bf93caead/wrapt-2.3.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1598becd30f8f2777d18564064eb4f4dbe1ab0e05a8f09786d0ef505ac782bf3", size = 161113, upload-time = "2026-07-28T06:04:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ee/82f1fc9e431b5c2c5a6d201aa865dbeae3984c311c6d11a185f0c8367cf6/wrapt-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3da470536bf9645143323dd41b32db55c6f4304ad382094c1a1da8a92061e10d", size = 170530, upload-time = "2026-07-28T06:04:44.212Z" }, + { url = "https://files.pythonhosted.org/packages/37/a5/5dc590e863a419930d988f8b7ca3e75a6befcfb10b6003b3a152f3d5f732/wrapt-2.3.0-cp312-cp312-win32.whl", hash = "sha256:fb8e2e6704a1e0b1b989546c69e2688371ef4a07fa5f61bde3eb6211186f5ac1", size = 78323, upload-time = "2026-07-28T06:04:45.484Z" }, + { url = "https://files.pythonhosted.org/packages/51/f9/4a6925a07951df56394f7e6ebe14f69f1c5ef9d87aa63e0839acf15aa63a/wrapt-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cdc021cb0b62471d6aac7f2bd92f3b4658073775f9ee7fcd325c511129e7bcc8", size = 81180, upload-time = "2026-07-28T06:04:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4f/8b5de0395b2a72216751d41c9861df6facaeb611b619d8810ed2b3b23eb2/wrapt-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:67bfe2485f50368c3fcd2275fc1fd100e350d601e0058921a7c82678a465aeab", size = 80155, upload-time = "2026-07-28T06:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6e/0f88a072483e76b881e3fdcd6b6ffb4a5791002514fe541e72b1b73c859a/wrapt-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d3fb71e65b001adfc42684522eeccd9c21d8ba679945abc993439567b66e59f", size = 81960, upload-time = "2026-07-28T06:04:49.622Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ff/b7e2776e7c294075eb712cc9ef573d1b818f393006d09787262b8fc871c4/wrapt-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51a7a4181c1295774812271fbcd7c909df372bc25579d4ed9eb875caaf0ae86f", size = 82435, upload-time = "2026-07-28T06:04:50.9Z" }, + { url = "https://files.pythonhosted.org/packages/d8/90/343bb5d0f1f9669bc252a6073f085b4abf862511bd5c9c9eaec754341f1d/wrapt-2.3.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9045917809c63fdf7abe3a2ceaed3d670b8ee4500ddd9291192d30aeb34467c5", size = 170350, upload-time = "2026-07-28T06:04:52.187Z" }, + { url = "https://files.pythonhosted.org/packages/59/f8/13b79a392930bd0dd6b86cbfbfe1c40944110456e1dc6d809e5c46ece904/wrapt-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54ca1d5573f69b5fe1d74f1f65799c68015e82f685efec9fd8cfa40a094c44d0", size = 170022, upload-time = "2026-07-28T06:04:53.599Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/4f1b6918f5290db959d6e0c07f77385d87cede29c39c9cf8f145e9c82954/wrapt-2.3.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:242b60c21e30866e6a2fa606c612b47c553fa60c0eaeeeb7797fb842ac0ce609", size = 161043, upload-time = "2026-07-28T06:04:54.936Z" }, + { url = "https://files.pythonhosted.org/packages/01/e1/45d3cf74414780bdff6d0380467e003f6eb0f028b6c9403db868dbc7209c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3f3d7ec0a51fbfe00d3aef047641ff2c58b25565b4717fc1f90e050be01cba8", size = 168576, upload-time = "2026-07-28T06:04:56.261Z" }, + { url = "https://files.pythonhosted.org/packages/f3/73/2fa58dd97f191c997755e2c6d569a68f0c433db4e4b36099bdd7227b6cac/wrapt-2.3.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:261f53870cd4fb2bf38f9f972c56c728fd224cb7c65721307de59d9e7e6741ae", size = 159140, upload-time = "2026-07-28T06:04:57.754Z" }, + { url = "https://files.pythonhosted.org/packages/29/a8/08a56e2000a8816d449dcbad8c8b081697acbbd490821ceca0f9d8e8d20c/wrapt-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8159ec0b0cb7608175eb150de94c19e34f4d47ac655f5ca9baf45df6b688ffd3", size = 169263, upload-time = "2026-07-28T06:04:59.161Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d4/354e1725e35a73b2af4fa70a3e024c7a5d1bf1802dfb862dcb668aae0253/wrapt-2.3.0-cp313-cp313-win32.whl", hash = "sha256:10461884b3014fbfc8eb7d09a93c5f246363e6711d9d881f95eb8c27fdef049f", size = 78241, upload-time = "2026-07-28T06:05:00.507Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7e/34c87fa2174848dfee820322aaa318bab08913998ccecc8d2f57b4ad4639/wrapt-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac870cc97b73bb00ac353329e9559a4bebc47c4c86792ed9b23b58c15b6ad838", size = 81113, upload-time = "2026-07-28T06:05:01.839Z" }, + { url = "https://files.pythonhosted.org/packages/11/86/fcc9a530579e008c9478bb565a6cdfbfd33536660f069c8b91a6607c5050/wrapt-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:a65e8db2b4e90c2e7ade931086351c98ef420bf7a94ee08c95ac8a3cbbc43579", size = 80182, upload-time = "2026-07-28T06:05:03.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/50/3864848b95b28ef73e17551fc8dccbff2628a834f52cf26a57f9c419fb83/wrapt-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:fd1f2f557dd3491fe75905e578f4db967393d40d1a8f468edc4d40ac7f2d5944", size = 83921, upload-time = "2026-07-28T06:05:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/3b/4c/3d1921a60c3e8c71c540ff136e6a47a1fbccf7f671e818394889f7871d9c/wrapt-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9f5d2aec29dfc76c37e23897dee92766a3fd4f3bff3ae7fc9c6b4bf37d8c1360", size = 84412, upload-time = "2026-07-28T06:05:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/fa/1a/4a796ff7adb26ada6d4b758c94d47a38320b085e7099afc088efbbcdb006/wrapt-2.3.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:646d20d413ffcd1b0a2f700076e2d0252d872dcb7754860a73e45a59ea883614", size = 207168, upload-time = "2026-07-28T06:05:07.256Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3e/d7777776806c579b761bac2f91721dda9f04c7a1b380213c5935cc750ae6/wrapt-2.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:379f670f45b7bb8993edd9f6fc36c6cc65edb81cffa0b504be34acb0303fff0a", size = 214351, upload-time = "2026-07-28T06:05:08.945Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/2d64d394df7bf181955b3bb562bf33c4492fb4be113f53071106d43ad8b5/wrapt-2.3.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6208f302f110295d64b22a7ac96500c791bf492dce4366e622e4912b077c9687", size = 199020, upload-time = "2026-07-28T06:05:10.418Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3d/fb31d3db7d9834d265fb1a27a2adf0ddf51557c67458c97b22439ad6ae3d/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ed635a9ca4f3a5a2b900c10c69e823373bc00ebc114b459383596d3487da3570", size = 209969, upload-time = "2026-07-28T06:05:11.983Z" }, + { url = "https://files.pythonhosted.org/packages/1f/d1/8724b5da582e62070dc9bf4d8bf1972f317297eefd7ba1f2b5c6393ccf6c/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e3b9eaa742ae7a0aaaaad4ca4b69469d757af2d6e6663ef1dadc47adec0aeb41", size = 196324, upload-time = "2026-07-28T06:05:13.557Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/3d9ef411149543016ee6bcf3af707f787cebd946527452b94bf122e9b7b4/wrapt-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d0f7284f88f4833705132d06d3b425a43095c2cbd07c58166aac3ab646ba12a4", size = 202610, upload-time = "2026-07-28T06:05:15.048Z" }, + { url = "https://files.pythonhosted.org/packages/13/9b/4fc042ceb757866dd4a5fc057b3b736f2b360d3703ce9f830d83dc9226e0/wrapt-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:7ebb274aba688b043429eb1500ff8a76ce0cb8ac0812ca3e301f06247b8722b3", size = 79178, upload-time = "2026-07-28T06:05:16.469Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/b94878f8eed809ca042685276bcea9f24e8c2ca7c9653bb80bbb920a68a5/wrapt-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c4bded758ad6f03b965830944a2f0bc5b2eb3767fe5a7310134315d1a6610e98", size = 82634, upload-time = "2026-07-28T06:05:18.026Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/663e1de5332a71685a729754312d327d4cada767c36e1c5a2db4c8de49e6/wrapt-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:d2cc64539da63e39ffb9c7ede849b6e8ddaaf7b3876b5cfb04efd85a5f3f4eb6", size = 81387, upload-time = "2026-07-28T06:05:19.417Z" }, + { url = "https://files.pythonhosted.org/packages/58/10/b073beaea89bc0d3670a75ff51139430a54b6af7ba7796507730634536dd/wrapt-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea52a0d0f08c584943d5764be0e84efa912c8da23c23e1e285ff2f5641c18fcc", size = 81978, upload-time = "2026-07-28T06:05:21.133Z" }, + { url = "https://files.pythonhosted.org/packages/b3/31/0916d9cebf848ed3f1a0c1888faee421747df77331e4db2bc527a9a85988/wrapt-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd85b0aa88efdb189d6ae2f35f4526943a8f091c38599c9c31478241c819e6a1", size = 82518, upload-time = "2026-07-28T06:05:22.562Z" }, + { url = "https://files.pythonhosted.org/packages/f5/73/31c1bf0f3384062751c2094dadb314916d70aa9b6bfd26d994b4a7b393fa/wrapt-2.3.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:141ed6211286a9660d8d6702de598b43f0934b4f0eda16393f100a80f501d945", size = 170187, upload-time = "2026-07-28T06:05:23.904Z" }, + { url = "https://files.pythonhosted.org/packages/ed/25/fce087d54b79b8905f3c3c9dd5f454bbd8d8acb80b960c4a6aee5b4659b3/wrapt-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e49885a62ec4ee854d1b9e6371fda6afd219917225752abf729a3f36d4df9a5", size = 169288, upload-time = "2026-07-28T06:05:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/0d09e6dddc6b7a7230ac77f50254b5980ab4fcd22976f72f8cc8a0404458/wrapt-2.3.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d6159c9b2fefec02314e1332dbbbfaf960e369dfd26bcf7f8b258b5732065b3", size = 160932, upload-time = "2026-07-28T06:05:27.022Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ca/0913af0d2ec0c43865d32d615f518fea66c13c5c930e489e9b0de248e9a8/wrapt-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:24da48596326ef8e448cfa837b454f638713d3531262375f00e5a9681682fc07", size = 169017, upload-time = "2026-07-28T06:05:28.501Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f2/3d1e47ea81b822210f5df1bf942fd90780a75c055243d569b664529dea88/wrapt-2.3.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cd3a2edf0427013736b8127955cec62608c56e53ea47e82812ea32059cda407f", size = 159065, upload-time = "2026-07-28T06:05:30.01Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/ef2066ced8e5fca204e2b361e9708e36555b40949c583d997ea3b590817d/wrapt-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0df3bff4e7ce45759f33fd39335fe2f60477bb9ecf7b8aa41e7d07ee36a23", size = 168821, upload-time = "2026-07-28T06:05:31.649Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e1/016104650d4e572fa91506eb396b3dd8efbccc9284fdc1c9479c3d21db28/wrapt-2.3.0-cp314-cp314-win32.whl", hash = "sha256:2935d5454b3f179a29b12cf390ee47246740ba2c3a7545b1b46ba31a5f2a4a0b", size = 78700, upload-time = "2026-07-28T06:05:33.391Z" }, + { url = "https://files.pythonhosted.org/packages/3d/97/6fdc20a9f2ca304748b3f0819cbf377d55260562777bf0b615431bc3c181/wrapt-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:cc2cea812e5cb179a796b766747e7d3b21088760d8deb95676d482b8c8e6fa7d", size = 81422, upload-time = "2026-07-28T06:05:34.774Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a4/9cbd53bf05746bea2c392af39cb052427a8ec95cbd494d930733d8f44681/wrapt-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:22cc5c0a717bd4da87018ae0bffd4c19c6fb679d3ff357216ba566ab26c76cab", size = 80639, upload-time = "2026-07-28T06:05:36.228Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/6c5e4a0f66ea0d2b2dd267e8dd05a0014eea56840b3c8595d40b0a5d1f91/wrapt-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a6b5984cd65dd639546f0eb4b8eacf1c31cb2fe9fb5c27bffe240987cdb2cf84", size = 84030, upload-time = "2026-07-28T06:05:37.714Z" }, + { url = "https://files.pythonhosted.org/packages/6a/eb/a1aedf03283bc9cbf8a1783995ddc54e3c5a86878f19002d2c428494f4c5/wrapt-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c88abcf53daef80e01a75c7530e727fa6e2c1888fe83e3dcdba4c96216a1f5c7", size = 84419, upload-time = "2026-07-28T06:05:39.131Z" }, + { url = "https://files.pythonhosted.org/packages/63/61/50d511c0dc5105563849e86daa3e16ac7feef699f79fb05af45ea70107d5/wrapt-2.3.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:85de890ff968196e92dd1ae73a9fb8970495e7650a457b1c9ef0ac3dd550bce2", size = 207171, upload-time = "2026-07-28T06:05:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/3f/59/9b538cf7795217e810699d16bc88b96a830d9b5c403eb2ec2db6b5f2ae81/wrapt-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50f416b74d092bb9f41b424e90dd457f365f7ba4b11de62a23679769a21bd85c", size = 214329, upload-time = "2026-07-28T06:05:42.287Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/9935d62b1499e5c8b3d191e99ba4eb31ca237a0b699142011a837e9dc7ea/wrapt-2.3.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:39febbee6d77301d31da6996b152ce52452da7c7ef72aba10c2fa976dff9c295", size = 199079, upload-time = "2026-07-28T06:05:43.958Z" }, + { url = "https://files.pythonhosted.org/packages/2b/01/4446b80fa2ffa47a3449b250d004ba1c1937f07f64a179608fec735df866/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:93513bec052c6cd987f9f580c3df068c8bc4ebae6543736be3ca7ec5959cafcd", size = 209992, upload-time = "2026-07-28T06:05:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/d4/07/56f26c9f9979586a021e8148747004aba4498f49458c90b0502969b904e1/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:729126e667da34d251b8ebf8a45ef0c5ddadc21542b3d6e1abf4259ece6508df", size = 196334, upload-time = "2026-07-28T06:05:47.608Z" }, + { url = "https://files.pythonhosted.org/packages/8b/41/6d7bcc895b0f28b2250e10908f060687b9165429dcd7f22ddb3d4c031b74/wrapt-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:626b69db2021aa01671ec7bbc9740e558522bd44c18cf2ce69bf3d666a014109", size = 202644, upload-time = "2026-07-28T06:05:49.183Z" }, + { url = "https://files.pythonhosted.org/packages/cd/25/7860927edba06b758b8852a6f02e832be715563c67a6795d94350bc81099/wrapt-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:629d73378082c00a8173031f9fb30a3ac6abbc894a5bfdfae71fabc60642d501", size = 79685, upload-time = "2026-07-28T06:05:50.976Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0f/270bafe92fde3b069a39bc01e39ee79340895b335640df861d43d2a51885/wrapt-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:42869085687f0aefd57c0f636c3f9354f8ffb321a8ba9cb52d19beb796e561c5", size = 83104, upload-time = "2026-07-28T06:05:52.405Z" }, + { url = "https://files.pythonhosted.org/packages/55/b3/af176d79a8515a8a720eccdad9a96f6e31a30abf2865430c8c42adf2fd13/wrapt-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b1e5aa486e269b00ed35e64771c7d0ab8096cfd2643405ca8cd60ebedc099a51", size = 81774, upload-time = "2026-07-28T06:05:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/00/39/3daf9f47be208606586de4568ba6713db53ebc8fd7a575aea1fe57983b69/wrapt-2.3.0-py3-none-any.whl", hash = "sha256:d8c7ed08477429752b8c44991f40ad7838b18332a160698740a6bfbc10d998a2", size = 61866, upload-time = "2026-07-28T06:06:12.9Z" }, +] + +[[package]] +name = "xxhash" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/a5/1386f35da1475fcaeef42581deae73417c6d2a6a0b2d2e8914de18844dcd/xxhash-4.0.1.tar.gz", hash = "sha256:d55bf4ef10eb09b8b6866790e083d26d087d84caa3cc0946ba87c3ca7ecaf7b7", size = 101513, upload-time = "2026-08-17T08:24:08.557Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/de/6854b311cb468985de555e49717357ba1de8ffe4b08eda3f4c522a418fda/xxhash-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3f68fe400ceec235f3e4a4b02a28c2fd2d283584a193223c921dd4c48f1d0754", size = 38470, upload-time = "2026-08-17T08:20:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/91/2a/7d79952633fccbf9c2a59cc1dfe9d073f9edfceb052ba9bff5075e31408d/xxhash-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1dddc257279417d93c9e59420d49ef90aece90d7a01996db3aade74b0281b1", size = 36229, upload-time = "2026-08-17T08:20:10.259Z" }, + { url = "https://files.pythonhosted.org/packages/70/6a/6a6f4220c39bae1636eb3f8ef217359b8afe268493ee858adf9002130257/xxhash-4.0.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4972332c079d6aad69c4620a68d015a4ecb33141583f70d642cf9edf6a713763", size = 243168, upload-time = "2026-08-17T08:20:07.211Z" }, + { url = "https://files.pythonhosted.org/packages/c8/03/a1d745b324bede7af8a274d889d25b758402629695499a957375cc5fe994/xxhash-4.0.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1b603d0686c99fa0879f104a74e7db58367634c6e50ba827bee9aa095e23205", size = 266061, upload-time = "2026-08-17T08:20:18.253Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/55c7565db23d2d29a2e31ea9dd0001ad3f4729298f42ba32967dde7644ab/xxhash-4.0.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:33fd538191f47071deef6b1f676535e2aa770f1fd150ae4cc75a34c9e930be3d", size = 287357, upload-time = "2026-08-17T08:20:08.901Z" }, + { url = "https://files.pythonhosted.org/packages/72/31/c967c98cbf151df6ae8756fc9821b1f78d1feffa8420ff2ad5645f27f98f/xxhash-4.0.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33e270d302c95ec426dfa0f5a4e16bff2ab8d7b8a46faa4746affb05e684ac77", size = 270683, upload-time = "2026-08-17T08:20:10.943Z" }, + { url = "https://files.pythonhosted.org/packages/70/98/df9f3c14bc2fb2712ed1497349dc7e8c0b0f20a1f353e2e146d83a1adef9/xxhash-4.0.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3fb1d30d4b6d6e2c4a08e5ac6fffdb2b572d2cfcca15a5509cf4e7a1350f955c", size = 497802, upload-time = "2026-08-17T08:20:17.217Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3b/e5d20b4f602d15fc5fc066f601052a7d6f5a401a50d67fac92098a5829a5/xxhash-4.0.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e27dbed5c4ba033919e4b4ed8dc14e029e91d14a93cd9f920d25277c7df6781", size = 249503, upload-time = "2026-08-17T08:20:40.14Z" }, + { url = "https://files.pythonhosted.org/packages/39/98/4cd03fa7ba791657aa555d0655e89a0d66193b1331e4391114b50227f80f/xxhash-4.0.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43bcf2a871f28f16135545415cab3ec43904d4c80425a64598a9e6cebfb2b5ba", size = 333384, upload-time = "2026-08-17T08:20:18.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/67/a0a076dbcecad2af0cceb873ba4f95475f3955fb81d82f9c5e9f97a12854/xxhash-4.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5dc434c946012e6d8a72b10f970ea30755b718251dd7591dbfdabafd3bcb21bc", size = 262463, upload-time = "2026-08-17T08:20:08.719Z" }, + { url = "https://files.pythonhosted.org/packages/48/94/767b68736250813923d74703ef628b0f57ff1c5bf690d7a6082928d1a14d/xxhash-4.0.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0b1082fd0f089ce9098ed77aad8b777b5d156f8ac601c69cab73811822b8ef07", size = 289405, upload-time = "2026-08-17T08:20:35.493Z" }, + { url = "https://files.pythonhosted.org/packages/ab/61/69d9ec6e85f9933f26b39753b404f97ffd2fc3bfeeb5e26333752595e060/xxhash-4.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd649663ddeafbfd4734eb8abae921dd5baa1242f20bda54e8bc927369ccded4", size = 247989, upload-time = "2026-08-17T08:20:10.759Z" }, + { url = "https://files.pythonhosted.org/packages/a7/66/e6a033879893074bc441faec65587f9ef9cca49ee3cb99ef20c699b014cf/xxhash-4.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:97b455de3e8b1b0b1e4594cb61a468992563f03ca264062fbb0a66b393c01d90", size = 267987, upload-time = "2026-08-17T08:20:20.947Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/a99a412b010cf2cc2bd38a0a44043c894ffa80b46c8d4e1522e40d2ef401/xxhash-4.0.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:2194bf96d5f3d4e0cb65deba370ec83dda3edfba42155f9384190ed5e51ea5e2", size = 322541, upload-time = "2026-08-17T08:20:22.675Z" }, + { url = "https://files.pythonhosted.org/packages/0b/12/1a91408e66d1716bd278cf55529d4ba251d90c6b694ea78a09dcbd9d36c6/xxhash-4.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:84df5f8da574caadbc0cb1b8866ecc2368cc941f0cd05f677756c802f370dafa", size = 466264, upload-time = "2026-08-17T08:21:12.422Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c8/8abd4c1b90962f794ed9b139767b0a971e5168e8393961ccf046956c0039/xxhash-4.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c09ada495567c9c9a8156c5ebcfb93be7fece0755062d738c972dcbecd0d84b5", size = 246149, upload-time = "2026-08-17T08:20:23.385Z" }, + { url = "https://files.pythonhosted.org/packages/22/3f/e020285d737a712bbfcf01dfdbbec7773c7c8289447a5373540caf2cdf5f/xxhash-4.0.1-cp310-cp310-win32.whl", hash = "sha256:85bdd40cb505a11e0ca04191711266c5fd696ed786ae83849955e457774edc96", size = 34630, upload-time = "2026-08-17T08:20:10.001Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9b/4e8384b299b40664a225ce9deef54c42b3d59f5181a16d4a374ea3503f8f/xxhash-4.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:8ec4777d92fd61a5c8fdeddab894fd65bea301a8092fb5419ec6472aa4d458d7", size = 37058, upload-time = "2026-08-17T08:21:05.846Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e9/d73c870d9dd26f8d6782ecf716b31e836bb6269c888b6bb6e1fcf71eed36/xxhash-4.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:03600a8987849b2bef7be795a60a6052b635c63fa98b718b08ca5ee823691cfc", size = 33289, upload-time = "2026-08-17T08:20:19.707Z" }, + { url = "https://files.pythonhosted.org/packages/0e/58/bc81e25cceab76ce4b400441e3a43312bf3887fedbb2e5f80cc5a7dd7f75/xxhash-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8b4477edc03091f51f5309406d230851c23cf4822029e3bf40b8df53093fff1c", size = 38473, upload-time = "2026-08-17T08:20:34.303Z" }, + { url = "https://files.pythonhosted.org/packages/64/8d/d95e810c9a2930906f1fbd0e38f77554abb8e042a190aa9cbb24da39e9a2/xxhash-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:04f9a24de11a6647666d5302fd73d6a5224ce50ddc965fb0bb44cee736e6bd7c", size = 36228, upload-time = "2026-08-17T08:20:36.641Z" }, + { url = "https://files.pythonhosted.org/packages/72/be/ebcded2a32ba664a17a1737a91b6baa298bcda6942acdad81af81660b74c/xxhash-4.0.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8c5ce76b94ba49f3be8a8f2611abc6564210702c72ac9e237ca2bebfd17794", size = 253292, upload-time = "2026-08-17T08:21:27.206Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/72d31d787d988d1130253bc34d249c553f02c9387e7dda789b6d5aa7963b/xxhash-4.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4c8842fb19d78b5e8c2a52baf4c8357658cc56c62bc822b86ce0f942f28e286", size = 276545, upload-time = "2026-08-17T08:20:26.726Z" }, + { url = "https://files.pythonhosted.org/packages/39/ae/048f3b1f283a340bef3697fe5a9d4f10de1696a379af9af6b2352c24d82a/xxhash-4.0.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a43418e1a90b4809a9caf64aeb8b0696e3e1f300a323acc1e6ee2f93ae319fcf", size = 296295, upload-time = "2026-08-17T08:20:15.759Z" }, + { url = "https://files.pythonhosted.org/packages/8e/43/e9a593c81445c8e8d669402edb8264144624e7e5e2406df7d33e3c164d90/xxhash-4.0.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b3662719007e059abde7eddacf8517142ba076ddc7b30c807260e57d28c3c191", size = 279966, upload-time = "2026-08-17T08:21:22.217Z" }, + { url = "https://files.pythonhosted.org/packages/f9/4c/29da2b166955a300521c0abd498ad4481916c3743949b106192b781f58fc/xxhash-4.0.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06d7fbd609503c3be5e65cdb6bb2f040d6a98574404e2e1d5c60815c97fff4aa", size = 509850, upload-time = "2026-08-17T08:20:32.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/ff/e39e1900179ce7f0f23e7000d9fc672471a8d05b6b2dc657cb496c8975d0/xxhash-4.0.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:101aa300de6ceef3d9c77569706330d8921fc45dd82bceed2084f1e9f2557a24", size = 261005, upload-time = "2026-08-17T08:20:40.735Z" }, + { url = "https://files.pythonhosted.org/packages/89/79/76ee26720d13219458f4b2ec0b22f539fe2bcb1f83dc22a24be4cda4e285/xxhash-4.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4296fcc790876a8b0f297edc83d3b088457b774d8f67b4636807f8a2ec69a79", size = 339620, upload-time = "2026-08-17T08:20:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c7/56b53252d64ecb2f3a0d283b41033561b1bec9c268095150153b694c2e52/xxhash-4.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:57d7fa8f23908d173001c21a9e82bfc6ad997d1b6c270fb121812b7ed158891c", size = 272558, upload-time = "2026-08-17T08:21:33.883Z" }, + { url = "https://files.pythonhosted.org/packages/fc/76/83101a2f2ad3eb6b4b5d571e0aa394a576e3e23121707d507d80197ac385/xxhash-4.0.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85e402dab0f9acd3604539747c6fcc57dc188a18af6ab07eb8189351cd32466c", size = 300417, upload-time = "2026-08-17T08:20:36.183Z" }, + { url = "https://files.pythonhosted.org/packages/34/5d/5be1166ec4fc4bc896e508dc51189a2dad200b373269a430e214fb152693/xxhash-4.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fb59a0dd61fb2ad481c03fda399d78ce57dab6bb62c2c8fdb446a7ba4754b89a", size = 259286, upload-time = "2026-08-17T08:20:21.51Z" }, + { url = "https://files.pythonhosted.org/packages/77/6c/42f6201f13278787c58a6b3a1cd597b47e8665a7d4f68a3440d001c05a75/xxhash-4.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0b20a06454b34f1531fc677c54efe2ecdec691ef9224f7fa919bf2c1363f7ff1", size = 278230, upload-time = "2026-08-17T08:21:47.62Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/3cb7f149a5a469c628604c33bec6d79764698b315700a4bbf1c6fb15622b/xxhash-4.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f7db035447a0ac8959aa230c5d36545ecf9f547413eb1711c0ca6f0ba1418925", size = 329846, upload-time = "2026-08-17T08:35:10.922Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ee/934eb1e11f4d0e95f3828825f8da4b6d59e338235d63edc3d929769262d2/xxhash-4.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:34ed93e20bfd98d722b902121643791eeb4b1641871e2dc63d0d4c2d93f187df", size = 477285, upload-time = "2026-08-17T08:20:46.297Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0b/77835dcd7aec970db74f5724c94207ada83b3e2f5e1474ab44b9c8fe5ea5/xxhash-4.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6247f5e23ee94f2557ac9dab738a336f607c6ff476fcf66ca70c3aef5eee15a", size = 257552, upload-time = "2026-08-17T08:20:58.211Z" }, + { url = "https://files.pythonhosted.org/packages/da/6c/a3ce7a1a9c4ec9ad8babba591a996a71c474ff9630c5fb04c8c9d8b995ca/xxhash-4.0.1-cp311-cp311-win32.whl", hash = "sha256:348c8f288dc961d6bbd1985c8152a3ed7a85c95df00e82320f0c5215d922a399", size = 34630, upload-time = "2026-08-17T08:21:53.323Z" }, + { url = "https://files.pythonhosted.org/packages/d9/58/60d2170e8cda0891aab25dcaa74797b420c3c6cde8a5bc8372f17b30c0cf/xxhash-4.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ac0f291ab6485bd71f33941f9b92771318332a05d505460b41e893a549caadc0", size = 36992, upload-time = "2026-08-17T08:20:41.973Z" }, + { url = "https://files.pythonhosted.org/packages/ac/de/b229a39f9bbbe30cbcf9afaaa8993cb65286715c90a3b058c3769196ae02/xxhash-4.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:72f34834518157a75e7090f328ee7a16c70c804cfc7c694fa069cc888e9fc03e", size = 33289, upload-time = "2026-08-17T08:20:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/26/6c/dc7cffeadd06336cd934947187cd38abb263103bbc552ca0f55fe4ff595a/xxhash-4.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1ee523f51718e41753f04f7102bb4dc55a18d2ea5cbaceef8ec7ca08571bd428", size = 38444, upload-time = "2026-08-17T08:21:54.332Z" }, + { url = "https://files.pythonhosted.org/packages/75/c9/cf736f6db8c3273af18925061572db0d4357818a9ce425f4b5fb0021918e/xxhash-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:515a822c73abbf6a0b7c70976d9662be342835c9d78b8dc7c023411f39c35dbc", size = 36195, upload-time = "2026-08-17T08:35:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/da/a2/ca1929354b6851529d0148f7f335b5e2b0281f83bab3e19f0896dc579796/xxhash-4.0.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f5d031f35962e5483a613214e61f09fe24ab523062c3646d592dc16c4a217451", size = 253113, upload-time = "2026-08-17T08:20:52.152Z" }, + { url = "https://files.pythonhosted.org/packages/de/bb/542005206af59518bc8d78a210f1e0172217bc53beb32f64a5b632e72b6b/xxhash-4.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da0264844a09b538c894e5eff25313d941deb4dedec2131b98418a71a3c9944e", size = 276525, upload-time = "2026-08-17T08:21:01.886Z" }, + { url = "https://files.pythonhosted.org/packages/1b/df/607cff25dcb0f1d35c3b04493f6ad8471edb03fd4eacbdcc5ceddef1f3e9/xxhash-4.0.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1642907941ee4b75aacc3db688af52ea02ca2305ab22af7ee686ed726b332684", size = 297703, upload-time = "2026-08-17T08:21:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/15/ba/9d2275eea0b9d9c6b02921be23f7588356c60df95c763b25f0e045894d43/xxhash-4.0.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4af350bc3f329970c0e3a59af84a8a30998bf8a9167eb50cd48e59baaa1d7bec", size = 280252, upload-time = "2026-08-17T08:20:47.299Z" }, + { url = "https://files.pythonhosted.org/packages/1d/aa/2299d9f6369e550aef2abb64945e39daa34412725aa46a20d99b74d76f67/xxhash-4.0.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8ba782ca3bf1e81492611152b9a0d5264971339e95e34d69de0ac2c926be496d", size = 511041, upload-time = "2026-08-17T08:20:36.771Z" }, + { url = "https://files.pythonhosted.org/packages/83/97/31bd8b8279e6935a0719f6910ced15e9d5a2cd554b253f6027ce1b5a1c2c/xxhash-4.0.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:237b8f63a2a0fcfb1ffc06e21dad23add44e6d354b2b014364a1d41e419a4dee", size = 261812, upload-time = "2026-08-17T08:22:00.469Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/d180a2da23c105d8e0b02d54f9f5841013fc81c233010ec781e31f1aee4c/xxhash-4.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:81507a68ba84c55241fb61cce1469f473a5da4205fc8ef6f698e5948eea8dd88", size = 339878, upload-time = "2026-08-17T08:35:17.626Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3d/f584cd3172fe934f0f5a0a3917d0d7ce781f74d794fd43bb72be71c3ef6f/xxhash-4.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1ea31d61bcd2cd2f3ec4ca80a64187bbd7948f490b63cf0dcbc6e717b4c1e9", size = 272871, upload-time = "2026-08-17T08:20:56.067Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/2c7956b2b551682e00b9aebce9ceb0a991a131d65f9850c09f5f9760be2e/xxhash-4.0.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:06713a5aaf1d0905c5579416c020c02e42b3ceb931e86c7d3b7fb85403dee3f3", size = 301440, upload-time = "2026-08-17T08:21:35.911Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/0739f6482184a8026f4b022718f5f815d352059312e80696825433f0a8e7/xxhash-4.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8cda075b10bb3917b002c74a04f9e02b7d13b5bf732571404d51c52b11c7329", size = 260157, upload-time = "2026-08-17T08:22:01.416Z" }, + { url = "https://files.pythonhosted.org/packages/a1/25/b31a7bcf1d7d116842812e54f9b944843b4236ea4fa85634e8259f342212/xxhash-4.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c10b9206753b64aa791b35b201485477525b26fdec5bf86e8364c388a03e2592", size = 278233, upload-time = "2026-08-17T08:21:15.674Z" }, + { url = "https://files.pythonhosted.org/packages/db/e8/5293bae090fc6119dbc5fcf5c4cc0e1536394b52d73b7904d033836c73db/xxhash-4.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:f3e1a44af01b6692de0ec6caba5f0bf93ceb36896e02b7fc00952c6ea7ef39e1", size = 330270, upload-time = "2026-08-17T08:20:51.128Z" }, + { url = "https://files.pythonhosted.org/packages/72/9e/e2ab12d40921f3f34c9317637d65e011aeababf8288356ea8d527de2c1d0/xxhash-4.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6fc415b5568bd9accc7187f1729a99707330c0a67a8b9f93c1149ed573ed75d", size = 478555, upload-time = "2026-08-17T08:22:04.183Z" }, + { url = "https://files.pythonhosted.org/packages/6d/32/c6148d39a49efa95f39b4cf0d41ef35a487f3b30f6fb1fc8fe8d8eab577e/xxhash-4.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:96d8de55029d42251945531f6aa7590c32b48163c66a43bf29d8657d7446a377", size = 258174, upload-time = "2026-08-17T08:35:21.18Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/0b04b68d6c5bc71c7a2c344f1287327b67e607f28fbcfd937697caca64b6/xxhash-4.0.1-cp312-cp312-pyemscripten_2024_0_wasm32.whl", hash = "sha256:0163b5d259de23ae9e07b7eabf435ce4704f6f205589a2b154e6af4be985ce1b", size = 20767, upload-time = "2026-08-17T08:21:00.806Z" }, + { url = "https://files.pythonhosted.org/packages/a6/be/476092aba34d1fcd313e1613a3bb3bc692f253d167b54bc90049043b5034/xxhash-4.0.1-cp312-cp312-win32.whl", hash = "sha256:1216f7ba5683f17a89eb7dcb4bc50a0b743dfe1902278d7b3d0786f538118433", size = 34669, upload-time = "2026-08-17T08:21:49.486Z" }, + { url = "https://files.pythonhosted.org/packages/aa/02/f9413d94fae43cec6d1a74c4f12156c6f4a7f5fd50e1d34defebdee3dec9/xxhash-4.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:5c2d525a3afabcd8e3549d85fc7e111fde6bc302d06a1893fe73adb79823415e", size = 37073, upload-time = "2026-08-17T08:22:04.886Z" }, + { url = "https://files.pythonhosted.org/packages/c1/83/6fe93c1b95acf962bc61a246df09dc2dcce895ccfc1080c9f48d0b652b92/xxhash-4.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:86b2b12bec60c678ed8f5cca0258ad93a8928ebddb6ca7732f0875afe1451d1a", size = 33299, upload-time = "2026-08-17T08:35:12.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/dd/c707286b527722f776e1fb81dd202c45623355ba1a2972337a2a26075b2b/xxhash-4.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:8c9fe122444e129881afd1d4d1c7ac0d3ce2d91b68c2b40173b6025ff1c31f9a", size = 43639, upload-time = "2026-08-17T08:20:54.945Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3b/bb71639a0f95635f61936a6f2653599c4261b645ddddd8d00f9dfe3613e2/xxhash-4.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:1f3346c5c287ac3c7f38b20380f55e8768230e7252af59fabcf3b87ab21e4256", size = 40657, upload-time = "2026-08-17T08:22:12.616Z" }, + { url = "https://files.pythonhosted.org/packages/3c/91/76f3f5385faa9886a36f21fcc603f40b4c0c40ce622382f133160c48b4d9/xxhash-4.0.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:4e5141543c7f7fe3087500bbb4ac2845cb528a980aa91f8f1e661e2292ff4a5d", size = 34708, upload-time = "2026-08-17T08:35:24.614Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4a/f48f0e3e1b1ab072979fff2a5be899234e28090883e8b519d0b10215d708/xxhash-4.0.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f09ee747e2a5f876cc5ad56947734811828335e13b403dd8ea1e06d77a9dd48d", size = 35650, upload-time = "2026-08-17T08:21:09.337Z" }, + { url = "https://files.pythonhosted.org/packages/c4/53/b73d7472b196101ad1f57ed0674af3af803ac3e9ec2feadd650a7b262562/xxhash-4.0.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:acf52474b2494ef66dc7e0fb6d5e2b50c18313039ad4d275fbf9f9907c804bc5", size = 37958, upload-time = "2026-08-17T08:22:10.616Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f2/024946ad8fa532074af4e4380179da54b7ec9facc8bd0b279ec0fac4e63a/xxhash-4.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1b3cccf75eeb5b01639b2feadb042a8e07889293b7ca72fa2985e7dcb64763cf", size = 38032, upload-time = "2026-08-17T08:22:09.535Z" }, + { url = "https://files.pythonhosted.org/packages/da/e0/934af8d99bb5885711006bec30a691f728edd513d2c40f053f887d8e7577/xxhash-4.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd878d32f5c6cbce9783f8d6897561fb772211edba9dde49d85672b88ed45276", size = 35895, upload-time = "2026-08-17T08:35:16.53Z" }, + { url = "https://files.pythonhosted.org/packages/20/5f/a8011f6a1558f7ca66d9077bb4f192b1871afcea62fbd5733605d2015755/xxhash-4.0.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:41e579025a6e13a99e6d71e39c9cfc621a0dcdbbf19106325e145fa858f2d794", size = 259464, upload-time = "2026-08-17T08:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/ff/89/9665a44397547e7a3d58c0942425a976d58dcfd4b538f33220a312bf6912/xxhash-4.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74379a577a9f3b6afbdedf1b90e5c7764467051977f18a326d7d607336d743bd", size = 283949, upload-time = "2026-08-17T08:22:17.003Z" }, + { url = "https://files.pythonhosted.org/packages/34/2d/78774141266457468f29f3f5803092df4db87d8148ba74e4debd041649db/xxhash-4.0.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:acb31ecdd1a97fab5cd39a84ee9f515e727d319f796fec48703b8339b9998360", size = 303898, upload-time = "2026-08-17T08:35:27.951Z" }, + { url = "https://files.pythonhosted.org/packages/59/48/d78d22de576b42528bff87c14207de50de4f0b888221a50ff7c9d675d670/xxhash-4.0.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b7875ac1a2edcb691f27642b8b94b904baa6bcecb7d79c72df2228ba8cb5c51", size = 287241, upload-time = "2026-08-17T08:21:13.042Z" }, + { url = "https://files.pythonhosted.org/packages/4c/de/7a1755a59c59fd46176f293bbdd99e399a6537ba9537fc723aa4d1bf6e27/xxhash-4.0.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4751f1d7eecae6b2d2a773630f1a7248f125c9a92a456694d03c15bceffc9d68", size = 519856, upload-time = "2026-08-17T08:22:15.35Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fb/76580c08e916507859b0f335393cb5fdc59452c4402edbc6bcca6e47e7df/xxhash-4.0.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a51b061d54cda8b83e62c44458bfbf0dabbef9b975dd9649952ba5076b9f349", size = 268572, upload-time = "2026-08-17T08:22:14.533Z" }, + { url = "https://files.pythonhosted.org/packages/d0/2b/1abde3e07b8f2077a38b4fbfaf764115008bfe0ff03bc7756a52c9fd0607/xxhash-4.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:74a164e8b63f1e9cf35c9a7809d082b033d1a00e7375d5d814415436e7867e57", size = 344967, upload-time = "2026-08-17T08:35:23.569Z" }, + { url = "https://files.pythonhosted.org/packages/5c/15/80b6ddf0732eef48a8b5fe717398274794392bd6dbe82af38d189d214772/xxhash-4.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f5e5c6df4b703afcbe9352d238a51efd97c3b91fdc3a2052e40fdacb1e7505f", size = 279956, upload-time = "2026-08-17T08:21:24.97Z" }, + { url = "https://files.pythonhosted.org/packages/77/e0/11cbc43c205bf81fad50d69c7319cd1b1ccc01a66cd4fb8766357126c43d/xxhash-4.0.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d54b8ae068af532c8cdf56abb9e09a60fbe7b10792444c9c27987bb6d3b450fa", size = 307583, upload-time = "2026-08-17T08:22:22.541Z" }, + { url = "https://files.pythonhosted.org/packages/1c/11/cf0bc07feb2791045b6ac075d4bf64f1a5beedef2f46ae70d7104d63a19f/xxhash-4.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1749f0688020209fe0d357ce1e1cd9ec9c6161ed0405ea949d24581c4c43fa91", size = 265848, upload-time = "2026-08-17T08:35:31.298Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c4/7ada4bea2a2795073dfc42d96842930efbe7a0c1857ef4b522e4e90e5d83/xxhash-4.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:94ac8a6b8c47951173f0b67bf862bcb971bf24e493b9fbbdb0e010cbbc7d9f54", size = 284409, upload-time = "2026-08-17T08:21:23.156Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f4/d8ce83dd6b99ccfbdadaf2db968ae40334d2e5f73a0297e593b9ddb3df39/xxhash-4.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:a33de7633c948ab2dc144af370a66e7e7af29b425dcd0f7e4f59689fb9391b53", size = 335921, upload-time = "2026-08-17T08:22:21.802Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9f/f47d8724bd8bc45b395b06b7cacea2dae0d00031af1b707184a091161df6/xxhash-4.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:247ece770647c0aef080561fa996f9774b4dadce2d0c42eeb98229db7dcf820d", size = 487023, upload-time = "2026-08-17T08:22:19.729Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/2d87098f3371cc1e42dd04d2285ad56bca4c56667bc501bff02d2b9fd6b5/xxhash-4.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a4553d36cc0b7fce1f35ba8a94dfd775aa3ed12f5eab2dc3b46ac75a0706b0bb", size = 264333, upload-time = "2026-08-17T08:35:27.001Z" }, + { url = "https://files.pythonhosted.org/packages/27/b8/93795ca5898ec7d7d0455283ad261c0fc76b4f0c0a69e86233bd7badb0bd/xxhash-4.0.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:87aa309a93bd5ec13f14309a305ff4e9bf74c5363fc46c264c0a22edfd5b0670", size = 20581, upload-time = "2026-08-17T08:21:39.207Z" }, + { url = "https://files.pythonhosted.org/packages/b6/96/926f7335a0a1647952c00421e8da877f658094f61336306c7cadc335c94d/xxhash-4.0.1-cp313-cp313-win32.whl", hash = "sha256:cba763d84b06bda2c38d5185dee76f1b9dfdc0789e96e476d9e10005526d0788", size = 34449, upload-time = "2026-08-17T08:22:29.362Z" }, + { url = "https://files.pythonhosted.org/packages/ea/61/8a5aeb811de093bab3434e77eff0e9461624a1a56a6a93d315d080aab2aa/xxhash-4.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:97b94fb29abf21f5f0bde15f7dbdd3a4aa2dc59f37026adc7b4bee8563b84375", size = 36520, upload-time = "2026-08-17T08:35:34.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/14/97f3c74000ca36955e9cb86f6d270dcd5848b5c65afa623453f5cf2d83d6/xxhash-4.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:08ed8da18cd4fd0a6a5d6a444852d8fbd0e565388a74a4937085451b5f1a312a", size = 33428, upload-time = "2026-08-17T08:21:31.713Z" }, + { url = "https://files.pythonhosted.org/packages/81/0e/ea406a02b561d3275232ccfdb3e29df80f7a65414940e3a15721c7bea40f/xxhash-4.0.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:af05a3f650220a6c59fa0ad2410249f2d2470a05225807c378fb67458693f8df", size = 43747, upload-time = "2026-08-17T08:22:31.37Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f0/b0c94d61ccf6b5d1f8847b58ef8f923125ac4919ed5bd0eb082750ca7cbd/xxhash-4.0.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:a6e3653df1a70b8ac4191216324242e4be2bca18c9a7c10934e1bd56dc7ca15e", size = 40749, upload-time = "2026-08-17T08:22:29.431Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c5/8085881a538983be0fd1c865d5df236242fea496044e2c8ca32b9f2ba39c/xxhash-4.0.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:4528cf80ebbbf57d40edfb31521ae265daa6dd636d615b1cf0ac86209579e59d", size = 34734, upload-time = "2026-08-17T08:35:33.68Z" }, + { url = "https://files.pythonhosted.org/packages/d3/94/8803d13c968fc75ca434eea991d29ac5fd8a36b4afc9a6a9803c53933db4/xxhash-4.0.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:90cb2a1c9cc503a054a19612b48ff6e8e47805f618bdb3224a07568aad03a37e", size = 35671, upload-time = "2026-08-17T08:21:48.322Z" }, + { url = "https://files.pythonhosted.org/packages/85/d5/ad91d7f0fd294190d37c08236fe661f5c4e3f83dcd1a121877a2e64681ce/xxhash-4.0.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a949b072ea59c6eca0811ccd9e95133cc50d2afda8d464b5b077c78f78efa269", size = 38094, upload-time = "2026-08-17T08:22:39.763Z" }, + { url = "https://files.pythonhosted.org/packages/89/f4/2b7ebdc1869caca5f02c4cba8379b631050d3c3d4adb9187e4dc1a6b8d3c/xxhash-4.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:79a3203aadf39637869dfea1185227d8452844d78b837e54fb1117b4d34ba5c3", size = 38244, upload-time = "2026-08-17T08:35:38.081Z" }, + { url = "https://files.pythonhosted.org/packages/90/9d/f66cf6935f528e575f1ae4d6560d376e7587569747186f4fae8777cadc1b/xxhash-4.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d9f3848ffaf010bdbabdbf4c25641fa258b6227ff27bc74a4d06edef521a4873", size = 35904, upload-time = "2026-08-17T08:21:37.358Z" }, + { url = "https://files.pythonhosted.org/packages/07/29/34569d7b482f0dc060074faafd163c588f915cbc3e3e218f1ffd8a3ad340/xxhash-4.0.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9283d9dd6b44acad35118e2976fc763a065509e4118debdb61916ec322ed17b9", size = 259595, upload-time = "2026-08-17T08:22:38.153Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d2/a2370acfcd48732cf5c2b87f06cfbf7fa51c0ce0dd736bde42939eb9ebf7/xxhash-4.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c7c642a0f79c3e3cf2965475507574d3d1a50ec71060039d60cb87358667cb2", size = 284279, upload-time = "2026-08-17T08:22:36.396Z" }, + { url = "https://files.pythonhosted.org/packages/08/15/17d33c24e6c4a1c0b9ddc5584f0c25d51d48b34bacde1416a2235a19db4b/xxhash-4.0.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96dedccfb09a73a25751053a183159b88f4ee75f388df8166040c152ac0531c6", size = 303973, upload-time = "2026-08-17T08:35:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e0/4ec0d69ad5738729098a61e631b7ed2df22a922b0e03014b597c72bd863d/xxhash-4.0.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81664268dba92e037b740ecf37fa02f1cab4a391f93f28e35792b3341c60648f", size = 287535, upload-time = "2026-08-17T08:21:52.158Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8b/4f9b17e7a9eb71c65548ecddd9c18b84e3c18ca41c4d436ad2a3000d3f7b/xxhash-4.0.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:839f58c5bd9989875be0fd28446dbf32cace2c2cd8bf2f6762acdc38a95cd1aa", size = 519257, upload-time = "2026-08-17T08:22:43.272Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/3276b3e743b8ddbed9c3f71c76d9dd6a75d72aa4e678b1447b635cfd92e0/xxhash-4.0.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa44b4c7c5d0ffa31356b4428659516c0e47647825c74079a296b3857b6d99d", size = 268190, upload-time = "2026-08-17T08:35:44.985Z" }, + { url = "https://files.pythonhosted.org/packages/08/d4/f1555de3c96721320930dbb7988c8482d82b85970076aba1a8d40e83ad43/xxhash-4.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e681a6fc7e4f715252b9b5acfb30536ec7dd1f75033a32dc617e6fa95af1a3fd", size = 345553, upload-time = "2026-08-17T08:21:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/ac/98/c28908f27007087b61139d290f908dd827ffd40b88af0c43f9e1a1a7ffd5/xxhash-4.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6301d92545c591ad31c3e050aa40a5f8a4c16413f1f9e6f9322c6f0f9d2b736", size = 280499, upload-time = "2026-08-17T08:22:52.236Z" }, + { url = "https://files.pythonhosted.org/packages/a9/76/3ef57622c65816348f8196273485baab4752aae064959901e85cd867e067/xxhash-4.0.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:6efb8f21cc136c79b3e5bb747c8682d37916fb202cdbbc32182de5c4e47f821f", size = 307211, upload-time = "2026-08-17T08:22:40.815Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/5804504bbc808968e57d6a50286dd8f8cc06e0ddd6e4ab4b1dc89ae42f35/xxhash-4.0.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:760de77279e9cf9c81d012ce0705cba13afccee9b09c480f17d778c8c5cefae8", size = 265865, upload-time = "2026-08-17T08:35:42.727Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ee/8572fdfd70e7aaaf150af899871c2cc0bb88c3295ca82172a31e04ca5168/xxhash-4.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a16a3fa6936e36bb1414d16a6bd012c9033e5161b68b426805b61d895392437d", size = 284545, upload-time = "2026-08-17T08:21:56.965Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f8/6eadcca0904660c848b466524e82a233d16c9d2d5258433aaf3546142d86/xxhash-4.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:9c3c4b9aa9a27196b921197f7daf9e6c1412739df06a99cfa6e923879362eff6", size = 336022, upload-time = "2026-08-17T08:22:46.346Z" }, + { url = "https://files.pythonhosted.org/packages/27/df/4aa107b81602d6d6d09ab5a607c530d2d3a6b28e2e9a59b01875bd877c54/xxhash-4.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:863f3d3b44110f7243e86cf994aa5c5d88f2348b6e84ab4402fadadfbf9f7da7", size = 486671, upload-time = "2026-08-17T08:35:49.016Z" }, + { url = "https://files.pythonhosted.org/packages/45/b7/b2bf9b5301e9cd5f2e335fea8da0f5cf209a6594cb1fe77754774ad4a6fd/xxhash-4.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63aa52659bc32bb9bd7cb5caf523b4d14429a477762cfac886132d687c1f80fc", size = 263744, upload-time = "2026-08-17T08:21:56.165Z" }, + { url = "https://files.pythonhosted.org/packages/0b/96/35b1c02177ae26234892c2310fb4822ba62411acccbf425ab8f9fd99354a/xxhash-4.0.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:67e57b834e07ed973cee7b6da1548ff28a56458d77696fd2a5f397f340694848", size = 20563, upload-time = "2026-08-17T08:35:11.924Z" }, + { url = "https://files.pythonhosted.org/packages/51/c2/a06300b165fbd6b0cb4a9742987f2e997a9f447ce3bf7c6ac97b862ce62a/xxhash-4.0.1-cp314-cp314-win32.whl", hash = "sha256:b6c1f9c59bbe593f88a0aad30be4150f15bd57bd64efb95feeabcb8e563f1ecd", size = 35151, upload-time = "2026-08-17T08:22:44.283Z" }, + { url = "https://files.pythonhosted.org/packages/06/96/c5b37296b78f80fc97124c0fee0c7bbd1bdb6f3b18bcd8748bb113b2d8fc/xxhash-4.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:da544672efd9ad76077928a3e6c5d894e52ce82d3bf14002db4a1bf17d1a36a2", size = 37156, upload-time = "2026-08-17T08:35:46.551Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5e/248f9cd169c2fb62236bedfba246d213bce728f74901e99047e3f3c55875/xxhash-4.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:d0d24a4f3fb63852cd09af46ae4b7a4d00cc8b8615a046dca543786e728d1056", size = 34379, upload-time = "2026-08-17T08:21:59.446Z" }, + { url = "https://files.pythonhosted.org/packages/58/c8/db1d37c0da0324d0298f6abd931ca1d4736e049d9f2081230a8421da74d2/xxhash-4.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:349775ac30372b344d2338b2a168c0a1312a644194da25b8bec476d55761a128", size = 38656, upload-time = "2026-08-17T08:22:49.119Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8e/e18998ec465fb977bc74272e5bf3c2e886c13b014cbef916cd607802c709/xxhash-4.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:43e5f9169e73d0f0db33b5f6b8554bcce69ac278c966daf83d5eb4eb2f13829f", size = 36306, upload-time = "2026-08-17T08:35:52.853Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1a/b83f86f8a987a3cbcb7e005a6824ff64aecae35abc1395a0d44ee16c3319/xxhash-4.0.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4a252fb862b0ae2590587e625f47a0e03da05cf0205e8830b67b6596c06038b1", size = 273729, upload-time = "2026-08-17T08:21:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/02/4e/2db15aa8508e0cd5b632927a53b98234f24039ea65377e6cf996c06d2d4f/xxhash-4.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2df3ca8757dc381e75e90a4d7995a6324f58a923c7145220a7b2c0231f66fddc", size = 301083, upload-time = "2026-08-17T08:35:14.113Z" }, + { url = "https://files.pythonhosted.org/packages/26/94/ed759787ffe802bd8e31cfcdad3755cbeca2dcdafd2f790cd6f25d195199/xxhash-4.0.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bfed61996d618eb90d6eaae0178002e3466a28b06bfc557a7a3a7266378d8c5a", size = 312745, upload-time = "2026-08-17T08:22:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/f64b4a4cc8b51e950709207f55f7f56ae9c5af6631dd31d7fb443312418c/xxhash-4.0.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9761ff4a0ffa583fe850731ad24fe82c88cccb7a2294727db0955f3279a4cb3f", size = 301419, upload-time = "2026-08-17T08:35:50.143Z" }, + { url = "https://files.pythonhosted.org/packages/a0/71/bac313b8de073569b8db3152044a7cfcce87a3fa9698c18fe9f914dee6b1/xxhash-4.0.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edccc2ec58435a580f96a48a3ccae8cd0a480824119165dd90108718ad81ae6e", size = 534485, upload-time = "2026-08-17T08:22:11.515Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0c/16b5e419f24e59507ee05626d2bb0deafdb03f9f27783bc0785a9849602e/xxhash-4.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4741d42d59e4e5fa1a86c17ab9c27dc8ea459c700d91b6742fdb9138d9a516cb", size = 279605, upload-time = "2026-08-17T08:22:52.934Z" }, + { url = "https://files.pythonhosted.org/packages/5f/55/5787dd6e2d8d5b61256a5039f6b18c2193c7c1de4a2fd2413288d0d9c604/xxhash-4.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:440c401e146ce64bdb3beb8ff0c84677b6f21307c28a34779071cecee5d4d70c", size = 358924, upload-time = "2026-08-17T08:35:58.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/68/89be41991f3b0a2e91f940bdf3128852c3ed571cf560d98ad0f67024afe4/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5b7979f71d06ae45a769de0699900a246d8cb632db1e8bfdc79ec019063a503c", size = 295305, upload-time = "2026-08-17T08:22:13.683Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5a/52ff0a0cc361aad393ff9a46ffe3aabbcf9c03d6c8f2612da7d553048276/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:62198213fc3e0c56e567894b318ba45834e007d065f84ba6dc9165d21546fc56", size = 320228, upload-time = "2026-08-17T08:35:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/0f/b5/91c60ff22c7f6cd5f6d7a5bad5a2cdcb4c33987dfa50bf13f0d856279b2e/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b3bece52127ac20044311ee73567f9f0893b5de64f9028aecc90cc740cfd525a", size = 279414, upload-time = "2026-08-17T08:23:03.212Z" }, + { url = "https://files.pythonhosted.org/packages/b9/94/9685954804d47d0390871a64bec606a0d536406382d71a784df3a5883fb4/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a865d2d470220e659220fdb59d5b6c4422802d8d6098e1324bc4d12444798914", size = 297594, upload-time = "2026-08-17T08:35:57.881Z" }, + { url = "https://files.pythonhosted.org/packages/89/62/b67ac9412907b7a07a2a0c08c3440b9e4480231a7b3de0767e87011e4564/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8580aab306888224074c7edeec734de0c3c5ccde65b2da4e6c9a5e28f7c0a1bd", size = 348526, upload-time = "2026-08-17T08:22:18.571Z" }, + { url = "https://files.pythonhosted.org/packages/37/ed/6723cc49a9f567d52d01fd7c1741b0f2e3a13e71d15f7ac49d753a20c115/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:2d52dc7c33c1b83082b707f6b7814dc76d2faaa2ea62bd9c5fab4b36f83c087f", size = 499307, upload-time = "2026-08-17T08:22:56.52Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2e/7b10e101ab988d93b791023be7191d7661271d6ab31ac082276b9091042a/xxhash-4.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6a9f98af872355e0c02439e48583958eee00e60b928bb20476460d9d40cb7b4e", size = 274989, upload-time = "2026-08-17T08:36:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8d/7eabcc8d29cce40621443cff24c07d7306ef574b8956c47ac59f21098005/xxhash-4.0.1-cp314-cp314t-win32.whl", hash = "sha256:a14578102a6081465aec9cf73c76c3cd3f79f0709bdb3b8ae7ab0b54c9d8b089", size = 35482, upload-time = "2026-08-17T08:22:32.336Z" }, + { url = "https://files.pythonhosted.org/packages/ca/89/2a4268e1971f63038b79fb75e3b9c8de942cd77acabbb0c5625352a31940/xxhash-4.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c57963970d359a72262f7fe6be88f945e2334d4bc41462b7f08c37b0abf35ca6", size = 37490, upload-time = "2026-08-17T08:35:22.475Z" }, + { url = "https://files.pythonhosted.org/packages/90/7b/950ecab1fe4cf421d0a6211ddd9a0ac82e39e55c45a111ceb90953dc6c9a/xxhash-4.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:b659fad79c99b0238c7ad7e9d7dbf4eebfea9097c2dba65fa0a4d18a25b29a2f", size = 34596, upload-time = "2026-08-17T08:23:10.001Z" }, + { url = "https://files.pythonhosted.org/packages/c4/03/7dc3b85fac10751613bfedb0e120734e0e8710054abad3f931e9d3843a14/xxhash-4.0.1-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:5adf927dca8c47fde7e683fe69efdd81bc865c4db1fb6bb00b391e2b6185207b", size = 43749, upload-time = "2026-08-17T08:36:00.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/bfac071c5b1c6d6a3d48ab1ab96a15e958a1d7061f4afc97804292d87264/xxhash-4.0.1-cp315-cp315-android_24_x86_64.whl", hash = "sha256:c30dd1af66a820820398b26e0d74e7a9aa43cae705924f23ed828cd8e5c26c3d", size = 40758, upload-time = "2026-08-17T08:22:30.209Z" }, + { url = "https://files.pythonhosted.org/packages/79/87/49a260e685d1a74c56a69432a8ee0527ddcbd684a3c51f87edc3b75639c5/xxhash-4.0.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1bc591533fc975614f7e13594daee76af96b8e1fbcf8de76c8773858fa9e7cea", size = 34788, upload-time = "2026-08-17T08:23:09.014Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/50d72ed2170dae872e1c0fe333d0908e0a2afbffe74c5c9037d5406a4b89/xxhash-4.0.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:567cbc630302a46a8ecfd943b309ccf5372bb3718f1f3762d452df30f033bcf0", size = 35746, upload-time = "2026-08-17T08:36:05.557Z" }, + { url = "https://files.pythonhosted.org/packages/66/f0/969deaa2bab3bfd5ad5b023442124d2255b9961eef6f797ec74eb8683bdf/xxhash-4.0.1-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e998cb3685b92101ec5de0fb4d9485cf01e50bc418211955c55d98064664cf4c", size = 38098, upload-time = "2026-08-17T08:22:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/86/aa/45ed7d7b8d7b66202a47bf8ff3b77cea28d2ea54dfcdd202b4cfe043e3dc/xxhash-4.0.1-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:c3074db513c81f764053e3da079312ecf85a50d8350c71f4cc0105d9662a9e6c", size = 38251, upload-time = "2026-08-17T08:35:25.774Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9d/45e7520a7856e13800a5dc8cd038d34c6372429465b163af0c5722f16918/xxhash-4.0.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:3088dadbffa33c29e0518578430a7dff2e901a212e487aefa5faaa0dc06dad34", size = 35986, upload-time = "2026-08-17T08:23:25.854Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/5ad466e5fea18c9f9bdc5828c0506f62190061b4a1b0e688aa54969d0a9e/xxhash-4.0.1-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1b50223d92df94d54e1a31469335a2c74b16692e6c1cb726f1e6949514458706", size = 264609, upload-time = "2026-08-17T08:36:04.229Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cf/8f269f85217e3dbd45e31e25e46cc26f3aff0e159ef05d228b4b982c778c/xxhash-4.0.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:427b62d62d4f967fbb10b82a3813e4875c2a6e7e7634739f17265b650c7f65a6", size = 285910, upload-time = "2026-08-17T08:22:38.589Z" }, + { url = "https://files.pythonhosted.org/packages/ca/30/2fc1a16ee0f9501d074b798ebfae52e24fa602c7117f5c4b81de71eada72/xxhash-4.0.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c6370189e8e66b7e608f533b939a9de092ddca6cce084ca0d3d414d2ed5b5d59", size = 306566, upload-time = "2026-08-17T08:23:16.895Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a7/08375cf2b997e1903663fe7525c5973b1987a4f8ad2b8d47463e9143f2ee/xxhash-4.0.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec1a470c6db94ac4589c203921e89ac1bc13e796a8b1784d8135e1893559cd3b", size = 287978, upload-time = "2026-08-17T08:36:09.296Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/90a7b404c11add9e53a497d06236152852490c3b2f21e468d97a58f26afe/xxhash-4.0.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37f667dee0f867c42894b34e2a6fe26bf195c0ea4683d9d2b713db023f242c3a", size = 520098, upload-time = "2026-08-17T08:22:41.565Z" }, + { url = "https://files.pythonhosted.org/packages/11/02/7fba10b1b17eb46308f09cc0a4ed513d74dff16b1e22a1c439f011c77129/xxhash-4.0.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f18732adcc271741bd651c3e56fa519d8a237d2cccda01fe3afb226bf87f783b", size = 268273, upload-time = "2026-08-17T08:35:29.043Z" }, + { url = "https://files.pythonhosted.org/packages/54/49/c21b228877357a3be43eeeaa22182ad1685796f415390ada475922c084e4/xxhash-4.0.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b42a5a26607e4b2409fea174773a66f2dff9dfdbf2c1a851bb7b804e2c97535", size = 350164, upload-time = "2026-08-17T08:23:29.494Z" }, + { url = "https://files.pythonhosted.org/packages/00/3c/c15bb4aa33d94b78a5553b52e7fa1070565f0199925aeadec3871de20ce9/xxhash-4.0.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:99166cc98637e8bf550cda2aab07f4f1d5f899c45fbd721801aeabcc9d404824", size = 281975, upload-time = "2026-08-17T08:36:08.139Z" }, + { url = "https://files.pythonhosted.org/packages/18/7a/b1d0388315fe7752b7725b68a912667526a1dd48ed492fcc031ac03f4b52/xxhash-4.0.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:6cf633df84d80a1668fcf61e330791dae46825e395549e7d34f376411e75088a", size = 307872, upload-time = "2026-08-17T08:22:42.206Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a1/037cb2dd8cf725c9565dfe3712b2915c0e0276a9154913dbfcbcecbeb672/xxhash-4.0.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:e259bb7e1e2d8de6b35f430f5c7220b1c0ebf3962d1ba7ec7545980d5931edb8", size = 268241, upload-time = "2026-08-17T08:23:23.997Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a9/67c44422d0ee082169b238ce24bd2796b82d7c21ed953471365df8c508d8/xxhash-4.0.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:704381264b36a18b9c62ecbabe2e71d0fc58c77c129c15355c989b10bf05b6b0", size = 284970, upload-time = "2026-08-17T08:36:13.476Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d0/254a5f51c4014cacc77a26f321372338b924f54e89efb730164ee336d850/xxhash-4.0.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:e90b4bcf1d9eb1010fdaee7c9209fb667e74c0684f3ba17f9032bd7319da90c9", size = 338074, upload-time = "2026-08-17T08:22:51.166Z" }, + { url = "https://files.pythonhosted.org/packages/64/03/f21c4830118d72ef3a958ce8bf2152f49e0d4cf200907616c9be6caf372a/xxhash-4.0.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:a65785e653573fcd1e33062760ab4c3c3440e8e910765018e4b6ed4ad07b54a0", size = 487626, upload-time = "2026-08-17T08:35:32.768Z" }, + { url = "https://files.pythonhosted.org/packages/45/1f/268a689d741d7da649317eb4ce41760140beb4179aaf43a7216fdbe8100c/xxhash-4.0.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e3996ff9b6f99180357024336bf5749a8ad6476a9a2523e535c5212b995b12a2", size = 263852, upload-time = "2026-08-17T08:23:41.871Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/adaf8101cd7f143191a0b390600294d83924b32cb13770fde8803dce27a2/xxhash-4.0.1-cp315-cp315-pyemscripten_2026_5_wasm32.whl", hash = "sha256:99054b838b74d8d3995ea0d410976ae967c46207ae22d6ddfc535e809197dab9", size = 20569, upload-time = "2026-08-17T08:36:11.952Z" }, + { url = "https://files.pythonhosted.org/packages/ee/2c/56a5eb8c993420fc07114c08f447a2b66ee996510b4764cb368b9b44c9f0/xxhash-4.0.1-cp315-cp315-win32.whl", hash = "sha256:6c45258a37fc22721395c09927cb982d3e7a83607cab15be7e2416501bd3a330", size = 35145, upload-time = "2026-08-17T08:22:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/67/c7/65f210db43e62157d0fef3b4d4d7b394821e7733c8bb4ece49f91410a725/xxhash-4.0.1-cp315-cp315-win_amd64.whl", hash = "sha256:0ab851b45c70d4992be7cdeeee16f97a0b677408c758c4b1efb1cfe8030bfd37", size = 37161, upload-time = "2026-08-17T08:23:32.438Z" }, + { url = "https://files.pythonhosted.org/packages/33/ae/1a641d1d60ba219756d9ebe907ff0ecf4445adcf4fa96f6e3da57b91d439/xxhash-4.0.1-cp315-cp315-win_arm64.whl", hash = "sha256:a5b21b42a01a343096a1c018d35e9b7aec9c7065dda53ae8da071e37478b2cea", size = 34378, upload-time = "2026-08-17T08:36:15.912Z" }, + { url = "https://files.pythonhosted.org/packages/48/7f/7698b320b251806d1249e513922a626f19027e104c829a611272250350eb/xxhash-4.0.1-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:44ab12e8cd17d4f001769f00ad465208b4bcb897ed29e65f058f74466b57a98f", size = 38610, upload-time = "2026-08-17T08:22:55.203Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3d/436497e775b647b3b3e9a4ffe8c76c59fa4aa7a9fab6447cb59acf1b50ea/xxhash-4.0.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:45e88111ebe331de478ef8d4293efbe88f3cf8b863386c9a2357136b838e1af0", size = 36378, upload-time = "2026-08-17T08:35:36.18Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d8/17a4f8182b9257898aa2a77c2a45f70233eb8e50681a280e8e09d2ee76e9/xxhash-4.0.1-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf430c587f447a554c53768ad76b9846fe7c5632180ef6f69c4fce8b0552fbd0", size = 273559, upload-time = "2026-08-17T08:23:51.075Z" }, + { url = "https://files.pythonhosted.org/packages/83/28/121bd5a5c5adb88e0da772c7bef61964cf9da92956a7a237c7d24c4351b8/xxhash-4.0.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adbd48b30e3f82c89fb2b3e6a87cdd28d113b190a5ed0ee2dee286323ee9a621", size = 299133, upload-time = "2026-08-17T08:36:14.731Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/57c7b6e04642ed738a0d08a31bed7fc63fdacb661d665f98739cc9751b62/xxhash-4.0.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e71b34978e77868cbf2d18c5206a4603f9c644dd7181bec5643bd40141d3b8c5", size = 314527, upload-time = "2026-08-17T08:22:54.224Z" }, + { url = "https://files.pythonhosted.org/packages/8e/18/42793917dbab0ea1ff71458aea4875e17a7263f2797b798af048dc81e867/xxhash-4.0.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:488ca5c5e28ef56ec4bbb12f835b3f1cbecc5f3510062e70117bc6594851932a", size = 299199, upload-time = "2026-08-17T08:23:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/37/60/51dc92443923d8e908d5614f1145d8d696450f9d6c8f1abe243c6f2a0222/xxhash-4.0.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:421b94f3ba7067958d02e38960d987756347aa150df06df11aa68ae1af78c619", size = 531967, upload-time = "2026-08-17T08:36:18.66Z" }, + { url = "https://files.pythonhosted.org/packages/88/c5/d0de77de09661fac71742c4155b1cd65e274f7cc277819d702b6c8ff2db5/xxhash-4.0.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f33cf0baa91eccd2cb7b62bf00f10c2264ef578b71dd33a12962e71a36eb4d32", size = 278764, upload-time = "2026-08-17T08:22:58.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/9a/589929c655aba1bfb2c41ee03e50eec1547c39c3042a66bda9c173a9614b/xxhash-4.0.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:23a4376b4a3183cb50d4d2a3179f887a7773cc695eb2c908e551bec3221b8c60", size = 359876, upload-time = "2026-08-17T08:35:40.35Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a8/c1d8c94d54d91db2215565f4b4151c1593af3e6d27ac4c00fd1e8d714a02/xxhash-4.0.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:38c3d22129a6958846a3098d68bc8e661704461c0be4793ae28836e4690c8478", size = 293548, upload-time = "2026-08-17T08:23:54.951Z" }, + { url = "https://files.pythonhosted.org/packages/8b/67/85d8abca94508a4dd10561d9dea3e6e68843c6986dd6d9c1b3729c8622e4/xxhash-4.0.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:87cbdec1a7dd930079671a60b249f3ca4e773e6fbd0676e21e36fdc9dd0f3b00", size = 318780, upload-time = "2026-08-17T08:36:17.623Z" }, + { url = "https://files.pythonhosted.org/packages/1b/16/2b920ed456b9cdcfc99ddc20c3afe42f9f807ee5850773c12fd891f3c08d/xxhash-4.0.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:6cbf4e21ef0890804b5bb9ad25c48f9c127758d7f6c66bef374efcacc63c738a", size = 277582, upload-time = "2026-08-17T08:22:57.156Z" }, + { url = "https://files.pythonhosted.org/packages/fa/cc/5811b5997aebb8452047f5800d32fc50eaa29d0ba08d4e426f84450b9c2f/xxhash-4.0.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:c101180495cb4ba3617b279a944345c53a5e73b0c150053d1fa8d8af32de9579", size = 295116, upload-time = "2026-08-17T08:23:40.868Z" }, + { url = "https://files.pythonhosted.org/packages/2d/dc/c2f3f9c2f4d6aadb79f17a9f1c9a7ee82638cc873680da044cf29537d2ee/xxhash-4.0.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:c0e6ccc2b19ec8a726b2e26062ac71ea63e15500d6bf85910e42481844fdffc1", size = 347065, upload-time = "2026-08-17T08:36:21.618Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4c/750cc642c92252e10772ec09e1a1d995581ba4c3ceb24f6e2d57c7ce47ca/xxhash-4.0.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:8bcba9456242ebf180a04d9443812fd85ffe6bd12bda464dd116fcece8886ff3", size = 497208, upload-time = "2026-08-17T08:23:17.88Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2d/58693cb13d6395f39b6b9bb40c5e0db53a5df7c9fce805aa7e792f64a1a5/xxhash-4.0.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:83b8c2013edb5dc1f9e7268b6496130705bc48d79c86bb8817b3d210b81a5513", size = 274338, upload-time = "2026-08-17T08:35:44.062Z" }, + { url = "https://files.pythonhosted.org/packages/4a/08/9aa9787586d9b3e92d63343ce7dc24f0f445fd9e74ff5d6e85dd82233df5/xxhash-4.0.1-cp315-cp315t-win32.whl", hash = "sha256:aa6ccc7f31018484d652cf52db020003433f3c9fa83189c028bd807d2adde503", size = 35471, upload-time = "2026-08-17T08:24:05.795Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ab/4615789c333bee331ac417885c50105715eeb8244bfc68d2bc37dcfd63ca/xxhash-4.0.1-cp315-cp315t-win_amd64.whl", hash = "sha256:daade8936c4deaaf7b01561324ce438ba4f885d717e9adc62b4d67212ad7d7bd", size = 37488, upload-time = "2026-08-17T08:36:19.929Z" }, + { url = "https://files.pythonhosted.org/packages/fb/81/49f718beb0c55d0411bc4bd90b50a3fbe5863a0e97a2f4d11682ba13d298/xxhash-4.0.1-cp315-cp315t-win_arm64.whl", hash = "sha256:f00330ac7e24769e2032203f2b01794d670916b0c1799fd261340f1af9499875", size = 34590, upload-time = "2026-08-17T08:23:19.597Z" }, + { url = "https://files.pythonhosted.org/packages/86/79/9127ff42a887a348dc4ce3211cf1a962836887adee6f57078132bfba78b4/xxhash-4.0.1-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:ff48915bf1871a1f19f74c11834c6329443d306cedc0c05fe7fe617810422a80", size = 31836, upload-time = "2026-08-17T08:36:28.261Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/f238693bfdd642adb59c99683964d46d9947fe721ff44d3bd850ae675407/xxhash-4.0.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a76345f5aceb4ec404918edf9c7f2b5507db864dc0d7455982009ac0890b57b", size = 34453, upload-time = "2026-08-17T08:23:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/40/4b/796ace33cdfb75c91ba6d11615c3bd436355b9f3103e05865bbee9abce57/xxhash-4.0.1-graalpy312-graalpy250_312_native-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31d86f9e81f3e84e00131ac7c54caf5119ae4ddd82c09c31cff597c813ce1ee2", size = 38488, upload-time = "2026-08-17T08:23:59.901Z" }, + { url = "https://files.pythonhosted.org/packages/ad/23/2d549e5d5d7759eaf9ac2d2d2ab81ff60f1bb2b52cdaae8e5ec5c6524354/xxhash-4.0.1-graalpy312-graalpy250_312_native-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deca2a30d983d240b8375ec2ee0a4288e72042827fc61df2f7671f8467e4cb2f", size = 38206, upload-time = "2026-08-17T08:36:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/79/98/1ee576b27f78e6107ee4ea8ac03e8a52888dff256e57d560f8282c195563/xxhash-4.0.1-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:7c343ee174d417a44d0c3355602c0cbbfa52a04d1bbbf1723378c7d2c8f60626", size = 37127, upload-time = "2026-08-17T08:23:42.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c5/8b528f4a394a1eabfd5d8b4271f24fff67fa1aebe58bdd3c64967662e6fe/xxhash-4.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:26fe6238c2d5b11ed5063b9bf4eb290624b004fd074688da6bb079bd564f10d7", size = 36450, upload-time = "2026-08-17T08:35:59.217Z" }, + { url = "https://files.pythonhosted.org/packages/b6/03/909926dcedb64ceee629e18d8dffa758f9d3fdbe01e25530ce2bf0eaf9d0/xxhash-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:e53926e76131a74e79cc0b39fa712c227875f180afc68646bd1e1d8a17e60313", size = 33467, upload-time = "2026-08-17T08:24:12.924Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/ad50456fbfed07f29169bc3fd34e70cbee306a88c17d1a1141a7638de0ee/xxhash-4.0.1-pp310-pypy310_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0718ad66f4ded2411f8e62bdba549ee71e313a2d26ef5060ca3fdbf29897dd3c", size = 48048, upload-time = "2026-08-17T08:36:30.847Z" }, + { url = "https://files.pythonhosted.org/packages/33/c4/83cfe6b591a3067ab3f1339931c91211a8b3864d0ea76ad8184f0c1b0515/xxhash-4.0.1-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2200b98a805351cb3142ae4e1fdcc9e91b5e20f5d30d4862b0b96f92558f4e", size = 42719, upload-time = "2026-08-17T08:23:53.581Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/0f654f2831abba09d502751e38816759086dd92749501b128b7c3676ea87/xxhash-4.0.1-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea5ecf800b45bdb34afe05a1d0dae1f8ea02a290e50636dccd399063f6b180f8", size = 39477, upload-time = "2026-08-17T08:24:03.735Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d4/375b9e4b719ee70e0a6d9e98e2b60aab09efd59f46c7871a112a2e6b0fcb/xxhash-4.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce6d5cc94a50291d080259a126cbf1e9ba4ac861e6429d2f3cdbb1474f51945d", size = 37244, upload-time = "2026-08-17T08:36:35.452Z" }, + { url = "https://files.pythonhosted.org/packages/ea/4f/e0648288a17d0d1084ca4f7bef206097831988fc86af74aa1dff8f1fbd68/xxhash-4.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:554f87034635bcec47c5d72447bf3db7e02da1bf493a0ada010db28a76f891c6", size = 36333, upload-time = "2026-08-17T08:23:52.913Z" }, + { url = "https://files.pythonhosted.org/packages/22/15/34b7f72e9b5a8bfd7e6178de9e1e342bc3de9f07111a5ae26c00506d9edf/xxhash-4.0.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3c2445edafc300cc40feb6a25a8356a971c30cd0bf47b5349c2ad74c508343b1", size = 33519, upload-time = "2026-08-17T08:36:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/06/4b/e0af324ccf701bf84a7a060bef11c915d45d9e3c5b9caf5b94d62ecb040b/xxhash-4.0.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bdd16718b63aa3ebd68aabb79021a40e47c81374852d41a306b9453141bbcbee", size = 47995, upload-time = "2026-08-17T08:24:14.335Z" }, + { url = "https://files.pythonhosted.org/packages/59/46/cc7130e6ca6b41ab72eb6a03177b933c7c74145545c99a8610ecc208c449/xxhash-4.0.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b99ebaf9e816ac5069423b1367ee7e8078fbcebcf62545506bb0608d2f4f468", size = 42725, upload-time = "2026-08-17T08:36:34.144Z" }, + { url = "https://files.pythonhosted.org/packages/1c/24/4f26ff9a7dd0998f6d1036bdddef7ce3e78972a74f7fffa7967e7bc3b7e4/xxhash-4.0.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f484ed57bb3e4142f9d6439568658c38be5f94b702ba00a1ff32c69783b6c66d", size = 39532, upload-time = "2026-08-17T08:23:57.556Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f3/1ac078fc8fceadcf066469acecacb35d2821cbfaf7d6fc5ac2107c7a314d/xxhash-4.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fac4832b638000106207bc44e44b9616a6a416aaee56c62b01d61f3705e49f58", size = 37252, upload-time = "2026-08-17T08:24:06.652Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/ac/cacdda1f0a90441297210bc34cf7e4ac1b7318c8030ebd83bdf6fe82f1db/yarl-1.24.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88f50c94e21a0a7f14042c015b0eba1881af78562e7bf007e0033e624da59750", size = 135466, upload-time = "2026-07-20T02:04:21.695Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a5/1b2ceace0230e40c52ab1b263148059a43a6303219b996affc68f8381836/yarl-1.24.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6efbccc3d7f75d5b03105172a8dc86d82ba4da86817952529dd93185f4a88be2", size = 97291, upload-time = "2026-07-20T02:04:24.045Z" }, + { url = "https://files.pythonhosted.org/packages/59/1d/340d1a0db7bbce1f291afc044255ebf4ebbce2b25ab1b3f7d3d069080f5d/yarl-1.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0ebfaffe1a16cb72141c8e09f18cc76856dbe58639f393a4f2b26e474b96b871", size = 97154, upload-time = "2026-07-20T02:04:25.761Z" }, + { url = "https://files.pythonhosted.org/packages/05/41/25596a33c2fb5098dca8dc3773b04221db64ded0b7f8f09885647d864610/yarl-1.24.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac73abdc7ab75610f95a8fd994c6457e87752b02a63987e188f937a1fc180f0", size = 109196, upload-time = "2026-07-20T02:04:27.543Z" }, + { url = "https://files.pythonhosted.org/packages/f2/df/dd9f2fb8a5c6054fbefd1538d2b9b1127e612d2ee64b307a070173b57afd/yarl-1.24.5-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4d97a951a81039050e45f04e96689b58b8243fa5e62aa14fe67cb6075300885e", size = 102556, upload-time = "2026-07-20T02:04:29.16Z" }, + { url = "https://files.pythonhosted.org/packages/cb/57/4754b9d2c8945880290ecba0864e8b0441e117bba70534fe819e3645e174/yarl-1.24.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fe7b7bb170daccbba19ad33012d2b15f1e7942296fd4d45fc1b79013da8cc0f2", size = 117965, upload-time = "2026-07-20T02:04:30.845Z" }, + { url = "https://files.pythonhosted.org/packages/74/b5/6a9ece27d2043c3386f902dd078ab35d29ef5126b3206ebffb673283a7cb/yarl-1.24.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89a1bbb58e0e3f7a283653d854b1e95d65e5cfd4af224dac5f02629ec1a3e621", size = 116266, upload-time = "2026-07-20T02:04:32.573Z" }, + { url = "https://files.pythonhosted.org/packages/9e/bc/a6653249f6ee59ec85dcfec008d9cbc16586dad613963bb17a91b2b993a5/yarl-1.24.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fa5e51397466ea7e98de493fa2ff1b8193cfef8a7b0f9b4842f92d342df0dba", size = 110758, upload-time = "2026-07-20T02:04:34.235Z" }, + { url = "https://files.pythonhosted.org/packages/65/7e/c5a12fb8208df7b981bc82256e7831ce428eeaf893f7bbe6179c57bb9252/yarl-1.24.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4103b77b8a8225e413107d2349b65eb3c1c52627b5cc5c3c4c1c6a798b218950", size = 110120, upload-time = "2026-07-20T02:04:35.85Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/1b659b964626694667b3ec01bf4bcff564b73ae7c48ea1fbfe588b78b461/yarl-1.24.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f9f3e9c8a9ecffa57bef8fb4fa19e5fa4d2d8307cf6bac5b1fca5e5860f4ba00", size = 108834, upload-time = "2026-07-20T02:04:37.67Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/bf48f55c2104e40c15b7b13fad0a5756a11552a55f01c90bc90a66ab81c3/yarl-1.24.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0ebc836c47a6477e182169c6a476fc691d12b518894bf7dd2572f0d59f1c7ed", size = 103442, upload-time = "2026-07-20T02:04:39.576Z" }, + { url = "https://files.pythonhosted.org/packages/37/ac/84b273ac133ecdce598fc1f4140a08a1bf2044048bff8106371d207d105f/yarl-1.24.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:96d30286dd02679e32a39aa8f0b7498fc847fcda46cfc09df5513e82ce252440", size = 117413, upload-time = "2026-07-20T02:04:41.549Z" }, + { url = "https://files.pythonhosted.org/packages/a4/55/9307e03977d3b290dfa42e5d2bae7b6140808fd1786fbe70cd9d3bee53c5/yarl-1.24.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fd8c81f346b58f45818d09ea11db69a8d5fd34a224b79871f6d44f12cd7977b1", size = 109498, upload-time = "2026-07-20T02:04:43.468Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/791a6f314cb4c989c19f8e3a10271f1e469c077143915e52474d80f26b4b/yarl-1.24.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5c55256dee8f4b27bfbf636c8363383c7c8db7890c7cba5217d7bd5f5f21dab6", size = 116062, upload-time = "2026-07-20T02:04:45.319Z" }, + { url = "https://files.pythonhosted.org/packages/19/1a/ddd3807b86055010e2f99aa89b3c640effdb65696766c20597f696f48a1c/yarl-1.24.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9f4d8cf085a4c6a40fb97ea0f46938a8df43c85d31f9d45e2a8867ea9293790d", size = 110941, upload-time = "2026-07-20T02:04:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/6d/03/f34271bba042d2187508bf62aea20a14129efb5a1acfc6a2efe7544630b4/yarl-1.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:240cbec09667c1fed4c6cd0060b9ec57332427d7441289a2ed8875dc9fb2b224", size = 97534, upload-time = "2026-07-20T02:04:48.774Z" }, + { url = "https://files.pythonhosted.org/packages/e4/02/ecc8dc31b9f355731e700f8402b8075d2ea1737dbc4baf4abf0f0fc64288/yarl-1.24.5-cp310-cp310-win_arm64.whl", hash = "sha256:8a6987eaad834cb32dd57d9d582225f0054a5d1af706ccfbbdba735af4927e13", size = 93603, upload-time = "2026-07-20T02:04:50.686Z" }, + { url = "https://files.pythonhosted.org/packages/fe/db/3cb5df059756a45761cc3dee8fd25ec82b83a6585ea3542b969fda850f99/yarl-1.24.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2c1fe720934a16ea8e7146175cba2126f87f54912c8c5435e7f7c7a51ef808d3", size = 135043, upload-time = "2026-07-20T02:04:52.39Z" }, + { url = "https://files.pythonhosted.org/packages/44/f8/767d6bd5a03db63bc467df2fb56d6fafeae9667d74aea92cd6af399f828b/yarl-1.24.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c687ed078e145f5fd53a14854beff320e1d2ab76df03e2009c98f39a0f68f39a", size = 96942, upload-time = "2026-07-20T02:04:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/ce/97/10b939c44d7b28d1dbc389cfc7012306d1ea8dba01eaef44b39fffaee52a/yarl-1.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:709f1efed56c4a145793c046cd4939f9959bcd818979a787b77d8e09c57a0840", size = 97046, upload-time = "2026-07-20T02:04:56.638Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7a/b410dbe39b6255c55fb2a2bcee96eb844d0789235ddc381a889a90dc72d6/yarl-1.24.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874019bd513008b009f58657134e5d0c5e030b3559bd0553976837adf52fe966", size = 110512, upload-time = "2026-07-20T02:04:58.955Z" }, + { url = "https://files.pythonhosted.org/packages/83/c7/da591971f78a5617e1f21f5699858ebccd836fe181a6493788ffc91ba69b/yarl-1.24.5-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a4582acf7ef76482f6f511ebaf1946dae7f2e85ec4728b81a678c01df63bd723", size = 102454, upload-time = "2026-07-20T02:05:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8e/73b0ed4de47289a78a96045d76d1cfe5e41848bf0da59ce25b2ec87ee05d/yarl-1.24.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2cabe6546e41dabe439999a23fcb5246e0c3b595b4315b96ef755252be90caeb", size = 117617, upload-time = "2026-07-20T02:05:02.325Z" }, + { url = "https://files.pythonhosted.org/packages/cf/14/b744747bc4f57a8d55bd744df463457524583e1e9f7538b5ace0346ab92e/yarl-1.24.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:17f57620f5475b3c69109376cc87e42a7af5db13c9398e4292772a706ff10780", size = 116135, upload-time = "2026-07-20T02:05:04.05Z" }, + { url = "https://files.pythonhosted.org/packages/66/ca/95aa4d0e5b7ea4f20e4d577c42d001ed9df207569fdb063cc5ed4ebb496b/yarl-1.24.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:570fec8fbd22b032733625f03f10b7ff023bc399213db15e72a7acaef28c2f4e", size = 111935, upload-time = "2026-07-20T02:05:05.738Z" }, + { url = "https://files.pythonhosted.org/packages/72/0d/d2ad8d6b147832d177a4e720ba1962fe686eb0913b74503b3eca094b8bba/yarl-1.24.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5fede79c6f73ff2c3ef822864cb1ada23196e62756df53bc6231d351a49516a2", size = 110010, upload-time = "2026-07-20T02:05:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/50/18/eb335e4120903903f4865041355ae46256a2406eb2865bc24827f4f27b61/yarl-1.24.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ccf9aca873b767977c73df497a85dbedee4ee086ae9ae49dc461333b9b79f58", size = 110058, upload-time = "2026-07-20T02:05:09.246Z" }, + { url = "https://files.pythonhosted.org/packages/44/70/97353add32c62ad6f206d948ac5a5ee84398225e534dc6ed6433d1b335b6/yarl-1.24.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ad5d8201d310b031e6cd839d9bac2d4e5a01533ce5d3d5b50b7de1ef3af1de61", size = 103308, upload-time = "2026-07-20T02:05:11.31Z" }, + { url = "https://files.pythonhosted.org/packages/68/39/5e7398d4b6f6b3c9062823ebc60802df5b272e3fe9e788f9734c6ee46c85/yarl-1.24.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:841f0852f48fefea3b12c9dfec00704dfa3aef5215d0e3ce564bb3d7cd8d57c6", size = 116898, upload-time = "2026-07-20T02:05:13.099Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/09e52f2239e8b96357eccca05915382e4ba5405ebfb623b6036040d99654/yarl-1.24.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:9baafc71b04f8f4bb0703b21d6fc9f0c30b346c636a532ff16ec8491a5ea4b1f", size = 109400, upload-time = "2026-07-20T02:05:14.821Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6a/e94133d4c2d1a14d2384310bf3e79d9cf32c9d1eae1c6f034fb80d098fa1/yarl-1.24.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d897129df1a22b12aeed2c2c98df0785a2e8e6e0bde87b389491d0025c187077", size = 115934, upload-time = "2026-07-20T02:05:17.78Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/34955ed967b976fc38edcbb6d538dee79dbda4cb7fc7f72a0907a7c78e0f/yarl-1.24.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd625535328fd9882374356269227670189adfcc6a2d90284f323c05862eecbd", size = 112178, upload-time = "2026-07-20T02:05:19.675Z" }, + { url = "https://files.pythonhosted.org/packages/f5/46/d7bd3a8859d47dcfaffd7127af7076032a7da278a9a02e17b5f37bfb6712/yarl-1.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:f4239bbec5a3577ddb49e4b50aeb32d8e5792098262ae2f63723f916a29b1a25", size = 97544, upload-time = "2026-07-20T02:05:21.523Z" }, + { url = "https://files.pythonhosted.org/packages/01/69/c1bfd21e32c638974ea2c542a0b8c53ef1fa9eff336020f5d014f9503ff2/yarl-1.24.5-cp311-cp311-win_arm64.whl", hash = "sha256:3ac6aff147deb9c09461b2d4bbdf6256831198f5d8a23f5d37138213090b6d8a", size = 93359, upload-time = "2026-07-20T02:05:23.493Z" }, + { url = "https://files.pythonhosted.org/packages/1b/84/71d051c850b5af41d168c679d9eb67eb7c55283ac4ee131673edf134bc4e/yarl-1.24.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d693396e5aea78db03decd60aec9ece16c9b40ba00a587f089615ff4e718a81d", size = 136035, upload-time = "2026-07-20T02:05:25.489Z" }, + { url = "https://files.pythonhosted.org/packages/03/4d/8ad27f9a1b7e69313cca5d695b925b48efe51208d3490e0844bae97cabc0/yarl-1.24.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3363fcc96e665878946ad7a106b9a13eac0541766a690ef287c0232ac768b6ec", size = 97642, upload-time = "2026-07-20T02:05:27.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/05b4131c407006cd1e410e9c6539f16a0945724677e5364447313c15ea3e/yarl-1.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9d399bdcfb4a0f659b9b3788bbc89babe63d9a6a65aacdf4d4e7065ff2e6316c", size = 97323, upload-time = "2026-07-20T02:05:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/20/16/e618c875c73e0e39611f20a581b3d5e8d59b8857bf001bee3263044c6deb/yarl-1.24.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90333fd89b43c0d08ac85f3f1447593fc2c66de18c3d6378d7125ea118dc7a54", size = 107741, upload-time = "2026-07-20T02:05:31.367Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c4defeaf3ed33fcb346aacf9c6e971a8d4e2bde04a0310e79abb208e7965/yarl-1.24.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:665b0a2c463cc9423dd647e0bfd9f4ccc9b50f768c55304d5e9f80b177c1de12", size = 103570, upload-time = "2026-07-20T02:05:33.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e7/0e0e0de5865ebd5914537ef486f36c727a59865c3ac0cf5ff1b32aececbf/yarl-1.24.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e006d3a974c4ee19512e5f058abedb6eef36a5e553c14812bdeba1758d812e6d", size = 115815, upload-time = "2026-07-20T02:05:35.292Z" }, + { url = "https://files.pythonhosted.org/packages/2b/27/ca56b700cb170aba25a3893b75355b213935657dc5714d2383354a270e62/yarl-1.24.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7d42c531243450ef0d4d9c172e7ed6ef052640f195629065041b5add4e058d1", size = 116025, upload-time = "2026-07-20T02:05:37.503Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d0/d56c859b8222116f5d68459199f48359e0bf121b6f65a69bf329b3602ba0/yarl-1.24.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f08c7513ecef5aad65687bfdf6bc601ae9fccd04a42904501f8f7141abad9eb9", size = 109835, upload-time = "2026-07-20T02:05:39.506Z" }, + { url = "https://files.pythonhosted.org/packages/70/a2/3a35557e4d1a79425040eba202ccaf08bdc8717680fc77e2498a1ad2e0a5/yarl-1.24.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c95b17fe34ed802f17e205112e6e10db92275c34fee290aa9bdc55a9c724027", size = 108884, upload-time = "2026-07-20T02:05:41.584Z" }, + { url = "https://files.pythonhosted.org/packages/e4/35/ef4c26356b7913c68983bac2d72a4212b3347af551cb8d250b99b5ed7b7f/yarl-1.24.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56b149b22de33b23b0c6077ab9518c6dcb538ad462e1830e68d06591ccf6e38b", size = 107308, upload-time = "2026-07-20T02:05:43.697Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/ff0dc66c2ccf3e0153ab97ff61eabab4400e6a5264af427ab30cd69f1857/yarl-1.24.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a8fe66b8f300da93798025a785a5b90b42f3810dc2b72283ff84a41aaaebc293", size = 103646, upload-time = "2026-07-20T02:05:45.895Z" }, + { url = "https://files.pythonhosted.org/packages/74/f0/33b9271c7f881766359d58266fa0811d2e5210ed860e28da7dc6d7786344/yarl-1.24.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:377fe3732edbaf78ee74efdf2c9f49f6e99f20e7f9d2649fda3eb4badd77d76e", size = 115305, upload-time = "2026-07-20T02:05:47.832Z" }, + { url = "https://files.pythonhosted.org/packages/ef/65/fd79fb1868c4a80db8661091de525bf430f63c3bea1b20e8b6a84fc7d359/yarl-1.24.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e8ffa78582120024f476a611d7befc123cee59e47e8309d470cf667d806e613b", size = 108404, upload-time = "2026-07-20T02:05:49.604Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ba/dbabe6b262f17a816c70cfc09558dbf03ece3ec76684d02f911a3d3a189c/yarl-1.24.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:daba5e594f06114e37db186efd2dd916609071e59daca901a0a2e71f02b142ce", size = 115940, upload-time = "2026-07-20T02:05:51.741Z" }, + { url = "https://files.pythonhosted.org/packages/a5/43/fab2d1dad9d340a268cdde63756a123d069723efff6a372d123fa74a9517/yarl-1.24.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:65be18ec59496c13908f02a2472751d9ef840b4f3fb5726f129306bf6a2a7bba", size = 110006, upload-time = "2026-07-20T02:05:53.554Z" }, + { url = "https://files.pythonhosted.org/packages/c4/27/41eb51bbd1b8d89546b83897cfb0164f1e109304fd408dbb151b639eec0f/yarl-1.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:a929d878fec099030c292803b31e5d5540a7b6a31e6a3cc76cb4685fc2a2f51b", size = 97618, upload-time = "2026-07-20T02:05:55.57Z" }, + { url = "https://files.pythonhosted.org/packages/3c/25/b2553764b3d65db711d8f45416351ec4f420847558eb669edcbcaadf5780/yarl-1.24.5-cp312-cp312-win_arm64.whl", hash = "sha256:7ce27823052e2013b597e0c738b13e7e36b8ccb9400df8959417b052ab0fd92c", size = 93018, upload-time = "2026-07-20T02:05:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/e1/63/64ef361967cc983573149dc1515d531db5da8a4c92d22bb833d59e01b313/yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2", size = 135075, upload-time = "2026-07-20T02:05:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/bb/89/55920fd853ce43e608adbc3962456f0d649d6bb15250dc2988321da0fe1c/yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb", size = 97225, upload-time = "2026-07-20T02:06:01.769Z" }, + { url = "https://files.pythonhosted.org/packages/15/f0/7688d3f2cfff7590df2af38ec46d969f4281a4dddb08a9ad2eafbcdddf98/yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075", size = 96751, upload-time = "2026-07-20T02:06:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/a851a0f94aaaf379dd4f901bfc80f634280bec51eb260b47363e2a4cd62e/yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff", size = 107960, upload-time = "2026-07-20T02:06:05.699Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a8/faea066c12f9c77ca0de90641f1655f9dd7b412477bf28c76d692f3aecff/yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448", size = 103500, upload-time = "2026-07-20T02:06:07.556Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/1e67084c2a6e2f2db0e3be798328cb3be42c0119b621d25461479a224d21/yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f", size = 115780, upload-time = "2026-07-20T02:06:09.599Z" }, + { url = "https://files.pythonhosted.org/packages/58/86/1f94664e147474337e3359f52012cf3d02f825f694317b178bfba1078c62/yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd", size = 115308, upload-time = "2026-07-20T02:06:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/0a/43/8e55ae7538ba5f28ccb3c845c6dd4549cf7016d5992e5326512519107cdd/yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16", size = 110574, upload-time = "2026-07-20T02:06:13.129Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ba/a889ec8765cedcf2ac44dcb02d6a21e4861399b243b263c5f2dde27ee740/yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213", size = 109914, upload-time = "2026-07-20T02:06:15.243Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c3/e45f821af67b791c2dbbe4a9f4137a1d33f8d386654a05a0c3f47bdfa25d/yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24", size = 107712, upload-time = "2026-07-20T02:06:17.443Z" }, + { url = "https://files.pythonhosted.org/packages/02/00/2ab0f42c9857fcb490bfaa6647b14540b53d241ab209f23220b958cc5832/yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385", size = 104251, upload-time = "2026-07-20T02:06:19.259Z" }, + { url = "https://files.pythonhosted.org/packages/7a/70/709d9a286e98af2c7fd8e4e6cada658b5c0e30d87dd7e2a63c2fb5767217/yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c", size = 115319, upload-time = "2026-07-20T02:06:21.207Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6c/3eaa515142991fe84cfc483ff986492211f1978f90161ccefdbec919d09b/yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4", size = 109163, upload-time = "2026-07-20T02:06:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/bb/64/711dafce66c323a3144d470547a71c5384c57623308ac8bb5e4b903ac148/yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144", size = 115435, upload-time = "2026-07-20T02:06:24.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f3/9b9d0e6d84bea851eb1ba99e4bdc755b86fd813e49ec86dfe42f26befdef/yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4", size = 110691, upload-time = "2026-07-20T02:06:26.973Z" }, + { url = "https://files.pythonhosted.org/packages/86/e4/62a06b7e87c4246ac76b7c2da136f972eb4a3a1fc94abb07e7022d6fdb0a/yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740", size = 97454, upload-time = "2026-07-20T02:06:29.163Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/5fc8025b318ab10db413b61056bd0d95c557a70e8df4210c7511f866329c/yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1", size = 92813, upload-time = "2026-07-20T02:06:31.113Z" }, + { url = "https://files.pythonhosted.org/packages/a9/08/5f3085fef9564217074db9dd8573de1795bc82cde61a7ad10b6a7234a569/yarl-1.24.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2729fcfc4f6a596fb0c50f32090400aa9367774ac296a00387e65098c0befa76", size = 135680, upload-time = "2026-07-20T02:06:33.273Z" }, + { url = "https://files.pythonhosted.org/packages/98/35/ba9436e579bd48a8801f2021d842d9ab4994c26e4c7dd3a4c1f1bcb57a9e/yarl-1.24.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ff330d3c30db4eb6b01d79e29d2d0b407a7ecad39cfd9ec993ece57396a2ec0d", size = 97395, upload-time = "2026-07-20T02:06:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/18/a9/a07f76f3c44e02b25cc743af5ef93eef27f7013eadca770451b6a6ccb5db/yarl-1.24.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e42d75862735da90e7fc5a7b23db0c976f737113a54b3c9777a9b665e9cbff75", size = 97223, upload-time = "2026-07-20T02:06:37.216Z" }, + { url = "https://files.pythonhosted.org/packages/77/f7/a9a1d6fa7dd9e388f95b30f6ad3ec4e285f6c8f61f44ce16070c3fcfe414/yarl-1.24.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3732e66413163e72508da9eff9ce9d2846fde51fae45d3605393d3e6cd303e9", size = 108777, upload-time = "2026-07-20T02:06:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/2f/44/e0b86c302471fabd6f02808ecf2ac52b8412b624787849d4bf2cdb466f6f/yarl-1.24.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5b8ee53be440a0cffc991a27be3057e0530122548dbe7c0892df08822fce5ede", size = 103119, upload-time = "2026-07-20T02:06:41.456Z" }, + { url = "https://files.pythonhosted.org/packages/d1/16/9c16d180bf8faaf223225eb50e1245870ff1ae0e302a27153988e65c51fd/yarl-1.24.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:af3aefa655adb5869491fa907e652290386800ae99cc50095cba71e2c6aefdca", size = 116471, upload-time = "2026-07-20T02:06:43.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8d/b219b9df28a02ce95cfbdd41d2f7caa5669d0ff979c1c9975697145e33c5/yarl-1.24.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2120b96872df4a117cde97d270bac96aea7cc52205d305cf4611df694a487027", size = 115974, upload-time = "2026-07-20T02:06:45.874Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e8/f20557aca240d88e69850ad1ee91756821d094bb1310565c04d25c6682a2/yarl-1.24.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66410eb6345d467151934b49bfa70fb32f5b35a6140baa40ad97d6436abea2e9", size = 110830, upload-time = "2026-07-20T02:06:47.852Z" }, + { url = "https://files.pythonhosted.org/packages/db/18/199b85109a53eeca64ee19c9cca228287e8e4ab0cc1a09b28f530e65cce0/yarl-1.24.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4af7b7e1be0a69bee8210735fe6dcfc38879adfac6d62e789d53ba432d1ffa41", size = 110054, upload-time = "2026-07-20T02:06:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/ed28147f8cd7f48c49367c90713b30a555284b6105a6a56f3a05568da795/yarl-1.24.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa139875ff98ab97da323cfadfaff08900d1ad42f1b5087b0b812a55c5a06373", size = 108312, upload-time = "2026-07-20T02:06:51.835Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/55e16ae0a5c227cea8df1c6871ba57d614a34243146c05729caf2a1bd9c5/yarl-1.24.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:0055afc45e864b92729ac7600e2d102c17bef060647e74bca75fa84d66b9ff36", size = 103662, upload-time = "2026-07-20T02:06:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ea/dbd7c2caec459c9a426f18b02688ecbfb58620d0f6a3422d24769fbaf8ab/yarl-1.24.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f0e466ed7511fe9d459a819edbc6c2585c0b6eabde9fa8a8947552468a7a6ef0", size = 116090, upload-time = "2026-07-20T02:06:56.015Z" }, + { url = "https://files.pythonhosted.org/packages/06/84/39ce4ce3059e07fece5fbdbee8c4053406af9aca911ce9fa5f8548aab6af/yarl-1.24.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f141474e85b7e54998ec5180530a7cda99ab29e282fa50e0756d89981a9b43c5", size = 109523, upload-time = "2026-07-20T02:06:57.926Z" }, + { url = "https://files.pythonhosted.org/packages/a9/8b/71ff44137b405c64a7788075669c24010019f57a7464b78c3a6cbee539d9/yarl-1.24.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:e2935f8c39e3b03e83519292d78f075189978f3f4adc15a78144c7c8e2a1cba5", size = 116084, upload-time = "2026-07-20T02:06:59.868Z" }, + { url = "https://files.pythonhosted.org/packages/62/c0/423078fdd4042e1862c11f0ffd977a0ffa393783c12bee94685923bc189e/yarl-1.24.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9d1216a7f6f77836617dba35687c5b78a4170afc3c3f18fc788f785ba26565c4", size = 111006, upload-time = "2026-07-20T02:07:01.907Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/6daa2ee9d95e5c98b8128f8df91eb692eb423ab274b8cf08db52152fad26/yarl-1.24.5-cp314-cp314-win_amd64.whl", hash = "sha256:5ba4f78df2bcc19f764a4b26a8a4f5049c110090ad5825993aacb052bf8003ad", size = 99215, upload-time = "2026-07-20T02:07:03.852Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0e/464a847d7359e0da75dd9fc5c1d1aa35d0159ea31e5f8e66a3c1c29ff3d0/yarl-1.24.5-cp314-cp314-win_arm64.whl", hash = "sha256:9e4e16c73d717c5cf27626c524d0a2e261ad20e46932b2670f64ad5dde23e26f", size = 94566, upload-time = "2026-07-20T02:07:06.074Z" }, + { url = "https://files.pythonhosted.org/packages/e2/55/e03acc4446772660bc335e86e41ef31e4d0d838fd641531a11a5ee33b493/yarl-1.24.5-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e1ae548a9d901adca07899a4147a7c826bbcc06239d3ce9a59f57886a28a4c88", size = 142533, upload-time = "2026-07-20T02:07:08.284Z" }, + { url = "https://files.pythonhosted.org/packages/ae/71/4acd3a1fc7cf14345cdb302665ecd2097f62c365b4f14ca17d4f37775cf9/yarl-1.24.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:ff405d91509d88e8d44129cd87b18d70acd1f0c1aeabd7bc3c46792b1fe2acba", size = 100776, upload-time = "2026-07-20T02:07:10.197Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/cfb76b7fe99686db264bff829779a539d923e7564ffd7ef18da6c54c3774/yarl-1.24.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47e98aab9d8d82ff682e7b0b5dded33bf138a32b817fcf7fa3b27b2d7c412928", size = 100913, upload-time = "2026-07-20T02:07:12.357Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3f/7116e782992abbd4fb6948488aec72078895e929a23078290739e8396fce/yarl-1.24.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f0a658a6d3fafee5c6f63c58f3e785c8c43c93fbc02bf9f2b6663f8185e0971f", size = 106507, upload-time = "2026-07-20T02:07:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/d4d2d73ee78229cc889872eb8e085d8f5c6f51abdb178409fd9b23cf74fd/yarl-1.24.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4377407001ca3c057773f44d8ddd6358fa5f691407c1ba92210bd3cf8d9e4c95", size = 99219, upload-time = "2026-07-20T02:07:16.019Z" }, + { url = "https://files.pythonhosted.org/packages/3e/fa/a6df1a9bccd644eec00abee0dff4277416222cec435330fd1f2858523ec1/yarl-1.24.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7c0494a31a1ac5461a226e7947a9c9b78c44e1dc7185164fa7e9651557a5d9bc", size = 111804, upload-time = "2026-07-20T02:07:18.141Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/7b2a1f4bcc20e9447156dd2b1c4d01f70d9df0759025ee7d09a84ffae134/yarl-1.24.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a7cff474ab7cd149765bb784cf6d78b32e18e20473fb7bda860bce98ab58e9da", size = 110943, upload-time = "2026-07-20T02:07:20.06Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/22c92affb0f9b623ca753d27d968b5625b868f12c6378d049d55ae247643/yarl-1.24.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cbb833ccacdb5519eff9b8b71ee618cc2801c878e77e288775d77c3a2ced858a", size = 108251, upload-time = "2026-07-20T02:07:22.217Z" }, + { url = "https://files.pythonhosted.org/packages/45/44/5769b96298c1e195fb412997b6090af2a84105cf59c17613558a2d011d1f/yarl-1.24.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:82f75e05912e84b7a0fe57075d9c59de3cb352b928330f2eb69b2e1f54c3e1f0", size = 106025, upload-time = "2026-07-20T02:07:24.083Z" }, + { url = "https://files.pythonhosted.org/packages/4c/40/009e8e791fd9762c0e1567e69248acb4f49064597e1680874c16dd8bb798/yarl-1.24.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:16a2f5010280020e90f5330257e6944bc33e73593b136cc5a241e6c1dc292498", size = 106573, upload-time = "2026-07-20T02:07:26.248Z" }, + { url = "https://files.pythonhosted.org/packages/20/c6/b7480578f8a0a80946f36ad6df547ecec704f9ba69d2de60f8aa6f1c1cbf/yarl-1.24.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:ffcd54362564dc1a30fb74d8b8a6e5a6b11ebd5e27266adc3b7427a21a6c9104", size = 100751, upload-time = "2026-07-20T02:07:28.098Z" }, + { url = "https://files.pythonhosted.org/packages/d4/27/4476f3360b91a48c5cf125e91f59a3bd35299d84a431a258d57f5977bb11/yarl-1.24.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0465ec8cedc2349b97a6b595ace64084a50c6e839eca40aa0626f38b8350e331", size = 111643, upload-time = "2026-07-20T02:07:30.88Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/5cdd3e5ee944e8af31e52f6cd3d3af5fd7b937e036ccbbba2c9ffebede95/yarl-1.24.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4db9aecb141cb7a5447171b57aa1ed3a8fee06af40b992ffc31206c0b0121550", size = 106312, upload-time = "2026-07-20T02:07:33.06Z" }, + { url = "https://files.pythonhosted.org/packages/18/86/f406b0c2a6f99575de2da671ef47aa06f89a5be83a27a46971c3b86cecdb/yarl-1.24.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f540c013589084679a6c7fac07096b10159737918174f5dfc5e11bf5bca4dfe6", size = 110379, upload-time = "2026-07-20T02:07:35.155Z" }, + { url = "https://files.pythonhosted.org/packages/f0/6c/9f3adfbd3b30b4fa0f7ccb3a83eba2c1152d3fff554d535e640ba0f7ba2b/yarl-1.24.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a61834fb15d81322d872eaafd333838ae7c9cea84067f232656f75965933d047", size = 108497, upload-time = "2026-07-20T02:07:37.35Z" }, + { url = "https://files.pythonhosted.org/packages/dd/37/91eb2e5ca883a529c1b390348a74cd9fc0512171727f547ce70bfe02be5c/yarl-1.24.5-cp314-cp314t-win_amd64.whl", hash = "sha256:5c88e5815a49d289e599f3513aa7fde0bc2092ff188f99c940f007f90f53d104", size = 102450, upload-time = "2026-07-20T02:07:39.578Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f4/ed5c402ac8fde4403ed3366c2716bfddc8a6677ebd59f3d62772cc7fe468/yarl-1.24.5-cp314-cp314t-win_arm64.whl", hash = "sha256:cf139c02f5f23ef6532040a30ff662c00a318c952334f211046b8e60b7f17688", size = 97222, upload-time = "2026-07-20T02:07:41.55Z" }, + { url = "https://files.pythonhosted.org/packages/61/02/962c1cbfc401a30c1d034dc67ff395f64b52302c6d62de556c1fca99acc0/yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7", size = 58612, upload-time = "2026-07-20T02:07:43.461Z" }, +]