diff --git a/models/tts/kokoro-v1.1-zh/AGENTS.md b/models/tts/kokoro-v1.1-zh/AGENTS.md index 339877e..14ed7b2 100644 --- a/models/tts/kokoro-v1.1-zh/AGENTS.md +++ b/models/tts/kokoro-v1.1-zh/AGENTS.md @@ -5,7 +5,7 @@ planning changes, asking product-scope questions, or starting model execution. It contains the shared requirements, artifact gaps, gate sequence, and runbook. - Preserve settled scope: one female voice, English/Mandarin/code-switching, - compact Kokoro v1.1-zh-compatible adaptation, FluidAudio/Core ML direction; + compact Kokoro v1.1-zh-compatible PyTorch adaptation; Core ML is deferred; no voice cloning or runtime multi-speaker expansion. - Inspect the receiving environment's existing setup and approvals first. Ask only for genuinely missing inputs; do not make the user repeat settled goals. diff --git a/models/tts/kokoro-v1.1-zh/project-handoff.md b/models/tts/kokoro-v1.1-zh/project-handoff.md index cda6e5f..d175892 100644 --- a/models/tts/kokoro-v1.1-zh/project-handoff.md +++ b/models/tts/kokoro-v1.1-zh/project-handoff.md @@ -1,734 +1,40 @@ -# English–Mandarin single-voice Kokoro: project handoff +# Kokoro bilingual project status -Updated: 2026-09-15. Shared requirements and implementation runbook, not evidence -that a trainer or production model already exists. Start here when continuing -this project in another environment. Internal agent execution plans stay in -ignored `.mobius/`; this project contract is intentionally tracked. +Current deliverable: a **PyTorch model and matching voice weights** for one female +English/Mandarin voice, including code-switching. Core ML and Apple integration +are deferred. Keep Kokoro v1.1-zh's compact architecture and vocabulary; this is +not a multi-speaker product. -Jump to [artifact inventory](#2-what-is-actually-available), -[data requirements](#5-data-establish-what-we-have-the-dataset-means), -[implementation gates](#6-harness-work-packages-and-required-gates), -[evaluation](#7-automated-evaluation-available-proxies-and-required-improvements), -[export/release](#8-export-apple-integration-and-production-gates), or -[open inputs and receiver checklist](#9-evidence-package-and-genuinely-unresolved-inputs). +## Start here -## 1. Read this before asking the user to repeat the project +- [Training and inference commands](training/README.md) +- [Selected model, methods, results, and limits](training/MODEL_CARD.md) +- [Machine-readable results and hashes](training/results.json) -Build a compact, good-quality TTS model with **one female voice that speaks -English, Mandarin, and natural English–Mandarin code-switching**. Start with -**Kokoro v1.1-zh**, build a compatible training harness, reproduce the untouched -checkpoint, and only then fine-tune on verified real bilingual recordings. -The deployment direction is **FluidAudio / Core ML on Apple devices**. +## What exists -The source Mac is for small samples, demos, code preparation, and lightweight -tests—not official quality evaluation or training. The receiving environment -has reportedly been settled already. Inspect its existing setup and prior -approvals; do not restart provider selection or ask the six generic product -questions again. Its actual hardware, dataset paths, and budget are not recorded -in this checkout. +Training ran on an RTX A6000 using real EMIME MF5 English/Mandarin recordings. +The selected deterministic checkpoint is update 500. Strict checkpoint loading, +untouched baseline parity, real-data gradients, micro-overfit, exact restart, +export parity, and offline CPU/GPU inference are implemented and checked. +Model weights, real recordings, and generated evaluation audio remain local. +Run artifacts are under ignored `.runs/` and `.artifacts/`. -This handoff does **not** independently authorize paid jobs, provisioning, -uploading recordings, publishing weights, or deploying to users. Preserve any -valid authorization already recorded in the receiving environment. Ask only -for a genuinely missing decision that blocks the next consequential action. -Code inspection, implementation, and lightweight tests can continue while -data access or run approval is being resolved. +The frozen 74-utterance test improved English WER from 6.15% to 4.31%; Mandarin +CER increased from 1.50% to 1.93%. Mixed ASR is unreliable when the recognizer +translates the utterance. This model is not production-qualified. -### Decision ledger +## Remaining work -| Subject | Status | Instruction | -| --- | --- | --- | -| Languages | User-decided | English + Mandarin, including within-sentence switching. “Chinese” here means Mandarin, not Cantonese or every Chinese language. | -| Voice count | User-decided | One female output voice; no cloning or runtime multi-speaker product. | -| Priority | User-decided | Quality first, while keeping the model compact; do not expand scope to many voices. | -| Starting model | User-decided | Kokoro v1.1-zh; compatible StyleTTS2-derived fine-tuning, not training a new architecture from scratch. | -| Existing sample voice | Verified demo fact | `zf_001`, speed 1, native 24-kHz mono. This is the baseline reference, not proof that our eventual recorded speaker is `zf_001`. | -| Runtime direction | Established project direction | FluidAudio/Core ML/Apple; exact oldest device, hard size/latency ceilings, and supported OS matrix still need confirmation. | -| Local execution | User-decided | Source Mac is demo/preparation only. Do not run a full suite or optimizer there. | -| Remote environment | Reportedly decided elsewhere | Recover its actual specification and authorization locally in that environment. No automatic provider change. | -| Architecture preservation | Proposed engineering default | Keep the approximately 82M-parameter generator and its interfaces first. Optimize size only after quality/parity work. | -| First training scope | Proposed engineering default | Conservative supervised adaptation before decoder/adversarial changes; validate the schedule rather than assuming it works. | -| Streaming | Not specified | Do not add a new streaming architecture as an implicit first-release requirement. Establish whether chunked playback meets the actual product need. | -| Data | User said datasets were researched | An accessible, audited single-speaker bilingual manifest has not been recovered here. Find the earlier selection/recordings before sourcing replacements. | -| Production acceptance | Not yet fixed here | Calibrate and freeze a protocol and thresholds before selecting a winning run. Historical demo scores are not acceptance targets. | +- Bilingual listening review: pronunciation, tones, naturalness, language switches, + and voice consistency. Report failures rather than inferring quality from ASR. +- Better real same-speaker code-switch coverage and session-disjoint evaluation. +- Production voice rights/consent and release acceptance. -PocketTTS and Dia2 were explored earlier; they are not the selected baseline. -Kokoro-French was discussed as a conversion/export reference, not as a French -training objective. No exact French repository/revision is established in this -handoff. Do not invent that dependency: the existing Mobius Kokoro-zh exporter -is the concrete starting point. Revisit architecture only if a documented -compatibility or quality gate fails and the user agrees to the change. +Preserve real recordings and test splits. Generated speech is evaluation audio, +never substitute training data. Publishing weights or deploying is a separate +action requiring applicable authorization. Do not restart dataset acquisition +or request Swift/Apple access as a prerequisite to the existing PyTorch work. -## 2. What is actually available - -| Artifact | Location/status | What it does **not** establish | -| --- | --- | --- | -| 12-clip demo metadata | [coreml/bilingual-demo](coreml/bilingual-demo/README.md), included in PR #94 | Not a training set, sealed test set, or official quality baseline. | -| Saved phonemes, IDs, diagnostics, hashes | [manifest.json](coreml/bilingual-demo/manifest.json), `evidence/`, `provenance/` | Hashes identify artifacts; they do not transfer source code, models, or WAVs. | -| Offline demo integrity checker | `coreml/bilingual-demo/verify-demo.py`, nine tests | No synthesis, ASR, learned scoring, acoustic assessment, or training. | -| Original 12 WAVs | Source workspace only; Git-ignored; about 4.2 MiB / 44.625 seconds | Public playback links are unavailable until the exact matching WAVs are supplied. | -| Existing Core ML converter | [coreml/scripts/convert-coreml.py](coreml/scripts/convert-coreml.py), other scripts and docs in `coreml/` | Not a trainer or a demonstrated exporter of our future fine-tuned checkpoint. | -| Swift bilingual frontend | Modified FluidAudio source checkout, under `Sources/FluidAudio/TTS/KokoroAne/` | Not shipped by this Mobius PR. An ordinary FluidAudio clone is not proven to reproduce the demo. | -| Local evaluation prototype | FluidAudio `Benchmarks/tts/bilingual/` | Not included here; Swift/Core ML rendering and MLX scoring are not a ready CUDA stack. | -| Local 300-prompt development suite | Source workspace evaluation artifacts | Not sealed; not licensed/consented target-speaker training recordings. | -| Earlier training proposal | Source workspace private `.mobius/kokoro-bilingual-training/PROPOSAL.md` | Not transferred through Git; speculative settings there are not approved requirements. This handoff supersedes it for cross-environment continuation. | -| Compatible training graph, gradient proof, pilot checkpoint | **Not established** | Do not report training readiness or a trained model from the demo. | -| Audited data/splits, official baseline, final acceptance report | **Not established here** | Recover any additional work already done in the receiving environment, with evidence. | - -The upstream model card describes an 82M StyleTTS2/iSTFTNet-derived release -supporting English and Chinese, with no released diffusion/style-encoder -training package. Its published training-data history is **not our accessible -dataset or a grant to reuse those recordings**. -[Upstream model card](https://huggingface.co/hexgrad/Kokoro-82M-v1.1-zh). - -### Verify that the receiving checkout contains this handoff - -From a clean Mobius checkout, after preserving any unrelated work: - -```bash -gh pr checkout 94 --repo FluidInference/mobius -git branch --show-current -git log -1 --oneline -git status --short -test -f models/tts/kokoro-v1.1-zh/project-handoff.md -cd models/tts/kokoro-v1.1-zh/coreml/bilingual-demo -uv run --frozen python verify-demo.py -uv run --frozen python -m unittest discover -s tests -v -``` - -The PR branch is `docs/kokoro-bilingual-demo`. The initial demo commit was -`50e10ab596e686fefd2422eea5d22ffbcb17e7f2`; that commit alone does **not** contain -this handoff. If the PR has merged, use the branch containing the merged -handoff instead. Do not discard local changes to perform this checkout. -The checker should validate 12 metadata records and explicitly say audio was -not checked. For already-supplied WAVs, add `--audio-dir `. -This verifies bytes, not quality, and does not invoke models. - -### Transfer/acquisition ledger the receiving agent must complete - -For each required artifact, record owner, accessible URI/path, revision/hash, -access restrictions, and status: `present`, `verified`, or `missing`. - -1. Mobius checkout containing this file and the existing conversion code. -2. FluidAudio source commit **plus the relevant uncommitted patch if needed**. - Match frontend files against `signature.tts_source_files` in - [run provenance](coreml/bilingual-demo/provenance/run.json). A commit ID alone - cannot describe the dirty source tree used by the demo. Transfer only the - scoped source changes, not the user's whole workspace. -3. Original demo WAVs if byte-level reproduction/listening is required. Missing - demo WAVs do not prevent designing the harness or text-only tests. -4. The local evaluator/corpus code if reusing it; otherwise implement a new, - explicitly versioned backend with compatible output records. Do not claim - this PR already includes that code. -5. Original PyTorch model, configuration, vocabulary, and baseline voice pack - from approved caches or upstream; separately identify deployed Core ML - model assets. Converted asset hashes are not the PyTorch checkpoint hash. -6. Actual target-speaker recordings/transcripts, earlier dataset research, - provenance/consent records, and split manifests. Never substitute demo TTS - output for these recordings. -7. Existing environment/run authorization, storage destinations, and secret - access through the environment's normal secret mechanism. Never put access - tokens, private recording URLs, or personal paths in committed documents. - -## 3. Immediate findings: fix inputs before teaching the model - -The detailed evidence and uncertainty are in -[ISSUES.md](coreml/bilingual-demo/ISSUES.md). No listed fix was implemented by -the demo or by this documentation handoff. - -| ID | Evidence | Required follow-up | -| --- | --- | --- | -| F1: numeral/erhua | `这本书的价格是二十三元五角。` has a saved phoneme sequence consistent with 二 being merged into the preceding syllable; ASR returns 13 rather than 23. | Preserve source-character/word-role information and restrict erhua eligibility. Add regressions for numeral 二, real 儿 suffixes, independent 儿 words, punctuation, and word boundaries. Do not disable all erhua. | -| F2: API | Saved input has word-like `ˈæpi`. | Proposed exact-token A–P–I pronunciation policy; validate vocabulary IDs. Do not spell every uppercase word as letters. | -| F3: GitHub | Saved input contains `ɡˈɪθʌb`. | Proposed exact-token “git hub” pronunciation; inspect existing lexicon hooks. Do not attribute the whole sentence's ASR mismatch to this token. | -| E1: ASR mode | Same mixed WAV has 0% MER with fixed `zh` and 50% with automatic language selection. | Preserve both hypotheses; fix the primary decoding policy before comparisons. Never choose the transcript with the best reference match. | -| U1/U2: short English words | “a” / “The” exist in the input phones but are omitted/merged by ASR. | Acoustic and recognizer explanations remain open. Inspect narrow segments or an independent judge before labeling these training failures. | -| C1: fallback/tones | The recorded run fell back after a g2pW vocabulary request returned 404; some tone/polyphone prompts have zero CER. | Pin frontend assets and log fallback. Zero CER does not certify tones or pronunciation. This is a historical run fact, not a new availability check. | - -Implement approved frontend fixes in their owning FluidAudio code with unit -tests, then reconcile the Python training frontend with Swift. Keep the original -demo immutable. A corrected frontend receives a new version and new outputs; -do not overwrite evidence to make old hashes or scores look improved. - -The shared frontend contract must cover normalization, mixed-script span -boundaries, acronym/brand policy, numbers/dates/currency, polyphones, tone -sandhi, erhua, punctuation/pauses, out-of-vocabulary symbols, BOS/EOS, and -chunking. Store text → normalized text → language spans → phones → IDs, plus -lexicon/fallback provenance. Unexpected symbol loss is an error or explicitly -reviewed exclusion, never silent success. Add identical text fixtures on both -Python and Swift sides; any intentional divergence must be versioned. - -## 4. Architecture and compatibility contract - -The existing converter describes seven deployment stages: -**Albert → PostAlbert → Alignment → Prosody → Noise → Vocoder → Tail**. -These are execution/export partitions of one generator, not separate English -and Mandarin acoustic models. The existing Chinese checkpoint can already -produce both languages; that does not certify equally strong pronunciation, -naturalness, speaker consistency, or code-switching. -[Existing conversion documentation](coreml/README.md). - -Preserve and verify from the pinned files: - -- Model configuration and checkpoint tensor shapes, including **`n_token=178`**. - The documented vocabulary dictionary has **171 entries**; its entry count - is not the embedding size. Do not renumber IDs or resize embeddings to 171. -- ALBERT, its projection, duration/prosody predictor, text encoder, and iSTFTNet - decoder. Record parameter counts and the exact trainable/frozen key lists. -- The 256-dimensional reference style, its two 128-dimensional uses, and the - baseline length-conditioned voice table (the converter documents a flat - fp32 `[510, 256]` voice pack). Document style selection in every - caller, including whether length counts phones, valid IDs, or special tokens; - test short/long boundary cases and off-by-one behavior. -- Native 24-kHz output, actual alignment/frame strides, padding, masks, tensor - layouts, duration limits, and chunk boundaries. Measure shapes from code and - tensors; do not assume every stage uses the same frame grid. -- Existing conversion limits (`T_enc` up to 512, alignment bounds documented - in `coreml/docs/shape-bounds.md`) are implementation constraints to test, not - permission to silently truncate long user text. - -The public inference path disables gradients and constructs rounded integer -durations and hard alignment. Simply removing `no_grad` does not create a -useful gradient path through those operations. Build a separate supervised -training forward with explicit duration targets and a documented alignment -strategy, while preserving an unchanged inference path for parity. -[Kokoro model implementation](https://github.com/hexgrad/kokoro/blob/main/kokoro/model.py). - -“StyleTTS2-compatible” here means adapting the necessary training machinery to -Kokoro's released generator. It does **not** mean feeding its checkpoint into -an unmodified StyleTTS2 trainer or requiring a new diffusion model. Training-only -aligners, target extractors, and any later discriminators must not accidentally -become deployment dependencies. Preserve Kokoro's learned text representation -initially; replacing it with an unrelated PL-BERT is an architecture experiment. - -### Exact baseline identity - -Upstream repository: `hexgrad/Kokoro-82M-v1.1-zh`. -Model filename: `kokoro-v1_1-zh.pth`. -Published model SHA-256: - -```text -b1d8410fa44dfb5c15471fd6c4225ea6b4e9ac7fa03c98e8bea47a9928476e2b -``` - -Verify the downloaded/cached file, pin the repository revision, and separately -hash `config.json`, vocabulary, `zf_001.pt`, converted voice bytes, code, -dependencies, and any auxiliary models. A mutable `main` URL is a discovery -reference, not a reproducibility pin. Treat untrusted checkpoint formats as -code-execution risks; use trusted sources and appropriate safe loading. -[Published checkpoint identity](https://huggingface.co/hexgrad/Kokoro-82M-v1.1-zh). - -## 5. Data: establish what “we have the dataset” means - -First search the receiving environment and prior research for the selected -recordings. This checkout does not establish dataset names, accessible paths, -usable hours, speaker consent, or split membership. Do not ask the user to -select everything again until that search is complete, and do not claim the -absence of records here proves the recordings do not exist elsewhere. - -### Audit requirements - -- Verify permission for the intended training, derived voice, storage/transfer, - and release use; preserve consent/license evidence and restrictions. A public - download URL or upstream model license alone is insufficient evidence. -- Verify that the English and Mandarin recordings really are the **same target - female speaker**. Combining two unrelated female speakers is not a verified - single-voice dataset. If one bilingual speaker is unavailable, document the - adaptation/voice-identity tradeoff and obtain a decision before changing scope. -- Count usable hours and utterances separately for English, Mandarin, and - actual within-utterance code-switching, plus sessions, accents, recording - conditions, and rejected material. Do not infer bilingual coverage from a - dataset's name or from concatenating unrelated monolingual clips. -- Retain original recordings and transcripts immutably. Audit clipping, - truncation, background speech/music, overlapping speakers, excessive silence, - duplicate audio, sample rates/channels, transcript mismatch, and segmentation. - Specify QC cutoffs and record exclusions; do not quietly delete source data. -- Decide the spoken-text convention for numerals, abbreviations, punctuation, - English contractions, and simplified/traditional Chinese. Preserve both raw - transcript and model-normalized text; do not “correct” away what was spoken. -- Cover tones/polyphones, short English function words, consonant endings, - numbers, names, acronyms, and both switch directions. This is coverage to - measure, not a claim that every proposed corpus already contains it. -- Split by recording session/source passage and near-duplicate text/audio, - not random neighboring clips. Audit leakage before freezing train/dev/test. - With one speaker, session-disjoint tests matter; do not promise an impossible - speaker-disjoint test of that same single-speaker product. -- Keep the 12 demo prompts and their translations/paraphrases in development, - never a newly claimed sealed acceptance set. Keep generated demo WAVs and - benchmark reference recordings out of target-speaker training. - -No minimum hours, 40/40/20 sampling mixture, or large-data acquisition budget -has been validated. Earlier exploratory hour/mixture suggestions are not -requirements. Choose sampling weights from the audited coverage, log effective -exposure by language, and check that minority-language oversampling does not -turn into memorization. - -### Manifest and preprocessing contract to implement - -One immutable record per utterance, with a versioned schema: - -| Field group | Required information | -| --- | --- | -| Identity | Stable utterance ID, corpus/source ID, real speaker ID, recording session and source-passage IDs. | -| Audio | Restricted-access source URI, original SHA-256, original format/rate/channels, segment offsets, duration; prepared-audio URI/hash and exact transformation chain. | -| Text | Raw transcript, normalized spoken text, language and mixed-span annotations, annotation provenance. | -| Rights | Reference to consent/license/use restrictions and audit status; no private legal documents embedded in public manifests. | -| Split/QC | Train/dev/test assignment, split-manifest hash, duplicate-group ID, QC flags, exclusion/review reason. | -| Model input | Frontend version/hash, phones, token IDs, special-token convention, token length, dropped/unknown symbols, fallback trace. | -| Targets | Aligner version, token-map version, durations/alignment confidence, feature-grid specification, pitch/voicing/other target provenance. | - -Do not invent populated records for unavailable recordings. Validate missing -fields explicitly. Prepared audio should match the model's 24-kHz mono contract -with a pinned resampler/channel policy; retain originals and account for any -trimming in alignment timestamps. Training crop boundaries, mel/STFT parameters, -pitch frame grids, masks, and token-duration sums must agree. - -Select and validate an aligner against bilingual real recordings. StyleTTS2's -auxiliary aligner is a candidate, **not** a guaranteed drop-in: reconcile symbol -inventory, blank/pad IDs, language phones, sample rate, and time resolution. -Likewise validate pitch/voicing targets rather than interpreting the generator's -`N` feature as an arbitrary waveform-amplitude label. Reject or review low- -confidence alignments. Forced alignment given the answer transcript is not -independent recognition or a pronunciation-accuracy score. -[StyleTTS2 auxiliary modules](https://github.com/yl4579/StyleTTS2#pre-trained-modules). - -## 6. Harness work packages and required gates - -**These are implementation requirements, not existing commands.** There is no -working `train.py`, data-audit CLI, or CUDA scorer delivered by this handoff. -Put the future self-contained PyTorch toolkit under a sibling `training/` -directory, following Mobius layout conventions. Give it its own `pyproject.toml`, -lockfile, documented Python/CUDA matrix, configurations, tests, and CLIs. Do not -force a Linux trainer to import Core ML or MLX dependencies. - -Use explicit stages (audit, preprocess, parity, gradient check, micro-overfit, -pilot, evaluate, export), each with input/output schemas and failure exit codes. -No default command may silently advance from inspection into an optimizer run. -Pin executable source rather than relying on open-ended package constraints. - -### Gate 0 — source/environment/data inventory - -Produce an environment record with OS, CPU/GPU/VRAM, driver/CUDA, Python, -Torch/audio libraries, dependency lock, workspace/storage paths, disk quota, -existing run authorization, and compute/storage limits. Do not prescribe an -80-GB GPU or a vendor from historical suggestions. Inspect what is available. - -Complete the transfer ledger and data audit. Missing training data blocks -real-data training, not architecture inspection or parity implementation. -Missing budget approval blocks paid execution, not writing tests. Record which -gate each missing item blocks; avoid declaring the entire project blocked. - -### Gate 1 — untouched-checkpoint reproduction - -1. Instantiate a compatible generator and build an explicit checkpoint-key - mapping. Report every matched, renamed, missing, unexpected, or shape- - mismatched key and parameter count. Fail on unexplained mismatches; do not - use permissive loading to hide newly random parameters. -2. Compare an independently instantiated, frozen upstream inference oracle and - the candidate implementation with the **same** token IDs, style tensor, - speed, precision, device/backend, and stochastic inputs. First bypass text - frontend differences; test frontend equivalence separately. -3. Put both in evaluation mode. Account for dropout, random excitation/phase, - and random-number consumption. A shared seed alone may not ensure identical - noise if execution paths consume randomness differently. Capture/inject the - same noise where necessary without changing normal inference semantics. -4. Capture intermediate outputs: text representation/projection, duration - logits and integer durations, alignment, text features, F0/noise features, - decoder intermediates, and final waveform. Compare shapes/lengths, exact - discrete values, max/mean error, and appropriate signal/spectral differences. -5. Calibrate tight same-backend floating-point tolerances with repeated frozen - runs. Separately document cross-backend/export tolerances. Existing Core ML - waveform/mel correlation smoke thresholds are **not** a sufficient proof of - untouched PyTorch checkpoint reproduction. -6. Cover English, Mandarin, mixed scripts, punctuation/short prompts, numeric - and acronym fixtures, and permitted length/style-row boundaries. Preserve - per-case inputs, settings, hashes, tensors or diagnostics, and failures. - -**Pass:** a reproducible report with no unexplained weight/input differences and -all declared parity checks passing. Matching one pleasant sentence is not a -pass. Do not optimize weights until this gate passes. - -### Gate 2 — gradient and target correctness - -Implement a training forward distinct from the oracle. Initially use validated -teacher/forced alignment for acoustic supervision and direct duration loss; -document any differentiable/soft-alignment alternative explicitly. Match all -target frame rates, masks, crop offsets, and duration sums. - -For real recordings, audit per loss and parameter group: - -- Finite loss and finite gradients; expected nonzero gradients across multiple - real batches, not a requirement that every element be nonzero every step. -- Which parameters are intended to learn and which are frozen. Check optimizer - membership and actual weight deltas; frozen modules must remain unchanged. -- No accidental `detach`, inference-only context, inaccessible rounded-duration - path, padding leakage, or target/prediction length mismatch. -- F0/voicing treatment, unvoiced masks, duration supervision, and mel/STFT - reconstruction all have explicit definitions. A regularizer alone reducing - loss is not proof that the speech path learns. -- Training/evaluation mode and checkpoint reload preserve the untouched - inference behavior until an optimizer update is deliberately made. - -**Pass:** a gradient-coverage report and target/alignment checks. Do not label a -forward/backward call successful merely because it did not raise an exception. - -### Gate 3 — tiny real-data overfit and restart test - -Proposed diagnostic scale: roughly **10–30 real utterances**, spanning English, -Mandarin, and genuine mixed speech if available. This is an engineering test, -not a sufficient training dataset or quality claim. Declare its step/time cap -and optimizer settings before execution in the authorized environment. - -Demonstrate that aligned reconstruction and relevant prediction losses improve, -that expected parameters change, and that free-running synthesis still works. -Distinguish teacher-aligned reconstruction from inference with predicted -durations; success on the former does not prove the latter. Inspect held-out -controls for collapse. Save, interrupt, restore, and continue a checkpoint. -Compare uninterrupted/resumed state and outputs under the declared determinism -policy. A test failure goes back to implementation, not a larger training run. - -### Gate 4 — bounded bilingual pilot - -Proposed first adaptation schedule, subject to Gates 2–3: - -- Keep architecture/vocabulary fixed; initially freeze the decoder and ALBERT. - Adapt the projection, text encoder, and duration/prosody components as justified - by gradient coverage. If freezing prevents useful learning, change one group - at a time and record an ablation. -- Start with duration, validated pitch/voicing, and acoustic reconstruction - losses, plus documented regularization/real-data bilingual retention. Define - loss weights and masks from observed scales; do not silently import unrelated - recipe defaults. Real recordings, not synthetic demo audio, supply supervision. -- Keep the baseline style table fixed for the first proof. A small regularized - style residual or learned single-speaker representation is an optional later - experiment, not a proven solution. Preserve zero-update compatibility and - test unseen length rows; do not let sparse per-length parameters memorize clips. -- If the actual recorded speaker differs from `zf_001`, explicitly evaluate - whether the fixed style can fit that speaker. Decide whether to adapt style - and/or decoder after the micro-overfit evidence, without adding a cloning API. -- Unfreeze decoder components at a separately justified learning rate only if - the measured error calls for it. Add adversarial/WavLM losses only after - supervised stability, licensing, memory, and evaluation checks. Keep every - added dependency out of the inference package unless deliberately required. -- Prove single-GPU correctness first. Multi-GPU is not a default assumption; - upstream StyleTTS2 documents distributed-training limitations. - [Upstream training guidance](https://github.com/yl4579/StyleTTS2#training). - -Before a pilot, write the exact trainable-key list, sampling weights, batch size, -gradient accumulation/effective batch, crop/length policy, optimizer/LRs, -scheduler/warmup, precision, loss weights, gradient clipping, seed, validation -cadence, max steps/wall time/cost, checkpoint cadence/retention, and stop rules. -Those numeric settings are **not validated yet**; estimate them from the small -run's memory and throughput, then freeze a bounded pilot config. Mixed precision -must pass stability checks rather than being assumed safe everywhere. - -Stop on persistent nonfinite loss/gradients, invalid alignments, collapse, -out-of-budget execution, or agreed language-retention failures. Save diagnostics; -do not restart an unbounded sweep. Evaluate on development data at declared -intervals; reserve sealed tests for the predeclared candidate-selection stage. - -### Checkpoint/reproducibility contract - -Each run records parent/base hashes, code commits and dirty patches, complete -resolved config, dataset/split/frontend/auxiliary-model hashes, environment, -seed policy, metrics, wall time, and resource use. Resume checkpoints include -generator/style state, optimizer/scheduler/scaler, step/epoch, sampler position, -Python/NumPy/Torch/device RNG state, and any enabled discriminator state. -If exact dataloader replay is not supported, explicitly define the restart -boundary and validate its consequences. Use atomic checkpoint writes and hash -verification; log best-dev and latest separately. A deployment checkpoint is a -separate minimal artifact, not the entire training-state archive. - -## 7. Automated evaluation: available proxies and required improvements - -The user wants useful automatic accuracy measurement, not a process blocked -on manual listening after every step. Build reproducible automatic reports and -targeted failure triage. Do not relabel proxy scores as ground truth. - -### Historical prototype, not production certification - -The source workspace has a 300-prompt development corpus: 120 English, 140 -Mandarin, and 40 mixed. It combines external evaluation prompts and authored -parallel/challenge prompts. The 12 public demo prompts are all project-authored; -the external corpus and its redistribution obligations are not transferred by -this PR. Record source revisions/licenses if retrieving the larger corpus. - -The local corpus builder records 100 English and 100 Chinese prompts from -`MiniMaxAI/TTS-Multilingual-Test-Set`, revision -`cb416f0ac3658da0577e97873065e19fe6488917`, with `CC-BY-SA-4.0` provenance. -Recover `Benchmarks/tts/bilingual/corpus.py` and -`Documentation/TTS/MinimaxCorpus.md` from the FluidAudio source handoff before -reusing or redistributing that material. These are evaluation texts, not the -missing single-speaker bilingual training recordings. Public benchmark results -are comparable only when model/voice, prompt slice, frontend, judge, and scoring -protocol match; do not treat unrelated published WER/MOS values as our baseline. - -- ASR: MLX Whisper large-v3-turbo, fixed `en` for English and `zh` for Mandarin - and mixed; auto-language mixed decoding is an additional diagnostic. No - expected transcript prompt; temperature 0; no previous-text conditioning. -- Normalization: `nfkc-simplified-lower-punctuation-space-v1`. English WER uses - words, Mandarin CER uses characters, mixed MER uses Han characters plus - English-word/numeric runs. Number formatting, acronym spelling, contraction - variants, and homophones can still affect results. -- Naturalness: one UTMOSv2 configuration/checkpoint, **not human MOS** and not - established as calibrated for Mandarin/code-switching. Do not turn a scalar - into a pass/fail quality threshold without calibration. -- Additional local prototype diagnostics: SpeechBrain ECAPA embedding cosine - consistency, signal checks, and YIN-based F0 summaries. These are not all - included in the public demo evidence. Without real target-speaker references, - embedding consistency is not proof of the correct voice; F0 summaries do not - establish tone accuracy. - -The exact saved ASR/naturalness/signal configurations and hashes are in -[demo provenance](coreml/bilingual-demo/provenance/run.json) and adjacent -`*-config.json` files. Historical percentages must not become production -targets or be compared directly to scores from a new backend. - -### Formal protocol to build in the designated environment - -1. **Content recovery:** report pooled edits/reference units and per-utterance - distributions separately for EN WER, ZH CER, and mixed MER. Also report - substitutions/deletions/insertions, completion rate, failed/missing samples, - and coverage. Never silently drop failures or combine incompatible metrics - into a single headline “accuracy.” Preserve raw and normalized transcripts. -2. **Semantic-critical content:** report number/currency/date/name/acronym - failures with reviewed expected spoken forms. A normalization may equate - twenty-three with 23, but must never equate 13 with 23. Keep strict and - equivalence-aware diagnostics separate and freeze rules in advance. -3. **Switching:** slice by switch direction, number of switches, position, - English span length, short function words, and surrounding punctuation. - Use annotated span alignment for span-level errors; do not guess language - boundaries solely from the recognizer's automatic language label. -4. **Independent checks:** add a second, independently selected bilingual ASR - judge or labeled diagnostic subset. Two modes of Whisper are not two - independent judges. Disagreement flags uncertain cases; do not select the - favorable transcript. Audit ASR on real reference speech as well as synthesis - to expose recognizer bias. -5. **Pronunciation/tones:** prepare an annotated diagnostic subset with expected - lexical pronunciation and context/sandhi rules. A suitable phoneme/tone - recognizer must be validated before reporting PER/tone accuracy. Until then, - mark acoustic tone accuracy **unmeasured**, not passed because CER is zero. -6. **Naturalness and voice:** report predicted MOS as a proxy by language and - condition; test its agreement with a small blinded bilingual calibration set. - Compare speaker embeddings against held-out real target-speaker audio and - across languages/sessions, accounting for channel effects. If the target - voice changes from `zf_001`, that identity change is not automatically drift. -7. **Signal/robustness:** nonfinite samples, clipping, empty/truncated output, - unexpected silence, repetition, extreme duration/speaking rate, chunk seams, - punctuation, and maximum supported lengths. Calibrate duration expectations - separately by language and case type. -8. **Statistics:** paired baseline/candidate comparisons on the same inputs; - bootstrap at an appropriate utterance/session/topic unit, fixed seed and - documented confidence intervals. Report sample counts and tails. Repeated - prompts, stochastic synthesis, judge bias, and corpus design remain sources - of uncertainty beyond a naive per-utterance interval. -9. **Backend changes:** a CUDA Whisper port is not numerically identical to MLX. - Pin model/revision, decoding, resampler, VAD/chunking, precision, and text - normalization. Re-render/rescore both baseline and candidate with the same - formal protocol; do not compare a new CUDA number to an old Mac percentage. - -Automation should select the most informative disagreement/failure clips for -limited human calibration and final listening review. It must not require a -human to approve every checkpoint. Conversely, without calibrated pronunciation -and perceptual checks, do not claim fully automatic proof of human-level quality. - -### Separate frontend gains from training gains - -Keep three labeled conditions (and a fourth ablation if useful): - -| Condition | Weights | Frontend | Purpose | -| --- | --- | --- | --- | -| Historical reference | Original | Historical | Preserve what the demo actually measured; not necessarily the formal baseline. | -| Corrected-input baseline | Original | Fixed/pinned | Measure the benefit of frontend repair under the new formal protocol. | -| Trained candidate | Fine-tuned | Same fixed/pinned frontend | Attribute incremental changes to training, not different input text/phones. | - -For a controlled acoustic comparison, hold style/voice policy and speed fixed -where possible. If style adaptation is part of training, report that factor and -its ablation separately. For deployment, also compare the complete intended -pipeline rather than relying only on precomputed-token tests. - -## 8. Export, Apple integration, and production gates - -### Gate 5 — candidate export and parity - -The existing [conversion script](coreml/scripts/convert-coreml.py) constructs -`KModel(repo_id='hexgrad/Kokoro-82M-v1.1-zh')`; its current CLI does not expose -a trained-checkpoint argument. **Running it unchanged can export the upstream -model instead of our candidate.** Add explicit local checkpoint/config/style -inputs, strict loading, and embedded candidate hashes. Test that candidate -weights are actually loaded and that missing inputs fail rather than silently -falling back to an upstream download. - -- Start with an uncompressed candidate export and verify stage-by-stage and - end-to-end parity against that exact PyTorch candidate. Then evaluate fp16 / - palettization/other size changes independently. The existing mixed-precision - and int8-palettized pipeline is a reference, not a free quality guarantee. -- Preserve the seven-stage interfaces or version any intentional changes. - Export the one required voice/style artifact; additional source voices need - not ship in the single-voice product. Do not assume fewer packaged voices - materially reduce the shared generator's parameter count. -- Audit the deployed FluidAudio stage names, revisions, and fixes. The inspected - source runtime expects `KokoroNoise_v2.mlmodelc` while the older converter - documents `KokoroNoise`. This is a compatibility/fix-history check, not a - filename rename that can be assumed to solve numerical differences. -- Verify vocabulary/embedding consistency, style-row selection, BOS/EOS, - dynamic-shape limits, duration/alignment bounds, special cases, excitation, - and noise/tail precision. Validate held-out bilingual audio after export. -- Pin Core ML tools and the export OS/Python environment. Core ML execution and - device profiling require a designated supported Apple host; a CUDA machine - alone cannot establish actual ANE/device performance. Do not automatically - designate the source demo Mac as the official test host. - -### Gate 6 — FluidAudio/device acceptance - -Implement any integration changes in the owning FluidAudio repository, with -its API/thread-safety conventions and real-model tests. Never use -`@unchecked Sendable`, mock models, or synthetic stand-in audio to claim model -validation. Text-only and metadata tests are fine for their actual scope. - -Confirm the actual supported-device/OS matrix. Mobius conversion guidance -uses iOS 17+/macOS 14+; that is an engineering starting point, not a user-approved -oldest-device performance contract. Measure, on designated devices: - -- Cold download/compile/load separately from warm synthesis; model/package - bytes, peak memory, end-to-end latency, and real-time factor (`elapsed / audio - duration`). Separate frontend time, inference time, and disk I/O. -- Per-stage compute-unit placement and CPU/GPU fallback, not just a claim of - “ANE compatible.” Use the repository's `coreml-cli` guidance when appropriate. -- Short/medium/long EN/ZH/mixed inputs, chunk seams and pause continuity, - repeated invocations, cancellation/concurrency as supported by the API, - initialization failure, invalid inputs, and offline/cache behavior. -- If streaming/chunked playback is required, explicitly define and measure - time to first playable audio, buffering, continuity, and cancellation. Do - not infer these from total waveform generation time. -- Full deployed pipeline quality relative to the accepted candidate, including - any precision/quantization changes. A fast export that changes pronunciation - or loses words fails the quality requirement. - -### Gate 7 — release candidate, not automatic rollout - -Before candidate selection, freeze an acceptance record covering each language, -code-switching, semantic-critical errors, voice consistency/naturalness, -robustness, artifact size, supported devices, and latency/memory budgets. -Populate numeric thresholds from baseline/pilot calibration and product needs; -do not invent a 20% WER improvement, +1 CER point allowance, or 5% speed margin -and present it as an approved requirement. - -Require: all preceding gates passed, sealed evaluation completed under the -frozen protocol, known limitations disclosed, scoped final listening/calibration -complete, and no unresolved release-blocking regression. Promotion needs the -agreed acceptance authority. “Loss decreased” is not a release criterion. - -Release artifacts must include minimal inference weights, config, vocabulary, -single-voice style asset, Core ML packages, hashes, version/compatibility metadata, -model card, provenance and applicable notices, evaluation/device reports, and -integration-test evidence. Training-only assets and private recordings do not -belong in the app bundle. Audit redistribution obligations for the base model, -training data, auxiliary models, and export code individually. - -Use versioned download/cache paths with checksum validation and an atomic -activation mechanism; keep the previous known-good artifact for rollback. -Provide a staged rollout/rollback procedure and define allowed failure telemetry -without collecting user text/audio by default. Publishing artifacts and actual -production deployment are separate authorized actions, not automatic side -effects of finishing training. - -## 9. Evidence package and genuinely unresolved inputs - -Future run artifacts should make each gate inspectable. Suggested logical -outputs below are **contracts to implement**, not files already present: - -```text -/ - environment.json # machine, dependencies, limits, authorization reference - inputs.json # code/data/model/frontend hashes and access references - resolved-config.yaml # exact settings, no secrets - data-audit.json # rights status, counts, QC, overlap, exclusions - checkpoint-map.json # every weight mapping and shape result - parity/ # same-backend and export reports kept separate - gradient-audit.json # intended/observed trainability and target checks - checkpoints/ # private training/resume states with retention policy - renders/ # immutable WAVs + text/phones/IDs/style/seed records - evaluation/ # per-utterance judge outputs, metrics, uncertainty - export/ # candidate-bound manifests and parity evidence - device-report.json # hardware, cold/warm timing, memory, placement - acceptance.md # frozen criteria, results, limitations, approval -``` - -Store heavy/private artifacts in the approved artifact store; commit schemas, -code, documentation, and safe summaries only. New sample audio must come from -real recordings or the real model as appropriate, and must be labeled by origin. -Generated evaluation audio is never silently treated as training recordings. - -| Unknown to resolve | First action / owner | Blocks | -| --- | --- | --- | -| Receiving machine and existing authorization | Receiving agent inspects its environment and earlier setup decisions. | Actual execution beyond the recorded approval. | -| Dataset location, rights, same-speaker bilingual coverage | Receiving agent retrieves earlier research/data; data owner supplies only missing access/consent evidence. | Audited preprocessing and all real-data optimizer runs. | -| Exact target voice/accent if the recordings do not determine it | Audit the recordings and existing decisions; ask product/data owner only if ambiguous. | Final voice acceptance; may affect style adaptation. | -| Reproducible Swift frontend patch/evaluator transfer | Source-workspace maintainer provides scoped versioned source; receiver checks hashes and portability. | Exact old-demo reproduction and deployment frontend parity. | -| Compatible aligner/targets and differentiable graph | Training implementation work, measured at Gates 1–3. | A credible pilot; not something the user should have to design. | -| CUDA evaluator implementation and judge calibration | Evaluation implementation work, with targeted bilingual validation. | Trustworthy model comparisons and production claims. | -| GPU/time/storage limits and deadline | Recover existing limits first; user/operator fills only missing bounds. | Paid runs/sweeps and retention decisions. | -| Oldest device, hard footprint/latency limits, streaming requirement | Recover product configuration first; product owner resolves missing requirements. | Final device acceptance, not initial checkpoint mapping. | -| Numeric quality gates and release approver | Propose after baseline/pilot calibration, freeze before candidate selection. | Production promotion and rollout. | - -### First actions for the receiving agent - -1. Confirm this handoff is in the checked-out revision; run the offline demo - checks. Report exactly which artifacts are present versus referenced. -2. Inspect existing hardware, setup, data, permissions, and prior run budget. - Fill the transfer/unknown ledger rather than asking the generic model-goal, - language, speaker-count, or platform questions again. -3. Recover scoped frontend/evaluator source if needed. Implement/test F1 and - agreed lexical policies before producing canonical training inputs. -4. Build strict checkpoint mapping and untouched-inference parity tooling. - In parallel in the workflow, audit real data and define splits/target formats; - do not wait for a final production device choice to start these safe tasks. -5. Implement the actual supervised training path, gradient audit, and restart - test. Run model checks only in the authorized environment, within its bounds. -6. Present the gate evidence and a capped pilot configuration. Start only if - existing authority covers that run; otherwise ask for the missing approval. -7. Continue through evaluation, export, device verification, and release - acceptance. Report failed gates honestly instead of declaring E2E completion - when only a checkpoint or a WAV exists. - -The first status response should say: **what was found, what is implemented, -which gate is next, and the precise missing input (if any)**. Do not imply that -the project is undefined because some production thresholds remain open. - -### Copyable receiving-environment task - -> Continue the one-female-voice English–Mandarin/code-switching Kokoro v1.1-zh -> project. Read `models/tts/kokoro-v1.1-zh/project-handoff.md` and applicable -> `AGENTS.md` files first. Preserve the product decisions and the Mac demo-only -> boundary. Inspect this environment's already-settled setup, data, and run -> authorizations. Complete the artifact/readiness ledger, then implement the -> checkpoint-parity, data-audit, and training-readiness work packages. Do not -> confuse demo evidence with a trainer or official baseline. Ask only for -> missing access/decisions/authority that actually block the next gate; continue -> safe implementation work where possible. Never launch unapproved paid work, -> publish recordings/weights, or deploy merely because this handoff exists. - -## 10. Reference hierarchy - -User decisions above and verified artifacts take precedence over exploratory -suggestions. Sources accessed for this handoff are navigation references; -pin executable revisions before using them in a run. - -- [Demo evidence and limitations](coreml/bilingual-demo/README.md) and - [frontend/evaluator issue evidence](coreml/bilingual-demo/ISSUES.md). -- [Existing Mobius conversion](coreml/README.md), - [architecture](coreml/docs/architecture.md), - [shape bounds](coreml/docs/shape-bounds.md), and - [conversion trials](coreml/TRIALS.md). -- [Kokoro v1.1-zh release/model card](https://huggingface.co/hexgrad/Kokoro-82M-v1.1-zh) - and [inference source](https://github.com/hexgrad/kokoro). -- [StyleTTS2 training reference](https://github.com/yl4579/StyleTTS2): useful - machinery and limitations, not a Kokoro-specific ready-made recipe. -- [UTMOSv2](https://github.com/sarulab-speech/UTMOSv2), - [historical MLX ASR model](https://huggingface.co/mlx-community/whisper-large-v3-turbo), - [local speaker-proxy model](https://huggingface.co/speechbrain/spkrec-ecapa-voxceleb). - -Delivery of this file makes the instructions available through Git. It does -not prove the other environment has pulled them or received local-only assets; -the receiving agent must acknowledge its actual revision and artifact inventory. +The [original Mac demo](coreml/bilingual-demo/README.md) is historical reference; +its saved inputs remain immutable and are also used by baseline parity checks. diff --git a/models/tts/kokoro-v1.1-zh/training/.gitignore b/models/tts/kokoro-v1.1-zh/training/.gitignore new file mode 100644 index 0000000..46ad9eb --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/.gitignore @@ -0,0 +1,4 @@ +.artifacts/ +.runs/ +.pytest_cache/ +*.egg-info/ diff --git a/models/tts/kokoro-v1.1-zh/training/LICENSE.kokoro b/models/tts/kokoro-v1.1-zh/training/LICENSE.kokoro new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/LICENSE.kokoro @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/models/tts/kokoro-v1.1-zh/training/MODEL_CARD.md b/models/tts/kokoro-v1.1-zh/training/MODEL_CARD.md new file mode 100644 index 0000000..9a4c269 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/MODEL_CARD.md @@ -0,0 +1,102 @@ +# Kokoro MF5 English/Mandarin adaptation + +## Artifact + +A single female voice adapted from Kokoro-82M-v1.1-zh, retaining its architecture, +vocabulary, 24 kHz output, and length-indexed 256-dimensional style interface. +The selected checkpoint is deterministic run v2, update **500**. Weights remain +local, along with generated evaluation audio. + +| File | Bytes | SHA-256 | +|---|---:|---| +| `model.pth` | 327,453,363 | `2c27865b7c794124197e49ab9b7c814da088e9ef87f1dd605f95b87e1458a4c0` | +| `voice.pt` | 523,739 | `119eca879d472a5327bc1e01c358958e9028a62d09d3a528d953248e94fbc8b3` | + +Use the matching voice table, configuration, and frontend with these weights. +[results.json](results.json) records model/data/report identities and exact counts. + +## Data and training + +Real EMIME MF5 microphone-0 recordings: 314 utterances, approximately 28 minutes. +Official test IDs and translated parallel passages stay together. Other passages +have deterministic development assignment. There is one session per language, +so these splits are passage-disjoint, not session-disjoint. + +| Split | English utterances | Mandarin utterances | Prepared seconds | +|---|---:|---:|---:| +| Train | 96 | 129 | 1,347.95 | +| Development | 6 | 9 | 83.15 | +| Test | 43 | 31 | 274.55 | + +The frontend uses pinned Misaki, NFKC and simplified Chinese, explicit API/GitHub +pronunciations, and distinct numeral/erhua handling. Unknown symbols and overly +long inputs fail. Audio is resampled from 22,050 to 24,000 Hz and padded to the +600-sample duration grid. Temporary baseline synthesis supplies token boundaries +transferred by MFCC DTW onto real recordings. This is approximate alignment; +**only real recordings are reconstruction targets**. Pinned JDC supplies F0; +pitch/energy targets use the 300-sample grid and StyleTTS2 mel convention. + +Trainable parameters: ALBERT projection, prosody predictor, text encoder, and +one shared voice-style residual. ALBERT, decoder, and identity affine constants +are frozen. AdamW uses LR 2e-5 (style: 1e-3), two accumulated utterances per step, +2.4-second crops, balanced language sampling, and seed 1729. Loss combines mel, +duration, F0, energy, and style regularization. Strict deterministic execution +uses equivalent slice-based reflection padding to avoid nondeterministic CUDA +padding gradients. Checkpoints retain optimizer and all RNG states. + +The eight-recording overfit check reduced loss 43.1%. After an exploratory +1,000-update run, the corrected deterministic run started from the base weights +and completed 500 updates. Its development acoustic loss fell 5.98555 → 3.19828. +Candidates 100/300/500 were compared on development ASR; 300 and 500 tied, +with lower acoustic loss selecting 500. Test results did not influence selection. + +## Measured quality + +Matched baseline/candidate inputs, seed, speed, frontend, and respective voice +tables. Whisper large-v3-turbo uses pinned weights, greedy decoding, and explicit +English/Chinese transcription. Scores apply NFKC, simplified Chinese, lowercase, +and punctuation removal; numeral spellings are not equated. Evaluation uses +FP32 generator tensors, FP16 ASR, matmul TF32 off, and cuDNN TF32 allowed. + +| Metric | Baseline | Trained | +|---|---:|---:| +| Development English WER, 53 words | 1.89% | 0.00% | +| Development Mandarin CER, 235 characters | 7.23% | 5.96% | +| Held-out English WER, 325 words / 43 utterances | 6.15% | 4.31% | +| Held-out Mandarin CER, 466 characters / 31 utterances | 1.50% | 1.93% | +| Mixed-control raw CER, 6 utterances | 13.18% | 19.38% | + +English ASR improved; Mandarin test CER increased by two character errors. +The mixed score regressed under a recognizer that sometimes translates mixed +speech instead of transcribing it. It is retained as a diagnostic, not treated +as a reliable measurement of code-switch quality. ASR does not establish +naturalness, Mandarin tone correctness, or speaker consistency. No blinded +bilingual listening study has been completed. + +## Implementation validation + +- Untouched baseline: 688 state tensors and 15 inference cases match upstream. +- Eight real-recording gradient/forward checks pass; alignment negative controls + distinguish matching text from wrong-text recordings. +- Three updates uninterrupted versus save/restart after update one match + bit-for-bit, including model, optimizer, RNG, and validation state. +- Exported weights/voice match the selected checkpoint; upstream Kokoro produces + identical waveforms/durations on 12 controls. Exactly 148 generator tensors + changed; frozen weights did not change. +- Standalone offline CPU and GPU synthesis pass. Peak training allocation: 2.01 GB. + +## Limits and provenance + +This is an experimental model. The corpus provides no real code-switch speech, +no session-disjoint validation, and no independently certified production voice +consent. Listening, tone, and naturalness acceptance remain open. No deployment +or Core ML acceptance is implied. + +Corpus: [EMIME](https://www.emime.org/participate/emime-bilingual-database.html) +(ODbL/DbCL per its README). Base code/weights and auxiliary models have separate +licenses; see [NOTICE.md](NOTICE.md). Acquisition revisions and checksums are in +[baseline.lock.json](baseline.lock.json) and [training-assets.lock.json](training-assets.lock.json). +The original full run reports remain in +[Git history](https://github.com/FluidInference/mobius/tree/fb13fc48d599b733759a4ecc60d0c10b6499fffc/models/tts/kokoro-v1.1-zh/training/evidence) +and the local ignored run directories; the recorded run's dependency-lock hash +is retained in `results.json`. diff --git a/models/tts/kokoro-v1.1-zh/training/NOTICE.md b/models/tts/kokoro-v1.1-zh/training/NOTICE.md new file mode 100644 index 0000000..3d4fad2 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/NOTICE.md @@ -0,0 +1,30 @@ +# Upstream implementation attribution + +The compatible generator assembly and inference orchestration follow +[hexgrad/kokoro](https://github.com/hexgrad/kokoro), commit +`dfb907a02bba8152ca444717ca5d78747ccb4bec`, especially `kokoro/model.py`. +Kokoro's source is Apache-2.0 licensed; see [LICENSE.kokoro](LICENSE.kokoro). +The actual layer modules are installed from that pinned upstream source, +including its StyleTTS2/iSTFTNet attribution. They are not vendored here. + +The model checkpoint, voice data, training-corpus candidates, and source code +have separate provenance and use requirements. This file does not grant new +rights to recordings or a derived production voice. + +Acoustic target extraction follows the published StyleTTS2 training convention +and loads the JDC pitch model from +[yl4579/StyleTTS2](https://github.com/yl4579/StyleTTS2/tree/5cedc71c333f8d8b8551ca59378bdcc7af4c9529), +pinned in `training-assets.lock.json`. Acquisition downloads its MIT license +alongside the target model. The JDC source/checkpoint is not redistributed in Git. + +EMIME Mandarin/English recordings are credited to Mirjam Wester, Hui Liang, +and the Centre for Speech Technology Research, University of Edinburgh. +The corpus archive's own README specifies ODbL 1.0 and DbCL 1.0. Preserve its +README/license with acquired data. This experiment uses MF5 microphone 0; +the toolkit does not grant publicity/personality rights or certify consent +for a production synthetic voice. + +The evaluation ASR model is OpenAI Whisper large-v3-turbo (MIT). Misaki and +its language resources, eSpeak, OpenCC, spaCy, and other installed dependencies +retain their respective licenses. `uv.lock` records the actual packages; +bundling a trained checkpoint does not relicense those components. diff --git a/models/tts/kokoro-v1.1-zh/training/README.md b/models/tts/kokoro-v1.1-zh/training/README.md new file mode 100644 index 0000000..ea3857f --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/README.md @@ -0,0 +1,104 @@ +# Kokoro bilingual PyTorch training + +[Model and measured results](MODEL_CARD.md) · [Result manifest](results.json) + +Fine-tune Kokoro v1.1-zh on real English/Mandarin recordings and export a +PyTorch generator with its matching voice table. The selected MF5 model is an +experimental adaptation; production speech quality is not established. Model +weights, real recordings, and generated evaluation audio remain local. +Core ML is outside the current scope. + +## Inference + +From this source directory, with a previously exported bundle: + +```bash +uv sync --frozen +uv run --frozen python -m kokoro_training.bundle infer \ + --bundle /path/to/bundle \ + --text '请打开 API,然后把结果发给我。' \ + --output .runs/example.wav --device cuda +``` + +Use `--device cpu` for CPU inference. A bundle contains `model.pth`, `voice.pt`, +`config.json`, `vocab.json`, source, dependency lock, and SHA-256 manifest. Use +its matching voice table. Loading verifies hashes and maps all weights strictly; +inference does not download models. Output is 24 kHz mono WAV plus a JSON sidecar. +Split text above 510 content tokens. Speed is limited to 0.25–4 and output to +4,000 duration frames (100 seconds). + +## Reproduce training and selection + +Tested on Linux, Python 3.12, Torch 2.11.0+cu130, and an RTX A6000. Install the +system `espeak-ng` library first. `uv.lock`, `baseline.lock.json`, and +`training-assets.lock.json` pin packages, base weights, corpus, JDC, and ASR. +Run from this directory; commands refuse to overwrite existing outputs. + +```bash +uv sync --frozen +uv run --frozen kokoro-readiness fetch-baseline --assets .artifacts/baseline +uv run --frozen kokoro-readiness parity \ + --assets .artifacts/baseline --output .runs/reproduce/parity --device cuda +uv run --frozen python -m kokoro_training.acquire data +uv run --frozen python -m kokoro_training.acquire targets +uv run --frozen python -m kokoro_training.acquire asr +uv run --frozen python -m kokoro_training.acquire extract-mf5 +uv run --frozen python -m kokoro_training.prepare --output .runs/reproduce/prepared + +uv run --frozen python scripts/check-real-training.py \ + --data .runs/reproduce/prepared --output .runs/reproduce/gradients.json +uv run --frozen python scripts/check-resume.py \ + --data .runs/reproduce/prepared --output .runs/reproduce/resume +uv run --frozen python -m kokoro_training.train \ + --data .runs/reproduce/prepared --output .runs/reproduce/micro \ + --micro 8 --steps 100 --lr 0.0001 --validate-every 50 +uv run --frozen python -m kokoro_training.train \ + --data .runs/reproduce/prepared --output .runs/reproduce/pilot \ + --steps 500 --lr 0.00002 --train-style --style-lr 0.001 \ + --validate-every 100 --snapshot-every 100 + +for step in 100 300 500; do + uv run --frozen python -m kokoro_training.bundle export \ + --checkpoint .runs/reproduce/pilot/snapshot-${step}.pt \ + --output .artifacts/reproduce-step${step} + uv run --frozen python -m kokoro_training.evaluate \ + --data .runs/reproduce/prepared --bundle .artifacts/reproduce-step${step} \ + --output .runs/reproduce/mf5-v2-step${step}-eval --controls +done +uv run --frozen python scripts/select-checkpoint.py \ + --run .runs/reproduce/pilot --evaluations .runs/reproduce \ + --output .artifacts/reproduce-selected --record .runs/reproduce/selection.json +uv run --frozen python -m kokoro_training.evaluate \ + --data .runs/reproduce/prepared --bundle .artifacts/reproduce-selected \ + --output .runs/reproduce/test --split test +``` + +The [selection policy](evaluation-policy.json) compares development English WER +and Mandarin CER, breaking ties with acoustic loss. Freeze the selected hash +before test evaluation. `best.pt` alone means lowest acoustic development loss. +`--resume` continues `last.pt` with matching data/settings and a larger step target. + +## Checks and artifacts + +```bash +uv run --frozen pytest -q +uv run --frozen ruff check src tests scripts +uv run --frozen python scripts/check-bundle.py \ + --bundle .artifacts/reproduce-selected \ + --checkpoint .runs/reproduce/pilot/snapshot-500.pt \ + --output .runs/reproduce/export-check.json +``` + +For `check-bundle.py`, use the checkpoint step recorded in `selection.json`; +500 was selected in the recorded run. Parity checks compare an independently +loaded upstream `KModel`, all state tensors, intermediate outputs, and RNG state. +The identity affine tensors added by upstream export support are explicitly +initialized and frozen. Model checks require the acquired assets; unit tests +never download them. + +Raw training/evaluation reports live under ignored `.runs/`; recordings and +weights under ignored `.artifacts/`. [results.json](results.json) retains the +selected model, hashes, counts, validation outcomes, and quality regressions. +The optional `kokoro-readiness audit-data` and `inventory-emime` commands inspect +new acquisition manifests/archives; they do not run training or approve a voice. +See [NOTICE.md](NOTICE.md) for upstream and dataset attribution. diff --git a/models/tts/kokoro-v1.1-zh/training/baseline.lock.json b/models/tts/kokoro-v1.1-zh/training/baseline.lock.json new file mode 100644 index 0000000..4b62680 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/baseline.lock.json @@ -0,0 +1,20 @@ +{ + "schema_version": 1, + "repo_id": "hexgrad/Kokoro-82M-v1.1-zh", + "revision": "01e7505bd6a7a2ac4975463114c3a7650a9f7218", + "kokoro_source_revision": "dfb907a02bba8152ca444717ca5d78747ccb4bec", + "files": { + "config.json": { + "sha256": "bc333efa5ce4ceff433c8c8e5d027a1eca0166001e4e4a62bea2d26ff7a46890", + "bytes": 3228 + }, + "voices/zf_001.pt": { + "sha256": "9bdc9a87e13e9bb1ea3e7803259c2ecbfebaeeb2ff80b5d0c76df1a464c1c962", + "bytes": 523331 + }, + "kokoro-v1_1-zh.pth": { + "sha256": "b1d8410fa44dfb5c15471fd6c4225ea6b4e9ac7fa03c98e8bea47a9928476e2b", + "bytes": 327247856 + } + } +} diff --git a/models/tts/kokoro-v1.1-zh/training/evaluation-policy.json b/models/tts/kokoro-v1.1-zh/training/evaluation-policy.json new file mode 100644 index 0000000..6e6f01d --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/evaluation-policy.json @@ -0,0 +1,20 @@ +{ + "version": "mf5-pilot-v2-selection-v1", + "scope": "local experimental PyTorch checkpoint selection; not production acceptance", + "run": "mf5-pilot-v2", + "optimizer_update_budget": 500, + "candidate_steps": [100, 300, 500], + "selection_split": "dev", + "primary_score": "arithmetic mean of development English WER and Mandarin CER", + "tie_break": "lower development supervised total loss", + "signal_rejection": "nonfinite waveform, RMS <= 0.001, or clipping fraction >= 0.001", + "controls": "report all twelve fixed controls for every candidate; mixed forced-language ASR is diagnostic due documented translation instability", + "test_policy": "freeze model hash and selection record before one evaluation on all 74 corpus test utterances; never reselect using test results", + "production_qualified": false, + "production_limitations": [ + "production voice rights/consent not certified", + "no real same-speaker code-switch training data", + "no session-disjoint evaluation", + "no bilingual listening/tone/naturalness acceptance" + ] +} diff --git a/models/tts/kokoro-v1.1-zh/training/evaluation-prompts.json b/models/tts/kokoro-v1.1-zh/training/evaluation-prompts.json new file mode 100644 index 0000000..07732a8 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/evaluation-prompts.json @@ -0,0 +1,14 @@ +[ + {"id": "mixed-api", "text": "请打开 API,然后把结果发给我。", "language": "mixed"}, + {"id": "mixed-github", "text": "我把新的 PyTorch 模型上传到 GitHub 了。", "language": "mixed"}, + {"id": "mixed-meeting", "text": "今天下午三点有一个 meeting,请不要迟到。", "language": "mixed"}, + {"id": "mixed-coffee", "text": "Could you bring 两杯咖啡 to the office?", "language": "mixed"}, + {"id": "mixed-release", "text": "The next release 支持中文和 English。", "language": "mixed"}, + {"id": "mixed-price", "text": "这个 package 的价格是二十三元。", "language": "mixed"}, + {"id": "zh-numerals", "text": "二加二等于四,十二加十一等于二十三。", "language": "zh"}, + {"id": "zh-independent-er", "text": "儿童在公园里玩,一共有二十二个人。", "language": "zh"}, + {"id": "zh-erhua", "text": "小孩儿在这儿等了一会儿。", "language": "zh"}, + {"id": "en-api", "text": "The API is ready. You can find the code on GitHub.", "language": "en"}, + {"id": "en-numerals", "text": "There are twenty three messages and twelve new files.", "language": "en"}, + {"id": "en-question", "text": "Would you like to hear the next sentence?", "language": "en"} +] diff --git a/models/tts/kokoro-v1.1-zh/training/pyproject.toml b/models/tts/kokoro-v1.1-zh/training/pyproject.toml new file mode 100644 index 0000000..32ecb0d --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/pyproject.toml @@ -0,0 +1,48 @@ +[project] +name = "kokoro-zh-training" +version = "0.1.0" +description = "Real-data bilingual Kokoro PyTorch training, verified weights, and evaluation" +requires-python = ">=3.12,<3.13" +dependencies = [ + "kokoro", + "torch==2.11.0+cu130", + "transformers==4.57.6", + "numpy==2.2.6", + "soundfile==0.13.1", + "jsonschema==4.26.0", + "misaki[zh]==0.9.4", + "scipy==1.17.1", + "librosa==0.11.0", + "jiwer==4.0.0", + "en-core-web-sm", + "opencc-python-reimplemented==0.1.7", +] + +[dependency-groups] +dev = ["pytest==8.4.2", "ruff==0.12.12"] + +[project.scripts] +kokoro-readiness = "kokoro_training.cli:main" + +[build-system] +requires = ["hatchling==1.27.0"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/kokoro_training"] + +[tool.uv.sources] +kokoro = { git = "https://github.com/hexgrad/kokoro", rev = "dfb907a02bba8152ca444717ca5d78747ccb4bec" } +torch = { index = "pytorch-cu130" } +en-core-web-sm = { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl" } + +[[tool.uv.index]] +name = "pytorch-cu130" +url = "https://download.pytorch.org/whl/cu130" +explicit = true + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.ruff] +line-length = 100 diff --git a/models/tts/kokoro-v1.1-zh/training/results.json b/models/tts/kokoro-v1.1-zh/training/results.json new file mode 100644 index 0000000..2c415c4 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/results.json @@ -0,0 +1,250 @@ +{ + "schema_version": 1, + "model": "Kokoro v1.1-zh / EMIME MF5 adaptation", + "training_date": "2026-09-15", + "production_qualified": false, + "artifacts": { + "model.pth": { + "sha256": "2c27865b7c794124197e49ab9b7c814da088e9ef87f1dd605f95b87e1458a4c0", + "bytes": 327453363 + }, + "voice.pt": { + "sha256": "119eca879d472a5327bc1e01c358958e9028a62d09d3a528d953248e94fbc8b3", + "bytes": 523739 + }, + "config.json": { + "sha256": "bc333efa5ce4ceff433c8c8e5d027a1eca0166001e4e4a62bea2d26ff7a46890", + "bytes": 3228 + } + }, + "source": { + "baseline_lock": "baseline.lock.json", + "training_assets_lock": "training-assets.lock.json", + "original_reports_commit": "fb13fc48d599b733759a4ecc60d0c10b6499fffc", + "training_dependency_lock_sha256": "2a5b4cb9f1884f707aaf8a2970fc998065b3d01b6c12ae36cc4a341df4ac32d3", + "training_checkpoint_sha256": "9888fae1dcf7374c908478b295e36089d88476bff1fe57da9b2e8d2e6dcb806d", + "training_manifest_sha256": "7c70acb06011992e57a77aa5f509ecf0ccefde2a06009b79c4df88771148e7b7" + }, + "data": { + "speaker": "EMIME-MF5", + "microphone": 0, + "counts": { + "train/en": { + "utterances": 96, + "seconds": 419.90000000000003 + }, + "test/en": { + "utterances": 43, + "seconds": 158.14999999999998 + }, + "dev/en": { + "utterances": 6, + "seconds": 25.1 + }, + "train/zh": { + "utterances": 129, + "seconds": 928.05 + }, + "test/zh": { + "utterances": 31, + "seconds": 116.39999999999999 + }, + "dev/zh": { + "utterances": 9, + "seconds": 58.050000000000004 + } + }, + "session_disjoint": false, + "real_code_switch_recordings": 0, + "split_policy": "official test IDs + translated passage grouping; deterministic 10% dev from remaining passages" + }, + "training": { + "steps": 500, + "seed": 1729, + "lr": 2e-05, + "style_lr": 0.001, + "train_style": true, + "accumulation": 2, + "crop_frames": 96, + "validate_every": 100, + "recording_presentations": 1000, + "unique_recordings_seen": 223, + "trainable_parameters": 22194996, + "device": "NVIDIA RTX A6000", + "torch": "2.11.0+cu130" + }, + "selection": { + "policy": "evaluation-policy.json", + "candidates": [ + { + "step": 100, + "english_wer": 0.018867924528301886, + "mandarin_cer": 0.0851063829787234, + "development_loss": 3.9851582368214924, + "signal_pass": true + }, + { + "step": 300, + "english_wer": 0.0, + "mandarin_cer": 0.059574468085106386, + "development_loss": 3.3455609003702804, + "signal_pass": true + }, + { + "step": 500, + "english_wer": 0.0, + "mandarin_cer": 0.059574468085106386, + "development_loss": 3.1982753117879232, + "signal_pass": true + } + ], + "selected_step": 500, + "test_used_for_selection": false + }, + "evaluation": { + "protocol": "whisper-turbo-greedy-forced-language-v1", + "asr": { + "repo": "openai/whisper-large-v3-turbo", + "revision": "41f01f3fe87f28c78e2fbf8b568835947dd65ed9", + "weights_sha256": "542566a422ae4f3fd23f1ba11add198fca01bbf82e66e6a2857b3f608b1eb9d1" + }, + "normalization": "NFKC, simplified Chinese, lowercase, strip punctuation; WER English, CER Mandarin/mixed", + "precision": { + "generator_tensor_dtype": "float32", + "asr_tensor_dtype": "torch.float16", + "matmul_allow_tf32": false, + "cudnn_allow_tf32": true + }, + "heldout": { + "test/en/baseline": { + "utterances": 43, + "errors": 20, + "reference_units": 325, + "metric": "WER", + "rate": 0.06153846153846154 + }, + "test/en/candidate": { + "utterances": 43, + "errors": 14, + "reference_units": 325, + "metric": "WER", + "rate": 0.043076923076923075 + }, + "test/en/real_reference": { + "utterances": 43, + "errors": 17, + "reference_units": 325, + "metric": "WER", + "rate": 0.052307692307692305 + }, + "test/zh/baseline": { + "utterances": 31, + "errors": 7, + "reference_units": 466, + "metric": "CER", + "rate": 0.015021459227467811 + }, + "test/zh/candidate": { + "utterances": 31, + "errors": 9, + "reference_units": 466, + "metric": "CER", + "rate": 0.019313304721030045 + }, + "test/zh/real_reference": { + "utterances": 31, + "errors": 5, + "reference_units": 466, + "metric": "CER", + "rate": 0.01072961373390558 + } + }, + "development_controls": { + "controls/mixed/baseline": { + "utterances": 6, + "errors": 17, + "reference_units": 129, + "metric": "CER", + "rate": 0.13178294573643412 + }, + "controls/mixed/candidate": { + "utterances": 6, + "errors": 25, + "reference_units": 129, + "metric": "CER", + "rate": 0.1937984496124031 + }, + "controls/zh/baseline": { + "utterances": 3, + "errors": 14, + "reference_units": 42, + "metric": "CER", + "rate": 0.3333333333333333 + }, + "controls/zh/candidate": { + "utterances": 3, + "errors": 14, + "reference_units": 42, + "metric": "CER", + "rate": 0.3333333333333333 + }, + "controls/en/baseline": { + "utterances": 3, + "errors": 3, + "reference_units": 28, + "metric": "WER", + "rate": 0.10714285714285714 + }, + "controls/en/candidate": { + "utterances": 3, + "errors": 3, + "reference_units": 28, + "metric": "WER", + "rate": 0.10714285714285714 + } + }, + "heldout_report_sha256": "cea3ae165b150e405ea9e6775c7afa7a81ec736904f9ff2eb90994e47c6a682e" + }, + "validation": { + "baseline_parity_cases": 15, + "baseline_parity_passed": true, + "loaded_state_tensors": 688, + "gradient_and_supervised_parity_passed": true, + "real_recording_cases": 8, + "resume": { + "passed": true, + "checks": { + "model": true, + "optimizer": true, + "cpu_rng": true, + "cuda_rng": true, + "sample_rng": true, + "step": true, + "best": true + }, + "protocol": "3 uninterrupted real-data optimizer updates vs 1 update + new process + 2 updates" + }, + "targets_reproduced": true, + "bundle": { + "passed": true, + "checkpoint_tensors_match": true, + "voice_table_matches": true, + "all_upstream_state_tensors_match": true + }, + "export_parity_cases": 12, + "changed_generator_tensors": 148, + "frozen_parameters_unchanged": true, + "micro_loss_initial": 6.79434859752655, + "micro_loss_final": 3.8678985238075256, + "development_loss_initial": 5.985551230112711, + "development_loss_final": 3.1982753117879232, + "peak_cuda_bytes": 2006450688 + }, + "limitations": [ + "Small single-speaker corpus; only one session per language", + "No real code-switch training recordings", + "Mixed-language ASR can translate instead of transcribe", + "Mandarin held-out CER regresses slightly", + "Production voice consent and bilingual listening/tone acceptance not established" + ] +} diff --git a/models/tts/kokoro-v1.1-zh/training/schemas/recording.schema.json b/models/tts/kokoro-v1.1-zh/training/schemas/recording.schema.json new file mode 100644 index 0000000..bc0e8dc --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/schemas/recording.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Original real recording acquisition record v1", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "id", "corpus", "speaker_id", "speaker_gender", "speaker_evidence_ref", "session_id", "passage_id", "duplicate_group", "language", "origin", "audio_path", "audio_sha256", "sample_rate", "channels", "duration_seconds", "raw_text", "normalized_text", "split", "rights", "qc"], + "properties": { + "schema_version": {"const": 1}, + "id": {"type": "string", "pattern": "^[A-Za-z0-9_.-]+$"}, + "corpus": {"type": "string", "minLength": 1}, + "speaker_id": {"type": "string", "minLength": 1}, + "speaker_gender": {"enum": ["female", "male", "unknown"]}, + "speaker_evidence_ref": {"type": "string", "minLength": 1}, + "session_id": {"type": "string", "minLength": 1}, + "passage_id": {"type": "string", "minLength": 1}, + "duplicate_group": {"type": "string", "minLength": 1}, + "language": {"enum": ["en", "zh", "mixed"]}, + "origin": {"enum": ["real_recording", "generated", "unknown"]}, + "audio_path": {"type": "string", "minLength": 1}, + "audio_sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}, + "sample_rate": {"type": "integer", "minimum": 8000}, + "channels": {"type": "integer", "minimum": 1}, + "duration_seconds": {"type": "number", "exclusiveMinimum": 0}, + "raw_text": {"type": "string", "minLength": 1}, + "normalized_text": {"type": "string", "minLength": 1}, + "split": {"enum": ["train", "dev", "test", "unassigned"]}, + "rights": { + "type": "object", "additionalProperties": false, + "required": ["status", "evidence_ref", "scope"], + "properties": { + "status": {"enum": ["approved", "pending", "denied"]}, + "evidence_ref": {"type": "string", "minLength": 1}, + "scope": {"enum": ["research_only", "production_voice", "unknown"]} + } + }, + "qc": { + "type": "object", "additionalProperties": false, + "required": ["status", "evidence_ref"], + "properties": { + "status": {"enum": ["reviewed", "pending", "rejected"]}, + "evidence_ref": {"type": "string", "minLength": 1} + } + } + } +} diff --git a/models/tts/kokoro-v1.1-zh/training/scripts/check-bundle.py b/models/tts/kokoro-v1.1-zh/training/scripts/check-bundle.py new file mode 100644 index 0000000..78690be --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/scripts/check-bundle.py @@ -0,0 +1,80 @@ +"""Verify exported trained tensors, upstream interoperability, and real inference.""" + +import argparse +import json +import os +from pathlib import Path +import time + +os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") +import torch +from kokoro import KModel + +from kokoro_training.artifacts import ROOT, sha256, write_json +from kokoro_training.bundle import load_bundle +from kokoro_training.frontend import Frontend +from kokoro_training.model import CompatibleGenerator, select_style + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--bundle", type=Path, required=True) + parser.add_argument("--checkpoint", type=Path, required=True) + parser.add_argument("--assets", type=Path, default=Path(".artifacts/baseline")) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + if args.output.exists(): + raise ValueError("Choose a new evidence path") + torch.set_num_threads(4) + torch.backends.cuda.matmul.allow_tf32 = False + model, voices, manifest = load_bundle(args.bundle, "cuda") + saved = torch.load(args.checkpoint, map_location="cpu", weights_only=True) + for key, tensor in model.state_dict().items(): + assert torch.equal(tensor.cpu(), saved["model"]["generator." + key]), key + original_voices = torch.load(args.assets / "voices/zf_001.pt", weights_only=True) + assert torch.equal(voices.cpu(), original_voices + saved["model"]["style_offset"][None]) + oracle = KModel(repo_id="hexgrad/Kokoro-82M-v1.1-zh", config=str(args.bundle / "config.json"), + model=str(args.bundle / "model.pth")).cuda().eval() + for key, tensor in model.state_dict().items(): + assert torch.equal(tensor, oracle.state_dict()[key]), key + base = CompatibleGenerator(model.config) + base.load_checkpoint(torch.load(args.assets / "kokoro-v1_1-zh.pth", weights_only=True)) + changes = {} + for name, tensor in model.state_dict().items(): + delta = tensor.cpu() - base.state_dict()[name] + group = changes.setdefault(name.split(".")[0], {"changed_tensors": 0, "total_tensors": 0, "max_absolute_change": 0.0}) + group["total_tensors"] += 1 + group["changed_tensors"] += not torch.equal(tensor.cpu(), base.state_dict()[name]) + group["max_absolute_change"] = max(group["max_absolute_change"], float(delta.abs().max())) + assert changes["bert"]["changed_tensors"] == 0 + assert changes["decoder"]["changed_tensors"] == 0 + assert all(changes[k]["changed_tensors"] > 0 for k in ("bert_encoder", "predictor", "text_encoder")) + frontend = Frontend(model.config["vocab"]) + cases = [] + for case in json.loads((ROOT / "evaluation-prompts.json").read_text()): + ids = torch.tensor([frontend(case["text"])["input_ids"]], device="cuda") + style, _ = select_style(voices, ids.shape[1] - 2) + torch.manual_seed(1729) + expected, durations = oracle.forward_with_tokens(ids, style) + torch.manual_seed(1729) + torch.cuda.synchronize() + start = time.perf_counter() + actual, actual_durations = model(ids, style) + torch.cuda.synchronize() + elapsed = time.perf_counter() - start + assert torch.equal(expected.reshape(-1), actual.reshape(-1)), case["id"] + assert torch.equal(durations, actual_durations) + assert torch.isfinite(actual).all() and float(actual.square().mean().sqrt()) > 0.001 + assert float((actual.abs() >= 1).float().mean()) < 0.001 + cases.append({"id": case["id"], "sample_count": actual.numel(), "upstream_waveform_max_abs": 0.0, + "seconds": actual.numel() / 24000, "inference_seconds": elapsed, + "rtf": elapsed / (actual.numel() / 24000), "peak": float(actual.abs().max())}) + write_json(args.output, {"passed": True, "checkpoint_tensors_match": True, "voice_table_matches": True, + "all_upstream_state_tensors_match": True, "model_sha256": sha256(args.bundle / "model.pth"), + "step": manifest["step"], "changes_from_baseline": changes, "cases": cases, + "timing_scope": "warm GPU inference only, excludes frontend and loading; shared GPU run"}) + print(json.dumps({"passed": True, "cases": len(cases), "changes": changes})) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/scripts/check-real-training.py b/models/tts/kokoro-v1.1-zh/training/scripts/check-real-training.py new file mode 100644 index 0000000..ecf05a9 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/scripts/check-real-training.py @@ -0,0 +1,89 @@ +"""Explicit GPU integration check using acquired recordings, never fixture audio.""" + +import argparse +import json +import os +from pathlib import Path + +os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") +import numpy as np +import torch + +from kokoro_training.artifacts import write_json +from kokoro_training.features import dtw_durations +from kokoro_training.model import CompatibleGenerator, select_style +from kokoro_training.supervised import AdaptationModel, SupervisedLoss +from kokoro_training.train import batch_for, load_data + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--data", type=Path, required=True) + parser.add_argument("--assets", type=Path, default=Path(".artifacts/baseline")) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + if args.output.exists(): + raise ValueError("Choose a new evidence path") + torch.set_num_threads(4) + torch.use_deterministic_algorithms(True) + rows, _ = load_data(args.data) + g = CompatibleGenerator(json.loads((args.assets / "config.json").read_text())) + g.load_checkpoint(torch.load(args.assets / "kokoro-v1_1-zh.pth", weights_only=True)) + model = AdaptationModel(g, torch.load(args.assets / "voices/zf_001.pt", weights_only=True), train_style=True).cuda() + criterion = SupervisedLoss().cuda() + cases, controls = [], [] + for language in ("en", "zh"): + subset = [r for r in rows if r["language"] == language and r["split"] == "train"][:4] + for index, row in enumerate(subset): + batch = batch_for(row, args.data, "cuda") + model.eval() + style, _ = select_style(model.voices, batch["ids"].shape[1] - 2) + torch.manual_seed(3456) + audio, durations = g(batch["ids"], style) + size = int(durations.sum()) + parity_batch = {**batch, "durations": durations[None], "audio": audio[None], + "f0": torch.zeros(size * 2, device="cuda"), "energy": torch.zeros(1, size * 2, device="cuda")} + torch.manual_seed(3456) + with torch.no_grad(): + output = model(parity_batch) + delta = float((output["audio"].reshape(-1) - audio).abs().max()) + assert delta == 0, delta + model.train() + model.zero_grad(set_to_none=True) + losses = criterion(model(batch, crop_frames=96)) + losses["total"].backward() + coverage = {} + for name, parameter in model.named_parameters(): + if parameter.requires_grad: + assert parameter.grad is not None and torch.isfinite(parameter.grad).all(), name + group = name.split(".")[1] if name.startswith("generator.") else name + coverage[group] = coverage.get(group, 0.0) + float(parameter.grad.norm()) + else: + assert parameter.grad is None, name + assert all(v > 0 for v in coverage.values()), coverage + cases.append({"id": row["id"], "language": language, "supervised_parity_max_abs": delta, + "gradient_group_norm_sums": coverage, "loss": float(losses["total"].detach())}) + teacher = audio.cpu().numpy() + real = batch["audio"].cpu().numpy().reshape(-1) + wrong = batch_for(subset[(index + 1) % len(subset)], args.data, "cpu")["audio"].numpy().reshape(-1) + # Wrong-text negative control is real speech from the same speaker/language. + report = {"id": row["id"]} + for label, target in (("self", teacher), ("matched", real), ("wrong_text", wrong)): + try: + transferred, detail = dtw_durations(teacher, target, durations.cpu().numpy()) + if label == "self": + assert np.array_equal(transferred, durations.cpu().numpy()) + report[label] = detail["mean_path_cost"] + except ValueError as exc: + report[label] = {"rejected": str(exc)} + controls.append(report) + passed = all(isinstance(c["matched"], float) and isinstance(c["wrong_text"], float) + and c["matched"] < c["wrong_text"] for c in controls) + write_json(args.output, {"gradient_and_supervised_parity_passed": True, "cases": cases, + "alignment_controls": controls, "all_matched_costs_below_wrong_text": passed, + "limitations": "DTW control discrimination is not phoneme-boundary annotation accuracy"}) + print(json.dumps({"cases": len(cases), "all_matched_costs_below_wrong_text": passed})) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/scripts/check-resume.py b/models/tts/kokoro-v1.1-zh/training/scripts/check-resume.py new file mode 100644 index 0000000..020b465 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/scripts/check-resume.py @@ -0,0 +1,47 @@ +"""Compare uninterrupted real-data optimization with save/restart/resume.""" + +import argparse +import json +from pathlib import Path +import subprocess +import sys + +import torch + +from kokoro_training.artifacts import write_json + + +def equal(left, right): + if isinstance(left, torch.Tensor): + return isinstance(right, torch.Tensor) and torch.equal(left, right) + if isinstance(left, dict): + return left.keys() == right.keys() and all(equal(left[k], right[k]) for k in left) + if isinstance(left, (list, tuple)): + return len(left) == len(right) and all(equal(a, b) for a, b in zip(left, right, strict=True)) + return left == right + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--data", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + args.output.mkdir(parents=True, exist_ok=False) + common = [sys.executable, "-m", "kokoro_training.train", "--data", str(args.data), + "--micro", "2", "--train-style", "--validate-every", "1", "--accumulation", "1"] + for name, steps, resume in [("continuous", 3, False), ("resumed", 1, False), ("resumed", 3, True)]: + with (args.output / f"{name}-{steps}.log").open("w") as log: + subprocess.run([*common, "--output", str(args.output / name), "--steps", str(steps), + *(["--resume"] if resume else [])], stdout=log, stderr=subprocess.STDOUT, check=True) + continuous = torch.load(args.output / "continuous/last.pt", weights_only=True, map_location="cpu") + resumed = torch.load(args.output / "resumed/last.pt", weights_only=True, map_location="cpu") + checks = {key: equal(continuous[key], resumed[key]) for key in + ("model", "optimizer", "cpu_rng", "cuda_rng", "sample_rng", "step", "best")} + write_json(args.output / "result.json", {"passed": all(checks.values()), "checks": checks, + "protocol": "3 uninterrupted real-data optimizer updates vs 1 update + new process + 2 updates"}) + print(json.dumps(checks)) + assert all(checks.values()), checks + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/scripts/select-checkpoint.py b/models/tts/kokoro-v1.1-zh/training/scripts/select-checkpoint.py new file mode 100644 index 0000000..47a9d9b --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/scripts/select-checkpoint.py @@ -0,0 +1,58 @@ +"""Freeze a development-selected checkpoint before the test set is evaluated.""" + +import argparse +import json +from pathlib import Path +import torch + +from kokoro_training.artifacts import ROOT, sha256, write_json +from kokoro_training.bundle import export + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--run", type=Path, required=True) + parser.add_argument("--evaluations", type=Path, required=True) + parser.add_argument("--assets", type=Path, default=Path(".artifacts/baseline")) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--record", type=Path, required=True) + args = parser.parse_args() + torch.set_num_threads(4) + if args.record.exists(): + raise ValueError("Refusing to rewrite a selection record") + policy = json.loads((ROOT / "evaluation-policy.json").read_text()) + validations = {event["step"]: event["development"] for event in + (json.loads(line) for line in (args.run / "metrics.jsonl").read_text().splitlines()) + if "development" in event} + candidates = [] + for step in policy["candidate_steps"]: + result_file = args.evaluations / f"mf5-v2-step{step}-eval/results.json" + result = json.loads(result_file.read_text()) + if result["step"] != step or not all(e["suite"] in {"dev", "controls"} for e in result["entries"]): + raise ValueError("Selection may use development data and controls only") + signals = [entry["signal"] for entry in result["entries"] if entry["variant"] == "candidate"] + signal_pass = all(s["rms"] > 0.001 and s["clipping_fraction"] < 0.001 for s in signals) + english = result["aggregates"]["dev/en/candidate"]["rate"] + mandarin = result["aggregates"]["dev/zh/candidate"]["rate"] + candidates.append({"step": step, "score": (english + mandarin) / 2, + "english_wer": english, "mandarin_cer": mandarin, + "development_loss": validations[step]["total"], "signal_pass": signal_pass, + "evaluation_sha256": sha256(result_file), "evaluated_model_sha256": result["model_sha256"]}) + eligible = [entry for entry in candidates if entry["signal_pass"]] + if not eligible: + raise ValueError("No candidate passed signal checks") + selected = min(eligible, key=lambda entry: (entry["score"], entry["development_loss"])) + checkpoint = args.run / f"snapshot-{selected['step']}.pt" + bundle = export(checkpoint, args.assets, args.output) + if bundle["files"]["model.pth"]["sha256"] != selected["evaluated_model_sha256"]: + raise ValueError("Re-exported weights differ from the evaluated checkpoint") + record = {"policy": policy, "policy_sha256": sha256(ROOT / "evaluation-policy.json"), + "candidates": candidates, "selected": selected, + "checkpoint_sha256": sha256(checkpoint), "bundle_manifest_sha256": sha256(args.output / "bundle.json"), + "test_used_for_selection": False} + write_json(args.record, record) + print(json.dumps(selected, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/__init__.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/__init__.py new file mode 100644 index 0000000..5e6cafa --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/__init__.py @@ -0,0 +1 @@ +"""Readiness tooling; no implicit training, acquisition, or model execution.""" diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/acquire.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/acquire.py new file mode 100644 index 0000000..2c6589c --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/acquire.py @@ -0,0 +1,84 @@ +"""Explicit, checksum-pinned acquisition of real MF5 data, JDC, and evaluation ASR.""" + +import argparse +import json +from pathlib import Path, PurePosixPath +import re +import shutil +import tarfile +import urllib.request + +from .artifacts import ROOT, sha256, write_json + + +def acquire(part, output): + lock = json.loads((ROOT / "training-assets.lock.json").read_text()) + for entry in lock["files"]: + if entry["part"] != part: + continue + path = output / entry["path"] + if path.exists(): + if path.stat().st_size != entry["bytes"] or sha256(path) != entry["sha256"]: + raise ValueError(f"Existing asset differs: {entry['path']}") + continue + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".partial") + try: + with urllib.request.urlopen(entry["url"], timeout=60) as stream, temporary.open("wb") as destination: + shutil.copyfileobj(stream, destination) + if temporary.stat().st_size != entry["bytes"] or sha256(temporary) != entry["sha256"]: + raise ValueError(f"Acquisition checksum mismatch: {entry['path']}") + temporary.replace(path) + finally: + temporary.unlink(missing_ok=True) + if part == "targets": + write_json(output / "targets/lock.json", lock["target_model"]) + + +def extract_mf5(archive, output): + lock = json.loads((ROOT / "training-assets.lock.json").read_text()) + expected = next(e for e in lock["files"] if e["part"] == "data") + if sha256(archive) != expected["sha256"]: + raise ValueError("Unexpected corpus archive") + if output.exists(): + raise ValueError("Refusing to overwrite extracted recordings") + output.mkdir(parents=True) + documents = {"README_mandarin_1.0.txt", "README_mandarin_1.1.txt", "odbl-10.txt", + "english_prompts.txt", "mandarin_prompts.txt", "EnglishTestingData", + "MandarinTestingData", "EMIME_MANDARIN_DATABASE_ACCENTS.pdf"} + count = 0 + with tarfile.open(archive, "r|bz2") as tar: + for member in tar: + name = PurePosixPath(member.name) + if name.is_absolute() or ".." in name.parts or member.issym() or member.islnk(): + raise ValueError("Unsafe corpus member") + if not member.isfile(): + continue + is_recording = re.fullmatch(r"MF5_(ENG|MAN)_\d+_0.wav", name.name) + if not is_recording and name.name not in documents: + continue + if "Mandarin_test_set_segmentations" in name.parts or member.size > 100_000_000: + raise ValueError("Unexpected selected corpus member") + path = output / str(name) + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("xb") as destination, tar.extractfile(member) as source: + shutil.copyfileobj(source, destination) + count += bool(is_recording) + if count != 314: + raise ValueError(f"Expected 314 single-microphone MF5 recordings, got {count}") + print(f"Extracted {count} real MF5 recordings and supporting documents") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("part", choices=("data", "targets", "asr", "extract-mf5")) + parser.add_argument("--output", type=Path, default=Path(".artifacts")) + args = parser.parse_args() + if args.part == "extract-mf5": + extract_mf5(args.output / "emime/UEDIN_mandarin_bilingual_data_v1.1.tar.bz2", args.output / "emime/mf5-source") + else: + acquire(args.part, args.output) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/artifacts.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/artifacts.py new file mode 100644 index 0000000..8ec6c57 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/artifacts.py @@ -0,0 +1,118 @@ +"""Pinned acquisition and portable provenance (no private paths in reports).""" + +import hashlib +import importlib.metadata +import json +import os +from pathlib import Path +import platform +import shutil +import subprocess +import tempfile +import urllib.request + +ROOT = Path(__file__).resolve().parents[2] +LOCK = ROOT / "baseline.lock.json" + + +def sha256(path: Path) -> str: + with path.open("rb") as stream: + return hashlib.file_digest(stream, "sha256").hexdigest() + + +def write_json(path: Path, value: dict) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + data = json.dumps(value, indent=2, ensure_ascii=False, allow_nan=False) + "\n" + with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, delete=False) as stream: + temporary = Path(stream.name) + stream.write(data) + try: + temporary.replace(path) + finally: + temporary.unlink(missing_ok=True) + + +def verify_assets(directory: Path) -> dict: + lock = json.loads(LOCK.read_text()) + for name, expected in lock["files"].items(): + path = directory / name + if not path.is_file() or path.stat().st_size != expected["bytes"]: + raise ValueError(f"Missing or wrong-sized pinned asset: {name}") + if sha256(path) != expected["sha256"]: + raise ValueError(f"SHA-256 mismatch: {name}") + return lock + + +def fetch_assets(directory: Path) -> dict: + """Explicit network operation; download only the three locked baseline assets.""" + lock = json.loads(LOCK.read_text()) + for name, expected in lock["files"].items(): + target = directory / name + if target.is_file() and sha256(target) == expected["sha256"]: + continue + if target.exists(): + raise ValueError(f"Refusing to replace mismatched existing asset: {name}") + target.parent.mkdir(parents=True, exist_ok=True) + url = f"https://huggingface.co/{lock['repo_id']}/resolve/{lock['revision']}/{name}" + temporary = target.with_suffix(target.suffix + ".partial") + try: + with urllib.request.urlopen(url, timeout=60) as response, temporary.open("wb") as out: + shutil.copyfileobj(response, out) + if ( + temporary.stat().st_size != expected["bytes"] + or sha256(temporary) != expected["sha256"] + ): + raise ValueError(f"Downloaded asset failed verification: {name}") + temporary.replace(target) + finally: + temporary.unlink(missing_ok=True) + return verify_assets(directory) + + +def environment() -> dict: + import torch + import kokoro + + def command(args): + result = subprocess.run(args, cwd=ROOT, capture_output=True, text=True, check=False) + return result.stdout.strip() + + kokoro_root = Path(kokoro.__file__).parent + data = { + "schema_version": 1, + "python": platform.python_version(), + "os": platform.system(), + "architecture": platform.machine(), + "cpu_count": os.cpu_count(), + "torch": torch.__version__, + "cuda_build": torch.version.cuda, + "cuda_available": torch.cuda.is_available(), + "packages": { + p: importlib.metadata.version(p) + for p in ["kokoro", "transformers", "numpy", "soundfile"] + }, + "dependency_lock_sha256": sha256(ROOT / "uv.lock"), + "baseline_lock_sha256": sha256(LOCK), + "code_revision": command(["git", "rev-parse", "HEAD"]), + "working_tree_dirty": bool(command(["git", "status", "--porcelain"])), + "tool_source_sha256": { + p.name: sha256(p) for p in sorted(Path(__file__).parent.glob("*.py")) + }, + "kokoro_source_sha256": {p.name: sha256(p) for p in sorted(kokoro_root.glob("*.py"))}, + "disk_free_bytes": shutil.disk_usage(ROOT).free, + } + limit = Path("/sys/fs/cgroup/memory.max") + if limit.exists(): + data["container_memory_limit"] = limit.read_text().strip() + if torch.cuda.is_available(): + gpu = torch.cuda.get_device_properties(0) + data["gpu"] = { + "name": gpu.name, + "vram_bytes": gpu.total_memory, + "capability": list(torch.cuda.get_device_capability(0)), + "bf16_supported": torch.cuda.is_bf16_supported(), + } + data["driver"] = command( + ["nvidia-smi", "--query-gpu=driver_version", "--format=csv,noheader"] + ) + return data diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/bundle.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/bundle.py new file mode 100644 index 0000000..9f1bfe4 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/bundle.py @@ -0,0 +1,181 @@ +"""Export/load an auditable PyTorch checkpoint with its matching voice and frontend.""" + +import argparse +import json +import os +from pathlib import Path +import shutil + +os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") +import soundfile as sf +import torch + +from .artifacts import ROOT, sha256, verify_assets, write_json +from .frontend import Frontend, VERSION +from .model import CompatibleGenerator, select_style + + +def export(checkpoint: Path, assets: Path, output: Path): + if output.exists(): + raise ValueError("Refusing to overwrite a model bundle") + verify_assets(assets) + saved = torch.load(checkpoint, weights_only=True, map_location="cpu") + config = json.loads((assets / "config.json").read_text()) + model = CompatibleGenerator(config) + state = {k.removeprefix("generator."): v for k, v in saved["model"].items() if k.startswith("generator.")} + model.load_state_dict(state, strict=True) + if not all(torch.isfinite(v).all() for v in state.values()): + raise ValueError("Nonfinite checkpoint") + # Nested module dictionaries also load through the upstream KModel API. + nested = {name: child.state_dict() for name, child in model.named_children()} + voices = torch.load(assets / "voices/zf_001.pt", weights_only=True, map_location="cpu") + voices = voices + saved["model"]["style_offset"][None] + select_style(voices, 1) + output.mkdir(parents=True) + torch.save(nested, output / "model.pth") + torch.save(voices, output / "voice.pt") + shutil.copy2(assets / "config.json", output / "config.json") + shutil.copy2(ROOT / "uv.lock", output / "uv.lock") + shutil.copy2(ROOT / "LICENSE.kokoro", output / "LICENSE.kokoro") + write_json(output / "vocab.json", config["vocab"]) + # Carry the exact source required by the source-checkout command documented below. + source = output / "src/kokoro_training" + source.mkdir(parents=True) + for file in (ROOT / "src/kokoro_training").glob("*.py"): + shutil.copy2(file, source / file.name) + shutil.copy2(ROOT / "pyproject.toml", output / "pyproject.toml") + shutil.copy2(ROOT / "baseline.lock.json", output / "baseline.lock.json") + for name in ("training-assets.lock.json", "evaluation-prompts.json", "evaluation-policy.json", "NOTICE.md"): + shutil.copy2(ROOT / name, output / name) + (output / "README.md").write_text(f"""# Kokoro English/Mandarin PyTorch adaptation + +Actual trained weights from optimizer update {saved['step']}, adapted from +Kokoro-82M-v1.1-zh using real EMIME MF5 recordings. Output: mono 24 kHz. +Use `model.pth` together with this bundle's `voice.pt`, configuration, vocabulary, +and frontend. The generator retains the 81.8M-parameter Kokoro architecture. + +## Inference + +Requires Linux, Python 3.12, `uv`, and the system `espeak-ng` library. +From this directory: + +```bash +uv sync --frozen +uv run --frozen python -m kokoro_training.bundle infer \\ + --bundle . --text '请打开 API,然后把结果发给我。' \\ + --output example.wav --device cuda +``` + +Use `--device cpu` for CPU execution. Inference loads local weights only. +Text above 510 content tokens must be split by the caller. Speed must be +within 0.25..4 and synthesis is capped at 4,000 duration frames (100 seconds). +The output WAV is accompanied by tokens, durations, seed, and hashes in JSON. + +`model.pth` contains nested module state dictionaries accepted by upstream +`kokoro.KModel(config='config.json', model='model.pth', +repo_id='hexgrad/Kokoro-82M-v1.1-zh')`. The bundled inference path additionally +checks hashes, token bounds, and strict weight mapping. `voice.pt` has shape +510 × 1 × 256; choose row `number_of_content_tokens - 1`, excluding BOS/EOS. + +## Scope and provenance + +This is a local experimental trained model, not a production-qualified voice. +The corpus provides separate English and Mandarin recordings, no real same-voice +code-switch recordings, and only one session per language. Production voice +consent and bilingual listening/tone/naturalness acceptance are not certified. +Do not treat ASR diagnostics as those certifications. No deployment is included. + +`bundle.json` records hashes for all packaged components and the training +checkpoint/configuration. `NOTICE.md` records upstream and corpus attribution. +The base model is Apache-2.0; the corpus README specifies ODbL/DbCL. These +attributions do not independently grant production voice/personality rights. + +Full run reports and source: https://github.com/FluidInference/mobius/pull/95 +""") + manifest = {"format": "kokoro-pytorch-bundle-v1", "sample_rate": 24000, + "model": "Kokoro-82M-v1.1-zh MF5 supervised adaptation", "step": saved["step"], + "training_checkpoint_sha256": sha256(checkpoint), "training_config": saved["config"], + "frontend_version": VERSION, "speaker": "EMIME-MF5", "base_style": "zf_001", + "production_qualified": False, + "limitations": ["Small real-recording adaptation, not production-qualified", + "No real same-speaker code-switch recordings used", + "Session-disjoint validation unavailable in this corpus", + "Production voice consent/rights not certified; local research artifact"], + "files": {str(p.relative_to(output)): {"sha256": sha256(p), "bytes": p.stat().st_size} + for p in sorted(output.rglob("*")) if p.is_file()}} + write_json(output / "bundle.json", manifest) + return manifest + + +def load_bundle(directory: Path, device="cpu"): + manifest = json.loads((directory / "bundle.json").read_text()) + if manifest["format"] != "kokoro-pytorch-bundle-v1" or manifest["frontend_version"] != VERSION: + raise ValueError("Unsupported model/frontend version") + required = {"model.pth", "voice.pt", "config.json", "vocab.json", "src/kokoro_training/frontend.py"} + if not required <= manifest["files"].keys(): + raise ValueError("Incomplete bundle manifest") + for name, expected in manifest["files"].items(): + path = directory / name + if not path.resolve().is_relative_to(directory.resolve()) or sha256(path) != expected["sha256"]: + raise ValueError(f"Bundle checksum/path mismatch: {name}") + if sha256(ROOT / "src/kokoro_training/frontend.py") != manifest["files"]["src/kokoro_training/frontend.py"]["sha256"]: + raise ValueError("Installed frontend differs from the bundled frontend") + config = json.loads((directory / "config.json").read_text()) + if json.loads((directory / "vocab.json").read_text()) != config["vocab"]: + raise ValueError("Bundled vocabulary differs from model configuration") + generator = CompatibleGenerator(config) + generator.load_checkpoint(torch.load(directory / "model.pth", weights_only=True, map_location="cpu")) + voices = torch.load(directory / "voice.pt", weights_only=True, map_location="cpu") + select_style(voices, 1) + return generator.to(device).eval(), voices.to(device), manifest + + +def synthesize(model, voices, front, text, device, seed=1729, speed=1.0): + inputs = front(text) + ids = torch.tensor([inputs["input_ids"]], device=device) + style, row = select_style(voices, ids.shape[1] - 2) + with torch.random.fork_rng(devices=[torch.device(device).index or 0] if str(device).startswith("cuda") else []): + torch.manual_seed(seed) + audio, durations = model(ids, style, speed=speed) + if not torch.isfinite(audio).all() or audio.numel() != int(durations.sum()) * 600: + raise ValueError("Invalid synthesized waveform") + inputs.update(seed=seed, speed=speed, style_row=row, sample_rate=24000, + samples=audio.numel(), durations=durations.cpu().tolist(), + peak=float(audio.abs().max()), rms=float(audio.square().mean().sqrt()), + clipping_fraction=float((audio.abs() >= 1).float().mean()), origin="model_generated_evaluation_audio") + return audio.cpu().numpy(), inputs + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + pack = commands.add_parser("export") + pack.add_argument("--checkpoint", type=Path, required=True) + pack.add_argument("--assets", type=Path, default=Path(".artifacts/baseline")) + pack.add_argument("--output", type=Path, required=True) + infer = commands.add_parser("infer") + infer.add_argument("--bundle", type=Path, required=True) + infer.add_argument("--text", required=True) + infer.add_argument("--output", type=Path, required=True) + infer.add_argument("--device", default="cpu") + infer.add_argument("--seed", type=int, default=1729) + infer.add_argument("--speed", type=float, default=1.0) + args = parser.parse_args() + torch.set_num_threads(4) + if args.command == "export": + result = export(args.checkpoint, args.assets, args.output) + print(json.dumps({"bundle": str(args.output), "step": result["step"], "model": result["files"]["model.pth"]})) + else: + if args.output.exists() or args.output.with_suffix(".json").exists(): + raise ValueError("Refusing to overwrite synthesis artifacts") + model, voices, manifest = load_bundle(args.bundle, args.device) + audio, metadata = synthesize(model, voices, Frontend(model.config["vocab"]), args.text, args.device, args.seed, args.speed) + args.output.parent.mkdir(parents=True, exist_ok=True) + sf.write(args.output, audio, 24000, subtype="FLOAT") + metadata.update(model_sha256=manifest["files"]["model.pth"]["sha256"], wav_sha256=sha256(args.output)) + write_json(args.output.with_suffix(".json"), metadata) + print(json.dumps({"output": str(args.output), "seconds": len(audio) / 24000, "peak": metadata["peak"]})) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/cli.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/cli.py new file mode 100644 index 0000000..7bb35bc --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/cli.py @@ -0,0 +1,75 @@ +"""Each command performs one explicit stage; none starts an optimizer.""" + +import argparse +import json +import os +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="stage", required=True) + fetch = sub.add_parser( + "fetch-baseline", help="Explicitly download and verify pinned public assets" + ) + fetch.add_argument("--assets", type=Path, required=True) + env = sub.add_parser("environment") + env.add_argument("--output", type=Path, required=True) + par = sub.add_parser("parity") + par.add_argument("--assets", type=Path, required=True) + par.add_argument("--output", type=Path, required=True) + par.add_argument("--device", choices=["cpu", "cuda"], default="cuda") + par.add_argument("--seed", type=int, default=1729) + par.add_argument("--max-frames", type=int, default=4000) + audit = sub.add_parser("audit-data") + audit.add_argument("--manifest", type=Path, required=True) + audit.add_argument("--audio-root", type=Path, required=True) + audit.add_argument("--output", type=Path, required=True) + emime = sub.add_parser( + "inventory-emime", help="Inspect a supplied archive without extracting it" + ) + emime.add_argument("--archive", type=Path, required=True) + emime.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + # Must be set before CUDA initialization, including environment inspection. + os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8" + from .artifacts import environment, fetch_assets, write_json + + try: + if args.stage == "fetch-baseline": + report = fetch_assets(args.assets) + elif args.stage == "environment": + report = environment() + write_json(args.output, report) + elif args.stage == "parity": + if args.max_frames < 1: + raise ValueError("max-frames must be positive") + from .parity import parity + + report = parity(args.assets, args.output, args.device, args.seed, args.max_frames) + elif args.stage == "inventory-emime": + from .emime import inventory_emime + + report = inventory_emime(args.archive) + write_json(args.output, report) + else: + from .data import audit_data + + report = audit_data(args.manifest, args.audio_root) + write_json(args.output, report) + print( + json.dumps( + report + if args.stage != "parity" + else {k: v for k, v in report.items() if k != "cases"}, + indent=2, + ) + ) + return 0 if report.get("passed", True) else 1 + except (ValueError, OSError) as exc: + print(json.dumps({"passed": False, "error": str(exc)})) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/data.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/data.py new file mode 100644 index 0000000..6bd00a3 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/data.py @@ -0,0 +1,181 @@ +"""Acquisition-manifest audit. Not an aligner, preprocessor, or rights certifier.""" + +from collections import defaultdict +import json +import math +from pathlib import Path +import unicodedata + +from jsonschema import Draft202012Validator + +from .artifacts import ROOT, sha256 + + +def text_key(text: str) -> str: + # Conservative exact-text leakage key: retain digits/letters/Hanzi. + return "".join(c for c in unicodedata.normalize("NFKC", text).casefold() if c.isalnum()) + + +def audit_records(records: list, audio_root: Path | None) -> dict: + schema = json.loads((ROOT / "schemas/recording.schema.json").read_text()) + validator = Draft202012Validator(schema) + errors, measurements = [], [] + counts = defaultdict(lambda: {"utterances": 0, "seconds": 0.0}) + groups = { + k: defaultdict(set) + for k in ["session", "passage", "duplicate_group", "audio_sha256", "text"] + } + ids, speakers, splits, seen_audio = set(), set(), set(), set() + reviewed_rights = [] + demo = json.loads((ROOT.parent / "coreml/bilingual-demo/manifest.json").read_text()) + development_text = {text_key(c["text"]) for c in demo["clips"]} + if not records: + errors.append({"record": None, "reason": "empty manifest"}) + if audio_root is None: + errors.append({"record": None, "reason": "audio bytes not checked"}) + for index, record in enumerate(records): + + def error(reason): + errors.append({"record": index + 1, "reason": reason}) + + failures = list(validator.iter_errors(record)) + if failures: + for failure in failures: + # Do not copy private transcript/path/rights content into the report. + error( + f"schema violation at {'.'.join(map(str, failure.path)) or ''}: {failure.validator}" + ) + continue + reviewed_rights.append( + record["rights"]["status"] == "approved" + and record["rights"]["scope"] == "production_voice" + ) + if record["id"] in ids: + error("duplicate utterance ID") + ids.add(record["id"]) + speakers.add((record["corpus"], record["speaker_id"])) + if record["speaker_gender"] != "female": + error("target female speaker not established") + if record["origin"] != "real_recording": + error("not a real target-speaker recording") + if record["rights"]["status"] != "approved": + error("rights review not approved") + if record["qc"]["status"] != "reviewed": + error("recording/transcript QC not reviewed") + if record["split"] == "unassigned": + error("split not assigned") + duration = record["duration_seconds"] + if not math.isfinite(duration): + error("nonfinite duration") + continue + if not record["raw_text"].strip() or not record["normalized_text"].strip(): + error("blank transcript") + split = record["split"] + splits.add(split) + if record["audio_sha256"] in seen_audio: + error("duplicate audio bytes; do not count repeated recordings twice") + seen_audio.add(record["audio_sha256"]) + key = text_key(record["normalized_text"]) + if key in development_text and split != "dev": + error("historical demo prompt must remain development-only") + counts[record["language"]]["utterances"] += 1 + counts[record["language"]]["seconds"] += duration + for group, value in { + "session": (record["corpus"], record["speaker_id"], record["session_id"]), + "passage": (record["corpus"], record["passage_id"]), + "duplicate_group": (record["corpus"], record["duplicate_group"]), + "audio_sha256": record["audio_sha256"], + "text": key, + }.items(): + groups[group][value].add(split) + if audio_root is not None: + relative = Path(record["audio_path"]) + root = audio_root.resolve() + path = (root / relative).resolve() + if relative.is_absolute() or not path.is_relative_to(root): + error("audio path escapes supplied root") + continue + try: + if sha256(path) != record["audio_sha256"]: + error("audio SHA-256 mismatch") + continue + import numpy as np + import soundfile as sf + + info = sf.info(path) + if ( + info.samplerate != record["sample_rate"] + or info.channels != record["channels"] + or abs(info.duration - duration) > 1 / info.samplerate + ): + error("audio header/duration differs from manifest") + peak, nonfinite, clipped, samples = 0.0, 0, 0, 0 + for block in sf.blocks(path, blocksize=65536, dtype="float32", always_2d=True): + nonfinite += int((~np.isfinite(block)).sum()) + peak = max(peak, float(np.nan_to_num(np.abs(block)).max())) + clipped += int((np.abs(block) >= 1).sum()) + samples += block.size + if nonfinite or samples == 0 or peak == 0: + error("nonfinite, empty, or all-zero recording") + measurements.append( + { + "record": index + 1, + "peak": peak, + "clipping_fraction": clipped / samples if samples else 0, + "nonfinite_samples": nonfinite, + } + ) + except (OSError, RuntimeError, ValueError): + error("audio unavailable or undecodable") + if len(speakers) != 1: + errors.append({"record": None, "reason": "exactly one verified target speaker required"}) + for group, values in groups.items(): + leaking = sum(len(splits) > 1 for splits in values.values()) + if leaking: + errors.append({"record": None, "reason": f"{group} crosses splits", "groups": leaking}) + for language in ("en", "zh"): + if language not in counts: + errors.append({"record": None, "reason": f"missing {language} recordings"}) + if not {"train", "dev", "test"}.issubset(splits): + errors.append({"record": None, "reason": "train/dev/test splits not all represented"}) + return { + "schema_version": 1, + "passed": not errors, + "records": len(records), + "scope": "acquisition metadata and audio integrity only", + "training_ready": False, + "counts_by_language": dict(counts), + "speaker_count": len(speakers), + "errors": errors, + "audio_measurements": measurements, + "production_rights_recorded": bool(records) + and len(reviewed_rights) == len(records) + and all(reviewed_rights), + "limitations": [ + "rights/speaker/QC evidence references require external review", + "near-duplicate/paraphrase and semantic audio-text checks not implemented", + "mixed-language labels are declarations, not acoustic verification", + "frontend, prepared-audio, alignment and targets not yet audited", + ], + } + + +def audit_data(manifest: Path, audio_root: Path) -> dict: + if not manifest.is_file(): + return { + "passed": False, + "training_ready": False, + "records": 0, + "errors": [{"reason": "recording manifest missing; no training data acquired"}], + } + try: + records = [json.loads(line) for line in manifest.read_text().splitlines() if line.strip()] + report = audit_records(records, audio_root) + except (ValueError, TypeError) as exc: + return { + "passed": False, + "training_ready": False, + "errors": [{"reason": f"invalid JSONL manifest ({type(exc).__name__})"}], + } + report["manifest_sha256"] = sha256(manifest) + return report diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/emime.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/emime.py new file mode 100644 index 0000000..6428d2f --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/emime.py @@ -0,0 +1,94 @@ +"""Read-only archive inventory; no extraction, speaker selection, or training.""" + +from collections import defaultdict +import hashlib +import io +from pathlib import Path, PurePosixPath +import re +import tarfile + +import soundfile as sf + +from .artifacts import sha256 + +WAV_NAME = re.compile(r"^(M[FM][1-7])_(ENG|MAN)_(\d+)_([01])\.wav$", re.IGNORECASE) + + +def inventory_emime(archive: Path) -> dict: + counts = defaultdict(lambda: {"recordings": 0, "seconds": 0.0, "sample_rates": set()}) + documents, errors = [], [] + wav_count, extra_wav_count, matched_bytes = 0, 0, 0 + members_seen = set() + with tarfile.open(archive, mode="r|bz2") as tar: + for member in tar: + name = PurePosixPath(member.name) + if name.is_absolute() or ".." in name.parts or member.issym() or member.islnk(): + raise ValueError("Unexpected unsafe archive member") + if not member.isfile(): + continue + if member.name in members_seen: + raise ValueError("Duplicate archive member") + members_seen.add(member.name) + if member.size > 100_000_000: + raise ValueError("Unexpected member exceeds 100 MB inspection cap") + if name.suffix.lower() == ".wav": + wav_count += 1 + match = WAV_NAME.match(name.name) + # Segmented test copies are separate views, not new training recordings. + if match is None or "Mandarin_test_set_segmentations" in name.parts: + extra_wav_count += 1 + continue + speaker, lang, _, microphone = match.groups() + stream = tar.extractfile(member) + audio_bytes = stream.read() + matched_bytes += len(audio_bytes) + try: + info = sf.info(io.BytesIO(audio_bytes)) + except RuntimeError: + errors.append({"file": str(name), "reason": "unreadable audio header"}) + continue + group = counts[(speaker.upper(), lang.upper(), microphone)] + group["recordings"] += 1 + group["seconds"] += info.duration + group["sample_rates"].add(info.samplerate) + if info.channels != 1: + errors.append({"file": str(name), "reason": "unexpected non-mono audio"}) + elif name.suffix.lower() in {".txt", ".pdf"} or "license" in name.name.lower(): + content = tar.extractfile(member).read() + documents.append( + { + "file": str(name), + "bytes": len(content), + "sha256": hashlib.sha256(content).hexdigest(), + } + ) + if not counts: + errors.append({"reason": "no matching EMIME recordings found"}) + return { + "schema_version": 1, + "passed": not errors, + "scope": "archive metadata/header inventory; not QC, data approval, or training readiness", + "training_ready": False, + "archive_sha256": sha256(archive), + "archive_bytes": archive.stat().st_size, + "wav_files": wav_count, + "extra_or_segmented_wav_files": extra_wav_count, + "matched_audio_bytes": matched_bytes, + "groups": [ + { + "speaker_id": speaker, + "language": lang, + "microphone": mic, + **{k: (sorted(v) if isinstance(v, set) else v) for k, v in stats.items()}, + } + for (speaker, lang, mic), stats in sorted(counts.items()) + ], + "documents": documents, + "errors": errors, + "limitations": [ + "microphone views must not double usable hours or cross splits", + "test segmentations overlap source passages", + "no within-utterance code-switching demonstrated by directory labels", + "target speaker, accent/listening QC, transcript alignment, rights/consent and splits unresolved", + ], + } diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/evaluate.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/evaluate.py new file mode 100644 index 0000000..dbb7f46 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/evaluate.py @@ -0,0 +1,142 @@ +"""Matched, free-running baseline/candidate/reference ASR evaluation.""" + +import argparse +import json +import os +from pathlib import Path +import re +import unicodedata + +os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") +import jiwer +from opencc import OpenCC +from scipy.signal import resample_poly +import soundfile as sf +import torch +from transformers import WhisperForConditionalGeneration, WhisperProcessor + +from .artifacts import ROOT, sha256, verify_assets, write_json +from .bundle import load_bundle, synthesize +from .frontend import Frontend +from .model import CompatibleGenerator +from .train import load_data + +ASR_REVISION = "41f01f3fe87f28c78e2fbf8b568835947dd65ed9" + + +def normalize(text, language): + text = OpenCC("t2s").convert(unicodedata.normalize("NFKC", text)).lower() + text = "".join(c if c.isalnum() or c.isspace() else " " for c in text) + return " ".join(text.split()) if language == "en" else re.sub(r"\s+", "", text) + + +def error_counts(reference, hypothesis, language): + reference, hypothesis = normalize(reference, language), normalize(hypothesis, language) + result = jiwer.process_words(reference, hypothesis) if language == "en" else jiwer.process_characters(reference, hypothesis) + return {"errors": result.substitutions + result.deletions + result.insertions, + "reference_units": result.hits + result.substitutions + result.deletions, + "substitutions": result.substitutions, "deletions": result.deletions, "insertions": result.insertions, + "metric": "WER" if language == "en" else "CER"} + + +def evaluate(args): + torch.set_num_threads(4) + torch.backends.cuda.matmul.allow_tf32 = False + asset_lock = json.loads((ROOT / "training-assets.lock.json").read_text()) + for entry in asset_lock["files"]: + if entry["part"] == "asr" and sha256(args.asr / Path(entry["path"]).name) != entry["sha256"]: + raise ValueError("Evaluation ASR checkpoint/configuration differs from pinned assets") + if args.output.exists(): + raise ValueError("Refusing to overwrite evaluation evidence") + args.output.mkdir(parents=True) + records, contract = load_data(args.data) + cases = [{**r, "suite": args.split} for r in records if r["split"] == args.split] + if args.controls: + cases += [{**r, "suite": "controls"} for r in json.loads((ROOT / "evaluation-prompts.json").read_text())] + verify_assets(args.assets) + candidate, candidate_voices, bundle = load_bundle(args.bundle, args.device) + baseline = CompatibleGenerator(json.loads((args.assets / "config.json").read_text())) + baseline.load_checkpoint(torch.load(args.assets / "kokoro-v1_1-zh.pth", weights_only=True, map_location="cpu")) + baseline.to(args.device).eval() + baseline_voices = torch.load(args.assets / "voices/zf_001.pt", weights_only=True, map_location=args.device) + frontend = Frontend(candidate.config["vocab"]) + entries = [] + audio_dir = args.output / "audio" + audio_dir.mkdir() + for case in cases: + for name, model, voices in [("baseline", baseline, baseline_voices), ("candidate", candidate, candidate_voices)]: + audio, metadata = synthesize(model, voices, frontend, case["text"], args.device) + path = audio_dir / f"{case['id']}-{name}.wav" + sf.write(path, audio, 24000, subtype="FLOAT") + entries.append({"id": case["id"], "suite": case["suite"], "language": case["language"], "variant": name, + "text": case["text"], "audio": str(path.relative_to(args.output)), "audio_sha256": sha256(path), + "signal": {k: metadata[k] for k in ("samples", "peak", "rms", "clipping_fraction")}, + "input_ids": metadata["input_ids"], "phonemes": metadata["phonemes"]}) + if "targets" in case: + audio = torch.load(args.data / case["targets"], weights_only=True)["audio"].numpy().reshape(-1) + path = audio_dir / f"{case['id']}-reference.wav" + sf.write(path, audio, 24000, subtype="FLOAT") + entries.append({"id": case["id"], "suite": case["suite"], "language": case["language"], "variant": "real_reference", + "text": case["text"], "audio": str(path.relative_to(args.output)), "audio_sha256": sha256(path)}) + del candidate, baseline + torch.cuda.empty_cache() + processor = WhisperProcessor.from_pretrained(args.asr, local_files_only=True) + asr = WhisperForConditionalGeneration.from_pretrained(args.asr, local_files_only=True, + dtype=torch.float16 if str(args.device).startswith("cuda") else torch.float32).to(args.device).eval() + for language in ("en", "zh", "mixed"): + subset = [e for e in entries if e["language"] == language] + for offset in range(0, len(subset), args.batch_size): + batch = subset[offset:offset + args.batch_size] + audios = [resample_poly(sf.read(args.output / e["audio"], dtype="float32")[0], 2, 3) for e in batch] + if any(len(audio) > 30 * 16000 for audio in audios): + raise ValueError("ASR short-utterance protocol exceeds 30 seconds; no truncation allowed") + inputs = processor(audios, sampling_rate=16000, return_tensors="pt", return_attention_mask=True) + with torch.no_grad(): + tokens = asr.generate(input_features=inputs.input_features.to(args.device, dtype=asr.dtype), + attention_mask=inputs.attention_mask.to(args.device), + language="english" if language == "en" else "chinese", task="transcribe", + do_sample=False, num_beams=1, max_new_tokens=256) + hypotheses = processor.batch_decode(tokens, skip_special_tokens=True) + for entry, hypothesis in zip(batch, hypotheses, strict=True): + entry["transcript"] = hypothesis + entry["error"] = error_counts(entry["text"], hypothesis, language) + print(json.dumps({k: entry[k] for k in ("id", "variant", "transcript", "error")}, ensure_ascii=False), flush=True) + aggregates = {} + for entry in entries: + key = f"{entry['suite']}/{entry['language']}/{entry['variant']}" + group = aggregates.setdefault(key, {"utterances": 0, "errors": 0, "reference_units": 0, "metric": entry["error"]["metric"]}) + group["utterances"] += 1 + for field in ("errors", "reference_units"): + group[field] += entry["error"][field] + for group in aggregates.values(): + group["rate"] = group["errors"] / group["reference_units"] + result = {"model_sha256": bundle["files"]["model.pth"]["sha256"], "step": bundle["step"], + "precision": {"generator_tensor_dtype": "float32", "asr_tensor_dtype": str(asr.dtype), + "matmul_allow_tf32": torch.backends.cuda.matmul.allow_tf32, + "cudnn_allow_tf32": torch.backends.cudnn.allow_tf32}, + "manifest_sha256": contract["manifest_sha256"], "protocol": "whisper-turbo-greedy-forced-language-v1", + "asr": {"repo": "openai/whisper-large-v3-turbo", "revision": ASR_REVISION, + "weights_sha256": sha256(args.asr / "model.safetensors")}, + "normalization": "NFKC, simplified Chinese, lowercase, strip punctuation; WER English, CER Mandarin/mixed", + "limitations": ["ASR proxy, not listening/tone/naturalness certification", "mixed forces Chinese transcription; English normalization is not numeral expansion"], + "aggregates": aggregates, "entries": entries} + write_json(args.output / "results.json", result) + print(json.dumps(aggregates, indent=2), flush=True) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--data", type=Path, required=True) + parser.add_argument("--bundle", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--assets", type=Path, default=Path(".artifacts/baseline")) + parser.add_argument("--asr", type=Path, default=Path(".artifacts/whisper-turbo")) + parser.add_argument("--device", default="cuda") + parser.add_argument("--split", choices=("dev", "test"), default="dev") + parser.add_argument("--controls", action="store_true") + parser.add_argument("--batch-size", type=int, default=4) + evaluate(parser.parse_args()) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/features.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/features.py new file mode 100644 index 0000000..d1d50c3 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/features.py @@ -0,0 +1,79 @@ +"""Explicit native-24k waveform and 300/600-sample feature grids.""" + +import numpy as np +import torch +from torch import nn +import librosa + +SAMPLE_RATE = 24000 +HOP = 300 +ALIGNMENT_HOP = 600 + + +class MelFeatures(nn.Module): + def __init__(self): + super().__init__() + # Match StyleTTS2's published MelSpectrogram defaults, including its + # 16k filter-bank parameter applied to native 24k audio. Do not silently + # change the pretrained JDC/N-target convention to another mel scale. + bank = librosa.filters.mel(sr=16000, n_fft=2048, n_mels=80, + fmin=0, fmax=8000, htk=True, norm=None) + self.register_buffer("bank", torch.tensor(bank)) + self.register_buffer("window", torch.hann_window(1200)) + + def forward(self, audio: torch.Tensor): + # Equivalent to STFT center=True/reflect, with deterministic slice/flip + # gradients instead of CUDA reflection_pad1d's atomic backward. + if audio.shape[-1] <= 1024: + raise ValueError("Mel analysis requires more than 1024 waveform samples") + padded = torch.cat((audio[..., 1:1025].flip(-1), audio, + audio[..., -1025:-1].flip(-1)), dim=-1) + spec = torch.stft(padded, n_fft=2048, hop_length=300, win_length=1200, + window=self.window, center=False, return_complex=True).abs().square() + mel = self.bank @ spec + return mel + + def normalized(self, audio: torch.Tensor): + return (torch.log(self(audio) + 1e-5) + 4) / 4 + + +def alignment_features(audio: np.ndarray) -> np.ndarray: + mel = librosa.feature.melspectrogram(y=audio, sr=24000, n_fft=2048, + hop_length=600, win_length=1200, n_mels=80) + mfcc = librosa.feature.mfcc(S=librosa.power_to_db(mel), n_mfcc=20)[1:] + mfcc = (mfcc - mfcc.mean(axis=1, keepdims=True)) / (mfcc.std(axis=1, keepdims=True) + 1e-5) + return mfcc[:, :len(audio) // ALIGNMENT_HOP] + + +def dtw_durations(teacher: np.ndarray, real: np.ndarray, durations: np.ndarray): + """Transfer teacher token boundaries through a real-recording DTW path. + + This is approximate acoustic alignment, not independent recognition. The + real waveform remains the sole reconstruction target; teacher audio is + transient alignment scaffolding, never inserted into the recording corpus. + """ + source = alignment_features(teacher) + target = alignment_features(real) + if target.shape[1] < len(durations): + raise ValueError("Fewer real alignment frames than text tokens") + from scipy.spatial.distance import cdist + cost = cdist(source.T, target.T, metric="cosine") + accumulated, reverse_path = librosa.sequence.dtw(C=cost, global_constraints=True, band_rad=0.25) + path = reverse_path[::-1] + if not np.isfinite(accumulated[-1, -1]): + raise ValueError("No finite monotone alignment path") + # Every source frame receives at least one target correspondence. + mapped = np.array([np.mean(path[path[:, 0] == i, 1]) for i in range(source.shape[1])]) + raw = np.interp(np.cumsum(durations)[:-1] - 0.5, np.arange(len(mapped)), mapped) + 0.5 + boundaries = np.zeros(len(durations) + 1, dtype=np.int64) + boundaries[-1] = target.shape[1] + for i, value in enumerate(raw, 1): + boundaries[i] = np.clip(round(value), boundaries[i - 1] + 1, + target.shape[1] - (len(durations) - i)) + result = np.diff(boundaries) + if result.min() < 1 or result.max() > 50 or result.sum() != target.shape[1]: + raise ValueError("Transferred durations violate model frame/duration bounds") + return result, {"method": "teacher-mfcc-dtw-v1", "mean_path_cost": float(cost[path[:, 0], path[:, 1]].mean()), + "path_steps": len(path), "teacher_frames": source.shape[1], + "real_frames": target.shape[1], "duration_ratio": target.shape[1] / source.shape[1], + "max_token_duration": int(result.max())} diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/frontend.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/frontend.py new file mode 100644 index 0000000..e6a65cf --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/frontend.py @@ -0,0 +1,43 @@ +"""Versioned English/Mandarin frontend for the PyTorch model, with no dropped IDs.""" + +import hashlib +import json +import re +import unicodedata + +from misaki import en, espeak, zh +from opencc import OpenCC + +VERSION = "misaki-0.9.4-bilingual-v1" +OVERRIDES = {"API": "ˌA pˌi ˈI", "GitHub": "ɡˈɪt hˌʌb"} + + +class Frontend: + def __init__(self, vocab: dict): + self.vocab = vocab + self.simplify = OpenCC("t2s") + self.english = en.G2P( + trf=False, british=False, fallback=espeak.EspeakFallback(british=False) + ) + self.english.lexicon.golds.update(OVERRIDES) + self.mandarin = zh.ZHG2P(version="1.1", en_callable=lambda s: self.english(s)[0]) + + def __call__(self, text: str) -> dict: + normalized = self.simplify.convert(unicodedata.normalize("NFKC", text)).strip() + if not normalized: + raise ValueError("Empty text") + has_zh = bool(re.search(r"[\u4e00-\u9fff]", normalized)) + has_en = bool(re.search(r"[A-Za-z]", normalized)) + language = "mixed" if has_zh and has_en else "zh" if has_zh else "en" + phones = (self.mandarin(normalized)[0] if has_zh else self.english(normalized)[0]).strip() + unknown = sorted(set(phones) - set(self.vocab)) + if unknown: + raise ValueError(f"Frontend emitted unsupported symbols: {unknown}") + ids = [0, *[self.vocab[p] for p in phones], 0] + if not 3 <= len(ids) <= 512: + raise ValueError(f"Input has {len(ids)} tokens; split text before inference") + return {"text": text, "normalized_text": normalized, "language": language, + "phonemes": phones, "input_ids": ids, "frontend_version": VERSION, + "frontend_policy_sha256": hashlib.sha256(json.dumps(OVERRIDES, sort_keys=True).encode()).hexdigest(), + "backend": "misaki.en + misaki.zh 1.1/pypinyin; espeak English fallback", + "unknown_symbols": []} diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/model.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/model.py new file mode 100644 index 0000000..6488ed0 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/model.py @@ -0,0 +1,190 @@ +"""Compatible generator assembly and explicit, fail-closed checkpoint loading. + +Assembly follows hexgrad/kokoro model.py (Apache-2.0); the pinned upstream +modules remain the implementation of the generator layers. The inference +orchestration below is separate from KModel.forward_with_tokens. +""" + +import math +import torch +from torch import nn +from kokoro.istftnet import AdaIN1d, Decoder +from kokoro.modules import CustomAlbert, ProsodyPredictor, TextEncoder +from transformers import AlbertConfig + + +class CheckpointError(ValueError): + def __init__(self, report): + super().__init__("Checkpoint mapping failed; inspect checkpoint-map.json") + self.report = report + + +def map_checkpoint(expected: dict, checkpoint: dict, identity_constants=None) -> tuple[dict, dict]: + """Only explicitly documented DDP/legacy weight_norm renames are permitted.""" + mapped, entries, errors = {}, [], [] + for group, state in checkpoint.items(): + if not isinstance(state, dict): + errors.append({"key": group, "reason": "expected module state dictionary"}) + continue + for source, tensor in state.items(): + key = source.removeprefix("module.") + if key.endswith(".weight_g"): + key = key[:-9] + ".parametrizations.weight.original0" + elif key.endswith(".weight_v"): + key = key[:-9] + ".parametrizations.weight.original1" + target = f"{group}.{key}" + entry = {"source": f"{group}.{source}", "target": target, "renamed": source != key} + if target in mapped: + errors.append({**entry, "reason": "mapping collision"}) + continue + if target not in expected: + errors.append({**entry, "reason": "unexpected key"}) + continue + if not isinstance(tensor, torch.Tensor): + errors.append({**entry, "reason": "not a tensor"}) + continue + entry.update(shape=list(tensor.shape), dtype=str(tensor.dtype), elements=tensor.numel()) + if tensor.shape != expected[target].shape or tensor.dtype != expected[target].dtype: + errors.append( + { + **entry, + "reason": "shape or dtype mismatch", + "expected_shape": list(expected[target].shape), + "expected_dtype": str(expected[target].dtype), + } + ) + continue + if not torch.isfinite(tensor).all(): + errors.append({**entry, "reason": "nonfinite tensor"}) + continue + mapped[target] = tensor + entries.append(entry) + initialized = [] + for key, constant in (identity_constants or {}).items(): + if key not in mapped: + if key not in expected or constant.shape != expected[key].shape: + raise ValueError("Invalid identity-constant allowlist") + mapped[key] = constant + initialized.append( + { + "target": key, + "shape": list(constant.shape), + "value": constant.flatten()[0].item(), + "reason": "upstream AdaIN1d export workaround; frozen identity affine", + } + ) + missing = sorted(set(expected) - set(mapped)) + report = { + "passed": not errors and not missing, + "matched_checkpoint_keys": len(entries), + "loaded_state_keys": len(mapped), + "renamed_keys": sum(e["renamed"] for e in entries), + "missing": missing, + "errors": errors, + "entries": entries, + "identity_constants": initialized, + } + if not report["passed"]: + raise CheckpointError(report) + return mapped, report + + +def validate_tokens(ids, n_token: int, context_length: int) -> None: + if not 3 <= len(ids) <= context_length: + raise ValueError("Expected 1..510 content tokens with BOS/EOS; no silent truncation") + if ids[0] != 0 or ids[-1] != 0: + raise ValueError("Expected zero BOS/EOS tokens") + if any(type(i) is not int or not 0 <= i < n_token for i in ids): + raise ValueError("Invalid token ID") + if 0 in ids[1:-1]: + raise ValueError("Unexpected special token inside content") + + +def select_style(voices: torch.Tensor, content_length: int) -> tuple[torch.Tensor, int]: + if voices.shape != (510, 1, 256) or not torch.isfinite(voices).all(): + raise ValueError("Expected a finite [510, 1, 256] upstream voice table") + if not 1 <= content_length <= 510: + raise ValueError("Voice length must be 1..510 content IDs (BOS/EOS excluded)") + row = content_length - 1 + return voices[row], row + + +class CompatibleGenerator(nn.Module): + """Pinned upstream layers with independently assembled inference orchestration.""" + + def __init__(self, config: dict): + super().__init__() + self.config = config + self.bert = CustomAlbert(AlbertConfig(vocab_size=config["n_token"], **config["plbert"])) + self.bert_encoder = nn.Linear(self.bert.config.hidden_size, config["hidden_dim"]) + self.predictor = ProsodyPredictor( + style_dim=config["style_dim"], + d_hid=config["hidden_dim"], + nlayers=config["n_layer"], + max_dur=config["max_dur"], + dropout=config["dropout"], + ) + self.text_encoder = TextEncoder( + channels=config["hidden_dim"], + kernel_size=config["text_encoder_kernel_size"], + depth=config["n_layer"], + n_symbols=config["n_token"], + ) + self.decoder = Decoder( + dim_in=config["hidden_dim"], + style_dim=config["style_dim"], + dim_out=config["n_mels"], + disable_complex=False, + **config["istftnet"], + ) + # Pinned upstream AdaIN1d adds affine=True solely for ONNX shape export. + # The release contains no such affine weights. Preserve exact identity + # behavior and keep these non-learned constants out of future optimizers. + self.identity_constants = {} + for name, module in self.named_modules(): + if isinstance(module, AdaIN1d): + self.identity_constants[f"{name}.norm.weight"] = torch.ones_like(module.norm.weight) + self.identity_constants[f"{name}.norm.bias"] = torch.zeros_like(module.norm.bias) + module.norm.requires_grad_(False) + + def load_checkpoint(self, checkpoint: dict) -> dict: + mapped, report = map_checkpoint(self.state_dict(), checkpoint, self.identity_constants) + self.load_state_dict(mapped, strict=True) + report["parameter_count"] = sum(p.numel() for p in self.parameters()) + report["parameter_groups"] = { + name: sum(p.numel() for p in child.parameters()) + for name, child in self.named_children() + } + return report + + @torch.no_grad() + def forward(self, ids: torch.Tensor, style: torch.Tensor, speed: float = 1.0): + if ids.ndim != 2 or ids.shape[0] != 1 or ids.dtype != torch.long: + raise ValueError("Parity inference accepts one int64 sequence at a time") + validate_tokens( + ids[0].tolist(), self.config["n_token"], self.bert.config.max_position_embeddings + ) + if style.shape != (1, 256) or not torch.isfinite(style).all(): + raise ValueError("Expected finite [1, 256] style") + if not math.isfinite(speed) or not 0.25 <= speed <= 4: + raise ValueError("Speed must be finite and within 0.25..4") + lengths = torch.tensor([ids.shape[1]], device=ids.device, dtype=torch.long) + mask = torch.zeros_like(ids, dtype=torch.bool) + encoded = self.bert(ids, attention_mask=(~mask).int()) + projected = self.bert_encoder(encoded).transpose(1, 2) + prosody = self.predictor.text_encoder(projected, style[:, 128:], lengths, mask) + recurrent, _ = self.predictor.lstm(prosody) + logits = self.predictor.duration_proj(recurrent) + durations = (logits.sigmoid().sum(-1) / speed).round().clamp(min=1).long()[0] + if int(durations.sum()) > 4000: + raise ValueError("Generated duration exceeds the 4000-frame inference limit; split text or increase speed") + # An explicit one-hot alignment is inference-only. Training needs real targets. + token_at_frame = torch.repeat_interleave( + torch.arange(ids.shape[1], device=ids.device), durations + ) + alignment = torch.nn.functional.one_hot(token_at_frame, ids.shape[1]).T[None].float() + features = prosody.transpose(1, 2) @ alignment + f0, noise = self.predictor.F0Ntrain(features, style[:, 128:]) + text = self.text_encoder(ids, lengths, mask) + audio = self.decoder(text @ alignment, f0, noise, style[:, :128]).squeeze() + return audio, durations diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/parity.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/parity.py new file mode 100644 index 0000000..3f258d3 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/parity.py @@ -0,0 +1,260 @@ +"""Bounded, same-backend parity against an independently instantiated oracle.""" + +import hashlib +import json +from pathlib import Path +import time + +import torch +from kokoro import KModel + +from .artifacts import ROOT, environment, sha256, verify_assets, write_json +from .model import CheckpointError, CompatibleGenerator, select_style, validate_tokens + +TRACE_MODULES = ( + "bert", + "bert_encoder", + "predictor.text_encoder", + "predictor.lstm", + "predictor.duration_proj", + "predictor.F0_proj", + "predictor.N_proj", + "text_encoder", + "decoder.generator.m_source", + "decoder.generator.conv_post", + "decoder", +) + + +def tensor_hash(tensor: torch.Tensor) -> str: + return hashlib.sha256(tensor.detach().cpu().contiguous().numpy().tobytes()).hexdigest() + + +def compare_tensors(reference: torch.Tensor, candidate: torch.Tensor) -> dict: + result = { + "reference_shape": list(reference.shape), + "candidate_shape": list(candidate.shape), + "reference_dtype": str(reference.dtype), + "candidate_dtype": str(candidate.dtype), + } + if reference.shape != candidate.shape or reference.dtype != candidate.dtype: + return {**result, "passed": False, "reason": "shape/dtype mismatch"} + if not torch.isfinite(reference).all() or not torch.isfinite(candidate).all(): + return {**result, "passed": False, "reason": "nonfinite values"} + diff = (reference.double() - candidate.double()).abs() + return { + **result, + "passed": torch.equal(reference, candidate), + "max_absolute_error": diff.max().item() if diff.numel() else 0, + "mean_absolute_error": diff.mean().item() if diff.numel() else 0, + } + + +def compare_traces(reference: dict, candidate: dict) -> dict: + if reference.keys() != candidate.keys(): + return {"passed": False, "reason": "different trace keys"} + checks = {key: compare_tensors(value, candidate[key]) for key, value in reference.items()} + return {"passed": all(c["passed"] for c in checks.values()), "checks": checks} + + +def run_trace(model, ids, style, seed: int, max_frames: int): + trace, handles = {}, [] + + def capture(name, value): + if isinstance(value, torch.Tensor): + trace[name] = value.detach().cpu().clone() + elif isinstance(value, (tuple, list)): + for i, child in enumerate(value): + capture(f"{name}.{i}", child) + + def hook(name): + def save(module, args, output): + if name == "predictor.duration_proj": + frames = output.sigmoid().sum(-1).round().clamp(min=1).sum().item() + if frames > max_frames: + raise ValueError(f"Predicted alignment exceeds declared {max_frames}-frame cap") + capture(name, output) + + return save + + for name in TRACE_MODULES: + handles.append(model.get_submodule(name).register_forward_hook(hook(name))) + handles.append( + model.decoder.register_forward_pre_hook( + lambda module, args: capture("decoder.inputs", args) + ) + ) + devices = [ids.device.index or 0] if ids.is_cuda else [] + try: + with torch.random.fork_rng(devices=devices), torch.no_grad(): + torch.manual_seed(seed) + if ids.is_cuda: + torch.cuda.manual_seed_all(seed) + if isinstance(model, KModel): + audio, durations = model.forward_with_tokens(ids, style, speed=1) + else: + audio, durations = model(ids, style, speed=1) + capture("audio", audio) + capture("integer_durations", durations) + capture("rng.cpu.after", torch.get_rng_state()) + if ids.is_cuda: + capture("rng.cuda.after", torch.cuda.get_rng_state(ids.device)) + finally: + for handle in handles: + handle.remove() + return trace + + +def cases_from_manifest(manifest: Path, config: dict) -> list[dict]: + data = json.loads(manifest.read_text()) + if len(data["clips"]) != 12: + raise ValueError("Expected the fixed 12-clip development manifest") + cases = [] + for clip in data["clips"]: + ids = clip["input_ids"] + validate_tokens(ids, config["n_token"], config["plbert"]["max_position_embeddings"]) + if ( + clip["unknown_phonemes"] + or [0, *[config["vocab"][p] for p in clip["phonemes"]], 0] != ids + ): + raise ValueError(f"Saved phoneme/token mismatch in {clip['id']}") + cases.append( + { + "id": clip["id"], + "language": clip["language"], + "input_ids": ids, + "origin": "historical development tokens, including known frontend defects", + } + ) + # Token-interface probes, not training examples or naturalness evidence. + cases.extend( + [ + { + "id": "boundary-one-phone", + "language": "probe", + "input_ids": [0, config["vocab"]["a"], 0], + }, + { + "id": "boundary-punctuation", + "language": "probe", + "input_ids": [0, config["vocab"]["."], 0], + }, + { + "id": "boundary-510-phones", + "language": "probe", + "input_ids": [0] + [config["vocab"]["a"]] * 510 + [0], + }, + ] + ) + return cases + + +def parity(assets: Path, output: Path, device: str, seed: int, max_frames: int) -> dict: + if output.exists(): + raise ValueError("Use a new run directory; existing evidence must remain immutable") + output.mkdir(parents=True) + started = time.monotonic() + lock = verify_assets(assets) + write_json(output / "environment.json", environment()) + torch.set_num_threads(4) + torch.use_deterministic_algorithms(True) + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + torch.backends.cuda.matmul.allow_tf32 = False + torch.backends.cudnn.allow_tf32 = False + config = json.loads((assets / "config.json").read_text()) + candidate = CompatibleGenerator(config) + checkpoint = torch.load(assets / "kokoro-v1_1-zh.pth", map_location="cpu", weights_only=True) + try: + mapping = candidate.load_checkpoint(checkpoint) + except CheckpointError as exc: + write_json(output / "checkpoint-map.json", exc.report) + raise + write_json(output / "checkpoint-map.json", mapping) + oracle = KModel( + repo_id=lock["repo_id"], config=config, model=str(assets / "kokoro-v1_1-zh.pth") + ) + # Upstream's permissive fallback is not evidence: independently check every loaded tensor. + state_check = compare_traces(oracle.state_dict(), candidate.state_dict()) + write_json(output / "loaded-state-parity.json", state_check) + if not state_check["passed"]: + raise ValueError("Independently loaded upstream and candidate states differ") + del checkpoint + candidate.to(device).eval().requires_grad_(False) + oracle.to(device).eval().requires_grad_(False) + voices = torch.load(assets / "voices/zf_001.pt", map_location="cpu", weights_only=True) + manifest = ROOT.parent / "coreml/bilingual-demo/manifest.json" + cases = cases_from_manifest(manifest, config) + write_json( + output / "inputs.json", + {"assets": lock, "manifest_sha256": sha256(manifest), "cases": cases}, + ) + write_json( + output / "resolved-config.json", + { + "device": device, + "dtype": "float32", + "seed": seed, + "max_alignment_frames": max_frames, + "speed": 1, + "style": "zf_001: row = content token count - 1, BOS/EOS excluded", + "tolerance": {"atol": 0, "rtol": 0}, + "deterministic_algorithms": True, + "cublas_workspace_config": ":4096:8", + "tf32": False, + "optimizer_steps": 0, + "stochastic_control": "restore full RNG via fork_rng; compare source outputs and final RNG states", + }, + ) + results = [] + for i, case in enumerate(cases): + print(f"[{i + 1}/{len(cases)}] {case['id']}", flush=True) + try: + style, row = select_style(voices, len(case["input_ids"]) - 2) + ids = torch.tensor([case["input_ids"]], dtype=torch.long, device=device) + style = style.to(device) + reference = run_trace(oracle, ids, style, seed, max_frames) + repeated = run_trace(oracle, ids, style, seed, max_frames) + repeat_check = compare_traces(reference, repeated) + del repeated + actual = run_trace(candidate, ids, style, seed, max_frames) + check = compare_traces(reference, actual) + result = { + "id": case["id"], + "passed": repeat_check["passed"] and check["passed"], + "style_row": row, + "style_sha256": tensor_hash(style), + "oracle_repeat": repeat_check, + "candidate": check, + "waveform_sha256": tensor_hash(actual["audio"]), + "audio_samples": actual["audio"].numel(), + "alignment_frames": int(actual["integer_durations"].sum()), + "sample_rate": 24000, + "origin": "real upstream model inference; not training audio", + } + del reference, actual + except (ValueError, RuntimeError) as exc: + result = {"id": case["id"], "passed": False, "error": str(exc)} + write_json(output / "cases" / f"{case['id']}.json", result) + results.append({k: v for k, v in result.items() if k not in {"oracle_repeat", "candidate"}}) + summary = { + "schema_version": 1, + "passed": all(r["passed"] for r in results), + "scope": "untouched FP32 same-backend inference parity; no training or quality claim", + "cases": results, + "elapsed_seconds": time.monotonic() - started, + "peak_cuda_allocated_bytes": torch.cuda.max_memory_allocated() + if device.startswith("cuda") + else None, + "remaining_gates": [ + "frontend parity", + "real-data/target audit", + "gradient coverage", + "micro-overfit/resume", + "pilot", + "quality evaluation", + "export/device acceptance", + ], + } + write_json(output / "summary.json", summary) + return summary diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/prepare.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/prepare.py new file mode 100644 index 0000000..abc833b --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/prepare.py @@ -0,0 +1,152 @@ +"""Prepare actual MF5 recordings and explicitly version approximate targets.""" + +from collections import defaultdict +import importlib.util +import json +from pathlib import Path +import re + +import numpy as np +from scipy.signal import resample_poly +import soundfile as sf +import torch + +from .artifacts import sha256, verify_assets, write_json +from .features import MelFeatures, dtw_durations +from .frontend import Frontend +from .model import CompatibleGenerator, select_style + + +def prompt_map(path: Path): + result = {} + for line in path.read_text().splitlines(): + match = re.match(r"^\s*(\d+)\s+(.+)", line) + if match: + result[int(match[1])] = match[2].strip() + return result + + +def load_pitch(targets: Path, device): + lock = json.loads((targets / "lock.json").read_text()) + for source in ["Utils/JDC/model.py", "Utils/JDC/bst.t7"]: + if sha256(targets / Path(source).name) != lock["files"][source]["sha256"]: + raise ValueError("Pitch extractor provenance mismatch") + spec = importlib.util.spec_from_file_location("pinned_jdc", targets / "model.py") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + model = module.JDCNet(num_class=1, seq_len=192) + weights = torch.load(targets / "bst.t7", map_location="cpu", weights_only=True) + model.load_state_dict(weights["net"], strict=True) + return model.to(device).eval().requires_grad_(False) + + +def prepare(source: Path, assets: Path, targets: Path, output: Path, device="cuda", limit=None): + if output.exists(): + raise ValueError("Refusing to overwrite prepared evidence") + output.mkdir(parents=True) + torch.set_num_threads(4) + torch.manual_seed(1729) + lock = verify_assets(assets) + config = json.loads((assets / "config.json").read_text()) + frontend = Frontend(config["vocab"]) + model = CompatibleGenerator(config) + model.load_checkpoint(torch.load(assets / "kokoro-v1_1-zh.pth", weights_only=True, map_location="cpu")) + model.to(device).eval().requires_grad_(False) + voices = torch.load(assets / "voices/zf_001.pt", weights_only=True) + pitch = load_pitch(targets, device) + mel = MelFeatures().to(device) + texts = {lang: prompt_map(next(source.rglob(name))) for lang, name in + [("ENG", "english_prompts.txt"), ("MAN", "mandarin_prompts.txt")]} + official = {lang: {int(m) for m in re.findall(r"^\s*(\d+)", next(source.rglob(name)).read_text(), re.M)} + for lang, name in [("ENG", "EnglishTestingData"), ("MAN", "MandarinTestingData")]} + parallel_test = {i for values in official.values() for i in values if i <= 25} + records, exclusions = [], [] + files = sorted(source.rglob("MF5_*_0.wav")) + if limit: + files = files[:limit] + for i, path in enumerate(files): + print(f"[{i + 1}/{len(files)}] {path.stem}", flush=True) + _, language, number, _ = path.stem.split("_") + number = int(number) + text = texts[language][number] + try: + front = frontend(text) + audio, rate = sf.read(path, dtype="float32") + if audio.ndim != 1 or rate != 22050 or not np.isfinite(audio).all(): + raise ValueError("Unexpected or nonfinite source audio") + clip_fraction = float((np.abs(audio) >= 0.9999).mean()) + if clip_fraction > 0.001 or not 1.0 <= len(audio) / rate <= 14.0: + raise ValueError("Pilot signal/length exclusion") + # Original retained. Rational resampler is pinned in scipy; pad <25 ms only. + real = resample_poly(audio, up=160, down=147).astype(np.float32) + pad = (-len(real)) % 600 + real = np.pad(real, (0, pad)) + waveform = torch.tensor(real, device=device)[None] + ids = torch.tensor([front["input_ids"]], device=device) + style, row = select_style(voices, ids.shape[1] - 2) + with torch.no_grad(): + generated, teacher_durations = model(ids, style.to(device)) + durations, alignment = dtw_durations(generated.cpu().numpy(), real, + teacher_durations.cpu().numpy()) + normalized_mel = mel.normalized(waveform)[..., :len(real) // 300] + f0, _, _ = pitch(normalized_mel[:, None]) + if f0.shape[-1] != normalized_mel.shape[-1]: + raise ValueError("Pitch target frame-grid mismatch") + energy = torch.log(torch.exp(normalized_mel * 4 - 4).norm(dim=1).clamp(min=1e-8)) + if not torch.isfinite(f0).all() or not torch.isfinite(energy).all(): + raise ValueError("Nonfinite acoustic target") + # Official test utterances and both sides of parallel translations remain held out. + passage = f"parallel-{number}" if number <= 25 else f"{language}-{number}" + is_test = number in official[language] or number in parallel_test + import hashlib + split = "test" if is_test else "dev" if int(hashlib.sha256(passage.encode()).hexdigest()[:8], 16) % 10 == 0 else "train" + item = {"id": path.stem, "language": "en" if language == "ENG" else "zh", "split": split, + "speaker_id": "EMIME-MF5", "session_id": f"MF5-{language}", "passage_id": passage, + "source_sha256": sha256(path), "source_rate": rate, "source_samples": len(audio), + "prepared_rate": 24000, "prepared_samples": len(real), "pad_right_samples": pad, + "clipping_fraction": clip_fraction, **front, "alignment": alignment, + "style_row": row, "targets": f"targets/{path.stem}.pt", + "origin": "real_recording", "rights_scope": "local_research_adaptation_only"} + target_path = output / item["targets"] + target_path.parent.mkdir(exist_ok=True) + torch.save({"audio": waveform.cpu(), "ids": ids.cpu(), + "durations": torch.tensor(durations)[None], "f0": f0.cpu(), "energy": energy.cpu()}, target_path) + item["targets_sha256"] = sha256(target_path) + records.append(item) + except (ValueError, KeyError, RuntimeError) as exc: + exclusions.append({"id": path.stem, "reason": str(exc)}) + (output / "manifest.jsonl").write_text("".join(json.dumps(r, ensure_ascii=False) + "\n" for r in records)) + counts = defaultdict(lambda: {"utterances": 0, "seconds": 0}) + for record in records: + group = counts[f"{record['split']}/{record['language']}"] + group["utterances"] += 1 + group["seconds"] += record["prepared_samples"] / 24000 + write_json(output / "data-contract.json", { + "schema_version": 1, "speaker": "EMIME-MF5", "microphone": 0, "baseline": lock, + "manifest_sha256": sha256(output / "manifest.jsonl"), "counts": dict(counts), + "exclusions": exclusions, "pitch_extractor": json.loads((targets / "lock.json").read_text()), + "split_policy": "official test IDs + translated passage grouping; deterministic 10% dev from remaining passages", + "session_disjoint": False, + "limitations": ["only one source session per language; session-disjoint bilingual split unavailable", + "no real code-switching recordings in EMIME", "approximate teacher-DTW alignment requires calibration", + "production voice rights/consent not certified; weights remain local research artifacts"], + "preprocessing": {"resampler": "scipy.resample_poly 160/147", "sample_rate": 24000, + "alignment_hop": 600, "pitch_energy_hop": 300, "right_padding": "to next multiple of 600"}, + }) + return dict(counts) + + +def main(): + import argparse + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, default=Path(".artifacts/emime/mf5-source")) + parser.add_argument("--assets", type=Path, default=Path(".artifacts/baseline")) + parser.add_argument("--targets", type=Path, default=Path(".artifacts/targets")) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--device", default="cuda") + args = parser.parse_args() + print(json.dumps(prepare(args.source, args.assets, args.targets, args.output, args.device))) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/supervised.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/supervised.py new file mode 100644 index 0000000..261d61c --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/supervised.py @@ -0,0 +1,104 @@ +"""Supervised acoustic path: real alignment targets bypass integer-duration gradients.""" + +import torch +from torch import nn +import torch.nn.functional as F + +from .features import MelFeatures +from .model import CompatibleGenerator, select_style, validate_tokens + + +class DeterministicLeftReflection(nn.Module): + """Exact ReflectionPad1d((1, 0)) with a deterministic slice gradient.""" + + def forward(self, value): + if value.shape[-1] < 2: + raise ValueError("Reflection requires at least two frames") + return torch.cat((value[..., 1:2], value), dim=-1) + + +class AdaptationModel(nn.Module): + def __init__(self, generator: CompatibleGenerator, voices: torch.Tensor, train_style=False): + super().__init__() + self.generator = generator + if generator.decoder.generator.reflection_pad.padding != (1, 0): + raise ValueError("Unexpected upstream decoder reflection padding") + generator.decoder.generator.reflection_pad = DeterministicLeftReflection() + self.register_buffer("voices", voices, persistent=False) + self.style_offset = nn.Parameter(torch.zeros(1, 256), requires_grad=train_style) + generator.requires_grad_(False) + for name in ["bert_encoder", "predictor", "text_encoder"]: + getattr(generator, name).requires_grad_(True) + for name, parameter in generator.named_parameters(): + if name in generator.identity_constants: + parameter.requires_grad_(False) + + def train(self, mode=True): + super().train(mode) + self.generator.bert.eval() + self.generator.decoder.eval() + return self + + def forward(self, batch: dict, crop_start=0, crop_frames=None): + ids, durations = batch["ids"], batch["durations"] + if ids.shape[0] != 1 or ids.shape != durations.shape: + raise ValueError("Initial trainer uses batch=1 with explicit accumulation") + validate_tokens(ids[0].tolist(), self.generator.config["n_token"], 512) + if durations.dtype != torch.long or durations.min() < 1 or durations.max() > 50: + raise ValueError("Invalid supervised durations") + total_frames = int(durations.sum()) + if batch["audio"].shape != (1, total_frames * 600): + raise ValueError("Waveform and duration sum do not share the 600-sample grid") + if batch["f0"].numel() != total_frames * 2 or batch["energy"].numel() != total_frames * 2: + raise ValueError("Pitch/energy targets do not share the 300-sample grid") + crop_frames = total_frames if crop_frames is None else min(crop_frames, total_frames) + if crop_start < 0 or crop_start + crop_frames > total_frames: + raise ValueError("Invalid crop boundary") + lengths = torch.tensor([ids.shape[1]], device=ids.device) + mask = torch.zeros_like(ids, dtype=torch.bool) + style, _ = select_style(self.voices, ids.shape[1] - 2) + style = style + self.style_offset + with torch.no_grad(): + representation = self.generator.bert(ids, attention_mask=(~mask).int()) + projected = self.generator.bert_encoder(representation).transpose(1, 2) + prosody = self.generator.predictor.text_encoder(projected, style[:, 128:], lengths, mask) + recurrent, _ = self.generator.predictor.lstm(prosody) + duration_logits = self.generator.predictor.duration_proj(recurrent) + frame_tokens = torch.repeat_interleave(torch.arange(ids.shape[1], device=ids.device), durations[0]) + alignment = F.one_hot(frame_tokens, ids.shape[1]).T[None].float() + aligned_prosody = prosody.transpose(1, 2) @ alignment + f0, energy = self.generator.predictor.F0Ntrain(aligned_prosody, style[:, 128:]) + text = self.generator.text_encoder(ids, lengths, mask) @ alignment + sl = slice(crop_start, crop_start + crop_frames) + sl2 = slice(crop_start * 2, (crop_start + crop_frames) * 2) + audio = self.generator.decoder(text[..., sl], f0[..., sl2], energy[..., sl2], style[:, :128]).reshape(1, -1) + target = batch["audio"][..., crop_start * 600:(crop_start + crop_frames) * 600] + if audio.shape != target.shape: + raise ValueError("Decoder crop length differs from real audio target") + return {"audio": audio, "target_audio": target, "duration_logits": duration_logits, + "f0": f0[..., sl2], "target_f0": batch["f0"].reshape(1, -1)[..., sl2], + "energy": energy[..., sl2], "target_energy": batch["energy"].reshape(1, -1)[..., sl2], + "duration_targets": durations, "style_offset": self.style_offset} + + +class SupervisedLoss(nn.Module): + def __init__(self): + super().__init__() + self.mel = MelFeatures() + + def forward(self, output): + logits, target_duration = output["duration_logits"], output["duration_targets"] + survival = torch.arange(logits.shape[-1], device=logits.device)[None, None] < target_duration[..., None] + duration_ce = F.binary_cross_entropy_with_logits(logits, survival.float()) + duration = F.smooth_l1_loss(torch.log1p(logits.sigmoid().sum(-1)), torch.log1p(target_duration.float())) + predicted_mel = self.mel.normalized(output["audio"]) + target_mel = self.mel.normalized(output["target_audio"]) + mel = F.l1_loss(predicted_mel, target_mel) + voiced = output["target_f0"] >= 50 + f0 = F.smooth_l1_loss(output["f0"][voiced] / 100, output["target_f0"][voiced] / 100) if voiced.any() else logits.sum() * 0 + unvoiced = F.smooth_l1_loss(output["f0"][~voiced] / 100, output["target_f0"][~voiced] / 100) if (~voiced).any() else logits.sum() * 0 + energy = F.smooth_l1_loss(output["energy"], output["target_energy"]) + style = output["style_offset"].square().mean() + total = 5 * mel + duration_ce + duration + f0 + 0.1 * unvoiced + energy + 0.01 * style + return {"total": total, "mel": mel, "duration_ce": duration_ce, "duration": duration, + "f0": f0, "unvoiced": unvoiced, "energy": energy, "style": style} diff --git a/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/train.py b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/train.py new file mode 100644 index 0000000..db480aa --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/src/kokoro_training/train.py @@ -0,0 +1,225 @@ +"""Single-speaker real-recording adaptation with resumable, bounded local runs.""" + +import argparse +import hashlib +import json +import os +from pathlib import Path +import random +import time + +os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8") +import torch + +from .artifacts import sha256, verify_assets, write_json +from .model import CompatibleGenerator +from .supervised import AdaptationModel, SupervisedLoss + + +def state_hash(items): + digest = hashlib.sha256() + for key, value in sorted(items): + digest.update(key.encode()) + digest.update(value.detach().cpu().contiguous().numpy().tobytes()) + return digest.hexdigest() + + +def atomic_save(value, path): + temporary = path.with_suffix(".tmp") + torch.save(value, temporary) + os.replace(temporary, path) + + +def load_data(directory): + contract = json.loads((directory / "data-contract.json").read_text()) + if sha256(directory / "manifest.jsonl") != contract["manifest_sha256"]: + raise ValueError("Prepared manifest checksum mismatch") + records = [json.loads(line) for line in (directory / "manifest.jsonl").read_text().splitlines()] + passages, sources, prepared, texts, seen_ids = {}, {}, {}, {}, set() + for row in records: + if row["id"] in seen_ids: + raise ValueError("Duplicate recording ID") + seen_ids.add(row["id"]) + if row["origin"] != "real_recording" or row["speaker_id"] != contract["speaker"]: + raise ValueError("Expected the declared single real speaker") + identities = [(passages, row["passage_id"]), (sources, row["source_sha256"]), + (prepared, row["targets_sha256"])] + if row.get("normalized_text"): + identities.append((texts, row["normalized_text"].strip().casefold())) + for index, key in identities: + if key in index and index[key] != row["split"]: + raise ValueError("Train/evaluation leakage") + index[key] = row["split"] + target = directory / row["targets"] + if not target.resolve().is_relative_to(directory.resolve()) or sha256(target) != row["targets_sha256"]: + raise ValueError("Prepared target checksum/path mismatch") + return records, contract + + +def batch_for(row, directory, device): + return {key: value.to(device) for key, value in torch.load( + directory / row["targets"], map_location="cpu", weights_only=True).items()} + + +@torch.no_grad() +def validate(model, criterion, rows, directory, device, crop_frames): + model.eval() + totals, by_language = {}, {} + # Preserve the training RNG; every evaluation uses the same excitation/crops. + with torch.random.fork_rng(devices=[torch.device(device).index or 0] if str(device).startswith("cuda") else []): + torch.manual_seed(123456) + for row in rows: + batch = batch_for(row, directory, device) + frames = int(batch["durations"].sum()) + output = model(batch, crop_start=max(0, (frames - crop_frames) // 2), crop_frames=crop_frames) + losses = criterion(output) + for key, value in losses.items(): + totals[key] = totals.get(key, 0.0) + float(value) / len(rows) + by_language.setdefault(row["language"], []).append(float(losses["total"])) + model.train() + return {**totals, "language_total": {k: sum(v) / len(v) for k, v in by_language.items()}} + + +def train(args): + torch.set_num_threads(4) + torch.backends.cuda.matmul.allow_tf32 = False + torch.backends.cudnn.allow_tf32 = False + torch.backends.cudnn.benchmark = False + torch.backends.cudnn.deterministic = True + torch.use_deterministic_algorithms(True) + torch.manual_seed(args.seed) + random.seed(args.seed) + verify_assets(args.assets) + rows, contract = load_data(args.data) + train_rows = [r for r in rows if r["split"] == "train"] + dev_rows = [r for r in rows if r["split"] == "dev"] + if args.micro: + train_rows = [r for lang in ("en", "zh") for r in sorted( + (r for r in train_rows if r["language"] == lang), + key=lambda r: (r["alignment"]["mean_path_cost"], r["id"]))[:args.micro // 2]] + if not train_rows or not dev_rows: + raise ValueError("Training and development data both required") + run_config = {k: str(v) if isinstance(v, Path) else v for k, v in vars(args).items()} + run_config.update(manifest_sha256=contract["manifest_sha256"], + train_ids=[r["id"] for r in train_rows], dev_ids=[r["id"] for r in dev_rows]) + if args.output.exists() and not args.resume: + raise ValueError("Refusing to overwrite a run; use --resume explicitly") + args.output.mkdir(parents=True, exist_ok=True) + config = json.loads((args.assets / "config.json").read_text()) + generator = CompatibleGenerator(config) + generator.load_checkpoint(torch.load(args.assets / "kokoro-v1_1-zh.pth", map_location="cpu", weights_only=True)) + voices = torch.load(args.assets / "voices/zf_001.pt", map_location="cpu", weights_only=True) + model = AdaptationModel(generator, voices, train_style=args.train_style).to(args.device).train() + criterion = SupervisedLoss().to(args.device) + learned = [p for name, p in model.named_parameters() if p.requires_grad and name != "style_offset"] + groups = [{"params": learned, "lr": args.lr}] + if args.train_style: + groups.append({"params": [model.style_offset], "lr": args.style_lr}) + optimizer = torch.optim.AdamW(groups, betas=(0.9, 0.99), weight_decay=0.01) + frozen_before = state_hash((n, p) for n, p in model.named_parameters() if not p.requires_grad) + trainable_before = state_hash((n, p) for n, p in model.named_parameters() if p.requires_grad) + step, best = 0, float("inf") + sample_rng = random.Random(args.seed) + if args.resume: + saved = torch.load(args.output / "last.pt", map_location="cpu", weights_only=True) + for key in ("manifest_sha256", "train_ids", "dev_ids", "lr", "style_lr", "train_style", "seed", "accumulation", "crop_frames", "device"): + if saved["config"][key] != run_config[key]: + raise ValueError(f"Resume configuration differs: {key}") + model.load_state_dict(saved["model"], strict=True) + optimizer.load_state_dict(saved["optimizer"]) + torch.set_rng_state(saved["cpu_rng"]) + if str(args.device).startswith("cuda"): + torch.cuda.set_rng_state_all(saved["cuda_rng"]) + sample_rng.setstate(saved["sample_rng"]) + step, best = saved["step"], saved["best"] + else: + write_json(args.output / "run-config.json", run_config) + initial = validate(model, criterion, dev_rows, args.data, args.device, args.crop_frames) + micro_initial = validate(model, criterion, train_rows, args.data, args.device, args.crop_frames) if args.micro else None + write_json(args.output / "initial.json", {"development": initial, "micro_train": micro_initial, + "trainable_parameters": sum(p.numel() for p in model.parameters() if p.requires_grad), + "frozen_hash": frozen_before, "trainable_hash": trainable_before}) + print(json.dumps({"step": 0, "development": initial, "micro_train": micro_initial}), flush=True) + start = time.monotonic() + by_language = {lang: [r for r in train_rows if r["language"] == lang] for lang in ("en", "zh")} + if not all(by_language.values()): + raise ValueError("Both languages required") + parameters = [p for p in model.parameters() if p.requires_grad] + while step < args.steps: + optimizer.zero_grad(set_to_none=True) + total = {} + used = [] + for _ in range(args.accumulation): + row = sample_rng.choice(by_language[sample_rng.choice(("en", "zh"))]) + batch = batch_for(row, args.data, args.device) + crop_start = sample_rng.randint(0, max(0, int(batch["durations"].sum()) - args.crop_frames)) + losses = criterion(model(batch, crop_start=crop_start, crop_frames=args.crop_frames)) + if not all(torch.isfinite(v) for v in losses.values()): + raise FloatingPointError(f"Nonfinite loss at step {step}, {row['id']}") + (losses["total"] / args.accumulation).backward() + for key, value in losses.items(): + total[key] = total.get(key, 0.0) + float(value.detach()) / args.accumulation + used.append(row["id"]) + grad_norm = torch.nn.utils.clip_grad_norm_(parameters, 5.0, error_if_nonfinite=True) + optimizer.step() + step += 1 + event = {"step": step, "loss": total, "gradient_norm_before_clip": float(grad_norm), + "recordings": used, "elapsed_seconds": time.monotonic() - start} + if step % args.validate_every == 0 or step == args.steps: + event["development"] = validate(model, criterion, dev_rows, args.data, args.device, args.crop_frames) + improved = event["development"]["total"] < best + best = min(best, event["development"]["total"]) + checkpoint = {"schema_version": 1, "step": step, "best": best, "config": run_config, + "model": model.state_dict(), "optimizer": optimizer.state_dict(), + "cpu_rng": torch.get_rng_state(), "cuda_rng": torch.cuda.get_rng_state_all(), + "sample_rng": sample_rng.getstate()} + atomic_save(checkpoint, args.output / "last.pt") + if improved: + atomic_save(checkpoint, args.output / "best.pt") + if args.snapshot_every and step % args.snapshot_every == 0: + atomic_save({k: checkpoint[k] for k in ("schema_version", "step", "config", "model")}, + args.output / f"snapshot-{step}.pt") + print(json.dumps(event), flush=True) + elif step % 10 == 0: + print(json.dumps({"step": step, "total": total["total"], "elapsed_seconds": event["elapsed_seconds"]}), flush=True) + with (args.output / "metrics.jsonl").open("a") as stream: + stream.write(json.dumps(event) + "\n") + frozen_after = state_hash((n, p) for n, p in model.named_parameters() if not p.requires_grad) + if frozen_before != frozen_after: + raise AssertionError("Frozen model parameters changed") + summary = {"completed_steps": step, "best_development_total": best, "frozen_parameters_unchanged": True, + "trainable_parameters_changed": trainable_before != state_hash((n, p) for n, p in model.named_parameters() if p.requires_grad), + "style_offset_rms": float(model.style_offset.detach().square().mean().sqrt()), + "peak_cuda_bytes": torch.cuda.max_memory_allocated(), "last_sha256": sha256(args.output / "last.pt"), + "best_sha256": sha256(args.output / "best.pt"), "production_qualified": False} + if args.micro: + summary["micro_train_final"] = validate(model, criterion, train_rows, args.data, args.device, args.crop_frames) + write_json(args.output / "summary.json", summary) + print(json.dumps(summary), flush=True) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--assets", type=Path, default=Path(".artifacts/baseline")) + parser.add_argument("--data", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--steps", type=int, default=1000) + parser.add_argument("--lr", type=float, default=2e-5) + parser.add_argument("--style-lr", type=float, default=1e-3) + parser.add_argument("--train-style", action="store_true") + parser.add_argument("--micro", type=int, default=0) + parser.add_argument("--accumulation", type=int, default=2) + parser.add_argument("--crop-frames", type=int, default=96) + parser.add_argument("--validate-every", type=int, default=100) + parser.add_argument("--snapshot-every", type=int, default=0) + parser.add_argument("--seed", type=int, default=1729) + parser.add_argument("--device", default="cuda") + parser.add_argument("--resume", action="store_true") + args = parser.parse_args() + if min(args.steps, args.accumulation, args.crop_frames, args.validate_every) <= 0 or args.lr <= 0 or args.micro % 2: + parser.error("Positive training settings and an even micro-set size required") + train(args) + + +if __name__ == "__main__": + main() diff --git a/models/tts/kokoro-v1.1-zh/training/tests/test_frontend.py b/models/tts/kokoro-v1.1-zh/training/tests/test_frontend.py new file mode 100644 index 0000000..e062787 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/tests/test_frontend.py @@ -0,0 +1,45 @@ +import json + +import pytest + +from kokoro_training.artifacts import ROOT +from kokoro_training.frontend import Frontend + + +@pytest.fixture(scope="module") +def frontend(): + config = ROOT / ".artifacts/baseline/config.json" + if not config.exists(): + pytest.skip("baseline acquisition required for frontend integration tests") + return Frontend(json.loads(config.read_text())["vocab"]) + + +def test_numeral_is_not_erhua(frontend): + result = frontend("这本书的价格是二十三元五角。") + assert "ㄕ十4/ㄦ4ㄕ十2ㄙㄢ1" in result["phonemes"] + assert not result["unknown_symbols"] + + +def test_real_erhua_and_independent_er(frontend): + result = frontend("小孩儿在这儿,儿童有二十人。") + assert "ㄏㄞR2" in result["phonemes"] + assert "ㄦ2ㄊ中2" in result["phonemes"] + assert "ㄦ4ㄕ十2" in result["phonemes"] + + +def test_exact_api_github_policy(frontend): + phones = frontend("这个 API 在 GitHub 上。 ")["phonemes"] + assert "ˌA pˌi ˈI" in phones + assert "ɡˈɪt hˌʌb" in phones + assert "θ" not in phones + + +def test_pure_english_does_not_become_chinese_numbers(frontend): + assert frontend("We have 23 books.")["language"] == "en" + + +def test_empty_and_overlength_rejected(frontend): + with pytest.raises(ValueError): + frontend(" ") + with pytest.raises(ValueError): + frontend("hello world " * 120) diff --git a/models/tts/kokoro-v1.1-zh/training/tests/test_readiness.py b/models/tts/kokoro-v1.1-zh/training/tests/test_readiness.py new file mode 100644 index 0000000..43f8f41 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/tests/test_readiness.py @@ -0,0 +1,203 @@ +"""Unit tests validate guards/metadata, not acoustic quality or gradient readiness.""" + +import copy +import json +import io +import tarfile + +import pytest +import torch + +from kokoro_training.artifacts import ROOT, verify_assets +from kokoro_training.data import audit_data, audit_records +from kokoro_training.emime import inventory_emime +from kokoro_training.model import CheckpointError, map_checkpoint, select_style, validate_tokens +from kokoro_training.parity import cases_from_manifest, compare_tensors, compare_traces + + +def test_explicit_legacy_mapping(): + value = torch.ones(2, 1) + target = "decoder.conv.parametrizations.weight.original0" + mapped, report = map_checkpoint({target: value}, {"decoder": {"module.conv.weight_g": value}}) + assert report["passed"] and report["renamed_keys"] == 1 + assert torch.equal(mapped[target], value) + + +@pytest.mark.parametrize( + "bad", [torch.zeros(3), torch.ones(2, dtype=torch.float64), torch.tensor([float("nan"), 0.0])] +) +def test_invalid_weights_fail(bad): + with pytest.raises(CheckpointError): + map_checkpoint({"bert.weight": torch.zeros(2)}, {"bert": {"module.weight": bad}}) + + +def test_unknown_missing_and_collision_fail(): + for checkpoint in [ + {}, + {"other": {"weight": torch.zeros(2)}}, + {"bert": {"weight": torch.zeros(2), "module.weight": torch.zeros(2)}}, + ]: + with pytest.raises(CheckpointError): + map_checkpoint({"bert.weight": torch.zeros(2)}, checkpoint) + + +def test_identity_allowlist_does_not_hide_missing_learned_weights(): + expected = {"decoder.norm.weight": torch.ones(2), "bert.weight": torch.zeros(2)} + with pytest.raises(CheckpointError) as exc: + map_checkpoint(expected, {}, {"decoder.norm.weight": torch.ones(2)}) + assert exc.value.report["missing"] == ["bert.weight"] + + +@pytest.mark.parametrize( + "ids", + [[], [0, 0], [0, 178, 0], [0, True, 0], [0, 1, 0, 2, 0], [1, 2, 0], [0] + [1] * 511 + [0]], +) +def test_invalid_token_contract(ids): + with pytest.raises(ValueError): + validate_tokens(ids, 178, 512) + + +def test_style_boundary_rows(): + # Tensor indexing fixture only; never used as a model or speech sample. + voice = torch.arange(510.0)[:, None, None].expand(510, 1, 256) + assert select_style(voice, 1)[1] == 0 + assert select_style(voice, 510)[1] == 509 + for n in [0, 511]: + with pytest.raises(ValueError): + select_style(voice, n) + + +def test_different_or_nonfinite_trace_fails(): + assert not compare_tensors(torch.ones(2), torch.ones(3))["passed"] + assert not compare_tensors(torch.ones(2), torch.tensor([1.0, float("nan")]))["passed"] + assert not compare_tensors(torch.ones(2), torch.tensor([1.0, 1.0001]))["passed"] + assert not compare_traces({"audio": torch.ones(2)}, {})["passed"] + + +def test_missing_assets_fail_without_network(tmp_path): + with pytest.raises(ValueError): + verify_assets(tmp_path) + + +def test_missing_and_empty_dataset_fail(tmp_path): + assert not audit_data(tmp_path / "missing.jsonl", tmp_path)["passed"] + assert not audit_records([], None)["passed"] + + +def record(index=1, **overrides): + """Deliberately invented metadata fixture, never exported as a dataset.""" + data = { + "schema_version": 1, + "id": f"test-{index}", + "corpus": "unit-test-only", + "speaker_id": "unit-test-speaker", + "speaker_gender": "female", + "speaker_evidence_ref": "unit-test-ref", + "session_id": f"session-{index}", + "passage_id": f"passage-{index}", + "duplicate_group": f"group-{index}", + "language": "en" if index == 1 else "zh", + "origin": "real_recording", + "audio_path": f"unavailable-{index}.wav", + "audio_sha256": str(index) * 64, + "sample_rate": 24000, + "channels": 1, + "duration_seconds": 1.0, + "raw_text": f"fixture {index}", + "normalized_text": f"fixture {index}", + "split": "train" if index == 1 else "dev", + "rights": {"status": "approved", "evidence_ref": "unit-test-ref", "scope": "research_only"}, + "qc": {"status": "reviewed", "evidence_ref": "unit-test-ref"}, + } + data.update(overrides) + return data + + +@pytest.mark.parametrize( + "change,reason", + [ + ({"origin": "generated"}, "not a real"), + ( + {"rights": {"status": "pending", "evidence_ref": "pending", "scope": "unknown"}}, + "rights review", + ), + ({"speaker_gender": "unknown"}, "female speaker"), + ({"duration_seconds": float("nan")}, "nonfinite"), + ({"split": "unassigned"}, "split not assigned"), + ], +) +def test_unready_record_rejected(change, reason): + report = audit_records([record(**change), record(2)], None) + assert any(reason in e["reason"] for e in report["errors"]) + assert report["training_ready"] is False + + +@pytest.mark.parametrize( + "field", ["session_id", "passage_id", "duplicate_group", "audio_sha256", "normalized_text"] +) +def test_split_leakage_rejected(field): + a, b = record(), record(2) + b[field] = a[field] + report = audit_records([a, b], None) + assert any("crosses splits" in e["reason"] for e in report["errors"]) + + +def test_demo_prompts_cannot_be_training_data(): + manifest = json.loads((ROOT.parent / "coreml/bilingual-demo/manifest.json").read_text()) + report = audit_records([record(normalized_text=manifest["clips"][0]["text"]), record(2)], None) + assert any("development-only" in e["reason"] for e in report["errors"]) + + +def test_audio_path_escape_rejected(tmp_path): + report = audit_records([record(audio_path="../outside.wav"), record(2)], tmp_path) + assert any("escapes" in e["reason"] for e in report["errors"]) + + +def test_two_speakers_not_single_voice(): + report = audit_records([record(), record(2, speaker_id="different")], None) + assert any("exactly one" in e["reason"] for e in report["errors"]) + + +def test_bad_json_manifest_fails(tmp_path): + path = tmp_path / "broken.jsonl" + path.write_text("not JSON") + assert not audit_data(path, tmp_path)["passed"] + + +@pytest.mark.parametrize("bad", [None, [], {"rights": []}, {"rights": None}]) +def test_malformed_records_report_failure(bad): + assert not audit_records([bad], None)["passed"] + + +def test_archive_traversal_is_rejected_without_extraction(tmp_path): + archive = tmp_path / "unsafe.tar.bz2" + with tarfile.open(archive, "w:bz2") as tar: + member = tarfile.TarInfo("../outside.txt") + member.size = 4 + tar.addfile(member, io.BytesIO(b"test")) + with pytest.raises(ValueError, match="unsafe"): + inventory_emime(archive) + assert not (tmp_path.parent / "outside.txt").exists() + + +def test_empty_archive_cannot_pass_inventory(tmp_path): + archive = tmp_path / "empty.tar.bz2" + with tarfile.open(archive, "w:bz2"): + pass + assert not inventory_emime(archive)["passed"] + + +def test_saved_tokens_must_match_pinned_vocab(tmp_path): + baseline = ROOT / ".artifacts/baseline/config.json" + if not baseline.exists(): + pytest.skip("explicit baseline acquisition required for vocabulary integration check") + config = json.loads(baseline.read_text()) + source = ROOT.parent / "coreml/bilingual-demo/manifest.json" + cases = cases_from_manifest(source, config) + assert len(cases) == 15 + manifest = copy.deepcopy(json.loads(source.read_text())) + manifest["clips"][0]["input_ids"][1] = 1 + path = tmp_path / "bad-manifest.json" + path.write_text(json.dumps(manifest)) + with pytest.raises(ValueError, match="mismatch"): + cases_from_manifest(path, config) diff --git a/models/tts/kokoro-v1.1-zh/training/tests/test_training.py b/models/tts/kokoro-v1.1-zh/training/tests/test_training.py new file mode 100644 index 0000000..e017e39 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/tests/test_training.py @@ -0,0 +1,95 @@ +"""Failure modes in data boundaries, scoring, and checkpoint persistence.""" + +import json +from pathlib import Path + +import pytest +import torch + +from kokoro_training.artifacts import sha256 +from kokoro_training.evaluate import error_counts, normalize +from kokoro_training.train import atomic_save, load_data +from kokoro_training.features import MelFeatures +from kokoro_training.supervised import DeterministicLeftReflection + + +def test_deterministic_reflection_matches_upstream_values_and_gradients(): + values = torch.randn(2, 3, 11, dtype=torch.float64, requires_grad=True) + weights = torch.randn(2, 3, 12, dtype=torch.float64) + expected = torch.nn.functional.pad(values, (1, 0), mode="reflect") + actual = DeterministicLeftReflection()(values) + assert torch.equal(actual, expected) + original_grad = torch.autograd.grad((expected * weights).sum(), values)[0] + fixed_grad = torch.autograd.grad((actual * weights).sum(), values)[0] + assert torch.equal(original_grad, fixed_grad) + + +def test_mel_reflection_preserves_target_values(): + audio = torch.randn(1, 6000) + mel = MelFeatures() + original = mel.bank @ torch.stft(audio, n_fft=2048, hop_length=300, win_length=1200, + window=mel.window, center=True, return_complex=True).abs().square() + assert torch.equal(mel(audio), original) + + +def test_asr_normalization_preserves_lexical_errors(): + assert normalize("兒童,有二十二個人!", "zh") == "儿童有二十二个人" + assert error_counts("The API is ready.", "the api is ready", "en")["errors"] == 0 + assert error_counts("二十三元", "十三元", "zh")["deletions"] == 1 + assert error_counts("Open API", "open happy", "en")["substitutions"] == 1 + assert error_counts("三点 meeting", "三点米厅", "mixed")["errors"] > 0 + + +def test_atomic_checkpoint_roundtrip(tmp_path): + path = tmp_path / "last.pt" + atomic_save({"step": 1, "tensor": torch.arange(5)}, path) + atomic_save({"step": 2, "tensor": torch.arange(3)}, path) + saved = torch.load(path, weights_only=True) + assert saved["step"] == 2 + assert torch.equal(saved["tensor"], torch.arange(3)) + assert not path.with_suffix(".tmp").exists() + + +def write_manifest(directory: Path, records): + manifest = directory / "manifest.jsonl" + manifest.write_text("".join(json.dumps(row) + "\n" for row in records)) + (directory / "data-contract.json").write_text(json.dumps({ + "speaker": "declared", "manifest_sha256": sha256(manifest)})) + + +def records_with_file(tmp_path): + # A file-integrity fixture only: never passed into acoustic training. + records = [] + for i, split in enumerate(("train", "dev")): + target = tmp_path / f"target-{i}.pt" + torch.save({"integrity_test_only": torch.tensor([i])}, target) + records.append({"id": str(i), "origin": "real_recording", "speaker_id": "declared", + "passage_id": f"passage-{i}", "source_sha256": f"source-{i}", + "normalized_text": f"integrity fixture {i}", + "split": split, "targets": target.name, "targets_sha256": sha256(target)}) + return records + + +@pytest.mark.parametrize("field", ["passage_id", "source_sha256", "targets_sha256", "normalized_text"]) +def test_reject_cross_split_leakage(tmp_path, field): + records = records_with_file(tmp_path) + records[1][field] = records[0][field] + write_manifest(tmp_path, records) + with pytest.raises(ValueError, match="leakage"): + load_data(tmp_path) + + +def test_reject_modified_acoustic_targets(tmp_path): + records = records_with_file(tmp_path) + write_manifest(tmp_path, records) + (tmp_path / "target-0.pt").write_bytes(b"modified") + with pytest.raises(ValueError, match="checksum"): + load_data(tmp_path) + + +def test_reject_modified_manifest(tmp_path): + write_manifest(tmp_path, records_with_file(tmp_path)) + with (tmp_path / "manifest.jsonl").open("a") as stream: + stream.write("\n") + with pytest.raises(ValueError, match="manifest checksum"): + load_data(tmp_path) diff --git a/models/tts/kokoro-v1.1-zh/training/training-assets.lock.json b/models/tts/kokoro-v1.1-zh/training/training-assets.lock.json new file mode 100644 index 0000000..9b9f436 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/training-assets.lock.json @@ -0,0 +1,133 @@ +{ + "schema_version": 1, + "target_model": { + "repo": "yl4579/StyleTTS2", + "revision": "5cedc71c333f8d8b8551ca59378bdcc7af4c9529", + "files": { + "Utils/JDC/model.py": { + "sha256": "61ae501ab5a967e2988aa8a5498a482dfd7fdd82c040a251003636ec4f08b4aa", + "bytes": 7649 + }, + "Utils/JDC/bst.t7": { + "sha256": "54dc94364b97e18ac1dfa6287714ed121248cfaac4cfd39d061c6e0a089ef169", + "bytes": 21029926 + }, + "models.py": { + "sha256": "d9f54b98514d142dd20663318784a63805b2b718ace7a44b738431c6d0f48daf", + "bytes": 26116 + }, + "LICENSE": { + "sha256": "7f023c12a1d6b25583506f6fc77f5287c7c71b65c0f886922f14153bcc07dfa9", + "bytes": 1075 + } + } + }, + "asr_revision": "41f01f3fe87f28c78e2fbf8b568835947dd65ed9", + "files": [ + { + "part": "data", + "path": "emime/UEDIN_mandarin_bilingual_data_v1.1.tar.bz2", + "url": "https://data.cstr.ed.ac.uk/emime/UEDIN_mandarin_bilingual_data_v1.1.tar.bz2", + "sha256": "c6d268abb998172d9ecc7e8bb7080cc51c3e2d3cca758044b99cd39109e14b93", + "bytes": 1384527144 + }, + { + "part": "targets", + "path": "targets/model.py", + "url": "https://raw.githubusercontent.com/yl4579/StyleTTS2/5cedc71c333f8d8b8551ca59378bdcc7af4c9529/Utils/JDC/model.py", + "sha256": "61ae501ab5a967e2988aa8a5498a482dfd7fdd82c040a251003636ec4f08b4aa", + "bytes": 7649 + }, + { + "part": "targets", + "path": "targets/bst.t7", + "url": "https://raw.githubusercontent.com/yl4579/StyleTTS2/5cedc71c333f8d8b8551ca59378bdcc7af4c9529/Utils/JDC/bst.t7", + "sha256": "54dc94364b97e18ac1dfa6287714ed121248cfaac4cfd39d061c6e0a089ef169", + "bytes": 21029926 + }, + { + "part": "targets", + "path": "targets/LICENSE", + "url": "https://raw.githubusercontent.com/yl4579/StyleTTS2/5cedc71c333f8d8b8551ca59378bdcc7af4c9529/LICENSE", + "sha256": "7f023c12a1d6b25583506f6fc77f5287c7c71b65c0f886922f14153bcc07dfa9", + "bytes": 1075 + }, + { + "part": "asr", + "path": "whisper-turbo/added_tokens.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/added_tokens.json", + "bytes": 34648, + "sha256": "3c51f66c4c21f9e126970078f11ae77a78c74aee8df606ee9daba86e467108e0" + }, + { + "part": "asr", + "path": "whisper-turbo/config.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/config.json", + "bytes": 1256, + "sha256": "c5b526b3e3cd64cd8940dabb45e8ba726629e22d8ed389c29b552f9140daf04a" + }, + { + "part": "asr", + "path": "whisper-turbo/generation_config.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/generation_config.json", + "bytes": 3772, + "sha256": "cce11bfe3aaa6ae9e072ea2637caaec8795e68d9b67e655a5af16ee509681a4c" + }, + { + "part": "asr", + "path": "whisper-turbo/merges.txt", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/merges.txt", + "bytes": 493869, + "sha256": "2df2990a395e35e8dfbc7511e08c12d56018d8d04691e0133e5d63b21e154dc6" + }, + { + "part": "asr", + "path": "whisper-turbo/model.safetensors", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/model.safetensors", + "bytes": 1617824864, + "sha256": "542566a422ae4f3fd23f1ba11add198fca01bbf82e66e6a2857b3f608b1eb9d1" + }, + { + "part": "asr", + "path": "whisper-turbo/normalizer.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/normalizer.json", + "bytes": 52666, + "sha256": "bf1c507dc8724ca9cf9903640dacfb69dae2f00edee4f21ceba106a7392f26dd" + }, + { + "part": "asr", + "path": "whisper-turbo/preprocessor_config.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/preprocessor_config.json", + "bytes": 340, + "sha256": "7ccc62c6f2765af1f3b46c00c9b5894426835a05021c8b9c01eecb6dfb542711" + }, + { + "part": "asr", + "path": "whisper-turbo/special_tokens_map.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/special_tokens_map.json", + "bytes": 2186, + "sha256": "baea4ea09372eb4fca86b4e4346139fd73cb807d5087e9de0948e971739c3e74" + }, + { + "part": "asr", + "path": "whisper-turbo/tokenizer.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/tokenizer.json", + "bytes": 2710337, + "sha256": "297b13372ac43916285644fb9687add3cc62ee2a1adb60da3dc25cc94c1871fd" + }, + { + "part": "asr", + "path": "whisper-turbo/tokenizer_config.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/tokenizer_config.json", + "bytes": 282843, + "sha256": "844b642c73a91359722f47b35705f7174686df33d252695d8572cf9ac03a6389" + }, + { + "part": "asr", + "path": "whisper-turbo/vocab.json", + "url": "https://huggingface.co/openai/whisper-large-v3-turbo/resolve/41f01f3fe87f28c78e2fbf8b568835947dd65ed9/vocab.json", + "bytes": 1036558, + "sha256": "e2aa043ef015641d363d8288e7c241c85e36a5c761fb303598e0710233344387" + } + ] +} diff --git a/models/tts/kokoro-v1.1-zh/training/uv.lock b/models/tts/kokoro-v1.1-zh/training/uv.lock new file mode 100644 index 0000000..c9c2260 --- /dev/null +++ b/models/tts/kokoro-v1.1-zh/training/uv.lock @@ -0,0 +1,1959 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" + +[[package]] +name = "addict" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ef/fd7649da8af11d93979831e8f1f8097e85e82d5bfeabc8c68b39175d8e75/addict-2.4.0.tar.gz", hash = "sha256:b3b2210e0e067a281f5646c8c5db92e99b7231ea8b0eb5f74dbdf9e259d4e494", size = 9186, upload-time = "2020-11-21T16:21:31.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/00/b08f23b7d7e1e14ce01419a467b583edbb93c6cdb8654e54a9cc579cd61f/addict-2.4.0-py3-none-any.whl", hash = "sha256:249bb56bbfd3cdc2a004ea0ff4c2b6ddc84d53bc2194761636eb314d5cfa5dfc", size = 3832, upload-time = "2020-11-21T16:21:29.588Z" }, +] + +[[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.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/d2/f4d173e22df740bc37b1db102b386ba719b66e95b0f0d751f556b387e6d2/anyio-4.15.1.tar.gz", hash = "sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94", size = 276966, upload-time = "2026-09-05T10:42:39.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" }, +] + +[[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 = "audioread" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/4a/874ecf9b472f998130c2b5e145dcdb9f6131e84786111489103b66772143/audioread-3.1.0.tar.gz", hash = "sha256:1c4ab2f2972764c896a8ac61ac53e261c8d29f0c6ccd652f84e18f08a4cab190", size = 20082, upload-time = "2025-10-26T19:44:13.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/16/fbe8e1e185a45042f7cd3a282def5bb8d95bb69ab9e9ef6a5368aa17e426/audioread-3.1.0-py3-none-any.whl", hash = "sha256:b30d1df6c5d3de5dcef0fb0e256f6ea17bdcf5f979408df0297d8a408e2971b4", size = 23143, upload-time = "2025-10-26T19:44:12.016Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "blis" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/d0/d8cc8c9a4488a787e7fa430f6055e5bd1ddb22c340a751d9e901b82e2efe/blis-1.3.3.tar.gz", hash = "sha256:034d4560ff3cc43e8aa37e188451b0440e3261d989bb8a42ceee865607715ecd", size = 2644873, upload-time = "2025-11-17T12:28:30.511Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/d1/429cf0cf693d4c7dc2efed969bd474e315aab636e4a95f66c4ed7264912d/blis-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a1c74e100665f8e918ebdbae2794576adf1f691680b5cdb8b29578432f623ef", size = 6929663, upload-time = "2025-11-17T12:27:44.482Z" }, + { url = "https://files.pythonhosted.org/packages/11/69/363c8df8d98b3cc97be19aad6aabb2c9c53f372490d79316bdee92d476e7/blis-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f6c595185176ce021316263e1a1d636a3425b6c48366c1fd712d08d0b71849a", size = 1230939, upload-time = "2025-11-17T12:27:46.19Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/fbf65d906d823d839076c5150a6f8eb5ecbc5f9135e0b6510609bda1e6b7/blis-1.3.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d734b19fba0be7944f272dfa7b443b37c61f9476d9ab054a9ac53555ceadd2e0", size = 2818835, upload-time = "2025-11-17T12:27:48.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ad/58deaa3ad856dd3cc96493e40ffd2ed043d18d4d304f85a65cde1ccbf644/blis-1.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ef6d6e2b599a3a2788eb6d9b443533961265aa4ec49d574ed4bb846e548dcdb", size = 11366550, upload-time = "2025-11-17T12:27:49.958Z" }, + { url = "https://files.pythonhosted.org/packages/78/82/816a7adfe1f7acc8151f01ec86ef64467a3c833932d8f19f8e06613b8a4e/blis-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8c888438ae99c500422d50698e3028b65caa8ebb44e24204d87fda2df64058f7", size = 3023686, upload-time = "2025-11-17T12:27:52.062Z" }, + { url = "https://files.pythonhosted.org/packages/1e/e2/0e93b865f648b5519360846669a35f28ee8f4e1d93d054f6850d8afbabde/blis-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8177879fd3590b5eecdd377f9deafb5dc8af6d684f065bd01553302fb3fcf9a7", size = 14250939, upload-time = "2025-11-17T12:27:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/20/07/fb43edc2ff0a6a367e4a94fc39eb3b85aa1e55e24cc857af2db145ce9f0d/blis-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:f20f7ad69aaffd1ce14fe77de557b6df9b61e0c9e582f75a843715d836b5c8af", size = 6192759, upload-time = "2025-11-17T12:27:56.176Z" }, +] + +[[package]] +name = "catalogue" +version = "2.0.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/244d58127e1cdf04cf2dc7d9566f0d24ef01d5ce21811bab088ecc62b5ea/catalogue-2.0.10.tar.gz", hash = "sha256:4f56daa940913d3f09d589c191c74e5a6d51762b3a9e37dd53b7437afd6cda15", size = 19561, upload-time = "2023-09-25T06:29:24.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/96/d32b941a501ab566a16358d68b6eb4e4acc373fab3c3c4d7d9e649f7b4bb/catalogue-2.0.10-py3-none-any.whl", hash = "sha256:58c2de0020aa90f4a2da7dfad161bf7b3b054c86a5f09fcedc0b2b740c109a9f", size = 17325, upload-time = "2023-09-25T06:29:23.337Z" }, +] + +[[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 = "cffi" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/69/43965eccfdead3b9220015fd1320e117be8c6ed01a62ffab76eeb752f5d5/cffi-2.1.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0", size = 184821, upload-time = "2026-08-03T21:19:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/54/7d/16e5a096677b5e313ca80cd5e5170efa3ea44624a82bb111925522da64b1/cffi-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf", size = 184719, upload-time = "2026-08-03T21:19:46.129Z" }, + { url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" }, + { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" }, + { url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" }, + { url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" }, + { url = "https://files.pythonhosted.org/packages/80/fb/0bb75b7039588c074b37ae99f40d9bfddf990ecb2fbc346ebccd2e56b9be/cffi-2.1.1-cp312-cp312-win32.whl", hash = "sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e", size = 175292, upload-time = "2026-08-03T21:19:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/d9/79/615cc094e2fb508cade7de88d3b4f6c4ec2bab695c97bce9153dc65aadf5/cffi-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a", size = 185919, upload-time = "2026-08-03T21:19:56.89Z" }, + { url = "https://files.pythonhosted.org/packages/70/c6/d0ea84713fe46b243a436a18fcd47d639732747e21635c8a27191b06dc30/cffi-2.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80", size = 180093, upload-time = "2026-08-03T21:19:58.155Z" }, +] + +[[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/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/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.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "cloudpathlib" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/9f/1791893f3d51ec36cd9e3f8da0130d278ee909a68bc17c83b3b3de98c91b/cloudpathlib-0.25.0.tar.gz", hash = "sha256:63612e17778c5e3a51b472def8d785d0aaaf347486d6b6786dc7be627556d4c6", size = 56441, upload-time = "2026-08-22T18:41:21.493Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/f9/9084c945d0b3ef8f129b4f0dd21baec759761a337902b70f17a3945015dd/cloudpathlib-0.25.0-py3-none-any.whl", hash = "sha256:8faef3ed3a0dd71d134e8617b4fdc5ce56a12a6b485c080cfe80106e5f1d1f5d", size = 66109, upload-time = "2026-08-22T18:41:20.417Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "cn2an" +version = "0.5.24" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "proces" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/07/b45328cc4a4b88adea7b24d84d82f569e362773d4a4b69aba6e2350b058a/cn2an-0.5.24.tar.gz", hash = "sha256:c276cfc4b3c9e758214841de597502eb178de50b8da2633ed345564f90705f0e", size = 23744, upload-time = "2026-04-22T18:57:22.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/e6/f3436a83c6dddc306ee8d6cf7dc1b90a5a3496ae66d9392100c0c5c3f578/cn2an-0.5.24-py3-none-any.whl", hash = "sha256:3d57d862a56133070ba6a737b8095bfc94716b41b7684e7a98d2d5b049495db7", size = 227468, upload-time = "2026-04-22T18:57:20.496Z" }, +] + +[[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 = "confection" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/65/efd0fe8a936fc8ca2978cb7b82581fb20d901c6039e746a808f746b7647b/confection-1.3.3.tar.gz", hash = "sha256:f0f6810d567ff73993fe74d218ca5e1ffb6a44fb03f391257fc5d033546cbfaa", size = 54895, upload-time = "2026-03-24T18:45:24.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/e4/d66708bdf0d92fb4d49b22cdff4b10cec38aca5dcd7e81d909bb55c65cd7/confection-1.3.3-py3-none-any.whl", hash = "sha256:b9fef9ee84b237ef4611ec3eb5797b70e13063e6310ad9f15536373f5e313c82", size = 35902, upload-time = "2026-03-24T18:45:22.664Z" }, +] + +[[package]] +name = "csvw" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "isodate" }, + { name = "jsonschema" }, + { name = "language-tags" }, + { name = "python-dateutil" }, + { name = "rdflib" }, + { name = "rfc3986" }, + { name = "termcolor" }, + { name = "uritemplate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/f1/95a10e22505e84bf346dd8df41d9c6135aea564d83d8e39b79b0851ba4db/csvw-4.1.0.tar.gz", hash = "sha256:bfe2b64442552b392577a8d30edf886510c8d05e665606fa5caa1508365fd700", size = 83167, upload-time = "2026-07-06T08:37:26.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/7f/9586327961379fd23362eafddac9fc9ba1d6783686d20154d55e93dd8a61/csvw-4.1.0-py2.py3-none-any.whl", hash = "sha256:d432d4cccd8dcdf31ccb2272d6d7ad40a9ebde7de3c7d6ced9033afb23bb76dd", size = 69495, upload-time = "2026-07-06T08:37:24.411Z" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/d0/76d0e45d98bf4933bf48eac6bbeb17464684540f69edec41fc37c7a422b0/cuda_bindings-13.4.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:84ee88862e2e6ac39a5434c061f7f4389fbefbc418487d0670c86601d517d038", size = 6479976, upload-time = "2026-09-10T01:16:54.622Z" }, + { url = "https://files.pythonhosted.org/packages/43/56/d7b219516980f3333e232c13d727e8f4dc59afc5381cbbcfbc1215014c81/cuda_bindings-13.4.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f444d7e488cbc47e79b7be0d1cfe97201f3e1f186a18ee10f2e4da3265975b16", size = 7170956, upload-time = "2026-09-10T01:16:56.854Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/e6/22df83f82f9bc26cb1c42265cf14d34d4908dba2a0f261bd7b28244acb00/cuda_pathfinder-1.8.1-py3-none-any.whl", hash = "sha256:ae0137ff9e56ea97499bcbf54f5f2778ec25f3266715ac86da192a795af982a8", size = 62552, upload-time = "2026-09-02T16:55:28.64Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +curand = [ + { name = "nvidia-curand", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] + +[[package]] +name = "curated-tokenizers" +version = "0.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/3c/5d98e3fdbae12a0079d5ac06849bce200dd0cbf88b404d4d0fef4f7e3860/curated_tokenizers-0.0.10.tar.gz", hash = "sha256:1c2b506e9c49e999b41939d783e788e021de5586a66a594b222fc2592aa169cf", size = 2237645, upload-time = "2026-08-24T10:05:27.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/c3/5bac181563a4f61fffebb14713d65696b122f0129ffae33e0db339c53bd7/curated_tokenizers-0.0.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84366a23a728635b3d9e2f7433ba2abefb1f6b205b01aaf74c57750de6bb7cd6", size = 708221, upload-time = "2026-08-24T10:03:55.437Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4a/74ee64f9b85534d47102b2a2ff2d2014ef8c72cb338fd2f294e1c4b02ab2/curated_tokenizers-0.0.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6da08827cbf28f5b8d37ecfdd26c027be99d5fac2d2dc2d03a611c7ce294c1af", size = 690321, upload-time = "2026-08-24T10:03:57.436Z" }, + { url = "https://files.pythonhosted.org/packages/80/d2/266a0fe5e0cab7db9df706ec3ef4616a9687d07e757abf46fd1898cc294d/curated_tokenizers-0.0.10-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7094e2c5add10d187c35205bf472eba483f352182496614dfe4e754f271ad242", size = 6208507, upload-time = "2026-08-24T10:03:59.592Z" }, + { url = "https://files.pythonhosted.org/packages/37/fb/20d524a76f8bd226733931b67063c87ecead3b58a7cc3543c27ca46b63c7/curated_tokenizers-0.0.10-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:841743db1da4a4a1298675584771ed973945c31ea9f6dde9f06e9eb6b3974bf3", size = 6309412, upload-time = "2026-08-24T10:04:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/23/64/9e6ed5f3e15fbba7ddff522eafa19d28dd5b185311b2c21a06ce9670d456/curated_tokenizers-0.0.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d7be982004b217cf734bd5528cca14241853124225131a472565ba4dde4cf2d9", size = 6924980, upload-time = "2026-08-24T10:04:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c7/17d38d7a0cbd88942ea457b09002dd3b834b0ed0a8b127e6341db19355bd/curated_tokenizers-0.0.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:584197edf6f4014137e58e90e0c11e7b7ff7b9c820eeba1190441cbf76d4455e", size = 7120908, upload-time = "2026-08-24T10:04:05.689Z" }, + { url = "https://files.pythonhosted.org/packages/00/7d/355fcdc28f9691daa7ffea052adf6fd057c48d8d969a7e46bdaf6652c5b1/curated_tokenizers-0.0.10-cp312-cp312-win32.whl", hash = "sha256:200923b1551271dbf702ef7ae45f5c7f2bbca521b6206aa8faecc340e69c6dec", size = 705939, upload-time = "2026-08-24T10:04:07.674Z" }, + { url = "https://files.pythonhosted.org/packages/de/ae/57934e2f1a827870b72efdd1406308724ef874fa2a28557d1769869bb860/curated_tokenizers-0.0.10-cp312-cp312-win_amd64.whl", hash = "sha256:8ae890aae25d84248749b0a9b1e771713e72338e59d9c44bf22a3367c1ef867a", size = 752613, upload-time = "2026-08-24T10:04:09.619Z" }, + { url = "https://files.pythonhosted.org/packages/30/cd/60b4232dcdc4d5a41f8d3984a1115e4fffd9dde8fe54e5d129af469381b7/curated_tokenizers-0.0.10-cp312-cp312-win_arm64.whl", hash = "sha256:992878ef75a41bd26091ff14d6580e8ad4090cdda97534cdcbf2395e2acf8403", size = 722690, upload-time = "2026-08-24T10:04:11.783Z" }, +] + +[[package]] +name = "curated-transformers" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/06/6c12c149a7f737dacc76b4c3949dbc7ff87d622567b86996896ae4d104aa/curated-transformers-0.1.1.tar.gz", hash = "sha256:4671f03314df30efda2ec2b59bc7692ea34fcea44cb65382342c16684e8a2119", size = 16313, upload-time = "2023-05-24T07:29:22.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/67/3b72b3fdfcadab61bc8f59c17e63770e526ffabd583ed32f174a7c01af85/curated_transformers-0.1.1-py2.py3-none-any.whl", hash = "sha256:d716063d73d803c6925d2dab56fde9b9ab8e89e663c2c0587804944ba488ff01", size = 25972, upload-time = "2023-05-24T07:29:21.119Z" }, +] + +[[package]] +name = "cymem" +version = "2.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/2f0fbb32535c3731b7c2974c569fb9325e0a38ed5565a08e1139a3b71e82/cymem-2.0.13.tar.gz", hash = "sha256:1c91a92ae8c7104275ac26bd4d29b08ccd3e7faff5893d3858cb6fadf1bc1588", size = 12320, upload-time = "2025-11-14T14:58:36.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/52/478a2911ab5028cb710b4900d64aceba6f4f882fcb13fd8d40a456a1b6dc/cymem-2.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8afbc5162a0fe14b6463e1c4e45248a1b2fe2cbcecc8a5b9e511117080da0eb", size = 43745, upload-time = "2025-11-14T14:57:32.52Z" }, + { url = "https://files.pythonhosted.org/packages/f9/71/f0f8adee945524774b16af326bd314a14a478ed369a728a22834e6785a18/cymem-2.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9251d889348fe79a75e9b3e4d1b5fa651fca8a64500820685d73a3acc21b6a8", size = 42927, upload-time = "2025-11-14T14:57:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/62/6d/159780fe162ff715d62b809246e5fc20901cef87ca28b67d255a8d741861/cymem-2.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:742fc19764467a49ed22e56a4d2134c262d73a6c635409584ae3bf9afa092c33", size = 258346, upload-time = "2025-11-14T14:57:34.917Z" }, + { url = "https://files.pythonhosted.org/packages/eb/12/678d16f7aa1996f947bf17b8cfb917ea9c9674ef5e2bd3690c04123d5680/cymem-2.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f190a92fe46197ee64d32560eb121c2809bb843341733227f51538ce77b3410d", size = 260843, upload-time = "2025-11-14T14:57:36.503Z" }, + { url = "https://files.pythonhosted.org/packages/31/5d/0dd8c167c08cd85e70d274b7235cfe1e31b3cebc99221178eaf4bbb95c6f/cymem-2.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d670329ee8dbbbf241b7c08069fe3f1d3a1a3e2d69c7d05ea008a7010d826298", size = 254607, upload-time = "2025-11-14T14:57:38.036Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c9/d6514a412a1160aa65db539836b3d47f9b59f6675f294ec34ae32f867c82/cymem-2.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a84ba3178d9128b9ffb52ce81ebab456e9fe959125b51109f5b73ebdfc6b60d6", size = 262421, upload-time = "2025-11-14T14:57:39.265Z" }, + { url = "https://files.pythonhosted.org/packages/dd/fe/3ee37d02ca4040f2fb22d34eb415198f955862b5dd47eee01df4c8f5454c/cymem-2.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:2ff1c41fd59b789579fdace78aa587c5fc091991fa59458c382b116fc36e30dc", size = 40176, upload-time = "2025-11-14T14:57:40.706Z" }, + { url = "https://files.pythonhosted.org/packages/94/fb/1b681635bfd5f2274d0caa8f934b58435db6c091b97f5593738065ddb786/cymem-2.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:6bbd701338df7bf408648191dff52472a9b334f71bcd31a21a41d83821050f67", size = 35959, upload-time = "2025-11-14T14:57:41.682Z" }, +] + +[[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 = "dlinfo" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/8e/8f2f94cd40af1b51e8e371a83b385d622170d42f98776441a6118f4dd682/dlinfo-2.0.0.tar.gz", hash = "sha256:88a2bc04f51d01bc604cdc9eb1c3cc0bde89057532ca6a3e71a41f6235433e17", size = 12727, upload-time = "2025-01-16T15:43:10.756Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/90/022c79d6e5e6f843268c10b84d4a021ee3afba0621d3c176d3ff2024bfc8/dlinfo-2.0.0-py3-none-any.whl", hash = "sha256:b32cc18e3ea67c0ca9ca409e5b41eed863bd1363dbc9dd3de90fedf11b61e7bc", size = 3654, upload-time = "2025-01-16T15:43:09.474Z" }, +] + +[[package]] +name = "docopt" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/55/8f8cab2afd404cf578136ef2cc5dfb50baa1761b68c9da1fb1e4eed343c9/docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491", size = 25901, upload-time = "2014-06-16T11:18:57.406Z" } + +[[package]] +name = "en-core-web-sm" +version = "3.8.0" +source = { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl" } +wheels = [ + { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl", hash = "sha256:1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85" }, +] + +[[package]] +name = "espeakng-loader" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/92/f44ed7f531143c3c6c97d56e2b0f9be8728dc05e18b96d46eb539230ed46/espeakng_loader-0.2.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b77477ae2ddf62a748e04e49714eabb2f3a24f344166200b00539083bd669904", size = 9938387, upload-time = "2025-01-17T01:22:42.064Z" }, + { url = "https://files.pythonhosted.org/packages/a8/26/258c0cd43b9bc1043301c5f61767d6a6c3b679df82790c9cb43a3277b865/espeakng_loader-0.2.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d27cdca31112226e7299d8562e889d3e38a1e48055c9ee381b45d669072ee59f", size = 9892565, upload-time = "2025-01-17T01:22:40.365Z" }, + { url = "https://files.pythonhosted.org/packages/de/1e/25ec5ab07528c0fbb215a61800a38eca05c8a99445515a02d7fa5debcb32/espeakng_loader-0.2.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08721baf27d13d461f6be6eed9a65277e70d68234ff484fd8b9897b222cdcb6d", size = 10078484, upload-time = "2025-01-17T01:22:43.373Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ad/1b768d8daffc2996e07bbcb6f534d8de3202cd75fce1f1c45eced1ce6465/espeakng_loader-0.2.4-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:d1e798141b46a050cdb75fcf3c17db969bb2c40394f3f4a48910655d547508b9", size = 10037736, upload-time = "2025-01-17T01:22:42.576Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ed/a3d872fbad4f3a3f3db0e8c31768ab14e77cd77306de16b8b20b1e1df7ea/espeakng_loader-0.2.4-py3-none-win_amd64.whl", hash = "sha256:41f1e08ac9deda2efd1ea9de0b81dab9f5ae3c4b24284f76533d0a7b1dd7abd7", size = 9437292, upload-time = "2025-01-17T01:23:27.463Z" }, + { url = "https://files.pythonhosted.org/packages/29/64/0b75bc50ec53b4e000bac913625511215aa96124adf5dba8c4baa17c02cd/espeakng_loader-0.2.4-py3-none-win_arm64.whl", hash = "sha256:d7a2928843eaeb2df82f99a370f44e8a630f59b02f9b0d1f168a03c4eeb76b89", size = 9426841, upload-time = "2025-01-17T01:23:21.766Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/46/126b1831dca12060d4a8296bf9c4fe5c93c4f22197fa239cb0cc82042bba/filelock-3.32.6.tar.gz", hash = "sha256:a3f55a18af3652a94d8f47d6055df434f254ca1d02ef2524850c6d249ca2512c", size = 225172, upload-time = "2026-09-08T22:57:11.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/06/4f138f618dbea66803291274f228f01daf29f306fe8b96bc30dab765df75/filelock-3.32.6-py3-none-any.whl", hash = "sha256:3f16ecd0117feae0dfc147e8c62eb5daeccd8bd800378c3ddf416de9b4feb6b1", size = 100189, upload-time = "2026-09-08T22:57:10.182Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, +] + +[[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/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 = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, +] + +[[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 = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "jieba" +version = "0.42.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/cb/18eeb235f833b726522d7ebed54f2278ce28ba9438e3135ab0278d9792a2/jieba-0.42.1.tar.gz", hash = "sha256:055ca12f62674fafed09427f176506079bc135638a14e23e25be909131928db2", size = 19214172, upload-time = "2020-01-20T14:27:23.5Z" } + +[[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 = "jiwer" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rapidfuzz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/1e/963dfc249d5bbe88079b57a22556e981ddd9208e4b6116e48bdbfc01f26b/jiwer-4.0.0.tar.gz", hash = "sha256:ae9c051469102a61ef0927100baeeb4546f78d180c9b0948281d08eaf44c191e", size = 28074, upload-time = "2025-06-19T16:05:23.004Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/c9/172c525330c739a068c01050759a6f855ce16212db10a0359e690a03ac48/jiwer-4.0.0-py3-none-any.whl", hash = "sha256:7efaf0bd336b095d99ddef9dd67e1ee829d75d58aa2a81d9639870b01d6d95ea", size = 23034, upload-time = "2025-06-19T16:05:21.821Z" }, +] + +[[package]] +name = "joblib" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/1d/537ab090f302b838943a1b56497dd53059b9a9b46a074936470173a2e207/joblib-1.6.0.tar.gz", hash = "sha256:2ccc96785b12046c08fd6d55839c12857831b54a3c1673ffadd2f04bfc4eda03", size = 327903, upload-time = "2026-08-31T09:39:04.122Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/53/84099323c2ec4be98d935f63c033ac4151ee83836ca1050ede3b3aadf155/joblib-1.6.0-py3-none-any.whl", hash = "sha256:3dbbf9f6e4b592a2357b854608e980fe6390d131d7a82f011a377ef2ebef7aba", size = 306115, upload-time = "2026-08-31T09:39:02.298Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "kokoro" +version = "0.9.4" +source = { git = "https://github.com/hexgrad/kokoro?rev=dfb907a02bba8152ca444717ca5d78747ccb4bec#dfb907a02bba8152ca444717ca5d78747ccb4bec" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "loguru" }, + { name = "misaki", extra = ["en"] }, + { name = "numpy" }, + { name = "torch" }, + { name = "transformers" }, +] + +[[package]] +name = "kokoro-zh-training" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "en-core-web-sm" }, + { name = "jiwer" }, + { name = "jsonschema" }, + { name = "kokoro" }, + { name = "librosa" }, + { name = "misaki", extra = ["zh"] }, + { name = "numpy" }, + { name = "opencc-python-reimplemented" }, + { name = "scipy" }, + { name = "soundfile" }, + { name = "torch" }, + { name = "transformers" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "en-core-web-sm", url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl" }, + { name = "jiwer", specifier = "==4.0.0" }, + { name = "jsonschema", specifier = "==4.26.0" }, + { name = "kokoro", git = "https://github.com/hexgrad/kokoro?rev=dfb907a02bba8152ca444717ca5d78747ccb4bec" }, + { name = "librosa", specifier = "==0.11.0" }, + { name = "misaki", extras = ["zh"], specifier = "==0.9.4" }, + { name = "numpy", specifier = "==2.2.6" }, + { name = "opencc-python-reimplemented", specifier = "==0.1.7" }, + { name = "scipy", specifier = "==1.17.1" }, + { name = "soundfile", specifier = "==0.13.1" }, + { name = "torch", specifier = "==2.11.0+cu130", index = "https://download.pytorch.org/whl/cu130" }, + { name = "transformers", specifier = "==4.57.6" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = "==8.4.2" }, + { name = "ruff", specifier = "==0.12.12" }, +] + +[[package]] +name = "language-tags" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/d1/b37165bed9016c19e2cdecf4176fbd902a014fb8a87dd44415438e190969/language_tags-1.3.1.tar.gz", hash = "sha256:b15f05505dad3ad296a1782d5a6083fd141309186094d1ab08095348f4203f37", size = 222642, upload-time = "2026-05-08T11:46:24.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/21/b48b9e8408b3faec9fb7cb2f68352c7724add35a980c948eaceb29ac41e4/language_tags-1.3.1-py3-none-any.whl", hash = "sha256:f7db7ef8879523019603e09644a86d95ba60595ce5e5ea82d46e8971b0f68f7b", size = 216654, upload-time = "2026-05-08T11:46:17.836Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, +] + +[[package]] +name = "librosa" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioread" }, + { name = "decorator" }, + { name = "joblib" }, + { name = "lazy-loader" }, + { name = "msgpack" }, + { name = "numba" }, + { name = "numpy" }, + { name = "pooch" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "soundfile" }, + { name = "soxr" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/36/360b5aafa0238e29758729e9486c6ed92a6f37fa403b7875e06c115cdf4a/librosa-0.11.0.tar.gz", hash = "sha256:f5ed951ca189b375bbe2e33b2abd7e040ceeee302b9bbaeeffdfddb8d0ace908", size = 327001, upload-time = "2025-03-11T15:09:54.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/ba/c63c5786dfee4c3417094c4b00966e61e4a63efecee22cb7b4c0387dda83/librosa-0.11.0-py3-none-any.whl", hash = "sha256:0b6415c4fd68bff4c29288abe67c6d80b587e0e1e2cfb0aad23e4559504a7fa1", size = 260749, upload-time = "2025-03-11T15:09:52.982Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/27/72ae94ea5c8f7349ec1c229d4cd058feb799cbd0833ad6d1b47c919b37b7/llvmlite-0.49.0.tar.gz", hash = "sha256:00f16db782f4a13c78c5804aedc434e46794a77e89999a168f9401106270e50a", size = 194467, upload-time = "2026-08-11T16:26:00.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/ae/3f699ebe3590e15e023a6372dd147526fd8ec398aacf9ceb844e854964a8/llvmlite-0.49.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b541c8fac3450db7574d1f53cf9dff83f285bfed9d69bf81fe71fc2a7d4f97fe", size = 40479231, upload-time = "2026-08-11T16:23:56.773Z" }, + { url = "https://files.pythonhosted.org/packages/be/3c/e97f69c62a2d972066d9a2612ce1f3de313035ac897a5b9f787cad8b55f7/llvmlite-0.49.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6acba646d88abbc87d5c113a3d62c1fbf8b8fee11c6493f516803e30f21ae870", size = 59890658, upload-time = "2026-08-11T16:24:05.451Z" }, + { url = "https://files.pythonhosted.org/packages/69/e6/e942ee08605fc0526ff3854260c384d8315a5830e16c4c2a5aebc14dc9bf/llvmlite-0.49.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec8ad805e7515cb8440a690eb3cef4d34acb29eef80b705ec4e1c1ad3c43c68", size = 58344481, upload-time = "2026-08-11T16:24:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/84/49/2a44871cac6b5a2fd4aabd68cfdaf6de9a5c7edb36dee5d47b77bda4b50f/llvmlite-0.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a9c9e3af4e214acfefa4f73ebe7bc3fb35854a62b654edb3953f5ae33c08ba3", size = 41865543, upload-time = "2026-08-11T16:24:20.41Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[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/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" }, +] + +[[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 = "misaki" +version = "0.9.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "addict" }, + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c7/fb01370a76585b46595a01b52f18e65c8ba6d7a313a05e5d9fff0a8e1c69/misaki-0.9.4.tar.gz", hash = "sha256:3960fa3e6de179a90ee8e628446a4a4f6b8c730b6e3410999cf396189f4d9c40", size = 3756765, upload-time = "2025-04-05T21:57:14.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/ec/0ee4110ddb54278b8f21c40a140370ae8f687036c4edf578316602697c56/misaki-0.9.4-py3-none-any.whl", hash = "sha256:90e2eeb169786c014c429e5058d2ea6bcd02d651f2a24450ba6c9ffc0f8da15a", size = 3617774, upload-time = "2025-04-05T21:57:10.678Z" }, +] + +[package.optional-dependencies] +en = [ + { name = "espeakng-loader" }, + { name = "num2words" }, + { name = "phonemizer-fork" }, + { name = "spacy" }, + { name = "spacy-curated-transformers" }, +] +zh = [ + { name = "cn2an" }, + { name = "jieba" }, + { name = "ordered-set" }, + { name = "pypinyin" }, + { name = "pypinyin-dict" }, +] + +[[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 = "msgpack" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/ea2100ec54d30c46ee9dba10a3bfb79b655e96c6df237238a3234c75869b/msgpack-1.2.2.tar.gz", hash = "sha256:9eb0b0e602064527a045ea28c4f174ed69383587e29cebe28947e3b84106eb2a", size = 187025, upload-time = "2026-08-27T10:03:47.793Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/78/90c15bebb1a72667349ca62d4507e9d9369e7f8f76b95f490b823d3622e5/msgpack-1.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a4348705be86e029d04e741cf9ed0dfe03e942d7d3b92e838fa80d3aa2c3ebc", size = 84275, upload-time = "2026-08-27T10:02:07.106Z" }, + { url = "https://files.pythonhosted.org/packages/88/88/c2b6d8e81571da87aa232c0e34a3f3a0e618e6235892065ec82d1d81fc7a/msgpack-1.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a652ceeededf71d3fa40c303a02a149d42338d310162367b91c539d4bd6e0a3", size = 83970, upload-time = "2026-08-27T10:02:08.488Z" }, + { url = "https://files.pythonhosted.org/packages/da/c0/d3ede9f5d16acb4c05a9281859f1e99ef9f877a928eb78454c37f70db001/msgpack-1.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90986cc9aab9d7d1d8f38bcbf65d3f7ac83bdd90c35765db7d691b4829698cba", size = 409401, upload-time = "2026-08-27T10:02:09.877Z" }, + { url = "https://files.pythonhosted.org/packages/41/f0/29f591bea185616cf417645ac03bd3ad9b317483ad8572160e325f7fe777/msgpack-1.2.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:77c2e018417dc1d66f235e383877ee885b60ade9d29e494dd581e08af2cb1923", size = 420619, upload-time = "2026-08-27T10:02:11.526Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8e/c70c8c9180c5ddf4440eb8658ebead98e22e7686fbf84f6b165031430750/msgpack-1.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e91332144f69bc3018c91232fac26da580ef748fb8eaddd7914d4458001cc4f", size = 379747, upload-time = "2026-08-27T10:02:13.345Z" }, + { url = "https://files.pythonhosted.org/packages/50/9a/f10ce11fa62700c9ab87a22e65b9ca272f7f673ddd31aeb2de6ae272ad35/msgpack-1.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e915d390d7068b257ca8b62f3fc59fad135c8631d1017ab03b0b924b07c5367", size = 398944, upload-time = "2026-08-27T10:02:15.006Z" }, + { url = "https://files.pythonhosted.org/packages/82/fe/d7be978456ff8552e69a8e270d882e7530e01513c096b293d83df03753ea/msgpack-1.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c522420d78db2431887d45b518e304d86e27b9ad0b30f24e3806a6ad5d8bdbfc", size = 373979, upload-time = "2026-08-27T10:02:16.618Z" }, + { url = "https://files.pythonhosted.org/packages/be/af/91b0d8d3fb3063e259daee3ea8515cea6282f68f4b0e5f0b6fea25762c6e/msgpack-1.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b554d8164ebb526892194f71dcd96ef1fefe0c250087498785d3ffc04a80be3", size = 417781, upload-time = "2026-08-27T10:02:18.293Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3c/ce8e9efe1fd9e95c78b3705e4300ba7feba3dc6c00fb76259895db155518/msgpack-1.2.2-cp312-cp312-win32.whl", hash = "sha256:0e3315de5a4b2920ccef48d96b4448025e064a10d0f5a250f6584477d839c8d4", size = 65267, upload-time = "2026-08-27T10:02:19.869Z" }, + { url = "https://files.pythonhosted.org/packages/85/98/a33b8b4af14e3476bb0da1b8c36ef7a0f28dcf95db1c5e68ff88cb89d591/msgpack-1.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:b68614fba0570349833b7dd999ff0aed4e5cc8d9eb6e3a7d4527be33c65e33d3", size = 72275, upload-time = "2026-08-27T10:02:21.141Z" }, + { url = "https://files.pythonhosted.org/packages/df/5e/2f323a33a6aba5bd4b2d8b430e4fab21d92cd91c093b49ee287bc166ee54/msgpack-1.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:59d5b93efa45fd09f620d0c9ba81cde339a2c9937af3eea42ee9653094ce6640", size = 65488, upload-time = "2026-08-27T10:02:22.575Z" }, +] + +[[package]] +name = "murmurhash" +version = "1.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/2e/88c147931ea9725d634840d538622e94122bceaf346233349b7b5c62964b/murmurhash-1.0.15.tar.gz", hash = "sha256:58e2b27b7847f9e2a6edf10b47a8c8dd70a4705f45dccb7bf76aeadacf56ba01", size = 13291, upload-time = "2025-11-14T09:51:15.272Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/46/be8522d3456fdccf1b8b049c6d82e7a3c1114c4fc2cfe14b04cba4b3e701/murmurhash-1.0.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d37e3ae44746bca80b1a917c2ea625cf216913564ed43f69d2888e5df97db0cb", size = 27884, upload-time = "2025-11-14T09:50:13.133Z" }, + { url = "https://files.pythonhosted.org/packages/ed/cc/630449bf4f6178d7daf948ce46ad00b25d279065fc30abd8d706be3d87e0/murmurhash-1.0.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0861cb11039409eaf46878456b7d985ef17b6b484103a6fc367b2ecec846891d", size = 27855, upload-time = "2025-11-14T09:50:14.859Z" }, + { url = "https://files.pythonhosted.org/packages/ff/30/ea8f601a9bf44db99468696efd59eb9cff1157cd55cb586d67116697583f/murmurhash-1.0.15-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5a301decfaccfec70fe55cb01dde2a012c3014a874542eaa7cc73477bb749616", size = 134088, upload-time = "2025-11-14T09:50:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/c9/de/c40ce8c0877d406691e735b8d6e9c815f36a82b499d358313db5dbe219d7/murmurhash-1.0.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32c6fde7bd7e9407003370a07b5f4addacabe1556ad3dc2cac246b7a2bba3400", size = 133978, upload-time = "2025-11-14T09:50:17.572Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/bd49963ecd84ebab2fe66595e2d1ed41d5e8b5153af5dc930f0bd827007c/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d8b43a7011540dc3c7ce66f2134df9732e2bc3bbb4a35f6458bc755e48bde26", size = 132956, upload-time = "2025-11-14T09:50:18.742Z" }, + { url = "https://files.pythonhosted.org/packages/4f/7c/2530769c545074417c862583f05f4245644599f1e9ff619b3dfe2969aafc/murmurhash-1.0.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43bf4541892ecd95963fcd307bf1c575fc0fee1682f41c93007adee71ca2bb40", size = 134184, upload-time = "2025-11-14T09:50:19.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/a4/b249b042f5afe34d14ada2dc4afc777e883c15863296756179652e081c44/murmurhash-1.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:f4ac15a2089dc42e6eb0966622d42d2521590a12c92480aafecf34c085302cca", size = 25647, upload-time = "2025-11-14T09:50:21.049Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/028179259aebc18fd4ba5cae2601d1d47517427a537ab44336446431a215/murmurhash-1.0.15-cp312-cp312-win_arm64.whl", hash = "sha256:4a70ca4ae19e600d9be3da64d00710e79dde388a4d162f22078d64844d0ebdda", size = 23338, upload-time = "2025-11-14T09:50:22.359Z" }, +] + +[[package]] +name = "narwhals" +version = "2.26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/68/5351e34623d253423240ea7de3f8fc74fa8ab14b1ab3c0ec4ac8997413c9/narwhals-2.26.0.tar.gz", hash = "sha256:6b9cadca82f375c7e4cf584fdc86ca25da54827307a9c58f94547ee6104b82dd", size = 686970, upload-time = "2026-09-08T13:32:08.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b5/1b84b2c784db76d69442334bc8b8748c840f13ca53be086f4f250ad4a0bc/narwhals-2.26.0-py3-none-any.whl", hash = "sha256:29326d74f107c347fd1009bd58e38d9f7c7c5b51e6de97bc93dbc325d9038b54", size = 474034, upload-time = "2026-09-08T13:32:07.159Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +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 = "num2words" +version = "0.5.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docopt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/58/ad645bd38b4b648eb2fc2ba1b909398e54eb0cbb6a7dbd2b4953e38c9621/num2words-0.5.14.tar.gz", hash = "sha256:b066ec18e56b6616a3b38086b5747daafbaa8868b226a36127e0451c0cf379c6", size = 218213, upload-time = "2024-12-17T20:17:10.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/5b/545e9267a1cc080c8a1be2746113a063e34bcdd0f5173fd665a5c13cb234/num2words-0.5.14-py3-none-any.whl", hash = "sha256:1c8e5b00142fc2966fd8d685001e36c4a9911e070d1b120e1beb721fa1edb33d", size = 163525, upload-time = "2024-12-17T20:17:06.074Z" }, +] + +[[package]] +name = "numba" +version = "0.67.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/90/2544f4e3a61e501d6c9a5418fd4b905323222693d54a02cab0106a0af865/numba-0.67.0.tar.gz", hash = "sha256:cd75aa535b33fa05d9d930b1ae8af9f97a2881e96d72dfb38ec9b78284d9f851", size = 2836515, upload-time = "2026-08-11T23:04:00.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/915cddba90010348ed0444451132fdde9b000bcbaff1582029b5bf115d11/numba-0.67.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6004d8d5f28d4028687fb2d972d629295b13685943bd2ed5cd8810c3b848e219", size = 2745050, upload-time = "2026-08-11T23:03:25.607Z" }, + { url = "https://files.pythonhosted.org/packages/bb/38/926757caaac18a66f057d7544a63620bf360a07d281c9f7ecadd2aa83963/numba-0.67.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f63d43db06b4756424d6d2484737c902e0ae944a0eec3e8b0b4de2c695b15caa", size = 3884596, upload-time = "2026-08-11T23:03:27.688Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6d/58291dc58da39d98b32db7f044729f6d8d4920cd9622fbab3179b54ff4c4/numba-0.67.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76d3335aaeffb9dc88309420890e73497a00be08a7530441bc2b58ffe025bfa5", size = 3585290, upload-time = "2026-08-11T23:03:29.684Z" }, + { url = "https://files.pythonhosted.org/packages/6e/63/ab21828b4056afed71f9ecb40f4de26c2c19de731cc001961aca74b79464/numba-0.67.0-cp312-cp312-win_amd64.whl", hash = "sha256:50e2b72406c18cda5dd7431b0082cb85ea94e06c64c33607248fc8bef92cfb81", size = 2815645, upload-time = "2026-08-11T23:03:31.732Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +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/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" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.0.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, +] + +[[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.19.0.56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +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" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +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" }, +] +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.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.28.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, +] + +[[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 = "opencc-python-reimplemented" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/6d/c6f37eed651dd6b752e50f80a93396cdaa42a6acc6ce05ad7452303ea511/opencc-python-reimplemented-0.1.7.tar.gz", hash = "sha256:4f777ea3461a25257a7b876112cfa90bb6acabc6dfb843bf4d11266e43579dee", size = 482566, upload-time = "2023-02-11T03:58:42.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/6b/055b7806f320cc8f2cdf23c5f70221c0dc1683fca9ffaf76dfc2ad4b91b6/opencc_python_reimplemented-0.1.7-py2.py3-none-any.whl", hash = "sha256:41b3b92943c7bed291f448e9c7fad4b577c8c2eae30fcfe5a74edf8818493aa6", size = 481813, upload-time = "2023-02-11T03:58:39.66Z" }, +] + +[[package]] +name = "ordered-set" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "phonemizer-fork" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "dlinfo" }, + { name = "joblib" }, + { name = "segments" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/fa/9294d2f11890ca49d0bdac7a4da60cbe5686629bfd4987cae0ad75e051cc/phonemizer_fork-3.3.2.tar.gz", hash = "sha256:10e16e827d0443b087062e21b55e805c00989cf1343b2e81e734cae5f6c0cf69", size = 300989, upload-time = "2025-01-30T13:02:31.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/f1/0dcce21b0ae16a82df4b6583f8f3ad8e55b35f7e98b6bf536a4dd225fa08/phonemizer_fork-3.3.2-py3-none-any.whl", hash = "sha256:97305c76f4183b3825dae8f4c032265fe78c9946ce58c47d4b62161349264b74", size = 82700, upload-time = "2025-01-30T13:02:28.667Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.11.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/18/f3bb8ef0d3b930692343da8aa4d3cbcd6749477c053959395ac81965a6e9/platformdirs-4.11.8.tar.gz", hash = "sha256:f23abafea7dd4276d1f29104b83598d7dcc567cafd07c9c951e66665645437fc", size = 37182, upload-time = "2026-09-08T22:20:42.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/e1/5b7b8bbb55084d1425bcb9bc823ff519e1b2be05f6ebb0089e2eacc38413/platformdirs-4.11.8-py3-none-any.whl", hash = "sha256:52f2f181bbfde907966932cc8312d967d02976422d66d537ea16092b8e291081", size = 24027, upload-time = "2026-09-08T22:20:41.537Z" }, +] + +[[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 = "pooch" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "platformdirs" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788, upload-time = "2026-01-30T19:15:09.649Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" }, +] + +[[package]] +name = "preshed" +version = "3.0.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cymem" }, + { name = "murmurhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/75/fe6b7bbd0dea530a001b0e24c331b21a0be2786e402abf3c57f5dce43d4b/preshed-3.0.13.tar.gz", hash = "sha256:d75f718bbfd97e992f7827e0fa7faf6a91bdd9c922d5baa4b50d62731396cb89", size = 18338, upload-time = "2026-03-23T08:57:31.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/fb/ccff23c44c04088c248539005fcda78b9014512a34d170c5360f02ad908b/preshed-3.0.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5d14eea14bd01291388928991d7df7d60b9fd19ae970e55006eb4d29b0c1e8eb", size = 138497, upload-time = "2026-03-23T08:56:35.321Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ce/cad5a8145881a771e6c0d002f2e585fc19b962f120860b54d32af5baa342/preshed-3.0.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f05b08ce92399c0655b5e0eb5a1cc1f9e295703ed3aabdfaf6538dfa8ae23d57", size = 138010, upload-time = "2026-03-23T08:56:36.399Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a2/c5fed4fb3e946699259d11e4036a3cfdd8c89b3e542e3077d46781642425/preshed-3.0.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62cf7f3113132891d6bba70ff547ad81c6fe50a31930bbbb8499f1d47cd122b7", size = 861498, upload-time = "2026-03-23T08:56:37.67Z" }, + { url = "https://files.pythonhosted.org/packages/51/94/8c9bc48a6ea4903f53a1a0031ce8e35687526949f25821762ef21493c007/preshed-3.0.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b8de3f58043070a354477995acdd98626ce43e4193c708ebd0f694e467f5155", size = 868988, upload-time = "2026-03-23T08:56:39.324Z" }, + { url = "https://files.pythonhosted.org/packages/b6/df/ecd2f40055ff52527ca117ffbfafb888c1a3079b59fbabe03c5b8f9b7240/preshed-3.0.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:183b339956a9e1d7a4a00038a3b9587a734db9e8bd915939a49791bd1b372156", size = 1847382, upload-time = "2026-03-23T08:56:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/e6/88/bdb244e40284ded3632a9f88c23bc80230bd7b2ae4a8b7f2cc91adead7a8/preshed-3.0.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e77bed56aded7cbe5d28d6bd2178bc5b13eda0e0e464dab205fb578fa915000", size = 1919236, upload-time = "2026-03-23T08:56:42.616Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c9/c91ea56342e6c364fc69b444a1ac5432327857199c44032c9cc9dc4c3a23/preshed-3.0.13-cp312-cp312-win_amd64.whl", hash = "sha256:04d8f13f2986e5d11af5ac51f55ce3106c70c41b483d20ea392e6180bdd0f870", size = 122938, upload-time = "2026-03-23T08:56:44.271Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0b/6a99d99619fd83b14c696e2489caed7070647488d4d3ac0b723d35db2de0/preshed-3.0.13-cp312-cp312-win_arm64.whl", hash = "sha256:19318dc1cd8cac6663c6c830bf7e0002d2de853769fb03e056774e97c21bedfd", size = 109194, upload-time = "2026-03-23T08:56:45.346Z" }, +] + +[[package]] +name = "proces" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/3d/4159b57736ced0fd22553226df20a985ef7655519c80ffcb8a9fb49ebeee/proces-0.1.7.tar.gz", hash = "sha256:70a05d9e973dd685f7a9092c58be695a8181a411d63796c213232fd3fdc43775", size = 31188, upload-time = "2023-09-09T03:27:38.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/88/06cc0c7d890ed8d7e16ef0e56880dea516a21643fb1f3a69a50f4cc6f716/proces-0.1.7-py3-none-any.whl", hash = "sha256:308325bbc96877263f06e57e5e9c760c4b42cc722887ad60be6b18fc37d68762", size = 137718, upload-time = "2023-09-09T03:27:35.463Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/ef/fc4f868f4e2cee79f863883abffceff107875f569b848507319842d2a681/pydantic-2.13.5.tar.gz", hash = "sha256:51a9c5f7b2f8e636f04c6cada605d9b6a3bf1348fdf945a3d8869b19bba0ee08", size = 845750, upload-time = "2026-08-28T14:04:00.916Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/47/c95ffc2009878c7aac0c5e08528022dcb885933252a88b5f170058014464/pydantic-2.13.5-py3-none-any.whl", hash = "sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73", size = 472589, upload-time = "2026-08-28T14:03:59.136Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/f9/8a06bea35ef8daf588f707784c973a7046e0034c8d8cfb08828eeffb8b75/pydantic_core-2.46.5.tar.gz", hash = "sha256:10416c15b8839ecc4ef4d0885da76da6fd0f67333a0eb8aff6d93c4b8f2910fc", size = 472262, upload-time = "2026-08-28T10:01:31.677Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3f/76358795aa7a8c6d4f36e2cb828ad1c90ee118e1393a9281664f5aade9d4/pydantic_core-2.46.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b9fe6fb92520e3fd61f2e49000b6911b188824f089b75973ea06d6267f0b476d", size = 2076516, upload-time = "2026-08-28T09:58:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/db/50/26b091836076ce4cb2fac264186936acc069e0595772cfd02a563bc4761a/pydantic_core-2.46.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a39ac25a9a2fa4072efdb429833c4a4c8009a51ff9eea3eeae131713cd27991e", size = 1922874, upload-time = "2026-08-28T09:58:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/2a8ce3849e299d44e2d2c196b6082643a3235565a735cb51db7a6261f614/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fdc8b93a41521988916eeaa271173fcca7fa0803d62f87675aac8dcec1c8e29", size = 1951772, upload-time = "2026-08-28T09:58:25.435Z" }, + { url = "https://files.pythonhosted.org/packages/87/46/ac0dc8bdd9e6048183a14eb127764e7ad9240021c17513074a4711b0e31e/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b98134087d9de723658d17a42c7d0da8d6e2ef08015dee7dc93889047315f5e4", size = 2031832, upload-time = "2026-08-28T09:58:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c2/339de5bef7be36301a2231eaa52e62163742c2281f11b5f4892bc79785cd/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e652ab17569c94bff5475520f907b7148b8c24036a8ebbe5cf7cf7493d28579a", size = 2208645, upload-time = "2026-08-28T09:58:28.948Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a0/9ff22b797724262da14427abaed4dd1d864a139693fc5e7809114376a716/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d925f3d9afd05a8c0fb3a1031463a8d59ebe5e2afad297e29c78be19e13b4e62", size = 2265935, upload-time = "2026-08-28T09:58:30.625Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a4/eb9409ec0736e50aa70a412f16c204ed149516846912f7e6724d4c73ee53/pydantic_core-2.46.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2", size = 2066284, upload-time = "2026-08-28T09:58:32.289Z" }, + { url = "https://files.pythonhosted.org/packages/c0/02/7f6156ffc926857f1c37c07d9a388682865a81830ab6a1b637082c25e399/pydantic_core-2.46.5-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:816ff0a6550ffc06c098ccd2e0698600f9aa7da192a79eaa6f9af504a35db869", size = 2105889, upload-time = "2026-08-28T09:58:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/e781d357ebe09fc929f995700f1b3503e8897f1cece183ecb1300d4d67e9/pydantic_core-2.46.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c7ea57fc63aa7da93a1bd2d644e6577befae10c52c4e36377635eea1056a74f5", size = 2158006, upload-time = "2026-08-28T09:58:35.647Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/644597d84ab400e50609c192120b85c9681c22d3a20461b9060a79be0a7a/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:efd62a42486f1bda5d24cb4f63d15a3c7768375fe83d36f9417b4ad7a2fb20b3", size = 2158408, upload-time = "2026-08-28T09:58:37.38Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ee/ca3b7b3a4b3769ffe9ce9432a7c9be755de9593a46d3b0d54d0409323e44/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2bc9419666990c06d7397831f2126a1ecc3594aaa3ff7de5bf2d066802f4e07b", size = 2309609, upload-time = "2026-08-28T09:58:39.22Z" }, + { url = "https://files.pythonhosted.org/packages/ce/52/39fa1f451486019524ca685020390e7ca351832fd874530ba30c8628e6dc/pydantic_core-2.46.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:18a09e1e1011b462f2e32774f25859ef1223d5c2b0546a633cf56654710721e0", size = 2342618, upload-time = "2026-08-28T09:58:40.89Z" }, + { url = "https://files.pythonhosted.org/packages/81/5e/468fc630568c61dcef3cd47ad32ffbeed9af643f49208d1ea86ab4f890c4/pydantic_core-2.46.5-cp312-cp312-win32.whl", hash = "sha256:5cb482e9e84c851f4e623fe4acc1ced89168cf1fe18f7089db4548c8f5bbb65b", size = 1939475, upload-time = "2026-08-28T09:58:42.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/c9/4c19f41b84cf6b622a72fbeed7665b25d47a187d68d47d0d430c07f23268/pydantic_core-2.46.5-cp312-cp312-win_amd64.whl", hash = "sha256:5e81740c09e310f5aa5cbd3e434a01c154d4bef93241c7877b39f211d2b78ba8", size = 2043140, upload-time = "2026-08-28T09:58:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/af/dd/0c1a050299147c746e5256db16d645ab5efd4f78c59937d581a0524e74a2/pydantic_core-2.46.5-cp312-cp312-win_arm64.whl", hash = "sha256:f7b0ec93a2893de856652154d73b7ba622f26fa97726487dcac373de5f4c6084", size = 1997729, upload-time = "2026-08-28T09:58:46.13Z" }, + { url = "https://files.pythonhosted.org/packages/df/dd/053c2e4303f791f3b8f8a14ab0b22008e8eb21d868c0c90b4f9be705b76a/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:013d6f3483d81e02e7c328831808f336c8596ee33b4bd4026b9ffb1e960b8942", size = 2062540, upload-time = "2026-08-28T10:01:00.318Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/a18df751a5e37dd51bfad7f68e766999125bebe68c9e1d10a493ad01bd63/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:e9c134bb666dd54b778b9fc0d2b50cbb7f979b9e3716f26a88c9ab3b6fc1dd0f", size = 1902040, upload-time = "2026-08-28T10:01:02.529Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/01d40f9d07ce8a779fd6e0bd8ad4fba91309500dd67b869e2e219d261a6d/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:347ec774390c87326a2e4929d58d3f7e8763a104d5d35f4cd595a4c952366433", size = 1967479, upload-time = "2026-08-28T10:01:05.004Z" }, + { url = "https://files.pythonhosted.org/packages/fa/04/c81d4841331c2178b6fb09ae225425e110ed72d990c9fe556c4ec03d1013/pydantic_core-2.46.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e24d8f05fa2d28513d94e877e9c75ad66175376209b3977f916e240e623193c", size = 2111034, upload-time = "2026-08-28T10:01:07.345Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pypinyin" +version = "0.55.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/a4/784cf98c09e0dc22776b0d7d8a4a5b761218bcae4608c2416ce1e167c8af/pypinyin-0.55.0.tar.gz", hash = "sha256:b5711b3a0c6f76e67408ec6b2e3c4987a3a806b7c528076e7c7b86fcf0eaa66b", size = 839836, upload-time = "2025-07-20T12:01:50.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/7b/4cabc76fcc21c3c7d5c671d8783984d30ac9d3bb387c4ba784fca3cdfa3a/pypinyin-0.55.0-py2.py3-none-any.whl", hash = "sha256:d53b1e8ad2cdb815fb2cb604ed3123372f5a28c6f447571244aca36fc62a286f", size = 840203, upload-time = "2025-07-20T12:01:48.535Z" }, +] + +[[package]] +name = "pypinyin-dict" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pypinyin" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/7a/f56b7096cde930a65f8d5dc8cb726136d53c23175148f6aa1daa75419126/pypinyin_dict-0.9.0.tar.gz", hash = "sha256:8c491396baa1567311f2ec759cbc154638f3bcefdc711d34e53e373e3a429fa5", size = 9264679, upload-time = "2025-01-12T09:35:15.704Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/8f/add772a61256a9ac91d95bf5ec3dffc1de97c8e5da53d40655044b2e1509/pypinyin_dict-0.9.0-py2.py3-none-any.whl", hash = "sha256:10cfbe40af87d704b867533177be8cd72837da9e224755dd275798e88097067a", size = 9506709, upload-time = "2025-01-12T09:35:10.318Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[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 = "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/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" }, +] + +[[package]] +name = "rapidfuzz" +version = "3.14.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/97/226c43b7b5d957bc3840ed52ea99eed261f99834c4619be7a4742cbaeafa/rapidfuzz-3.14.6.tar.gz", hash = "sha256:e13a8160d017b499ec7a2fa9d0ce1ae2e7377080815785819f966fb235d4eb60", size = 57955060, upload-time = "2026-08-30T21:45:51.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/d2/5a7646b185a61400220e4783d23461c1e864a9ee82ba443b18c218e2364b/rapidfuzz-3.14.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b46cecf27025e7a934332ade033e6a394da8a493f19fa1d835e3b2968a4ff7da", size = 1965178, upload-time = "2026-08-30T21:42:24.164Z" }, + { url = "https://files.pythonhosted.org/packages/8b/72/10fc4e414eeed7963e2f1c315c731cb68196f0478cb244c78a21f5ce8662/rapidfuzz-3.14.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1901414b135afb1a7f4b1ef940b95523b49cc5642aecf02af740f37567e98137", size = 1248230, upload-time = "2026-08-30T21:42:26.088Z" }, + { url = "https://files.pythonhosted.org/packages/39/e9/0794043c1a0af09cacdbb6a9e8b9b2079cdf73337e7c29b4a9f117415bb9/rapidfuzz-3.14.6-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96a548979cd939b2c69358a0f5088a408524fbf7454f04bf90939fa971e64310", size = 1380396, upload-time = "2026-08-30T21:42:27.97Z" }, + { url = "https://files.pythonhosted.org/packages/2f/73/9218cf4424ab86260ee88ebdb612c5ed4d9bfd6b6d1e2f3c3bf4599d13bf/rapidfuzz-3.14.6-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b22ef7e5e2341efc6216b666491022027b984e5aef93446064742f43f3c1d926", size = 1674037, upload-time = "2026-08-30T21:42:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f5/bad528b6dfc608a48838508f270c79332ab05592703c9a46504ba95e9eab/rapidfuzz-3.14.6-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f0d2d95c787d812b9106cfbcb94ad37a49f59df9287e00a75eb61afc246e8759", size = 2722897, upload-time = "2026-08-30T21:42:31.737Z" }, + { url = "https://files.pythonhosted.org/packages/13/da/49ab137f788a0e03e872d4c6b3d5c9c6c6bed4e4ccea381f69c4d186341b/rapidfuzz-3.14.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0debb5f43662ea84d2f0228a0c7407ff647f9c3d13f3b692efff0cde46eebce0", size = 3168023, upload-time = "2026-08-30T21:42:33.663Z" }, + { url = "https://files.pythonhosted.org/packages/59/33/81ca664a15194b8b4a7e863b534e36c057724f9709c7781e9400d0edf024/rapidfuzz-3.14.6-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:1d253e1fe44648242a0029b42ba23adf238ed2a7eb3d8ed0a03731a23f074ae0", size = 1474666, upload-time = "2026-08-30T21:42:35.5Z" }, + { url = "https://files.pythonhosted.org/packages/87/eb/b16f9f8cc255c8dc7c0d7712aa7e7c12a6fd85c8b2b56665f2a24222a941/rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e06c6050c9bf6cd72305e3e6a293918b2b92cf2a067007585a53898624902e3c", size = 2402289, upload-time = "2026-08-30T21:42:37.309Z" }, + { url = "https://files.pythonhosted.org/packages/4a/73/eaa1ca89f6ab12c0fe7f943226ce4ad1d2c67eb281dfd706279771fcff5a/rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d85a6e9180e53cde95c95dfeb05a2ac94ead4d9d803a8fd186d2719a678b8483", size = 2788332, upload-time = "2026-08-30T21:42:39.412Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ad/db927fbe23f621dd292a6332a19822703084617c0281a88156a8c138d4e0/rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:35db2670f69fa3a4eb4741055581477ff92f2cf39e7e06f43ebcb97c2192fe7c", size = 2510540, upload-time = "2026-08-30T21:42:41.629Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b2/8e9012968fab837babe1292edcbe1c972605f5b3af19c7fcac2ded731d39/rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f9d93e5424d1e4c103b57906b8beba270e680afda3ffdff7ea3bc6173b37083c", size = 3299876, upload-time = "2026-08-30T21:42:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/19/99/799ce99328ea97fe5d7510048ffea148b8ad4a838366f908691be52342a5/rapidfuzz-3.14.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9b0a501f37fb852c54469375baa25874246b3bbc8b6e21fb4cd186a32335868", size = 4277032, upload-time = "2026-08-30T21:42:46.08Z" }, + { url = "https://files.pythonhosted.org/packages/07/8a/995b4746c5bc1f561e64de1fa546927183fec7a369fe988716ef394a6d0a/rapidfuzz-3.14.6-cp312-cp312-win32.whl", hash = "sha256:9e974251a9833791bc557b46f975676a56c2d58946f795cd2964b095496dfdcc", size = 1887051, upload-time = "2026-08-30T21:42:48.265Z" }, + { url = "https://files.pythonhosted.org/packages/84/c4/12f01df5778227c8655fcd9b429fc001d43270f5d8d154edc9066bab1de3/rapidfuzz-3.14.6-cp312-cp312-win_amd64.whl", hash = "sha256:cfca36e4612208875e08611a779164b6cb8900ab8bbd3d82d4cfdfae9efbfac9", size = 1731992, upload-time = "2026-08-30T21:42:50.211Z" }, + { url = "https://files.pythonhosted.org/packages/19/8d/92217f0bc81ec458b4134ad53714b1be0cd3be21494227d73510b06467d6/rapidfuzz-3.14.6-cp312-cp312-win_arm64.whl", hash = "sha256:96bbd5a1c67d135334d02fae74f1d933fdda204ea03d544a59dab6b1cbfbf565", size = 1186693, upload-time = "2026-08-30T21:42:52.63Z" }, +] + +[[package]] +name = "rdflib" +version = "7.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/f5/18bb77b7af9526add0c727a3b2048959847dc5fb030913e2918bf384fec3/rdflib-7.6.0.tar.gz", hash = "sha256:6c831288d5e4a5a7ece85d0ccde9877d512a3d0f02d7c06455d00d6d0ea379df", size = 4943826, upload-time = "2026-02-13T07:15:55.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/c2/6604a71269e0c1bd75656d5a001432d16f2cc5b8c057140ec797155c295e/rdflib-7.6.0-py3-none-any.whl", hash = "sha256:30c0a3ebf4c0e09215f066be7246794b6492e054e782d7ac2a34c9f70a15e0dd", size = 615416, upload-time = "2026-02-13T07:15:46.487Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/5c/f403115361de25809e8f785686ec7096e30fef73be9ae35aa51da4e80abb/regex-2026.9.10.tar.gz", hash = "sha256:1e321e2c84f0e52c457f5ea5944f796d6e8e09cb99738ea98dcc1bfe402a128d", size = 417072, upload-time = "2026-09-09T21:00:21.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/c8/bfbe893e90ee0148bd2860dd086f09b5d2080ca2b125f740c2e118c16982/regex-2026.9.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880ac684c27176464c00c3fdc456116364f5ebc70da07aad0c2d4a7ba45e98db", size = 496609, upload-time = "2026-09-09T20:57:09.987Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ac/56d5ae6efb759255c3b3db650a4be25f96a844ea3613b91e1e189a3b7294/regex-2026.9.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9d36b03dc362aa40ffaaec9d9bd75e87763529563ec008c43b0e07782f5be7a", size = 297024, upload-time = "2026-09-09T20:57:11.35Z" }, + { url = "https://files.pythonhosted.org/packages/60/4b/0f2d5f6bbb791cc10f22f0ed16c487e630dde8fa8fa0bd92a2bfe21a4b20/regex-2026.9.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866de9f98df0611d7b62b3a8729d3284a64c0cc6edd90bb95a533e443a4939cb", size = 291905, upload-time = "2026-09-09T20:57:13.127Z" }, + { url = "https://files.pythonhosted.org/packages/89/51/3fb5fe0d32f4cf0bc982286722c729a8d6f522d2fa2d5d14a702d9fc87f8/regex-2026.9.10-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c66d54042a14a503907d81861b8a5235e6d1f03d4fbc1d8767f652eaf957ac1", size = 800055, upload-time = "2026-09-09T20:57:14.68Z" }, + { url = "https://files.pythonhosted.org/packages/89/46/ee507bd2f9d4420f26a594b35c551d7194b66f5d7897f63730fae6ec05c1/regex-2026.9.10-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:032da15431c890d376f53547f0a6219f4f4cd19f3e4f11bdc321453b5bd207e4", size = 871133, upload-time = "2026-09-09T20:57:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/cbaa90689684f91b1bc017e7f8c6d9425c6bd299108db02482dc51376d8a/regex-2026.9.10-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:23ac9a28180f274d7dd7651fa131ad5b02d343b75df4b040737f0356223895dd", size = 919627, upload-time = "2026-09-09T20:57:18.402Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/dcdf5e0d898024005cfcce631e3e934d111dfbe177ca0b7f253ae8a735a2/regex-2026.9.10-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e67f8843f0e4b931f1fa860bf3bbe4134b714c0155cc5c7c0d7ea450230aae0", size = 804587, upload-time = "2026-09-09T20:57:19.859Z" }, + { url = "https://files.pythonhosted.org/packages/73/70/eedfe81c29bae266a06ab4250978361a9bccd474704d88d4f4ef4506dff8/regex-2026.9.10-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bb7774924f8cd69f49cba0b3c2d679a6326f777e0e67d130ad5203e4df53f0d3", size = 777320, upload-time = "2026-09-09T20:57:21.62Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e5/86b207077efbcd91305700488f170b7eb1e1c54721cea74175273aa3b9a4/regex-2026.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c32480f3371b75068decaf9e5da72c224e953830dd71e36e06cf80e30ea39d8", size = 790572, upload-time = "2026-09-09T20:57:23.048Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/f622c3b2323f4c035b98e80221740a442127ad7993135b814f52057430db/regex-2026.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d2d377fd1cad611b806cdd732d86b65f536c768209890cb442556548daa65a23", size = 865485, upload-time = "2026-09-09T20:57:24.658Z" }, + { url = "https://files.pythonhosted.org/packages/4a/be/34bd621d3d6ac906ad67e57ed56c40cd45f7d51b9c0328335e97a7cb8ecb/regex-2026.9.10-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c014641157e9049b0603b8daa5343bd408d9b757b709aaa0f373cd3fab2d7944", size = 767925, upload-time = "2026-09-09T20:57:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/0d/28/ddbf7cba86f2adf5038c6c16aa829636ffc6e437f81bb0cbf302899cea5e/regex-2026.9.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1562aabd9d4eb09bd88a62ad97ed06800094b529ac43419e43020b9cefec79b0", size = 858800, upload-time = "2026-09-09T20:57:27.901Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f1/e8d7656ff3d3bd32e881d32f540b4981c79dee61908d6b790a45966e6895/regex-2026.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2479171edccced52ef02b899558f88ab2c235fe05b93180fdcae1670aacd89e1", size = 791648, upload-time = "2026-09-09T20:57:29.786Z" }, + { url = "https://files.pythonhosted.org/packages/fc/65/eac1a79115c8475d8ff539602eb54031564d719fdea5a599c4b0a1a26d1b/regex-2026.9.10-cp312-cp312-win32.whl", hash = "sha256:239620b0e0681669367c0e218c8eb2551d9f8fe3b9fccfc8d0003377804e8348", size = 267326, upload-time = "2026-09-09T20:57:31.782Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d4/4dcd0a05d3e97ca829165df1c39717f899d1d484dac8a6032439f2cb8d6d/regex-2026.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:4db7d00c4afbfbb55b8e17b1e371da11418ea9389b030acec63c1fa4c7ad4b86", size = 277933, upload-time = "2026-09-09T20:57:33.648Z" }, + { url = "https://files.pythonhosted.org/packages/55/f8/22617a80dee28f2451011eae36bc26b3d78c4994ba87b5281d60acf9b6c0/regex-2026.9.10-cp312-cp312-win_arm64.whl", hash = "sha256:c25a754bb81a2edcfc3b65eda50f017d736f818112ed43e8aafd595cb00678ae", size = 277447, upload-time = "2026-09-09T20:57:35.156Z" }, +] + +[[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 = "rfc3986" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/30/5b1b6c28c105629cc12b33bdcbb0b11b5bb1880c6cfbd955f9e792921aa8/rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", size = 49378, upload-time = "2021-05-07T23:29:27.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/e5/63ca2c4edf4e00657584608bee1001302bbf8c5f569340b78304f2f446cb/rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97", size = 31976, upload-time = "2021-05-07T23:29:25.611Z" }, +] + +[[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 = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, +] + +[[package]] +name = "ruff" +version = "0.12.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/f0/e0965dd709b8cabe6356811c0ee8c096806bb57d20b5019eb4e48a117410/ruff-0.12.12.tar.gz", hash = "sha256:b86cd3415dbe31b3b46a71c598f4c4b2f550346d1ccf6326b347cc0c8fd063d6", size = 5359915, upload-time = "2025-09-04T16:50:18.273Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/79/8d3d687224d88367b51c7974cec1040c4b015772bfbeffac95face14c04a/ruff-0.12.12-py3-none-linux_armv6l.whl", hash = "sha256:de1c4b916d98ab289818e55ce481e2cacfaad7710b01d1f990c497edf217dafc", size = 12116602, upload-time = "2025-09-04T16:49:18.892Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c3/6e599657fe192462f94861a09aae935b869aea8a1da07f47d6eae471397c/ruff-0.12.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7acd6045e87fac75a0b0cdedacf9ab3e1ad9d929d149785903cff9bb69ad9727", size = 12868393, upload-time = "2025-09-04T16:49:23.043Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d2/9e3e40d399abc95336b1843f52fc0daaceb672d0e3c9290a28ff1a96f79d/ruff-0.12.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:abf4073688d7d6da16611f2f126be86523a8ec4343d15d276c614bda8ec44edb", size = 12036967, upload-time = "2025-09-04T16:49:26.04Z" }, + { url = "https://files.pythonhosted.org/packages/e9/03/6816b2ed08836be272e87107d905f0908be5b4a40c14bfc91043e76631b8/ruff-0.12.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:968e77094b1d7a576992ac078557d1439df678a34c6fe02fd979f973af167577", size = 12276038, upload-time = "2025-09-04T16:49:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d5/707b92a61310edf358a389477eabd8af68f375c0ef858194be97ca5b6069/ruff-0.12.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42a67d16e5b1ffc6d21c5f67851e0e769517fb57a8ebad1d0781b30888aa704e", size = 11901110, upload-time = "2025-09-04T16:49:32.07Z" }, + { url = "https://files.pythonhosted.org/packages/9d/3d/f8b1038f4b9822e26ec3d5b49cf2bc313e3c1564cceb4c1a42820bf74853/ruff-0.12.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b216ec0a0674e4b1214dcc998a5088e54eaf39417327b19ffefba1c4a1e4971e", size = 13668352, upload-time = "2025-09-04T16:49:35.148Z" }, + { url = "https://files.pythonhosted.org/packages/98/0e/91421368ae6c4f3765dd41a150f760c5f725516028a6be30e58255e3c668/ruff-0.12.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59f909c0fdd8f1dcdbfed0b9569b8bf428cf144bec87d9de298dcd4723f5bee8", size = 14638365, upload-time = "2025-09-04T16:49:38.892Z" }, + { url = "https://files.pythonhosted.org/packages/74/5d/88f3f06a142f58ecc8ecb0c2fe0b82343e2a2b04dcd098809f717cf74b6c/ruff-0.12.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ac93d87047e765336f0c18eacad51dad0c1c33c9df7484c40f98e1d773876f5", size = 14060812, upload-time = "2025-09-04T16:49:42.732Z" }, + { url = "https://files.pythonhosted.org/packages/13/fc/8962e7ddd2e81863d5c92400820f650b86f97ff919c59836fbc4c1a6d84c/ruff-0.12.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01543c137fd3650d322922e8b14cc133b8ea734617c4891c5a9fccf4bfc9aa92", size = 13050208, upload-time = "2025-09-04T16:49:46.434Z" }, + { url = "https://files.pythonhosted.org/packages/53/06/8deb52d48a9a624fd37390555d9589e719eac568c020b27e96eed671f25f/ruff-0.12.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afc2fa864197634e549d87fb1e7b6feb01df0a80fd510d6489e1ce8c0b1cc45", size = 13311444, upload-time = "2025-09-04T16:49:49.931Z" }, + { url = "https://files.pythonhosted.org/packages/2a/81/de5a29af7eb8f341f8140867ffb93f82e4fde7256dadee79016ac87c2716/ruff-0.12.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:0c0945246f5ad776cb8925e36af2438e66188d2b57d9cf2eed2c382c58b371e5", size = 13279474, upload-time = "2025-09-04T16:49:53.465Z" }, + { url = "https://files.pythonhosted.org/packages/7f/14/d9577fdeaf791737ada1b4f5c6b59c21c3326f3f683229096cccd7674e0c/ruff-0.12.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a0fbafe8c58e37aae28b84a80ba1817f2ea552e9450156018a478bf1fa80f4e4", size = 12070204, upload-time = "2025-09-04T16:49:56.882Z" }, + { url = "https://files.pythonhosted.org/packages/77/04/a910078284b47fad54506dc0af13839c418ff704e341c176f64e1127e461/ruff-0.12.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b9c456fb2fc8e1282affa932c9e40f5ec31ec9cbb66751a316bd131273b57c23", size = 11880347, upload-time = "2025-09-04T16:49:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/30185fcb0e89f05e7ea82e5817b47798f7fa7179863f9d9ba6fd4fe1b098/ruff-0.12.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f12856123b0ad0147d90b3961f5c90e7427f9acd4b40050705499c98983f489", size = 12891844, upload-time = "2025-09-04T16:50:02.591Z" }, + { url = "https://files.pythonhosted.org/packages/21/9c/28a8dacce4855e6703dcb8cdf6c1705d0b23dd01d60150786cd55aa93b16/ruff-0.12.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:26a1b5a2bf7dd2c47e3b46d077cd9c0fc3b93e6c6cc9ed750bd312ae9dc302ee", size = 13360687, upload-time = "2025-09-04T16:50:05.8Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fa/05b6428a008e60f79546c943e54068316f32ec8ab5c4f73e4563934fbdc7/ruff-0.12.12-py3-none-win32.whl", hash = "sha256:173be2bfc142af07a01e3a759aba6f7791aa47acf3604f610b1c36db888df7b1", size = 12052870, upload-time = "2025-09-04T16:50:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/85/60/d1e335417804df452589271818749d061b22772b87efda88354cf35cdb7a/ruff-0.12.12-py3-none-win_amd64.whl", hash = "sha256:e99620bf01884e5f38611934c09dd194eb665b0109104acae3ba6102b600fd0d", size = 13178016, upload-time = "2025-09-04T16:50:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/28/7e/61c42657f6e4614a4258f1c3b0c5b93adc4d1f8575f5229d1906b483099b/ruff-0.12.12-py3-none-win_arm64.whl", hash = "sha256:2a8199cab4ce4d72d158319b63370abf60991495fb733db96cd923a34c52d093", size = 12256762, upload-time = "2025-09-04T16:50:15.737Z" }, +] + +[[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 = "scikit-learn" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "narwhals" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/eb/eaf5e07fcc0da7149b0e084f24e54edd7441b9a89ce7e034032ae97fe3a0/scikit_learn-1.9.1.tar.gz", hash = "sha256:629cada3e33e2b9bf376cdc7614a47a4140b8aedc1d836579e359736fbd82977", size = 7786908, upload-time = "2026-09-10T18:34:04.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/a7/25f0a43d2fde306e8ef45f45121192f687b79beaf4bae8c21607c46c5e63/scikit_learn-1.9.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c0f8b5d09b44101cea2767f300680bada1ea27f976fe4b48b83950a4f55a49a", size = 8775209, upload-time = "2026-09-10T18:32:42.804Z" }, + { url = "https://files.pythonhosted.org/packages/60/ea/57e57539ce175d774fc291ed091b0a6d756854b92cd92554c6bb4d0ae498/scikit_learn-1.9.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:8c14ce41d561f7749f990b41d6703fe02c4669fbc485e598e069e0a1967b488e", size = 8295541, upload-time = "2026-09-10T18:32:45.069Z" }, + { url = "https://files.pythonhosted.org/packages/78/2b/5721a174406bfba49bce20ae997b3b64cf355c3f623a2638284ab6a82156/scikit_learn-1.9.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4c20a6c017d820faa7ac8c783e3d0c6a9a2e297bf9f55332ca17cdf7fd4d04d", size = 8871592, upload-time = "2026-09-10T18:32:46.999Z" }, + { url = "https://files.pythonhosted.org/packages/8e/57/a50162f3d29feb979ab6347c6debda506dfb525bcff3c50dd17606651c7e/scikit_learn-1.9.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e5d7b18a5b9dca241a74695f3275fa4c895a9dadc72b3d8df5fa9d1083c9b83e", size = 9166043, upload-time = "2026-09-10T18:32:49.343Z" }, + { url = "https://files.pythonhosted.org/packages/72/8d/27c054166bac671770d1ea0ef7716134fe8119c0df224a58c89fd735a61c/scikit_learn-1.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:4b59abb30618121cc46b45972d6bf53a7128b4df4cd346c6ca6f4d5f9031e49c", size = 8262238, upload-time = "2026-09-10T18:32:51.679Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d6/493086006ea0c68ad62c40a8dece1961b61bf503f45400f133d47f56e5be/scikit_learn-1.9.1-cp312-cp312-win_arm64.whl", hash = "sha256:d5945a2908be62350e2978344e62b56c1552c2ca4f844ebf6277c94944d647dd", size = 7902102, upload-time = "2026-09-10T18:32:53.67Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, +] + +[[package]] +name = "segments" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "csvw" }, + { name = "regex" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/57/85cac3a8e32370e88fa5fa92812edb6025db7fcbed51452bd56ee1524957/segments-2.4.0.tar.gz", hash = "sha256:bba71f5520ddd54c8aa2f4d765a60618c6862162d6e7356a4a097f2223166f5b", size = 18662, upload-time = "2026-03-07T10:01:28.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/60/eef9acce946177f92c9aabf432224d87ab908bafafac516a36ab924199f3/segments-2.4.0-py2.py3-none-any.whl", hash = "sha256:4021dc67f201cc03c864c74c618bdb163b1af629da3040babbaa37d8813f3db0", size = 16321, upload-time = "2026-03-07T10:01:27.885Z" }, +] + +[[package]] +name = "setuptools" +version = "81.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, +] + +[[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 = "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 = "smart-open" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/53/9c513747547fd595d5c143259129ea8b9c3ea2f6b7bb9dcea2b1966ded3c/smart_open-8.0.1.tar.gz", hash = "sha256:18b1c4496003c6902be17c15f032b5c319f307c89c6ae9e6b028b508bed8b2cf", size = 61882, upload-time = "2026-07-15T13:56:10.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/96/325b8c507ccecc50421fecc0345a502ee6e4a44785af3c4e6ecbadad624a/smart_open-8.0.1-py3-none-any.whl", hash = "sha256:3e97f90e92a952cb57863dfe132082c400a52eeeb27c067692fb51dbcc5b0089", size = 73504, upload-time = "2026-07-15T13:56:09.033Z" }, +] + +[[package]] +name = "soundfile" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/41/9b873a8c055582859b239be17902a85339bec6a30ad162f98c9b0288a2cc/soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b", size = 46156, upload-time = "2025-01-25T09:17:04.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/28/e2a36573ccbcf3d57c00626a21fe51989380636e821b341d36ccca0c1c3a/soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445", size = 25751, upload-time = "2025-01-25T09:16:44.235Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ab/73e97a5b3cc46bba7ff8650a1504348fa1863a6f9d57d7001c6b67c5f20e/soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33", size = 1142250, upload-time = "2025-01-25T09:16:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e5/58fd1a8d7b26fc113af244f966ee3aecf03cb9293cb935daaddc1e455e18/soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593", size = 1101406, upload-time = "2025-01-25T09:16:49.662Z" }, + { url = "https://files.pythonhosted.org/packages/58/ae/c0e4a53d77cf6e9a04179535766b3321b0b9ced5f70522e4caf9329f0046/soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb", size = 1235729, upload-time = "2025-01-25T09:16:53.018Z" }, + { url = "https://files.pythonhosted.org/packages/57/5e/70bdd9579b35003a489fc850b5047beeda26328053ebadc1fb60f320f7db/soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618", size = 1313646, upload-time = "2025-01-25T09:16:54.872Z" }, + { url = "https://files.pythonhosted.org/packages/fe/df/8c11dc4dfceda14e3003bb81a0d0edcaaf0796dd7b4f826ea3e532146bba/soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5", size = 899881, upload-time = "2025-01-25T09:16:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/14/e9/6b761de83277f2f02ded7e7ea6f07828ec78e4b229b80e4ca55dd205b9dc/soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9", size = 1019162, upload-time = "2025-01-25T09:16:59.573Z" }, +] + +[[package]] +name = "soxr" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/11/27cebce4a108f77afea7c80545115536b45e3f11ebfb914f638fdd9ba847/soxr-1.1.0.tar.gz", hash = "sha256:9f228ae21c78fa9359ca98d8a5e8e91f30639e438e574133dace62c5b5309e44", size = 173067, upload-time = "2026-05-03T00:15:18.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/8a/f3da7973b5f1b05d2d7e94d5376b881dcbc05297900cae6c3d33d95b209b/soxr-1.1.0-cp312-abi3-macosx_10_14_x86_64.whl", hash = "sha256:e0e09fa633ce2e67df08b298afced4d184f6e753fc330f241022250f1d0d61da", size = 204124, upload-time = "2026-05-03T00:14:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/03/dc/200013a74641f8774664bbcd2346c695c05c2e300ea792adcb40a293eed0/soxr-1.1.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6a7ad82b8d5f3fcc04b1d2ca055562b96af571e1d4fa7c6c61d0fb509ac43b4", size = 165457, upload-time = "2026-05-03T00:14:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/88/2b/2e5eba817a762a2ec589ff165b8bc5955b25a0ad140045f7cd8e45410543/soxr-1.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf98c0d7b7d5ef5bf072fee8d3020e8b664f2d195933ea7bc5089267c2e22a06", size = 206529, upload-time = "2026-05-03T00:14:57.646Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f1/0e55195893228609c9a08c3b13b7a83a46c3a992cd00d3304f0f320cfb07/soxr-1.1.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b033078e86f3c4a658e5697fac8995764fad9e799563616b630136b613167f1", size = 240413, upload-time = "2026-05-03T00:14:59.363Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4d/621e4150e4815246ad552d215a8a294a90143fedd19ee442cf82d3b3abc8/soxr-1.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:6ae2a174bffea94e8ead857dad85999d3f49f091774dbad5b046c0417d7092f4", size = 174357, upload-time = "2026-05-03T00:15:00.724Z" }, +] + +[[package]] +name = "spacy" +version = "3.8.16" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, + { name = "click" }, + { name = "confection" }, + { name = "cymem" }, + { name = "jinja2" }, + { name = "murmurhash" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "setuptools" }, + { name = "spacy-legacy" }, + { name = "spacy-loggers" }, + { name = "srsly" }, + { name = "thinc" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "wasabi" }, + { name = "weasel" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/5d/b0b4cd2f6e8a0470e50f7f0cfc1cea6e2f25572e97fb5d404c7941d5e88a/spacy-3.8.16.tar.gz", hash = "sha256:a3d19da23637cc396b42d22fc33852680f675d8ddcb847d3e2a0d094712d1794", size = 1330989, upload-time = "2026-08-24T10:05:57.936Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/b2/33e8e4cac876090c5d8b4016b23ca2647f66c2a87c518ea010047e78ee1f/spacy-3.8.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e045765035e9760f38637101f41a7c89d3b69659dcc70e716bb4011a95969ac9", size = 6565218, upload-time = "2026-08-24T10:04:38.825Z" }, + { url = "https://files.pythonhosted.org/packages/d2/1e/247e43597b10576eccfed55af7999663f00ae934394acebef724cabdc1b7/spacy-3.8.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:44a641085abbe3a09ea56a89f2e50b5f51aea6cf69213b70305fb48e341b883b", size = 6460523, upload-time = "2026-08-24T10:04:40.873Z" }, + { url = "https://files.pythonhosted.org/packages/0e/8c/2b150ea6e9b667b710e7365328edfdd3c6729af4e48124baa80c4784c26b/spacy-3.8.16-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2c46c35467d963a62dc0407c99d3a18562d4c85ee887a57e4a07dde020f1a38", size = 34901687, upload-time = "2026-08-24T10:04:44.138Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9c/3b5d64a72ac40cf584134c6639ac67f8f2d504eb081c91da3ad2ffae5c04/spacy-3.8.16-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8d0f63c3124d0a34a37e9b519d004cee1269744be07f91f843902e6c9f3e557a", size = 35496883, upload-time = "2026-08-24T10:04:48.383Z" }, + { url = "https://files.pythonhosted.org/packages/45/16/8c9d9afed3afbfd585e876c330ec8dcff878a64bc99c108355c8b59ea954/spacy-3.8.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:397a80c4d09a6237eebaaee00a2e5f8732ff4cc165f94679b899f30179d38ea8", size = 35012008, upload-time = "2026-08-24T10:04:52.983Z" }, + { url = "https://files.pythonhosted.org/packages/32/dd/eeaf28004591f0d282da809194d9f7bfb6a1c0626ca0d7d69e8b5436751f/spacy-3.8.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2810fd2ce41f6a8dde62642dad0d11fd9db7cb6a1cd40b1c8b70a01586e6ff9f", size = 36028110, upload-time = "2026-08-24T10:04:56.874Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c0/4122fa6c3670d504ad5a3094c9b512f0d0908f66cefeb8c8c3349f8c6a28/spacy-3.8.16-cp312-cp312-win_amd64.whl", hash = "sha256:5991c334e71c23b798c25e0d403295dde4d2d1fb58c2e075450b964db03c05ea", size = 15180961, upload-time = "2026-08-24T10:05:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/36/55/2fd66419866455289c090cd177faf7d3ee360f36b2eea5e4c74b2c541a58/spacy-3.8.16-cp312-cp312-win_arm64.whl", hash = "sha256:770cc0581fc06c0723cb1488dc1d1da0570801168da786674626f342d903ed37", size = 14552405, upload-time = "2026-08-24T10:05:03.382Z" }, +] + +[[package]] +name = "spacy-curated-transformers" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "curated-tokenizers" }, + { name = "curated-transformers" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/b3/a4fd3cf28008cbe1d95463b5c76a0d9c8da7b9ad4f06289c2be4aae62052/spacy_curated_transformers-0.3.1.tar.gz", hash = "sha256:7e53fccf64260e641b0a3f2b65b6d98381b86cef6eeb21ce279e8db849e8525d", size = 218990, upload-time = "2025-05-28T10:29:32.69Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d8/f053d43125ae4ad14f3e2a12a475a656128233f1f40a272c6e09a05c73e8/spacy_curated_transformers-0.3.1-py2.py3-none-any.whl", hash = "sha256:503559b6a1d6e44ec2c978e18ed871ce5c3d56871dc9216c0e1523428204e610", size = 237943, upload-time = "2025-05-28T10:29:31.058Z" }, +] + +[[package]] +name = "spacy-legacy" +version = "3.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/79/91f9d7cc8db5642acad830dcc4b49ba65a7790152832c4eceb305e46d681/spacy-legacy-3.0.12.tar.gz", hash = "sha256:b37d6e0c9b6e1d7ca1cf5bc7152ab64a4c4671f59c85adaf7a3fcb870357a774", size = 23806, upload-time = "2023-01-23T09:04:15.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/55/12e842c70ff8828e34e543a2c7176dac4da006ca6901c9e8b43efab8bc6b/spacy_legacy-3.0.12-py2.py3-none-any.whl", hash = "sha256:476e3bd0d05f8c339ed60f40986c07387c0a71479245d6d0f4298dbd52cda55f", size = 29971, upload-time = "2023-01-23T09:04:13.45Z" }, +] + +[[package]] +name = "spacy-loggers" +version = "1.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/3d/926db774c9c98acf66cb4ed7faf6c377746f3e00b84b700d0868b95d0712/spacy-loggers-1.0.5.tar.gz", hash = "sha256:d60b0bdbf915a60e516cc2e653baeff946f0cfc461b452d11a4d5458c6fe5f24", size = 20811, upload-time = "2023-09-11T12:26:52.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/78/d1a1a026ef3af911159398c939b1509d5c36fe524c7b644f34a5146c4e16/spacy_loggers-1.0.5-py3-none-any.whl", hash = "sha256:196284c9c446cc0cdb944005384270d775fdeaf4f494d8e269466cfa497ef645", size = 22343, upload-time = "2023-09-11T12:26:50.586Z" }, +] + +[[package]] +name = "srsly" +version = "2.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "catalogue" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/db/f794f219a6c788b881252d2536a8c4a97d2bdaadc690391e1cb53d123d71/srsly-2.5.3.tar.gz", hash = "sha256:08f98dbecbff3a31466c4ae7c833131f59d3655a0ad8ac749e6e2c149e2b0680", size = 490881, upload-time = "2026-03-23T11:56:59.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/e9f7fcec4cc92ad8bad6316c4241638b8cf7380382d4489d94ec6c436452/srsly-2.5.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:71e51c046ccbeefb86524c6b1e17574f579c6ac4dc8ea4a09437d3e8f88342d3", size = 658379, upload-time = "2026-03-23T11:55:59.85Z" }, + { url = "https://files.pythonhosted.org/packages/21/e4/fea4512e9785f58509b2cf67d993323848e583161b5fcfdc7dd9d7c1f3df/srsly-2.5.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f73c0db911552e94fe2016e1759d261d2f47926f68826664cada3723c87006a", size = 658513, upload-time = "2026-03-23T11:56:01.239Z" }, + { url = "https://files.pythonhosted.org/packages/20/b1/53591681b6ff2699a4f97b2d5552ba196eaa6a979b0873605f4c04b5f7ee/srsly-2.5.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c1ac27ae5f4bb9163c7d2c45fc8ec173aac3d92e32086d9472b326c5c6e570e", size = 1172265, upload-time = "2026-03-23T11:56:02.589Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c9/741e29f534919a944a16da4184924b1d3404c4bf60716ab2b91be771d1e3/srsly-2.5.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:99026bcd9cbd3211cc36517400b04ca0fc5d3e412b14daf84ee6e65f67d9a2d8", size = 1180873, upload-time = "2026-03-23T11:56:03.944Z" }, + { url = "https://files.pythonhosted.org/packages/89/57/5554f786eccf78b2750d6ac63be126e1b67badec2cb409dd611cf6f8c52b/srsly-2.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:07d682679e639eb46ff7e6da4a92714f4d5ffe351d088ee66f221e9b1f8865bb", size = 1120437, upload-time = "2026-03-23T11:56:05.283Z" }, + { url = "https://files.pythonhosted.org/packages/eb/95/9b4f73b1be3692f86d72ccc131c8e50f26f824d5c8830a59390bcc5b60ef/srsly-2.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8e0542d85d6b55cf2934050d6ffcb1cd76c768dcf9572e7467002cf087bb366d", size = 1137376, upload-time = "2026-03-23T11:56:06.613Z" }, + { url = "https://files.pythonhosted.org/packages/5a/de/89ca640ca1953c4612279ce515d0af35658df3c06cdb324329bc91b4a7e1/srsly-2.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:598f1e494c18cacb978299d77125415a586417081959f8ec3f068b32d97f8933", size = 652459, upload-time = "2026-03-23T11:56:07.994Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/7ab6d49e36d9cc72ee15746cabd116eb6f338be8a06c1882968ee9d6c7d7/srsly-2.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:4b1b721cd3ad1a9b2343519aadc786a4d09d5c0666962d49852eb12d6ec3fe26", size = 638411, upload-time = "2026-03-23T11:56:09.31Z" }, +] + +[[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 = "termcolor" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, +] + +[[package]] +name = "thinc" +version = "8.3.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blis" }, + { name = "catalogue" }, + { name = "confection" }, + { name = "cymem" }, + { name = "murmurhash" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "preshed" }, + { name = "pydantic" }, + { name = "setuptools" }, + { name = "srsly" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/46/76df95f2c327f9a9cef30c1523bf285627897097163584dcf5f77b2ebce2/thinc-8.3.13.tar.gz", hash = "sha256:68e658549fc1eb3ff92aed5147fcbb9c15d6e9cc0e623b4d0998d16522ffb4f9", size = 194640, upload-time = "2026-03-23T07:22:36.41Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/af/f7c1ebfe92eb5d27d7f2f3da67a11e2eb57bc30ab1553279af6dc65b65a8/thinc-8.3.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:77a41f66285321d20aaedaea1e87d7cd48dca6d2427bed1867ec7cba7109fc8d", size = 821097, upload-time = "2026-03-23T07:21:56.698Z" }, + { url = "https://files.pythonhosted.org/packages/45/8f/69d7338575d98df85d0b54c0f5fc277dba72587fe9ab846ecdd12a998bcb/thinc-8.3.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3710d318b4e5460cf366a6f7b5ddbefb5d39dbd4cfa408222750fdc6c27c4411", size = 791932, upload-time = "2026-03-23T07:21:58.38Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a5/21d010c81e81e1589e5ccb4950e521804d13726e541e87f644c51815673b/thinc-8.3.13-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5a08c87143a6d20177652dca1ec0dc815d88216d8fc62594a57e8bc45bf5ed49", size = 3854219, upload-time = "2026-03-23T07:21:59.819Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ff/6914bf370bd1d604d89e6dfb46b97d10cd9b00d42ff8c036283e92314a8c/thinc-8.3.13-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b5ec9ff313819e7d8667794a3559463fa89ff45aaa73e3fd8d6273b1e0d7a7f", size = 3903307, upload-time = "2026-03-23T07:22:01.652Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3d/5572b47fa155fb3388c071515b74024fa17a6efd1df9406da378f0aa84ef/thinc-8.3.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5c9a48f2bc1e04f138240ed5f9b815a9141a5de26accd0f08fa0137fcefed258", size = 4836882, upload-time = "2026-03-23T07:22:03.565Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/a8d77c7bac089697c6df302cc3c936a1ab36a4720deae889e6f1dbcbd0eb/thinc-8.3.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:79a29a44d76bd02f5ac0624268c6e42b3576ae472c791a8ae9c2d813ae789b59", size = 5033398, upload-time = "2026-03-23T07:22:05.045Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/5651bb1f904d04220fc7670035ada921bf0638e2cff6444d67c12887a968/thinc-8.3.13-cp312-cp312-win_amd64.whl", hash = "sha256:ed1dc709ac4f2f03b710457889e4e02f05de51bc8456980c241d0b28798bc7cb", size = 1721248, upload-time = "2026-03-23T07:22:06.749Z" }, + { url = "https://files.pythonhosted.org/packages/94/8d/683703de021ffbe46833d722b70f49ffbbca8e5bd6876256977555d92d7d/thinc-8.3.13-cp312-cp312-win_arm64.whl", hash = "sha256:c6a049703a6011c8fe26ee41af7e70272145594140d82f79bb23de619c6a6525", size = 1645777, upload-time = "2026-03-23T07:22:08.104Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/dc/6c58154c1c65f758ea979e7139cb76993a9cfc662d14e9be3c4a667cfb77/threadpoolctl-3.7.0.tar.gz", hash = "sha256:61348cfb77d53b9242e0017029244b559b810c142ced65b4e21eeca1843959a7", size = 31961, upload-time = "2026-09-15T15:46:20.263Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/3f/f88a53f60a472b46f4023f56d204dd7de33d34c5d2acbfa0d70a674e639e/threadpoolctl-3.7.0-py3-none-any.whl", hash = "sha256:cd8b60b5641b45c67bbf73c64c843235fc2d8a480c87389f52f5dbee893b86be", size = 26362, upload-time = "2026-09-15T15:46:19.168Z" }, +] + +[[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" }, +] + +[[package]] +name = "torch" +version = "2.11.0+cu130" +source = { registry = "https://download.pytorch.org/whl/cu130" } +dependencies = [ + { name = "cuda-bindings", marker = "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" }, + { 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 = "sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:252f237d417fac3ba59b1635815c1f035a8241f2af038f2c076ed430932d89f1", upload-time = "2026-04-27T20:01:46Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:96911323dcfcd42028c7e8edde7bdf25bb187753234e8775f0f3f112e86a22db", upload-time = "2026-04-27T20:02:14Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-win_amd64.whl", hash = "sha256:ef8beae16d781c3244ef28dc7bee6d8871c26bbde65d5bf66e902cb61972c4ab", upload-time = "2026-04-27T20:03:28Z" }, +] + +[[package]] +name = "tqdm" +version = "4.70.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/ea/b2a5bd54b28a324dae8211928b2d730b6547500342c7e6c6dea08bd0a485/tqdm-4.70.1.tar.gz", hash = "sha256:cefd0eca11b2a37a3aee776544d4f4ae913f02688135b5556b8788dfa474afc4", size = 171846, upload-time = "2026-09-11T07:25:16.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl", hash = "sha256:c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73", size = 80199, upload-time = "2026-09-11T07:25:14.599Z" }, +] + +[[package]] +name = "transformers" +version = "4.57.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498, upload-time = "2026-01-16T10:38:31.289Z" }, +] + +[[package]] +name = "triton" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, +] + +[[package]] +name = "typer" +version = "0.27.2" +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/16/f7/57713ba479fd405eb76de31404b2c744c289e336b2d999511ebf51e496f7/typer-0.27.2.tar.gz", hash = "sha256:269b7eb9d3c202ca84b4bc9618cb04ebb43d3d4d1e567e4c768607232c05f945", size = 204045, upload-time = "2026-08-28T10:26:55.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/bf/205d0004930ede8f542fb58f601526fccf4ae7626075ca1e6c4de5d3d652/typer-0.27.2-py3-none-any.whl", hash = "sha256:b3a5fc4342d5fc8fda8fc3010b1cf117e9249aab7fae800c2eff62fd3842d97d", size = 123130, upload-time = "2026-08-28T10:26:53.752Z" }, +] + +[[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 = "uritemplate" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/60/f174043244c5306c9988380d2cb10009f91563fc4b31293d27e17201af56/uritemplate-4.2.0.tar.gz", hash = "sha256:480c2ed180878955863323eea31b0ede668795de182617fef9c6ca09e6ec9d0e", size = 33267, upload-time = "2025-06-02T15:12:06.318Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, +] + +[[package]] +name = "urllib3" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/05/b17359e1cefb4f909b5e40b1b90a496d987258916dbbf88e842c729f510e/urllib3-2.8.0.tar.gz", hash = "sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63", size = 458972, upload-time = "2026-09-15T19:29:36.253Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/9d/c4e665119135114480843e7ab388fa94d8480650450e6f8e26b70d323a4c/urllib3-2.8.0-py3-none-any.whl", hash = "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", size = 135717, upload-time = "2026-09-15T19:29:34.577Z" }, +] + +[[package]] +name = "wasabi" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/f9/054e6e2f1071e963b5e746b48d1e3727470b2a490834d18ad92364929db3/wasabi-1.1.3.tar.gz", hash = "sha256:4bb3008f003809db0c3e28b4daf20906ea871a2bb43f9914197d540f4f2e0878", size = 30391, upload-time = "2024-05-31T16:56:18.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/7c/34330a89da55610daa5f245ddce5aab81244321101614751e7537f125133/wasabi-1.1.3-py3-none-any.whl", hash = "sha256:f76e16e8f7e79f8c4c8be49b4024ac725713ab10cd7f19350ad18a8e3f71728c", size = 27880, upload-time = "2024-05-31T16:56:16.699Z" }, +] + +[[package]] +name = "weasel" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpathlib" }, + { name = "confection" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "smart-open" }, + { name = "srsly" }, + { name = "typer" }, + { name = "wasabi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/e5/e272bb9a045105a1fdf4b798d8086f5932a178f4d738f17a74f5c9e0ae9a/weasel-1.0.0.tar.gz", hash = "sha256:7b129b44c90cc543b760532974ca1e4eb30dad2aa2026f57bdce66354ae610fc", size = 38682, upload-time = "2026-03-20T08:10:25.266Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/07/57ebf7a6798b016c064bd0ca81b4c6a99daa4dc377b898bc7b41eb6b5af0/weasel-1.0.0-py3-none-any.whl", hash = "sha256:89518acee027f49d743126c3502d35e6dd14f5768be5c37c9af47c171b6005cc", size = 50713, upload-time = "2026-03-20T08:10:23.637Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + +[[package]] +name = "wrapt" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/42/a6/6375d56c44d590ef24acf0f8f5bf7ed768ff7a510b959306ec412611e90f/wrapt-2.4.1.tar.gz", hash = "sha256:fd6390aab9e8aa40c52eff3c180f098e8d9f5894b1fd4c4fd2c207067b33ed16", size = 164597, upload-time = "2026-09-10T23:12:16.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/1f/a2f3225c5ecf522684c1d051aea8ce8253f240e55be75826b787777afd6c/wrapt-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7e86fbc2ac8a363ea04abf631fad82720e16b17a25020f32dbe9b24a2ed2b0e3", size = 98890, upload-time = "2026-09-10T23:10:07.876Z" }, + { url = "https://files.pythonhosted.org/packages/68/6c/eb45660fd4d92cce11ec923f55bb2e647a6c18d30e53734eb07a3c530e31/wrapt-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:24389748f0b9d5b67e478fad4fc8b3f1108422ef80716e48eead6cebcebbff08", size = 98742, upload-time = "2026-09-10T23:10:09.236Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4f/17a89a580cb0082e61b8375074d5c9e5d38e4aa83ee19b6174ee472d17c2/wrapt-2.4.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:30d11c289b013bf384ff1a1a6553f150d0b855901708a9bef667a5680f8247c9", size = 236301, upload-time = "2026-09-10T23:10:11.039Z" }, + { url = "https://files.pythonhosted.org/packages/01/ca/4700eb008a34bf02de328806ddde15fc84c8d1e65d3dcafb92a935a50319/wrapt-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9356dbb59199a0e4709de35fa4a1ac1a88ef6da99711a397f5b009233faff326", size = 237805, upload-time = "2026-09-10T23:10:12.594Z" }, + { url = "https://files.pythonhosted.org/packages/75/5d/26c1740299b29e190d5f4b4a99eb001401042a9d9ab338e3f8e1ce140ecb/wrapt-2.4.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8342f332dada211f64b74609e332d727b13315e9a83177f7918bf68c59f815f2", size = 217037, upload-time = "2026-09-10T23:10:14.028Z" }, + { url = "https://files.pythonhosted.org/packages/3a/55/ec72991153a2ae8b40238bc44cec7c3ddf7706ef6e2d314b0c6f5c7febce/wrapt-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2f86e328c482bc5383b4eda5094be0bed3617fc3076aa9225ff1a9eb6372de9b", size = 234659, upload-time = "2026-09-10T23:10:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/7cfa1e070dcda76ea56a3e252341cba1cd1e9412baf23cd7adfebf1114e2/wrapt-2.4.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4dc92697444ee380544fbb43c86612d8486529aadf917c22b5524141d4af074c", size = 214590, upload-time = "2026-09-10T23:10:16.744Z" }, + { url = "https://files.pythonhosted.org/packages/79/10/248841cb30107f6f32c53a02662e1c3e0c7c06bea0b8ebfaaee94885dcee/wrapt-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edd03758a7578526642508b8833d43496fdfba0f64e0025dfca153a7c1777735", size = 225103, upload-time = "2026-09-10T23:10:18.346Z" }, + { url = "https://files.pythonhosted.org/packages/90/02/5b2bf7b35b008a39939a2908e85dba3b867596e535fc9f12ddf3ba1fcaf6/wrapt-2.4.1-cp312-cp312-win32.whl", hash = "sha256:5d83e412665aeb1e854eefbf1564d0d67872d9994b502a0bce96e6ff7f4970b7", size = 93441, upload-time = "2026-09-10T23:10:19.81Z" }, + { url = "https://files.pythonhosted.org/packages/d4/2a/47be56772bfb07ef242d6e924049688e38384af2b2fc99b0f31180988448/wrapt-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:b4e7efdd476ac631a0181551fd9aace844765ea3ce2b5133b194fae4421e8ad0", size = 98808, upload-time = "2026-09-10T23:10:21.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ec/40e4c9735626afd1dc0eeb310310278361f192800503cda0f5b3d8d24db4/wrapt-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:38819761401baa2d11916d7265b82f23265f8fe5a31c431dd7c24a8863c65f88", size = 95240, upload-time = "2026-09-10T23:10:22.39Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a2/edcfc8d9a30375791b775715f8501364588f65b494ec4f6930568a19e765/wrapt-2.4.1-py3-none-any.whl", hash = "sha256:1e84ec5d89a0a07a0ef6bcd343f5c8ecdc95601d71de3058cdc63274e86c193c", size = 75317, upload-time = "2026-09-10T23:12:14.82Z" }, +]