From 090a45f6271ff1a2bab3b5ec5ee6a1a01621a77c Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Sun, 19 Jul 2026 17:20:25 -0700 Subject: [PATCH 1/7] sortformer stage 1 --- docs/porting/families/_intake-schema.json | 4 +- docs/porting/families/sortformer.md | 107 ++++++++ .../streaming-4spk-v2.1/intake.json | 235 ++++++++++++++++++ scripts/intake.py | 5 + .../streaming-4spk-v2.1.manifest.json | 12 + 5 files changed, 361 insertions(+), 2 deletions(-) create mode 100644 docs/porting/families/sortformer.md create mode 100644 reports/porting/sortformer/streaming-4spk-v2.1/intake.json create mode 100644 tests/golden/sortformer/streaming-4spk-v2.1.manifest.json diff --git a/docs/porting/families/_intake-schema.json b/docs/porting/families/_intake-schema.json index b0fe5da5..7d7d33ae 100644 --- a/docs/porting/families/_intake-schema.json +++ b/docs/porting/families/_intake-schema.json @@ -85,7 +85,7 @@ "type": "array", "items": { "type": "string", - "enum": ["encoder-transducer", "encoder-decoder", "audio-llm", "encoder-ctc"] + "enum": ["encoder-transducer", "encoder-decoder", "audio-llm", "encoder-ctc", "encoder-diarizer"] }, "description": "Heuristic matches against known patterns. Human selects one in architecture_pattern." }, @@ -288,7 +288,7 @@ }, "architecture_pattern": { "type": ["string", "null"], - "enum": ["encoder-transducer", "encoder-decoder", "audio-llm", "encoder-ctc", null] + "enum": ["encoder-transducer", "encoder-decoder", "audio-llm", "encoder-ctc", "encoder-diarizer", null] }, "known_risks": { "type": "array", diff --git a/docs/porting/families/sortformer.md b/docs/porting/families/sortformer.md new file mode 100644 index 00000000..b1167d68 --- /dev/null +++ b/docs/porting/families/sortformer.md @@ -0,0 +1,107 @@ +# Sortformer + +Status: research + +Intake signed off 2026-07-19: capability scope approved as drafted; +acceptance set = AMI (DER + JER vs NeMo reference, plus prob-tensor parity). + +Streaming Sortformer is a **frame-level end-to-end neural speaker diarizer** +(architecture pattern `encoder-diarizer`, new to this repo). It is NOT a +transcription model: it consumes 16 kHz mono audio and emits a `T x 4` +matrix of per-frame per-speaker activity probabilities (one row per 80 ms +frame, up to 4 speakers, columns in speaker arrival order). It runs online +with a stateful Arrival-Order Speaker Cache (AOSC) + FIFO. + +Primary reason this family is being ported: it is the named hard +dependency of the Parakeet multitalker speaker-attributed ASR path +(`spk_supervision='diar'`). See +`reports/porting/parakeet/multitalker-parakeet-streaming-0.6b-v1/intake.json`. + +## Identity + +- Family key: `sortformer` +- Upstream architecture string: `nemo.collections.asr.models.sortformer_diar_models.SortformerEncLabelModel` +- Architecture pattern: `encoder-diarizer` (NEST/FastConformer encoder -> 18L Transformer encoder -> 2 FF -> 4 sigmoid/frame) +- Hugging Face repo: `nvidia/diar_streaming_sortformer_4spk-v2.1` +- Hugging Face revision: `fafaab5faa1617a0ca52d38dd3dc4bd636800d3d` +- License: NVIDIA Open Model License (v2.1). Sibling `diar_streaming_sortformer_4spk-v2` is CC-BY-4.0. +- Variants: + - `streaming-4spk-v2.1` — this port (best meeting DER; multitalker's named dependency). + - `streaming-4spk-v2` — architecturally identical, CC-BY-4.0. Deferred; add by dropping in weights once v2.1 is validated. + +## References + +- Canonical reference: NeMo `SortformerEncLabelModel.from_pretrained(...)` + `sortformer_modules.SortformerModules` (streaming speaker cache). Same NeMo toolkit already pinned for parakeet/canary. +- Instrumented reference: `scripts/envs/sortformer/` (to be created at Stage 2, mirroring `scripts/envs/parakeet`). +- Cross-check references: + - Sortformer paper: https://arxiv.org/abs/2409.06656 + - Streaming Sortformer (AOSC): https://arxiv.org/abs/2507.18446 + - NEST encoder: https://arxiv.org/abs/2408.13106 + - NeMo eval script: `examples/speaker_tasks/diarization/neural_diarizer/e2e_diarize_speech.py` + +## Commands + +Reference run (NeMo): + +```bash +# TODO (Stage 2): pin scripts/envs/sortformer, then: +# uv run --project scripts/envs/sortformer python - <<'PY' +# from nemo.collections.asr.models import SortformerEncLabelModel +# m = SortformerEncLabelModel.from_pretrained("nvidia/diar_streaming_sortformer_4spk-v2.1") +# m.eval() +# segs, probs = m.diarize(audio=["samples/.wav"], batch_size=1, include_tensor_outputs=True) +# PY +``` + +Reference dumps: + +```bash +TODO # Stage 2: dump the T x 4 probability tensor + intermediate encoder activations for parity. +``` + +Conversion: + +```bash +TODO # Stage 3: scripts/convert-sortformer.py -> GGUF (encoder-diarizer arch). +``` + +Validation: + +```bash +TODO # Parity: raw T x 4 prob-tensor max-abs-diff vs NeMo reference (arrival-order columns; no Hungarian). +TODO # Acceptance: DER + JER vs the reference model on the same manifest (proposed set: AMI, freely downloadable). +``` + +Benchmarks: + +```bash +TODO +``` + +## Capability Validation + +> NOTE (non-standard family): the template's forced `MUST PASS` rows are +> transcription rows (explicit-language / auto transcribe). Sortformer +> emits no transcript, so those rows are `OUT OF SCOPE — not a +> transcription model`. The forced-row spirit is preserved by making the +> **diarization** capability the obligated one: streaming diarization, +> offline (single-chunk) diarization, and the raw activity-tensor output +> are the `MUST PASS` rows for this family. These substitutions require +> user sign-off at intake. + +| Capability | Mode | Command / test | Expected observable | Target | Status | +|------------|------|----------------|---------------------|--------|--------| +| Streaming diarization (<=4 spk) | online, AOSC/FIFO cache | port streaming diarize vs NeMo reference at a fixed preset (e.g. 1.04s latency) | prob-tensor parity vs reference; DER + JER within tolerance on AMI | MUST PASS | TODO | +| Offline diarization | single large chunk | port diarize vs NeMo reference | prob-tensor parity vs reference | MUST PASS | TODO | +| Speaker-activity tensor | `include_tensor_outputs` | T x 4 sigmoid probs, port vs reference on the same audio | max-abs-diff within tolerance, columns arrival-order aligned | MUST PASS | TODO | +| Multitalker interop | feed diar supervision | Sortformer T x 4 drives parakeet multitalker layer-0 speaker kernel | multitalker emits speaker-attributed transcript | OUT OF SCOPE — owned by the multitalker port; unblocked once both land | TODO | +| Transcription (text) | n/a | n/a | model produces no text output | OUT OF SCOPE — not a transcription model | TODO | +| Translation | n/a | n/a | n/a | OUT OF SCOPE — not a transcription model | TODO | +| Timestamps (transcription) | n/a | n/a | diarization segment times are intrinsic, not a transcript-timestamp capability | OUT OF SCOPE — not a transcription model | TODO | +| >4 speakers | n/a | n/a | hard cap at 4 speakers | OUT OF SCOPE — architectural 4-speaker cap | TODO | + +## Notes + +- Frontend `normalize=NA` (no per-feature normalization); 128 mels, n_fft 512, win 400 / hop 160, hann, dither 1e-5. `preemphasis` is not set in `model_config.yaml` (NeMo default 0.97 assumed) and MUST be confirmed against the Stage 2 Oracle dump; the sibling streaming-parakeet models disable it. +- Output post-processing (probs -> RTTM segments) is dataset-tuned upstream. Port gate is prob-tensor parity; DER/JER use a fixed/documented post-processing and are compared to the reference model, not to the published numbers. +- Streaming operating-point knobs: `chunk_len`, `chunk_right_context`, `fifo_len`, `spkcache_update_period`, `spkcache_len` (all in 80 ms frames). Published DER corresponds to specific presets (1.04s vs 30.4s input-buffer latency). diff --git a/reports/porting/sortformer/streaming-4spk-v2.1/intake.json b/reports/porting/sortformer/streaming-4spk-v2.1/intake.json new file mode 100644 index 00000000..10dd2b0d --- /dev/null +++ b/reports/porting/sortformer/streaming-4spk-v2.1/intake.json @@ -0,0 +1,235 @@ +{ + "schema_version": "transcribe-intake-v1", + "family": "sortformer", + "hf_repo": "nvidia/diar_streaming_sortformer_4spk-v2.1", + "hf_revision": "fafaab5faa1617a0ca52d38dd3dc4bd636800d3d", + "sources": { + "config": { + "kind": "hf_file", + "path": "config.json", + "status": "missing", + "detail": "NeMo .nemo archive only; no HF config.json. Architecture from model_config.yaml inside diar_streaming_sortformer_4spk-v2.1.nemo (nemo_version 2.6.0rc0)." + }, + "preprocessor": { + "kind": "hf_file", + "path": "preprocessor_config.json", + "status": "missing", + "detail": "Inside .nemo model_config.yaml: AudioToMelSpectrogramPreprocessor, 16kHz, features=128, n_fft=512, window_size=0.025, window_stride=0.01, window=hann, normalize=NA, dither=1e-5." + }, + "tokenizer_config": { + "kind": "hf_file", + "path": "tokenizer_config.json", + "status": "missing", + "detail": "No tokenizer. This is a diarizer: output is a T x 4 speaker-activity probability matrix, not text. No vocabulary of any kind." + }, + "tokenizer_json": { + "kind": "hf_file", + "path": "tokenizer.json", + "status": "missing", + "detail": "N/A for a diarizer." + }, + "generation_config": { + "kind": "hf_file", + "path": "generation_config.json", + "status": "missing", + "detail": "N/A; no autoregressive decoding." + }, + "safetensors_metadata": { + "kind": "hf_api", + "path": "HfApi.get_safetensors_metadata", + "status": "missing", + "detail": "PyTorch state_dict (model_weights.ckpt) inside the .nemo tar; not safetensors." + }, + "model_card": { + "kind": "hf_api", + "path": "https://huggingface.co/nvidia/diar_streaming_sortformer_4spk-v2.1", + "status": "found", + "detail": "Streaming Sortformer 4-spk v2.1, 117M params, DER published per dataset at 1.04s and 30.4s input-buffer latency." + }, + "nemo_model_config": { + "kind": "reference_code", + "path": "diar_streaming_sortformer_4spk-v2.1.nemo::model_config.yaml", + "status": "found", + "detail": "target: nemo.collections.asr.models.sortformer_diar_models.SortformerEncLabelModel. Extracted via HTTP range read of the tar head (config is the first member)." + }, + "reference_modeling_code": { + "kind": "reference_code", + "path": "nemo.collections.asr.models.SortformerEncLabelModel + nemo.collections.asr.modules.sortformer_modules.SortformerModules", + "status": "found", + "detail": "Same NeMo toolkit already pinned for parakeet/canary. diarize() and the streaming speaker-cache (AOSC/FIFO) logic live in sortformer_modules." + } + }, + "variants": [ + { + "name": "streaming-4spk-v2.1", + "memory_gb": 2, + "files": ["diar_streaming_sortformer_4spk-v2.1.nemo"] + } + ], + "config": { + "architecture_candidates": ["encoder-diarizer"], + "key_fields": { + "target": "nemo.collections.asr.models.sortformer_diar_models.SortformerEncLabelModel", + "model_type": "sortformer_enc_label", + "max_num_of_spks": 4, + "streaming_mode": true, + "frame_period_seconds": 0.08, + "output_shape": "T x 4 sigmoid speaker-activity probabilities (arrival-order columns)", + "encoder.type": "ConformerEncoder (NEST / FastConformer-Large)", + "encoder.n_layers": 17, + "encoder.d_model": 512, + "encoder.n_heads": 8, + "encoder.ff_expansion_factor": 4, + "encoder.conv_kernel_size": 9, + "encoder.conv_norm_type": "batch_norm", + "encoder.self_attention_model": "rel_pos", + "encoder.subsampling": "dw_striding", + "encoder.subsampling_factor": 8, + "encoder.subsampling_conv_channels": 256, + "encoder.xscaling": true, + "transformer_encoder.num_layers": 18, + "transformer_encoder.hidden_size": 192, + "transformer_encoder.inner_size": 768, + "transformer_encoder.num_attention_heads": 8, + "transformer_encoder.hidden_act": "relu", + "transformer_encoder.pre_ln": false, + "head": "2 feedforward layers -> 4 sigmoid outputs per frame", + "loss": "BCELoss (train-time; sort-based PIL/ATS objective, pil_weight=0.5 ats_weight=0.5)", + "sortformer_modules.spkcache_len": 188, + "sortformer_modules.fifo_len": 0, + "sortformer_modules.chunk_len": 188, + "sortformer_modules.spkcache_update_period": 188 + }, + "varying_across_variants": [] + }, + "dtype": { + "expected": "float32", + "source": "manual", + "evidence": "NeMo .nemo PyTorch state_dict; NeMo diarization models run and store at float32. No config torch_dtype and no safetensors header. Confirm the state_dict dtype at Stage 3 convert.", + "details": { + "config_declared": null, + "header_distribution": {} + }, + "expected_f32_tensors": [] + }, + "frontend": { + "sample_rate": 16000, + "n_mels": 128, + "hop_length": 160, + "fft_size": 512, + "window": "hann_periodic", + "normalization": "none", + "preemphasis": 0.97, + "dither": 1e-05, + "center": true, + "padding_mode": "reflect", + "mel_filterbank_norm": "slaney" + }, + "tokenizer": { + "type": "other", + "vocab_size": 0, + "special_tokens": {}, + "has_language_tokens": false, + "vocab_sha256": null + }, + "capabilities": { + "languages": ["en"], + "language_detection": false, + "translation": false, + "timestamps": [], + "streaming": true, + "speaker_diarization": true + }, + "upstream_benchmarks": [ + { + "dataset": "AMI Test IHM (forced-alignment RTTM)", + "language": "en", + "metric": "other", + "score": 16.67, + "score_unit": "percent DER", + "source": "model card", + "notes": "Metric is DER (lower is better), collar 0.0s with overlap, 1.04s input-buffer latency. FREELY DOWNLOADABLE. CONFIRMED acceptance set at intake sign-off (parity + DER + JER vs the NeMo reference). 30.4s-latency DER is 15.90." + }, + { + "dataset": "AMI Test SDM (forced-alignment RTTM)", + "language": "en", + "metric": "other", + "score": 20.57, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0s, 1.04s latency. Freely downloadable single-distant-mic condition. 30.4s-latency DER is 17.80." + }, + { + "dataset": "DIHARD III Eval full (1-9 spk)", + "language": "en", + "metric": "other", + "score": 20.21, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0s, 1.04s latency. Context only: DIHARD III is LDC-gated, not freely reproducible here. Uses dataset-tuned post-processing." + }, + { + "dataset": "CALLHOME-part2 full (2-6 spk)", + "language": "en", + "metric": "other", + "score": 11.19, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.25s, 1.04s latency. Context only: NIST-SRE-2000 Disc8 is LDC-gated." + }, + { + "dataset": "CH109 (CALLHOME American English, 2 spk)", + "language": "en", + "metric": "other", + "score": 5.09, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.25s, 1.04s latency. Context only: LDC-gated." + }, + { + "dataset": "AliMeeting Test near (forced-alignment RTTM)", + "language": "zh", + "metric": "other", + "score": 12.6, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0s, 1.04s latency. Mandarin meeting corpus; context only (model is English-primary)." + }, + { + "dataset": "NOTSOFAR1 Eval SC full (3-7 spk)", + "language": "en", + "metric": "other", + "score": 28.75, + "score_unit": "percent DER", + "source": "model card", + "notes": "DER, collar 0.0s, 1.04s latency. Meeting corpus; v2.1 improved markedly over v2 here (34.52 -> 28.75)." + } + ], + "reference_framework": "nemo", + "reference_rationale": "The model ships only as a NeMo .nemo checkpoint and loads exclusively via nemo.collections.asr.models.SortformerEncLabelModel (from_pretrained / restore_from). There is no HF transformers modeling class, no config.json, no trust_remote_code path. The forward pass, the AudioToMelSpectrogramPreprocessor frontend, and the streaming speaker-cache logic (AOSC/FIFO, arrival-order permutation) all live in NeMo (sortformer_diar_models.py + sortformer_modules.py). NeMo is therefore the sole source of truth, and it is the same toolkit already pinned for the parakeet and canary ports (scripts/envs/parakeet, scripts/envs/canary).", + "architecture_pattern": "encoder-diarizer", + "known_risks": [ + "NEW ARCHITECTURE FAMILY for the runtime. Sortformer is a frame-level end-to-end neural diarizer: NEST/FastConformer encoder -> 18-layer Transformer encoder -> 2 feedforward layers -> 4 sigmoid outputs per 80ms frame. Output is a T x 4 speaker-activity probability matrix, NOT text. transcribe.cpp has no such forward path today: the existing diarization support (moss, granite) is host-side text parsing of an audio-LLM's emergent [Sxx] speaker tags, a completely different mechanism. This port needs a genuinely new arch scaffold (encoder-diarizer): encoder + per-frame multi-label classification head, no decoder, no tokenizer, no generation loop.", + "STREAMING-ONLY STATEFUL INFERENCE is the core novelty and the largest lift. The model runs online with an Arrival-Order Speaker Cache (AOSC) plus a FIFO queue plus a speaker cache, all measured in 80ms frames and driven by chunk_len / chunk_right_context / fifo_len / spkcache_update_period / spkcache_len (sortformer_modules.SortformerModules). Offline is just streaming with one large chunk. Porting requires reproducing the speaker-cache update/eviction and arrival-order permutation exactly; these carry state across chunks and are where numerical parity is hardest.", + "MULTITALKER INTEROP is an explicit integration goal (not standalone-only). The parakeet multitalker port (reports/porting/parakeet/multitalker-parakeet-streaming-0.6b-v1) names nvidia/diar_streaming_sortformer_4spk-v2.1 as its HARD external dependency: multitalker's layer-0 speaker kernel consumes per-frame per-speaker speech-activity supervision (spk_supervision='diar'), which is exactly Sortformer's T x 4 output. Both sides run the same frame grid (10ms hop x8 subsampling = 80ms) and the same normalize=NA frontend. The diarization output/interface must be designed to feed the multitalker speaker-kernel (arrival-order speaker indices, 80ms cadence, streaming state alignment), not just to emit standalone RTTM.", + "MIXED-NORM TWO-ENCODER STACK. NEST/FastConformer encoder (ConformerEncoder, 17 layers, d=512, 8 heads, rel_pos with untied biases, xscaling, dw_striding x8, subsampling_conv_channels=256, conv kernel 9, conv_norm_type=batch_norm) feeds an 18-layer Transformer encoder (d=192, inner=768, 8 heads, ReLU, pre_ln=false i.e. post-LN, pre_ln_final_layer_norm=true). Conformer batch-norm convs vs Transformer LayerNorm, and the post-LN Transformer, must each be wired correctly; the d=512 -> d=192 handoff between the two stacks is a specific integration point.", + "FRONTEND normalize=NA (no per-feature normalization), unlike non-streaming parakeet (per_feature). 128 mels, n_fft=512, win_length=400 (25ms), hop=160 (10ms), hann, dither=1e-5. preemphasis is NOT set in model_config.yaml so the NeMo default (0.97) is assumed here, but the sibling streaming-parakeet models disable preemphasis; the true value MUST be confirmed against the Stage 2 Oracle dump. A silent frontend mismatch degrades DER without any shape error.", + "OUTPUT IS PROBABILITIES, NOT SEGMENTS. DER/JER require post-processing (thresholding plus optional onset/offset + median filtering) to turn the T x 4 probs into RTTM speaker segments. NVIDIA's published DER uses per-dataset tuned post-processing YAMLs. The porting parity gate must therefore be raw prob-tensor parity vs the NeMo reference (arrival-order columns are directly comparable, no Hungarian needed); DER and JER are computed with a fixed/documented post-processing and compared to the reference model on the SAME audio+manifest, not to the published numbers.", + "ACCEPTANCE DATA. LibriSpeech test-clean is single-speaker and degenerate for diarization, so it is NOT a valid acceptance set here. Most published eval corpora (CALLHOME, DIHARD III, CH109, NIST-SRE) are LDC-gated and not freely reproducible. AMI and VoxConverse are freely downloadable; AMI (NVIDIA publishes DER on it) is the proposed reproducible acceptance set. Confirm final acceptance set at sign-off.", + "OPERATING-POINT KNOBS. Hard cap of 4 speakers (>4 degrades sharply). The streaming knobs (chunk_len, chunk_right_context, fifo_len, spkcache_update_period, spkcache_len) trade latency for accuracy; the published numbers correspond to specific presets (e.g. 1.04s vs 30.4s input-buffer latency). These must be exposed/settable in the runtime to reproduce a published operating point and to match whichever preset the multitalker path expects." + ], + "intake_gaps": [ + { + "field": "frontend.preemphasis", + "reason": "Not present in model_config.yaml; recorded as the NeMo default 0.97 but the sibling streaming-parakeet models disable preemphasis. Confirm the actual value against the Stage 2 Oracle dump before convert (info-only for Gate A, but load-bearing for DER)." + }, + { + "field": "upstream_benchmarks.acceptance_dataset", + "reason": "RESOLVED at sign-off: acceptance set is AMI (freely downloadable, published DER), verified as prob-tensor parity + DER + JER vs the NeMo reference on the same manifest. Not LibriSpeech (single-speaker => degenerate for diarization)." + }, + { + "field": "capabilities.timestamps", + "reason": "Left empty: diarization output is intrinsically timed speaker segments, but the transcription-timestamp capability (none/segment/word/token) does not apply to a model that emits no transcript." + } + ] +} diff --git a/scripts/intake.py b/scripts/intake.py index 637b6bca..4cce1534 100755 --- a/scripts/intake.py +++ b/scripts/intake.py @@ -84,6 +84,11 @@ def _guess_architecture(config: dict) -> list[str]: candidates.append("encoder-decoder") if any(t in blob for t in ["audiolm", "audio_llm", "qwen2audio", "qwen3asr"]): candidates.append("audio-llm") + if any(t in blob for t in ["sortformer", "diariz", "eend", "enclabel"]): + # Frame-level end-to-end neural diarizers: encoder + per-frame + # multi-label sigmoid over a fixed max-speaker count (Sortformer, + # EEND/EEND-EDA, pyannote segmentation). Not a transcription head. + candidates.append("encoder-diarizer") return candidates or ["encoder-decoder"] # default guess; human confirms diff --git a/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json b/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json new file mode 100644 index 00000000..deedcf5e --- /dev/null +++ b/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json @@ -0,0 +1,12 @@ +{ + "schema": "transcribe-golden-manifest-v1", + "family": "sortformer", + "variant": "streaming-4spk-v2.1", + "source_model": { + "hf_repo": "nvidia/diar_streaming_sortformer_4spk-v2.1", + "hf_revision": "fafaab5faa1617a0ca52d38dd3dc4bd636800d3d" + }, + "expected_dtype": "float32", + "dtype_source": "manual", + "_skeleton": true +} From 1ec6a18b047f47e0d19ec073a2b2a6fbe8fe2b5b Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Tue, 21 Jul 2026 04:26:05 -0700 Subject: [PATCH 2/7] stage 2-4 --- .gitignore | 4 + docs/extension-kinds.md | 1 + docs/porting/families/sortformer.md | 148 +- include/transcribe/extensions.h | 1 + include/transcribe/sortformer.h | 81 + reports/porting/sortformer/forward-map.md | 138 + .../streaming-4spk-v2.1/intake.json | 4 +- samples/sortformer-2spk-mix.wav | Bin 0 -> 384044 bytes scripts/convert-sortformer.py | 316 ++ scripts/diar/compare_compress_spkcache.py | 238 ++ scripts/diar/fetch_ami_forced_alignment.py | 73 + scripts/diar/ingest_ami.py | 127 + ...ing_sortformer_4spk-v2_callhome-part1.yaml | 11 + ...eaming_sortformer_4spk-v2_dihard3-dev.yaml | 11 + scripts/diar/run_cpp_sortformer.py | 171 + scripts/diar/run_reference_sortformer_nemo.py | 130 + scripts/diar/score_der.py | 130 + scripts/dump_reference_sortformer_nemo.py | 299 ++ scripts/envs/sortformer/pyproject.toml | 27 + scripts/envs/sortformer/uv.lock | 3187 +++++++++++++++++ scripts/gen_sortformer_oracle_audio.py | 112 + scripts/lib/test_quant_policy_sync.py | 7 + scripts/validate.py | 39 +- src/CMakeLists.txt | 4 + src/arch/sortformer/capabilities.cpp | 35 + src/arch/sortformer/model.cpp | 986 +++++ src/arch/sortformer/sortformer.h | 169 + src/arch/sortformer/stream.cpp | 467 +++ src/arch/sortformer/weights.cpp | 193 + src/arch/sortformer/weights.h | 119 + src/transcribe-arch.cpp | 6 +- src/transcribe-mel.cpp | 13 +- src/transcribe-mel.h | 8 + tests/CMakeLists.txt | 20 + .../sortformer/sortformer-2spk-mix.rttm | 4 + .../streaming-4spk-v2.1.manifest.json | 45 +- tests/sortformer_stream_ext_unit.cpp | 191 + tests/tolerances/sortformer.json | 35 + 38 files changed, 7520 insertions(+), 30 deletions(-) create mode 100644 include/transcribe/sortformer.h create mode 100644 reports/porting/sortformer/forward-map.md create mode 100644 samples/sortformer-2spk-mix.wav create mode 100644 scripts/convert-sortformer.py create mode 100644 scripts/diar/compare_compress_spkcache.py create mode 100644 scripts/diar/fetch_ami_forced_alignment.py create mode 100644 scripts/diar/ingest_ami.py create mode 100644 scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_callhome-part1.yaml create mode 100644 scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml create mode 100644 scripts/diar/run_cpp_sortformer.py create mode 100644 scripts/diar/run_reference_sortformer_nemo.py create mode 100644 scripts/diar/score_der.py create mode 100644 scripts/dump_reference_sortformer_nemo.py create mode 100644 scripts/envs/sortformer/pyproject.toml create mode 100644 scripts/envs/sortformer/uv.lock create mode 100644 scripts/gen_sortformer_oracle_audio.py create mode 100644 src/arch/sortformer/capabilities.cpp create mode 100644 src/arch/sortformer/model.cpp create mode 100644 src/arch/sortformer/sortformer.h create mode 100644 src/arch/sortformer/stream.cpp create mode 100644 src/arch/sortformer/weights.cpp create mode 100644 src/arch/sortformer/weights.h create mode 100644 tests/golden/sortformer/sortformer-2spk-mix.rttm create mode 100644 tests/sortformer_stream_ext_unit.cpp create mode 100644 tests/tolerances/sortformer.json diff --git a/.gitignore b/.gitignore index f7cf944a..3c60cc0c 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,10 @@ __pycache__/ # WER evaluation data + generated working reports. /samples/wer/ +# Diarization eval corpora (AMI audio + fetched RTTMs); regenerable via +# scripts/diar/ingest_ami.py + fetch_ami_forced_alignment.py. The committed +# oracle case lives at samples/sortformer-2spk-mix.wav (outside samples/diar/). +/samples/diar/ /reports/* # Reviewable porting evidence (intake, porting log) is intentionally committed. diff --git a/docs/extension-kinds.md b/docs/extension-kinds.md index 2d5eee84..cb8dd94d 100644 --- a/docs/extension-kinds.md +++ b/docs/extension-kinds.md @@ -47,6 +47,7 @@ parameter; existing kinds keep their slot for life. | `0x5453534D` | `MSST` | `STREAM` | `TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM` | moonshine_streaming | `include/transcribe/moonshine_streaming.h` | | `0x4E524857` | `WHRN` | `RUN` | `TRANSCRIBE_EXT_KIND_WHISPER_RUN` | whisper | `include/transcribe/whisper.h` | | `0x54535256` | `VRST` | `STREAM` | `TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM` | voxtral_realtime | `include/transcribe/voxtral_realtime.h` | +| `0x54534653` | `SFST` | `RUN` | `TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM` | sortformer | `include/transcribe/sortformer.h` | Empty rows reserved for future allocations: diff --git a/docs/porting/families/sortformer.md b/docs/porting/families/sortformer.md index b1167d68..4693b307 100644 --- a/docs/porting/families/sortformer.md +++ b/docs/porting/families/sortformer.md @@ -1,9 +1,15 @@ # Sortformer -Status: research +Status: Stage 4 complete (offline + streaming) Intake signed off 2026-07-19: capability scope approved as drafted; acceptance set = AMI (DER + JER vs NeMo reference, plus prob-tensor parity). +Stages 1-4 done. Stage 4 offline checkpoint (tensor parity, 5/5) and streaming +checkpoint (AOSC/FIFO + `_compress_spkcache`, `diar.probs` parity, AMI DER) +both green. **C++ AMI DER 14.59% / JER 19.51%** (16 mtgs, very_high_latency, +forced-alignment RTTMs + dihard3 PP) reproduces the measured reference +14.83% / 19.89% within 0.24 pt (per-meeting median |Δ| ~0.02 pt). Next: Stage 5 +quants. Streaming Sortformer is a **frame-level end-to-end neural speaker diarizer** (architecture pattern `encoder-diarizer`, new to this repo). It is NOT a @@ -29,6 +35,61 @@ dependency of the Parakeet multitalker speaker-attributed ASR path - `streaming-4spk-v2.1` — this port (best meeting DER; multitalker's named dependency). - `streaming-4spk-v2` — architecturally identical, CC-BY-4.0. Deferred; add by dropping in weights once v2.1 is validated. +### Diarization scope policy + +Sortformer is the repo's first diarization-only family. transcribe.cpp +remains a transcription library; diarization-only models are in scope only +when they (a) feed a transcription pipeline in-repo (here: the named +`spk_supervision='diar'` dependency of parakeet multitalker speaker- +attributed ASR), or (b) reuse an encoder family the repo already maintains +(here: the NEST FastConformer is parakeet's ConformerEncoder, reused +verbatim). A diarizer meeting neither clause (e.g. a pyannote +segmentation+clustering port: new architecture, new dependency surface, no +in-repo ASR consumer) is out of scope. Standalone diarization output is +exposed because it falls out of the multitalker dependency for free, via +the pre-existing transcript-independent `transcribe_speaker_segment` ABI — +no diarizer-specific output surface was added. + +## Public API (run extension) + +`include/transcribe/sortformer.h` — `TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM` +(`SFST`, RUN slot; registered in `docs/extension-kinds.md`). A run produces +no text; results are read via `transcribe_n_speaker_segments` / +`transcribe_get_speaker_segment` (`TRANSCRIBE_FEATURE_DIARIZATION`). + +```c +transcribe_sortformer_stream_ext ext; +transcribe_sortformer_stream_ext_init(&ext); /* preset = DEFAULT (GGUF cfg) */ +ext.preset = TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY; +run_params.family = &ext.ext; /* probe accepts_ext_kind first */ +``` + +Preset menu (frames are 80 ms; chunk/rc/fifo/update/spkcache geometry per +`k_presets` in `src/arch/sortformer/stream.cpp`, mirroring the upstream +published operating points): + +| Preset | Lookahead | Geometry (chunk/rc/fifo/update/cache) | DER (AMI ihm test, FA + dihard3 PP) | Throughput (M4 CPU) | +| --- | --- | --- | --- | --- | +| `VERY_HIGH_LATENCY` | ~30.4 s | 340/40/40/300/188 | 14.59% (full 16 mtgs; REF 14.83%) | ~25-31x realtime | +| `HIGH_LATENCY` | ~10.0 s | 124/1/124/124/188 | not separately gated | — | +| `LOW_LATENCY` | ~1.04 s | 6/7/188/144/188 | 14.80% (3-mtg subset; REF 14.81%) | ~0.85x realtime (many small windows) | +| `DEFAULT` | GGUF cfg | checkpoint values | — | — | + +The menu is discrete (jointly-tuned bundles), not a latency dial; only +these bundles are accuracy-validated. Precedence: GGUF cfg < ext preset < +`TRANSCRIBE_SORTFORMER_STREAM_PRESET` env < per-field env (env layers are +validation hooks; `small` is a diagnostic-only env preset, deliberately +not in the public enum). An out-of-range preset or wrong-kind ext is +rejected pre-clear (`run_validate`), preserving the previous result. +Unit test: `tests/sortformer_stream_ext_unit.cpp` +(`TRANSCRIBE_SORTFORMER_GGUF`-gated). + +A future push-audio entry point (`transcribe_stream_begin/feed`, live +diarization) registers a separate STREAM-slot kind taking the same preset +enum; the multitalker interop path (raw T x 4 supervision into parakeet's +layer-0 speaker kernel) is internal C++ and does not round-trip through +this surface. + ## References - Canonical reference: NeMo `SortformerEncLabelModel.from_pretrained(...)` + `sortformer_modules.SortformerModules` (streaming speaker cache). Same NeMo toolkit already pinned for parakeet/canary. @@ -41,35 +102,58 @@ dependency of the Parakeet multitalker speaker-attributed ASR path ## Commands -Reference run (NeMo): +Oracle audio (deterministic 2-speaker mix, committed): ```bash -# TODO (Stage 2): pin scripts/envs/sortformer, then: -# uv run --project scripts/envs/sortformer python - <<'PY' -# from nemo.collections.asr.models import SortformerEncLabelModel -# m = SortformerEncLabelModel.from_pretrained("nvidia/diar_streaming_sortformer_4spk-v2.1") -# m.eval() -# segs, probs = m.diarize(audio=["samples/.wav"], batch_size=1, include_tensor_outputs=True) -# PY +uv run scripts/gen_sortformer_oracle_audio.py +# -> samples/sortformer-2spk-mix.wav + tests/golden/sortformer/sortformer-2spk-mix.rttm ``` -Reference dumps: +Reference dumps (NeMo, via scripts/envs/sortformer): ```bash -TODO # Stage 2: dump the T x 4 probability tensor + intermediate encoder activations for parity. +BASE=build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix +uv run --project scripts/envs/sortformer scripts/dump_reference_sortformer_nemo.py encoder \ + --model nvidia/diar_streaming_sortformer_4spk-v2.1 --audio samples/sortformer-2spk-mix.wav --out $BASE/encoder/ref +uv run --project scripts/envs/sortformer scripts/dump_reference_sortformer_nemo.py diarize \ + --model nvidia/diar_streaming_sortformer_4spk-v2.1 --audio samples/sortformer-2spk-mix.wav --out $BASE/diarize/ref ``` Conversion: ```bash -TODO # Stage 3: scripts/convert-sortformer.py -> GGUF (encoder-diarizer arch). +scripts/convert-sortformer.py -> models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf (arch `sortformer`). ``` Validation: ```bash -TODO # Parity: raw T x 4 prob-tensor max-abs-diff vs NeMo reference (arrival-order columns; no Hungarian). -TODO # Acceptance: DER + JER vs the reference model on the same manifest (proposed set: AMI, freely downloadable). +# Tensor parity (offline enc.* + diar.preds_offline + streaming diar.probs): +uv run scripts/validate.py all --family sortformer --backend cpu # 6/6 green (single-chunk diar.probs) +VALIDATE_SORTFORMER_PRESET=small uv run scripts/validate.py all --family sortformer # multi-chunk FIFO+compress diagnostic + +# Acceptance (DER + JER, official protocol: forced-alignment RTTMs + dihard3 PP). +uv run scripts/diar/ingest_ami.py --config ihm --split test # audio + manual RTTMs +uv run scripts/diar/fetch_ami_forced_alignment.py --config ihm --split test # forced-alignment RTTMs + FA manifest +# Reference baseline (one-time): +uv run --project scripts/envs/sortformer scripts/diar/run_reference_sortformer_nemo.py \ + --manifest samples/diar/ami-ihm-test.manifest.jsonl \ + --model nvidia/diar_streaming_sortformer_4spk-v2.1 --preset very_high_latency \ + --postprocessing-yaml scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml \ + --pred-dir reports/diar/pred/streaming-4spk-v2.1-ami-ihm-test-PPdihard \ + --out reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test.PPdihard.jsonl +# C++ port (dumps diar.probs per meeting, applies the SAME dihard3 PP): +uv run --project scripts/envs/sortformer scripts/diar/run_cpp_sortformer.py \ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ + --gguf models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf --preset very_high_latency \ + --postprocessing-yaml scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml \ + --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test.jsonl --backend cpu +uv run scripts/diar/score_der.py \ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ + --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test-fa.PPdihard.score.json +# -> REF DER 14.83% / JER 19.89% ; C++ DER 14.59% / JER 19.51% (within 0.24 pt). ``` Benchmarks: @@ -91,17 +175,45 @@ TODO | Capability | Mode | Command / test | Expected observable | Target | Status | |------------|------|----------------|---------------------|--------|--------| -| Streaming diarization (<=4 spk) | online, AOSC/FIFO cache | port streaming diarize vs NeMo reference at a fixed preset (e.g. 1.04s latency) | prob-tensor parity vs reference; DER + JER within tolerance on AMI | MUST PASS | TODO | -| Offline diarization | single large chunk | port diarize vs NeMo reference | prob-tensor parity vs reference | MUST PASS | TODO | -| Speaker-activity tensor | `include_tensor_outputs` | T x 4 sigmoid probs, port vs reference on the same audio | max-abs-diff within tolerance, columns arrival-order aligned | MUST PASS | TODO | +| Streaming diarization (<=4 spk) | online, AOSC/FIFO cache | port streaming diarize vs NeMo reference at a fixed preset (e.g. 1.04s latency) | prob-tensor parity vs reference; DER + JER within tolerance on AMI | MUST PASS | PASS — `diar.probs` parity (single-chunk 2.96e-4; multi-chunk `small` preset ~3.9e-4 uniform); AMI DER 14.59% / JER 19.51% vs ref 14.83% / 19.89% | +| Offline diarization | single large chunk | port diarize vs NeMo reference | prob-tensor parity vs reference | MUST PASS | PASS — `diar.preds_offline` 2.96e-4 (validate.py all 6/6) | +| Speaker-activity tensor | `include_tensor_outputs` | T x 4 sigmoid probs, port vs reference on the same audio | max-abs-diff within tolerance, columns arrival-order aligned | MUST PASS | PASS — `diar.probs` [150x4] within tolerance | | Multitalker interop | feed diar supervision | Sortformer T x 4 drives parakeet multitalker layer-0 speaker kernel | multitalker emits speaker-attributed transcript | OUT OF SCOPE — owned by the multitalker port; unblocked once both land | TODO | | Transcription (text) | n/a | n/a | model produces no text output | OUT OF SCOPE — not a transcription model | TODO | | Translation | n/a | n/a | n/a | OUT OF SCOPE — not a transcription model | TODO | | Timestamps (transcription) | n/a | n/a | diarization segment times are intrinsic, not a transcript-timestamp capability | OUT OF SCOPE — not a transcription model | TODO | | >4 speakers | n/a | n/a | hard cap at 4 speakers | OUT OF SCOPE — architectural 4-speaker cap | TODO | +## Reference baselines (Stage 2 Oracle) + +- **Tensor parity oracle** (`build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix/`): mel + fastconformer + encoder_proj + transformer + offline preds + streaming `diar.probs` [150×4]. Offline preds == streaming probs on this short clip (single chunk). Tolerances in `tests/tolerances/sortformer.json` (provisional). +- **Measured reference DER/JER** on AMI ihm test (16 meetings, 9.06 h), preset `very_high_latency`, collar 0.0s / overlap included (NVIDIA AMI protocol). The reference pipeline REPRODUCES the published number (15.90 @ very_high_latency AMI-IHM). Full 2x2 decomposition of the two protocol levers: + + | predictions | vs manual RTTMs (diarizers-community/ami) | vs forced-alignment RTTMs (nttcslab-sp) | + |---|---|---| + | no post-processing | 29.44% | 15.96% | + | + dihard3 post-processing | 27.95% | **14.83%** | + + - **Official gate = forced-alignment RTTMs + dihard3 PP: DER 14.83%, JER 19.89%** (missed 5.89%, FA 5.60%, confusion 3.34%). File: `reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test-fa.PPdihard.score.json`. Stage 4 / Stage 7 apply the SAME PP to the C++ probs and score vs the SAME forced-alignment RTTMs, gated against this number. + - The RTTM source is ~13 pts of the published-vs-manual gap; post-processing another ~1 pt. Confusion stays ~3% throughout, so the diarization core is correct; the manual-RTTM inflation was recall against dense annotations (within-utterance pauses), exactly as NVIDIA's forced-alignment `only_words` labels avoid. + - Ground truth: nttcslab-sp/diar-forced-alignment (ASRU 2025, card ref [7]), `only_words`, full-corpus-ASR partition — the labels NVIDIA used. PP: NeMo v2 `dihard3-dev` config (no AMI-specific or v2.1 PP is published; dihard3 is the meeting-domain proxy). + - Diagnostic/context files also kept: `...ami-ihm-test.score.json` (no-PP/manual 29.44%), `...ami-ihm-test-fa.noPP.score.json` (no-PP/FA 15.96%). + +## Conversion (Stage 3) + +`scripts/convert-sortformer.py` -> `models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf` (F32, 471 MB, arch string `sortformer`). 971 tensors emitted; 19 skipped (2 `preprocessor.*` recomputed in the C++ frontend, 17 BN `num_batches_tracked` counters). Gate B all-PASS. The GGUF tensor-name contract Stage 4's `src/arch/sortformer/weights.cpp` must implement: + +- `enc.pre_encode.*` — dw_striding subsample (same as Parakeet). +- `enc.blocks.{0..16}.*` — 17 Conformer/NEST blocks; identical suffix map to Parakeet's `ENCODER_BLOCK_TABLE` (macaron FF1/FF2, rel-pos self-attn with `pos_bias_u/v`, conv module with `conv.bn.{weight,bias,running_mean,running_var}`). `use_bias=true` (linear biases present); `conv_norm_type=batch_norm`. +- `diar.encoder_proj.{weight,bias}` — Linear 512 -> 192 between the two encoders. +- `tf.blocks.{0..17}.*` — 18 post-LN Transformer blocks: `norm_1` -> attn(`attn.{q,k,v,out}`) -> `norm_2` -> FFN(`ff.{in,out}`), ReLU, `pre_ln=false`. +- `diar.fc1.*`, `diar.spk_head.*` (4x384 -> 4 sigmoid), `diar.single_spk_head.*` (4x192). + +Tensor-name mapping decisions: Conformer names mirror `convert-parakeet.py` verbatim (shared NeMo ConformerEncoder). Transformer norms named `norm_1`/`norm_2` and BN kept as `.bn.` so `reference_dtype_for`/`policy.cpp` route them to F32; representatives registered in `scripts/lib/test_quant_policy_sync.py`. GGUF slug is the variant (`streaming-4spk-v2.1`), not the longer repo slug. + ## Notes -- Frontend `normalize=NA` (no per-feature normalization); 128 mels, n_fft 512, win 400 / hop 160, hann, dither 1e-5. `preemphasis` is not set in `model_config.yaml` (NeMo default 0.97 assumed) and MUST be confirmed against the Stage 2 Oracle dump; the sibling streaming-parakeet models disable it. +- Frontend `normalize=NA` (no per-feature normalization); 128 mels, n_fft 512, win 400 / hop 160, hann, dither 1e-5. `preemphasis=0.97` CONFIRMED from the loaded preprocessor at Stage 2 (NeMo default applied, not disabled). +- Reaching the published DER later (optional, not a port gate) needs forced-alignment RTTMs + a tuned post-processing config; the `--collar` / `--skip-overlap` knobs in `scripts/diar/score_der.py` are for diagnostics only. - Output post-processing (probs -> RTTM segments) is dataset-tuned upstream. Port gate is prob-tensor parity; DER/JER use a fixed/documented post-processing and are compared to the reference model, not to the published numbers. - Streaming operating-point knobs: `chunk_len`, `chunk_right_context`, `fifo_len`, `spkcache_update_period`, `spkcache_len` (all in 80 ms frames). Published DER corresponds to specific presets (1.04s vs 30.4s input-buffer latency). diff --git a/include/transcribe/extensions.h b/include/transcribe/extensions.h index 95790aa5..b93bf146 100644 --- a/include/transcribe/extensions.h +++ b/include/transcribe/extensions.h @@ -16,6 +16,7 @@ #include "transcribe.h" #include "transcribe/moonshine_streaming.h" #include "transcribe/parakeet.h" +#include "transcribe/sortformer.h" #include "transcribe/voxtral_realtime.h" #include "transcribe/whisper.h" diff --git a/include/transcribe/sortformer.h b/include/transcribe/sortformer.h new file mode 100644 index 00000000..3899d2d4 --- /dev/null +++ b/include/transcribe/sortformer.h @@ -0,0 +1,81 @@ +/* + * include/transcribe/sortformer.h - Sortformer-family public extension. + * + * Includes transcribe.h; safe to include in C or C++ TUs. Holds the + * streaming-operating-point run extension, its kind constant, and its + * init function. + * + * Sortformer (streaming-4spk-v2.1) is a diarization-only model: a run + * produces no text; the product is the who-spoke-when rows read back via + * transcribe_n_speaker_segments / transcribe_get_speaker_segment + * (TRANSCRIBE_FEATURE_DIARIZATION). The compute core is streaming + * (AOSC speaker cache + FIFO) but the shipped entry point is the batch + * transcribe_run over a whole recording, so this extension lives on the + * RUN slot. A future push-audio entry point (transcribe_stream_begin/ + * feed) would register a separate STREAM-slot kind taking the same + * preset enum. + * + * Probe via transcribe_model_accepts_ext_kind(model, + * TRANSCRIBE_EXT_SLOT_RUN, TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM) + * before pointing transcribe_run_params::family at the struct. + * + * FourCC kinds are reserved in docs/extension-kinds.md. + */ + +#ifndef TRANSCRIBE_SORTFORMER_H +#define TRANSCRIBE_SORTFORMER_H + +#include "transcribe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* 'SFST' little-endian = 0x54534653 */ +#define TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM 0x54534653u + +/* + * Streaming operating point (latency / accuracy trade-off). + * + * The model processes audio in fixed chunks and carries speaker identity + * across chunks in a bounded cache; the operating point sets the chunk + * geometry. Each named preset is a jointly-tuned bundle published by the + * upstream model (chunk length, lookahead, FIFO and speaker-cache + * geometry) - the menu is discrete, not a continuous latency dial, and + * only these bundles are accuracy-validated (AMI DER, see + * docs/porting/families/sortformer.md). + * + * DEFAULT The GGUF-shipped checkpoint configuration. + * VERY_HIGH_LATENCY ~30.4 s algorithmic lookahead (chunk 340 + rc 40 + * frames @ 80 ms). Highest accuracy; the published + * operating point for offline file processing. + * HIGH_LATENCY ~10.0 s lookahead (chunk 124 + rc 1). + * LOW_LATENCY ~1.04 s lookahead (chunk 6 + rc 7). The + * real-time operating point. Note: much higher + * compute per audio second than the larger chunks + * (many small windows); see the family doc for + * measured throughput. + * + * Values outside the enum range are rejected by transcribe_run with + * TRANSCRIBE_ERR_INVALID_ARG before the previous result is cleared. + */ +typedef enum { + TRANSCRIBE_SORTFORMER_PRESET_DEFAULT = 0, + TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY = 1, + TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY = 2, + TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY = 3, +} transcribe_sortformer_preset; + +struct transcribe_sortformer_stream_ext { + struct transcribe_ext ext; + transcribe_sortformer_preset preset; +}; + +/* Fills ext.size/kind and preset = DEFAULT (GGUF-shipped cfg). */ +TRANSCRIBE_API void transcribe_sortformer_stream_ext_init(struct transcribe_sortformer_stream_ext * ext); + +#ifdef __cplusplus +} +#endif + +#endif /* TRANSCRIBE_SORTFORMER_H */ diff --git a/reports/porting/sortformer/forward-map.md b/reports/porting/sortformer/forward-map.md new file mode 100644 index 00000000..18e154e9 --- /dev/null +++ b/reports/porting/sortformer/forward-map.md @@ -0,0 +1,138 @@ +# Forward map - sortformer + +Reference: NeMo `SortformerEncLabelModel` (nemo.collections.asr.models.sortformer_diar_models) @ pin `6967f48fda2a` +Closest in-tree analog: src/arch/parakeet/ (shared NeMo ConformerEncoder for the FastConformer stage) + +Compact map from the reference forward pass to the C++ port. Sortformer is an +`encoder-diarizer`: no tokenizer, no decoder, no text. The product is a `T x 4` +per-frame speaker-activity probability matrix (80 ms frames, arrival-order +columns, max 4 speakers). Two forward paths: + +- **offline / full-context** (`forward` -> `forward_infer`): single-chunk, + non-stateful. The C++ encoder must match this first. Gate tensors below. +- **streaming** (`forward_streaming`): AOSC speaker-cache + FIFO over 188-frame + chunks. Needed for the streaming capability row and DER on long audio. See + "Streaming path" section; only diverges from offline once T > chunk_len. + +Dims (from GGUF KVs): conformer 17L d=512 h=8 dff=2048 k=9 sub=8 feat_in=128 +BN; transformer 18L **post-LN** d=192 h=8 (head_dim=24) dff=768 ReLU, **no +final norm** (pre_ln=false); encoder_proj 512->192; diar fc1 192->192, +single_spk_head 192->4 (spk_head 384->4 is streaming-only). + +## Frontend + +| Stage | Reference location | Output shape | Gate tensor | ggml / C++ pattern | In-tree analog | +|-------|--------------------|--------------|-------------|--------------------|----------------| +| Mel log-spectrogram | NeMo `AudioToMelSpectrogramPreprocessor` (preprocessor.*, recomputed in C++) | [128, T_mel] (T_mel=1216 for 12s) | `enc.mel.in` | reuse parakeet MelFrontend: preemph 0.97, dither 1e-5, n_fft 512, win 400, hop 160, hann-periodic, 128 mel, `normalize=none` | parakeet `MelFrontend` | + +Note: parakeet's NeMo preprocessor uses `normalize=per_feature`; Sortformer +uses `normalize=none`. Confirm the parakeet MelFrontend is parameterized on +normalize (KV `stt.frontend.normalize`) and does NOT hardcode per-feature. + +## Encoder + +| Stage | Reference location | Output shape | Gate tensor | ggml / C++ pattern | In-tree analog | +|-------|--------------------|--------------|-------------|--------------------|----------------| +| FastConformer (pre_encode dw_striding x8 + 17 Conformer blocks) | `ConformerEncoder.forward` (encoder.*) | [T=150, 512] (ref emits [B,512,T], transposed to [T,512]) | `enc.fastconformer.out` | **verbatim reuse** of parakeet ConformerEncoder graph (macaron FF, rel-pos MHA w/ pos_bias_u/v, conv+BN, use_bias=true). Same tensor suffix map. | parakeet `encoder.cpp` | +| encoder_proj | `sortformer_modules.encoder_proj` (Linear 512->192) applied after `emb_seq.transpose(1,2)` | [150, 192] | `enc.encoder_proj.out` | `ggml_mul_mat(W, x) + b` | any linear | +| Transformer encoder (18x post-LN block) | `transformer/transformer_encoders.py::TransformerEncoder.forward` + `TransformerEncoderBlock.forward_postln` | [150, 192] | `enc.transformer.out` | per block below; no final norm | new (cohere decoder self-attn is closest MHA) | + +### Transformer post-LN block (repeat x18) + +Reference `forward_postln(x, mask)`: +``` +a = MHA(q=x, k=x, v=x, mask) # first_sub_layer +a = a + x # residual +a = LN1(a) # layer_norm_1 (eps 1e-5) +f = dense_out(relu(dense_in(a))) # second_sub_layer (PositionWiseFF), 192->768->192 +o = f + a # residual +o = LN2(o) # layer_norm_2 (eps 1e-5) +``` +MHA (`transformer_modules.MultiHeadAttention`): q/k/v each `Linear(192,192)` +with bias (`attn.{q,k,v}`); `attn_scale = sqrt(sqrt(head_dim))`; q and k each +divided by attn_scale before `q@k^T` (=> scores/sqrt(head_dim)); `+ mask` +(0 attend / -10000 pad); softmax; `@v`; `out_projection` (`attn.out`, bias). +Gate tensors: verify block 0, 8, 17 output via TRANSCRIBE_DUMP_ALL_BLOCKS. + +## Decoder + +No decoder. The "head" is the diarization sigmoid stack. + +| Stage | Reference location | Output shape | Gate tensor | ggml / C++ pattern | In-tree analog | +|-------|--------------------|--------------|-------------|--------------------|----------------| +| Speaker sigmoids (offline) | `sortformer_modules.forward_speaker_sigmoids` | [150, 4] | `diar.preds_offline` | `relu(x)` -> `fc1`(192->192) -> `relu` -> `single_spk_head`(192->4) -> `sigmoid`; then `* mask` | new | + +`forward_speaker_sigmoids(x)`: +``` +h = relu(x) # dropout is identity at eval +h = fc1(h) # first_hidden_to_hidden, Linear(192,192) +h = relu(h) +s = single_spk_head(h) # single_hidden_to_spks, Linear(192,4) +preds = sigmoid(s) +preds = preds * encoder_mask # zero padded frames +``` + +## Generation / KV Path + +No autoregressive generation. N/A. + +## Streaming path (forward_streaming) — IMPLEMENTED (Stage 4 streaming checkpoint) + +Sync (`async_streaming=False`) stateful loop over chunks of `chunk_len` +diar-frames (chunk_len * subsampling_factor mel frames). Per chunk +(`forward_streaming_step`): + +1. `encoder.pre_encode(chunk mel window incl. lc/rc context)` -> chunk_embs + [T_diar, 512], **un-xscaled** (C++ Graph A: `conf::build_pre_encode`). +2. concat `[spkcache | fifo | chunk_embs]` (all 512-dim, host-owned state). +3. `frontend_encoder(bypass_pre_encode=True)`: xscale x sqrt(512) + rel pos_emb + over T_concat + 17 conformer blocks (full attention) + `encoder_proj` + 512->192 (C++ Graph B: `conf::build_conformer_block` x17 + linear). +4. `forward_infer`: 18L post-LN transformer + `forward_speaker_sigmoids` + (relu -> fc1 -> relu -> **single_spk_head** 192->4 -> sigmoid). NOTE: the + inference path uses `single_hidden_to_spks` (192->4), NOT the 2*hidden + `hidden_to_spks` (384->4) head, which is unused at inference. All frames are + valid in sync mode so `apply_mask_to_preds` / encoder_mask is a no-op. +5. `streaming_update` (host): slice fifo/chunk preds, append the middle chunk + to FIFO, pop `pop_out_len` frames FIFO->spkcache, update the silence + profile, and `_compress_spkcache` when spkcache > spkcache_len. + +C++ implementation: `src/arch/sortformer/model.cpp` (two-phase Graph A/B + +`run_streaming` driver) and `src/arch/sortformer/stream.cpp` (host +`streaming_update_sync` / `_compress_spkcache` / silence profile, exact ports of +`sortformer_modules.py`). The tensor-dump oracle runs on CPU, so `_compress_ +spkcache`'s topk/sort tie-breaks target ATen CPU semantics (value desc, +flat-index asc; `-inf` picks mapped to `max_index` before the ascending sort). + +Validation: single-chunk `diar.probs` == `diar.preds_offline` bit-close +(2.96e-4, oracle 150 dihard3 PP -> `score_der.py`). + +## Capabilities And Language Controls + +| Capability | Reference behavior | C++ API behavior | Family-doc Capability Validation row | +|------------|--------------------|------------------|--------------------------------------| +| Streaming diarization <=4spk | `m.diarize()` streaming AOSC/FIFO | TODO: streaming hooks emit T x 4 | Streaming diarization (MUST PASS) | +| Offline diarization | `forward` single chunk | `run()` offline forward -> T x 4 | Offline diarization (MUST PASS) | +| Speaker-activity tensor | preds [T,4] | dump / result surface T x 4 | Speaker-activity tensor (MUST PASS) | +| Transcription/translation/timestamps | none (not a transcriber) | not exposed | OUT OF SCOPE rows | + +## Deviations From Closest Analog + +- No decoder, no tokenizer, no text result. Output is a float matrix; the + transcript-oriented result/session API must carry it (see open decision: + diar result surface vs tensor-dump-only for Stage 4). +- Transformer is **post-LN** (parakeet/cohere self-attn are pre-LN); LN sits + AFTER each residual add, and there is no final norm. +- MHA scaling is split (q and k pre-divided by sqrt(sqrt(head_dim))) rather + than scaling scores once; numerically equivalent, keep as-is for parity. +- Frontend `normalize=none` (parakeet uses per_feature). +- Encoder output is transposed [B,512,T]->[B,T,512] before encoder_proj. + +## Variant Notes + +- `streaming-4spk-v2.1`: family baseline (this port). +- `streaming-4spk-v2`: architecturally identical, CC-BY-4.0; add weights once + v2.1 validated (sibling-variant shortcut, Step 1). diff --git a/reports/porting/sortformer/streaming-4spk-v2.1/intake.json b/reports/porting/sortformer/streaming-4spk-v2.1/intake.json index 10dd2b0d..93bec331 100644 --- a/reports/porting/sortformer/streaming-4spk-v2.1/intake.json +++ b/reports/porting/sortformer/streaming-4spk-v2.1/intake.json @@ -148,7 +148,7 @@ "score": 16.67, "score_unit": "percent DER", "source": "model card", - "notes": "Metric is DER (lower is better), collar 0.0s with overlap, 1.04s input-buffer latency. FREELY DOWNLOADABLE. CONFIRMED acceptance set at intake sign-off (parity + DER + JER vs the NeMo reference). 30.4s-latency DER is 15.90." + "notes": "Metric is DER (lower is better), collar 0.0s with overlap. FREELY DOWNLOADABLE. CONFIRMED acceptance set. 30.4s-latency (very_high_latency) published DER is 15.90. Stage 2 REPRODUCED this: our measured NeMo reference on AMI ihm test = 15.96% (no PP) / 14.83% (dihard3 PP), both vs forced-alignment RTTMs (nttcslab-sp), preset very_high_latency. Scoring vs the denser diarizers-community manual RTTMs instead gives 29.44% (recall-inflated). Official gate = FA RTTMs + dihard3 PP = 14.83%." }, { "dataset": "AMI Test SDM (forced-alignment RTTM)", @@ -221,7 +221,7 @@ "intake_gaps": [ { "field": "frontend.preemphasis", - "reason": "Not present in model_config.yaml; recorded as the NeMo default 0.97 but the sibling streaming-parakeet models disable preemphasis. Confirm the actual value against the Stage 2 Oracle dump before convert (info-only for Gate A, but load-bearing for DER)." + "reason": "RESOLVED at Stage 2 Oracle: the loaded AudioToMelSpectrogramPreprocessor reports preemph=0.97 and normalize='NA' (none). The 0.97 default is applied, not disabled. Frontend is locked." }, { "field": "upstream_benchmarks.acceptance_dataset", diff --git a/samples/sortformer-2spk-mix.wav b/samples/sortformer-2spk-mix.wav new file mode 100644 index 0000000000000000000000000000000000000000..18941047a220fa9d353b2ffc69a3eed4f35702b3 GIT binary patch literal 384044 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

51S{)*t@_po!RNx^+Sy@z&Q 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⪙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 z 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%0Df_EUEV726p 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 skipped (C++ recomputes the mel frontend) + encoder.pre_encode.* -> enc.pre_encode.* (dw_striding subsample x8) + encoder.layers.{i}.* -> enc.blocks.{i}.* (17 Conformer/NEST blocks, d=512) + sortformer_modules.encoder_proj.* -> diar.encoder_proj.* (Linear 512 -> 192) + transformer_encoder.layers.{i}.* -> tf.blocks.{i}.* (18 post-LN Transformer blocks, d=192) + sortformer_modules.first_hidden_to_hidden.* -> diar.fc1.* + sortformer_modules.hidden_to_spks.* -> diar.spk_head.* (4 sigmoid outputs) + sortformer_modules.single_hidden_to_spks.* -> diar.single_spk_head.* (single-speaker path) + +The Conformer encoder is the same NeMo ConformerEncoder that Parakeet uses, +so the pre-encode + block tensor tables mirror scripts/convert-parakeet.py +(use_bias=true, conv_norm_type=batch_norm with running stats). + +Reference dtype is F32 (NeMo diarizer state_dict is fp32). + +Usage (via the Sortformer reference env, which has NeMo): + uv run --project scripts/envs/sortformer \ + scripts/convert-sortformer.py nvidia/diar_streaming_sortformer_4spk-v2.1 \ + --repo-id nvidia/diar_streaming_sortformer_4spk-v2.1 +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +import numpy as np +from gguf import GGMLQuantizationType + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib.gguf_common import ( # noqa: E402 + add_general_identity, + canonicalize_normalize, + encode_for_gguf, + gguf_name, + gguf_writer, + reference_dtype_for, + slug_from_repo_id, +) + +REFERENCE_TYPE = GGMLQuantizationType.F32 +REFERENCE_DTYPE_LABEL = "F32" +REFERENCE_FILE_TYPE = 0 # LlamaFileType ALL_F32 + +# --- Conformer encoder tables (same NeMo ConformerEncoder as Parakeet) ------- + +PRE_ENCODE_TABLE = [ + ("encoder.pre_encode.conv.0.weight", "enc.pre_encode.conv.0.weight"), + ("encoder.pre_encode.conv.0.bias", "enc.pre_encode.conv.0.bias"), + ("encoder.pre_encode.conv.2.weight", "enc.pre_encode.conv.2.weight"), + ("encoder.pre_encode.conv.2.bias", "enc.pre_encode.conv.2.bias"), + ("encoder.pre_encode.conv.3.weight", "enc.pre_encode.conv.3.weight"), + ("encoder.pre_encode.conv.3.bias", "enc.pre_encode.conv.3.bias"), + ("encoder.pre_encode.conv.5.weight", "enc.pre_encode.conv.5.weight"), + ("encoder.pre_encode.conv.5.bias", "enc.pre_encode.conv.5.bias"), + ("encoder.pre_encode.conv.6.weight", "enc.pre_encode.conv.6.weight"), + ("encoder.pre_encode.conv.6.bias", "enc.pre_encode.conv.6.bias"), + ("encoder.pre_encode.out.weight", "enc.pre_encode.out.weight"), + ("encoder.pre_encode.out.bias", "enc.pre_encode.out.bias"), +] + +# Per-conformer-block suffix map (source suffix under encoder.layers.{i}. -> +# GGUF suffix under enc.blocks.{i}.). Norms keep the "norm_" prefix and BN +# params keep ".bn." so reference_dtype_for / policy.cpp route them to F32. +CONFORMER_BLOCK_TABLE = [ + ("norm_feed_forward1.weight", "norm_ff1.weight"), + ("norm_feed_forward1.bias", "norm_ff1.bias"), + ("feed_forward1.linear1.weight", "ff1.linear1.weight"), + ("feed_forward1.linear1.bias", "ff1.linear1.bias"), + ("feed_forward1.linear2.weight", "ff1.linear2.weight"), + ("feed_forward1.linear2.bias", "ff1.linear2.bias"), + ("norm_self_att.weight", "norm_attn.weight"), + ("norm_self_att.bias", "norm_attn.bias"), + ("self_attn.linear_q.weight", "attn.linear_q.weight"), + ("self_attn.linear_q.bias", "attn.linear_q.bias"), + ("self_attn.linear_k.weight", "attn.linear_k.weight"), + ("self_attn.linear_k.bias", "attn.linear_k.bias"), + ("self_attn.linear_v.weight", "attn.linear_v.weight"), + ("self_attn.linear_v.bias", "attn.linear_v.bias"), + ("self_attn.linear_out.weight", "attn.linear_out.weight"), + ("self_attn.linear_out.bias", "attn.linear_out.bias"), + ("self_attn.linear_pos.weight", "attn.linear_pos.weight"), + ("self_attn.pos_bias_u", "attn.pos_bias_u"), + ("self_attn.pos_bias_v", "attn.pos_bias_v"), + ("norm_conv.weight", "norm_conv.weight"), + ("norm_conv.bias", "norm_conv.bias"), + ("conv.pointwise_conv1.weight", "conv.pointwise1.weight"), + ("conv.pointwise_conv1.bias", "conv.pointwise1.bias"), + ("conv.depthwise_conv.weight", "conv.depthwise.weight"), + ("conv.depthwise_conv.bias", "conv.depthwise.bias"), + ("conv.batch_norm.weight", "conv.bn.weight"), + ("conv.batch_norm.bias", "conv.bn.bias"), + ("conv.batch_norm.running_mean", "conv.bn.running_mean"), + ("conv.batch_norm.running_var", "conv.bn.running_var"), + ("conv.pointwise_conv2.weight", "conv.pointwise2.weight"), + ("conv.pointwise_conv2.bias", "conv.pointwise2.bias"), + ("norm_feed_forward2.weight", "norm_ff2.weight"), + ("norm_feed_forward2.bias", "norm_ff2.bias"), + ("feed_forward2.linear1.weight", "ff2.linear1.weight"), + ("feed_forward2.linear1.bias", "ff2.linear1.bias"), + ("feed_forward2.linear2.weight", "ff2.linear2.weight"), + ("feed_forward2.linear2.bias", "ff2.linear2.bias"), + ("norm_out.weight", "norm_out.weight"), + ("norm_out.bias", "norm_out.bias"), +] + +# Post-LN Transformer encoder block (NeMo TransformerEncoder, pre_ln=false). +# Norms named norm_1/norm_2 so the "norm_" F32 rule catches them. +TRANSFORMER_BLOCK_TABLE = [ + ("layer_norm_1.weight", "norm_1.weight"), + ("layer_norm_1.bias", "norm_1.bias"), + ("first_sub_layer.query_net.weight", "attn.q.weight"), + ("first_sub_layer.query_net.bias", "attn.q.bias"), + ("first_sub_layer.key_net.weight", "attn.k.weight"), + ("first_sub_layer.key_net.bias", "attn.k.bias"), + ("first_sub_layer.value_net.weight", "attn.v.weight"), + ("first_sub_layer.value_net.bias", "attn.v.bias"), + ("first_sub_layer.out_projection.weight", "attn.out.weight"), + ("first_sub_layer.out_projection.bias", "attn.out.bias"), + ("layer_norm_2.weight", "norm_2.weight"), + ("layer_norm_2.bias", "norm_2.bias"), + ("second_sub_layer.dense_in.weight", "ff.in.weight"), + ("second_sub_layer.dense_in.bias", "ff.in.bias"), + ("second_sub_layer.dense_out.weight", "ff.out.weight"), + ("second_sub_layer.dense_out.bias", "ff.out.bias"), +] + +# Diarization projection + head (sortformer_modules). +HEAD_TABLE = [ + ("sortformer_modules.encoder_proj.weight", "diar.encoder_proj.weight"), + ("sortformer_modules.encoder_proj.bias", "diar.encoder_proj.bias"), + ("sortformer_modules.first_hidden_to_hidden.weight", "diar.fc1.weight"), + ("sortformer_modules.first_hidden_to_hidden.bias", "diar.fc1.bias"), + ("sortformer_modules.hidden_to_spks.weight", "diar.spk_head.weight"), + ("sortformer_modules.hidden_to_spks.bias", "diar.spk_head.bias"), + ("sortformer_modules.single_hidden_to_spks.weight", "diar.single_spk_head.weight"), + ("sortformer_modules.single_hidden_to_spks.bias", "diar.single_spk_head.bias"), +] + +# state_dict keys the loader recomputes / does not need. +EXPECTED_UNUSED_PREFIXES = ("preprocessor.",) +EXPECTED_UNUSED_SUFFIXES = (".num_batches_tracked",) + + +def _to_fp32(t) -> np.ndarray: + import torch + if not isinstance(t, torch.Tensor): + raise TypeError(f"expected torch.Tensor, got {type(t).__name__}") + if t.dtype != torch.float32: + raise ValueError(f"expected fp32 tensor, got {t.dtype} — cast at the source") + return np.ascontiguousarray(t.detach().cpu().numpy()) + + +def _add(writer, name: str, arr: np.ndarray) -> None: + ggml_type = reference_dtype_for(name, REFERENCE_TYPE) + data, out_type = encode_for_gguf(arr, ggml_type) + writer.add_tensor(name, data, raw_dtype=out_type) + + +def convert(model_spec: str, out_path: Path, repo_id: str | None = None) -> None: + from omegaconf import OmegaConf + from nemo.collections.asr.models import SortformerEncLabelModel + + print(f"Output dtype: {REFERENCE_DTYPE_LABEL} (source/reference dtype)") + if model_spec.endswith(".nemo") or Path(model_spec).exists(): + model = SortformerEncLabelModel.restore_from(restore_path=model_spec, map_location="cpu", strict=False) + else: + model = SortformerEncLabelModel.from_pretrained(model_spec, map_location="cpu") + model.eval() + cfg = OmegaConf.to_container(model.cfg, resolve=True) + sd = model.state_dict() + sd_keys = set(sd) + + enc = cfg["encoder"] + tfc = cfg["transformer_encoder"] + pre = cfg["preprocessor"] + n_conf = int(enc["n_layers"]) + n_tf = int(tfc["num_layers"]) + max_spk = int(cfg.get("max_num_of_spks", 4)) + print(f"conformer_layers={n_conf} transformer_layers={n_tf} max_speakers={max_spk}") + + writer = gguf_writer(str(out_path), "sortformer") + + add_general_identity( + writer, + name="Streaming Sortformer Diarizer 4spk v2.1", + basename="sortformer", + size_label="117M", + version="v2.1", + file_type=REFERENCE_FILE_TYPE, + languages=["en"], + author="NVIDIA", + organization="nvidia", + license="other", + license_name="nvidia-open-model-license", + license_link="https://www.nvidia.com/en-us/agreements/enterprise-software/nvidia-open-model-license/", + repo_url=f"https://huggingface.co/{repo_id}" if repo_id else None, + description="End-to-end streaming speaker diarizer (encoder-diarizer): NEST/FastConformer + Transformer, 4 sigmoid speaker-activity outputs per 80ms frame.", + ) + + writer.add_string("stt.variant", out_path.parent.name) + + # ----- frontend (C++ recomputes; these are the reference params) ----- + sr = int(pre["sample_rate"]) + writer.add_uint32("stt.frontend.sample_rate", sr) + writer.add_uint32("stt.frontend.num_mels", int(pre["features"])) + writer.add_uint32("stt.frontend.n_fft", int(pre["n_fft"])) + writer.add_uint32("stt.frontend.hop_length", int(round(float(pre["window_stride"]) * sr))) + writer.add_uint32("stt.frontend.win_length", int(round(float(pre["window_size"]) * sr))) + writer.add_string("stt.frontend.window", str(pre.get("window", "hann"))) + writer.add_string("stt.frontend.normalize", canonicalize_normalize(pre.get("normalize"))) + writer.add_float32("stt.frontend.pre_emphasis", float(pre.get("preemph") if pre.get("preemph") is not None else 0.97)) + writer.add_float32("stt.frontend.dither", float(pre.get("dither", 0.0))) + + # ----- capabilities ----- + writer.add_bool("stt.capability.streaming", True) + writer.add_bool("stt.capability.speaker_diarization", True) + writer.add_bool("stt.capability.lang_detect", False) + writer.add_bool("stt.capability.translate", False) + writer.add_bool("stt.capability.timestamps", False) + + # ----- architecture dims ----- + writer.add_uint32("stt.sortformer.max_speakers", max_spk) + writer.add_uint32("stt.sortformer.frame_hop", int(round(float(pre["window_stride"]) * sr)) * int(enc["subsampling_factor"])) + writer.add_uint32("stt.sortformer.encoder.n_layers", n_conf) + writer.add_uint32("stt.sortformer.encoder.d_model", int(enc["d_model"])) + writer.add_uint32("stt.sortformer.encoder.n_heads", int(enc["n_heads"])) + writer.add_uint32("stt.sortformer.encoder.d_ff", int(enc["d_model"]) * int(enc["ff_expansion_factor"])) + writer.add_uint32("stt.sortformer.encoder.conv_kernel", int(enc["conv_kernel_size"])) + writer.add_uint32("stt.sortformer.encoder.subsampling_factor", int(enc["subsampling_factor"])) + writer.add_uint32("stt.sortformer.encoder.subsampling_channels", int(enc["subsampling_conv_channels"])) + writer.add_uint32("stt.sortformer.encoder.feat_in", int(enc["feat_in"])) + writer.add_uint32("stt.sortformer.encoder.pos_emb_max_len", int(enc.get("pos_emb_max_len", 5000))) + writer.add_string("stt.sortformer.encoder.conv_norm_type", str(enc.get("conv_norm_type", "batch_norm"))) + writer.add_uint32("stt.sortformer.transformer.n_layers", n_tf) + writer.add_uint32("stt.sortformer.transformer.d_model", int(tfc["hidden_size"])) + writer.add_uint32("stt.sortformer.transformer.n_heads", int(tfc["num_attention_heads"])) + writer.add_uint32("stt.sortformer.transformer.d_ff", int(tfc["inner_size"])) + writer.add_string("stt.sortformer.transformer.activation", str(tfc.get("hidden_act", "relu"))) + writer.add_bool("stt.sortformer.transformer.pre_ln", bool(tfc.get("pre_ln", False))) + + # ----- shipped streaming defaults (runtime-tunable presets live in the runner) ----- + sm = cfg["sortformer_modules"] + writer.add_uint32("stt.sortformer.stream.chunk_len", int(sm["chunk_len"])) + writer.add_uint32("stt.sortformer.stream.spkcache_len", int(sm["spkcache_len"])) + writer.add_uint32("stt.sortformer.stream.fifo_len", int(sm["fifo_len"])) + writer.add_uint32("stt.sortformer.stream.spkcache_update_period", int(sm["spkcache_update_period"])) + + # ----- tensors ----- + used: set[str] = set() + + def emit(src: str, dst: str): + if src not in sd: + raise KeyError(f"missing expected tensor: {src}") + _add(writer, dst, _to_fp32(sd[src])) + used.add(src) + + for src, dst in PRE_ENCODE_TABLE: + emit(src, dst) + for i in range(n_conf): + for s_suf, d_suf in CONFORMER_BLOCK_TABLE: + emit(f"encoder.layers.{i}.{s_suf}", f"enc.blocks.{i}.{d_suf}") + for i in range(n_tf): + for s_suf, d_suf in TRANSFORMER_BLOCK_TABLE: + emit(f"transformer_encoder.layers.{i}.{s_suf}", f"tf.blocks.{i}.{d_suf}") + for src, dst in HEAD_TABLE: + emit(src, dst) + + # ----- unused-key audit ----- + unexpected = [] + for k in sd_keys - used: + if k.startswith(EXPECTED_UNUSED_PREFIXES) or k.endswith(EXPECTED_UNUSED_SUFFIXES): + continue + unexpected.append(k) + if unexpected: + raise ValueError(f"{len(unexpected)} unmapped state_dict tensors, e.g. {sorted(unexpected)[:8]}") + print(f"Emitted {len(used)} tensors ({len(sd_keys) - len(used)} skipped: preprocessor + BN counters)") + + writer.write_header_to_file() + writer.write_kv_data_to_file() + writer.write_tensors_to_file() + writer.close() + print(f"Wrote GGUF: {out_path} ({out_path.stat().st_size/1e6:.1f} MB)") + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("model", help="HF repo id or path to a .nemo checkpoint") + ap.add_argument("--repo-id", default=None, help="HF repo id (for slug + provenance)") + ap.add_argument("--out", default=None, help="Override output GGUF path") + args = ap.parse_args() + + repo_id = args.repo_id or (args.model if "/" in args.model and not Path(args.model).exists() else None) + if args.out: + out_path = Path(args.out) + else: + if not repo_id: + raise SystemExit("error: pass --repo-id (or a HF repo id) so the output slug can be derived") + slug = slug_from_repo_id(repo_id) + out_path = Path("models") / slug / gguf_name(slug, REFERENCE_DTYPE_LABEL) + out_path.parent.mkdir(parents=True, exist_ok=True) + convert(args.model, out_path, repo_id=repo_id) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/compare_compress_spkcache.py b/scripts/diar/compare_compress_spkcache.py new file mode 100644 index 00000000..711939e2 --- /dev/null +++ b/scripts/diar/compare_compress_spkcache.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +""" +compare_compress_spkcache.py - Verify the Sortformer AOSC `_compress_spkcache` +step bit-for-bit between the NeMo reference and the transcribe.cpp C++ port +(residual-uncertainty #1). + +Both sides run the SAME clip at the SAME streaming preset, so the sequence of +compression calls lines up 1:1. For each call we compare: + + * frame_idx the sorted selected frame indices (C++ `frame_idx` vs NeMo + `_get_topk_indices` -> `topk_indices_sorted`, post-remainder, + disabled entries set to 0). An EXACT match confirms the topk + tie-break hypothesis (ATen CPU topk: value desc, flat-index + asc; -inf picks -> max_index before the ascending sort) on real + drifted scores. + * is_disabled the disabled mask (silence-pad / -inf placeholder frames). + * spkcache_preds the gathered [spkcache_len, n_spk] preds (numeric; small + F32 drift expected, reported as max_abs). + +NeMo dump: `compress.{k:03d}.{topk_indices,is_disabled,spkcache_preds}.npy` + (scripts/dump_reference_sortformer_nemo.py diarize --dump-compress). +C++ dump: `compress.{k:03d}.{frame_idx,is_disabled,spkcache_preds}.{f32,json}` + (TRANSCRIBE_SORTFORMER_COMPRESS_DUMP=1 + TRANSCRIBE_DUMP_DIR). + +Usage: + uv run scripts/diar/compare_compress_spkcache.py \ + --ref-dir build/validate/sortformer/.../compress-ref \ + --cpp-dir build/validate/sortformer/.../compress-cpp +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +import numpy as np + + +def _boost_topk(scores: np.ndarray, k: int, scale: float) -> np.ndarray: + """Increase the k highest scores per speaker column. Tie-break: value desc, + frame-index asc (ATen CPU topk). Mirrors _boost_topk_scores.""" + if k <= 0: + return scores + delta = scale * np.log(0.5) # negative -> subtracting boosts + n = scores.shape[0] + for s in range(scores.shape[1]): + col = scores[:, s] + order = np.lexsort((np.arange(n), -col)) # primary value desc, secondary idx asc + col[order[:min(k, n)]] -= delta + return scores + + +def select_indices(preds: np.ndarray, spkcache_len: int, sil_per_spk: int = 3, + pred_thr: float = 0.25, boost_latest: float = 0.05, + strong_rate: float = 0.75, weak_rate: float = 1.5, + min_pos_rate: float = 0.5, max_index: int = 99999): + """Independent numpy reimplementation of the Sortformer AOSC frame selection + (_get_log_pred_scores -> _disable_low_scores -> scores_boost_latest -> + _boost_topk_scores x2 -> silence pad -> _get_topk_indices). Returns + (frame_idx [spkcache_len], is_disabled [spkcache_len]). A third, neutral + arbiter for both the NeMo and C++ dumps.""" + N, n_spk = preds.shape + per_spk = spkcache_len // n_spk - sil_per_spk + import math + strong = math.floor(per_spk * strong_rate) + weak = math.floor(per_spk * weak_rate) + min_pos = math.floor(per_spk * min_pos_rate) + + lp = np.log(np.clip(preds, pred_thr, None)) + l1 = np.log(np.clip(1.0 - preds, pred_thr, None)) + scores = lp - l1 + l1.sum(1, keepdims=True) - math.log(0.5) + + is_speech = preds > 0.5 + scores = np.where(is_speech, scores, -np.inf) + is_pos = scores > 0 + n_pos = is_pos.sum(0, keepdims=True) + scores = np.where((~is_pos) & is_speech & (n_pos >= min_pos), -np.inf, scores) + + if boost_latest > 0: + scores[spkcache_len:, :] += boost_latest # -inf + x stays -inf + + scores = _boost_topk(scores, strong, 2.0) + scores = _boost_topk(scores, weak, 1.0) + + if sil_per_spk > 0: + scores = np.concatenate([scores, np.full((sil_per_spk, n_spk), np.inf)], axis=0) + n_frames = scores.shape[0] + n_frames_no_sil = n_frames - sil_per_spk + + flat = scores.T.reshape(-1) # flat index f = s * n_frames + i + order = np.lexsort((np.arange(flat.size), -flat)) # value desc, flat-idx asc + topk = order[:spkcache_len].copy() + topk = np.where(flat[topk] != -np.inf, topk, max_index) + topk_sorted = np.sort(topk) + is_disabled = topk_sorted == max_index + frame = np.remainder(topk_sorted, n_frames) + is_disabled = is_disabled | (frame >= n_frames_no_sil) + frame = np.where(is_disabled, 0, frame) + return frame.astype(np.int64), is_disabled.astype(bool) + + +def _load_cpp_f32(cpp_dir: Path, stem: str) -> np.ndarray: + """Load a C++ dump_host_f32 artifact (.f32 payload + .json shape).""" + meta = json.loads((cpp_dir / f"{stem}.json").read_text()) + shape = tuple(int(x) for x in meta["shape"]) # slow-to-fast + data = np.fromfile(cpp_dir / f"{stem}.f32", dtype=" int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--ref-dir", required=True, help="NeMo --dump-compress output dir") + ap.add_argument("--cpp-dir", required=True, help="C++ TRANSCRIBE_DUMP_DIR (compress.* artifacts)") + ap.add_argument("--preds-tol", type=float, default=1e-2, + help="Max allowed |Δ| on gathered spkcache_preds (F32 drift).") + ap.add_argument("--spkcache-len", type=int, default=188, + help="spkcache_len for the numpy adjudicator (188 for the shipped presets, " + "24 for `small`). Only used when input_preds dumps are present.") + args = ap.parse_args() + + ref_dir, cpp_dir = Path(args.ref_dir), Path(args.cpp_dir) + ref_calls = sorted(ref_dir.glob("compress.*.topk_indices.npy")) + cpp_calls = sorted(cpp_dir.glob("compress.*.frame_idx.json")) + n_ref, n_cpp = len(ref_calls), len(cpp_calls) + print(f"compression calls: ref={n_ref} cpp={n_cpp}") + if n_ref == 0: + raise SystemExit("error: no reference compression dumps found " + "(did the preset actually trigger AOSC compression?)") + if n_ref != n_cpp: + print(f"MISMATCH: call count differs (ref {n_ref} vs cpp {n_cpp}) -> streaming " + "trajectories diverged before/around compression", file=sys.stderr) + return 1 + + all_idx_ok = True + all_dis_ok = True + max_preds = 0.0 + # Adjudicator bookkeeping: does each side's own dumped selection match an + # independent numpy reimplementation run on that side's own input preds? + ref_self_ok = cpp_self_ok = True + ref_self_seen = cpp_self_seen = False + first_traj_div = None # first call where ref/cpp selections diverge + for k in range(n_ref): + ref_idx = np.load(ref_dir / f"compress.{k:03d}.topk_indices.npy").astype(np.int64) + ref_dis = np.load(ref_dir / f"compress.{k:03d}.is_disabled.npy").astype(bool) + cpp_idx = _load_cpp_f32(cpp_dir, f"compress.{k:03d}.frame_idx").astype(np.int64) + cpp_dis = _load_cpp_f32(cpp_dir, f"compress.{k:03d}.is_disabled").astype(bool) + + # Independent-arbiter check: run the numpy selection on each side's own + # input preds and confirm it reproduces that side's dumped selection. + ref_ipf = ref_dir / f"compress.{k:03d}.input_preds.npy" + cpp_ipf = cpp_dir / f"compress.{k:03d}.input_preds.json" + if ref_ipf.exists(): + ref_self_seen = True + nf, nd = select_indices(np.load(ref_ipf).astype(np.float32), args.spkcache_len) + if int(np.sum(nf != ref_idx)) or int(np.sum(nd != ref_dis)): + ref_self_ok = False + print(f" [{k:03d}] numpy-arbiter != NeMo dump " + f"(idx {int(np.sum(nf != ref_idx))}, dis {int(np.sum(nd != ref_dis))})", file=sys.stderr) + if cpp_ipf.exists(): + cpp_self_seen = True + nf, nd = select_indices(_load_cpp_f32(cpp_dir, f"compress.{k:03d}.input_preds"), args.spkcache_len) + if int(np.sum(nf != cpp_idx)) or int(np.sum(nd != cpp_dis)): + cpp_self_ok = False + print(f" [{k:03d}] numpy-arbiter != C++ dump " + f"(idx {int(np.sum(nf != cpp_idx))}, dis {int(np.sum(nd != cpp_dis))})", file=sys.stderr) + + if ref_idx.shape != cpp_idx.shape: + print(f" [{k:03d}] SHAPE MISMATCH idx ref{ref_idx.shape} cpp{cpp_idx.shape}", file=sys.stderr) + all_idx_ok = False + continue + idx_mism = int(np.sum(ref_idx != cpp_idx)) + dis_mism = int(np.sum(ref_dis != cpp_dis)) + all_idx_ok = all_idx_ok and idx_mism == 0 + all_dis_ok = all_dis_ok and dis_mism == 0 + if idx_mism and first_traj_div is None: + first_traj_div = k + + preds_line = "" + ref_pf = ref_dir / f"compress.{k:03d}.spkcache_preds.npy" + cpp_pf = cpp_dir / f"compress.{k:03d}.spkcache_preds.json" + if ref_pf.exists() and cpp_pf.exists(): + ref_p = np.load(ref_pf).astype(np.float32) + cpp_p = _load_cpp_f32(cpp_dir, f"compress.{k:03d}.spkcache_preds") + if ref_p.shape == cpp_p.shape: + m = float(np.max(np.abs(ref_p - cpp_p))) if ref_p.size else 0.0 + max_preds = max(max_preds, m) + preds_line = f" preds_max_abs {m:.2e}" + else: + preds_line = f" preds SHAPE MISMATCH ref{ref_p.shape} cpp{cpp_p.shape}" + + flag = "OK " if (idx_mism == 0 and dis_mism == 0) else "*** " + print(f" [{k:03d}] {flag}n={ref_idx.shape[0]:4d} idx_mismatch={idx_mism:4d} " + f"dis_mismatch={dis_mism:4d}{preds_line}") + + print() + print("== end-to-end trajectory (each side fed its OWN drifted preds) ==") + print(f"frame_idx : {'MATCH (bit-exact)' if all_idx_ok else 'MISMATCH'}" + + ("" if all_idx_ok else f" (first divergence at call {first_traj_div})")) + print(f"is_disabled : {'MATCH (bit-exact)' if all_dis_ok else 'MISMATCH'}") + print(f"spkcache_preds max_abs: {max_preds:.2e} (tol {args.preds_tol:.1e})") + + # The definitive residual-#1 check: is the compression FUNCTION correct? + # Independent numpy arbiter vs each side's own dump on that side's own input. + print() + print("== compression function (independent numpy arbiter, per-side own input) ==") + if ref_self_seen: + print(f"NeMo selection == numpy arbiter : {'MATCH' if ref_self_ok else 'MISMATCH'}") + if cpp_self_seen: + print(f"C++ selection == numpy arbiter : {'MATCH' if cpp_self_ok else 'MISMATCH'}") + if not (ref_self_seen and cpp_self_seen): + print("(input_preds dumps absent on one/both sides -> function-level arbiter skipped; " + "re-dump with the updated hooks to enable it)") + + # PASS criteria: if input_preds are present, the compression function is + # proven bit-exact against a neutral arbiter on both sides' real inputs + # (trajectory drift is expected and does NOT fail the check). If they are + # absent, fall back to end-to-end bit-exactness. + print() + if ref_self_seen and cpp_self_seen: + ok = ref_self_ok and cpp_self_ok + note = ("compression function bit-exact on both sides vs neutral arbiter" + if ok else "compression function disagrees with arbiter") + if ok and not all_idx_ok: + note += (f"; end-to-end trajectories diverge from call {first_traj_div} " + "purely via upstream F32 preds drift (near-tie topk flips)") + else: + ok = all_idx_ok and all_dis_ok and max_preds <= args.preds_tol + note = "compression internal state matches" if ok else "internal state mismatch" + print("VERDICT:", ("PASS - " + note) if ok else ("FAIL - " + note)) + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/fetch_ami_forced_alignment.py b/scripts/diar/fetch_ami_forced_alignment.py new file mode 100644 index 00000000..5f257e18 --- /dev/null +++ b/scripts/diar/fetch_ami_forced_alignment.py @@ -0,0 +1,73 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [] +# /// +""" +fetch_ami_forced_alignment.py - fetch NVIDIA's forced-alignment AMI RTTMs and +build a forced-alignment acceptance manifest. + +NVIDIA's published AMI DER uses tight forced-alignment `only_words` RTTMs +(nttcslab-sp/diar-forced-alignment, ASRU 2025), NOT the denser manual +annotations shipped in diarizers-community/ami. Scoring against the manual +RTTMs inflates DER by ~13 pts (missed detection on within-utterance pauses); +scoring against these forced-alignment RTTMs reproduces the published number. + +Reads samples/diar/ami--.manifest.jsonl (from ingest_ami.py), +downloads the matching forced-alignment RTTM per meeting, and writes: + + samples/diar/ami---fa/.rttm + samples/diar/ami---fa.manifest.jsonl (same audio, FA rttm) + +Usage: + uv run scripts/diar/fetch_ami_forced_alignment.py --config ihm --split test +""" + +from __future__ import annotations + +import argparse +import json +import sys +import urllib.request +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent.parent +RAW = "https://raw.githubusercontent.com/nttcslab-sp/diar-forced-alignment/master/AMI/{split}/{meeting}.rttm" +# nttcslab uses the AMI train/dev/eval split; diarizers-community uses train/validation/test. +SPLIT_MAP = {"test": "test", "validation": "dev", "train": "train"} + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--config", default="ihm", choices=["ihm", "sdm"]) + ap.add_argument("--split", default="test") + args = ap.parse_args() + + ds_id = f"ami-{args.config}-{args.split}" + src_manifest = REPO / "samples" / "diar" / f"{ds_id}.manifest.jsonl" + if not src_manifest.exists(): + print(f"error: {src_manifest} not found; run ingest_ami.py first.", file=sys.stderr) + return 1 + fa_split = SPLIT_MAP[args.split] + fa_dir = REPO / "samples" / "diar" / f"{ds_id}-fa" + fa_dir.mkdir(parents=True, exist_ok=True) + + rows = [json.loads(l) for l in open(src_manifest) if l.strip()] + out = [] + for r in rows: + meeting = r["id"].replace(".Mix-Headset", "").replace(".Array1-01", "") + url = RAW.format(split=fa_split, meeting=meeting) + dst = fa_dir / f"{meeting}.rttm" + urllib.request.urlretrieve(url, dst) + n = sum(1 for l in dst.read_text().splitlines() if l.startswith("SPEAKER")) + out.append({**r, "rttm": str(dst.relative_to(REPO))}) + print(f" {meeting}: {n} forced-alignment turns", flush=True) + + fa_manifest = REPO / "samples" / "diar" / f"{ds_id}-fa.manifest.jsonl" + fa_manifest.write_text("\n".join(json.dumps(o) for o in out) + "\n") + print(f"wrote {fa_manifest.relative_to(REPO)} ({len(out)} meetings)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/ingest_ami.py b/scripts/diar/ingest_ami.py new file mode 100644 index 00000000..64f12328 --- /dev/null +++ b/scripts/diar/ingest_ami.py @@ -0,0 +1,127 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "datasets>=3.6", +# "numpy>=1.26", +# "soundfile>=0.12", +# ] +# /// +""" +ingest_ami.py - build an AMI diarization manifest from diarizers-community/ami. + +Diarization analog of scripts/wer/ingest.py. Sortformer has no transcript, +so the acceptance corpus needs multi-speaker audio + ground-truth speaker +RTTMs, not reference text. This pulls the AMI corpus from the HF dataset +diarizers-community/ami (ungated), which ships one full meeting per row with +`audio` + parallel `timestamps_start` / `timestamps_end` / `speakers` lists. + +Config `ihm` = mixed-headset audio (NVIDIA's "AMI Test IHM" condition); +`sdm` = single distant mic. + +Output (mirrors the wer/ layout): + samples/diar/ami--/.wav 16-bit PCM mono 16 kHz + samples/diar/ami--/.rttm ground-truth speaker turns + samples/diar/ami--.manifest.jsonl + {"id","audio","rttm","duration","num_speakers"} + +Usage: + uv run scripts/diar/ingest_ami.py --config ihm --split test +""" + +from __future__ import annotations + +import argparse +import io +import json +import sys +from pathlib import Path + +import numpy as np +import soundfile as sf + +SR = 16000 +REPO = Path(__file__).resolve().parent.parent.parent + + +def _uri(path: str) -> str: + # "EN2002b.Mix-Headset.wav" -> "EN2002b.Mix-Headset" + return Path(path).name.rsplit(".wav", 1)[0] + + +def _decode_16k_mono(raw: bytes) -> np.ndarray: + audio, sr = sf.read(io.BytesIO(raw), dtype="float32", always_2d=False) + if audio.ndim > 1: + audio = audio.mean(axis=1) + if sr != SR: + n_out = int(round(len(audio) * SR / sr)) + x_old = np.linspace(0.0, 1.0, num=len(audio), endpoint=False) + x_new = np.linspace(0.0, 1.0, num=n_out, endpoint=False) + audio = np.interp(x_new, x_old, audio).astype(np.float32) + return audio.astype(np.float32) + + +def _write_rttm(path: Path, uri: str, starts, ends, speakers) -> int: + lines = [] + for s, e, spk in zip(starts, ends, speakers): + dur = float(e) - float(s) + if dur <= 0: + continue + lines.append(f"SPEAKER {uri} 1 {float(s):.3f} {dur:.3f} {spk} ") + path.write_text("\n".join(lines) + "\n") + return len(lines) + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--config", default="ihm", choices=["ihm", "sdm"]) + ap.add_argument("--split", default="test") + ap.add_argument("--force", action="store_true") + args = ap.parse_args() + + from datasets import load_dataset, Audio + + ds_id = f"ami-{args.config}-{args.split}" + out_dir = REPO / "samples" / "diar" / ds_id + manifest = REPO / "samples" / "diar" / f"{ds_id}.manifest.jsonl" + out_dir.mkdir(parents=True, exist_ok=True) + if manifest.exists() and not args.force: + n = sum(1 for _ in open(manifest)) + print(f"OK already exists: {manifest} ({n} meetings). Use --force to rebuild.") + return 0 + + print(f"loading diarizers-community/ami [{args.config}/{args.split}] ...", flush=True) + ds = load_dataset("diarizers-community/ami", args.config, split=args.split) + ds = ds.cast_column("audio", Audio(decode=False)) + + entries = [] + total_dur = 0.0 + for i, ex in enumerate(ds): + uri = _uri(ex["audio"]["path"] or f"{ds_id}-{i:03d}") + audio = _decode_16k_mono(ex["audio"]["bytes"]) + wav = out_dir / f"{uri}.wav" + rttm = out_dir / f"{uri}.rttm" + sf.write(str(wav), audio, SR, subtype="PCM_16") + n_turns = _write_rttm(rttm, uri, ex["timestamps_start"], ex["timestamps_end"], ex["speakers"]) + dur = len(audio) / SR + total_dur += dur + n_spk = len(set(ex["speakers"])) + entries.append({ + "id": uri, + "audio": str(wav.relative_to(REPO)), + "rttm": str(rttm.relative_to(REPO)), + "duration": round(dur, 2), + "num_speakers": n_spk, + }) + print(f" [{i}] {uri}: {dur/60:.1f}min, {n_spk} spk, {n_turns} turns", flush=True) + + with open(manifest, "w") as f: + for e in entries: + f.write(json.dumps(e) + "\n") + print(f"\nmanifest: {manifest.relative_to(REPO)}") + print(f"{len(entries)} meetings, {total_dur/60:.1f} min total audio") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_callhome-part1.yaml b/scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_callhome-part1.yaml new file mode 100644 index 00000000..6083004f --- /dev/null +++ b/scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_callhome-part1.yaml @@ -0,0 +1,11 @@ +# Postprocessing parameters for timestamp outputs from speaker diarization models. +# This speaker diarization postprocessing scheme is inspired by the postprocessing procedure in the following paper: +# Medennikov, Ivan, et al. "Target-Speaker Voice Activity Detection: a Novel Approach for Multi-Speaker Diarization in a Dinner Party Scenario." (2020). +# These parameters were optimized on CallHome Dataset from the NIST SRE 2000 Disc8, especially from the part1 (callhome1) specified in: Kaldi, “Kaldi x-vector recipe v2,” https://github.com/kaldi-asr/kaldi/blob/master/egs/callhome_diarization/v2/run.sh +parameters: + onset: 0.641 # Onset threshold for detecting the beginning and end of a speech + offset: 0.561 # Offset threshold for detecting the end of a speech + pad_onset: 0.229 # Adding durations before each speech segment + pad_offset: 0.079 # Adding durations after each speech segment + min_duration_on: 0.511 # Threshold for small non-speech deletion + min_duration_off: 0.296 # Threshold for short speech segment deletion \ No newline at end of file diff --git a/scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml b/scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml new file mode 100644 index 00000000..a9e10471 --- /dev/null +++ b/scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml @@ -0,0 +1,11 @@ +# Postprocessing parameters for timestamp outputs from speaker diarization models. +# This speaker diarization postprocessing scheme is inspired by the postprocessing procedure in the following paper: +# Medennikov, Ivan, et al. "Target-Speaker Voice Activity Detection: a Novel Approach for Multi-Speaker Diarization in a Dinner Party Scenario." (2020). +# These parameters were optimized on the development split of DIHARD3 dataset (See https://arxiv.org/pdf/2012.01477). +parameters: + onset: 0.56 # Onset threshold for detecting the beginning and end of a speech + offset: 1.0 # Offset threshold for detecting the end of a speech + pad_onset: 0.063 # Adding durations before each speech segment + pad_offset: 0.002 # Adding durations after each speech segment + min_duration_on: 0.007 # Threshold for small non-speech deletion + min_duration_off: 0.151 # Threshold for short speech segment deletion \ No newline at end of file diff --git a/scripts/diar/run_cpp_sortformer.py b/scripts/diar/run_cpp_sortformer.py new file mode 100644 index 00000000..f9067ae9 --- /dev/null +++ b/scripts/diar/run_cpp_sortformer.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +run_cpp_sortformer.py - C++ Sortformer diarization over an AMI-style manifest +(the C++ counterpart of run_reference_sortformer_nemo.py). + +For each meeting this runs the transcribe-cli C++ binary with the streaming +operating point selected (TRANSCRIBE_SORTFORMER_STREAM_PRESET) and the tensor +dump enabled (TRANSCRIBE_DUMP_DIR). The dumped raw `diar.probs` [T, n_spk] +sigmoid matrix is then post-processed with NeMo's *own* `ts_vad_post_processing` +(same dihard3 YAML the reference uses) so the timestamp post-processing is +bit-identical to the reference; only the probabilities differ. The resulting +per-speaker segments are written as one predicted RTTM per meeting. + +Score with scripts/diar/score_der.py against the forced-alignment RTTMs, e.g.: + + uv run --project scripts/envs/sortformer \ + scripts/diar/run_cpp_sortformer.py \ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ + --gguf models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf \ + --preset very_high_latency \ + --postprocessing-yaml scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml \ + --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test.jsonl + + uv run scripts/diar/score_der.py \ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ + --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test.score.json +""" + +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +import numpy as np + +# Diar frame = 80 ms = 8 * 10 ms (subsampling_factor). +UNIT_10MS_FRAME_COUNT = 8 + + +def _find_cli(repo: Path, override: str | None) -> Path: + if override: + p = Path(override) + if not p.exists(): + raise SystemExit(f"error: --cli not found: {p}") + return p + for cand in (repo / "build" / "bin" / "transcribe-cli", repo / "build" / "transcribe-cli"): + if cand.exists(): + return cand + raise SystemExit("error: transcribe-cli not found; build it first " + "(cmake --build build --target transcribe-cli)") + + +def _load_probs(dump_dir: Path) -> np.ndarray: + """Load the dumped diar.probs as [T, n_spk] float32.""" + meta = json.loads((dump_dir / "diar.probs.json").read_text()) + shape = tuple(int(x) for x in meta["shape"]) # slow-to-fast, e.g. [T, n_spk] + data = np.fromfile(dump_dir / "diar.probs.f32", dtype=" list[str]: + """Apply NeMo ts_vad_post_processing per speaker and emit RTTM lines.""" + import torch + from nemo.collections.asr.parts.utils.vad_utils import ts_vad_post_processing + + lines: list[str] = [] + n_spk = probs.shape[1] + for spk in range(n_spk): + ts_vad_vec = torch.from_numpy(np.ascontiguousarray(probs[:, spk])) + ts_mat = ts_vad_post_processing( + ts_vad_vec, + cfg_vad_params=cfg_vad, + unit_10ms_frame_count=UNIT_10MS_FRAME_COUNT, + bypass_postprocessing=False, + ) + for stt, end in ts_mat.tolist(): + stt = round(stt + offset, 2) + end = round(end + offset, 2) + dur = end - stt + if dur <= 0: + continue + lines.append(f"SPEAKER {uri} 1 {stt:.3f} {dur:.3f} speaker_{spk} ") + return lines + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--manifest", required=True) + ap.add_argument("--gguf", required=True) + ap.add_argument("--pred-dir", required=True) + ap.add_argument("--out", required=True) + ap.add_argument("--cli", default=None, help="Path to transcribe-cli (default: build/bin/transcribe-cli)") + ap.add_argument("--preset", default="very_high_latency", + help="Streaming operating point (TRANSCRIBE_SORTFORMER_STREAM_PRESET)") + ap.add_argument("--postprocessing-yaml", default=None, + help="dihard3 PP YAML (onset/offset/pad/min_duration); bypass PP if omitted.") + ap.add_argument("--backend", default="cpu") + ap.add_argument("--threads", type=int, default=0, help="0 -> CLI default") + ap.add_argument("--limit", type=int, default=0, help="Run only the first N meetings (smoke).") + args = ap.parse_args() + + from omegaconf import OmegaConf + + repo = Path(__file__).resolve().parent.parent.parent + cli = _find_cli(repo, args.cli) + entries = [json.loads(l) for l in open(args.manifest) if l.strip()] + if args.limit: + entries = entries[:args.limit] + pred_dir = Path(args.pred_dir) + pred_dir.mkdir(parents=True, exist_ok=True) + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + + if args.postprocessing_yaml: + pp = OmegaConf.load(args.postprocessing_yaml) + cfg_vad = pp.parameters if "parameters" in pp else pp + bypass = False + else: + cfg_vad = OmegaConf.create({"onset": 0.5, "offset": 0.5, "pad_onset": 0.0, "pad_offset": 0.0, + "min_duration_on": 0.0, "min_duration_off": 0.0}) + bypass = True + if bypass: + print("warning: no --postprocessing-yaml; using bypass thresholds (not the DER gate config)", flush=True) + + rows = [] + for i, e in enumerate(entries): + wav = str(repo / e["audio"]) if not Path(e["audio"]).is_absolute() else e["audio"] + uri = e["id"] + t0 = time.time() + with tempfile.TemporaryDirectory(prefix="sf-cpp-") as tmp: + env = os.environ.copy() + env["TRANSCRIBE_DUMP_DIR"] = tmp + env["TRANSCRIBE_SORTFORMER_STREAM_PRESET"] = args.preset + cmd = [str(cli), "--backend", args.backend, "-m", str(args.gguf)] + if args.threads > 0: + cmd += ["--threads", str(args.threads)] + cmd.append(wav) + res = subprocess.run(cmd, cwd=repo, env=env, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, errors="replace") + if res.returncode != 0: + sys.stderr.write(res.stdout or "") + raise SystemExit(f"error: transcribe-cli failed on {uri} (exit {res.returncode})") + probs = _load_probs(Path(tmp)) + lines = _probs_to_rttm(uri, probs, cfg_vad, offset=float(e.get("offset", 0.0))) + (pred_dir / f"{uri}.rttm").write_text("\n".join(lines) + "\n") + dt = time.time() - t0 + rtf = dt / max(e.get("duration", 1.0), 1e-6) + rows.append({"id": uri, "hyp_rttm": str(Path(args.pred_dir) / f"{uri}.rttm"), + "n_segments": len(lines), "n_frames": int(probs.shape[0]), + "duration": e.get("duration"), "compute_sec": round(dt, 2), "rtf": round(rtf, 4)}) + print(f" [{i}] {uri}: {len(lines)} segs, {probs.shape[0]} frames, {dt:.1f}s (rtf {rtf:.3f})", flush=True) + + with open(out_path, "w") as f: + for r in rows: + f.write(json.dumps(r) + "\n") + print(f"wrote {out_path} ({len(rows)} meetings)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/run_reference_sortformer_nemo.py b/scripts/diar/run_reference_sortformer_nemo.py new file mode 100644 index 00000000..334272a6 --- /dev/null +++ b/scripts/diar/run_reference_sortformer_nemo.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +""" +run_reference_sortformer_nemo.py - measured Sortformer reference baseline +over a diarization manifest (diarization analog of +scripts/wer/run_reference__*.py). + +Runs NeMo streaming Sortformer over every meeting in an AMI-style manifest +and writes one predicted RTTM per meeting plus a summary JSONL. Score with +scripts/diar/score_der.py. This is the ONE-TIME reference run: Stage 4 and +Stage 7 compare the C++ port's DER/JER against this measured baseline, not +against the publisher's number. + +Streaming presets (frames are 80 ms) from the v2.1 model card: + very_high_latency 30.4s chunk_len=340 rc=40 fifo=40 update=300 spkcache=188 (fastest; card AMI-IHM DER 15.90) + low_latency 1.04s chunk_len=6 rc=7 fifo=188 update=144 spkcache=188 (card AMI-IHM DER 16.67) + +Run through the Sortformer reference env: + uv run --project scripts/envs/sortformer \ + scripts/diar/run_reference_sortformer_nemo.py \ + --manifest samples/diar/ami-ihm-test.manifest.jsonl \ + --model nvidia/diar_streaming_sortformer_4spk-v2.1 \ + --preset very_high_latency \ + --pred-dir reports/diar/pred/streaming-4spk-v2.1-ami-ihm-test \ + --out reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test.jsonl +""" + +from __future__ import annotations + +import argparse +import json +import sys +import time +from pathlib import Path + +PRESETS = { + "very_high_latency": dict(chunk_len=340, chunk_right_context=40, fifo_len=40, + spkcache_update_period=300, spkcache_len=188), + "high_latency": dict(chunk_len=124, chunk_right_context=1, fifo_len=124, + spkcache_update_period=124, spkcache_len=188), + "low_latency": dict(chunk_len=6, chunk_right_context=7, fifo_len=188, + spkcache_update_period=144, spkcache_len=188), +} + + +def _apply_preset(m, preset: str) -> dict: + cfg = PRESETS[preset] + sm = m.sortformer_modules + sm.chunk_len = cfg["chunk_len"] + sm.chunk_right_context = cfg["chunk_right_context"] + sm.fifo_len = cfg["fifo_len"] + sm.spkcache_update_period = cfg["spkcache_update_period"] + sm.spkcache_len = cfg["spkcache_len"] + if hasattr(sm, "_check_streaming_parameters"): + sm._check_streaming_parameters() + return cfg + + +def _seg_to_rttm(uri: str, segments) -> list[str]: + """NeMo diarize segments str() as 'start end speaker_k'.""" + lines = [] + for s in segments: + parts = str(s).strip().split() + if len(parts) < 3: + continue + start, end, spk = float(parts[0]), float(parts[1]), parts[2] + dur = end - start + if dur <= 0: + continue + lines.append(f"SPEAKER {uri} 1 {start:.3f} {dur:.3f} {spk} ") + return lines + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--manifest", required=True) + ap.add_argument("--model", required=True) + ap.add_argument("--preset", default="very_high_latency", choices=list(PRESETS)) + ap.add_argument("--pred-dir", required=True) + ap.add_argument("--out", required=True) + ap.add_argument("--device", default="cpu") + ap.add_argument("--postprocessing-yaml", default=None, + help="Optional NeMo diarization post-processing YAML (onset/offset/pad/min_duration).") + ap.add_argument("--limit", type=int, default=0, help="Run only the first N meetings (smoke).") + args = ap.parse_args() + + import torch + from nemo.collections.asr.models import SortformerEncLabelModel + + repo = Path(__file__).resolve().parent.parent.parent + entries = [json.loads(l) for l in open(args.manifest) if l.strip()] + if args.limit: + entries = entries[:args.limit] + pred_dir = Path(args.pred_dir) + pred_dir.mkdir(parents=True, exist_ok=True) + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + + m = SortformerEncLabelModel.from_pretrained(args.model, map_location=args.device) + m.eval() + cfg = _apply_preset(m, args.preset) + print(f"preset={args.preset} {cfg}", flush=True) + + rows = [] + for i, e in enumerate(entries): + wav = str(repo / e["audio"]) if not Path(e["audio"]).is_absolute() else e["audio"] + uri = e["id"] + t0 = time.time() + kw = {} + if args.postprocessing_yaml: + kw["postprocessing_yaml"] = args.postprocessing_yaml + segs = m.diarize(audio=[wav], batch_size=1, **kw) + dt = time.time() - t0 + seglist = segs[0] if segs else [] + lines = _seg_to_rttm(uri, seglist) + (pred_dir / f"{uri}.rttm").write_text("\n".join(lines) + "\n") + rtf = dt / max(e.get("duration", 1.0), 1e-6) + rows.append({"id": uri, "hyp_rttm": str(Path(args.pred_dir) / f"{uri}.rttm"), + "n_segments": len(lines), "duration": e.get("duration"), + "compute_sec": round(dt, 2), "rtf": round(rtf, 4)}) + print(f" [{i}] {uri}: {len(lines)} segs, {dt:.1f}s (rtf {rtf:.3f})", flush=True) + + with open(out_path, "w") as f: + for r in rows: + f.write(json.dumps(r) + "\n") + print(f"wrote {out_path} ({len(rows)} meetings)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/diar/score_der.py b/scripts/diar/score_der.py new file mode 100644 index 00000000..85d5a21b --- /dev/null +++ b/scripts/diar/score_der.py @@ -0,0 +1,130 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.11" +# dependencies = [ +# "pyannote.metrics>=3.2", +# ] +# /// +""" +score_der.py - Diarization Error Rate + Jaccard Error Rate scorer. + +Diarization analog of scripts/wer/score.py. Compares predicted speaker +RTTMs (from scripts/diar/run_reference_sortformer_nemo.py, or later the C++ +port) against the ground-truth RTTMs named in an AMI-style manifest. + +DER and JER are computed with pyannote.metrics. Collar and overlap handling +mirror NVIDIA's AMI protocol: collar 0.0s, overlap INCLUDED (skip_overlap +False). The aggregate is pyannote's speech-time-weighted global figure, not +a mean of per-meeting rates. + +Writes .score.json: + {metric:"der", collar, skip_overlap, der, jer, der_pct, jer_pct, + n_meetings, total_speech_sec, missed_pct, false_alarm_pct, + confusion_pct, per_meeting:[{id, der, jer, ...}]} + +Usage: + uv run scripts/diar/score_der.py \ + --manifest samples/diar/ami-ihm-test.manifest.jsonl \ + --pred-dir reports/diar/pred/streaming-4spk-v2.1-ami-ihm-test \ + --out reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test.score.json +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +from pyannote.core import Annotation, Segment +from pyannote.metrics.diarization import DiarizationErrorRate, JaccardErrorRate + + +def load_rttm(path: Path, uri: str) -> Annotation: + ann = Annotation(uri=uri) + if not path.exists(): + return ann + for line in path.read_text().splitlines(): + p = line.split() + if len(p) < 8 or p[0] != "SPEAKER": + continue + start, dur, spk = float(p[3]), float(p[4]), p[7] + if dur <= 0: + continue + ann[Segment(start, start + dur)] = spk + return ann + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--manifest", required=True) + ap.add_argument("--pred-dir", required=True) + ap.add_argument("--out", required=True) + ap.add_argument("--collar", type=float, default=0.0) + ap.add_argument("--skip-overlap", action="store_true", + help="Exclude overlapped regions from scoring (default: include, matching NVIDIA AMI).") + args = ap.parse_args() + + repo = Path(__file__).resolve().parent.parent.parent + pred_dir = Path(args.pred_dir) + entries = [json.loads(l) for l in open(args.manifest) if l.strip()] + + der = DiarizationErrorRate(collar=args.collar, skip_overlap=args.skip_overlap) + jer = JaccardErrorRate(collar=args.collar, skip_overlap=args.skip_overlap) + + per_meeting = [] + for e in entries: + uri = e["id"] + ref = load_rttm(repo / e["rttm"], uri) + hyp = load_rttm(pred_dir / f"{uri}.rttm", uri) + comp = der(ref, hyp, detailed=True) + d_i = comp["diarization error rate"] + j_i = jer(ref, hyp) + per_meeting.append({ + "id": uri, + "der": round(d_i, 5), + "jer": round(j_i, 5), + "missed": round(comp["missed detection"], 3), + "false_alarm": round(comp["false alarm"], 3), + "confusion": round(comp["confusion"], 3), + "total": round(comp["total"], 3), + "num_speakers": e.get("num_speakers"), + }) + print(f" {uri}: DER {d_i*100:.2f}% JER {j_i*100:.2f}%", flush=True) + + # Global (speech-time-weighted) aggregates. + agg_der = abs(der) + agg_jer = abs(jer) + comp = der[:] # accumulated components + total = sum(pm["total"] for pm in per_meeting) + missed = sum(pm["missed"] for pm in per_meeting) + fa = sum(pm["false_alarm"] for pm in per_meeting) + conf = sum(pm["confusion"] for pm in per_meeting) + + out = { + "metric": "der", + "collar": args.collar, + "skip_overlap": bool(args.skip_overlap), + "der": round(agg_der, 5), + "jer": round(agg_jer, 5), + "der_pct": round(agg_der * 100, 2), + "jer_pct": round(agg_jer * 100, 2), + "n_meetings": len(per_meeting), + "total_speech_sec": round(total, 2), + "missed_pct": round(missed / total * 100, 2) if total else None, + "false_alarm_pct": round(fa / total * 100, 2) if total else None, + "confusion_pct": round(conf / total * 100, 2) if total else None, + "per_meeting": per_meeting, + } + out_path = Path(args.out) + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(json.dumps(out, indent=2) + "\n") + print(f"\nAGGREGATE DER {out['der_pct']}% JER {out['jer_pct']}% " + f"(missed {out['missed_pct']}%, FA {out['false_alarm_pct']}%, conf {out['confusion_pct']}%) " + f"over {out['n_meetings']} meetings") + print(f"wrote {out_path}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/dump_reference_sortformer_nemo.py b/scripts/dump_reference_sortformer_nemo.py new file mode 100644 index 00000000..bd3121b3 --- /dev/null +++ b/scripts/dump_reference_sortformer_nemo.py @@ -0,0 +1,299 @@ +#!/usr/bin/env python3 +""" +dump_reference_sortformer_nemo.py - Sortformer streaming diarizer reference +tensors from NVIDIA NeMo (the canonical reference). + +Sortformer is a frame-level end-to-end neural diarizer (arch pattern +`encoder-diarizer`), NOT a transcription model. There is no tokenizer and +no text output: the reference "behavioral artifact" is the T x 4 speaker +activity probability matrix plus the derived speaker segments. + +Pipeline (offline / full-context forward): + preprocessor (mel, 128 x T_mel) + -> encoder ConformerEncoder / NEST-FastConformer, d=512, x8 subsample + -> encoder_proj Linear 512 -> 192 (sortformer_modules.encoder_proj) + -> transformer_encoder 18L Transformer, d=192 + -> hidden_to_spks -> sigmoid -> preds [T, 4] + +Two reference paths are dumped: + * `encoder` runs the offline m.forward() with hooks and dumps the mel and + the per-stage encoder activations plus the offline preds. This + is the non-stateful reference the C++ encoder must match first. + * `diarize` runs the streaming m.diarize() (the runtime target) and dumps + the T x 4 probability matrix + the speaker segments. + +For short clips (T frames < chunk_len, e.g. the 12s oracle mix = 150 frames +< 188) the streaming path is a single chunk, so offline preds ~= streaming +probs; longer audio exercises the AOSC/FIFO speaker cache and the two will +diverge. + +Tensor output uses the shared contract via scripts.lib.ref_dump +(write_tensor records rms / p99_abs for Stage 6 magnitude-aware tolerances). + + uv run --project scripts/envs/sortformer \ + scripts/dump_reference_sortformer_nemo.py encoder \ + --model nvidia/diar_streaming_sortformer_4spk-v2.1 \ + --audio samples/sortformer-2spk-mix.wav \ + --out build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix/encoder/ref + + uv run --project scripts/envs/sortformer \ + scripts/dump_reference_sortformer_nemo.py diarize \ + --model nvidia/diar_streaming_sortformer_4spk-v2.1 \ + --audio samples/sortformer-2spk-mix.wav \ + --out build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix/diarize/ref +""" + +from __future__ import annotations + +import argparse +import sys +from pathlib import Path +from typing import Any + +import numpy as np +import soundfile as sf +import torch + +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from lib import ref_dump # noqa: E402 + +write_tensor = ref_dump.write_tensor +write_transcript = ref_dump.write_transcript + + +def _load(model: str): + from nemo.collections.asr.models import SortformerEncLabelModel + + if model.endswith(".nemo") or Path(model).exists(): + m = SortformerEncLabelModel.restore_from(restore_path=model, map_location="cpu", strict=False) + else: + m = SortformerEncLabelModel.from_pretrained(model, map_location="cpu") + m.eval() + return m + + +def _read_audio(path: str) -> tuple[np.ndarray, int]: + audio, sr = sf.read(path, dtype="float32", always_2d=False) + if audio.ndim > 1: + audio = audio.mean(axis=1) + return audio.astype(np.float32), int(sr) + + +def _to_2d_time_major(a: np.ndarray) -> np.ndarray: + """Squeeze batch and return [T, D]. ConformerEncoder emits [B, D, T]; + the transformer path emits [B, T, D]. Detect by which axis is the + known speaker/model dim is impossible generically, so we squeeze batch + and, when the first (non-batch) axis looks like a channel dim (512/192) + and the second is longer, transpose to time-major.""" + a = np.asarray(a, dtype=np.float32) + if a.ndim == 3 and a.shape[0] == 1: + a = a[0] + if a.ndim == 2 and a.shape[0] in (192, 512) and a.shape[1] != a.shape[0]: + a = a.T # [D, T] -> [T, D] + return np.ascontiguousarray(a.astype(np.float32)) + + +def _src(model: str, hook: str) -> dict[str, Any]: + return {"framework": "nemo", "model": model, "hook": hook} + + +def cmd_encoder(args: argparse.Namespace) -> int: + m = _load(args.model) + audio, sr = _read_audio(args.audio) + out_dir = Path(args.out) + sig = torch.tensor(audio).unsqueeze(0) + length = torch.tensor([audio.shape[0]]) + + # Mel (frontend) — dump native NeMo layout [n_mels, T_mel]. + with torch.no_grad(): + mel, mel_len = m.preprocessor(input_signal=sig, length=length) + write_tensor("enc.mel.in", np.ascontiguousarray(mel[0].numpy().astype(np.float32)), + "frontend", _src(args.model, "preprocessor.out"), out_dir=out_dir) + + captured: dict[str, torch.Tensor] = {} + + def grab(name): + def hook(_mod, _inp, out): + t = out[0] if isinstance(out, (tuple, list)) else out + captured[name] = t.detach().to(torch.float32).cpu() + return hook + + handles = [ + m.encoder.register_forward_hook(grab("fastconformer")), + m.sortformer_modules.encoder_proj.register_forward_hook(grab("encoder_proj")), + m.transformer_encoder.register_forward_hook(grab("transformer")), + ] + try: + with torch.no_grad(): + preds = m.forward(audio_signal=sig, audio_signal_length=length) + finally: + for h in handles: + h.remove() + + if isinstance(preds, (tuple, list)): + preds = preds[0] + write_tensor("enc.fastconformer.out", _to_2d_time_major(captured["fastconformer"].numpy()), + "encoder", _src(args.model, "encoder.out"), out_dir=out_dir) + write_tensor("enc.encoder_proj.out", _to_2d_time_major(captured["encoder_proj"].numpy()), + "encoder", _src(args.model, "encoder_proj.out"), out_dir=out_dir) + write_tensor("enc.transformer.out", _to_2d_time_major(captured["transformer"].numpy()), + "encoder", _src(args.model, "transformer_encoder.out"), out_dir=out_dir) + write_tensor("diar.preds_offline", _to_2d_time_major(preds.detach().numpy()), + "encoder", _src(args.model, "forward.preds(offline,full-context)"), out_dir=out_dir) + print(f"wrote encoder-stage tensors to {out_dir}") + return 0 + + +# Streaming presets (frames are 80 ms). Mirror scripts/diar/run_reference_ +# sortformer_nemo.py PRESETS plus a `small` preset that forces multi-chunk + +# AOSC compression on the short oracle clip (diar.probs parity). Keep in sync +# with the C++ preset table in src/arch/sortformer/stream.cpp. +_PRESETS = { + "very_high_latency": dict(chunk_len=340, chunk_right_context=40, fifo_len=40, + spkcache_update_period=300, spkcache_len=188), + "high_latency": dict(chunk_len=124, chunk_right_context=1, fifo_len=124, + spkcache_update_period=124, spkcache_len=188), + "low_latency": dict(chunk_len=6, chunk_right_context=7, fifo_len=188, + spkcache_update_period=144, spkcache_len=188), + "small": dict(chunk_len=20, chunk_right_context=1, fifo_len=10, + spkcache_update_period=20, spkcache_len=24), +} + + +def _apply_preset(m, preset: str) -> None: + cfg = _PRESETS[preset] + sm = m.sortformer_modules + sm.chunk_len = cfg["chunk_len"] + sm.chunk_right_context = cfg["chunk_right_context"] + sm.fifo_len = cfg["fifo_len"] + sm.spkcache_update_period = cfg["spkcache_update_period"] + sm.spkcache_len = cfg["spkcache_len"] + if hasattr(sm, "_check_streaming_parameters"): + sm._check_streaming_parameters() + + +def _install_compress_hooks(m) -> list[dict]: + """Wrap sortformer_modules._compress_spkcache / _get_topk_indices to record, + per compression call, the selected (sorted) frame indices, the is_disabled + mask, and the gathered spkcache_preds. batch=1 in the CLI path, so squeeze + the batch dim. Returns the list the hooks append to (one dict per call).""" + sm = m.sortformer_modules + calls: list[dict] = [] + orig_topk = sm._get_topk_indices + orig_compress = sm._compress_spkcache + + def wrapped_topk(scores): + topk_indices, is_disabled = orig_topk(scores) + calls.append({ + "n_frames": int(scores.shape[1]), + "topk_indices": topk_indices[0].detach().cpu().numpy().astype(np.int64), + "is_disabled": is_disabled[0].detach().cpu().numpy().astype(bool), + }) + return topk_indices, is_disabled + + def wrapped_compress(*a, **kw): + # input preds ([1, n_frames, n_spk]) drive the whole selection; capture + # them (positionally emb_seq, preds, mean_sil_emb, ... or by kw). + in_preds = kw["preds"] if "preds" in kw else a[1] + in_preds = in_preds[0].detach().cpu().numpy().astype(np.float32) + spkcache, spkcache_preds, spk_perm = orig_compress(*a, **kw) + if calls: # the just-appended call from wrapped_topk + calls[-1]["input_preds"] = in_preds + calls[-1]["spkcache_preds"] = spkcache_preds[0].detach().cpu().numpy().astype(np.float32) + return spkcache, spkcache_preds, spk_perm + + sm._get_topk_indices = wrapped_topk + sm._compress_spkcache = wrapped_compress + return calls + + +def _write_compress_dump(calls: list[dict], dump_dir: Path) -> None: + dump_dir.mkdir(parents=True, exist_ok=True) + for k, c in enumerate(calls): + np.save(dump_dir / f"compress.{k:03d}.topk_indices.npy", c["topk_indices"]) + np.save(dump_dir / f"compress.{k:03d}.is_disabled.npy", c["is_disabled"]) + if "spkcache_preds" in c: + np.save(dump_dir / f"compress.{k:03d}.spkcache_preds.npy", c["spkcache_preds"]) + if "input_preds" in c: + np.save(dump_dir / f"compress.{k:03d}.input_preds.npy", c["input_preds"]) + summary = {"n_calls": len(calls), "n_frames_in": [c["n_frames"] for c in calls]} + (dump_dir / "compress.summary.json").write_text(_json_dumps(summary)) + print(f"wrote {len(calls)} _compress_spkcache dumps to {dump_dir}") + + +def _json_dumps(obj) -> str: + import json + return json.dumps(obj, indent=2) + + +def cmd_diarize(args: argparse.Namespace) -> int: + m = _load(args.model) + audio, sr = _read_audio(args.audio) + out_dir = Path(args.out) + + if getattr(args, "preset", None): + _apply_preset(m, args.preset) + print(f"diarize preset={args.preset} {_PRESETS[args.preset]}", flush=True) + + compress_calls = _install_compress_hooks(m) if getattr(args, "dump_compress", None) else None + + segs, probs = m.diarize(audio=[audio], batch_size=1, sample_rate=sr, + include_tensor_outputs=True) + + if compress_calls is not None: + _write_compress_dump(compress_calls, Path(args.dump_compress)) + p = np.asarray(probs[0], dtype=np.float32) + if p.ndim == 3 and p.shape[0] == 1: + p = p[0] + p = np.ascontiguousarray(p.astype(np.float32)) # [T, 4] + write_tensor("diar.probs", p, "diarize", + {**_src(args.model, "diarize.probs(streaming)"), + "streaming_cfg": { + "chunk_len": int(m.sortformer_modules.chunk_len), + "fifo_len": int(m.sortformer_modules.fifo_len), + "spkcache_len": int(m.sortformer_modules.spkcache_len), + "spkcache_update_period": int(m.sortformer_modules.spkcache_update_period), + "chunk_right_context": int(getattr(m.sortformer_modules, "chunk_right_context", 0)), + }}, + out_dir=out_dir) + + # Behavioral artifact: speaker segments, as RTTM-like text (no transcript + # exists for a diarizer). "start end speaker" per line, emission order. + seg_lines = [str(s).strip() for s in segs[0]] + write_transcript(out_dir, "\n".join(seg_lines), + source=_src(args.model, "diarize.segments(streaming)")) + print(f"wrote diarize probs [{p.shape[0]}x{p.shape[1]}] + {len(seg_lines)} segments to {out_dir}") + return 0 + + +def add_common_args(p: argparse.ArgumentParser) -> None: + p.add_argument("--model", required=True, help="HF repo id or path to a .nemo checkpoint") + p.add_argument("--audio", required=True, help="Path to a 16 kHz mono WAV") + p.add_argument("--out", required=True, help="Output ref/ directory") + # Accepted for the validate.py harness contract; a diarizer takes no + # language, and torch threading does not affect the dumped tensors. + p.add_argument("--torch-threads", type=int, default=None, help=argparse.SUPPRESS) + p.add_argument("--language", default=None, help=argparse.SUPPRESS) + + +def main() -> int: + p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + sub = p.add_subparsers(dest="cmd", required=True) + ep = sub.add_parser("encoder", help="Dump mel + encoder-stage activations (offline forward)") + add_common_args(ep) + ep.set_defaults(func=cmd_encoder) + dp = sub.add_parser("diarize", help="Dump streaming T x 4 probs + speaker segments") + add_common_args(dp) + dp.add_argument("--preset", default=None, choices=list(_PRESETS), + help="Streaming operating point (default: checkpoint cfg). Must match the C++ " + "TRANSCRIBE_SORTFORMER_STREAM_PRESET for parity.") + dp.add_argument("--dump-compress", default=None, + help="Directory to write per-_compress_spkcache selected frame indices + preds " + "(residual-uncertainty #1: compression internal-state parity vs C++).") + dp.set_defaults(func=cmd_diarize) + args = p.parse_args() + return args.func(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/envs/sortformer/pyproject.toml b/scripts/envs/sortformer/pyproject.toml new file mode 100644 index 00000000..0e402747 --- /dev/null +++ b/scripts/envs/sortformer/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "transcribe-sortformer-env" +version = "0.1.0" +description = "Python env for Sortformer streaming diarizer reference dumps via NeMo (SortformerEncLabelModel)" +requires-python = ">=3.11,<3.13" +dependencies = [ + "torch>=2.2", + # Same NeMo pin as scripts/envs/parakeet: commit 6967f48fda2a already + # ships streaming Sortformer (sortformer_modules with spkcache/AOSC/FIFO + # and SortformerEncLabelModel.diarize(include_tensor_outputs=...)). + "nemo-toolkit[asr] @ git+https://github.com/NVIDIA-NeMo/NeMo.git@6967f48fda2a", + "huggingface-hub>=0.20", + "soundfile>=0.12", + "numpy>=1.26", + "sentencepiece>=0.2", + "gguf>=0.10.0", +] + +# NeMo main maps torch by platform: darwin->pypi (CPU), linux->cu129, +# windows->cpu. Restrict to the two we actually run on (laptop dumps +# on darwin, Modal sweeps on linux/cuda) so the resolver picks one +# torch source per split and the cpu/cu129 indexes do not collide. +[tool.uv] +environments = [ + "sys_platform == 'darwin'", + "sys_platform == 'linux'", +] diff --git a/scripts/envs/sortformer/uv.lock b/scripts/envs/sortformer/uv.lock new file mode 100644 index 00000000..80608bd5 --- /dev/null +++ b/scripts/envs/sortformer/uv.lock @@ -0,0 +1,3187 @@ +version = 1 +revision = 3 +requires-python = ">=3.11, <3.13" +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform == 'linux'", +] +supported-markers = [ + "sys_platform == 'darwin'", + "sys_platform == 'linux'", +] + +[[package]] +name = "absl-py" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/4f/d79676ab82f2e42fc3611618139f13a9c4c31d0cff4b486982047679a802/absl_py-2.5.0.tar.gz", hash = "sha256:0c996f25c0490700fadabe6351630f6111534fa0ae252cc6d2014ea3b141135f", size = 118119, upload-time = "2026-07-03T10:57:48.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/0a/a10b45aab35b175aded078a462dc8d0c698f5b13946e7cb0869097b78bb6/absl_py-2.5.0-py3-none-any.whl", hash = "sha256:0f17b89f2a4eaaedc4f28c622998aa690564b3012a396a4ffad0821007fe03ba", size = 137410, upload-time = "2026-07-03T10:57:46.735Z" }, +] + +[[package]] +name = "accelerate" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "psutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "safetensors", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/75/94cd5d389649578aca399e5aa822637eec18319a1dadc400ffe2f9a7493f/accelerate-1.14.0.tar.gz", hash = "sha256:41b9c4377a54e0b460a959b0defa1b736e4ca0a2373252d9a539964c2afe3c8d", size = 412167, upload-time = "2026-06-11T13:45:52.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/db/253133d7e7cb40d3af384bb2f5c0b4a2b7fdcffbc95c688cc67a20a3c103/accelerate-1.14.0-py3-none-any.whl", hash = "sha256:e94390c2863b873be18f623f9df48a0d8fe5eff13ea7f1a00092b0a7904888c6", size = 389246, upload-time = "2026-06-11T13:45:50.477Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "aiosignal", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "attrs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "frozenlist", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "multidict", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "propcache", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "yarl", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/dd/bf526e6f0a1120dd6f2df2e97bacfe4d358f13d17a0ff5847301a1375a51/aiohttp-3.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2", size = 765225, upload-time = "2026-06-07T21:06:07.957Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e1/a2872aa55495a70f61310d411541c6ee23812d9a884e000c716e1bc3edbf/aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f", size = 518743, upload-time = "2026-06-07T21:06:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e7/c60c7b209e509cc787de3cea0550a518538cfc08003e1c1e14c1c63fff71/aiohttp-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8", size = 514139, upload-time = "2026-06-07T21:06:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8d/614ace2f579702c9840ab1e1447fd8509e35b0b904f7196418fa2f57b25d/aiohttp-3.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04", size = 1784088, upload-time = "2026-06-07T21:06:12.887Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/726e90f99542bf292f81a96a12cc4847deb86f3ccf62c6f4014a201f4d33/aiohttp-3.14.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8", size = 1737835, upload-time = "2026-06-07T21:06:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4b/d176d5c4db9d33dacf0543102ea59503bc1d528af4cfd0b719949ca49389/aiohttp-3.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6", size = 1842801, upload-time = "2026-06-07T21:06:16.228Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d6/5a99b563690ea0cbed912ae94a2ce33993a5709a651a3a4fe761e7dd973a/aiohttp-3.14.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af", size = 1929992, upload-time = "2026-06-07T21:06:17.947Z" }, + { url = "https://files.pythonhosted.org/packages/76/7f/a987b14a3859094b3cea3f4825219c3e5536242564af6e3f9c2f6c994eb2/aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730", size = 1786989, upload-time = "2026-06-07T21:06:19.677Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1a/420e5c85a3e73349372ed22ce0b6af86bfa6ce16a4b20a64a2e94608c781/aiohttp-3.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621", size = 1640129, upload-time = "2026-06-07T21:06:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/a7/80/18a592ed3be0a402cc03670bd72ee1f8563ddbe1d8d5542dbf868f274136/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee", size = 1756576, upload-time = "2026-06-07T21:06:24.8Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0b/8b3d5713373858ff71a617daf6e3b0e81ad63e79d09a3cf2f6b6b983939c/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573", size = 1754668, upload-time = "2026-06-07T21:06:26.528Z" }, + { url = "https://files.pythonhosted.org/packages/9f/49/fd564575cf225821d7ba5a117cb8bc27213d8a7e1811162afb43ae077039/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7", size = 1817019, upload-time = "2026-06-07T21:06:28.297Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/e850c9ae6fc91356552ae668bb6c51e93fa29c8aef13398a10b56678557f/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf", size = 1631638, upload-time = "2026-06-07T21:06:30.242Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/3c337ba72451a89806ace6f75bddc92bafc5b8d53d90115a512858024b63/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85", size = 1835660, upload-time = "2026-06-07T21:06:31.943Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9c/9c18cf367a0498212d9ba7daf990b504a5e8ae064cda4b504e2647c89c03/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3", size = 1775698, upload-time = "2026-06-07T21:06:33.72Z" }, + { url = "https://files.pythonhosted.org/packages/1d/21/151624b51cd92553d95424daf4bf19f19ce9be9002d19253e7e7ce67197b/aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480", size = 757402, upload-time = "2026-06-07T21:06:40.311Z" }, + { url = "https://files.pythonhosted.org/packages/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/2aac325583aaa1353045f96dffa586d8a34e8322e14a7ba49cffeb103ab4/aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2", size = 512448, upload-time = "2026-06-07T21:06:43.813Z" }, + { url = "https://files.pythonhosted.org/packages/8a/72/a60607cb849faa8af8a356c9329ea2eb6f395d49e82cc82ccba1fd8deb8f/aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2", size = 1766854, upload-time = "2026-06-07T21:06:45.391Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d3/d9fe1c9ec7557ab4d0d82bebaa728c6418f0b93295ec2f4ab015f7710cc7/aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a", size = 1740884, upload-time = "2026-06-07T21:06:47.413Z" }, + { url = "https://files.pythonhosted.org/packages/c1/dc/f2cecfaf9337ba3e63f181500814ff502aa3d00d9c7ec93a9d23d10a27b2/aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264", size = 1810034, upload-time = "2026-06-07T21:06:50.165Z" }, + { url = "https://files.pythonhosted.org/packages/66/d7/2ff65c5e65c0d7476daf7e15c032e0805e36811185b9623e3238ad6c763e/aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842", size = 1904054, upload-time = "2026-06-07T21:06:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/d445818389df371f56d141d881153ba23183c4735a03f7356ffb43f7757d/aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c", size = 1790278, upload-time = "2026-06-07T21:06:54.049Z" }, + { url = "https://files.pythonhosted.org/packages/4d/aa/bf04cb4d865fc6101c2229a294ad744973b72e513fdc5a6b791e6983d72a/aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95", size = 1591795, upload-time = "2026-06-07T21:06:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b4/4dac0038960427ba832f6609dfb4ea5437d7fd80c72001b9e48f834f428b/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199", size = 1728397, upload-time = "2026-06-07T21:06:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/7cd4e8ad7aa3b75f17d56bb5498dd604a93d4e6eece822ba0568c413fff0/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817", size = 1766504, upload-time = "2026-06-07T21:07:00.009Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/fc01d9fcad0f73fed3f3d361f1f94f975947b50dff82919f6dc2bf4316cc/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a", size = 1777806, upload-time = "2026-06-07T21:07:02.064Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/47e2d090bddcc8fb4ccb4c314aadc32d7c5d9bb55f50f6ad1c92fc15d501/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4", size = 1580707, upload-time = "2026-06-07T21:07:03.942Z" }, + { url = "https://files.pythonhosted.org/packages/3d/36/f1a4ce904ae0b6930cfe9afc96d0896f7ec1a620c400405d63783bb95a9c/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087", size = 1798121, upload-time = "2026-06-07T21:07:05.987Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/e0075ce9ca0279ee1d4f0c0b85f54fea02ebc83c3007651a72bece658fec/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3", size = 1767580, upload-time = "2026-06-07T21:07:07.873Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "alembic" +version = "1.18.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sqlalchemy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/cc/ac0bed8e562e7407fe55c3ba85a4dce86e6dbd8730887bd1e406a6c5c18a/alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e", size = 2060480, upload-time = "2026-06-25T15:20:54.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/78/5fe6dc3a3a5b2f5a2a4faef8bfe336d5fa049a38884ab3172e0098160c01/alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc", size = 264664, upload-time = "2026-06-25T15:20:56.673Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, +] + +[[package]] +name = "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 = "braceexpand" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/93/badd4f5ccf25209f3fef2573073da9fe4a45a3da99fca2f800f942130c0f/braceexpand-0.1.7.tar.gz", hash = "sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705", size = 7777, upload-time = "2021-05-07T13:49:07.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/93/e8c04e80e82391a6e51f218ca49720f64236bc824e92152a2633b74cf7ab/braceexpand-0.1.7-py2.py3-none-any.whl", hash = "sha256:91332d53de7828103dcae5773fb43bc34950b0c8160e35e0f44c4427a3b85014", size = 5923, upload-time = "2021-05-07T13:49:05.146Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "(implementation_name != 'PyPy' and sys_platform == 'darwin') or (implementation_name != 'PyPy' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/67/85c89a59ba36a671e79638f44d466749f08179266a57e4f2ffdf92174072/cffi-2.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:02cb7ff33ded4f1532476731f89ede53e2e488a8e6205515a82144246ffa7dcc", size = 183845, upload-time = "2026-07-06T21:32:26.32Z" }, + { url = "https://files.pythonhosted.org/packages/ea/dd/e3b0baa2d3d6a857ac72b7efbf18e32e487c9cdafcc13049ad765495b15e/cffi-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5bce581e6b8c235e566a14768a943b172ada3ed73537bb0c0be1edee312d4e7", size = 184186, upload-time = "2026-07-06T21:32:28.025Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/9f3ef890cf3c6ab97bd531c5677f67613d302165d16f8142b2811782a614/cffi-2.1.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:30b65779d598c370374fefabf138d456fd6f3216bfa7bedfab1ba82025b0cd93", size = 211892, upload-time = "2026-07-06T21:32:29.565Z" }, + { url = "https://files.pythonhosted.org/packages/22/d7/1a74539db16d8bfd839ff1515948948efbb162e574650fd3d846896eea95/cffi-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88023dfe18799507b73f1dbb0d14326a17465de1bc9c9c7655c22845e9ddc3a2", size = 218793, upload-time = "2026-07-06T21:32:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d1/9a5b7169499e8e8d8e636de70b97ac7c9447104d2ff1a2cd94790cea5162/cffi-2.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0a96b74cda968eebbad56d973efe5098974f0a9fb323865bf99ea1fd24e3e64c", size = 205737, upload-time = "2026-07-06T21:32:32.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b0/e131a9c41f10607926278453d9596163594fe1c4ebc46efe3b5e5b34eb84/cffi-2.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a5781494d4d400a3f47f8f1da94b324f6e6b440a53387774002890a2a2f4b50f", size = 204909, upload-time = "2026-07-06T21:32:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d2/4398416cd699b35167947c6e22aca52c47e69ad5695073c9f1f2c52e04aa/cffi-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aa7a1b53a2a4452ada2d1b5dade9960b2522f1e61293a811a077439e39029565", size = 217883, upload-time = "2026-07-06T21:32:35.173Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/d4fe77b589e5e82d43ebc809bf2e6474afe8e48e32ea050b9357645b6471/cffi-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d8272c0e483b024e1b9ad029821470ed8ec65631dbd90217469da0e7cd89f1c", size = 221251, upload-time = "2026-07-06T21:32:36.527Z" }, + { url = "https://files.pythonhosted.org/packages/22/f0/a2fc43084c0433caf7f461bccc013e28f848d04ee1c5ed7fce71423cf4d9/cffi-2.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7762faa47e8ff7eb80bd261d9a7d8eea2d8baa69de5e95b70c1f338bbe712f02", size = 214250, upload-time = "2026-07-06T21:32:37.852Z" }, + { url = "https://files.pythonhosted.org/packages/04/8c/b925975448cf20634a9fbd5efceb807219db452653648d2897c0989cab2d/cffi-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89095c1968b4ba8285840e131bf2891b09ae137fe2146905acae0354fbce1b5e", size = 219441, upload-time = "2026-07-06T21:32:39.146Z" }, + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "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 = "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 = "colorlog" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/bf/cb30a51af3aa8ce63735f77e23dcd4fc0720fe0339bcb04f77345659c277/colorlog-6.11.0.tar.gz", hash = "sha256:9d90fb53fa906c8970c18fbe46506bae1fb5f86b513b8f867db37e4ace9be7ae", size = 17734, upload-time = "2026-07-17T12:16:46.59Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/a1/6b71004ab0fea510230be9ce05a4059029ac847c009fcc80b1b73d6fa5ab/colorlog-6.11.0-py3-none-any.whl", hash = "sha256:f1e27d75aa2cb138f3f640c0e305b65b680ccbef6ecc034eba7e03494ffcd2a1", size = 12016, upload-time = "2026-07-17T12:16:45.3Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, +] + +[[package]] +name = "cuda-bindings" +version = "12.9.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/f3/f9d1095f90d2a4df24cfcafe7487fd9444c6dacb94e3722be6fedd8ac26c/cuda_bindings-12.9.7-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16043ef5b15ab88fe9954c5c2061b1d8007591b27f2c916331056de0ebc6187e", size = 7114834, upload-time = "2026-05-27T18:44:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8a/1251e1794b69865aacd5629936006b18ea0816a495de4ecea9a825556eb3/cuda_bindings-12.9.7-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6496a88d84b1209d6651b0370c19c26319e157c22f6d018bf9a358cd8049041", size = 7647147, upload-time = "2026-05-27T18:44:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/45/557d4ed1fa54f0c7db8aee083229f624990d69f7d00f55477eed5c7e169a/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0666d3c082ef8f4b2d670950589373550e9f3bf564d635dd883f24a0b40402ff", size = 7071026, upload-time = "2026-05-27T18:44:13.356Z" }, + { url = "https://files.pythonhosted.org/packages/91/97/e3c6e58ece26a053419ba0a18444b5443cfc64451bbf37f84e8143b8bdca/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c7ef48c5e13ae90f3b2ecfb72f8e99ac43c8f4c43e67e1325b8aae331453687", size = 7611059, upload-time = "2026-05-27T18:44:15.252Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/53/8fc9b0cdc5b7f62746e6a01b85b6461e5ae27f871010a5fcf8fa6950766d/cuda_pathfinder-1.5.6-py3-none-any.whl", hash = "sha256:7e4c07c117b78ba1fb35dac4c444d21f3677b1b1ff56175c53a8e3025c5b43c0", size = 52972, upload-time = "2026-06-30T00:58:04.34Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "12.9.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/8f/a28e7da158e96ad61f7e1035e53851fafaddf22445300d664e68ec657fdc/cuda_toolkit-12.9.1-py2.py3-none-any.whl", hash = "sha256:0c8636dfacbecfe9867a949a211864f080a805bc54023ce4a361aa4e1fd8738b", size = 2303, upload-time = "2025-08-13T02:03:10.699Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime-cu12", marker = "sys_platform == 'linux'" }, +] +cufft = [ + { name = "nvidia-cufft-cu12", marker = "sys_platform == 'linux'" }, +] +cufile = [ + { name = "nvidia-cufile-cu12", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti-cu12", marker = "sys_platform == 'linux'" }, +] +curand = [ + { name = "nvidia-curand-cu12", marker = "sys_platform == 'linux'" }, +] +cusolver = [ + { name = "nvidia-cusolver-cu12", marker = "sys_platform == 'linux'" }, +] +cusparse = [ + { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc-cu12", marker = "sys_platform == 'linux'" }, +] +nvtx = [ + { name = "nvidia-nvtx-cu12", marker = "sys_platform == 'linux'" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "cytoolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "toolz", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/d4/16916f3dc20a3f5455b63c35dcb260b3716f59ce27a93586804e70e431d5/cytoolz-1.1.0.tar.gz", hash = "sha256:13a7bf254c3c0d28b12e2290b82aed0f0977a4c2a2bf84854fcdc7796a29f3b0", size = 642510, upload-time = "2025-10-19T00:44:56.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/82/edf1d0c32b6222f2c22e5618d6db855d44eb59f9b6f22436ff963c5d0a5c/cytoolz-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dba8e5a8c6e3c789d27b0eb5e7ce5ed7d032a7a9aae17ca4ba5147b871f6e327", size = 1314345, upload-time = "2025-10-19T00:40:13.273Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b5/0e3c1edaa26c2bd9db90cba0ac62c85bbca84224c7ae1c2e0072c4ea64c5/cytoolz-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:44b31c05addb0889167a720123b3b497b28dd86f8a0aeaf3ae4ffa11e2c85d55", size = 989259, upload-time = "2025-10-19T00:40:15.196Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/e2b2ee9fc684867e817640764ea5807f9d25aa1e7bdba02dd4b249aab0f7/cytoolz-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:653cb18c4fc5d8a8cfce2bce650aabcbe82957cd0536827367d10810566d5294", size = 986551, upload-time = "2025-10-19T00:40:16.831Z" }, + { url = "https://files.pythonhosted.org/packages/39/9f/4e8ee41acf6674f10a9c2c9117b2f219429a5a0f09bba6135f34ca4f08a6/cytoolz-1.1.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:853a5b4806915020c890e1ce70cc056bbc1dd8bc44f2d74d555cccfd7aefba7d", size = 2688378, upload-time = "2025-10-19T00:40:18.552Z" }, + { url = "https://files.pythonhosted.org/packages/78/94/ef006f3412bc22444d855a0fc9ecb81424237fb4e5c1a1f8f5fb79ac978f/cytoolz-1.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7b44e9de86bea013fe84fd8c399d6016bbb96c37c5290769e5c99460b9c53e5", size = 2798299, upload-time = "2025-10-19T00:40:20.191Z" }, + { url = "https://files.pythonhosted.org/packages/df/aa/365953926ee8b4f2e07df7200c0d73632155908c8867af14b2d19cc9f1f7/cytoolz-1.1.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:098d628a801dc142e9740126be5624eb7aef1d732bc7a5719f60a2095547b485", size = 2639311, upload-time = "2025-10-19T00:40:22.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ee/62beaaee7df208f22590ad07ef8875519af49c52ca39d99460b14a00f15a/cytoolz-1.1.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:779ee4096ed7a82cffab89372ffc339631c285079dbf33dbe7aff1f6174985df", size = 2979532, upload-time = "2025-10-19T00:40:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/c5/04/2211251e450bed111ada1194dc42c461da9aea441de62a01e4085ea6de9f/cytoolz-1.1.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f2ce18dd99533d077e9712f9faa852f389f560351b1efd2f2bdb193a95eddde2", size = 3018632, upload-time = "2025-10-19T00:40:26.175Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a2/4a3400e4d07d3916172bf74fede08020d7b4df01595d8a97f1e9507af5ae/cytoolz-1.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac266a34437812cf841cecbfe19f355ab9c3dd1ef231afc60415d40ff12a76e4", size = 2788579, upload-time = "2025-10-19T00:40:27.878Z" }, + { url = "https://files.pythonhosted.org/packages/fe/82/bb88caa53a41f600e7763c517d50e2efbbe6427ea395716a92b83f44882a/cytoolz-1.1.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1920b9b9c13d60d0bb6cd14594b3bce0870022eccb430618c37156da5f2b7a55", size = 2593024, upload-time = "2025-10-19T00:40:29.601Z" }, + { url = "https://files.pythonhosted.org/packages/09/a8/8b25e59570da16c7a0f173b8c6ec0aa6f3abd47fd385c007485acb459896/cytoolz-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47caa376dafd2bdc29f8a250acf59c810ec9105cd6f7680b9a9d070aae8490ec", size = 2715304, upload-time = "2025-10-19T00:40:31.151Z" }, + { url = "https://files.pythonhosted.org/packages/d4/56/faec7696f235521b926ffdf92c102f5b029f072d28e1020364e55b084820/cytoolz-1.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5ab2c97d8aaa522b038cca9187b1153347af22309e7c998b14750c6fdec7b1cb", size = 2654461, upload-time = "2025-10-19T00:40:32.884Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/f790ed167c04b8d2a33bed30770a9b7066fc4f573321d797190e5f05685f/cytoolz-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4bce006121b120e8b359244ee140bb0b1093908efc8b739db8dbaa3f8fb42139", size = 2672077, upload-time = "2025-10-19T00:40:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b3/80b8183e7eee44f45bfa3cdd3ebdadf3dd43ffc686f96d442a6c4dded45d/cytoolz-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fc0f1e4e9bb384d26e73c6657bbc26abdae4ff66a95933c00f3d578be89181b", size = 2881589, upload-time = "2025-10-19T00:40:36.315Z" }, + { url = "https://files.pythonhosted.org/packages/8f/05/ac5ba5ddb88a3ba7ecea4bf192194a838af564d22ea7a4812cbb6bd106ce/cytoolz-1.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:dd3f894ff972da1994d06ac6157d74e40dda19eb31fe5e9b7863ca4278c3a167", size = 2589924, upload-time = "2025-10-19T00:40:38.317Z" }, + { url = "https://files.pythonhosted.org/packages/8e/cd/100483cae3849d24351c8333a815dc6adaf3f04912486e59386d86d9db9a/cytoolz-1.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0846f49cf8a4496bd42659040e68bd0484ce6af819709cae234938e039203ba0", size = 2868059, upload-time = "2025-10-19T00:40:40.025Z" }, + { url = "https://files.pythonhosted.org/packages/34/6e/3a7c56b325772d39397fc3aafb4dc054273982097178b6c3917c6dad48de/cytoolz-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:16a3af394ade1973226d64bb2f9eb3336adbdea03ed5b134c1bbec5a3b20028e", size = 2721692, upload-time = "2025-10-19T00:40:41.621Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ec/01426224f7acf60183d3921b25e1a8e71713d3d39cb464d64ac7aace6ea6/cytoolz-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99f8e134c9be11649342853ec8c90837af4089fc8ff1e8f9a024a57d1fa08514", size = 1327800, upload-time = "2025-10-19T00:40:48.674Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/e07e8fedd332ac9626ad58bea31416dda19bfd14310731fa38b16a97e15f/cytoolz-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a6f44cf9319c30feb9a50aa513d777ef51efec16f31c404409e7deb8063df64", size = 997118, upload-time = "2025-10-19T00:40:50.919Z" }, + { url = "https://files.pythonhosted.org/packages/ab/72/c0f766d63ed2f9ea8dc8e1628d385d99b41fb834ce17ac3669e3f91e115d/cytoolz-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:945580dc158c557172fca899a35a99a16fbcebf6db0c77cb6621084bc82189f9", size = 991169, upload-time = "2025-10-19T00:40:52.887Z" }, + { url = "https://files.pythonhosted.org/packages/df/4b/1f757353d1bf33e56a7391ecc9bc49c1e529803b93a9d2f67fe5f92906fe/cytoolz-1.1.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:257905ec050d04f2f856854620d1e25556fd735064cebd81b460f54939b9f9d5", size = 2700680, upload-time = "2025-10-19T00:40:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/25/73/9b25bb7ed8d419b9d6ff2ae0b3d06694de79a3f98f5169a1293ff7ad3a3f/cytoolz-1.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82779049f352fb3ab5e8c993ab45edbb6e02efb1f17f0b50f4972c706cc51d76", size = 2824951, upload-time = "2025-10-19T00:40:56.137Z" }, + { url = "https://files.pythonhosted.org/packages/0c/93/9c787f7c909e75670fff467f2504725d06d8c3f51d6dfe22c55a08c8ccd4/cytoolz-1.1.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d3e405e435320e08c5a1633afaf285a392e2d9cef35c925d91e2a31dfd7a688", size = 2679635, upload-time = "2025-10-19T00:40:57.799Z" }, + { url = "https://files.pythonhosted.org/packages/50/aa/9ee92c302cccf7a41a7311b325b51ebeff25d36c1f82bdc1bbe3f58dc947/cytoolz-1.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:923df8f5591e0d20543060c29909c149ab1963a7267037b39eee03a83dbc50a8", size = 2938352, upload-time = "2025-10-19T00:40:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a3/3b58c5c1692c3bacd65640d0d5c7267a7ebb76204f7507aec29de7063d2f/cytoolz-1.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:25db9e4862f22ea0ae2e56c8bec9fc9fd756b655ae13e8c7b5625d7ed1c582d4", size = 3022121, upload-time = "2025-10-19T00:41:01.209Z" }, + { url = "https://files.pythonhosted.org/packages/e1/93/c647bc3334355088c57351a536c2d4a83dd45f7de591fab383975e45bff9/cytoolz-1.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7a98deb11ccd8e5d9f9441ef2ff3352aab52226a2b7d04756caaa53cd612363", size = 2857656, upload-time = "2025-10-19T00:41:03.456Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c2/43fea146bf4141deea959e19dcddf268c5ed759dec5c2ed4a6941d711933/cytoolz-1.1.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dce4ee9fc99104bc77efdea80f32ca5a650cd653bcc8a1d984a931153d3d9b58", size = 2551284, upload-time = "2025-10-19T00:41:05.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/df/cdc7a81ce5cfcde7ef523143d545635fc37e80ccacce140ae58483a21da3/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80d6da158f7d20c15819701bbda1c041f0944ede2f564f5c739b1bc80a9ffb8b", size = 2721673, upload-time = "2025-10-19T00:41:07.528Z" }, + { url = "https://files.pythonhosted.org/packages/45/be/f8524bb9ad8812ad375e61238dcaa3177628234d1b908ad0b74e3657cafd/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3b5c5a192abda123ad45ef716ec9082b4cf7d95e9ada8291c5c2cc5558be858b", size = 2722884, upload-time = "2025-10-19T00:41:09.698Z" }, + { url = "https://files.pythonhosted.org/packages/23/e6/6bb8e4f9c267ad42d1ff77b6d2e4984665505afae50a216290e1d7311431/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5b399ce7d967b1cb6280250818b786be652aa8ddffd3c0bb5c48c6220d945ab5", size = 2685486, upload-time = "2025-10-19T00:41:11.349Z" }, + { url = "https://files.pythonhosted.org/packages/d7/dd/88619f9c8d2b682562c0c886bbb7c35720cb83fda2ac9a41bdd14073d9bd/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e7e29a1a03f00b4322196cfe8e2c38da9a6c8d573566052c586df83aacc5663c", size = 2839661, upload-time = "2025-10-19T00:41:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8d/4478ebf471ee78dd496d254dc0f4ad729cd8e6ba8257de4f0a98a2838ef2/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5291b117d71652a817ec164e7011f18e6a51f8a352cc9a70ed5b976c51102fda", size = 2547095, upload-time = "2025-10-19T00:41:16.054Z" }, + { url = "https://files.pythonhosted.org/packages/e6/68/f1dea33367b0b3f64e199c230a14a6b6f243c189020effafd31e970ca527/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8caef62f846a9011676c51bda9189ae394cdd6bb17f2946ecaedc23243268320", size = 2870901, upload-time = "2025-10-19T00:41:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/33591c09dfe799b8fb692cf2ad383e2c41ab6593cc960b00d1fc8a145655/cytoolz-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:de425c5a8e3be7bb3a195e19191d28d9eb3c2038046064a92edc4505033ec9cb", size = 2765422, upload-time = "2025-10-19T00:41:20.075Z" }, + { url = "https://files.pythonhosted.org/packages/84/32/0522207170294cf691112a93c70a8ef942f60fa9ff8e793b63b1f09cedc0/cytoolz-1.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f32e93a55681d782fc6af939f6df36509d65122423cbc930be39b141064adff8", size = 922014, upload-time = "2025-10-19T00:44:44.911Z" }, + { url = "https://files.pythonhosted.org/packages/4c/49/9be2d24adaa18fa307ff14e3e43f02b2ae4b69c4ce51cee6889eb2114990/cytoolz-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5d9bc596751cbda8073e65be02ca11706f00029768fbbbc81e11a8c290bb41aa", size = 918134, upload-time = "2025-10-19T00:44:47.122Z" }, + { url = "https://files.pythonhosted.org/packages/5c/b3/6a76c3b94c6c87c72ea822e7e67405be6b649c2e37778eeac7c0c0c69de8/cytoolz-1.1.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9b16660d01c3931951fab49db422c627897c38c1a1f0393a97582004019a4887", size = 981970, upload-time = "2025-10-19T00:44:48.906Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8a/606e4c7ed14aa6a86aee6ca84a2cb804754dc6c4905b8f94e09e49f1ce60/cytoolz-1.1.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b7de5718e2113d4efccea3f06055758cdbc17388ecc3341ba4d1d812837d7c1a", size = 978877, upload-time = "2025-10-19T00:44:50.819Z" }, + { url = "https://files.pythonhosted.org/packages/97/ec/ad474dcb1f6c1ebfdda3c2ad2edbb1af122a0e79c9ff2cb901ffb5f59662/cytoolz-1.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a12a2a1a6bc44099491c05a12039efa08cc33a3d0f8c7b0566185e085e139283", size = 964279, upload-time = "2025-10-19T00:44:52.476Z" }, +] + +[[package]] +name = "datasets" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "fsspec", extra = ["http"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "multiprocess", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyarrow", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "xxhash", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/85/ce4f780c32f7e36d71257f1c27e8ba898ebe379cb54f211f5f2013f2c219/datasets-5.0.0.tar.gz", hash = "sha256:83dbbbdb07a33b82192b8c419deb18739b138ee2ce1a322d55ce6b100954ec1a", size = 631708, upload-time = "2026-06-05T13:18:26.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/66/73034ad30b59f13439b75e620989dacba4c047256e358ba7c2e9ec98ea22/datasets-5.0.0-py3-none-any.whl", hash = "sha256:7dd34927a0fd7046e98aad5cb9430e699c373238a15befa7b9bf22b991a7fee6", size = 555084, upload-time = "2026-06-05T13:18:24.435Z" }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "editdistance" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/18/9f4f975ca87a390832b1c22478f3702fcdf739f83211e24d054b7551270d/editdistance-0.8.1.tar.gz", hash = "sha256:d1cdf80a5d5014b0c9126a69a42ce55a457b457f6986ff69ca98e4fe4d2d8fed", size = 50006, upload-time = "2024-02-10T07:44:53.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/dc/d0c29fd52d8f9e795653ed2b838a2a48c739cdfff04ac5b79c6c0ecbdf79/editdistance-0.8.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:486105603a273d73d12a54f347dffa70ab281749d7c3879658b377bc49e4b98c", size = 106079, upload-time = "2024-02-10T07:43:34.34Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c6/75fa45d7b78fbea6fd894f4e48895a75bd3c83d4a9a6b57673881d74d3e0/editdistance-0.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fad081f5f86a175c1a09a4e9e45b95c9349e454c21e181e842e01c85f1f536fc", size = 80580, upload-time = "2024-02-10T07:43:35.947Z" }, + { url = "https://files.pythonhosted.org/packages/b7/a3/058d823b6285c3511dc94ed80620c3fb0c18b4aaa708f70ba71f3af28436/editdistance-0.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8cb78e125f6759398885a775f5eed07c2bb72b2f86da43e674c6b6a3335b273b", size = 79087, upload-time = "2024-02-10T07:43:36.923Z" }, + { url = "https://files.pythonhosted.org/packages/a0/3a/0b13c7864c93b1e9b9952bd2a33c5ef3c4fd1bf70a5fad6924789e70e5eb/editdistance-0.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3778ca60aa89def9144b70e330bcec5330c7da1d69cb28c612e90b84510a1d3d", size = 409296, upload-time = "2024-02-10T07:43:38.52Z" }, + { url = "https://files.pythonhosted.org/packages/96/8a/db0fd79e8ddb9b5f86f274107c5d0a27ec4f2af88877df1f26c2c6d150cc/editdistance-0.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fba945eaa0436cf40bc53d7e299dc537c7c71353379a095b7459ff4af910da33", size = 412913, upload-time = "2024-02-10T07:43:39.852Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d2/98be7112750ff17b436dd76f988f1e38570dcec0df8578ee19ef046f22fe/editdistance-0.8.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877f2a0d801f32bc1a1878901ffb947b974361e849c66e314a7f1d786a446b58", size = 407430, upload-time = "2024-02-10T07:43:41.048Z" }, + { url = "https://files.pythonhosted.org/packages/03/62/1815e3bf164910c47ba1948c8b5e937a40c7f9763b64e98fb6666b01dd06/editdistance-0.8.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e79d351ca40a6ead5f3763253fd7521572ee0d3e5d42538630e56d10f48db481", size = 909217, upload-time = "2024-02-10T07:43:42.916Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/a832cea7b507a9be54e4ac3d1340fb66dca5f9c16c70bf38d5039e8fdede/editdistance-0.8.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:70ed382b3052a51161bad0149d4665003bf3b949fce0b01bf1253a4cc1a88239", size = 969407, upload-time = "2024-02-10T07:43:44.912Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b4/db291d2a3845cbf8047b4b5aad3b3e038a8a2994d87027b40e1a1b0f4b74/editdistance-0.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a529bfb384c4000775d76739c4e64f73337f0f5a3784933b1321b577a62bed4e", size = 922112, upload-time = "2024-02-10T07:43:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/cb/4c/7f195588949b4e72436dc7fc902632381f96e586af829685b56daebb38b8/editdistance-0.8.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04af61b3fcdd287a07c15b6ae3b02af01c5e3e9c3aca76b8c1d13bd266b6f57", size = 106723, upload-time = "2024-02-10T07:43:50.268Z" }, + { url = "https://files.pythonhosted.org/packages/8d/82/31dc1640d830cd7d36865098329f34e4dad3b77f31cfb9404b347e700196/editdistance-0.8.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:18fc8b6eaae01bfd9cf999af726c1e8dcf667d120e81aa7dbd515bea7427f62f", size = 80998, upload-time = "2024-02-10T07:43:51.259Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2a/6b823e71cef694d6f070a1d82be2842706fa193541aab8856a8f42044cd0/editdistance-0.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a87839450a5987028738d061ffa5ef6a68bac2ddc68c9147a8aae9806629c7f", size = 79248, upload-time = "2024-02-10T07:43:52.873Z" }, + { url = "https://files.pythonhosted.org/packages/e1/31/bfb8e590f922089dc3471ed7828a6da2fc9453eba38c332efa9ee8749fd7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24b5f9c9673c823d91b5973d0af8b39f883f414a55ade2b9d097138acd10f31e", size = 415262, upload-time = "2024-02-10T07:43:54.498Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/57423942b2f847cdbbb46494568d00cd8a45500904ea026f0aad6ca01bc7/editdistance-0.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59248eabfad603f0fba47b0c263d5dc728fb01c2b6b50fb6ca187cec547fdb3", size = 418905, upload-time = "2024-02-10T07:43:55.779Z" }, + { url = "https://files.pythonhosted.org/packages/1b/05/dfa4cdcce063596cbf0d7a32c46cd0f4fa70980311b7da64d35f33ad02a0/editdistance-0.8.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e239d88ff52821cf64023fabd06a1d9a07654f364b64bf1284577fd3a79d0e", size = 412511, upload-time = "2024-02-10T07:43:57.567Z" }, + { url = "https://files.pythonhosted.org/packages/0e/14/39608ff724a9523f187c4e28926d78bc68f2798f74777ac6757981108345/editdistance-0.8.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2f7f71698f83e8c83839ac0d876a0f4ef996c86c5460aebd26d85568d4afd0db", size = 917293, upload-time = "2024-02-10T07:43:59.559Z" }, + { url = "https://files.pythonhosted.org/packages/df/92/4a1c61d72da40dedfd0ff950fdc71ae83f478330c58a8bccfd776518bd67/editdistance-0.8.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:04e229d6f4ce0c12abc9f4cd4023a5b5fa9620226e0207b119c3c2778b036250", size = 975580, upload-time = "2024-02-10T07:44:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/47/3d/9877566e724c8a37f2228a84ec5cbf66dbfd0673515baf68a0fe07caff40/editdistance-0.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e16721636da6d6b68a2c09eaced35a94f4a4a704ec09f45756d4fd5e128ed18d", size = 929121, upload-time = "2024-02-10T07:44:02.764Z" }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fiddle" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "graphviz", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "libcst", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/36/7a4fac76351619b36bbc7937abf59f7b601326dc4efc253b3c16819f782a/fiddle-0.3.0.tar.gz", hash = "sha256:5d083d3299a479868345513385a6c5546141bd92086c15d3dcbf8008a90075d3", size = 277884, upload-time = "2024-04-09T17:23:58.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/98/a38e949a91ff9e15874487fd8329ff53c25f3413c0cfc809eb6ff7eb7fa1/fiddle-0.3.0-py3-none-any.whl", hash = "sha256:f4824541c103a94a2f33f6c93eeddf6007c3a7300440087a95907f3e74362e61", size = 419830, upload-time = "2024-04-09T17:23:56.7Z" }, +] + +[[package]] +name = "filelock" +version = "3.31.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/9f/994e80905542b748eb5b9f36d71458f0aea51a7be0fcb52ad959787dc1b7/filelock-3.31.0.tar.gz", hash = "sha256:c188cbc4307c18894c5424fa73f97ea7fa127ddf62192487546da3a214d0a381", size = 180931, upload-time = "2026-07-18T05:53:29.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/4a/e213905d3b8ad3d35d14fc056b36134a274e7f6a1050e94428b5be10a94c/filelock-3.31.0-py3-none-any.whl", hash = "sha256:739b73e580fe88bb78d830aeddbc492519ece3d97ac8368de13a2032c61010c1", size = 96080, upload-time = "2026-07-18T05:53:27.732Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/27/954057b0d1f53f086f681755207dda6de6c660ce133c829158e8e8fe7895/fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973", size = 309748, upload-time = "2025-12-03T15:23:42.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/c7/b64cae5dba3a1b138d7123ec36bb5ccd39d39939f18454407e5468f4763f/fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b", size = 201422, upload-time = "2025-12-03T15:23:41.434Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[[package]] +name = "gguf" +version = "0.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ae/17f1308ae45cd7b08ebb521747d5b23f4efc4d172038a4e228dd5106c3ff/gguf-0.19.0.tar.gz", hash = "sha256:dbadcd6cc7ccd44256f2229fe7c2dff5e8aa5cf0612ab987fd2b1a57e428923f", size = 111220, upload-time = "2026-05-06T13:04:03.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/bb/d71d6da82763528c2c2ed6b59a9d6142c6595545a4c448e2085d155e88c2/gguf-0.19.0-py3-none-any.whl", hash = "sha256:70bcd10edfe697fb2dad6e40af2234b9d8ece9a41a99761405121ebda1c3c1cd", size = 118475, upload-time = "2026-05-06T13:04:02.588Z" }, +] + +[[package]] +name = "graphviz" +version = "0.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/b3/3ac91e9be6b761a4b30d66ff165e54439dcd48b83f4e20d644867215f6ca/graphviz-0.21.tar.gz", hash = "sha256:20743e7183be82aaaa8ad6c93f8893c923bd6658a04c32ee115edb3c8a835f78", size = 200434, upload-time = "2025-06-15T09:35:05.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl", hash = "sha256:54f33de9f4f911d7e84e4191749cac8cc5653f815b06738c54db9a15ab8b1e42", size = 47300, upload-time = "2025-06-15T09:35:04.433Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/f1/fbbfef6af0bad0548f09bc28948ea3c275b4edb19e17fc5ca9900a6a634d/greenlet-3.5.3.tar.gz", hash = "sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1", size = 200270, upload-time = "2026-06-26T19:28:24.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/58/5404031044f55afad7aad1aff8be3f22b1bed03e237cfeabbc7e5c8cfde0/greenlet-3.5.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4", size = 287424, upload-time = "2026-06-26T18:20:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bf/1c65e9b94a54d547068fa5b5a8a06f221f3316b48908e08668d29c77cb50/greenlet-3.5.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc", size = 606523, upload-time = "2026-06-26T19:07:08.859Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/b66baacc95775ad511287acb0137b95574a9ce5491902372b7564799d790/greenlet-3.5.3-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6", size = 618315, upload-time = "2026-06-26T19:10:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/78/2b/28ed29463522fdbe4c15b1f63922041626a7478316b34ab4adda3f0a4aba/greenlet-3.5.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7", size = 617381, upload-time = "2026-06-26T18:32:16.077Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7b/ad04e9d1337fc04965dc9fc616b6a72cb65a24b800a014c011ec812f5489/greenlet-3.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8", size = 1577771, upload-time = "2026-06-26T19:09:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/d8/33/6c87ab7ba663f70ca21f3022aad1ffe56d3f3e0521e836c2415e13abcc3c/greenlet-3.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8", size = 1644048, upload-time = "2026-06-26T18:31:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6e/4c37d51a2b7f82d2ff11bb6b5f7d766d9a011726624af255e843727627a3/greenlet-3.5.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2", size = 288685, upload-time = "2026-06-26T18:22:08.977Z" }, + { url = "https://files.pythonhosted.org/packages/7a/73/815dd90131c1b71ebdf53dbc7c276cafec2a1173b97559f97aba72724a87/greenlet-3.5.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b", size = 604761, upload-time = "2026-06-26T19:07:10.114Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/079cfe76bcef36b153b25607ee91c6fcb58f17f8b23c86bbbeabe0c88d72/greenlet-3.5.3-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab", size = 617044, upload-time = "2026-06-26T19:10:07.25Z" }, + { url = "https://files.pythonhosted.org/packages/37/87/b4d095775a3fb1bcafbb483fc206b27ebb785724c83051447737085dc54e/greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87142215824be6ac05e2e8e2786eec307ccbc27c36723c3881959df654af6861", size = 614244, upload-time = "2026-06-26T18:32:17.594Z" }, + { url = "https://files.pythonhosted.org/packages/8a/70/7559b609683650fa2b95b8ab84b4ab0b26556a635d19675e12aa832d826d/greenlet-3.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215275b1b49320987352e6c1b054acca0064f965a2c66992bed9a6f7d913f149", size = 1574210, upload-time = "2026-06-26T19:09:03.077Z" }, + { url = "https://files.pythonhosted.org/packages/ae/73/be55392074c60fc37655ca40fa6022457bfbf6718e9e342a7b0b41f96dd2/greenlet-3.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b1b0eed82364b0e32c4ea0f221452d33e6bb17ae094d9f72aed9851812747ea", size = 1638627, upload-time = "2026-06-26T18:31:44.748Z" }, +] + +[[package]] +name = "grpcio" +version = "1.82.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/bc/656b89387d6f4ed7e0686c7b64c2ae7e554a759aa58122c8e5fb99392c32/grpcio-1.82.1.tar.gz", hash = "sha256:707b24abd90fcb1e45bcc080577da1dbf9971d107490589b9539af8e1e77b4b5", size = 13187300, upload-time = "2026-07-08T12:36:16.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/5b/e5092af97fa671ca279b3e373251af4bf87d5fbda7dc85f6a616899562a7/grpcio-1.82.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:0ddb18a9a9e1f46692b3567ae4abb3f8d117ce6afea48650f8eca06d8ab5d06f", size = 6181472, upload-time = "2026-07-08T12:34:31.009Z" }, + { url = "https://files.pythonhosted.org/packages/c1/8f/18053a3a2ca03d0c2a1b8cc7271e705007a16aa5dae84bac00935c5b1a7f/grpcio-1.82.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:cf855b1af246720f567b0ce5d0724d45dfa4188eecc3296a2a69257b11b9e94b", size = 11970995, upload-time = "2026-07-08T12:34:33.603Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/21b1acb052876ad00959ec4d1b05fe08607d650bcfa282073bb164c2703c/grpcio-1.82.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb30cb13e25bc13cea70ffc69d6d90c49d36ea6c1d4549e6912f70177834cac", size = 6760127, upload-time = "2026-07-08T12:34:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/3e/12/25eef9c245c54f0061317d13a302357fe8ea03bac240b2b02ececcf54da4/grpcio-1.82.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1e822b2774f719c017cbe700b6e47173b6ae290fb84906f52a5a3c2c60b62e1e", size = 7484377, upload-time = "2026-07-08T12:34:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/a0/41/1a348767eb9d9bd7765dc4fa8a01723d3bb386d67f981ee5c6f9c02b8b1c/grpcio-1.82.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5dafb1ece8ed45dee7c738f166ec82e19673221ed5ab8967f72858a4685345b2", size = 6924269, upload-time = "2026-07-08T12:34:40.583Z" }, + { url = "https://files.pythonhosted.org/packages/e4/b9/3aae7a03d34c86ea27988db859a6087c186f6c3f53f9b551e07afd989bfa/grpcio-1.82.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06503106e7271e0a49fd5a1ac04747f1e47e87d900476db6fe45bc87ee411f4", size = 7531848, upload-time = "2026-07-08T12:34:43.277Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/3c4afa625d0dac9090707966916284c035fc5b2fb3e2c51e156accee6735/grpcio-1.82.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ff99bc8cafb6a952201c37b995f425e641c93ffa6e072258525feab57290141d", size = 8568217, upload-time = "2026-07-08T12:34:45.502Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/d8489c628e73e20a3d034e7f66912de7b1acb405f01d388f056a88e47924/grpcio-1.82.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:644ae1b94266ac785330f4590a69e52b6a7eb73029043a02209db81c81397d69", size = 7938771, upload-time = "2026-07-08T12:34:48.323Z" }, + { url = "https://files.pythonhosted.org/packages/dc/88/d1350bf3343a2ed87d801584e40609f6c6bd3087926eeca03de50348cf4a/grpcio-1.82.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:c09bd5fa0d5b1fbd773ec349fe61441c3e4ebf168c229aa7538a820bdfad6a58", size = 6144689, upload-time = "2026-07-08T12:34:55.567Z" }, + { url = "https://files.pythonhosted.org/packages/e6/33/71875cdecd27c24ac1385d4783a09853f01b84a825a36aec2a2bc7d0d080/grpcio-1.82.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1eae24810720734598e3e6a1a528d5de0f265fe3fc86575e9ecce424b9ec7379", size = 11952034, upload-time = "2026-07-08T12:34:58.128Z" }, + { url = "https://files.pythonhosted.org/packages/82/b2/d9125df3d8a140dec12cc82c05b7deafedeababcff6496f28b2fd5634d10/grpcio-1.82.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a6bd5daf5bde7b24d7ad2cbaf8bf9eac620d96222016bb5e7ddde930dec0673f", size = 6710772, upload-time = "2026-07-08T12:35:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/88/9b/69e2d1627398b964f34437dc476a5aff5a2cc8e7f247d26272b5674b5faf/grpcio-1.82.1-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1ecfde669cb687ac020d31ff76debe5dc7a62213335f02262eb6625628da1c03", size = 7450677, upload-time = "2026-07-08T12:35:03.926Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e7/8f855ca29c294956122a2a73023655b9b02602d5111dad2b9b00e7631c68/grpcio-1.82.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:011c8badee95734dee8bf05ce3464756a0ac3ebb8d443afd20c0e2b5e4640ad9", size = 6886855, upload-time = "2026-07-08T12:35:06.174Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f0/fa87e85f49925f44c479d07e58b051e69bcfef6b6d5fbc6749d140f6730a/grpcio-1.82.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b85f4564926fb23114d239392bdcae200db1e6179629edd7d7ab0ab89c96a197", size = 7501323, upload-time = "2026-07-08T12:35:08.49Z" }, + { url = "https://files.pythonhosted.org/packages/67/55/2e0b10ae1d3ef9dcc480b91dc2158f4931fc4675d3af0a2836e39b2a744f/grpcio-1.82.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2c0c8270833395644c3fe6b6a806397955a2bc0538000a19a78b90c05a6c16e0", size = 8536899, upload-time = "2026-07-08T12:35:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/bd/95/a3d8b0431fa221efc51ee39d73595ede74ba82a43b7c4313192e580face2/grpcio-1.82.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2ba199205ff46c7778290fe1673c91ac8e7e45678dd5c86e9e56fa33ec8788f6", size = 7913892, upload-time = "2026-07-08T12:35:13.944Z" }, +] + +[[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.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/39/67be8d71f900d9a55761b6022821d6679fb56c64f1b6063d5af2c2606727/hf_xet-1.5.2.tar.gz", hash = "sha256:73044bd31bae33c984af832d19c752a0dffb67518fee9ddbd91d616e1101cf47", size = 903674, upload-time = "2026-07-16T17:29:56.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/ba/2b70603c7552db82baeb2623e2336898304a17328845151be4fe1f48d420/hf_xet-1.5.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f922b8f5fb84f1dd3d7ab7a1316354a1bca9b1c73ecfc19c76e51a2a49d29799", size = 4033760, upload-time = "2026-07-16T17:29:43.884Z" }, + { url = "https://files.pythonhosted.org/packages/60/ac/b097a86a1e4a6098f3a79382643ab09d5733d87ccc864877ad1e12b49b70/hf_xet-1.5.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:045f84440c55cdeb659cf1a1dd48c77bcd0d2e93632e2fea8f2c3bdee79f38ed", size = 3841438, upload-time = "2026-07-16T17:29:45.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/35/db860aa3a0780660324a506ad4b3d322ddc6ecbba4b9340aed0942cbf21c/hf_xet-1.5.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db78c39c83d6279daddc98e2238f373ab8980685556d42472b4ec51abcf03e8c", size = 4428006, upload-time = "2026-07-16T17:29:46.996Z" }, + { url = "https://files.pythonhosted.org/packages/af/6b/832dd980af4b0c3ae0660e309285f2ffcdff2faa38129390dbb47aa4a3f9/hf_xet-1.5.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7db73c810500c54c6760be8c39d4b2e476974de85424c50063efc22fdda13025", size = 4221099, upload-time = "2026-07-16T17:29:48.525Z" }, + { url = "https://files.pythonhosted.org/packages/9e/05/ae50f0d34e3254e6c3e208beb2519f6b8673016fc4b3643badaf6450d186/hf_xet-1.5.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6395cfe3c9cbead4f16b31808b0e67eac428b66c656f856e99636adaddea878f", size = 4420766, upload-time = "2026-07-16T17:29:50.092Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/c050bc2743a2bcd68928bfee157b08681667a164a24ec95fbfcfcd717e08/hf_xet-1.5.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cde8cd167126bb6109b2ceb19b844433a4988643e8f3e01dd9dd0e4a34535097", size = 4636716, upload-time = "2026-07-16T17:29:51.62Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "h11", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "httpcore", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "filelock", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "fsspec", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "hf-xet", marker = "(platform_machine == 'AMD64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'amd64' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and sys_platform == 'linux') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'amd64' and sys_platform == 'linux') or (platform_machine == 'arm64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "httpx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/9b/d3bb4e7d792835daf34dd7091bbc7d7b4e0437d9388f1ea7239cce49f478/huggingface_hub-1.24.0.tar.gz", hash = "sha256:18431ff4daae0749aa9ba102fc952e314c98e1d30ebdec5319d85ca0a83e1ae5", size = 921848, upload-time = "2026-07-17T09:54:01.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/c3/aeaaf3911d2529614be18d1c8b5496afc185560e76568063d517283318af/huggingface_hub-1.24.0-py3-none-any.whl", hash = "sha256:6ed4120a84a6beec900640aa7e346bd766a6b7341e41526fef5dc8bd81fb7d59", size = 771904, upload-time = "2026-07-17T09:53:59.106Z" }, +] + +[[package]] +name = "hydra-core" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "omegaconf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/8e/07e42bc434a847154083b315779b0a81d567154504624e181caf2c71cd98/hydra-core-1.3.2.tar.gz", hash = "sha256:8a878ed67216997c3e9d88a8e72e7b4767e81af37afb4ea3334b269a4390a824", size = 3263494, upload-time = "2023-02-23T18:33:43.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/50/e0edd38dcd63fb26a8547f13d28f7a008bc4a3fd4eb4ff030673f22ad41a/hydra_core-1.3.2-py3-none-any.whl", hash = "sha256:fa0238a9e31df3373b35b0bfb672c34cc92718d21f81311d8996a16de1141d8b", size = 154547, upload-time = "2023-02-23T18:33:40.801Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "indic-numtowords" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/46/683e92580d9c1752917d2f9ec2a44d2adc21cdfe4deeaa0fe87fc23dbea8/indic_numtowords-1.1.0.tar.gz", hash = "sha256:d1addc21444c332e05bfd8726af427960c096c2a16776d98bee4fbc36ade5d25", size = 44220, upload-time = "2025-08-18T12:24:13.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/16/b2b6491d95a15bda712163b4e27428f2cb1ac3a1b4fb59b140dbc76f6ce5/indic_numtowords-1.1.0-py3-none-any.whl", hash = "sha256:bf4b7b9e539323d9b00bc868caa2d9369170b8f3ac4d19619bf9c6cdc6f89572", size = 71635, upload-time = "2025-08-18T12:24:11.065Z" }, +] + +[[package]] +name = "inflect" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typeguard", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, +] + +[[package]] +name = "intervaltree" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sortedcontainers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/c3/b2afa612aa0373f3e6bb190e6de35f293b307d1537f109e3e25dbfcdf212/intervaltree-3.2.1.tar.gz", hash = "sha256:f3f7e8baeb7dd75b9f7a6d33cf3ec10025984a8e66e3016d537e52130c73cfe2", size = 1231531, upload-time = "2025-12-24T04:25:06.773Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7f/8a80a1c7c2ed05822b5a2b312d2995f30c533641f8198366ba2e26a7bb03/intervaltree-3.2.1-py2.py3-none-any.whl", hash = "sha256:a8a8381bbd35d48ceebee932c77ffc988492d22fb1d27d0ba1d74a7694eb8f0b", size = 25929, upload-time = "2025-12-24T04:25:05.298Z" }, +] + +[[package]] +name = "ipython" +version = "9.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "decorator", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "ipython-pygments-lexers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "jedi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "matplotlib-inline", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pexpect", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "prompt-toolkit", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "psutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "stack-data", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "traitlets", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "(python_full_version < '3.12' and sys_platform == 'darwin') or (python_full_version < '3.12' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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 = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "rapidfuzz", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/3e/71b95cf0e2179fb5de8744a79fd36c8bd4e02e1803129a16d423884b6654/jiwer-3.1.0.tar.gz", hash = "sha256:dc492d09e570f1baba98c76aba09baf8e09c06e6808a4ba412dd4bde67fb79ac", size = 103187, upload-time = "2025-01-31T12:14:10.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/f4/35634d9eeff3b0bab51f5b9474ee569b1186bf29cf0d9d67b84acc80c53d/jiwer-3.1.0-py3-none-any.whl", hash = "sha256:5a14b5bba4692e1946ca3c6946435f7d90b1b526076ccb6c12be763e2146237d", size = 22303, upload-time = "2025-01-31T12:14:08.893Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "kaldi-python-io" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/45/e3e542ffa8970ebd782fcece35e2295de9c60e8c396c2c1a403410d1b24e/kaldi-python-io-1.2.2.tar.gz", hash = "sha256:4ebb4029c6c58296cc0abf96edff02832ba341d290ed37624a8d00105f0f7c00", size = 8814, upload-time = "2021-03-18T12:02:05.832Z" } + +[[package]] +name = "kaldialign" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/58/bd83457ab1f4296cb48da2057330fd669e14d0139353d87f9c138fc055af/kaldialign-0.12.0.tar.gz", hash = "sha256:f25743ef7dcf15716c5ac47e164dd7191cab791196a78c27cff6a99c253e1a14", size = 31415, upload-time = "2026-06-15T13:16:40.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/0a/8ab4e2d67477f99933d6223e895e9183c5dc03e998aece770b43ffd4996d/kaldialign-0.12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:188b7a5b267ee1435332c42a9862065b28ac72d74f883a58b2263d330dab1623", size = 165066, upload-time = "2026-06-15T13:16:32.139Z" }, + { url = "https://files.pythonhosted.org/packages/db/fe/3aced2daa28ab585e99b99c6503a5ad2423dfdfbb01591ea7b6c74ba5012/kaldialign-0.12.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb94f376bc851b32862b5e33f4ac948b8e0627e082ec6bf42611ca87564c19de", size = 99681, upload-time = "2026-06-15T13:17:01.06Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/8cab4ce97fe3f9654da1ca7e43a20b98de38491bb7aa4265e716aaa9450b/kaldialign-0.12.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0703057b1b8a5e46f58182d31729681ed0ce0a8fb4fcb5555f36cac39805d2b6", size = 110009, upload-time = "2026-06-15T13:16:57.525Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/f7c852333a77887ce9166415512d19da329c4896ec0a16127b250c20fd5e/kaldialign-0.12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3a4f914f4971d20784710944a965de5ce0d1aa1ce26934767c0aee95194e0d98", size = 166411, upload-time = "2026-06-15T13:16:45.578Z" }, + { url = "https://files.pythonhosted.org/packages/35/c9/a01c036b6e0afef2139bcc3088d3ce9fbad111864457c89ac059b8fbaee4/kaldialign-0.12.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62694144a5e0ae086910bd979bc6b99b36c97a4276614bd28b5b06fed133abbf", size = 99987, upload-time = "2026-06-15T13:18:05.331Z" }, + { url = "https://files.pythonhosted.org/packages/d0/00/77885350408ef2ec6281cafd3dfbec099c25893d502bcc7ac819637309c5/kaldialign-0.12.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8189d32046b3a865b6f147662620feba736580b5424368164c6a7bc927d3e0a8", size = 110985, upload-time = "2026-06-15T13:16:46.189Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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 = "lhotse" +version = "1.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioread", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "cytoolz", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "intervaltree", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tabulate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/5a/b606c87b0a50322200aafb0f0682e719890bf0f045152b53e161090a6e8f/lhotse-1.33.0.tar.gz", hash = "sha256:3e91fca8531fc4c1798d0a6de1b3c7ea6bf2e181df70e5985927a131761c67f5", size = 686482, upload-time = "2026-04-20T13:11:08.579Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/e2/fbcb65dfed851f28ea15eca62cf449bc0b36378b005e6bec720714a9fb19/lhotse-1.33.0-py3-none-any.whl", hash = "sha256:8697bc74a8f3101594fca5661c7318c30899f3fdb132a44c7e99e794be6ac061", size = 903925, upload-time = "2026-04-20T13:11:07.027Z" }, +] + +[[package]] +name = "libcst" +version = "1.8.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/cd/337df968b38d94c5aabd3e1b10630f047a2b345f6e1d4456bd9fe7417537/libcst-1.8.6.tar.gz", hash = "sha256:f729c37c9317126da9475bdd06a7208eb52fcbd180a6341648b45a56b4ba708b", size = 891354, upload-time = "2025-11-03T22:33:30.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/15/95c2ecadc0fb4af8a7057ac2012a4c0ad5921b9ef1ace6c20006b56d3b5f/libcst-1.8.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3649a813660fbffd7bc24d3f810b1f75ac98bd40d9d6f56d1f0ee38579021073", size = 2211289, upload-time = "2025-11-03T22:32:04.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/c3/7e1107acd5ed15cf60cc07c7bb64498a33042dc4821874aea3ec4942f3cd/libcst-1.8.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cbe17067055829607c5ba4afa46bfa4d0dd554c0b5a583546e690b7367a29b6", size = 2092927, upload-time = "2025-11-03T22:32:06.209Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ff/0d2be87f67e2841a4a37d35505e74b65991d30693295c46fc0380ace0454/libcst-1.8.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:59a7e388c57d21d63722018978a8ddba7b176e3a99bd34b9b84a576ed53f2978", size = 2237002, upload-time = "2025-11-03T22:32:07.559Z" }, + { url = "https://files.pythonhosted.org/packages/69/99/8c4a1b35c7894ccd7d33eae01ac8967122f43da41325223181ca7e4738fe/libcst-1.8.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:b6c1248cc62952a3a005792b10cdef2a4e130847be9c74f33a7d617486f7e532", size = 2301048, upload-time = "2025-11-03T22:32:08.869Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8b/d1aa811eacf936cccfb386ae0585aa530ea1221ccf528d67144e041f5915/libcst-1.8.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6421a930b028c5ef4a943b32a5a78b7f1bf15138214525a2088f11acbb7d3d64", size = 2300675, upload-time = "2025-11-03T22:32:10.579Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6b/7b65cd41f25a10c1fef2389ddc5c2b2cc23dc4d648083fa3e1aa7e0eeac2/libcst-1.8.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6d8b67874f2188399a71a71731e1ba2d1a2c3173b7565d1cc7ffb32e8fbaba5b", size = 2407934, upload-time = "2025-11-03T22:32:11.856Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3c/93365c17da3d42b055a8edb0e1e99f1c60c776471db6c9b7f1ddf6a44b28/libcst-1.8.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0c13d5bd3d8414a129e9dccaf0e5785108a4441e9b266e1e5e9d1f82d1b943c9", size = 2206166, upload-time = "2025-11-03T22:32:16.012Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cb/7530940e6ac50c6dd6022349721074e19309eb6aa296e942ede2213c1a19/libcst-1.8.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f1472eeafd67cdb22544e59cf3bfc25d23dc94058a68cf41f6654ff4fcb92e09", size = 2083726, upload-time = "2025-11-03T22:32:17.312Z" }, + { url = "https://files.pythonhosted.org/packages/1b/cf/7e5eaa8c8f2c54913160671575351d129170db757bb5e4b7faffed022271/libcst-1.8.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:089c58e75cb142ec33738a1a4ea7760a28b40c078ab2fd26b270dac7d2633a4d", size = 2235755, upload-time = "2025-11-03T22:32:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/55/54/570ec2b0e9a3de0af9922e3bb1b69a5429beefbc753a7ea770a27ad308bd/libcst-1.8.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c9d7aeafb1b07d25a964b148c0dda9451efb47bbbf67756e16eeae65004b0eb5", size = 2301473, upload-time = "2025-11-03T22:32:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/11/4c/163457d1717cd12181c421a4cca493454bcabd143fc7e53313bc6a4ad82a/libcst-1.8.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:207481197afd328aa91d02670c15b48d0256e676ce1ad4bafb6dc2b593cc58f1", size = 2298899, upload-time = "2025-11-03T22:32:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/35/1d/317ddef3669883619ef3d3395ea583305f353ef4ad87d7a5ac1c39be38e3/libcst-1.8.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:375965f34cc6f09f5f809244d3ff9bd4f6cb6699f571121cebce53622e7e0b86", size = 2408239, upload-time = "2025-11-03T22:32:23.275Z" }, +] + +[[package]] +name = "librosa" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioread", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "decorator", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "joblib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lazy-loader", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "msgpack", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numba", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pooch", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scikit-learn", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'darwin') or (python_full_version < '3.12' and sys_platform == 'linux')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soxr", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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 = "lightning" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lightning-utilities", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pytorch-lightning", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, + { name = "torchmetrics", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/d0/78ea244ac044cd4df15aa8294a50ff3561fb177e7e5ba788aaa542046cae/lightning-2.4.0.tar.gz", hash = "sha256:9156604cc56e4b2b603f34fa7f0fe5107375c8e6d85e74544b319a15faa9ed0e", size = 620632, upload-time = "2024-08-07T09:46:44.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/2c/85eaf42c983b0cd81bcda5876da2c8e2a9fd347908666ea9855724369171/lightning-2.4.0-py3-none-any.whl", hash = "sha256:560163af9711cf59055c448232c473150a299089efce0d2be3cc3288082d8768", size = 810971, upload-time = "2024-08-07T09:46:39.874Z" }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, +] + +[[package]] +name = "llvmlite" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/a0/acc8ffcd5bdc63df0097e22c719bfcd61b604358343089313a8aebbb24ab/llvmlite-0.48.0.tar.gz", hash = "sha256:543b19f9ef8f3c7c60d1468191e4ee1b1537bf9f8a3d56f64c0ddd98de92edd2", size = 184016, upload-time = "2026-07-02T20:20:05.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/55/595981f14fbae9ba966feb12af552b1fe69889e44e64ac883a731ed335e0/llvmlite-0.48.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:56a7e24607d3f02d7b1bae8d29c7e1e423d53143d68b072999777f19678fe77b", size = 40480651, upload-time = "2026-07-01T18:41:18.438Z" }, + { url = "https://files.pythonhosted.org/packages/26/08/0109d1b9cb3f4603f3890e30bc66c65332b79185f12a045343b2ae431f67/llvmlite-0.48.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6fa532d6bb3fd3f0803567c736401c54aecfe1a396d3ad25d2440d220e09f0e7", size = 59890118, upload-time = "2026-07-01T18:41:28.184Z" }, + { url = "https://files.pythonhosted.org/packages/02/eb/c5281be180c789cdffbf45b671884c57d7e61345ef3b0f643a4965e108e8/llvmlite-0.48.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:979a66a3f28a02565383ff463527dce78e9b856298872a361283132488e83591", size = 58343458, upload-time = "2026-07-01T18:41:23.397Z" }, + { url = "https://files.pythonhosted.org/packages/92/a2/28696a9e61e245d1a79816d29d106692a90a2b6e7d78c98b326db70827af/llvmlite-0.48.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:d66c3beb4209087ddd4cf4ed2a0856b6887e6a913bdcf1aacfec9851cf2cba4e", size = 40480651, upload-time = "2026-07-01T18:41:35.694Z" }, + { url = "https://files.pythonhosted.org/packages/80/f2/72409351db66d0a317ec5087e076f31fb7b773a640db8a90ce6b5cac9edd/llvmlite-0.48.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:416fa4c2c66c2c6dc6d0a402648c19206e548efa0aa1eff01ad5cdad0af8217d", size = 59890118, upload-time = "2026-07-01T18:41:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/3a/27/5ae2f3722606360480707adb47f001ad89df8251d06b14ee80336e660b66/llvmlite-0.48.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5e5a5131045b72345c71062ea1a91910dde913792b6c9b28ebb2c1c0a712e98", size = 58343459, upload-time = "2026-07-01T18:41:40.306Z" }, +] + +[[package]] +name = "lxml" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/b0/83f481780d1548750b8ce2ec824073deef2f452d9cd1a6faff8507e3d16d/lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", size = 8526461, upload-time = "2026-05-18T19:17:25.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/30fa0f808002c7329397bfbb24e306789c0b29f04aa5842c07b174b4216f/lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", size = 4595375, upload-time = "2026-05-18T19:17:34.555Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d2/edb71cf0e561581a7c5eb2626244320eb04e9f8ce6d563184fd668b45073/lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", size = 4923654, upload-time = "2026-05-18T19:17:42.917Z" }, + { url = "https://files.pythonhosted.org/packages/4c/77/1bc7eeb0de4577d783fb625aa092cc9357883bba35845a3666bf1259f3dc/lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", size = 5067921, upload-time = "2026-05-18T19:17:49.175Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3c/c0690d74bd2bc17bc03b5b0d093569ead597dd0bfa088bf99eef8c24e19c/lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", size = 5002456, upload-time = "2026-05-18T19:17:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/66/8d/d1b3271af0c0f1e27e8472a849e4d2c65bc7766884b9ad2da9e76e145c88/lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", size = 5202776, upload-time = "2026-05-18T19:18:08.924Z" }, + { url = "https://files.pythonhosted.org/packages/7a/45/689824ffb237fd10125ad273f32b28ff04dc6203c2822c85ff65a93df65e/lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", size = 5329945, upload-time = "2026-05-18T19:18:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c0/ef73af53767e958fd87d437c170f272e2f6e6c0f854939f133a895f1e711/lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", size = 4659237, upload-time = "2026-05-18T19:18:18.657Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/e1158e40397585e91cb0472374a1f63d0926a1ddeaa92f13d1a1ffe306d5/lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", size = 5265904, upload-time = "2026-05-18T19:18:24.883Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/8687e5d1400ed1c0bc41dace232ebb7553952b618ea1f2e5fb6e2cfbbe23/lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", size = 5045225, upload-time = "2026-05-18T19:17:20.073Z" }, + { url = "https://files.pythonhosted.org/packages/ca/18/d877bd1ae2e5ffdfd4836565aba350db31feb2f2656d6ce70316ed66a05e/lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", size = 4712721, upload-time = "2026-05-18T19:17:40.512Z" }, + { url = "https://files.pythonhosted.org/packages/44/4d/1f44fd1d770b10dacbf6b5c6e520f4d6e0708744930f719dc04e67cab981/lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", size = 5252549, upload-time = "2026-05-18T19:17:51.236Z" }, + { url = "https://files.pythonhosted.org/packages/64/5d/1d66b84f850089254c230ef6ea6b267a5a54e2e179a5d960036a05d501d7/lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", size = 5226877, upload-time = "2026-05-18T19:18:00.875Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6e/c4add832b6fc1e887125b96f880d7b9b70aae5248718e046b1704bcac4b9/lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", size = 8570821, upload-time = "2026-05-18T19:17:42.068Z" }, + { url = "https://files.pythonhosted.org/packages/22/00/ff3009c88e65de8011630acf8ab5a09cb2becd2aaf47fba2f3449f6224e9/lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", size = 4624252, upload-time = "2026-05-18T19:17:47.897Z" }, + { url = "https://files.pythonhosted.org/packages/42/95/bb63f0fd62e554fe078e1fb3c8fe9083c14ddc7ad7fa178d10e57e071ac7/lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", size = 4930746, upload-time = "2026-05-18T19:18:29.637Z" }, + { url = "https://files.pythonhosted.org/packages/eb/99/0013e8d9b5960f4f041cf0b73e2f80c23eb5205b1f7bfb20203243651359/lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", size = 5093723, upload-time = "2026-05-18T19:18:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/29/91/317b332636bfc7bddcff828d41b3307f50043f4b237e40849c333d80fa1a/lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", size = 5005557, upload-time = "2026-05-18T19:18:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/42/2f/cc9bf06afe70f9c9093ae60855d9759da9db601ec4080f7473319666ffd7/lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", size = 5631036, upload-time = "2026-05-18T19:18:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/08/f6/af32e23e563971ffb0fb86be52bc5be5c2c118858ffc119bf6a9039b173d/lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", size = 5240367, upload-time = "2026-05-18T19:18:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/8555d40948b09ce86f1bd0c68a7ac31d07b1929f92cc1b074006c97ef2d2/lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", size = 5350171, upload-time = "2026-05-18T19:18:52.779Z" }, + { url = "https://files.pythonhosted.org/packages/63/75/5d92da93729b7bad783689e6496049fa40927b45bec7bf183c981de3ca70/lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", size = 4694874, upload-time = "2026-05-18T19:18:55.139Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b5/3aad415a9a25b822e783f15deeb4dffccf5113030f1afa2222dd929313d9/lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", size = 5244492, upload-time = "2026-05-18T19:19:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a1/5fcf7eb9904b80086aa47dcf0027de07b1bb990afad2e6823144c368ae04/lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", size = 5048232, upload-time = "2026-05-18T19:18:12.67Z" }, + { url = "https://files.pythonhosted.org/packages/77/74/1f601b63c7a69fcdf10fa9b148c81da8442204194f6c55509cc485c786b9/lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", size = 4777023, upload-time = "2026-05-18T19:18:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b9/7a78f51aec95b1bf780d78e12705a9f6533284f8693dc5c0e6724fa53d3f/lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", size = 5645773, upload-time = "2026-05-18T19:18:23.223Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6e/98a7b7ad54e4e74fa1f20fff776913980619d0ebe5558232d7da6580bdd8/lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", size = 5233088, upload-time = "2026-05-18T19:18:31.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/b5/32/86a3f0f724a3a402d4627937a7fc27b160e45e7012b4adf47f6e1e844511/lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", size = 3930127, upload-time = "2026-05-18T19:19:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/d832e82af08723761556d004b1d04d281c09f9a8cecd7d3148548c9941a3/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", size = 4210769, upload-time = "2026-05-18T19:20:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/0dc5949f759ed7d951e0bb8c2f2d9d7aca1908d22352fa84a8afd2ea54af/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", size = 4318163, upload-time = "2026-05-18T19:20:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fb/8ab3845fe046ba4cbf74536bcf6801a774b7caf4350de1c5d37f1f0a9e90/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", size = 4250945, upload-time = "2026-05-18T19:20:47.385Z" }, + { url = "https://files.pythonhosted.org/packages/68/1b/7553ab136894374ffae8851ec06f98f511cd8e66246e41b6be059d0a7289/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", size = 4401664, upload-time = "2026-05-18T19:20:50.489Z" }, +] + +[[package]] +name = "mako" +version = "1.3.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/62/791b31e69ae182791ec67f04850f2f062716bbd205483d63a215f3e062d3/mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a", size = 400219, upload-time = "2026-04-28T19:01:08.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/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" }, +] + +[[package]] +name = "marshmallow" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/7e/1dbd4096eb7c148cd2841841916f78820bb85a4d80a0c25c02d30815a7fb/marshmallow-4.3.0.tar.gz", hash = "sha256:fb43c53b3fe240b8f6af37223d6ef1636f927ad9bea8ab323afad95dff090880", size = 224485, upload-time = "2026-04-03T21:46:32.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/e0/ff24e25218bb59eb6290a530cea40651b14068b6e3659b20f9c175179632/marshmallow-4.3.0-py3-none-any.whl", hash = "sha256:46c4fe6984707e3cbd485dfebbf0a59874f58d695aad05c1668d15e8c6e13b46", size = 49148, upload-time = "2026-04-03T21:46:31.241Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "cycler", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "fonttools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "kiwisolver", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyparsing", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mediapy" +version = "1.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipython", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "matplotlib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/b2/451be65c13d2d69b7601eded7ddd3f150884486715a9b3a705ffb08d0177/mediapy-1.1.6.tar.gz", hash = "sha256:9f44b760400964d8bea5121a213f94dc9a225d026d6a819901283a695e585634", size = 25459, upload-time = "2023-02-24T13:08:42.429Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/a0/0d55c59ea8a5f1b13b3eb931e1c0eab9c2a07322cad79cb51a596d2d2a5c/mediapy-1.1.6-py3-none-any.whl", hash = "sha256:c74370808b445666f95272bfdf0eb5707a43b7e05e5527f2dd0830e6892f976f", size = 24955, upload-time = "2023-02-24T13:08:40.53Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, +] + +[[package]] +name = "more-itertools" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, +] + +[[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.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/6b/e9b1cdc042c4458801d2545ed782a95f3d6ba8e270cce8745b8603c7f748/msgpack-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:29a3f6e9667868429d8240dfd063ea5ffdc1321c13d783aa23827a38de0dcb22", size = 82812, upload-time = "2026-06-18T16:12:45.022Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3a/dd518a1bf78ed1e9ad8afe57307c079a00eafe4b3068932a27ca1ea56b4f/msgpack-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aded5bdf32609dc7987a49bbbd15a8ef096193f96dd8bbeb791de729e650acf5", size = 82739, upload-time = "2026-06-18T16:12:46.025Z" }, + { url = "https://files.pythonhosted.org/packages/70/e0/7ba9e1542bf0771a27b8b37c1316e3f95ae9d748fd765284655c476ad4ef/msgpack-1.2.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:146ee4e9ce80b365c6d4c47073da9da7bcec473e58194ceee5dd7620ace77e06", size = 414233, upload-time = "2026-06-18T16:12:47.029Z" }, + { url = "https://files.pythonhosted.org/packages/03/8d/671d81534ea0e2b0e8a121be100020da09eb78861fe3aa8f3ef7dcd3bed1/msgpack-1.2.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a28d076ca7c82b9c8728ad90b7147489449557038bed50e4241eb832395169b4", size = 423843, upload-time = "2026-06-18T16:12:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b6/e5c737515ed1f166664b87601b532f58cbb73d8aa6a90b99f7c2c5037e8e/msgpack-1.2.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7d31c0ac0c640f877804c67cb2bc9f4e23dc2db97e96c2e67fa27d38283b41f8", size = 390772, upload-time = "2026-06-18T16:12:49.624Z" }, + { url = "https://files.pythonhosted.org/packages/a8/46/62ed8c2e87d7021eab19921594d961ef3aa3794eec76c716dc30f3bfd433/msgpack-1.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8ff92d7feeaf5bc26c51495b69e2f99ed97ab79346fb6555f44be7dd2ac6503b", size = 409559, upload-time = "2026-06-18T16:12:50.936Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/59aa3887b860bbf43532835e192b1c388a17590d6068ae4f8b2bc74c906e/msgpack-1.2.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:779197a6513bab3c3632265e3d0f7cb3227e62510841a6f34f1eaa37efbb345e", size = 387838, upload-time = "2026-06-18T16:12:52.161Z" }, + { url = "https://files.pythonhosted.org/packages/09/11/f8563e471093420cf6478cb3271a0175d8402b82d879783d4035d2d03360/msgpack-1.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:67f6dd22fa72a93752643f07889796d62739a13415ee630169a8ce764f86cf9f", size = 421732, upload-time = "2026-06-18T16:12:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/9e8cbd8f5582ca4b590336f2b91ee5662f6a6ca562b565abaf696a0f81ff/msgpack-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ef59c659f289eddf8aa6623823f19fa2f40a4029266889eac7a2505dd210c35", size = 83531, upload-time = "2026-06-18T16:12:58.249Z" }, + { url = "https://files.pythonhosted.org/packages/50/2e/ebdb85a8da151397a2790363676b7ed7c125924fe618e4c6d8befb0cc62c/msgpack-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d3567748a5107cb40cdf66a275430c2f87c07777698f4bfd25c35f44d533258c", size = 82657, upload-time = "2026-06-18T16:12:59.396Z" }, + { url = "https://files.pythonhosted.org/packages/26/aa/753ad8b007b464e1d8aa0c8e650b9c5f4f725e658fc5ac8a7635c55b7f6e/msgpack-1.2.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60926b75d00c8e816ef98f3034f484a8bc64242d66839cef4cf7e503142316a0", size = 410634, upload-time = "2026-06-18T16:13:00.383Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/6adabd4f6d5e686f97dd02ce7fce3fe4cf672cbac36b8f67ff4040e8ad8b/msgpack-1.2.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:020e881a764b20d8d7ca1a54fc01b8175519d108e3c3f194fddc200bda95951a", size = 419989, upload-time = "2026-06-18T16:13:01.776Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/85039b7b0eb168aaad7383a23c97e291a11f08351cb45a606ce865e4e3f1/msgpack-1.2.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4202c74688ca06591f78cb18988228bd4cca2cc75d57b60008372892d2f1e6e6", size = 377544, upload-time = "2026-06-18T16:13:03.637Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/35963899493b32030c85fc513b723ae66144ac70c11ebc52e889e16e3d99/msgpack-1.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b267ce94efb76fbd1b3373511420074ee3187f0f7811bf394531de13294735a", size = 400842, upload-time = "2026-06-18T16:13:05.012Z" }, + { url = "https://files.pythonhosted.org/packages/a6/df/8e2ac970c8f99264cd9997d1c73df5466bc19da3301d7dc5500862a9b089/msgpack-1.2.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f1d0f8f98ade9634e01fb704a408f9336c0a8f1117b369f5db83dc7551d8b1", size = 374108, upload-time = "2026-06-18T16:13:06.232Z" }, + { url = "https://files.pythonhosted.org/packages/17/dd/fa8bd265110dfa51c20cb529f9e6d240a16fafe7e645004c6af2d01353ba/msgpack-1.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f02cf17a6ca1abe29b5f980644f7551f94d71f2011509b26d8625ce038f0df64", size = 414939, upload-time = "2026-06-18T16:13:07.478Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743, upload-time = "2026-01-19T06:47:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738, upload-time = "2026-01-19T06:47:26.636Z" }, + { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741, upload-time = "2026-01-19T06:47:27.985Z" }, + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + +[[package]] +name = "narwhals" +version = "2.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/1d/58946e5aab18393e793bd4add6985b95d0e01c3a2d832f38f54468b10dcd/narwhals-2.24.0.tar.gz", hash = "sha256:b5c0f684ccd9d7475b564111e319a4964abcf2baf79d3cf6b1003d06ac9b828d", size = 661143, upload-time = "2026-07-13T10:49:19.086Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/85/a5bfaebfd305ac18b57b0854d74e37e586809061a91fda62f0bd50c8518e/narwhals-2.24.0-py3-none-any.whl", hash = "sha256:42fdedf44e5b2ca7505630d45b4ac3058f38d8485cba9fe1652ca23152df7489", size = 461030, upload-time = "2026-07-13T10:49:17.571Z" }, +] + +[[package]] +name = "nemo-toolkit" +version = "2.8.0rc0" +source = { git = "https://github.com/NVIDIA-NeMo/NeMo.git?rev=6967f48fda2a#6967f48fda2a776a68bbaed6a71d7fea78ccc3f6" } +dependencies = [ + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "fsspec", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numba", marker = "sys_platform == 'darwin'" }, + { name = "numexpr", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "onnx", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "ruamel-yaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scikit-learn", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tensorboard", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "text-unidecode", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "wget", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "wrapt", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[package.optional-dependencies] +asr = [ + { name = "braceexpand", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "cloudpickle", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "datasets", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "editdistance", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "einops", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "fiddle", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "hydra-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "inflect", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "jiwer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "kaldi-python-io", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "kaldialign", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lhotse", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "librosa", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lightning", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "marshmallow", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "mediapy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-pytorch-lightning-integration", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-training-telemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "omegaconf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "optuna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "peft", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyannote-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyannote-metrics", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyloudnorm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "resampy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "ruamel-yaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sacrebleu", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sacremoses", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'darwin') or (python_full_version < '3.12' and sys_platform == 'linux')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, + { name = "sentencepiece", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sox", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torchmetrics", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "transformers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "wandb", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "webdataset", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "whisper-normalizer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] + +[[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 = "numba" +version = "0.66.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "llvmlite", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/a0/570e3dc53e5602b49108f62a13e529f1eec8bfc7ef37d49c825924dcf546/numba-0.66.0.tar.gz", hash = "sha256:b900e63a0e26c05ea9a6d5a3a5a0a177cb64c5011887bf43edb8c3ed2c38d363", size = 2806181, upload-time = "2026-07-01T23:12:46.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/02/970796b4daa709604cde22e87a7cda9bde473c278ea4a75f59fe38cee47f/numba-0.66.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bbd531c327557a9004507fa6bff06c53ab51a7a5776b75261bb9cef1efe2b2ea", size = 2727049, upload-time = "2026-07-01T23:12:11.296Z" }, + { url = "https://files.pythonhosted.org/packages/8c/99/33a6ed9c1a0b5e42efa98eb0edf617d61dca576c82625947377b1d4540c9/numba-0.66.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc6629becb21a867d85401ec89f426dd24c484a4193ade8a38309debfd1529ca", size = 3808870, upload-time = "2026-07-01T23:12:12.944Z" }, + { url = "https://files.pythonhosted.org/packages/04/20/8c51126025211659235b8de2866dfa226984ae0c8273461a3cf374716741/numba-0.66.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aac69f3ccb8af100f5913c1241edc9692bad1cdd2508721713f426eb06c9a659", size = 3514498, upload-time = "2026-07-01T23:12:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/62/a3/70deb7f88461c1cd5d16aa990c2380604102661a427667b8950dcdccc27f/numba-0.66.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:53ca5900b7cab15109796030113a6b28576bae5ad7bb507ad6dd1360ddd81ba4", size = 2727264, upload-time = "2026-07-01T23:12:18.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/55/25c319845e9a4e08f16611ddbda56a192eb7b6ed13e1a2bff2da272ffb97/numba-0.66.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0999e3ee1b18c48e1fb51d11af35ef59852c7f4f50569c9550c25faef0616ad1", size = 3866252, upload-time = "2026-07-01T23:12:20.429Z" }, + { url = "https://files.pythonhosted.org/packages/71/ef/a82d6fd6bf1b0fe461651e924d3647eeec9ac17f8eee4896264bf7480930/numba-0.66.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efe0d2d5099790df945e0cb6e1b3104bd965d7bbfac50d62f1d5d1d6ade0825d", size = 3566974, upload-time = "2026-07-01T23:12:22.116Z" }, +] + +[[package]] +name = "numexpr" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/ca/c1217ae2c15c3284a9e219c269624f80fa1582622eb0400c711a26f84a43/numexpr-2.13.1.tar.gz", hash = "sha256:ecb722249c2d6ed7fefe8504bb17e056481a5f31233c23a7ee02085c3d661fa1", size = 119296, upload-time = "2025-09-30T18:36:33.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/aa/734ccb5b2d62ddb8c903adf1be8bf668df7fd31f886f8a274203a8317a43/numexpr-2.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bdf62745e072c670151c0705bddfe3f33c341dacb7eb255ddb1e8d2a257bfef5", size = 162936, upload-time = "2025-09-30T18:35:22.227Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bc/bc081354c99d896b5986bb6683bc7f36e221e1464d9b8a5d9c5ad7a29c13/numexpr-2.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:91cf0521d8fed3f804640c4a6d22b5d9813d7e64b32c38215de163c7f092f7cc", size = 151819, upload-time = "2025-09-30T18:35:23.612Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6d/c3a1c3c113a5cf72b431a9f4433511eb35f2063836ed1020f21781ca77aa/numexpr-2.13.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:58e2f111756fff63e27e495473d950e4c98bbebca55aa1572798b59110d6c84b", size = 450816, upload-time = "2025-09-30T18:35:24.887Z" }, + { url = "https://files.pythonhosted.org/packages/77/45/634492e37e31c9db273b6f0d39a83759bfda58ea32690a892b6a5246cfc4/numexpr-2.13.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a5a37b74561ed8dbd5f9be182d94419fa53f452e2d7d3e8d6dbef35a20f19f7", size = 441502, upload-time = "2025-09-30T18:35:26.262Z" }, + { url = "https://files.pythonhosted.org/packages/6a/04/cfd65881165fd800e0ea17985b03793a7a16488c1a93257b2cfa658bb73a/numexpr-2.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78cb76676e63f02dcf507e3c563888018a68b6a2e2cd444628e09df270dfd0b2", size = 1415631, upload-time = "2025-09-30T18:35:27.776Z" }, + { url = "https://files.pythonhosted.org/packages/c7/15/0d037d173c3cd0254fdf1cf148fa4aa79da10119a688cc2e1027de3e7cee/numexpr-2.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d29b3351de4c43b56d2ef7f138ab7a8988e797291bcbbd56d545e4e7902f254a", size = 1464365, upload-time = "2025-09-30T18:35:29.182Z" }, + { url = "https://files.pythonhosted.org/packages/b5/24/b87ad61f09132d92d92e93da8940055f1282ee30c913737ae977cebebab6/numexpr-2.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6aa48c2f2bfa142dfe260441486452be8f70b5551c17bc846fccf76123d4a226", size = 162534, upload-time = "2025-09-30T18:35:33.361Z" }, + { url = "https://files.pythonhosted.org/packages/91/b8/8ea90b2c64ef26b14866a38d13bb496195856b810c1a18a96cb89693b6af/numexpr-2.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:67a3dd8b51e94251f535a9a404f1ac939a3ebeb9398caad20ae9d0de37c6d3b3", size = 151938, upload-time = "2025-09-30T18:35:34.608Z" }, + { url = "https://files.pythonhosted.org/packages/ab/65/4679408c4c61badbd12671920479918e2893c8488de8d5c7f801b3a5f57d/numexpr-2.13.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca152998d44ea30b45ad6b8a050ac4a9408b61a17508df87ad0d919335d79b44", size = 452166, upload-time = "2025-09-30T18:35:36.643Z" }, + { url = "https://files.pythonhosted.org/packages/31/1b/11a1202f8b67dce8e119a9f6481d839b152cc0084940a146b52f8f38685b/numexpr-2.13.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4280c8f7cc024846be8fdd6582572bb0b6bad98fb2a68a367ef5e6e2e130d5f", size = 443123, upload-time = "2025-09-30T18:35:38.14Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5e/271bf56efac177abe6e5d5349365e460a2a4205a514c99e0b2203d827264/numexpr-2.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b86e1daa4e27d6bf6304008ed4630a055babf863db2ec8f282b4058bbfe466bd", size = 1417039, upload-time = "2025-09-30T18:35:39.832Z" }, + { url = "https://files.pythonhosted.org/packages/72/33/6b3164fdc553eceec901793f9df467a7b4151e21772514fc2a392f12c42f/numexpr-2.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30d189fc52ee4a33b869a0592553cd2ed686c20cded21b2ddf347a4d143f1bea", size = 1465878, upload-time = "2025-09-30T18:35:41.437Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, +] + +[[package]] +name = "nv-one-logger-core" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "overrides", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "strenum", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "toml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/37/963095797035f371e0db6ea761f5aaccb624fc786af217115b423baeb0e2/nv_one_logger_core-2.3.1.tar.gz", hash = "sha256:cbb2f87604c78b96a302f32d87199902129d76153a73a20f8455a250b3246c1d", size = 52640, upload-time = "2025-10-29T21:11:55.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/c4/ea91554c4fcbff66057f667690101d7a4b965605741350ac661b03fa6c46/nv_one_logger_core-2.3.1-py3-none-any.whl", hash = "sha256:0c8b77bcdac4daa1ea913bf8d4afd2a057bd5526e3654ac39f67caba157341a6", size = 63066, upload-time = "2025-10-29T21:11:52.753Z" }, +] + +[[package]] +name = "nv-one-logger-pytorch-lightning-integration" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nv-one-logger-training-telemetry", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "strenum", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/d0/3475b7ab17d367362f650fb0419e8669f41e63c1018f4a8ac2fbecfd2e85/nv_one_logger_pytorch_lightning_integration-2.3.1.tar.gz", hash = "sha256:b32d99b6a8f02a16538bcade939b0a7edd7249e936aacefe336b5519447340c3", size = 10979, upload-time = "2025-10-29T21:22:10.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/56/01a55efb365b6864646b4ac941a1d9de66f024e880764510ba5a7a63f62c/nv_one_logger_pytorch_lightning_integration-2.3.1-py3-none-any.whl", hash = "sha256:f92904055fb0082516480cc1e3dd0bb6cedb2b033985ebfd4814b9cbf7da2cb2", size = 9822, upload-time = "2025-10-29T21:22:09.37Z" }, +] + +[[package]] +name = "nv-one-logger-training-telemetry" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nv-one-logger-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "strenum", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/21/016fa067967734d52f1ccf5a2a37a1a65216f2d7053bc2b85872cce956ca/nv_one_logger_training_telemetry-2.3.1.tar.gz", hash = "sha256:8c67940ea71799afaf1f46df3ba2f52f93aea26321c6f1c1d54aae02efc2a4af", size = 44435, upload-time = "2025-10-29T21:21:42.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/15/97e6e4ddfe5fc35bcee74a45b7c33fb73abb83713c7dfa26420b971a86c3/nv_one_logger_training_telemetry-2.3.1-py3-none-any.whl", hash = "sha256:5319443829b59378a498c3c62ac98973e14f31be675c229ff2b14e2fe109aa0b", size = 44140, upload-time = "2025-10-29T21:21:40.72Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.9.1.4" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/6c/90d3f532f608a03a13c1d6c16c266ffa3828e8011b1549d3b61db2ad59f5/nvidia_cublas_cu12-12.9.1.4-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:7a950dae01add3b415a5a5cdc4ec818fb5858263e9cca59004bb99fdbbd3a5d6", size = 575006342, upload-time = "2025-06-05T20:04:16.902Z" }, + { url = "https://files.pythonhosted.org/packages/77/3c/aa88abe01f3be3d1f8f787d1d33dc83e76fec05945f9a28fbb41cfb99cd5/nvidia_cublas_cu12-12.9.1.4-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:453611eb21a7c1f2c2156ed9f3a45b691deda0440ec550860290dc901af5b4c2", size = 581242350, upload-time = "2025-06-05T20:04:51.979Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.9.79" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/78/351b5c8cdbd9a6b4fb0d6ee73fb176dcdc1b6b6ad47c2ffff5ae8ca4a1f7/nvidia_cuda_cupti_cu12-12.9.79-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:791853b030602c6a11d08b5578edfb957cadea06e9d3b26adbf8d036135a4afe", size = 10077166, upload-time = "2025-06-05T20:01:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/c1/2e/b84e32197e33f39907b455b83395a017e697c07a449a2b15fd07fc1c9981/nvidia_cuda_cupti_cu12-12.9.79-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:096bcf334f13e1984ba36685ad4c1d6347db214de03dbb6eebb237b41d9d934f", size = 10814997, upload-time = "2025-06-05T20:01:10.168Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.9.86" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/85/e4af82cc9202023862090bfca4ea827d533329e925c758f0cde964cb54b7/nvidia_cuda_nvrtc_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:210cf05005a447e29214e9ce50851e83fc5f4358df8b453155d5e1918094dcb4", size = 89568129, upload-time = "2025-06-05T20:02:41.973Z" }, + { url = "https://files.pythonhosted.org/packages/64/eb/c2295044b8f3b3b08860e2f6a912b702fc92568a167259df5dddb78f325e/nvidia_cuda_nvrtc_cu12-12.9.86-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:096d4de6bda726415dfaf3198d4f5c522b8e70139c97feef5cd2ca6d4cd9cead", size = 44528905, upload-time = "2025-06-05T20:02:29.754Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.9.79" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/e0/0279bd94539fda525e0c8538db29b72a5a8495b0c12173113471d28bce78/nvidia_cuda_runtime_cu12-12.9.79-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83469a846206f2a733db0c42e223589ab62fd2fabac4432d2f8802de4bded0a4", size = 3515012, upload-time = "2025-06-05T20:00:35.519Z" }, + { url = "https://files.pythonhosted.org/packages/bc/46/a92db19b8309581092a3add7e6fceb4c301a3fd233969856a8cbf042cd3c/nvidia_cuda_runtime_cu12-12.9.79-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25bba2dfb01d48a9b59ca474a1ac43c6ebf7011f1b0b8cc44f54eb6ac48a96c3", size = 3493179, upload-time = "2025-06-05T20:00:53.735Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/77/1c382fdc5de163b2ff14d6174d12dc318c0a42302f5e3a4fbc5114ab0501/nvidia_cudnn_cu12-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:d9da9c15344323afae571751393552652c52486eab0b886530997bef664e29de", size = 664659972, upload-time = "2026-03-09T19:27:37.986Z" }, + { url = "https://files.pythonhosted.org/packages/3b/52/94aecda69df65ba1079a8b7dbe84632af5614dc0ed2c733185f6431874e3/nvidia_cudnn_cu12-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:7d7479e1321c7a039b33827f0247791ee1be091759032c1f66a287c4a643396a", size = 657910570, upload-time = "2026-03-09T19:28:58.944Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.4.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/2b/76445b0af890da61b501fde30650a1a4bd910607261b209cccb5235d3daa/nvidia_cufft_cu12-11.4.1.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1a28c9b12260a1aa7a8fd12f5ebd82d027963d635ba82ff39a1acfa7c4c0fbcf", size = 200822453, upload-time = "2025-06-05T20:05:27.889Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/61e6996dd20481ee834f57a8e9dca28b1869366a135e0d42e2aa8493bdd4/nvidia_cufft_cu12-11.4.1.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c67884f2a7d276b4b80eb56a79322a95df592ae5e765cf1243693365ccab4e28", size = 200877592, upload-time = "2025-06-05T20:05:45.862Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.14.1.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/28/b960e06d705a440c030edd84e16888ee14c743390bdb2a6368e92ffe8ef8/nvidia_cufile_cu12-1.14.1.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9552e2231792e94b1ff17bc99e958cc0e6bbbaa4a9d91fa2dbeed97716628fe6", size = 1210714, upload-time = "2025-06-05T20:06:11.898Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d2/110af3a1f77999d5eebf6ffae5d2305ab839e53c76eec3696640cc25b35d/nvidia_cufile_cu12-1.14.1.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:8dea77590761e02cb6dd955a57cb6414c58aa3cb1b7adbf9919869a11509cf65", size = 1135994, upload-time = "2025-06-05T20:06:03.952Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.10.19" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1c/2a45afc614d99558d4a773fa740d8bb5471c8398eeed925fc0fcba020173/nvidia_curand_cu12-10.3.10.19-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:de663377feb1697e1d30ed587b07d5721fdd6d2015c738d7528a6002a6134d37", size = 68292066, upload-time = "2025-05-01T19:39:13.595Z" }, + { url = "https://files.pythonhosted.org/packages/31/44/193a0e171750ca9f8320626e8a1f2381e4077a65e69e2fb9708bd479e34a/nvidia_curand_cu12-10.3.10.19-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:49b274db4780d421bd2ccd362e1415c13887c53c214f0d4b761752b8f9f6aa1e", size = 68295626, upload-time = "2025-05-01T19:39:38.885Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.5.82" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/99/686ff9bf3a82a531c62b1a5c614476e8dfa24a9d89067aeedf3592ee4538/nvidia_cusolver_cu12-11.7.5.82-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:62efa83e4ace59a4c734d052bb72158e888aa7b770e1a5f601682f16fe5b4fd2", size = 337869834, upload-time = "2025-06-05T20:06:53.125Z" }, + { url = "https://files.pythonhosted.org/packages/33/40/79b0c64d44d6c166c0964ec1d803d067f4a145cca23e23925fd351d0e642/nvidia_cusolver_cu12-11.7.5.82-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:15da72d1340d29b5b3cf3fd100e3cd53421dde36002eda6ed93811af63c40d88", size = 338117415, upload-time = "2025-06-05T20:07:16.809Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.10.65" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/6f/8710fbd17cdd1d0fc3fea7d36d5b65ce1933611c31e1861da330206b253a/nvidia_cusparse_cu12-12.5.10.65-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:221c73e7482dd93eda44e65ce567c031c07e2f93f6fa0ecd3ba876a195023e83", size = 366359408, upload-time = "2025-06-05T20:07:42.501Z" }, + { url = "https://files.pythonhosted.org/packages/12/46/b0fd4b04f86577921feb97d8e2cf028afe04f614d17fb5013de9282c9216/nvidia_cusparse_cu12-12.5.10.65-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:73060ce019ac064a057267c585bf1fd5a353734151f87472ff02b2c5c9984e78", size = 366465088, upload-time = "2025-06-05T20:08:20.413Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/f8/a809966c96e824b92df09ee3b7032442f5e975d873d7dadfef818d527f48/nvidia_cusparselt_cu12-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5c72f727722f74762380e5f8755557c788b26d8fdcc49df1641c1b08e16d256c", size = 235985605, upload-time = "2025-09-05T18:46:39.601Z" }, + { url = "https://files.pythonhosted.org/packages/bb/14/e46964290aa587cb9fb7df20efdc60528ddd00d291ccffec47617fb06ca3/nvidia_cusparselt_cu12-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:cd1b1dc9e1ad31ea3353c1f985e2bd6f9e7ae0e797d7e6ce879d7b2ace5e80e8", size = 239274390, upload-time = "2025-09-05T18:47:44.816Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/cc/f48875411d1f176bce58e6343fd5d4131fc1db5420719ff25944bdc006c6/nvidia_nccl_cu12-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:0cf032ee22b560447daf0456108a75e32bd74a4de6c6b64725637a359fa48cd8", size = 293563644, upload-time = "2026-03-03T05:34:46.166Z" }, + { url = "https://files.pythonhosted.org/packages/31/1e/9e366f36efc550f07d6737f199e3f6bffafdf28795d007f10a77dd274f5c/nvidia_nccl_cu12-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:ecd0a012051abc20c1aa87328841efa8cade3ced65803046e38c2f03c0891fea", size = 293633942, upload-time = "2026-03-03T05:37:05.625Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.9.86" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/0c/c75bbfb967457a0b7670b8ad267bfc4fffdf341c074e0a80db06c24ccfd4/nvidia_nvjitlink_cu12-12.9.86-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:e3f1171dbdc83c5932a45f0f4c99180a70de9bd2718c1ab77d14104f6d7147f9", size = 39748338, upload-time = "2025-06-05T20:10:25.613Z" }, + { url = "https://files.pythonhosted.org/packages/97/bc/2dcba8e70cf3115b400fef54f213bcd6715a3195eba000f8330f11e40c45/nvidia_nvjitlink_cu12-12.9.86-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:994a05ef08ef4b0b299829cde613a424382aff7efb08a7172c1fa616cc3af2ca", size = 39514880, upload-time = "2025-06-05T20:10:04.89Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu12" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/6a/03aa43cc9bd3ad91553a88b5f6fb25ed6a3752ae86ce2180221962bc2aa5/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b48363fc6964dede448029434c6abed6c5e37f823cb43c3bcde7ecfc0457e15", size = 138936938, upload-time = "2025-09-06T00:32:05.589Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.9.79" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/ed/bb230dce7741f2778ba2ae3e8778fdb8bc58eee9fd95f07bf7b2d18e8081/nvidia_nvtx_cu12-12.9.79-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fec150986817f2b4e7eed72ed059f2dcb9ba3856b9a96134e448eac946a6952f", size = 85504, upload-time = "2025-06-05T20:03:10.21Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e4/82155e4aaedb41621087ba219c95e99c5e417f37a7649b4fb6ec32dcb14d/nvidia_nvtx_cu12-12.9.79-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d1f258e752294acdb4f61c3d31fee87bd0f60e459f1e2f624376369b524cd15d", size = 86120, upload-time = "2025-06-05T20:02:51.838Z" }, +] + +[[package]] +name = "omegaconf" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, +] + +[[package]] +name = "onnx" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/19/8ea73a64b368b75fe339771a20a02bc61ea1f551484c9e3d9d0bfbd0450f/onnx-1.22.0.tar.gz", hash = "sha256:ef40c0aaf0b643857ea9306fc7eddce17eaf9fb0407e4801f1fc5758443a38e0", size = 12024721, upload-time = "2026-06-15T12:50:05.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/55/30825c02c92a0380ce84c3feeeec95d329fa77548ba58cb10ad4bbfd83c6/onnx-1.22.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:2d8f229a553fa440fe623ed7b36fca5e7762da3af871c3f8f8ce451df73e2914", size = 20167891, upload-time = "2026-06-15T12:49:14.212Z" }, + { url = "https://files.pythonhosted.org/packages/4b/24/cd4ab52ecaf41c3fbed674772ccbfe39041cb257b8471a47a37e48bff3f8/onnx-1.22.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a89a7cb9ba13d78f009bdec448ec82a98972589734f157022a2bff7a5973a6", size = 18892720, upload-time = "2026-06-15T12:49:16.904Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a0/c9d9d56ceadb1c0a90a7cbec5a0510520ab6538938944fa84548e4b5b054/onnx-1.22.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d0a2bdb15eb2b3cb65c438f3423d9620d14fdce32f92380e6bb1b2e09568ef5", size = 19110720, upload-time = "2026-06-15T12:49:19.812Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6a/481561f1093834376ed493e4ca42a73e5be0d50031f2969c86593bdc7c96/onnx-1.22.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:596fbf0490947533c1c1045ba860851dc9fb77471023dac9a71ba5b42ceab103", size = 20167081, upload-time = "2026-06-15T12:49:32.078Z" }, + { url = "https://files.pythonhosted.org/packages/84/55/b34fc2aa30aa54b4a775402d24c4082242c720283a274fe976ac8eb94480/onnx-1.22.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae5a563f281cd9d2845622cecf6c092a57e4ee1b138f66fdbbdd4200567a5e16", size = 18889249, upload-time = "2026-06-15T12:49:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/09/a6/bd32357e6cc1ecb473afd78193d7231724f284435d2db25696ecfaaa1503/onnx-1.22.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:955e02e1f6d385b53d52f9cd7b9cdf5caf417c300bcfe3c64c6d542be763845b", size = 19106514, upload-time = "2026-06-15T12:49:37.424Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9d/3af461ac6c714b8b369cb71499659932f4f12cfb066250b62f7567c3d530/onnx-1.22.0-cp312-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:82e9f27fc1223cb06d68a56bed6f9d3caf3d0dad1b61bce45006d529b15bd94c", size = 16966387, upload-time = "2026-06-15T12:49:40.918Z" }, +] + +[[package]] +name = "optuna" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "colorlog", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sqlalchemy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/aa/05f5e3f662cc96a4c478fc3446b8ed6359825a2b504ecb614a9ac84e4a4d/optuna-4.9.0.tar.gz", hash = "sha256:b322e5cbdf1655fb84c37646c4a7a1f391de1b47806bbe222e015825d0a82b87", size = 485834, upload-time = "2026-06-01T06:23:30.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/f3/e5fcd5d9b15771ed6dc10e3a7eeddc672e418f4f4c4653d216cc1d857e2d/optuna-4.9.0-py3-none-any.whl", hash = "sha256:f52f3be6148654850c92a5860d398fd88ec6b2c84ab68d9c3d07dcff02e7afee", size = 425553, upload-time = "2026-06-01T06:23:28.804Z" }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, +] + +[[package]] +name = "pandas" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "python-dateutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/16/b5c76b838fd9bf6ce84d3a53346b8874ec05c5f0040d75ef2c320100cd2a/pandas-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:455f6f8139d4282188f526868dbc3c828470e88a3d9d59a891bd46a455f21b98", size = 10338495, upload-time = "2026-05-11T18:52:11.558Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b0/a4ffc4ae74d2d822200dcc46898987d8eb6032d1e2b219cae39da6f5cbcc/pandas-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4e15135e2ee5df1063313e2425ceef8ac0f4ae775893815b0923651b806a5639", size = 9938250, upload-time = "2026-05-11T18:52:17.005Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b2/3323601a52caee42c019e370090ca4544b241437240ca04f786cce82b0cf/pandas-3.0.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05f1f1752b8533ea03f7f39a9c15b1a058d067bb48f4748948e7a8691e0510f2", size = 10770558, upload-time = "2026-05-11T18:52:19.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/bbecd2f867b97abebe0f9b53d750f862251b40337e061b36676ded3d920f/pandas-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a1e45c80cceb3b4a21bc5939d52e8cbd8d9b7305309219d59e9754d9ce09e27", size = 11274611, upload-time = "2026-05-11T18:52:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/7f/4f/eafabf2d5fae5adf143b4d18d3706c5efdc368a7c4eb1ee8a3eddabbd0f6/pandas-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:14da8316da4d0c5a77618425996bfb1248ca87fc2c1486e6fde4652bd18b5824", size = 11784670, upload-time = "2026-05-11T18:52:25.4Z" }, + { url = "https://files.pythonhosted.org/packages/49/44/1eb20389301b57b19cc099a1c2f662501f72f08a65f912d05822613c1532/pandas-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a55066a0505dae0ba2b50a46637db34b46f9094c65c5d4800794ef6335010938", size = 12353708, upload-time = "2026-05-11T18:52:28.139Z" }, + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "peft" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accelerate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "psutil", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "safetensors", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "transformers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/0c/f2938db546ac7fc961ab5917cd50fcf5d0d70b406de93e3faccaa504e152/peft-0.18.0.tar.gz", hash = "sha256:c81c80b2056ab40c23d58ef25f74daab417ac653970718589a11a8af28218588", size = 634141, upload-time = "2025-11-13T11:13:06.603Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/55/481bf25613d40ef53534f664deba7b138fe566356b6ca10304e2b3b2529c/peft-0.18.0-py3-none-any.whl", hash = "sha256:624f69ca6393b765ccc6734adda7ca57d80b238f0900a42c357d8b67a03d62ff", size = 556427, upload-time = "2025-11-13T11:13:03.664Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/cd/4f25b2f95b23f5d2c9c1fe43e49841bff5800562149b2666afc09309aa8f/platformdirs-4.10.1.tar.gz", hash = "sha256:ceab4084426fe6319ce18e86deada8ab1b7487c7aee7040c55e277c9ae793695", size = 31678, upload-time = "2026-07-18T03:53:43.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/73/6fd0bb9ce84138c3857f12e9de63bc901852975a092d545f18087a204aa2/platformdirs-4.10.1-py3-none-any.whl", hash = "sha256:0e4eff26be2d75293977f7cddc153fd9b8eaa7fb0c7b64ffe4076cb443117443", size = 22906, upload-time = "2026-07-18T03:53:42.576Z" }, +] + +[[package]] +name = "pooch" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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 = "portalocker" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pyannote-core" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sortedcontainers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/be/4a35ea31c685aef801f7f35c193e7766ca1bb948ae497a625cbfaa8c31ba/pyannote_core-6.0.1.tar.gz", hash = "sha256:4b4ada3276f6df4e073fa79166636e3597d0dcb5a0fe26014a3477867cc033fb", size = 327540, upload-time = "2025-09-16T09:24:39.081Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/57/ecf62344b9b81debd0ca95ed987135e93d1b039507f8174f52d1d19d8c6b/pyannote_core-6.0.1-py3-none-any.whl", hash = "sha256:924550d6ecf6b05ad13bf3f66f59c29fc740cf1c62a6fca860ac2e66908203e5", size = 57505, upload-time = "2025-09-16T09:24:37.798Z" }, +] + +[[package]] +name = "pyannote-database" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pandas", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyannote-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/45/6210274c187cc457e854be8b56c6819fa14376f27e7e2b6021b2aa02449a/pyannote_database-6.1.1.tar.gz", hash = "sha256:bbe76da738257a9e64061123d9694ad7e949c4f171d91a9269606d873528cd10", size = 112225, upload-time = "2025-12-07T06:33:10.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/bf/6a6f5abaa4d9f803f34c9883ef5e316624eac6be0eaa87720216be9bba12/pyannote_database-6.1.1-py3-none-any.whl", hash = "sha256:36460c70ce9f50ff25c9ea365bc83ad625bb6b2494deccf6bd3fc750686ae684", size = 53735, upload-time = "2025-12-07T06:33:11.578Z" }, +] + +[[package]] +name = "pyannote-metrics" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pandas", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyannote-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyannote-database", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scikit-learn", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'darwin') or (python_full_version < '3.12' and sys_platform == 'linux')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/ba/7dbc2f790d5e321e46dc1e250ff00b69cdefc0c5695b811e96a4b932cddd/pyannote_metrics-4.1.tar.gz", hash = "sha256:afe24c54ee0799e8cfbe8ee85fa517793c3450bb7eae8fedd1a77ccec0343f7e", size = 883419, upload-time = "2026-05-06T16:33:10.163Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/d3/9cd1df66aa9be47a36df46f007e198bbdec3ddebc1bd8833c7a9e8a554a9/pyannote_metrics-4.1-py3-none-any.whl", hash = "sha256:34a54b7671f61709c1865d0484843e5b46ea3c4e4e5260ab065e5b3156c733d3", size = 52116, upload-time = "2026-05-06T16:33:08.586Z" }, +] + +[[package]] +name = "pyarrow" +version = "25.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/f3/95428098d1fa7d04432fb750eed06b41304c2f6a5d3319985e64db2d9d41/pyarrow-25.0.0.tar.gz", hash = "sha256:d2d697008b5ec06d75952ef260c2e9a8a0f6ccfce24266c04c9c8ade927cb3b4", size = 1199181, upload-time = "2026-07-10T08:29:50.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/98/ae2b5acf9876dbeffa6f320776242c52caab062df55c8ac5501ed2679e74/pyarrow-25.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:2e3b6544e26e393fe2cd530f523e36c1c8d3c345bbbb60cca3fd866be8322517", size = 35939080, upload-time = "2026-07-10T08:26:04.53Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/3de2a968edbd496c86cb8b932cdbee2d4b08c4a28e9884a15e5c705a646b/pyarrow-25.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:b724d127783b4c19f088fcdfc844cbc318809246a30307bcabd5ed02045e890e", size = 37633420, upload-time = "2026-07-10T08:26:10.354Z" }, + { url = "https://files.pythonhosted.org/packages/19/86/8399243a4ce080426ec37db18d5e29148b7ec960a8a8c7f9059a7bf6ef0a/pyarrow-25.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:244f98a595f70fa4fd35faa7508c4ae67e14a173397a4b3b49d2b3c360fb0062", size = 46861050, upload-time = "2026-07-10T08:26:16.397Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/72d704b02bc5fc6d06954d76a0208c1e79cad3ab370f6d6a91ffe5078870/pyarrow-25.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:0222f0071d13313962a88d21bf28b80d355ac39d81bfa6ff3fe00eeaf748e4be", size = 50056458, upload-time = "2026-07-10T08:26:23.271Z" }, + { url = "https://files.pythonhosted.org/packages/06/5d/3c31a60b6403d63cad2e0f829096f5fc5763a129ead4207a5d4690b96448/pyarrow-25.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b58726f118c079f9d4ed7e904975d4f15fd69d0741ba511a4e2dcaa4ef16354f", size = 49957793, upload-time = "2026-07-10T08:26:30.232Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/8f8a019061f9863a831915329264372a87ed25eaf9109ce56eb0e84012c5/pyarrow-25.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:38a2c887cb3883e241b70201688db34133b6dfadd04f03c8f9213df53770c18e", size = 53100544, upload-time = "2026-07-10T08:26:36.414Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/fdd3a4377807b7dcabe2d4b5aa99dbbc98e2e5df3f1ca4e7f0aec492d987/pyarrow-25.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:149730a3d1f0fb59d663a0b8aa210adfd9c17c27cd94a0d143e60daea8320d4e", size = 35850884, upload-time = "2026-07-10T08:26:47.357Z" }, + { url = "https://files.pythonhosted.org/packages/bf/71/9f053177a7709b8c90abb00a2375b916286f9f0d6cfb21a5cadd4ef811e8/pyarrow-25.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:0721332c30fdd453fdd1fc203b2ac1f4c9db5aea28fa38d41f2574c4b068b9ec", size = 37616197, upload-time = "2026-07-10T08:26:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/95/1a/22bfb6597dcdc861fa83c39c06e1457cb56f698940eff42fbb25de30e8e5/pyarrow-25.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fa1482b3da10cac2d4db6e26b81da543e237616af2ef6d466018b31ca586496f", size = 46841966, upload-time = "2026-07-10T08:27:07.685Z" }, + { url = "https://files.pythonhosted.org/packages/55/0e/cd705c042bc4fe7022478db577fcab4abdcfabb9bc37ab7a75556b3fcb2b/pyarrow-25.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:5d1dbf24e151042f2fa3c129563f65d66674128868496fb008c4272b16bdf778", size = 50088993, upload-time = "2026-07-10T08:27:14.268Z" }, + { url = "https://files.pythonhosted.org/packages/98/ee/d822e1ee31fe31ec5d057210e0605c950b975dcd8d9a332976cc859a9df8/pyarrow-25.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:20887a762dd61dcc530f93a140840ab1f6aa7836b33270e42d627ab3cf11e537", size = 49941005, upload-time = "2026-07-10T08:27:21.274Z" }, + { url = "https://files.pythonhosted.org/packages/33/1b/207a90cc64619a095eb75a263ae069735f2810056d43c667befd573ec083/pyarrow-25.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58d1ab556b0cea1c93fdb799b24ad58adb2f2a2788dbce782a94f64ae1a5cc9b", size = 53112355, upload-time = "2026-07-10T08:27:27.911Z" }, +] + +[[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.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydantic-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-inspection", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, +] + +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyloudnorm" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'darwin') or (python_full_version < '3.12' and sys_platform == 'linux')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/23/00/f915eaa75326f4209941179c2b93ac477f2040e4aeff5bb21d16eb8058f9/pyloudnorm-0.2.0.tar.gz", hash = "sha256:8bf597658ea4e1975c275adf490f6deb5369ea409f2901f939915efa4b681b16", size = 14037, upload-time = "2026-01-04T11:43:35.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/b6/65a49a05614b2548edbba3aab118f2ebe7441dfd778accdcdce9f6567f20/pyloudnorm-0.2.0-py3-none-any.whl", hash = "sha256:9bb69afb904f59d007a7f9ba3d75d16fb8aeef35c44d6df822a9f192d69cf13f", size = 10879, upload-time = "2026-01-04T11:43:34.534Z" }, +] + +[[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 = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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 = "pytorch-lightning" +version = "2.6.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lightning-utilities", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, + { name = "torchmetrics", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/2c/8e73a3929b4c4bd600cafd38a97aaf7242a8cf518fb9f33d27c274ec898f/pytorch_lightning-2.6.5.tar.gz", hash = "sha256:1c32cefa76a1a9c4c5250338272d961d1e48b180e68396849efe128538ddb28e", size = 661673, upload-time = "2026-05-27T14:33:41.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/4d/5740c27110b83634d8491c3b5facf0111b3e554c3164f4fb953be9bddaf6/pytorch_lightning-2.6.5-py3-none-any.whl", hash = "sha256:62d9c8549b2278fedc3364f0a5607a56c6063d18635008f8cf3fae8d802b0d76", size = 852407, upload-time = "2026-05-27T14:33:39.856Z" }, +] + +[[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/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/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" }, +] + +[[package]] +name = "rapidfuzz" +version = "3.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/21/ef6157213316e85790041254259907eb722e00b03480256c0545d98acd33/rapidfuzz-3.14.5.tar.gz", hash = "sha256:ba10ac57884ce82112f7ed910b67e7fb6072d8ef2c06e30dc63c0f604a112e0e", size = 57901753, upload-time = "2026-04-07T11:16:31.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/f9/3c41a7be8855803f4f6c713b472226a98d31d41869d98f64f4ca790510d6/rapidfuzz-3.14.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e251126d48615e1f02b4a178f2cd0cd4f0332b8a019c01a2e10480f7552554b4", size = 1952372, upload-time = "2026-04-07T11:13:58.32Z" }, + { url = "https://files.pythonhosted.org/packages/9e/89/c2557e37531d03465193bff0ab9de70b468420a807d71a26a65100635459/rapidfuzz-3.14.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ab449c9abd0d4e1f8145dce0798a4c822a1a1933d613c764a641bea88b8bdab", size = 1159782, upload-time = "2026-04-07T11:14:00.127Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b2/ffeeb7eca1a897d51b998f4c0ef0281696c3b06abcca4f88f9def708ffe1/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb2829fedd672dd7107267189dabe2bbe07972801d636014417c6861eb89e358", size = 1383677, upload-time = "2026-04-07T11:14:01.696Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d0/4539e42a2d596e068f7738f279638a4a74edd1fbb6f8594e2458058979c6/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d50e5861872935fece391351cbb5ba21d1bced277cf5e1143d207a0a35f1925", size = 3168906, upload-time = "2026-04-07T11:14:03.29Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1c/3ec897eb9d8b05308aa8ef6ae4ed64b088ad521a3f9d8ff469e7e97bc2b0/rapidfuzz-3.14.5-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:7092a216728f80c960bd6b3807275d1ee318b168986bd5dc523349581d4890b8", size = 1478176, upload-time = "2026-04-07T11:14:04.94Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ba/970c03a12ce20a5399e22afe9f8932fd4cd1265b8a8461d0e63b00eb4eae/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9669753caef7fdc6529f6adcc5883ed98d65976445d9322e7dbdb6b697feee13", size = 2402441, upload-time = "2026-04-07T11:14:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/81/93/61d351cae60c1d0e21ba5ff1a1015ad045539ed215da9d6e302204ed887a/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:823b1b9d9230809d8edcc18872770764bfe8ef4357995e16744047c8ccf0e489", size = 2511628, upload-time = "2026-04-07T11:14:09.234Z" }, + { url = "https://files.pythonhosted.org/packages/87/52/374d2d4f60fd98155142a869323aa221e30868cfa1f15171a0f64070c247/rapidfuzz-3.14.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f0b2af76b7e7060c09e1a0dfa9410eb19369cbe6164509bff2ef94094b54d2b6", size = 4275480, upload-time = "2026-04-07T11:14:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e3/574435c6aafb80254c191ef40d7aca2cb2bb97a095ec9395e9fa59ac307a/rapidfuzz-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d3378f471ef440473a396ce2f8e97ee12f89a78b495540e0a5617bbfe895638", size = 1944601, upload-time = "2026-04-07T11:14:18.771Z" }, + { url = "https://files.pythonhosted.org/packages/d0/1f/fbad3102a255ecc112ce9a7e779bacab7fd14398217be8868dc9082ba363/rapidfuzz-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e910eebca9fd0eba245c0555e764597e8a0cccb673a92da2dc2397050725f48", size = 1164293, upload-time = "2026-04-07T11:14:20.534Z" }, + { url = "https://files.pythonhosted.org/packages/88/37/a3eb7ff6121ed3a5f199a8c38cc86c8e481816f879cb0e0b738b078c9a7e/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01550fe5f60fd176aa66b7611289d46dc4aa4b1b904874c7b6d1d54e581c5ec1", size = 1371999, upload-time = "2026-04-07T11:14:22.63Z" }, + { url = "https://files.pythonhosted.org/packages/79/72/97a9728c711c7c1b06e107d3f0623880fb4ef90e147ed13c551a1730e7cc/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48bee0b91bebfaec41e1081e351000659ab7570cc4598d617aa04d5bf827f9e6", size = 3145715, upload-time = "2026-04-07T11:14:24.508Z" }, + { url = "https://files.pythonhosted.org/packages/ed/54/d5caabbea233ac90c286c87c260e49d7641467e87438a18d858e41c82e91/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:7e580cb04ad849ae9b786fa21383c6b994b6e6c1444ad1cb9f22392759d72741", size = 1456304, upload-time = "2026-04-07T11:14:26.515Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a7/2d1a81250ac8c01a0100c026018e76f0e7a097ff63e4c553e02a6938c6fb/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:09d6c9ba091854f07817055d795d604179c12a8f308ba4c7d56f3719dfea1646", size = 2389089, upload-time = "2026-04-07T11:14:28.635Z" }, + { url = "https://files.pythonhosted.org/packages/65/0d/c47c3872203ae88e6506997c0b576ad731f5261daa25d559be09c9756658/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1e989f86113be66574113b9c7bdf4793f3f863d248e47d911b355e05ca6b6b10", size = 2493404, upload-time = "2026-04-07T11:14:30.577Z" }, + { url = "https://files.pythonhosted.org/packages/8f/2f/71e0a5a3130792146c8a200a2dd1e52aa16f7c1074012e17f2601eea9a90/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ebd1a18e2e47bc0b292a07e6ed9c3642f8aaa672d12253885f599b50807a4f9", size = 4251709, upload-time = "2026-04-07T11:14:32.451Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ee/e71853bf82846c5c2174b924b71d8e8099fb05ff87c958a720380b434ba3/rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:578e6051f6d5e6200c259b47a103cf06bb875ab5814d17333fc0b5c290b22f4c", size = 1888603, upload-time = "2026-04-07T11:16:18.223Z" }, + { url = "https://files.pythonhosted.org/packages/36/82/40f67b730f32be2ebad9f62add1571c754f52249254b2e88af094b907eee/rapidfuzz-3.14.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbf1b8bb2695415b347f3727da1addca2acb82c9b97ac86bebf8b1bead1eb12d", size = 1120599, upload-time = "2026-04-07T11:16:20.682Z" }, + { url = "https://files.pythonhosted.org/packages/ef/9f/a3635cc4ec8fc6e14b46e7db1f7f8763d8c4bef33dcc124eea2e6cb2c8f3/rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4a8f5cc84c7ad6bffa0e9947b33eb343ad66e6b53e94fe54378a5508c5ed53", size = 1348524, upload-time = "2026-04-07T11:16:23.451Z" }, + { url = "https://files.pythonhosted.org/packages/cc/1b/2b229520f0b48464cfcd7aa758f74551d12c9bc4ab544022a60210aab064/rapidfuzz-3.14.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97c6d85283629646fa87acc22c66b30ea9d4de7f6fdf887daa2e30fa041829b5", size = 3099302, upload-time = "2026-04-07T11:16:25.858Z" }, +] + +[[package]] +name = "regex" +version = "2026.7.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/98/04b13f1ddfb63158025291c02e03eb42fbb7acb51d091d541050eb4e35e8/regex-2026.7.19.tar.gz", hash = "sha256:7e77b324909c1617cbb4c668677e2c6ae13f44d7c1de0d4f15f2e3c10f3315b5", size = 416440, upload-time = "2026-07-19T00:19:48.923Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/e5/cef4de2bac939280b68d32adc659478845238a8274f2f79c465063f590ad/regex-2026.7.19-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac777001cdfc28b72477d93c8564bb7583081ea8fb45cdca3d568e0a4f87183c", size = 494012, upload-time = "2026-07-19T00:16:39.927Z" }, + { url = "https://files.pythonhosted.org/packages/ff/87/e86f51eb117457bb7803132ffe5cb6e2841e2b5bea4cc85d397f3c6e257d/regex-2026.7.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:59787bd5f8c70aa339084e961d2996b53fbdeab4d5393bba5c1fe1fc32e02bae", size = 295281, upload-time = "2026-07-19T00:16:41.433Z" }, + { url = "https://files.pythonhosted.org/packages/41/2e/2360c41d8080a3d9ec7e5c90fad6eab3b50192869d10e9a5609e48c8177b/regex-2026.7.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:90c633e7e8d6bf4e992b8b36ce69e018f834b641dd6de8cea6d78c06ffa119c5", size = 290615, upload-time = "2026-07-19T00:16:43.058Z" }, + { url = "https://files.pythonhosted.org/packages/cf/69/b65ba4344efbc771b28fe5dde84cbbb6c8f9551165952fe78def5b9dde6a/regex-2026.7.19-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87ccab0db8d5f4fbb0272642113c1adb2ffc698c16d3a0944580222331fa7a20", size = 791804, upload-time = "2026-07-19T00:16:44.662Z" }, + { url = "https://files.pythonhosted.org/packages/81/b6/a40dfa0dc6224b36f620c00296eacc830489cbf8c2837b6750dfe6170375/regex-2026.7.19-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e50d748a32da622f256e8d505867f5d3c43a837c6a9f0efb149655fadd1042a", size = 861723, upload-time = "2026-07-19T00:16:46.412Z" }, + { url = "https://files.pythonhosted.org/packages/e3/02/735991dee71abd83196a7962f7ed8bf5aa05720ff06e2d3ff896a85e2bbb/regex-2026.7.19-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf1516fe58fc104f39b2d1dbe2d5e27d0cd45c4be2e42ba6ee0cc763701ec3c7", size = 905932, upload-time = "2026-07-19T00:16:47.956Z" }, + { url = "https://files.pythonhosted.org/packages/45/6c/e7098d8b846ccdbf431d8c081b61e496526a27a28094ed09e0dce21b3f54/regex-2026.7.19-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09f3e5287f94f17b709dc9a9e70865855feee835c861613be144218ce4ca82cc", size = 801407, upload-time = "2026-07-19T00:16:49.43Z" }, + { url = "https://files.pythonhosted.org/packages/8a/18/34b69274e2649bcc7d9b089c2b2983fb2632d8ecf667e359593be9072e79/regex-2026.7.19-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6383cd2ed53a646c659ba1fe65727db76437fdaa069e697a0b44a51d5843d864", size = 774448, upload-time = "2026-07-19T00:16:51.352Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e6/0a72247d025585fd3800b98e040b84d562a88af6303347100484849f4f01/regex-2026.7.19-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09d3007fc76249a83cdd33de160d50e6cb77f54e09d8fa9e7148e10607ce24af", size = 783297, upload-time = "2026-07-19T00:16:53.071Z" }, + { url = "https://files.pythonhosted.org/packages/b1/aa/c4f65ae7dd02a36b323a70c4cff326e1f3442361aaebc9311100a130d54f/regex-2026.7.19-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6f8c6e7a1cfa3dc9d0ee2de0e65e834537fa29992cc3976ffec914afc35c5dd5", size = 854736, upload-time = "2026-07-19T00:16:54.607Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/668082bcc817b9e694189b84997aeba7385b7779faa6711788679c482e35/regex-2026.7.19-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b2ea4a3e8357be8849e833beeae757ac3c7a6b3fc055c03c808a53c91ad30d82", size = 763298, upload-time = "2026-07-19T00:16:56.289Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fb/2d07ad555e7af88aa5f867fdafa47a8d945ee237c20af3ebceb46a820835/regex-2026.7.19-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:80115dd39481fd3a4b4080220799dbcacb921a844de4b827264ececacbe17c78", size = 844430, upload-time = "2026-07-19T00:16:57.933Z" }, + { url = "https://files.pythonhosted.org/packages/51/15/c82a471fe3dce56f03745635b43aa456c40dc0db089e07ef148b331507d1/regex-2026.7.19-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6ce43a0269d68cee79a7d1ade7def53c20f8f2a047b92d7b5d5bcc73ae88327", size = 789683, upload-time = "2026-07-19T00:16:59.583Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b9/d11d7e501ac8fd7d617684423ebb9561e0b998481c1e4cbc0cb212c5d74a/regex-2026.7.19-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2cc3460cedf7579948486eab03bc9ad7089df4d7281c0f47f4afe03e8d13f02d", size = 496778, upload-time = "2026-07-19T00:17:05.677Z" }, + { url = "https://files.pythonhosted.org/packages/3f/a9/a5ab6f312f24318019170dc485d5421fe4f89e43a98640da50d95a8a7041/regex-2026.7.19-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0e9554c8785eac5cffe6300f69a91f58ba72bc88a5f8d661235ad7c6aa5b8ccd", size = 297122, upload-time = "2026-07-19T00:17:07.59Z" }, + { url = "https://files.pythonhosted.org/packages/b3/63/4cab4d7f2d384a144d420b763d97674cb70619c878ea6fcd7640d0e62143/regex-2026.7.19-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7da47a0f248977f08e2cb659ff3c17ddc13a4d39b3a7baa0a81bf5b415430f6", size = 292009, upload-time = "2026-07-19T00:17:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/22/85/102a81b218298957d4ea7d2f084fae537a71add9d6ff93c8e67284c5f45e/regex-2026.7.19-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93db40c8de0815baab96a06e08a984bac71f989d13bab789e382158c5d426797", size = 796708, upload-time = "2026-07-19T00:17:11.542Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/dc136af5629938a037cd2b304c12240e132ec92f38be8ff9cc89af2a1f2d/regex-2026.7.19-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:66bd62c59a5427746e8c44becae1d9b99d22fb13f30f492083dfb9ad7c45cc18", size = 865651, upload-time = "2026-07-19T00:17:13.312Z" }, + { url = "https://files.pythonhosted.org/packages/e0/75/67402ae3cd9c8c988a4c805d15ee3eef015e7ca4cb112cf3e640fc1f4153/regex-2026.7.19-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1649eb39fcc9ea80c4d2f110fde2b8ab2aef3877b98f02ab9b14e961f418c511", size = 911756, upload-time = "2026-07-19T00:17:15.015Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8e/096d00c7c480ef2ff4265349b14e2261d4ab787ba1f74e2e80d1c58079c3/regex-2026.7.19-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dce8ec9695f531a1b8a6f314fd4b393adcccf2ea861db480cdf97a301d01a68", size = 801798, upload-time = "2026-07-19T00:17:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f0/41/e7ecac6edb5722417f85cc67eaf386322fbe8acf6918ec2fdc37c20dd9d0/regex-2026.7.19-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3080a7fd38ef049bd489e01c970c97dd84ff446a885b0f1f6b26d9b1ad13ce11", size = 776933, upload-time = "2026-07-19T00:17:19.347Z" }, + { url = "https://files.pythonhosted.org/packages/6f/69/03c9b3f058d66403e0ca2c938696e81d51cd4c6d47ec5265f02f96948d9a/regex-2026.7.19-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1d793a7988e04fcb1e2e135567443d82173225d657419ec09414a9b5a145b986", size = 784338, upload-time = "2026-07-19T00:17:21.057Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f7/b38ab3d43f284afbb618fcd15d0e77eb786ae461ce1f6bc7494619ddc0f2/regex-2026.7.19-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e8b0abe7d870f53ca5143895fef7d1041a0c831a140d3dc2c760dd7ba25d4a8b", size = 860452, upload-time = "2026-07-19T00:17:23.119Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/ff60ef0571121714f3cf9920bc183071e384a10b556d042e0fdb06cc07a5/regex-2026.7.19-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4e5413bd5f13d3a4e3539ca98f70f75e7fca92518dd7f117f030ebedd10b60cb", size = 765958, upload-time = "2026-07-19T00:17:24.81Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0f/bd34021162c0ab47f9a315bd56cd5642e920c8e5668a75ef6c6a6fca590d/regex-2026.7.19-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:73b133a9e6fb512858e7f065e96f1180aa46646bc74a83aea62f1d314f3dd035", size = 851765, upload-time = "2026-07-19T00:17:26.993Z" }, + { url = "https://files.pythonhosted.org/packages/2a/20/a2ca43edade0595cccfdc98636739f536d9e26898e7dbddc2b9e98898953/regex-2026.7.19-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbe6493fbd27321b1d1f2dd4f5c7e5bd4d8b1d7cab7f32fd67db3d0b2ed8248a", size = 789714, upload-time = "2026-07-19T00:17:28.699Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "charset-normalizer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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 = "resampy" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numba", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/f1/34be702a69a5d272e844c98cee82351f880985cfbca0cc86378011078497/resampy-0.4.3.tar.gz", hash = "sha256:a0d1c28398f0e55994b739650afef4e3974115edbe96cd4bb81968425e916e47", size = 3080604, upload-time = "2024-03-05T20:36:08.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b9/3b00ac340a1aab3389ebcc52c779914a44aadf7b0cb7a3bf053195735607/resampy-0.4.3-py3-none-any.whl", hash = "sha256:ad2ed64516b140a122d96704e32bc0f92b23f45419e8b8f478e5a05f83edcebd", size = 3076529, upload-time = "2024-03-05T20:36:02.439Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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 = "ruamel-yaml" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, +] + +[[package]] +name = "sacrebleu" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "lxml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "portalocker", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "regex", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tabulate", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/ed/d7acddcff74d690c56fe26a1f7828bdde548262828d0743414ea916c40c1/sacrebleu-2.6.0.tar.gz", hash = "sha256:91499b6cd46138d95154fff1e863c2f9be57e82f0c719d8dd718d0006cf6c566", size = 1893419, upload-time = "2026-01-12T17:17:20.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f2/6c90ccf3ad1d09a7d662a405b274f3c93b92df59c8d6a025d26aaf34d302/sacrebleu-2.6.0-py3-none-any.whl", hash = "sha256:3edc1531575cfe4ad04ce53491a9307e234af1c3f805a1f491cbec844229a8a8", size = 100785, upload-time = "2026-01-12T17:17:18.868Z" }, +] + +[[package]] +name = "sacremoses" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "joblib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "regex", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/51/fbdc4af4f6e85d26169e28be3763fe50ddfd0d4bf8b871422b0788dcc4d2/sacremoses-0.1.1.tar.gz", hash = "sha256:b6fd5d3a766b02154ed80b962ddca91e1fd25629c0978c7efba21ebccf663934", size = 883188, upload-time = "2023-10-30T15:56:20.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/f0/89ee2bc9da434bd78464f288fdb346bc2932f2ee80a90b2a4bbbac262c74/sacremoses-0.1.1-py3-none-any.whl", hash = "sha256:31e04c98b169bfd902144824d191825cd69220cdb4ae4bcf1ec58a7db5587b1a", size = 897476, upload-time = "2023-10-30T15:56:18.121Z" }, +] + +[[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" }, +] + +[[package]] +name = "scikit-learn" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "narwhals", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and sys_platform == 'darwin') or (python_full_version < '3.12' and sys_platform == 'linux')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, + { name = "threadpoolctl", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fa/6f/37092bdb25f712817231799fc5674d8e704066a8a70c1d2d40517e18b4ab/scikit_learn-1.9.0.tar.gz", hash = "sha256:8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557", size = 7750767, upload-time = "2026-06-02T11:54:32.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/be/e844fd9586e66540a15b71924d17a6cbc1bb749e81ddd0a796bcdba4c055/scikit_learn-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9db6f4d34e68c8899e4cab27fdf8eafe6ed21f2ba52ceb25ea250cd237f8e47b", size = 8789686, upload-time = "2026-06-02T11:53:05.439Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/ff880f62677a17d035817d543cb0fc8727d01eccbee81c5f7fc733a9d856/scikit_learn-1.9.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:f401448645a3e7bc115aa3c094097865155b34bff1cba8101857d9104e99074c", size = 8256782, upload-time = "2026-06-02T11:53:08.904Z" }, + { url = "https://files.pythonhosted.org/packages/25/64/eb40435e1a508ab1b4e284ce43ae80f6a162e5be5e38ed5a6fab467a9ea4/scikit_learn-1.9.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd3a8ef0c758555a3b23c03adaa858af32f7736785ded50ad5991f59c4ed03fa", size = 8992419, upload-time = "2026-06-02T11:53:11.551Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/4810a28e473185429e45a57eebcc91fc991b33d889cc0676063e671db03d/scikit_learn-1.9.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7e254636164090da847715a27f8e5478feb98c40a9e0ee90cbd277de9e5ceb8", size = 9281411, upload-time = "2026-06-02T11:53:15.063Z" }, + { url = "https://files.pythonhosted.org/packages/ac/20/75f915ff375d6249e6550ac740fdbbd66159a068fd3af1400ff62036b07a/scikit_learn-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2bd41b0d201bc81575531b96b713d3eb5e5f50fb0b82101ff0f92294fdc236ac", size = 8741122, upload-time = "2026-06-02T11:53:24.08Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d5/2b5148f2279196775e1db2aeb85d14b70ac80e7e32b3b28e7ebeafb0901d/scikit_learn-1.9.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5be45aa4a42a68a533913a6ed736cf309de2226411c79ef8d609a5456f1939b1", size = 8261512, upload-time = "2026-06-02T11:53:27.183Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ee/5adbc77656b71f9456a2f5a7a9fdb4bcf9207a6b962889f1c2f9323afa4e/scikit_learn-1.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e50ed4da51974e86e940690e9a3d82e729b62b5a49f7c9bac534d515d39d86f", size = 8837603, upload-time = "2026-06-02T11:53:30.328Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c2/63fdda36c56437eeb44aaf9493c8bcd62ce230ab1598924fc626ffbfa943/scikit_learn-1.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:056c92bb67ad4c28463c2f2653d9701449201e7e7a9e94e321be0f71c4fef2b8", size = 9132097, upload-time = "2026-06-02T11:53:33.456Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform == 'linux'", +] +dependencies = [ + { name = "numpy", marker = "(python_full_version < '3.12' and sys_platform == 'darwin') or (python_full_version < '3.12' and sys_platform == 'linux')" }, +] +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/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { 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" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and sys_platform == 'linux'", +] +dependencies = [ + { name = "numpy", marker = "(python_full_version >= '3.12' and sys_platform == 'darwin') or (python_full_version >= '3.12' and sys_platform == 'linux')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/33/ea3cb3839607eb175da835244a798f797f478c5ddf0e8ecdf57ea85a4c70/sentencepiece-0.2.2.tar.gz", hash = "sha256:3d2b5e824b5622038dc7b490897efe05ebbbb9e7350fc142f3ecc8789ef9bdf6", size = 8218435, upload-time = "2026-07-12T08:39:34.701Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/31/f23a2efaa0210b883574001b88fa64e499f798f0848a0b610fb9b384d162/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:69e9dc8078e128286ed3b975e37c837ba96e215a50c3ef9f3f8b7ab9e5a832a0", size = 2184255, upload-time = "2026-07-12T08:38:14.855Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/1ee0ccb772d71e822f625d6cb5f0ea825835e877f28a9ef299a1291df19e/sentencepiece-0.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6dd76f3e5c8b2eb8a3a3efee787bbf5b9a66e52a048fe09cab85eca33fec6790", size = 1438545, upload-time = "2026-07-12T08:38:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/2a/92/3a6ea4a2c6dd9e7062698a5a33534ca0e20844883338ae9c6b9c122c1a9f/sentencepiece-0.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:443ac618c7a2a1377cf5c82581fbb849591d14e656d5e5a3e4682d4e36a34e4e", size = 1346997, upload-time = "2026-07-12T08:38:18.499Z" }, + { url = "https://files.pythonhosted.org/packages/f3/3a/7839048997c7bc0c34c57526f539f835e20c7a57dc2a99f99579b11cdbef/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e2aae42960392d6dcb9a72d8e1e65a97294c965071b43c7b3429a42f350250e", size = 1324282, upload-time = "2026-07-12T08:38:20.342Z" }, + { url = "https://files.pythonhosted.org/packages/06/5f/9117bf854aef817ad0d0ee9310eed0308a7e529e7eaf2e80ad9cd281ef82/sentencepiece-0.2.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1416b92f2f010333786fe6306ed2631121d5ea492219b0841e967b6765e64107", size = 1394242, upload-time = "2026-07-12T08:38:22.976Z" }, + { url = "https://files.pythonhosted.org/packages/b8/13/7a562289c8d5b49ebdf3f9c1e8ab67cf14a8743b1d90c8f406bfdec36b72/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1edb10e520e4bddf74d85b0f5ae74cc2d60c2b448885080bfb618bc2b3a49f6b", size = 2188384, upload-time = "2026-07-12T08:38:28.486Z" }, + { url = "https://files.pythonhosted.org/packages/85/d1/912f14fd5eae168aba726ffb6a9a2dc1c71fe7676c53da6f5c442b886d4a/sentencepiece-0.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7c06c751c19d923435a54bff4f7e66e728fad160e8da28254f133abc9725820", size = 1441553, upload-time = "2026-07-12T08:38:30.552Z" }, + { url = "https://files.pythonhosted.org/packages/bd/44/caa9cab5f261a019e2808bc5046152775dc57352ba9cbae7525e9e7a1ed4/sentencepiece-0.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38111ed1f79268f399c505028023d5eaaf0ab4e5eafceb709468b0d3323e7838", size = 1347176, upload-time = "2026-07-12T08:38:32.211Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/cd798935668cff71d309d8ff10385844ecf216b1fe454f1993ed8bf2cb91/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbce24284f51f71d10a42b7b9c964dcb9048b28f1c8e5db40bcbcb6f428cba6a", size = 1325200, upload-time = "2026-07-12T08:38:33.689Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2d/37e3da037318a70066ded0d51bc2a7f35491ae6338dd993d5eb1503fc3b5/sentencepiece-0.2.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8a168b040bc61681293f79a949b5d911c8e25086f4260285b8d97ab5f1195da", size = 1397736, upload-time = "2026-07-12T08:38:35.771Z" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.66.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ff/670abe04c5072719b5060ed93851d0d69525d60f8f2c5810f8becd58f9c1/sentry_sdk-2.66.0.tar.gz", hash = "sha256:9727d35aa83c56cd53294676fe65b96296a334c9ce107fa2142bd70f47acb265", size = 935745, upload-time = "2026-07-16T12:42:04.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/bb/49b10783f29067da2eec179320617e94faf63196609de47aeab3c26c3325/sentry_sdk-2.66.0-py3-none-any.whl", hash = "sha256:096136c214c602be2b323524d30755dc5b30ec5a218a206207f33b12c05c6f11", size = 504769, upload-time = "2026-07-16T12:42:02.919Z" }, +] + +[[package]] +name = "setuptools" +version = "83.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, +] + +[[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 = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + +[[package]] +name = "soundfile" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/db/949331952a6fb1c5b12e9de80fd08747966c2039d1a61db4764fbd3981c2/soundfile-0.14.0.tar.gz", hash = "sha256:ba1c1a2d618bca5c406647c83b89f07cc8810fa506a50622a6993ba130c1de11", size = 47842, upload-time = "2026-06-06T08:58:47.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/d1/5e338af9ca6ed0786cd5bb03f6d60de1c325728c1189014f3b59aae7403c/soundfile-0.14.0-py2.py3-none-any.whl", hash = "sha256:8ba81ae3a89fd5ab3bef8a8eb481fbbe794e806309675a89b4df48b8d31908a8", size = 26799, upload-time = "2026-06-06T08:58:33.269Z" }, + { url = "https://files.pythonhosted.org/packages/7e/72/c6b21e58d3113596e7e8de0a08d6f1d95173492cfbca0a4db14148cbba2a/soundfile-0.14.0-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:19be05428da76ed61a4cad29b8e4bcf43a3e5c100089d2ec81dc961eed1b0dd4", size = 1144568, upload-time = "2026-06-06T08:58:35.231Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/dfdd6f8c748988427119f75eb860a3cedd858d1aea1fe28f39ad8559ef22/soundfile-0.14.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:d828d35a059626da52f1415b5faee610aeab393319cb3fc4a9aef47b619fc14c", size = 1103726, upload-time = "2026-06-06T08:58:37.948Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f8/fc39fad6f879633461d27394cd1ddaf1f769ffa0597dca35872f51b16461/soundfile-0.14.0-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e85724a90bc99a6e8062c0b4ddf725f53b2a3b70afd4da875e9d2cfc4e92f377", size = 1238050, upload-time = "2026-06-06T08:58:39.932Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a2/70fd4432b924684c372df8b0a45708c36c057ef3596c9eb53e0a806b980b/soundfile-0.14.0-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1e38bac1853412871318e82a1ba69a8be677619b56025bbfcccdb41b6cafe82d", size = 1315963, upload-time = "2026-06-06T08:58:41.716Z" }, +] + +[[package]] +name = "sox" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/a2/d8e0d8fd7abf509ead4a2cb0fb24e5758b5330166bf9223d5cb9f98a7e8d/sox-1.5.0.tar.gz", hash = "sha256:12c7be5bb1f548d891fe11e82c08cf5f1a1d74e225298f60082e5aeb2469ada0", size = 63905, upload-time = "2024-03-20T16:59:37.385Z" } + +[[package]] +name = "soxr" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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/8e/49/3e6bc84f87439f222f40b616e9a29a170f41fb564710ea510df19dc26907/soxr-1.1.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:34cc92208c3c412c046813e69da639c04a792c6a41fbfd7d909d359cd3e97a2d", size = 205699, upload-time = "2026-05-03T00:14:46.67Z" }, + { url = "https://files.pythonhosted.org/packages/2f/94/216f46096a85b07d1e6ba7fd44491402e912a3d688cd4f36f0a600ca155f/soxr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd30f7201eac896ebf5db7b09156e6f1a1b82601900d29d9c8449bdad8365b11", size = 167381, upload-time = "2026-05-03T00:14:48.012Z" }, + { url = "https://files.pythonhosted.org/packages/94/cb/06caa463b8181ec1981bd6376d4a873748b7008193188b8cfb60391eb131/soxr-1.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1577865e993f98ffb261257c3060fa76ec3db44ed3f181b16464268000424464", size = 210938, upload-time = "2026-05-03T00:14:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/86/47/d5964551ca818b7f0c7ef7f3899056263b60ef098a801066350a9672ca8f/soxr-1.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3da87e3ffa3e41823d873b051c7ecb2acebd8d1b6b46b752f5facf10a0d84ab9", size = 245268, upload-time = "2026-05-03T00:14:51.422Z" }, + { 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" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(platform_machine == 'AMD64' and sys_platform == 'darwin') or (platform_machine == 'WIN32' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'amd64' and sys_platform == 'darwin') or (platform_machine == 'ppc64le' and sys_platform == 'darwin') or (platform_machine == 'win32' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'AMD64' and sys_platform == 'linux') or (platform_machine == 'WIN32' and sys_platform == 'linux') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'amd64' and sys_platform == 'linux') or (platform_machine == 'ppc64le' and sys_platform == 'linux') or (platform_machine == 'win32' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "executing", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pure-eval", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "strenum" +version = "0.4.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/ad/430fb60d90e1d112a62ff57bdd1f286ec73a2a0331272febfddd21f330e1/StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff", size = 23384, upload-time = "2023-06-29T22:02:58.399Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/69/297302c5f5f59c862faa31e6cb9a4cd74721cd1e052b38e464c5b402df8b/StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659", size = 8851, upload-time = "2023-06-29T22:02:56.947Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + +[[package]] +name = "tensorboard" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "grpcio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "markdown", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pillow", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tensorboard-data-server", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "werkzeug", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/4b/cd2eec9642781a8f5b2fb9994e3933a7b259ab18e9d49aeede9b5acf6311/tensorboard-2.21.0-py3-none-any.whl", hash = "sha256:7279316dcb6bd5bc391d623dea841531299cde1887310e8133bc34a996d32255", size = 5516204, upload-time = "2026-06-29T20:48:04.472Z" }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, +] + +[[package]] +name = "text-unidecode" +version = "1.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +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" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "toolz" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "sys_platform == 'darwin'" }, + { name = "fsspec", marker = "sys_platform == 'darwin'" }, + { name = "jinja2", marker = "sys_platform == 'darwin'" }, + { name = "networkx", marker = "sys_platform == 'darwin'" }, + { name = "setuptools", marker = "sys_platform == 'darwin'" }, + { name = "sympy", marker = "sys_platform == 'darwin'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045, upload-time = "2026-07-08T16:05:22.997Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0+cu129" +source = { registry = "https://download.pytorch.org/whl/cu129" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'linux'", + "python_full_version < '3.12' and sys_platform == 'linux'", +] +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", marker = "sys_platform == 'linux'" }, + { name = "fsspec", marker = "sys_platform == 'linux'" }, + { name = "jinja2", marker = "sys_platform == 'linux'" }, + { name = "networkx", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu12", marker = "sys_platform == 'linux'" }, + { name = "setuptools", marker = "sys_platform == 'linux'" }, + { name = "sympy", marker = "sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu129/torch-2.13.0%2Bcu129-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f47dd63ad198aee205ddb90393e574d19b4bb83185f75e71d63ed7634cb47bad", upload-time = "2026-07-08T20:03:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cu129/torch-2.13.0%2Bcu129-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:545c1ecc90e0fb13a94a51ea9e82d03aaf416fa78fc8efe43be64cf93e7bc0fa", upload-time = "2026-07-08T20:04:05Z" }, + { url = "https://download-r2.pytorch.org/whl/cu129/torch-2.13.0%2Bcu129-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:4ee86fc2f4af36642751b95fecc7ba4ad18283f010c5a841208ecd850c22d48a", upload-time = "2026-07-08T20:05:05Z" }, + { url = "https://download-r2.pytorch.org/whl/cu129/torch-2.13.0%2Bcu129-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:df28741fcd89e3da7cce2d48cbe5299d6732d510ac20f5d422d0b85edf18c327", upload-time = "2026-07-08T20:06:04Z" }, +] + +[[package]] +name = "torchmetrics" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384, upload-time = "2026-03-09T17:41:19.756Z" }, +] + +[[package]] +name = "tqdm" +version = "4.69.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/69/40407dfc835517f058b603dbf37a6df094d8582b015a51eddc988febbcb7/tqdm-4.69.0.tar.gz", hash = "sha256:700c5e85dcd5f009dd6222588a29180a193a748247a5d855b4d67db93d79a53b", size = 792569, upload-time = "2026-07-17T18:09:06.2Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/21/99a0cdaf54eb35e77623c41b5a2c9472ee4404bba687052791fe2aba6773/tqdm-4.69.0-py3-none-any.whl", hash = "sha256:9979978912be667a6ef21fd5d8abf54e324e63d82f7f43c360792ebc2bc4e622", size = 676680, upload-time = "2026-07-17T18:09:04.172Z" }, +] + +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, +] + +[[package]] +name = "transcribe-sortformer-env" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "gguf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "nemo-toolkit", extra = ["asr"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sentencepiece", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "soundfile", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform == 'darwin'" }, + { name = "torch", version = "2.13.0+cu129", source = { registry = "https://download.pytorch.org/whl/cu129" }, marker = "sys_platform == 'linux'" }, +] + +[package.metadata] +requires-dist = [ + { name = "gguf", specifier = ">=0.10.0" }, + { name = "huggingface-hub", specifier = ">=0.20" }, + { name = "nemo-toolkit", extras = ["asr"], git = "https://github.com/NVIDIA-NeMo/NeMo.git?rev=6967f48fda2a" }, + { name = "numpy", specifier = ">=1.26" }, + { name = "sentencepiece", specifier = ">=0.2" }, + { name = "soundfile", specifier = ">=0.12" }, + { name = "torch", specifier = ">=2.2" }, +] + +[[package]] +name = "transformers" +version = "5.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "regex", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "safetensors", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tokenizers", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/fb/2a2ba88f325e68a921d8b69ff63b477830b2e73ade9a3c8c8cab2f06d741/transformers-5.14.1.tar.gz", hash = "sha256:60d196c27781eacf8637e2b533f517582907ad6f9ae142046d6b69431a5b2173", size = 9295927, upload-time = "2026-07-16T09:41:57.773Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/67/8d85ca2323233ae3c0365a659c4e52ee1f587b440e4bc577e7d8e4416d0f/transformers-5.14.1-py3-none-any.whl", hash = "sha256:9db974c4079ede2d1a3ea7ca5a240df33f2cc26fc2b36ba64c5f2a4f43b6e725", size = 11625234, upload-time = "2026-07-16T09:41:54.143Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, +] + +[[package]] +name = "typeguard" +version = "4.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1c/dfba5c4633cafc4c701f237d2ba63b416805047fd6d96aab4cfc40969f98/typeguard-4.5.2.tar.gz", hash = "sha256:5a16dcac23502039299c97c8941651bc33d7ea8cc4b2f7d6bbb1b528f6eea423", size = 80240, upload-time = "2026-05-14T12:59:40.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl", hash = "sha256:fcf9de18bd945cdb4c7b996e12b4c51ce83f92f191314a6d7cf1739586ec98cf", size = 36748, upload-time = "2026-05-14T12:59:39.473Z" }, +] + +[[package]] +name = "typer" +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "rich", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "shellingham", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/78/fda3361b56efc27944f24225f6ecd13d96d6fcfe37bd0eb34e2f4c63f9fc/typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5", size = 203430, upload-time = "2026-07-15T19:21:07.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/03/26a383c9e58c213199d1aad1c3d353cfc22d4444ec6d2c0bf8ad02523843/typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1", size = 122716, upload-time = "2026-07-15T19:21:05.553Z" }, +] + +[[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.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "wandb" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "platformdirs", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "protobuf", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pydantic", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "sentry-sdk", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/fb/8d3f96a8b143060d6fa145462d0785981373e04694e4152555ccb5d23939/wandb-0.28.1.tar.gz", hash = "sha256:870ccb1a01238b0ac07c6fd96a0810a1f79090aba04ea29f4ee012ac8327705d", size = 40578119, upload-time = "2026-07-16T18:47:05.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/21/8df50164d07623cfcefec19bbf9327d9be84b637a827cea1f0c06db005fd/wandb-0.28.1-py3-none-macosx_12_0_arm64.whl", hash = "sha256:da909a76e65c64c0d93acc485d2a19f66e336f1e3f725f1c98a070883e084943", size = 24277925, upload-time = "2026-07-16T18:46:42.383Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/6c3da7e6cb215ad363324db8dc4d83b93626f5e339822b05b1c38a6097fd/wandb-0.28.1-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:3da3db219c54bfd1082c00e9061c8ea894ba43e42733b5af00bb10c09d7158fe", size = 25480852, upload-time = "2026-07-16T18:46:45.102Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1a/d15bcfb4417fa69edcaa33db8ea012db733da1057e193b047e3f69fdd671/wandb-0.28.1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:ae9ae6fb29e2e2b1d097ed8b75c0c0240c778c2a8cad1d996dee870a1e401c2c", size = 24832138, upload-time = "2026-07-16T18:46:47.433Z" }, + { url = "https://files.pythonhosted.org/packages/b3/da/49924c7df2952dfd82c86c3779c339c0c3d6f6439387c03d97d0470c3658/wandb-0.28.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:8cfb898b6a6c884d9c9294b02764e88bce65049f027a124d6bee53fe722469b6", size = 26486533, upload-time = "2026-07-16T18:46:49.839Z" }, + { url = "https://files.pythonhosted.org/packages/11/c0/06b23518e29690784f1b3081e39c7679ca076cb0af094cb9b4bb309150f5/wandb-0.28.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:cf2b1533945395e4fdbe6182b272bb0ca8a02c10b3086a395e2d57686ae3ed0d", size = 25022635, upload-time = "2026-07-16T18:46:52.376Z" }, + { url = "https://files.pythonhosted.org/packages/23/30/6de2f7995a8a6eecbd03d24c79a139a734c0168f5520cf4c7ccb43c1dbbc/wandb-0.28.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7233061080507a4b4098bed1ccb381ce6f890c60397cd4153d060285bcb267bd", size = 27008895, upload-time = "2026-07-16T18:46:55.025Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + +[[package]] +name = "webdataset" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "braceexpand", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "numpy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "pyyaml", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/3a/68800d92e065cf4750ebecf973b13979c0c929b439e1293012938862038d/webdataset-1.0.2.tar.gz", hash = "sha256:7f0498be827cfa46cc5430a58768a24e2c6a410676a61be1838f53d61afdaab4", size = 80090, upload-time = "2025-06-19T23:26:21.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/00/aca6beb3658dab4ed3dbb41a78e6e7f31342e0b41d28088f205525751601/webdataset-1.0.2-py3-none-any.whl", hash = "sha256:3dbfced32b25c0d199c6b9787937b6f85742bc3c84f652c846893075c1c082d9", size = 74956, upload-time = "2025-06-19T23:26:20.354Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/b2/381be8cfdee792dd117872481b6e378f85c957dd7c5bca38897b08f765fd/werkzeug-3.1.8.tar.gz", hash = "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44", size = 875852, upload-time = "2026-04-02T18:49:14.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/8c/2e650f2afeb7ee576912636c23ddb621c91ac6a98e66dc8d29c3c69446e1/werkzeug-3.1.8-py3-none-any.whl", hash = "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", size = 226459, upload-time = "2026-04-02T18:49:12.72Z" }, +] + +[[package]] +name = "wget" +version = "3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/6a/62e288da7bcda82b935ff0c6cfe542970f04e29c756b0e147251b2fb251f/wget-3.2.zip", hash = "sha256:35e630eca2aa50ce998b9b1a127bb26b30dfee573702782aa982f875e3f16061", size = 10857, upload-time = "2015-10-22T15:26:37.51Z" } + +[[package]] +name = "whisper-normalizer" +version = "0.1.14" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "indic-numtowords", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "more-itertools", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "regex", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/22/7441906d01069452f054008d309c58bf833b5d0633e7c1acbb704d23b050/whisper_normalizer-0.1.14.tar.gz", hash = "sha256:e35c095bccfe566e64c8441f36e70af9af8b0b2e04d209102d50cea42d315f7b", size = 39078, upload-time = "2026-07-17T06:03:10.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/cd/5cfb3c2ff8222de9666feb7ef89a1c46e118a9a2a67bf8947bde2cebbd03/whisper_normalizer-0.1.14-py3-none-any.whl", hash = "sha256:93119c1425c6225df4ef33d071cde2f61706cab4f72bb5b4fe47c9f7cdce8747", size = 37387, upload-time = "2026-07-17T06:03:08.814Z" }, +] + +[[package]] +name = "wrapt" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/a4/282c8e64300a59fc834518a54bf0afabb4ff9218b5fa76958b450459a844/wrapt-2.2.2.tar.gz", hash = "sha256:0788e321027c999bf221b667bd4a54aaefd1a36283749a860ac3eb77daed0302", size = 129068, upload-time = "2026-06-20T23:49:44.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/15/0c2d55168707465abfc41f33c0b23d792a5fa9b65c26983606940900a120/wrapt-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f1a2ff355ece6a111ca7a20dc86df6659c9205d3fcee674ca34f2a2854fd4e73", size = 80782, upload-time = "2026-06-20T23:47:44.367Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b5/5c0b093eb48f8a062ef6267d3cb36e9bb1b88440181f6545a383c60efdf8/wrapt-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55b9a899e6fff5444f229d30aa6e9ac92d2216d9d60f33c771b5d76a760d5f8e", size = 81678, upload-time = "2026-06-20T23:47:45.857Z" }, + { url = "https://files.pythonhosted.org/packages/34/f3/de70937472dd3e8a4e6811192f9c6075efdffd4a2cd9b4596bf160f89668/wrapt-2.2.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a2d78c363f97d8bd718ee40432c66395685e9e98528ccaa423c3355d1715a26d", size = 159671, upload-time = "2026-06-20T23:47:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/40aed2330e7f02ecf74386ffcfef9ccb7108c6a430f15b6a252b663b1bed/wrapt-2.2.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d619e1eed9bd4f6ed9f24cd61971aa086fa86505289628d464bcf8a2c2e3f328", size = 160785, upload-time = "2026-06-20T23:47:48.759Z" }, + { url = "https://files.pythonhosted.org/packages/45/04/aa5309beed5344b00220ae6b3b24055852192656194c27947bee1736306a/wrapt-2.2.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:518b0c5e323511ec56a38894802ddd5e1222626484e68efe63f201854ad788e5", size = 153699, upload-time = "2026-06-20T23:47:50.177Z" }, + { url = "https://files.pythonhosted.org/packages/01/df/2def7e99d1fe87eea413f95f671924cdddcb08823b1ffd212748dfa6d062/wrapt-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4bccea5cdecffa9dd70e343741f0e41e0a16619313d04b72f78bb525162ebcd0", size = 159695, upload-time = "2026-06-20T23:47:51.602Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f6/a906d01a2ce12157bad2404957b3e2140da354b8a70b2fa48bbf282871c0/wrapt-2.2.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:209112cafd963710a05d199aae431d79a28bc76eb8e6d1bbbb8ad24340722cae", size = 152813, upload-time = "2026-06-20T23:47:53.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/49/bc0086292d239575b4c08f4cf8a4079fa58abbad58ec23abf84833a283ed/wrapt-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5a5290e4bf2f332fc29ce72ffb9a2fff678aaac047e2e9f5f7165cd7792e099", size = 158809, upload-time = "2026-06-20T23:47:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/2a/85/180b40628b23772692a0c76e8030114e1c0ae068470ed531919f0a5f2a4a/wrapt-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8417fd3c674d3c8023d080292d29301531a12daf8bd938dd419710dd2f464f2b", size = 81484, upload-time = "2026-06-20T23:47:59.924Z" }, + { url = "https://files.pythonhosted.org/packages/94/f2/21c90f2a16689702e2aaff45795b11018dff2c9b1242bac10d225483f676/wrapt-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e7070c7472582e31af3dfc2622b2381a0df7435110a9388ed8db5ffbce67efb", size = 82151, upload-time = "2026-06-20T23:48:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b3/7e6e9fcf4fe7e1b69a49fe6cc5a44e8224bab6283c5233c97e132f14908e/wrapt-2.2.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e096c9d39a59b35b63c9aacfbbbec2088ff51ff1fc31051acc60a07f42f273a", size = 169828, upload-time = "2026-06-20T23:48:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/894f132d857ed5a9904d937baf368badcbe5ea9e436e2f1930fe21c9f1f0/wrapt-2.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d1a6050405bf334be33bf66296f113563622972a34900ae6fa60fd283a1a900", size = 171544, upload-time = "2026-06-20T23:48:04.266Z" }, + { url = "https://files.pythonhosted.org/packages/29/de/3c833e03725b477e9ea34028224dd21a48781830101e4e036f77e8b6b102/wrapt-2.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10adb01371408c6de504a6658b9886480f1a4919a83752748a387a504a21df79", size = 160663, upload-time = "2026-06-20T23:48:05.708Z" }, + { url = "https://files.pythonhosted.org/packages/33/be/27edce350b24e3054d9d047f65f16d4c4d4c1f3f31c4278a1f8a95c723c8/wrapt-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3442eee2a5798f9b451f1b2cd7518ce8b7e28a2a364696c414460a0e295c012a", size = 169387, upload-time = "2026-06-20T23:48:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c4/9fd9679af8bf38e146652c7f47b6b352c3e5795b4ad1c0b7f94e15ac2aa7/wrapt-2.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6c99012a22f735a85eed7c4b86a3e99c30fdd57d9e115b2b45f796264b58d0bf", size = 158849, upload-time = "2026-06-20T23:48:08.91Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c2/aa6c0c2206803068c6859dabe01f8c84c43744da93d4c67b8946d21655ee/wrapt-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b686cfc008776a3952d6213cb296ed7f45d782a8453936406faa89eac0835ab", size = 168147, upload-time = "2026-06-20T23:48:10.374Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d2/6317eb6d4554855bbf12d61857774af34747bf88a42c19bf306de67e2fa3/wrapt-2.2.2-py3-none-any.whl", hash = "sha256:5bad217350f19ce99ca5b5e71d406765ea86fe541628426772b657375ee1c048", size = 61460, upload-time = "2026-06-20T23:49:42.966Z" }, +] + +[[package]] +name = "xxhash" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/63/71aa56b151a1b28770037a61bd4e461c2619cfc8866a4fcaf1548605e325/xxhash-3.8.1.tar.gz", hash = "sha256:b0de4bf3aa66363552d52c6a89003c479911f12098cd48a53d44a0f7a25f7c46", size = 86223, upload-time = "2026-07-06T10:49:58.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/5a/05eaa129555f85476a3e16ff869e95f81a78bbe4647eef9d0229f515a317/xxhash-3.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602efcad4a42c184e81d43a2b7e6e4f524d619878f2b6ee2ba469011f47c8147", size = 34699, upload-time = "2026-07-06T10:44:10.14Z" }, + { url = "https://files.pythonhosted.org/packages/80/59/0df1133958b2228929355e022aab1e958c7b2c43e27bf7f59bc9edfa8a54/xxhash-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:131324f719957b988861714de7d6ddf57b47abec3b0cc691302ffeaba0e05e10", size = 32373, upload-time = "2026-07-06T10:44:11.353Z" }, + { url = "https://files.pythonhosted.org/packages/3e/bf/1cfda5b5e6bf26617812b4a31662ef2220d2ad04e0a55b8ff9eb36e56a5c/xxhash-3.8.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:db77278a6eddadbf44ce5aae2fee5ebb4d061f026b1ce2130d058cd4d7a7b670", size = 220284, upload-time = "2026-07-06T10:44:12.683Z" }, + { url = "https://files.pythonhosted.org/packages/70/93/45dc0ad7913b69e5b08bd039236cf628380e4c9cc76a8a4c6625a328e058/xxhash-3.8.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c332dd48b8cb050da2bb2a3c96d72b1664168650a250ef9718e423df7989e05", size = 240980, upload-time = "2026-07-06T10:44:14.297Z" }, + { url = "https://files.pythonhosted.org/packages/e9/02/f28ba7d17f2c1410ee397982c817ab1bd5b2701070c2d2c373539aad000a/xxhash-3.8.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a5cd96f6dcdf4fa657b2d95668d71d58455248f98712ecffaa9c528edf40ccae", size = 264526, upload-time = "2026-07-06T10:44:16.017Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/f10651cec2c7981b20d693deae6bdfc438427d92be2db4ccabb6181f0021/xxhash-3.8.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c959f88160b13b4e730b0d75b459b7929fc0d2225c284c9683ac95d6feeeac6a", size = 241369, upload-time = "2026-07-06T10:44:17.698Z" }, + { url = "https://files.pythonhosted.org/packages/ff/40/136e0cbaf5db51e191423b1c98643593189f02b6cd90837bf64b19113d70/xxhash-3.8.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:027dee4355f3fcc41481650d846cf6cfc895c85a1ab7acd063063821a0df5b4c", size = 473186, upload-time = "2026-07-06T10:44:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/4b/3f/6aa808a96bdc43dba9a740dec56c744526ee3c0019e32c75e810fa90ae4d/xxhash-3.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad52a0e4bcc0ba956a953a169d1feec2734a64981d689e4fc8f490f7bf91af60", size = 220092, upload-time = "2026-07-06T10:44:20.956Z" }, + { url = "https://files.pythonhosted.org/packages/47/28/a8675e78a9ced96dab853416162268e10e05b452e95db7888cf69f58ac5f/xxhash-3.8.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5d3dfb1f0ff146da7952867a9414f0c7a29762f8825a84879592612fd6139342", size = 309846, upload-time = "2026-07-06T10:44:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/0f/7fe4d4ef4e69f0033e012396ee2a115886bca7b10b7e45ce398626436bfc/xxhash-3.8.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4482380b462ca9e59994d072a877ecadd1cf51102daeeab2db696f96ab763723", size = 237659, upload-time = "2026-07-06T10:44:24.135Z" }, + { url = "https://files.pythonhosted.org/packages/38/8f/83e9e31d4ed57fe963b99cb5b13a23e3e0f0dad1885aa0ebd2a7819dd423/xxhash-3.8.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:950ac754d16daea42038f38e7465eb84cda4d08d7343c1c915771b29470f065a", size = 268737, upload-time = "2026-07-06T10:44:25.875Z" }, + { url = "https://files.pythonhosted.org/packages/57/79/7e7de46dbe5d1f49afc96a0bc42e6b8df24eae3d6bad6007b99e42f48430/xxhash-3.8.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0418ec8b2331b9d4d575fc9284427e8e69449d7172e99e1a86fcdd1f51a0a937", size = 224955, upload-time = "2026-07-06T10:44:27.777Z" }, + { url = "https://files.pythonhosted.org/packages/ec/34/b8540839e958d5ef5c6101af6f16032109e7099698ae8edbc8dcefe4d8f4/xxhash-3.8.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:32a94ad2763e0263d9102037d349002c3d3c401e42770542c3eeb4801f311661", size = 239653, upload-time = "2026-07-06T10:44:29.422Z" }, + { url = "https://files.pythonhosted.org/packages/ce/87/a735d05f7f859354acadabe470ff40e2c46672275f96dcf096a761904def/xxhash-3.8.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:89b11a5cdd441aa463f6d34ca0241602bc09b001a76994b6059828494108c673", size = 300213, upload-time = "2026-07-06T10:44:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/3e1cb020237b68117fc212dc5f9753b87f865b4dfee7c1ce62d0836955b5/xxhash-3.8.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:09a204dd4bb0823daf938cdd0dc8057d5f1e14fe3cbde929424255f23f9de872", size = 442508, upload-time = "2026-07-06T10:44:33.023Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/f80090622141cc734b039ce1d15ce3ff6dced375e9680249bf5b9b8c6bf9/xxhash-3.8.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e710ad822c493fb80a4fbc1e3d0a807b1422cb90adbe64378f98291b7fa48fef", size = 216853, upload-time = "2026-07-06T10:44:34.983Z" }, + { url = "https://files.pythonhosted.org/packages/42/91/f65c34a7aa7b4e7cf4854f8e6ef3f7ee32ceac41d4f008da0780db0612f6/xxhash-3.8.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e6e49370822c1f4d8d90e678b06dbcb08b51a026a7c4b55479e7d467f2e813bc", size = 34680, upload-time = "2026-07-06T10:44:40.932Z" }, + { url = "https://files.pythonhosted.org/packages/57/04/b10a245a4c09a9cfa88f8e9ae755029413ad1ac17047f9a61906e5ae0799/xxhash-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:220d68130f83f7cc86d6edfdeab176adc73d7200bf3a8ec10c629e8cf605c215", size = 32397, upload-time = "2026-07-06T10:44:42.196Z" }, + { url = "https://files.pythonhosted.org/packages/3a/75/45ab795b5945b6388583bd75202106af505537935566c15a1577797a0e08/xxhash-3.8.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d365ee1892c1fa803536f8c6ce21d24b29c9718ec75eb856095c07830f8c478", size = 220549, upload-time = "2026-07-06T10:44:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/13/44/5ba2bd0a14ddf4193fc7d8ec29625f659f22c06d60b28f04bf46305d8330/xxhash-3.8.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:852bfe059720632e2f16a6a4745e41d20937b2bf2a42a401e2412046bb6971cc", size = 241186, upload-time = "2026-07-06T10:44:45.534Z" }, + { url = "https://files.pythonhosted.org/packages/23/32/c4147def4d1e4538b906f82731e0ba23424377fc50a7cddd03cd284c8f63/xxhash-3.8.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2f8c25a7061d952de589bd0ea0eaadee32378ff83dd6a677b267f9cd86f401f8", size = 264852, upload-time = "2026-07-06T10:44:47.199Z" }, + { url = "https://files.pythonhosted.org/packages/6c/bd/71ed14f4f0318bb7fd7b2ec51999413487fa8da8d41208e84d50d1ef0f98/xxhash-3.8.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:868a8dcaff1a84ba78038e1cef14fc88ccf84d9b4d12ea604696e0693296aa56", size = 242663, upload-time = "2026-07-06T10:44:48.846Z" }, + { url = "https://files.pythonhosted.org/packages/91/09/70af22c565a8473b3f2ae73f88e7721af281bc4a575236dbd1970c9f76f6/xxhash-3.8.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6536d8677d2fff7e64cd0b98b976df9de7aee0e69590044c2af5f51b76b7a170", size = 473510, upload-time = "2026-07-06T10:44:50.695Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/34db781c8f0cf99c544ca1f2bc2e5bf55426e1eb4ca6de8ea5da56a9f352/xxhash-3.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82c0cedd280eab2e8291270e6c04894dbc096f8159a39dcf1807429f026ca3cc", size = 220469, upload-time = "2026-07-06T10:44:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/93/5f/9a184f615fa5a4dce30c01534f62946ce5a11ce40f73785cbd356ccabaa9/xxhash-3.8.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daa86e4b68221d38e669bb236ba112d0335353829fb627c82e5909e4bbe8694c", size = 310290, upload-time = "2026-07-06T10:44:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/a9/dc/9b9a9789011ee153723a5eb9e7dd7fcbae2ba9b3fe7a729249ca7c252056/xxhash-3.8.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2bc7113e6f2b6b3922dd61796ca9f36af09da3773898e7003038dc992fc83b8d", size = 238173, upload-time = "2026-07-06T10:44:55.693Z" }, + { url = "https://files.pythonhosted.org/packages/ec/4d/71c6005ada9dcb608a4e1902e8475ecadb5f3fbfa04e1e244d276a2d0c43/xxhash-3.8.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5eed32dad81d6ba8e62dc7b9ffa0500199385d7810a8dd9d4eafaceb8c6e20bb", size = 269026, upload-time = "2026-07-06T10:44:57.424Z" }, + { url = "https://files.pythonhosted.org/packages/2f/87/d6c036ba25dfbd9c8633be5aa86fc9474bbb9e2c68212a841d090abe7344/xxhash-3.8.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:83697b0ea1f10e7f5d8b26a4906fa851393c61546c63839643a2b7fe2d868061", size = 224970, upload-time = "2026-07-06T10:44:59.085Z" }, + { url = "https://files.pythonhosted.org/packages/48/62/4c1f035a41c5752aa05e195b6c904c07b94fe9061a16de61e72a6e6b135f/xxhash-3.8.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:36fc69160465ae75c6ec4ac9f781bb2aa16ae7ff869e73c26fee85fbb11b9887", size = 240820, upload-time = "2026-07-06T10:45:00.746Z" }, + { url = "https://files.pythonhosted.org/packages/da/14/d39d565069b87e86d21a2af2a31d04db79249d25aa8d5b62959056a89857/xxhash-3.8.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:445e0f5a31f2f3546ae0895d4811e159518cdc9d824c11419898d40cfadb677e", size = 300619, upload-time = "2026-07-06T10:45:02.716Z" }, + { url = "https://files.pythonhosted.org/packages/13/22/75467acc887edc8cf71c97ab1708feb3df7a88bda589b9f399765c6387d2/xxhash-3.8.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:dfe0580fbfd5e4af87d0cc52d2044f155d55ebd8c8a93568758a2ea7d8e15975", size = 443267, upload-time = "2026-07-06T10:45:04.653Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b6/1da3baa5fa6ef705e3425fddd382be7dfc4dfba2686df90a20f16e9c7b1b/xxhash-3.8.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:095e1323fa108be1292c54c86da3ef3c7a7dc015b105a52133973bc07a6ad11a", size = 217338, upload-time = "2026-07-06T10:45:06.304Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/4d8040435aeac814fc69ba63621565fbeb19229a138e2568324a26b2a45c/xxhash-3.8.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:39c9d5b61508b0bb68f29e54546de0ed2a74943c6a18585535a7e37356f1dd12", size = 32687, upload-time = "2026-07-06T10:49:42.803Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/975f1f2318c760e5bcec109ed379713ae645d8d856c2a3b9ec5d26857087/xxhash-3.8.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:83b9130b80b216d56fdf9e87131946b353c9627930c061955a101ea82b09fed9", size = 29879, upload-time = "2026-07-06T10:49:45.172Z" }, + { url = "https://files.pythonhosted.org/packages/08/0b/40a2a55ff52cf635bfdc5eae67a772bec85b4f44c6c737f73f6f528d51d1/xxhash-3.8.1-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8304be0982130954b7fd3aad18e2c6f8ee40254bc3d2e635991c16d77c91e2bd", size = 43246, upload-time = "2026-07-06T10:49:47.905Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6d/56ed2b6b200f26fb474f3fd387d95d0601efcd5bb33430c90c68924bdd77/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b512261801b1e5fde7b6ebf2fef7977339c620cbbca88a0040ad9ad134f4d02", size = 38202, upload-time = "2026-07-06T10:49:50.59Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a3/56864d895d1161a9f17502088e9c1fb7c06bde2c2efdde620d22bb7a9c43/xxhash-3.8.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49aa8692507835dcc1e8ad8021f20c74c2dc13d83b5112e87877faa2a0035b20", size = 34448, upload-time = "2026-07-06T10:49:53.242Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "multidict", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "propcache", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" }, + { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" }, + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, +] diff --git a/scripts/gen_sortformer_oracle_audio.py b/scripts/gen_sortformer_oracle_audio.py new file mode 100644 index 00000000..2485675e --- /dev/null +++ b/scripts/gen_sortformer_oracle_audio.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""Deterministically synthesize a 2-speaker diarization oracle clip. + +Sortformer is a diarizer, so the oracle case must be multi-speaker; +samples/jfk.wav and the other committed clips are all single-speaker. +This builds a reproducible 2-speaker mixture from two distinct committed +mono clips, with a controlled overlap region, plus a ground-truth RTTM. + +Because we author the timeline, the RTTM is exact: it doubles as a +known-answer for a mini-DER sanity check without any external corpus. +Speaker identity does not depend on language (Sortformer keys on speaker +acoustics), so a cross-language pair maximizes speaker distinctness. + +Outputs (16 kHz mono, deterministic): + samples/sortformer-2spk-mix.wav + tests/golden/sortformer/sortformer-2spk-mix.rttm + +Run: + uv run scripts/gen_sortformer_oracle_audio.py +""" + +from __future__ import annotations + +import subprocess +import sys +from pathlib import Path + +import numpy as np +import soundfile as sf + +SR = 16000 +REPO = Path(__file__).resolve().parent.parent +SPK_A_SRC = REPO / "samples" / "jfk.wav" # English male, very distinctive +SPK_B_SRC = REPO / "samples" / "zh.wav" # different speaker/timbre +OUT_WAV = REPO / "samples" / "sortformer-2spk-mix.wav" +OUT_RTTM = REPO / "tests" / "golden" / "sortformer" / "sortformer-2spk-mix.rttm" +CLIP_ID = "sortformer-2spk-mix" + +# Authored timeline in seconds. Overlap is the [9.0, 10.5] region where +# both speakers are active. Total clip length 12.0 s. +SPK_A_WINDOWS = [(0.0, 3.0), (7.0, 10.5)] +SPK_B_WINDOWS = [(3.5, 6.5), (9.0, 12.0)] +TOTAL_SEC = 12.0 + + +def _load_mono_16k(path: Path) -> np.ndarray: + audio, sr = sf.read(str(path), dtype="float32", always_2d=False) + if audio.ndim > 1: + audio = audio.mean(axis=1) + if sr != SR: + # Deterministic linear resample; good enough for a diarization + # oracle clip (no ASR scoring on this audio). + n_out = int(round(len(audio) * SR / sr)) + x_old = np.linspace(0.0, 1.0, num=len(audio), endpoint=False) + x_new = np.linspace(0.0, 1.0, num=n_out, endpoint=False) + audio = np.interp(x_new, x_old, audio).astype(np.float32) + # Peak-normalize each source so both speakers sit at a similar level. + peak = float(np.max(np.abs(audio))) or 1.0 + return (audio / peak * 0.9).astype(np.float32) + + +def _tile_to(src: np.ndarray, n: int) -> np.ndarray: + if len(src) >= n: + return src[:n] + reps = int(np.ceil(n / max(1, len(src)))) + return np.tile(src, reps)[:n] + + +def _lay_windows(src: np.ndarray, windows: list[tuple[float, float]], total_n: int) -> np.ndarray: + track = np.zeros(total_n, dtype=np.float32) + for start, end in windows: + i0 = int(round(start * SR)) + i1 = int(round(end * SR)) + track[i0:i1] = _tile_to(src, i1 - i0) + return track + + +def main() -> int: + for p in (SPK_A_SRC, SPK_B_SRC): + if not p.exists(): + print(f"error: missing source clip {p}", file=sys.stderr) + return 1 + total_n = int(round(TOTAL_SEC * SR)) + a = _lay_windows(_load_mono_16k(SPK_A_SRC), SPK_A_WINDOWS, total_n) + b = _lay_windows(_load_mono_16k(SPK_B_SRC), SPK_B_WINDOWS, total_n) + mix = a + b + peak = float(np.max(np.abs(mix))) or 1.0 + mix = (mix / peak * 0.9).astype(np.float32) + + OUT_WAV.parent.mkdir(parents=True, exist_ok=True) + OUT_RTTM.parent.mkdir(parents=True, exist_ok=True) + sf.write(str(OUT_WAV), mix, SR, subtype="PCM_16") + + lines = [] + for spk, windows in (("spk_A", SPK_A_WINDOWS), ("spk_B", SPK_B_WINDOWS)): + for start, end in windows: + lines.append( + f"SPEAKER {CLIP_ID} 1 {start:.3f} {end - start:.3f} {spk} " + ) + OUT_RTTM.write_text("\n".join(lines) + "\n") + + dur = len(mix) / SR + print(f"wrote {OUT_WAV.relative_to(REPO)} ({dur:.2f}s, 16kHz mono)") + print(f"wrote {OUT_RTTM.relative_to(REPO)} ({len(lines)} turns, 2 speakers, overlap [9.0,10.5]s)") + # Emit a sha so the artifact is verifiably reproducible. + sha = subprocess.run(["shasum", "-a", "256", str(OUT_WAV)], capture_output=True, text=True) + print("sha256:", sha.stdout.split()[0] if sha.returncode == 0 else "n/a") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/lib/test_quant_policy_sync.py b/scripts/lib/test_quant_policy_sync.py index c0935ca9..4566aed4 100644 --- a/scripts/lib/test_quant_policy_sync.py +++ b/scripts/lib/test_quant_policy_sync.py @@ -75,6 +75,9 @@ "enc.layers.0.ln_pre.weight", # qwen3 encoder ln_pre "enc.blocks.0.self_attn.pos_bias_u", # conformer rel-pos bias u "enc.blocks.0.self_attn.pos_bias_v", # conformer rel-pos bias v + "tf.blocks.0.norm_1.weight", # sortformer transformer post-LN (norm_ prefix) + "tf.blocks.0.attn.q.bias", # sortformer transformer attn bias (.bias) + "diar.spk_head.bias", # sortformer diarization head bias (.bias) "dec.pos_enc", # cohere sinusoidal pos table "enc.pos_emb.weight", # whisper encoder pos_emb "dec.pos_emb.weight", # whisper decoder pos_emb @@ -100,6 +103,10 @@ "enc.blocks.3.attn.linear_out.weight", # attention output projection "dec.embed.token.weight", # cohere tied embedding (Embed) "dec.token_embd.weight", # llama-style embedding (Embed) + "tf.blocks.0.attn.q.weight", # sortformer transformer attn projection + "tf.blocks.0.ff.in.weight", # sortformer transformer FFN matrix + "diar.encoder_proj.weight", # sortformer 512->192 projection + "diar.spk_head.weight", # sortformer diarization head (4 sigmoid outputs) ] # KNOWN DRIFT — policy.cpp::classify_tensor places these in the Norm (F32) or diff --git a/scripts/validate.py b/scripts/validate.py index 14f21304..2b15d2de 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -403,8 +403,24 @@ def cmd_ref(args: argparse.Namespace) -> int: # decoder intermediates across subcommands (parakeet). Running # both is safe — if a tensor is dumped by both, the decode # pass overwrites the encoder pass (same values). - for stage in ["encoder", "decode"]: - cmd = base_args + [stage] + common_args + # + # Sortformer is an encoder-diarizer: the offline forward (encoder + # subcommand) emits the offline gate tensors; the streaming `diarize` + # subcommand emits diar.probs (AOSC/FIFO path). VALIDATE_SORTFORMER_PRESET + # selects a matching streaming operating point on both the reference + # (--preset) and the C++ side (TRANSCRIBE_SORTFORMER_STREAM_PRESET in + # cmd_cpp); unset -> the checkpoint-shipped cfg (single chunk on the + # short oracle, i.e. diar.probs == diar.preds_offline). + if args.family == "sortformer": + stages = ["encoder", "diarize"] + else: + stages = ["encoder", "decode"] + sf_preset = os.environ.get("VALIDATE_SORTFORMER_PRESET") + for stage in stages: + stage_args = list(common_args) + if args.family == "sortformer" and stage == "diarize" and sf_preset: + stage_args += ["--preset", sf_preset] + cmd = base_args + [stage] + stage_args run_cmd( cmd, repo, @@ -439,6 +455,18 @@ def cmd_cpp(args: argparse.Namespace) -> int: env = os.environ.copy() env["TRANSCRIBE_DUMP_DIR"] = str(out_dir) + # Sortformer: keep the C++ streaming operating point in lockstep with + # the reference `diarize --preset` (see cmd_ref) so the diar.probs + # tensors are comparable. Also enable the offline full-context forward + # so the enc.* / diar.preds_offline parity tensors are dumped (it is + # gated off by default because it is O(T^2) over the whole clip and + # would OOM on long DER audio). + if args.family == "sortformer": + env["TRANSCRIBE_SORTFORMER_OFFLINE_DUMP"] = "1" + sf_preset = os.environ.get("VALIDATE_SORTFORMER_PRESET") + if sf_preset: + env["TRANSCRIBE_SORTFORMER_STREAM_PRESET"] = sf_preset + # Whisper: by default, exercise the production C++ MelFrontend so # the per-tensor compare covers the full mel→encoder→decoder # pipeline. The env-var ref-mel injection is preserved as an @@ -599,8 +627,13 @@ def cmd_compare(args: argparse.Namespace) -> int: # Transcript comparison: if the reference produced a transcript.json, # verify the C++ transcript. Manifests can opt into normalized compare # for models whose generation differs only in punctuation/casing. + # + # Sortformer is a diarizer: it has no text transcript (the C++ CLI + # emits speaker segments, not `text`). Its behavioral artifact is the + # diar.probs tensor, gated above; the `diarize` stage's segment lines + # are informational only, so skip the text-transcript comparison. ref_transcript = ref_dir / "transcript.json" - if ref_transcript.exists(): + if ref_transcript.exists() and args.family != "sortformer": transcript_compare = case_transcript_compare(manifest, case) ref_data = json.loads(ref_transcript.read_text()) ref_text = str(ref_data.get("text", "")) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 46984c8d..87fcbfc1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,6 +59,10 @@ add_library(transcribe arch/moss/weights.cpp arch/moss/encoder.cpp arch/moss/decoder.cpp + arch/sortformer/model.cpp + arch/sortformer/stream.cpp + arch/sortformer/capabilities.cpp + arch/sortformer/weights.cpp arch/voxtral/model.cpp arch/voxtral/capabilities.cpp arch/voxtral/weights.cpp diff --git a/src/arch/sortformer/capabilities.cpp b/src/arch/sortformer/capabilities.cpp new file mode 100644 index 00000000..7c929ef2 --- /dev/null +++ b/src/arch/sortformer/capabilities.cpp @@ -0,0 +1,35 @@ +// arch/sortformer/capabilities.cpp - Sortformer capability defaults. +// +// Applied before transcribe::read_capability_kv (KV present overrides, +// KV absent keeps the default). Sortformer is a pure frame-level diarizer: +// no transcript, no translation, no transcript-timestamp capability. Its +// output is speaker segments via the transcript-independent speaker_segment +// surface, so DIARIZATION is a family invariant. + +#include "sortformer.h" + +namespace transcribe::sortformer { + +void apply_family_invariants(transcribe_model & model) { + transcribe_capabilities & caps = model.caps; + + // Fixed 16 kHz mel bank (NeMo AudioToMelSpectrogramPreprocessor). + caps.native_sample_rate = 16000; + + // Not a transcription model. + caps.supports_translate = false; + + // Diarization segment times are intrinsic; there is no transcript, so + // there is no transcript-timestamp capability to advertise. + caps.max_timestamp_kind = TRANSCRIBE_TIMESTAMPS_NONE; + + // The model emits speaker-attributed output (T x 4 activity -> "who + // spoke when" segments). run() populates the transcript-independent + // speaker_segment surface directly from the probs. + transcribe::set_feature(&model, TRANSCRIBE_FEATURE_DIARIZATION, true); + + // Abort callback honored at the top of each run. + transcribe::set_feature(&model, TRANSCRIBE_FEATURE_CANCELLATION, true); +} + +} // namespace transcribe::sortformer diff --git a/src/arch/sortformer/model.cpp b/src/arch/sortformer/model.cpp new file mode 100644 index 00000000..19351391 --- /dev/null +++ b/src/arch/sortformer/model.cpp @@ -0,0 +1,986 @@ +// arch/sortformer/model.cpp - Sortformer (encoder-diarizer) load / run / +// Arch instance. The 17-layer NEST FastConformer encoder is the same NeMo +// ConformerEncoder Parakeet ports, so it reuses parakeet::build_encoder_graph +// verbatim (constructed from a ParakeetWeights populated with only the +// pre_encode + block slots). This file adds the Sortformer-specific graph: +// encoder_proj (512->192) -> 18x post-LN Transformer -> diar sigmoid head, +// producing a T x 4 speaker-activity matrix. + +#include "../parakeet/encoder.h" +#include "../parakeet/weights.h" +#include "conformer/conformer.h" +#include "ggml.h" +#include "gguf.h" +#include "sortformer.h" +#include "transcribe-arch.h" +#include "transcribe-backend.h" +#include "transcribe-batch-util.h" +#include "transcribe-debug.h" +#include "transcribe-flash-policy.h" +#include "transcribe-load-common.h" +#include "transcribe-loader.h" +#include "transcribe-log.h" +#include "transcribe-mel.h" +#include "transcribe-meta.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace transcribe::sortformer { + +extern const Arch arch; + +namespace pk = transcribe::parakeet; +namespace conf = transcribe::conformer; + +static constexpr float kBnEps = 1e-5f; +static constexpr char k_default_variant[] = "streaming-4spk-v2.1"; + +SortformerModel::~SortformerModel() { + if (bn_fused_ctx != nullptr) { + ggml_free(bn_fused_ctx); + } + if (bn_fused_buffer != nullptr) { + transcribe::safe_buffer_free(bn_fused_buffer); + } + if (ctx_meta != nullptr) { + ggml_free(ctx_meta); + } + if (backend_buffer != nullptr) { + transcribe::safe_buffer_free(backend_buffer); + } + for (auto it = plan.scheduler_list.rbegin(); it != plan.scheduler_list.rend(); ++it) { + transcribe::safe_backend_free(*it); + } + plan.scheduler_list.clear(); + plan.primary = nullptr; +} + +SortformerSession::~SortformerSession() { + if (sched != nullptr) { + transcribe::safe_sched_free(sched); + } + if (compute_ctx != nullptr) { + ggml_free(compute_ctx); + } +} + +namespace { + +// Map the Sortformer hparams onto the parakeet encoder hparams that +// build_encoder_graph reads. The FastConformer is NeMo's ConformerEncoder +// with xscaling=True, rel_pos self-attention, batch_norm conv, use_bias, +// full (offline) attention. +void fill_conformer_hp(const SortformerHParams & s, pk::ParakeetHParams & p) { + p.enc_n_layers = s.enc_n_layers; + p.enc_d_model = s.enc_d_model; + p.enc_n_heads = s.enc_n_heads; + p.enc_d_ff = s.enc_d_ff; + p.enc_conv_kernel = s.enc_conv_kernel; + p.enc_subsampling_factor = s.enc_subsampling_factor; + p.enc_subsampling_channels = s.enc_subsampling_channels; + p.enc_pos_emb_max_len = s.enc_pos_emb_max_len; + p.enc_use_bias = true; + p.enc_xscaling = true; // NEST FastConformer uses xscaling + p.enc_att_context_left = -1; + p.enc_att_context_right = -1; + p.enc_att_context_style = pk::ParakeetHParams::AttContextStyle::Regular; + p.enc_conv_context_left = -1; + p.enc_conv_context_right = -1; + p.enc_conv_norm_type = pk::ParakeetHParams::ConvNormType::BatchNorm; + + // Frontend fields build_encoder_graph's pre_encode geometry reads. + p.fe_num_mels = s.fe_num_mels; + p.fe_sample_rate = s.fe_sample_rate; + p.fe_n_fft = s.fe_n_fft; + p.fe_win_length = s.fe_win_length; + p.fe_hop_length = s.fe_hop_length; + p.fe_normalize = s.fe_normalize; + p.fe_dither = s.fe_dither; + p.fe_pre_emphasis = s.fe_pre_emphasis; +} + +// Populate ONLY the pre_encode + block slots of a ParakeetWeights from the +// GGUF (the conformer). Predictor / joint / head stay empty. Mirrors the +// encoder portion of build_parakeet_weights (identical tensor names). +transcribe_status load_conformer_weights(ggml_context * ctx, const pk::ParakeetHParams & hp, pk::ParakeetWeights & w) { + char name[128]; + auto get = [&](const char * nm) -> ggml_tensor * { + ggml_tensor * t = ggml_get_tensor(ctx, nm); + if (t == nullptr) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: missing conformer tensor %s", nm); + } + return t; + }; +#define G(dst, nm) \ + do { \ + (dst) = get(nm); \ + if ((dst) == nullptr) return TRANSCRIBE_ERR_GGUF; \ + } while (0) +#define GL(dst, fmt, i) \ + do { \ + std::snprintf(name, sizeof(name), fmt, i); \ + G(dst, name); \ + } while (0) + + auto & pe = w.pre_encode; + G(pe.conv0_w, "enc.pre_encode.conv.0.weight"); + G(pe.conv0_b, "enc.pre_encode.conv.0.bias"); + G(pe.conv2_w, "enc.pre_encode.conv.2.weight"); + G(pe.conv2_b, "enc.pre_encode.conv.2.bias"); + G(pe.conv3_w, "enc.pre_encode.conv.3.weight"); + G(pe.conv3_b, "enc.pre_encode.conv.3.bias"); + G(pe.conv5_w, "enc.pre_encode.conv.5.weight"); + G(pe.conv5_b, "enc.pre_encode.conv.5.bias"); + G(pe.conv6_w, "enc.pre_encode.conv.6.weight"); + G(pe.conv6_b, "enc.pre_encode.conv.6.bias"); + G(pe.out_w, "enc.pre_encode.out.weight"); + G(pe.out_b, "enc.pre_encode.out.bias"); + + w.blocks.assign(static_cast(hp.enc_n_layers), pk::ParakeetBlock{}); + for (int i = 0; i < hp.enc_n_layers; ++i) { + auto & b = w.blocks[static_cast(i)]; + GL(b.norm_ff1_w, "enc.blocks.%d.norm_ff1.weight", i); + GL(b.norm_ff1_b, "enc.blocks.%d.norm_ff1.bias", i); + GL(b.ff1_lin1_w, "enc.blocks.%d.ff1.linear1.weight", i); + GL(b.ff1_lin2_w, "enc.blocks.%d.ff1.linear2.weight", i); + GL(b.norm_attn_w, "enc.blocks.%d.norm_attn.weight", i); + GL(b.norm_attn_b, "enc.blocks.%d.norm_attn.bias", i); + GL(b.attn_q_w, "enc.blocks.%d.attn.linear_q.weight", i); + GL(b.attn_k_w, "enc.blocks.%d.attn.linear_k.weight", i); + GL(b.attn_v_w, "enc.blocks.%d.attn.linear_v.weight", i); + GL(b.attn_out_w, "enc.blocks.%d.attn.linear_out.weight", i); + GL(b.attn_pos_w, "enc.blocks.%d.attn.linear_pos.weight", i); + GL(b.attn_pos_u, "enc.blocks.%d.attn.pos_bias_u", i); + GL(b.attn_pos_v, "enc.blocks.%d.attn.pos_bias_v", i); + GL(b.norm_conv_w, "enc.blocks.%d.norm_conv.weight", i); + GL(b.norm_conv_b, "enc.blocks.%d.norm_conv.bias", i); + GL(b.conv_pw1_w, "enc.blocks.%d.conv.pointwise1.weight", i); + GL(b.conv_dw_w, "enc.blocks.%d.conv.depthwise.weight", i); + GL(b.conv_pw2_w, "enc.blocks.%d.conv.pointwise2.weight", i); + GL(b.conv_bn_w, "enc.blocks.%d.conv.bn.weight", i); + GL(b.conv_bn_b, "enc.blocks.%d.conv.bn.bias", i); + GL(b.conv_bn_rm, "enc.blocks.%d.conv.bn.running_mean", i); + GL(b.conv_bn_rv, "enc.blocks.%d.conv.bn.running_var", i); + GL(b.norm_ff2_w, "enc.blocks.%d.norm_ff2.weight", i); + GL(b.norm_ff2_b, "enc.blocks.%d.norm_ff2.bias", i); + GL(b.ff2_lin1_w, "enc.blocks.%d.ff2.linear1.weight", i); + GL(b.ff2_lin2_w, "enc.blocks.%d.ff2.linear2.weight", i); + GL(b.norm_out_w, "enc.blocks.%d.norm_out.weight", i); + GL(b.norm_out_b, "enc.blocks.%d.norm_out.bias", i); + // use_bias=true: linear + conv biases (linear_pos stays bias-free). + GL(b.ff1_lin1_b, "enc.blocks.%d.ff1.linear1.bias", i); + GL(b.ff1_lin2_b, "enc.blocks.%d.ff1.linear2.bias", i); + GL(b.attn_q_b, "enc.blocks.%d.attn.linear_q.bias", i); + GL(b.attn_k_b, "enc.blocks.%d.attn.linear_k.bias", i); + GL(b.attn_v_b, "enc.blocks.%d.attn.linear_v.bias", i); + GL(b.attn_out_b, "enc.blocks.%d.attn.linear_out.bias", i); + GL(b.conv_pw1_b, "enc.blocks.%d.conv.pointwise1.bias", i); + GL(b.conv_dw_b, "enc.blocks.%d.conv.depthwise.bias", i); + GL(b.conv_pw2_b, "enc.blocks.%d.conv.pointwise2.bias", i); + GL(b.ff2_lin1_b, "enc.blocks.%d.ff2.linear1.bias", i); + GL(b.ff2_lin2_b, "enc.blocks.%d.ff2.linear2.bias", i); + } +#undef G +#undef GL + return TRANSCRIBE_OK; +} + +// Fuse the conformer BatchNorm into scale + bias tensors (parakeet's +// build_encoder_graph consumes conv_bn_fused_scale/bias, not the raw BN). +transcribe_status fuse_conformer_bn(SortformerModel & m) { + const size_t n_blocks = m.conformer.blocks.size(); + if (n_blocks == 0) { + return TRANSCRIBE_OK; + } + const int64_t d = m.conformer_hp.enc_d_model; + const size_t tensor_bytes = static_cast(d) * sizeof(float); + + const size_t ctx_size = n_blocks * 2 * ggml_tensor_overhead() + 256; + ggml_init_params params = { ctx_size, nullptr, /*no_alloc=*/true }; + m.bn_fused_ctx = ggml_init(params); + if (m.bn_fused_ctx == nullptr) { + return TRANSCRIBE_ERR_BACKEND; + } + for (size_t i = 0; i < n_blocks; ++i) { + auto & b = m.conformer.blocks[i]; + b.conv_bn_fused_scale = ggml_new_tensor_1d(m.bn_fused_ctx, GGML_TYPE_F32, d); + b.conv_bn_fused_bias = ggml_new_tensor_1d(m.bn_fused_ctx, GGML_TYPE_F32, d); + } + m.bn_fused_buffer = ggml_backend_alloc_ctx_tensors(m.bn_fused_ctx, m.plan.scheduler_list.back()); + if (m.bn_fused_buffer == nullptr) { + return TRANSCRIBE_ERR_BACKEND; + } + std::vector bn_w(d), bn_b(d), rm(d), rv(d), fused_s(d), fused_b(d); + for (size_t i = 0; i < n_blocks; ++i) { + auto & b = m.conformer.blocks[i]; + ggml_backend_tensor_get(b.conv_bn_w, bn_w.data(), 0, tensor_bytes); + ggml_backend_tensor_get(b.conv_bn_b, bn_b.data(), 0, tensor_bytes); + ggml_backend_tensor_get(b.conv_bn_rm, rm.data(), 0, tensor_bytes); + ggml_backend_tensor_get(b.conv_bn_rv, rv.data(), 0, tensor_bytes); + for (int64_t c = 0; c < d; ++c) { + const float s = bn_w[c] / std::sqrt(rv[c] + kBnEps); + fused_s[c] = s; + fused_b[c] = bn_b[c] - rm[c] * s; + } + ggml_backend_tensor_set(b.conv_bn_fused_scale, fused_s.data(), 0, tensor_bytes); + ggml_backend_tensor_set(b.conv_bn_fused_bias, fused_b.data(), 0, tensor_bytes); + } + return TRANSCRIBE_OK; +} + +// ---- diar-head graph helpers ---- + +ggml_tensor * layer_norm(ggml_context * ctx, ggml_tensor * x, ggml_tensor * w, ggml_tensor * b) { + x = ggml_norm(ctx, x, 1e-5f); + x = ggml_mul(ctx, x, w); + x = ggml_add(ctx, x, b); + return x; +} + +ggml_tensor * linear(ggml_context * ctx, ggml_tensor * W, ggml_tensor * x, ggml_tensor * b) { + ggml_tensor * y = ggml_mul_mat(ctx, W, x); + if (b != nullptr) { + y = ggml_add(ctx, y, b); + } + return y; +} + +// One post-LN Transformer encoder block (no positional encoding, full +// attention). x ne = [d, T]. +ggml_tensor * tf_block(ggml_context * ctx, const SortformerTfBlock & b, ggml_tensor * x, int d, int n_head, int T) { + const int head_dim = d / n_head; + const float attn_scale = std::sqrt(std::sqrt(static_cast(head_dim))); + const float inv_scale = 1.0f / attn_scale; + + ggml_tensor * residual = x; + + ggml_tensor * q = linear(ctx, b.attn_q_w, x, b.attn_q_b); + ggml_tensor * k = linear(ctx, b.attn_k_w, x, b.attn_k_b); + ggml_tensor * v = linear(ctx, b.attn_v_w, x, b.attn_v_b); + + q = ggml_cont(ctx, ggml_permute(ctx, ggml_reshape_3d(ctx, q, head_dim, n_head, T), 0, 2, 1, 3)); // [hd,T,H] + k = ggml_cont(ctx, ggml_permute(ctx, ggml_reshape_3d(ctx, k, head_dim, n_head, T), 0, 2, 1, 3)); // [hd,T,H] + v = ggml_cont(ctx, ggml_permute(ctx, ggml_reshape_3d(ctx, v, head_dim, n_head, T), 0, 2, 1, 3)); // [hd,T,H] + + // NeMo pre-divides q and k by sqrt(sqrt(head_dim)); scores = q.k^T / sqrt(head_dim). + q = ggml_scale(ctx, q, inv_scale); + k = ggml_scale(ctx, k, inv_scale); + + ggml_tensor * kq = ggml_mul_mat(ctx, k, q); // [T_k, T_q, H] + kq = ggml_soft_max(ctx, kq); // softmax over keys (ne0) + + ggml_tensor * v_t = ggml_cont(ctx, ggml_permute(ctx, v, 1, 0, 2, 3)); // [T, hd, H] + ggml_tensor * ctx_out = ggml_mul_mat(ctx, v_t, kq); // [hd, T_q, H] + ctx_out = ggml_cont(ctx, ggml_permute(ctx, ctx_out, 0, 2, 1, 3)); // [hd, H, T] + ctx_out = ggml_reshape_2d(ctx, ctx_out, d, T); // [d, T] + + ggml_tensor * attn_out = linear(ctx, b.attn_o_w, ctx_out, b.attn_o_b); + + // residual + LN1 (post-LN) + x = ggml_add(ctx, attn_out, residual); + x = layer_norm(ctx, x, b.norm1_w, b.norm1_b); + + // FFN: dense_in -> relu -> dense_out, residual + LN2 + ggml_tensor * res2 = x; + ggml_tensor * f = linear(ctx, b.ff_in_w, x, b.ff_in_b); + f = ggml_relu(ctx, f); + f = linear(ctx, b.ff_out_w, f, b.ff_out_b); + x = ggml_add(ctx, f, res2); + x = layer_norm(ctx, x, b.norm2_w, b.norm2_b); + return x; +} + +// ---- Streaming two-phase graph helpers ---- +// +// The FastConformer is reused via the family-agnostic conf:: primitives so +// the streaming path can split pre_encode (Graph A) from the block stack +// (Graph B) without touching parakeet. sf_*_view mirror the static +// to_view() helpers in parakeet/encoder.cpp; sf_conv_policy mirrors the +// policy build in build_encoder_graph (offline subsample, no causal). + +conf::ConvPolicy sf_conv_policy(const char * backend) { + conf::ConvPolicy policy{}; + policy.direct_pw = conf::detect_direct_pw(backend); + // Parakeet block/pre_encode depthwise defaults (encoder.cpp:50-66). + policy.direct_dw_in_block = conf::resolve_conv_direct("TRANSCRIBE_CONV_DIRECT_DW", "TRANSCRIBE_CONV_NO_DIRECT_DW", + /*backend_default=*/true); + const bool is_metal = backend != nullptr && + (std::strstr(backend, "Metal") != nullptr || std::strstr(backend, "metal") != nullptr); + policy.direct_dw_in_pre_encode = conf::resolve_conv_direct( + "TRANSCRIBE_CONV_DIRECT_DW", "TRANSCRIBE_CONV_NO_DIRECT_DW", /*backend_default=*/!is_metal); + policy.causal_pre_encode = false; // NEST offline subsample (Regular att) + return policy; +} + +conf::PreEncodeView sf_pre_view(const pk::ParakeetPreEncode & pe) { + conf::PreEncodeView v; + v.conv0_w = pe.conv0_w; v.conv0_b = pe.conv0_b; + v.conv2_w = pe.conv2_w; v.conv2_b = pe.conv2_b; + v.conv3_w = pe.conv3_w; v.conv3_b = pe.conv3_b; + v.conv5_w = pe.conv5_w; v.conv5_b = pe.conv5_b; + v.conv6_w = pe.conv6_w; v.conv6_b = pe.conv6_b; + v.out_w = pe.out_w; v.out_b = pe.out_b; + return v; +} + +conf::BlockView sf_block_view(const pk::ParakeetBlock & b) { + conf::BlockView v; + v.norm_ff1_w = b.norm_ff1_w; v.norm_ff1_b = b.norm_ff1_b; + v.ff1_lin1_w = b.ff1_lin1_w; v.ff1_lin1_b = b.ff1_lin1_b; + v.ff1_lin2_w = b.ff1_lin2_w; v.ff1_lin2_b = b.ff1_lin2_b; + v.norm_attn_w = b.norm_attn_w; v.norm_attn_b = b.norm_attn_b; + v.attn_q_w = b.attn_q_w; v.attn_q_b = b.attn_q_b; + v.attn_k_w = b.attn_k_w; v.attn_k_b = b.attn_k_b; + v.attn_v_w = b.attn_v_w; v.attn_v_b = b.attn_v_b; + v.attn_out_w = b.attn_out_w; v.attn_out_b = b.attn_out_b; + v.attn_pos_w = b.attn_pos_w; v.attn_pos_u = b.attn_pos_u; v.attn_pos_v = b.attn_pos_v; + v.norm_conv_w = b.norm_conv_w; v.norm_conv_b = b.norm_conv_b; + v.conv_pw1_w = b.conv_pw1_w; v.conv_pw1_b = b.conv_pw1_b; + v.conv_dw_w = b.conv_dw_w; v.conv_dw_b = b.conv_dw_b; + v.conv_pw2_w = b.conv_pw2_w; v.conv_pw2_b = b.conv_pw2_b; + v.conv_bn_fused_scale = b.conv_bn_fused_scale; v.conv_bn_fused_bias = b.conv_bn_fused_bias; + v.conv_ln_w = b.conv_bn_w; v.conv_ln_b = b.conv_bn_b; + v.norm_ff2_w = b.norm_ff2_w; v.norm_ff2_b = b.norm_ff2_b; + v.ff2_lin1_w = b.ff2_lin1_w; v.ff2_lin1_b = b.ff2_lin1_b; + v.ff2_lin2_w = b.ff2_lin2_w; v.ff2_lin2_b = b.ff2_lin2_b; + v.norm_out_w = b.norm_out_w; v.norm_out_b = b.norm_out_b; + return v; +} + +// Host sinusoidal rel-pos table [pos_len, d_model] (same as the offline +// builder in run(); factored for per-chunk reuse in the streaming path). +void fill_rel_pos_emb(std::vector & buf, std::vector & div_term, int pos_len, int d_model) { + const int zero_index = (pos_len - 1) / 2; + const float ln_10000 = std::log(10000.0f); + buf.assign(static_cast(pos_len) * d_model, 0.0f); + div_term.resize(static_cast(d_model / 2)); + for (int kk = 0; kk < d_model / 2; ++kk) { + div_term[static_cast(kk)] = + std::exp(static_cast(2 * kk) * (-ln_10000 / static_cast(d_model))); + } + for (int i = 0; i < pos_len; ++i) { + const float pos = static_cast(zero_index - i); + float * row = buf.data() + static_cast(i) * d_model; + for (int kk = 0; kk < d_model / 2; ++kk) { + const float div = div_term[static_cast(kk)]; + row[2 * kk] = std::sin(pos * div); + row[2 * kk + 1] = std::cos(pos * div); + } + } +} + +// Graph A: pre_encode over one mel window [n_mels, M] -> [enc_d_model, T_diar]. +struct PreEncodeBuild { + ggml_cgraph * graph = nullptr; + ggml_tensor * mel_in = nullptr; + ggml_tensor * out = nullptr; +}; + +PreEncodeBuild build_pre_encode_graph(ggml_context * ctx, SortformerModel & m, int M) { + PreEncodeBuild b{}; + const int n_mels = m.conformer_hp.fe_num_mels; + ggml_tensor * mel_in = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, M, n_mels, 1, 1); + ggml_set_name(mel_in, "chunk.mel.in"); + ggml_set_input(mel_in); + const conf::ConvPolicy policy = sf_conv_policy(m.backend.c_str()); + ggml_tensor * out = conf::build_pre_encode(ctx, sf_pre_view(m.conformer.pre_encode), mel_in, policy, + /*name_prefix=*/"chunk.pre_encode", /*error_tag=*/"sortformer", nullptr); + if (out == nullptr) { + return b; + } + ggml_cgraph * graph = ggml_new_graph_custom(ctx, 8192, false); + ggml_build_forward_expand(graph, out); + b.graph = graph; + b.mel_in = mel_in; + b.out = out; + return b; +} + +// Graph B: [enc_d_model, T_concat] concat -> xscale -> 17 conformer blocks -> +// encoder_proj -> 18 post-LN transformer -> diar head -> preds [n_spk, T_concat]. +struct StreamInferBuild { + ggml_cgraph * graph = nullptr; + ggml_tensor * concat_in = nullptr; + ggml_tensor * pos_emb_in = nullptr; + ggml_tensor * preds = nullptr; +}; + +StreamInferBuild build_stream_infer_graph(ggml_context * ctx, SortformerModel & m, int T_concat) { + StreamInferBuild b{}; + const int ed = m.conformer_hp.enc_d_model; + ggml_tensor * concat_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, ed, T_concat); + ggml_set_name(concat_in, "stream.concat.in"); + ggml_set_input(concat_in); + + // xscaling (NEST FastConformer, applied on the concat, not stored in cache). + ggml_tensor * x = ggml_scale(ctx, concat_in, std::sqrt(static_cast(ed))); + + const int pos_len = 2 * T_concat - 1; + ggml_tensor * pos_emb_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, ed, pos_len); + ggml_set_name(pos_emb_in, "stream.pos_emb.in"); + ggml_set_input(pos_emb_in); + + conf::BlockParams bp{}; + bp.d_model = ed; + bp.n_head = m.conformer_hp.enc_n_heads; + bp.conv_kernel = m.conformer_hp.enc_conv_kernel; + bp.kv_type = GGML_TYPE_F32; + bool enc_use_flash = true, dec_use_flash = false; + transcribe::flash::apply_env_overrides(enc_use_flash, dec_use_flash); + bp.use_flash = enc_use_flash; + bp.policy = sf_conv_policy(m.backend.c_str()); + bp.att_context_left = -1; // full (offline) attention over the concat + bp.att_context_right = -1; + bp.att_context_style = conf::BlockParams::AttContextStyle::Regular; + bp.conv_context_left = -1; + bp.conv_context_right = -1; + bp.conv_norm_type = conf::BlockParams::ConvNormType::BatchNorm; + + for (int i = 0; i < m.conformer_hp.enc_n_layers; ++i) { + x = conf::build_conformer_block(ctx, x, pos_emb_in, sf_block_view(m.conformer.blocks[static_cast(i)]), + bp); + } + + // encoder_proj (enc_d_model -> tf_d_model) then 18x post-LN transformer. + ggml_tensor * t = linear(ctx, m.weights.enc_proj_w, x, m.weights.enc_proj_b); + const int d = m.hparams.tf_d_model; + for (int i = 0; i < m.hparams.tf_n_layers; ++i) { + t = tf_block(ctx, m.weights.tf_blocks[static_cast(i)], t, d, m.hparams.tf_n_heads, T_concat); + } + + // Diar head (forward_speaker_sigmoids). All frames valid in sync mode, so + // the encoder_mask multiply is a no-op. + ggml_tensor * h = ggml_relu(ctx, t); + h = linear(ctx, m.weights.fc1_w, h, m.weights.fc1_b); + h = ggml_relu(ctx, h); + ggml_tensor * s = linear(ctx, m.weights.single_spk_head_w, h, m.weights.single_spk_head_b); + ggml_tensor * preds = ggml_sigmoid(ctx, s); // [n_spk, T_concat] + + ggml_cgraph * graph = ggml_new_graph_custom(ctx, 8192, false); + ggml_build_forward_expand(graph, preds); + b.graph = graph; + b.concat_in = concat_in; + b.pos_emb_in = pos_emb_in; + b.preds = preds; + return b; +} + +} // namespace + +transcribe_status load(Loader & loader, const transcribe_model_load_params * params, transcribe_model ** out_model) { + const int64_t t_load_start = ggml_time_us(); + + auto m = std::make_unique(); + m->arch = &arch; + m->t_load_us = 0; + + m->variant = loader.variant().empty() ? k_default_variant : loader.variant(); + m->backend.clear(); + + apply_family_invariants(*m); + m->caps.n_languages = 0; + m->caps.languages = nullptr; + + if (const transcribe_status st = read_capability_kv(loader.gguf(), m->caps); st != TRANSCRIBE_OK) { + return st; + } + if (const transcribe_status st = read_languages_kv(loader.gguf(), *m); st != TRANSCRIBE_OK) { + return st; + } + if (const transcribe_status st = read_sortformer_hparams(loader.gguf(), m->hparams); st != TRANSCRIBE_OK) { + return st; + } + fill_conformer_hp(m->hparams, m->conformer_hp); + + // Mel front-end (NeMo AudioToMelSpectrogramPreprocessor; normalize=none). + { + transcribe::MelConfig cfg{}; + cfg.sample_rate = m->hparams.fe_sample_rate; + cfg.num_mels = m->hparams.fe_num_mels; + cfg.n_fft = m->hparams.fe_n_fft; + cfg.win_length = m->hparams.fe_win_length; + cfg.hop_length = m->hparams.fe_hop_length; + cfg.pre_emphasis = m->hparams.fe_pre_emphasis; + cfg.normalize = m->hparams.fe_normalize; + cfg.pad_mode = "constant"; + // NeMo AudioToMelSpectrogramPreprocessor uses ceil(n/hop) framing; + // the mel tensor is pad_to=16 padded but forward() trims it back to + // the real length before the encoder, so feeding the ceil length is + // the faithful path (see forward-map Frontend note). + cfg.nemo_seq_len_ceil = true; + m->mel.emplace(cfg); + } + + gguf_init_params init_params{}; + init_params.no_alloc = true; + init_params.ctx = &m->ctx_meta; + gguf_context * gguf_data = gguf_init_from_file(loader.path().c_str(), init_params); + if (gguf_data == nullptr) { + return TRANSCRIBE_ERR_GGUF; + } + + if (const transcribe_status st = load_conformer_weights(m->ctx_meta, m->conformer_hp, m->conformer); + st != TRANSCRIBE_OK) { + gguf_free(gguf_data); + return st; + } + if (const transcribe_status st = build_sortformer_weights(m->ctx_meta, m->hparams, m->weights); + st != TRANSCRIBE_OK) { + gguf_free(gguf_data); + return st; + } + + const transcribe_backend_request backend_req = (params != nullptr) ? params->backend : TRANSCRIBE_BACKEND_AUTO; + if (const transcribe_status st = transcribe::load_common::init_backends( + backend_req, (params != nullptr) ? params->gpu_device : 0, "sortformer", m->plan); + st != TRANSCRIBE_OK) { + gguf_free(gguf_data); + return st; + } + m->backend = ggml_backend_name(m->plan.primary); + m->primary_backend = m->plan.primary; + + ggml_backend_buffer_t weights_buffer = ggml_backend_alloc_ctx_tensors(m->ctx_meta, m->plan.primary); + if (weights_buffer == nullptr) { + gguf_free(gguf_data); + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: ggml_backend_alloc_ctx_tensors failed"); + return TRANSCRIBE_ERR_GGUF; + } + m->backend_buffer = weights_buffer; + ggml_backend_buffer_set_usage(weights_buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS); + + if (const transcribe_status st = + transcribe::load_common::stream_tensor_data(loader.path(), gguf_data, m->ctx_meta, "sortformer"); + st != TRANSCRIBE_OK) { + gguf_free(gguf_data); + return st; + } + gguf_free(gguf_data); + + if (const transcribe_status st = fuse_conformer_bn(*m); st != TRANSCRIBE_OK) { + return st; + } + + m->t_load_us = ggml_time_us() - t_load_start; + *out_model = m.release(); + return TRANSCRIBE_OK; +} + +transcribe_status init_context(transcribe_model * model, const transcribe_session_params * params, + transcribe_session ** out_ctx) { + if (model->arch != &arch) { + return TRANSCRIBE_ERR_INVALID_ARG; + } + auto pc = std::make_unique(); + pc->model = model; + pc->n_threads = params->n_threads; + pc->kv_type = params->kv_type; + *out_ctx = pc.release(); + return TRANSCRIBE_OK; +} + +// Threshold-based probs -> speaker segments (simple offline segmentation; +// the DER-grade dihard3 post-processing is a later, streaming-checkpoint +// concern). probs is row-major [T, n_spk]. Emits one contiguous run per +// speaker as a speaker_segment row. +static void probs_to_speaker_segments(transcribe_session * pc, + const std::vector & probs, + int T, + int n_spk, + double ms_per_frame, + float threshold) { + for (int s = 0; s < n_spk; ++s) { + int run_start = -1; + for (int t = 0; t <= T; ++t) { + const bool active = (t < T) && (probs[static_cast(t) * n_spk + s] > threshold); + if (active && run_start < 0) { + run_start = t; + } else if (!active && run_start >= 0) { + transcribe_session::SpeakerSegmentEntry row; + row.t0_ms = static_cast(std::llround(run_start * ms_per_frame)); + row.t1_ms = static_cast(std::llround(t * ms_per_frame)); + row.speaker_id = s + 1; // 1-based + row.p = std::numeric_limits::quiet_NaN(); + pc->speaker_segments.push_back(row); + run_start = -1; + } + } + } +} + +// Lazily create the persistent multi-backend scheduler shared by the offline +// and streaming graphs. +static transcribe_status ensure_sched(SortformerSession * pc, SortformerModel * pm) { + if (pc->sched == nullptr) { + pc->sched = ggml_backend_sched_new(pm->plan.scheduler_list.data(), nullptr, + static_cast(pm->plan.scheduler_list.size()), + /*graph_size=*/8192, /*parallel=*/false, /*op_offload=*/true); + if (pc->sched == nullptr) { + return TRANSCRIBE_ERR_GGUF; + } + } + return TRANSCRIBE_OK; +} + +// Offline full-context forward. Builds the whole encoder + transformer + diar +// head over the entire mel and dumps the Stage-4 parity tensors (enc.* + +// diar.preds_offline). Invoked only when tensor dumping is active (the +// streaming path is the product); keeps the offline tensor gate green. +static transcribe_status run_offline_forward(SortformerSession * pc, SortformerModel * pm, int mel_n_frames) { + if (pc->compute_ctx != nullptr) { + ggml_free(pc->compute_ctx); + pc->compute_ctx = nullptr; + } + { + ggml_init_params ip{}; + ip.mem_size = 16 * 1024 * 1024; + ip.mem_buffer = nullptr; + ip.no_alloc = true; + pc->compute_ctx = ggml_init(ip); + if (pc->compute_ctx == nullptr) { + return TRANSCRIBE_ERR_GGUF; + } + } + ggml_context * ctx = pc->compute_ctx; + + pk::EncoderBuild eb = + pk::build_encoder_graph(ctx, pm->conformer, pm->conformer_hp, mel_n_frames, GGML_TYPE_F32, pm->backend.c_str()); + if (eb.mel_in == nullptr || eb.out == nullptr || eb.graph == nullptr) { + return TRANSCRIBE_ERR_GGUF; + } + const int T = static_cast(eb.out->ne[1]); + const int d = pm->hparams.tf_d_model; + + ggml_tensor * proj = linear(ctx, pm->weights.enc_proj_w, eb.out, pm->weights.enc_proj_b); + ggml_tensor * x = proj; + for (int i = 0; i < pm->hparams.tf_n_layers; ++i) { + x = tf_block(ctx, pm->weights.tf_blocks[static_cast(i)], x, d, pm->hparams.tf_n_heads, T); + } + ggml_tensor * tf_out = x; + + ggml_tensor * h = ggml_relu(ctx, tf_out); + h = linear(ctx, pm->weights.fc1_w, h, pm->weights.fc1_b); + h = ggml_relu(ctx, h); + ggml_tensor * s = linear(ctx, pm->weights.single_spk_head_w, h, pm->weights.single_spk_head_b); + ggml_tensor * preds = ggml_sigmoid(ctx, s); + + ggml_build_forward_expand(eb.graph, preds); + transcribe::debug::mark_tensor_for_dump(proj); + transcribe::debug::mark_tensor_for_dump(tf_out); + transcribe::debug::mark_tensor_for_dump(preds); + + if (const transcribe_status st = ensure_sched(pc, pm); st != TRANSCRIBE_OK) { + return st; + } + ggml_backend_sched_reset(pc->sched); + if (!ggml_backend_sched_alloc_graph(pc->sched, eb.graph)) { + return TRANSCRIBE_ERR_GGUF; + } + + ggml_backend_tensor_set(eb.mel_in, pc->mel_buf.data(), 0, pc->mel_buf.size() * sizeof(float)); + transcribe::debug::dump_tensor("enc.mel.in", eb.mel_in, "frontend"); + + if (eb.pos_emb_in != nullptr) { + const int d_model = pm->conformer_hp.enc_d_model; + const int pos_len = static_cast(eb.pos_emb_in->ne[1]); + fill_rel_pos_emb(pc->pos_buf, pc->pos_div_term, pos_len, d_model); + ggml_backend_tensor_set(eb.pos_emb_in, pc->pos_buf.data(), 0, pc->pos_buf.size() * sizeof(float)); + } + + transcribe::configure_sched_n_threads(pc->sched, pc->n_threads); + if (ggml_backend_sched_graph_compute(pc->sched, eb.graph) != GGML_STATUS_SUCCESS) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer offline forward: graph_compute failed"); + return TRANSCRIBE_ERR_GGUF; + } + + transcribe::debug::dump_tensor("enc.fastconformer.out", eb.out, "encoder"); + transcribe::debug::dump_tensor("enc.encoder_proj.out", proj, "encoder"); + transcribe::debug::dump_tensor("enc.transformer.out", tf_out, "encoder"); + transcribe::debug::dump_tensor("diar.preds_offline", preds, "encoder"); + return TRANSCRIBE_OK; +} + +// Streaming AOSC/FIFO forward (the product path). Chunks the mel per NeMo's +// streaming_feat_loader, runs Graph A (pre_encode) then Graph B (blocks + +// proj + transformer + head) over the [spkcache|fifo|chunk] concat, and drives +// the host streaming_update_sync / _compress_spkcache state machine. Emits the +// accumulated T x n_spk probs as diar.probs and the speaker segments. +static transcribe_status run_streaming(SortformerSession * pc, SortformerModel * pm, int mel_n_mels, int mel_n_frames, + double ms_per_frame, transcribe_sortformer_preset preset) { + const SortformerStreamParams P = resolve_stream_params(pm->hparams, preset); + const int ed = pm->conformer_hp.enc_d_model; + const int n_spk = pm->hparams.max_speakers; + const int sub = pm->hparams.enc_subsampling_factor; + const int feat_len = mel_n_frames; + + if (sub <= 0 || P.chunk_len <= 0) { + return TRANSCRIBE_ERR_INVALID_ARG; + } + pc->stream.reset(ed); + if (const transcribe_status st = ensure_sched(pc, pm); st != TRANSCRIBE_OK) { + return st; + } + + const int64_t t_stream_start = ggml_time_us(); + int stt = 0, end = 0, chunk_idx = 0; + while (end < feat_len) { + if (pc->poll_abort()) { + return TRANSCRIBE_ERR_ABORTED; + } + const int left_offset = std::min(P.chunk_left_context * sub, stt); + end = std::min(stt + P.chunk_len * sub, feat_len); + const int right_offset = std::min(P.chunk_right_context * sub, feat_len - end); + const int win_lo = stt - left_offset; + const int win_hi = end + right_offset; + const int M = win_hi - win_lo; + // Diar-frame context: left_offset is a multiple of sub -> round is + // exact; right_offset may be short on the final chunk -> ceil. + const int lc = (left_offset + sub / 2) / sub; + const int rc = (right_offset + sub - 1) / sub; + + if (pc->compute_ctx != nullptr) { + ggml_free(pc->compute_ctx); + pc->compute_ctx = nullptr; + } + { + ggml_init_params ip{}; + ip.mem_size = 32 * 1024 * 1024; + ip.mem_buffer = nullptr; + ip.no_alloc = true; + pc->compute_ctx = ggml_init(ip); + if (pc->compute_ctx == nullptr) { + return TRANSCRIBE_ERR_GGUF; + } + } + ggml_context * ctx = pc->compute_ctx; + transcribe::debug::push_name_prefix("stream.chunk"); + + // ---- Graph A: pre_encode over the mel window ---- + PreEncodeBuild A = build_pre_encode_graph(ctx, *pm, M); + if (A.graph == nullptr) { + transcribe::debug::pop_name_prefix(); + return TRANSCRIBE_ERR_GGUF; + } + const int T_diar = static_cast(A.out->ne[1]); + + // Mel window [n_mels, M] from full mel [n_mels, feat_len], cols + // [win_lo, win_hi). ggml ne=[M, n_mels] -> offset mel*M + col. + pc->chunk_mel_buf.resize(static_cast(mel_n_mels) * M); + for (int mel = 0; mel < mel_n_mels; ++mel) { + const float * src = pc->mel_buf.data() + static_cast(mel) * feat_len + win_lo; + std::copy(src, src + M, pc->chunk_mel_buf.data() + static_cast(mel) * M); + } + + ggml_backend_sched_reset(pc->sched); + if (!ggml_backend_sched_alloc_graph(pc->sched, A.graph)) { + transcribe::debug::pop_name_prefix(); + return TRANSCRIBE_ERR_GGUF; + } + ggml_backend_tensor_set(A.mel_in, pc->chunk_mel_buf.data(), 0, pc->chunk_mel_buf.size() * sizeof(float)); + transcribe::configure_sched_n_threads(pc->sched, pc->n_threads); + if (ggml_backend_sched_graph_compute(pc->sched, A.graph) != GGML_STATUS_SUCCESS) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer streaming: pre_encode compute failed"); + transcribe::debug::pop_name_prefix(); + return TRANSCRIBE_ERR_GGUF; + } + pc->chunk_embs_host.resize(static_cast(T_diar) * ed); + ggml_backend_tensor_get(A.out, pc->chunk_embs_host.data(), 0, pc->chunk_embs_host.size() * sizeof(float)); + + // ---- host concat [spkcache | fifo | chunk_embs] ---- + const int S = pc->stream.spkcache_n; + const int F = pc->stream.fifo_n; + const int T_concat = S + F + T_diar; + pc->concat_host.resize(static_cast(T_concat) * ed); + std::copy(pc->stream.spkcache.begin(), pc->stream.spkcache.begin() + static_cast(S) * ed, + pc->concat_host.begin()); + std::copy(pc->stream.fifo.begin(), pc->stream.fifo.begin() + static_cast(F) * ed, + pc->concat_host.begin() + static_cast(S) * ed); + std::copy(pc->chunk_embs_host.begin(), pc->chunk_embs_host.end(), + pc->concat_host.begin() + static_cast(S + F) * ed); + + // ---- Graph B: xscale + 17 blocks + proj + 18 tf + head ---- + StreamInferBuild B = build_stream_infer_graph(ctx, *pm, T_concat); + if (B.graph == nullptr) { + transcribe::debug::pop_name_prefix(); + return TRANSCRIBE_ERR_GGUF; + } + fill_rel_pos_emb(pc->pos_buf, pc->pos_div_term, 2 * T_concat - 1, ed); + + ggml_backend_sched_reset(pc->sched); + if (!ggml_backend_sched_alloc_graph(pc->sched, B.graph)) { + transcribe::debug::pop_name_prefix(); + return TRANSCRIBE_ERR_GGUF; + } + ggml_backend_tensor_set(B.concat_in, pc->concat_host.data(), 0, pc->concat_host.size() * sizeof(float)); + ggml_backend_tensor_set(B.pos_emb_in, pc->pos_buf.data(), 0, pc->pos_buf.size() * sizeof(float)); + transcribe::configure_sched_n_threads(pc->sched, pc->n_threads); + if (ggml_backend_sched_graph_compute(pc->sched, B.graph) != GGML_STATUS_SUCCESS) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer streaming: infer compute failed"); + transcribe::debug::pop_name_prefix(); + return TRANSCRIBE_ERR_GGUF; + } + pc->stream_preds_host.resize(static_cast(T_concat) * n_spk); + ggml_backend_tensor_get(B.preds, pc->stream_preds_host.data(), 0, + pc->stream_preds_host.size() * sizeof(float)); + + transcribe::debug::pop_name_prefix(); + + // ---- host streaming update (FIFO + AOSC compress) ---- + streaming_update_sync(pc->stream, P, n_spk, ed, pc->chunk_embs_host, T_diar, pc->stream_preds_host, T_concat, + lc, rc); + + stt = end; + ++chunk_idx; + } + pc->t_encode_us = ggml_time_us() - t_stream_start; + + // Trim padding tail: NeMo total_preds[:, :ceil(feat_len/sub)]. + const int n_frames = (feat_len + sub - 1) / sub; + const int used_n = std::min(pc->stream.total_n, n_frames); + + if (transcribe::debug::enabled()) { + const long long shape[2] = { used_n, n_spk }; + transcribe::debug::dump_host_f32("diar.probs", pc->stream.total_preds.data(), + static_cast(used_n) * n_spk, shape, 2, "diarize"); + } + + probs_to_speaker_segments(pc, pc->stream.total_preds, used_n, n_spk, ms_per_frame, /*threshold=*/0.5f); + (void) chunk_idx; + return TRANSCRIBE_OK; +} + +transcribe_status run(transcribe_session * session, const float * pcm, int n_samples, + const transcribe_run_params * params) { + auto * pc = static_cast(session); + auto * pm = static_cast(session->model); + + if (pc->poll_abort()) { + return TRANSCRIBE_ERR_ABORTED; + } + + // Streaming operating-point run ext (kind/size/range already validated + // by the dispatcher + run_validate pre-clear; re-check is belt-and-braces). + transcribe_sortformer_preset preset = TRANSCRIBE_SORTFORMER_PRESET_DEFAULT; + if (params != nullptr && params->family != nullptr) { + if (const transcribe_status st = transcribe_ext_check(params->family, TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM, + sizeof(struct transcribe_sortformer_stream_ext)); + st != TRANSCRIBE_OK) { + return st; + } + preset = reinterpret_cast(params->family)->preset; + } + pc->clear_result(); + transcribe::debug::init(); + + if (!pm->mel.has_value()) { + return TRANSCRIBE_ERR_INVALID_ARG; + } + int mel_n_mels = 0, mel_n_frames = 0; + if (const transcribe_status mst = + pm->mel->compute(pcm, static_cast(n_samples), pc->mel_buf, mel_n_mels, mel_n_frames); + mst != TRANSCRIBE_OK) { + return mst; + } + + const double ms_per_frame = 1000.0 * static_cast(pm->hparams.frame_hop) / + static_cast(pm->hparams.fe_sample_rate); + + // Offline forward: parity dumps only. Gated behind an explicit env + // (set by validate.py cmd_cpp) because it runs full-context O(T^2) + // attention over the WHOLE clip — fine for the short oracle, but it would + // OOM on the many-minute audio the DER runner feeds (which also enables + // dumping, to read diar.probs). The streaming path is always the product. + if (transcribe::debug::enabled() && std::getenv("TRANSCRIBE_SORTFORMER_OFFLINE_DUMP") != nullptr) { + if (const transcribe_status st = run_offline_forward(pc, pm, mel_n_frames); st != TRANSCRIBE_OK) { + return st; + } + } + + // Streaming AOSC/FIFO forward drives the product output + diar.probs. + if (const transcribe_status st = run_streaming(pc, pm, mel_n_mels, mel_n_frames, ms_per_frame, preset); + st != TRANSCRIBE_OK) { + return st; + } + + pc->result_kind = TRANSCRIBE_TIMESTAMPS_NONE; + pc->has_result = true; + return TRANSCRIBE_OK; +} + +// Kind+slot probe. Sortformer ships one RUN-slot extension (the streaming +// operating-point preset); there is no STREAM-slot surface (no push-audio +// entry point yet — a future one registers a separate kind). +static bool accepts_ext_kind(const transcribe_model * model, transcribe_ext_slot slot, uint32_t kind) { + if (model == nullptr) { + return false; + } + if (slot != TRANSCRIBE_EXT_SLOT_RUN) { + return false; + } + return kind == TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM; +} + +// Pre-clear validation for the _RUN slot (see Arch::run_validate): reject a +// malformed ext or an out-of-range preset before the previous result +// snapshot is destroyed. +static transcribe_status run_validate(const transcribe_session * /*ctx*/, const transcribe_run_params * params) { + if (params == nullptr || params->family == nullptr) { + return TRANSCRIBE_OK; // NULL ext -> family defaults + } + if (const transcribe_status st = transcribe_ext_check(params->family, TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM, + sizeof(struct transcribe_sortformer_stream_ext)); + st != TRANSCRIBE_OK) { + return st; + } + const auto * ext = reinterpret_cast(params->family); + switch (ext->preset) { + case TRANSCRIBE_SORTFORMER_PRESET_DEFAULT: + case TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY: + case TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY: + case TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY: + return TRANSCRIBE_OK; + } + return TRANSCRIBE_ERR_INVALID_ARG; +} + +extern const Arch arch = { + /* .name = */ "sortformer", + /* .load = */ load, + /* .init_context = */ init_context, + /* .run = */ run, + /* .run_batch = */ nullptr, + /* .stream_validate = */ nullptr, + /* .stream_begin = */ nullptr, + /* .stream_feed = */ nullptr, + /* .stream_finalize = */ nullptr, + /* .stream_reset = */ nullptr, + /* .accepts_ext_kind = */ accepts_ext_kind, + /* .run_validate = */ run_validate, +}; + +} // namespace transcribe::sortformer + +// --------------------------------------------------------------------------- +// Public sortformer extension init function (global scope, C linkage). +// Defined here so transcribe.cpp stays family-agnostic; stamps the +// transcribe_ext header (size + kind) and the preset default. +// --------------------------------------------------------------------------- + +extern "C" void transcribe_sortformer_stream_ext_init(struct transcribe_sortformer_stream_ext * p) { + if (p == nullptr) { + return; + } + std::memset(p, 0, sizeof(*p)); + p->ext.size = sizeof(*p); + p->ext.kind = TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM; + p->preset = TRANSCRIBE_SORTFORMER_PRESET_DEFAULT; +} diff --git a/src/arch/sortformer/sortformer.h b/src/arch/sortformer/sortformer.h new file mode 100644 index 00000000..33e2cebf --- /dev/null +++ b/src/arch/sortformer/sortformer.h @@ -0,0 +1,169 @@ +// arch/sortformer/sortformer.h - Sortformer-family internal model and +// context types. Sortformer is an `encoder-diarizer`: a NEST/FastConformer +// encoder (the same NeMo ConformerEncoder Parakeet ports, reused verbatim +// via parakeet::build_encoder_graph) followed by a Linear projection, an +// 18-layer post-LN Transformer encoder, and a 4-way sigmoid speaker head. +// No tokenizer, no decoder, no text: the product is a T x 4 per-frame +// speaker-activity probability matrix. +// +// The central dispatcher talks to the family only through the Arch trait +// and the base classes. + +#pragma once + +#include "../parakeet/encoder.h" // build_encoder_graph +#include "../parakeet/weights.h" // ParakeetHParams / ParakeetWeights (conformer) +#include "transcribe/sortformer.h" // public preset enum + run ext +#include "transcribe-backend.h" +#include "transcribe-mel.h" +#include "transcribe-model.h" +#include "transcribe-session.h" +#include "weights.h" + +#include +#include +#include +#include + +struct ggml_context; +struct ggml_tensor; +struct ggml_backend_buffer; +struct ggml_backend_sched; +typedef struct ggml_backend_buffer * ggml_backend_buffer_t; +typedef struct ggml_backend_sched * ggml_backend_sched_t; + +namespace transcribe::sortformer { + +// Family defaults, applied before transcribe::read_capability_kv (KV +// present overrides, KV absent keeps the default). Defined in +// capabilities.cpp. +void apply_family_invariants(transcribe_model & model); + +// Concrete model. Owns ctx_meta (every weight tensor's data buffer); +// the destructor frees it, invalidating every borrowed ggml_tensor* in +// `conformer` / `weights`. +struct SortformerModel final : public transcribe_model { + SortformerHParams hparams; + + // Conformer encoder reuse. `conformer_hp` carries only the encoder + + // frontend fields parakeet::build_encoder_graph reads; `conformer` + // holds only pre_encode + blocks (predictor/joint/etc. stay empty). + transcribe::parakeet::ParakeetHParams conformer_hp; + transcribe::parakeet::ParakeetWeights conformer; + + // Sortformer-specific weights (encoder_proj + transformer + diar head). + SortformerWeights weights; + + ggml_context * ctx_meta = nullptr; + transcribe::BackendPlan plan; + ggml_backend_buffer_t backend_buffer = nullptr; + + // Fused conformer BN params (computed at load from the raw BN tensors). + ggml_context * bn_fused_ctx = nullptr; + ggml_backend_buffer_t bn_fused_buffer = nullptr; + + // Mel front-end, constructed once at load() from hparams. + std::optional mel; + + SortformerModel() = default; + ~SortformerModel() override; + + // No tokenizer for a diarizer. + const transcribe::Tokenizer * tokenizer() const override { return nullptr; } +}; + +// Streaming operating point (resolved from GGUF stream defaults; a later +// change will expose the presets via a stream extension). All lengths are +// in 80 ms diar frames. +struct SortformerStreamParams { + int chunk_len = 188; + int chunk_left_context = 1; + int chunk_right_context = 1; + int fifo_len = 0; + int spkcache_len = 188; + int spkcache_update_period = 188; + int spkcache_sil_frames_per_spk = 3; + float sil_threshold = 0.2f; + float pred_score_threshold = 0.25f; + float scores_boost_latest = 0.05f; + float strong_boost_rate = 0.75f; + float weak_boost_rate = 1.5f; + float min_pos_scores_rate = 0.5f; + int max_index = 99999; +}; + +// Host-side streaming state (AOSC speaker cache + FIFO), mirroring NeMo's +// StreamingSortformerState. Embeddings are 512-dim pre_encode outputs stored +// row-major [n_frames, enc_d_model]; preds are [n_frames, n_spk]. +struct SortformerStreamState { + std::vector spkcache; // [spkcache_n * D] + std::vector spkcache_preds; // [spkcache_n * S] + int spkcache_n = 0; + bool spkcache_preds_init = false; // NeMo: spkcache_preds is None until first compress + std::vector fifo; // [fifo_n * D] + std::vector fifo_preds; // [fifo_n * S] + int fifo_n = 0; + std::vector mean_sil_emb; // [D] + int64_t n_sil_frames = 0; + std::vector total_preds; // [T_total * S], accumulated chunk preds + int total_n = 0; + int compress_count = 0; // # _compress_spkcache calls (parity-dump index) + + void reset(int emb_dim) { + spkcache.clear(); + spkcache_preds.clear(); + spkcache_n = 0; + spkcache_preds_init = false; + fifo.clear(); + fifo_preds.clear(); + fifo_n = 0; + mean_sil_emb.assign(static_cast(emb_dim), 0.0f); + n_sil_frames = 0; + total_preds.clear(); + total_n = 0; + compress_count = 0; + } +}; + +// Resolve the streaming operating point, lowest to highest precedence: +// GGUF-shipped stream cfg < public run-ext preset (transcribe_sortformer_ +// stream_ext; DEFAULT keeps the GGUF cfg) < TRANSCRIBE_SORTFORMER_STREAM_ +// PRESET env (very_high_latency / high_latency / low_latency / small; +// validation hook) < per-field env overrides. Defined in stream.cpp. +SortformerStreamParams resolve_stream_params(const SortformerHParams & hp, transcribe_sortformer_preset preset); + +// Exact host ports of the NeMo sync-streaming primitives (batch=1). All +// embeddings row-major [n_frames, emb_dim]; preds row-major [n_frames, n_spk]. +// Defined in stream.cpp. +void streaming_update_sync(SortformerStreamState & st, const SortformerStreamParams & p, int n_spk, int emb_dim, + const std::vector & chunk_embs, int T_diar, + const std::vector & preds, int T_concat, int lc, int rc); + +// Compress st.spkcache (spkcache_n > spkcache_len frames) in place down to +// spkcache_len frames, matching sortformer_modules._compress_spkcache. +void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams & p, int n_spk, int emb_dim); + +// Concrete context. Owns a per-call compute context and a persistent +// multi-backend scheduler. +struct SortformerSession final : public transcribe_session { + ggml_context * compute_ctx = nullptr; + ggml_backend_sched_t sched = nullptr; + + // Per-context scratch reused across runs. + std::vector mel_buf; + std::vector pos_buf; + std::vector pos_div_term; + std::vector probs_host; // [n_spk * T], read back from diar.preds + + // Streaming scratch (AOSC/FIFO path). + SortformerStreamState stream; + std::vector chunk_mel_buf; // [n_mels * M] mel window for Graph A + std::vector chunk_embs_host; // [T_diar * enc_d_model] pre_encode readback + std::vector concat_host; // [T_concat * enc_d_model] Graph B input + std::vector stream_preds_host; // [T_concat * n_spk] Graph B readback + + SortformerSession() = default; + ~SortformerSession() override; +}; + +} // namespace transcribe::sortformer diff --git a/src/arch/sortformer/stream.cpp b/src/arch/sortformer/stream.cpp new file mode 100644 index 00000000..9e97ab39 --- /dev/null +++ b/src/arch/sortformer/stream.cpp @@ -0,0 +1,467 @@ +// arch/sortformer/stream.cpp - Host-side sync streaming state machine for +// the Sortformer AOSC speaker-cache + FIFO path. Exact ports of NeMo's +// SortformerModules (sortformer_modules.py): streaming_update (sync branch), +// _get_silence_profile, and the exactness-critical _compress_spkcache stack +// (_get_log_pred_scores -> _disable_low_scores -> scores_boost_latest -> +// _boost_topk_scores x2 -> silence pad -> _get_topk_indices -> gather). +// +// Batch size is always 1 here (the CLI path), so every per-batch loop in the +// reference collapses and spk_perm is always None (inference, permute_spk= +// self.training=False). See reports/porting/sortformer/forward-map.md and the +// approved plan for the traced reference call graph. + +#include "sortformer.h" +#include "transcribe-debug.h" +#include "weights.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace transcribe::sortformer { + +namespace { + +constexpr float kNegInf = -std::numeric_limits::infinity(); +constexpr float kPosInf = std::numeric_limits::infinity(); + +// ---- env helpers ---- + +const char * env_get(const char * name) { + const char * v = std::getenv(name); + return (v != nullptr && v[0] != '\0') ? v : nullptr; +} + +bool env_int(const char * name, int & out) { + const char * v = env_get(name); + if (v == nullptr) { + return false; + } + char * end = nullptr; + const long parsed = std::strtol(v, &end, 10); + if (end == v) { + return false; + } + out = static_cast(parsed); + return true; +} + +// One card / validation preset. lc stays at the model default (1); only the +// chunking geometry changes. Mirrors scripts/diar/run_reference_sortformer_nemo.py +// PRESETS plus a "small" preset (forces multi-chunk + compression on the short +// oracle clip for diar.probs parity). +struct Preset { + const char * name; + int chunk_len; + int chunk_right_context; + int fifo_len; + int spkcache_update_period; + int spkcache_len; +}; + +const Preset k_presets[] = { + { "very_high_latency", 340, 40, 40, 300, 188 }, + { "high_latency", 124, 1, 124, 124, 188 }, + { "low_latency", 6, 7, 188, 144, 188 }, + { "small", 20, 1, 10, 20, 24 }, +}; + +} // namespace + +SortformerStreamParams resolve_stream_params(const SortformerHParams & hp, transcribe_sortformer_preset preset) { + SortformerStreamParams p; // score / boost params keep their (verified) defaults + // GGUF-shipped streaming cfg is the baseline. + p.chunk_len = hp.stream_chunk_len > 0 ? hp.stream_chunk_len : p.chunk_len; + p.spkcache_len = hp.stream_spkcache_len > 0 ? hp.stream_spkcache_len : p.spkcache_len; + p.fifo_len = hp.stream_fifo_len; // 0 is a valid shipped value + p.spkcache_update_period = hp.stream_spkcache_update_period > 0 ? hp.stream_spkcache_update_period + : p.spkcache_update_period; + + // Public run-ext preset (DEFAULT keeps the GGUF cfg). Range-checked by + // run_validate before the dispatcher clears the previous result. + const char * ext_name = nullptr; + switch (preset) { + case TRANSCRIBE_SORTFORMER_PRESET_DEFAULT: + break; + case TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY: + ext_name = "very_high_latency"; + break; + case TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY: + ext_name = "high_latency"; + break; + case TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY: + ext_name = "low_latency"; + break; + } + if (ext_name != nullptr) { + for (const Preset & pr : k_presets) { + if (std::string(ext_name) == pr.name) { + p.chunk_len = pr.chunk_len; + p.chunk_right_context = pr.chunk_right_context; + p.fifo_len = pr.fifo_len; + p.spkcache_update_period = pr.spkcache_update_period; + p.spkcache_len = pr.spkcache_len; + break; + } + } + } + + // Named preset env override (validation / DER operating point). + if (const char * name = env_get("TRANSCRIBE_SORTFORMER_STREAM_PRESET")) { + for (const Preset & pr : k_presets) { + if (std::string(name) == pr.name) { + p.chunk_len = pr.chunk_len; + p.chunk_right_context = pr.chunk_right_context; + p.fifo_len = pr.fifo_len; + p.spkcache_update_period = pr.spkcache_update_period; + p.spkcache_len = pr.spkcache_len; + break; + } + } + } + + // Per-field env overrides (highest precedence). + env_int("TRANSCRIBE_SORTFORMER_STREAM_CHUNK_LEN", p.chunk_len); + env_int("TRANSCRIBE_SORTFORMER_STREAM_FIFO_LEN", p.fifo_len); + env_int("TRANSCRIBE_SORTFORMER_STREAM_SPKCACHE_LEN", p.spkcache_len); + env_int("TRANSCRIBE_SORTFORMER_STREAM_UPDATE_PERIOD", p.spkcache_update_period); + env_int("TRANSCRIBE_SORTFORMER_STREAM_RC", p.chunk_right_context); + env_int("TRANSCRIBE_SORTFORMER_STREAM_LC", p.chunk_left_context); + return p; +} + +// ---- _get_silence_profile (sortformer_modules.py:636-667) ---- + +// File-local (only streaming_update_sync calls it). +static void get_silence_profile(SortformerStreamState & st, float sil_threshold, const float * pop_embs, + const float * pop_preds, int n, int n_spk, int emb_dim) { + if (n <= 0) { + return; + } + // is_sil[i] = (sum_s preds[i,s]) < sil_threshold ; sil_count = sum is_sil + std::vector is_sil(static_cast(n)); + int64_t sil_count = 0; + for (int i = 0; i < n; ++i) { + float s = 0.0f; + for (int c = 0; c < n_spk; ++c) { + s += pop_preds[static_cast(i) * n_spk + c]; + } + is_sil[static_cast(i)] = (s < sil_threshold) ? 1 : 0; + sil_count += is_sil[static_cast(i)]; + } + if (sil_count == 0) { + return; // has_new_sil false -> unchanged + } + const int64_t old_n = st.n_sil_frames; + const int64_t new_n = old_n + sil_count; + const float denom = static_cast(std::max(new_n, 1)); + for (int d = 0; d < emb_dim; ++d) { + float sil_sum = 0.0f; + for (int i = 0; i < n; ++i) { + if (is_sil[static_cast(i)]) { + sil_sum += pop_embs[static_cast(i) * emb_dim + d]; + } + } + const float old_sum = st.mean_sil_emb[static_cast(d)] * static_cast(old_n); + st.mean_sil_emb[static_cast(d)] = (old_sum + sil_sum) / denom; + } + st.n_sil_frames = new_n; +} + +namespace { + +// _get_log_pred_scores (sortformer_modules.py:669-686). scores[i,s] = +// log(clamp(p,thr)) - log(clamp(1-p,thr)) + sum_s log(clamp(1-p,thr)) - log(0.5). +std::vector get_log_pred_scores(const float * preds, int n, int n_spk, float thr) { + std::vector scores(static_cast(n) * n_spk); + const float log_half = std::log(0.5f); + for (int i = 0; i < n; ++i) { + float l1sum = 0.0f; + for (int s = 0; s < n_spk; ++s) { + const float p = preds[static_cast(i) * n_spk + s]; + l1sum += std::log(std::max(1.0f - p, thr)); + } + for (int s = 0; s < n_spk; ++s) { + const float p = preds[static_cast(i) * n_spk + s]; + const float lp = std::log(std::max(p, thr)); + const float l1 = std::log(std::max(1.0f - p, thr)); + scores[static_cast(i) * n_spk + s] = lp - l1 + l1sum - log_half; + } + } + return scores; +} + +// _disable_low_scores (sortformer_modules.py:782-808). +void disable_low_scores(const float * preds, std::vector & scores, int n, int n_spk, int min_pos) { + // Non-speech (preds <= 0.5) -> -inf. + for (int i = 0; i < n; ++i) { + for (int s = 0; s < n_spk; ++s) { + const size_t idx = static_cast(i) * n_spk + s; + if (!(preds[idx] > 0.5f)) { + scores[idx] = kNegInf; + } + } + } + // Per speaker: if it has >= min_pos positive-scored frames, disable its + // non-positive (but speech) frames. + for (int s = 0; s < n_spk; ++s) { + int pos_count = 0; + for (int i = 0; i < n; ++i) { + if (scores[static_cast(i) * n_spk + s] > 0.0f) { + ++pos_count; + } + } + if (pos_count >= min_pos) { + for (int i = 0; i < n; ++i) { + const size_t idx = static_cast(i) * n_spk + s; + const bool is_speech = preds[idx] > 0.5f; + const bool is_pos = scores[idx] > 0.0f; + if (is_speech && !is_pos) { + scores[idx] = kNegInf; + } + } + } + } +} + +// _boost_topk_scores (sortformer_modules.py:611-634). For each speaker column, +// the k highest-scored frames get scores -= scale*log(0.5) (log(0.5)<0 => +// boosts). Tie-break matches ATen CPU topk: value desc, frame index asc. +void boost_topk_scores(std::vector & scores, int n, int n_spk, int k, float scale) { + if (k <= 0) { + return; + } + // Reference: scores[topk] -= scale * log(0.5). log(0.5) < 0, so the top-k + // scores are increased (boosted). + const float delta = scale * std::log(0.5f); // negative + std::vector order(static_cast(n)); + for (int s = 0; s < n_spk; ++s) { + std::iota(order.begin(), order.end(), 0); + const int kk = std::min(k, n); + std::partial_sort(order.begin(), order.begin() + kk, order.end(), + [&](int a, int b) { + const float va = scores[static_cast(a) * n_spk + s]; + const float vb = scores[static_cast(b) * n_spk + s]; + if (va != vb) { + return va > vb; // descending value + } + return a < b; // tie -> lower index first + }); + for (int j = 0; j < kk; ++j) { + scores[static_cast(order[static_cast(j)]) * n_spk + s] -= delta; + } + } +} + +} // namespace + +// ---- _compress_spkcache (sortformer_modules.py:838-896) ---- + +void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams & p, int n_spk, int emb_dim) { + const int N = st.spkcache_n; // n_frames (> spkcache_len) + const int L = p.spkcache_len; // target frames + const int sil = p.spkcache_sil_frames_per_spk; + + const int per_spk = L / n_spk - sil; + const int strong = static_cast(std::floor(per_spk * p.strong_boost_rate)); + const int weak = static_cast(std::floor(per_spk * p.weak_boost_rate)); + const int min_pos = static_cast(std::floor(per_spk * p.min_pos_scores_rate)); + + const float * preds = st.spkcache_preds.data(); // [N, n_spk] + + // 1. log-pred scores, disable low scores. + std::vector scores = get_log_pred_scores(preds, N, n_spk, p.pred_score_threshold); + disable_low_scores(preds, scores, N, n_spk, min_pos); + + // 2. boost latest frames (rows >= spkcache_len). += on -inf stays -inf. + if (p.scores_boost_latest > 0.0f) { + for (int i = L; i < N; ++i) { + for (int s = 0; s < n_spk; ++s) { + scores[static_cast(i) * n_spk + s] += p.scores_boost_latest; + } + } + } + + // 3. strong then weak top-k boosting. + boost_topk_scores(scores, N, n_spk, strong, /*scale=*/2.0f); + boost_topk_scores(scores, N, n_spk, weak, /*scale=*/1.0f); + + // 4. append `sil` rows of +inf per speaker; n_frames = N + sil. + const int n_frames = N + sil; + const int n_frames_no_sil = N; + std::vector scores_ext(static_cast(n_frames) * n_spk); + std::copy(scores.begin(), scores.end(), scores_ext.begin()); + for (int i = N; i < n_frames; ++i) { + for (int s = 0; s < n_spk; ++s) { + scores_ext[static_cast(i) * n_spk + s] = kPosInf; + } + } + + // 5. _get_topk_indices: flatten permute(0,2,1) -> flat[s*n_frames + i]; + // top-L largest (value desc, flat-idx asc), -inf picks -> max_index, sort + // ascending, derive frame idx + is_disabled. + const int64_t M = static_cast(n_spk) * n_frames; + std::vector flat(static_cast(M)); + std::iota(flat.begin(), flat.end(), int64_t{ 0 }); + auto flat_val = [&](int64_t f) -> float { + const int s = static_cast(f / n_frames); + const int i = static_cast(f % n_frames); + return scores_ext[static_cast(i) * n_spk + s]; + }; + const int kk = static_cast(std::min(L, M)); + std::partial_sort(flat.begin(), flat.begin() + kk, flat.end(), + [&](int64_t a, int64_t b) { + const float va = flat_val(a); + const float vb = flat_val(b); + if (va != vb) { + return va > vb; + } + return a < b; + }); + std::vector picks(static_cast(L)); + for (int j = 0; j < L; ++j) { + if (j < kk) { + const int64_t f = flat[static_cast(j)]; + picks[static_cast(j)] = (flat_val(f) == kNegInf) ? p.max_index : f; + } else { + picks[static_cast(j)] = p.max_index; // fewer than L finite picks + } + } + std::sort(picks.begin(), picks.end()); + + std::vector frame_idx(static_cast(L)); + std::vector is_disabled(static_cast(L)); + for (int j = 0; j < L; ++j) { + const int64_t idx = picks[static_cast(j)]; + bool disabled = (idx == p.max_index); + int f = disabled ? 0 : static_cast(idx % n_frames); + if (!disabled && f >= n_frames_no_sil) { + disabled = true; // came from a +inf silence pad row + } + if (disabled) { + f = 0; + } + frame_idx[static_cast(j)] = f; + is_disabled[static_cast(j)] = disabled ? 1 : 0; + } + + // 6. gather embeddings + preds; disabled -> mean_sil_emb / 0. + std::vector new_emb(static_cast(L) * emb_dim); + std::vector new_preds(static_cast(L) * n_spk); + for (int j = 0; j < L; ++j) { + const int f = frame_idx[static_cast(j)]; + if (is_disabled[static_cast(j)]) { + std::copy(st.mean_sil_emb.begin(), st.mean_sil_emb.end(), + new_emb.begin() + static_cast(j) * emb_dim); + // preds row already zero-initialized. + } else { + std::copy(st.spkcache.begin() + static_cast(f) * emb_dim, + st.spkcache.begin() + static_cast(f + 1) * emb_dim, + new_emb.begin() + static_cast(j) * emb_dim); + std::copy(st.spkcache_preds.begin() + static_cast(f) * n_spk, + st.spkcache_preds.begin() + static_cast(f + 1) * n_spk, + new_preds.begin() + static_cast(j) * n_spk); + } + } + + // Parity dump (residual-uncertainty #1): emit the per-compression selected + // frame indices + is_disabled mask + gathered preds so a matching NeMo dump + // can be compared index-for-index. Gated behind an explicit env on top of + // the normal dump-enabled flag so DER/validate runs are unaffected. + if (transcribe::debug::enabled() && env_get("TRANSCRIBE_SORTFORMER_COMPRESS_DUMP") != nullptr) { + const int k = st.compress_count; + char name[64]; + std::vector fidx(static_cast(L)), fdis(static_cast(L)); + for (int j = 0; j < L; ++j) { + fidx[static_cast(j)] = static_cast(frame_idx[static_cast(j)]); + fdis[static_cast(j)] = is_disabled[static_cast(j)] ? 1.0f : 0.0f; + } + const long long li = L; + const long long shp_in[2] = { N, n_spk }; + std::snprintf(name, sizeof(name), "compress.%03d.input_preds", k); + transcribe::debug::dump_host_f32(name, st.spkcache_preds.data(), static_cast(N) * n_spk, + shp_in, 2, "compress"); + std::snprintf(name, sizeof(name), "compress.%03d.frame_idx", k); + transcribe::debug::dump_host_f32(name, fidx.data(), li, &li, 1, "compress"); + std::snprintf(name, sizeof(name), "compress.%03d.is_disabled", k); + transcribe::debug::dump_host_f32(name, fdis.data(), li, &li, 1, "compress"); + const long long shp[2] = { L, n_spk }; + std::snprintf(name, sizeof(name), "compress.%03d.spkcache_preds", k); + transcribe::debug::dump_host_f32(name, new_preds.data(), static_cast(L) * n_spk, shp, 2, + "compress"); + } + ++st.compress_count; + + st.spkcache = std::move(new_emb); + st.spkcache_preds = std::move(new_preds); + st.spkcache_n = L; +} + +// ---- streaming_update (sync branch, sortformer_modules.py:526-609) ---- + +void streaming_update_sync(SortformerStreamState & st, const SortformerStreamParams & p, int n_spk, int emb_dim, + const std::vector & chunk_embs, int T_diar, + const std::vector & preds, int /*T_concat*/, int lc, int rc) { + const int S = st.spkcache_n; // spkcache_len (local, pre-update) + const int F = st.fifo_n; // fifo_len (local, pre-update) + const int C = T_diar - lc - rc; // chunk_len (middle frames) + + // fifo_preds = preds[S : S+F] (rebuilt from the concat preds each call). + st.fifo_preds.assign(preds.begin() + static_cast(S) * n_spk, + preds.begin() + static_cast(S + F) * n_spk); + // chunk_preds = preds[S+F+lc : S+F+lc+C]. + std::vector chunk_preds(preds.begin() + static_cast(S + F + lc) * n_spk, + preds.begin() + static_cast(S + F + lc + C) * n_spk); + + // Append the middle chunk (embs + preds) to the FIFO. + st.fifo.insert(st.fifo.end(), chunk_embs.begin() + static_cast(lc) * emb_dim, + chunk_embs.begin() + static_cast(lc + C) * emb_dim); + st.fifo_preds.insert(st.fifo_preds.end(), chunk_preds.begin(), chunk_preds.end()); + st.fifo_n = F + C; + + if (F + C > p.fifo_len) { + int pop = p.spkcache_update_period; + pop = std::max(pop, C - p.fifo_len + F); + pop = std::min(pop, F + C); + + std::vector pop_embs(st.fifo.begin(), st.fifo.begin() + static_cast(pop) * emb_dim); + std::vector pop_preds(st.fifo_preds.begin(), + st.fifo_preds.begin() + static_cast(pop) * n_spk); + + get_silence_profile(st, p.sil_threshold, pop_embs.data(), pop_preds.data(), pop, n_spk, emb_dim); + + // fifo = fifo[pop:]. + st.fifo.erase(st.fifo.begin(), st.fifo.begin() + static_cast(pop) * emb_dim); + st.fifo_preds.erase(st.fifo_preds.begin(), st.fifo_preds.begin() + static_cast(pop) * n_spk); + st.fifo_n = (F + C) - pop; + + // Append pop-out to speaker cache. + st.spkcache.insert(st.spkcache.end(), pop_embs.begin(), pop_embs.end()); + st.spkcache_n = S + pop; + if (st.spkcache_preds_init) { + st.spkcache_preds.insert(st.spkcache_preds.end(), pop_preds.begin(), pop_preds.end()); + } + + if (st.spkcache_n > p.spkcache_len) { + if (!st.spkcache_preds_init) { + // First compress: seed spkcache_preds = preds[:S] ++ pop_preds. + st.spkcache_preds.assign(preds.begin(), preds.begin() + static_cast(S) * n_spk); + st.spkcache_preds.insert(st.spkcache_preds.end(), pop_preds.begin(), pop_preds.end()); + st.spkcache_preds_init = true; + } + compress_spkcache(st, p, n_spk, emb_dim); + } + } + + // Accumulate the chunk's predictions. + st.total_preds.insert(st.total_preds.end(), chunk_preds.begin(), chunk_preds.end()); + st.total_n += C; +} + +} // namespace transcribe::sortformer diff --git a/src/arch/sortformer/weights.cpp b/src/arch/sortformer/weights.cpp new file mode 100644 index 00000000..f9c8e3c1 --- /dev/null +++ b/src/arch/sortformer/weights.cpp @@ -0,0 +1,193 @@ +// arch/sortformer/weights.cpp - Sortformer hparam KV reader + weight +// catalog for the Sortformer-specific tensors (encoder_proj + transformer +// + diar head). The conformer weights are loaded separately (parakeet +// reuse); see model.cpp::load. + +#include "weights.h" + +#include "ggml.h" +#include "gguf.h" +#include "transcribe-log.h" + +#include +#include + +namespace transcribe::sortformer { + +namespace { + +transcribe_status kv_u32(const gguf_context * g, const char * key, int32_t & out) { + const int64_t id = gguf_find_key(g, key); + if (id < 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: missing KV %s", key); + return TRANSCRIBE_ERR_GGUF; + } + out = static_cast(gguf_get_val_u32(g, id)); + return TRANSCRIBE_OK; +} + +transcribe_status kv_f32(const gguf_context * g, const char * key, float & out) { + const int64_t id = gguf_find_key(g, key); + if (id < 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: missing KV %s", key); + return TRANSCRIBE_ERR_GGUF; + } + out = gguf_get_val_f32(g, id); + return TRANSCRIBE_OK; +} + +transcribe_status kv_str(const gguf_context * g, const char * key, std::string & out) { + const int64_t id = gguf_find_key(g, key); + if (id < 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: missing KV %s", key); + return TRANSCRIBE_ERR_GGUF; + } + out = gguf_get_val_str(g, id); + return TRANSCRIBE_OK; +} + +} // namespace + +transcribe_status read_sortformer_hparams(const gguf_context * g, SortformerHParams & hp) { + if (g == nullptr) { + return TRANSCRIBE_ERR_INVALID_ARG; + } + +#define RD_U32(key, field) \ + if (const transcribe_status st = kv_u32(g, key, hp.field); st != TRANSCRIBE_OK) return st +#define RD_F32(key, field) \ + if (const transcribe_status st = kv_f32(g, key, hp.field); st != TRANSCRIBE_OK) return st +#define RD_STR(key, field) \ + if (const transcribe_status st = kv_str(g, key, hp.field); st != TRANSCRIBE_OK) return st + + RD_U32("stt.sortformer.max_speakers", max_speakers); + RD_U32("stt.sortformer.frame_hop", frame_hop); + + RD_U32("stt.sortformer.encoder.n_layers", enc_n_layers); + RD_U32("stt.sortformer.encoder.d_model", enc_d_model); + RD_U32("stt.sortformer.encoder.n_heads", enc_n_heads); + RD_U32("stt.sortformer.encoder.d_ff", enc_d_ff); + RD_U32("stt.sortformer.encoder.conv_kernel", enc_conv_kernel); + RD_U32("stt.sortformer.encoder.subsampling_factor", enc_subsampling_factor); + RD_U32("stt.sortformer.encoder.subsampling_channels", enc_subsampling_channels); + RD_U32("stt.sortformer.encoder.feat_in", enc_feat_in); + RD_U32("stt.sortformer.encoder.pos_emb_max_len", enc_pos_emb_max_len); + RD_STR("stt.sortformer.encoder.conv_norm_type", enc_conv_norm_type); + + RD_U32("stt.sortformer.transformer.n_layers", tf_n_layers); + RD_U32("stt.sortformer.transformer.d_model", tf_d_model); + RD_U32("stt.sortformer.transformer.n_heads", tf_n_heads); + RD_U32("stt.sortformer.transformer.d_ff", tf_d_ff); + RD_STR("stt.sortformer.transformer.activation", tf_activation); + { + const int64_t id = gguf_find_key(g, "stt.sortformer.transformer.pre_ln"); + hp.tf_pre_ln = (id >= 0) && gguf_get_val_bool(g, id); + } + + RD_U32("stt.frontend.num_mels", fe_num_mels); + RD_U32("stt.frontend.sample_rate", fe_sample_rate); + RD_U32("stt.frontend.n_fft", fe_n_fft); + RD_U32("stt.frontend.win_length", fe_win_length); + RD_U32("stt.frontend.hop_length", fe_hop_length); + RD_STR("stt.frontend.window", fe_window); + RD_STR("stt.frontend.normalize", fe_normalize); + RD_F32("stt.frontend.dither", fe_dither); + RD_F32("stt.frontend.pre_emphasis", fe_pre_emphasis); + + RD_U32("stt.sortformer.stream.chunk_len", stream_chunk_len); + RD_U32("stt.sortformer.stream.spkcache_len", stream_spkcache_len); + RD_U32("stt.sortformer.stream.fifo_len", stream_fifo_len); + RD_U32("stt.sortformer.stream.spkcache_update_period", stream_spkcache_update_period); + +#undef RD_U32 +#undef RD_F32 +#undef RD_STR + + if (hp.tf_n_heads == 0 || hp.tf_d_model % hp.tf_n_heads != 0) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: tf_d_model %d not divisible by tf_n_heads %d", + hp.tf_d_model, hp.tf_n_heads); + return TRANSCRIBE_ERR_GGUF; + } + if (hp.tf_pre_ln) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: pre_ln=true transformer not supported (post-LN only)"); + return TRANSCRIBE_ERR_GGUF; + } + return TRANSCRIBE_OK; +} + +namespace { + +// Look up a tensor by name and validate its ne against the expected dims +// (fast-to-slow; pass -1 to skip a dim). Returns null + logs on failure. +ggml_tensor * get_checked(ggml_context * ctx, const char * name, int64_t ne0, int64_t ne1) { + ggml_tensor * t = ggml_get_tensor(ctx, name); + if (t == nullptr) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: missing tensor %s", name); + return nullptr; + } + if ((ne0 >= 0 && t->ne[0] != ne0) || (ne1 >= 0 && t->ne[1] != ne1)) { + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, + "sortformer: tensor %s shape mismatch: have [%lld,%lld] want [%lld,%lld]", name, + (long long) t->ne[0], (long long) t->ne[1], (long long) ne0, (long long) ne1); + return nullptr; + } + return t; +} + +} // namespace + +transcribe_status build_sortformer_weights(ggml_context * ctx, const SortformerHParams & hp, SortformerWeights & w) { + const int64_t d = hp.tf_d_model; + const int64_t dff = hp.tf_d_ff; + const int64_t ed = hp.enc_d_model; + const int64_t spk = hp.max_speakers; + char name[128]; + +#define GET(dst, nm, e0, e1) \ + do { \ + (dst) = get_checked(ctx, (nm), (e0), (e1)); \ + if ((dst) == nullptr) return TRANSCRIBE_ERR_GGUF; \ + } while (0) + + GET(w.enc_proj_w, "diar.encoder_proj.weight", ed, d); + GET(w.enc_proj_b, "diar.encoder_proj.bias", d, -1); + + w.tf_blocks.resize(static_cast(hp.tf_n_layers)); + for (int i = 0; i < hp.tf_n_layers; ++i) { + SortformerTfBlock & b = w.tf_blocks[static_cast(i)]; +#define GETB(dst, suffix, e0, e1) \ + do { \ + std::snprintf(name, sizeof(name), "tf.blocks.%d.%s", i, suffix); \ + GET(dst, name, e0, e1); \ + } while (0) + GETB(b.norm1_w, "norm_1.weight", d, -1); + GETB(b.norm1_b, "norm_1.bias", d, -1); + GETB(b.attn_q_w, "attn.q.weight", d, d); + GETB(b.attn_q_b, "attn.q.bias", d, -1); + GETB(b.attn_k_w, "attn.k.weight", d, d); + GETB(b.attn_k_b, "attn.k.bias", d, -1); + GETB(b.attn_v_w, "attn.v.weight", d, d); + GETB(b.attn_v_b, "attn.v.bias", d, -1); + GETB(b.attn_o_w, "attn.out.weight", d, d); + GETB(b.attn_o_b, "attn.out.bias", d, -1); + GETB(b.norm2_w, "norm_2.weight", d, -1); + GETB(b.norm2_b, "norm_2.bias", d, -1); + GETB(b.ff_in_w, "ff.in.weight", d, dff); + GETB(b.ff_in_b, "ff.in.bias", dff, -1); + GETB(b.ff_out_w, "ff.out.weight", dff, d); + GETB(b.ff_out_b, "ff.out.bias", d, -1); +#undef GETB + } + + GET(w.fc1_w, "diar.fc1.weight", d, d); + GET(w.fc1_b, "diar.fc1.bias", d, -1); + GET(w.single_spk_head_w, "diar.single_spk_head.weight", d, spk); + GET(w.single_spk_head_b, "diar.single_spk_head.bias", spk, -1); + GET(w.spk_head_w, "diar.spk_head.weight", 2 * d, spk); + GET(w.spk_head_b, "diar.spk_head.bias", spk, -1); + +#undef GET + return TRANSCRIBE_OK; +} + +} // namespace transcribe::sortformer diff --git a/src/arch/sortformer/weights.h b/src/arch/sortformer/weights.h new file mode 100644 index 00000000..014a9908 --- /dev/null +++ b/src/arch/sortformer/weights.h @@ -0,0 +1,119 @@ +// arch/sortformer/weights.h - Sortformer hparams + the Sortformer-specific +// weight slots (encoder_proj + the 18-layer post-LN Transformer encoder + +// the diarization sigmoid head). The 17-layer NEST/FastConformer encoder is +// NOT here: it reuses parakeet::ParakeetWeights (pre_encode + blocks), +// loaded separately in the sortformer loader. +// +// Tensor layout conventions (matched by scripts/convert-sortformer.py): +// - Linear weights: PyTorch [out, in] -> ggml ne [in, out]. +// - LayerNorm: separate weight + bias, shape [d_model]. + +#pragma once + +#include "transcribe.h" + +#include +#include +#include + +struct gguf_context; +struct ggml_context; +struct ggml_tensor; + +namespace transcribe::sortformer { + +struct SortformerHParams { + // Diarization. + int32_t max_speakers = 4; // hard architectural cap + int32_t frame_hop = 0; // samples per diar frame (hop * subsampling) + + // Conformer encoder (mirrors the parakeet fields; see convert KVs). + int32_t enc_n_layers = 0; + int32_t enc_d_model = 0; + int32_t enc_n_heads = 0; + int32_t enc_d_ff = 0; + int32_t enc_conv_kernel = 0; + int32_t enc_subsampling_factor = 0; + int32_t enc_subsampling_channels = 0; + int32_t enc_feat_in = 0; + int32_t enc_pos_emb_max_len = 0; + std::string enc_conv_norm_type; // "batch_norm" + + // Transformer encoder (post-LN, no positional encoding, no final norm). + int32_t tf_n_layers = 0; + int32_t tf_d_model = 0; + int32_t tf_n_heads = 0; + int32_t tf_d_ff = 0; + std::string tf_activation; // "relu" + bool tf_pre_ln = false; + + // Frontend (mel feature extractor). + int32_t fe_num_mels = 0; + int32_t fe_sample_rate = 0; + int32_t fe_n_fft = 0; + int32_t fe_win_length = 0; + int32_t fe_hop_length = 0; + std::string fe_window; // "hann" + std::string fe_normalize; // "none" + float fe_dither = 0.0f; + float fe_pre_emphasis = 0.0f; + + // Shipped streaming defaults (deferred streaming path; read for KV + // completeness and later use). + int32_t stream_chunk_len = 0; + int32_t stream_spkcache_len = 0; + int32_t stream_fifo_len = 0; + int32_t stream_spkcache_update_period = 0; + + int32_t tf_head_dim() const { return tf_n_heads > 0 ? tf_d_model / tf_n_heads : 0; } +}; + +// One post-LN Transformer encoder block. All linears carry bias. +struct SortformerTfBlock { + ggml_tensor * norm1_w = nullptr; // [d] + ggml_tensor * norm1_b = nullptr; // [d] + ggml_tensor * attn_q_w = nullptr; // [d, d] + ggml_tensor * attn_q_b = nullptr; // [d] + ggml_tensor * attn_k_w = nullptr; + ggml_tensor * attn_k_b = nullptr; + ggml_tensor * attn_v_w = nullptr; + ggml_tensor * attn_v_b = nullptr; + ggml_tensor * attn_o_w = nullptr; + ggml_tensor * attn_o_b = nullptr; + ggml_tensor * norm2_w = nullptr; // [d] + ggml_tensor * norm2_b = nullptr; // [d] + ggml_tensor * ff_in_w = nullptr; // [d, d_ff] + ggml_tensor * ff_in_b = nullptr; // [d_ff] + ggml_tensor * ff_out_w = nullptr; // [d_ff, d] + ggml_tensor * ff_out_b = nullptr; // [d] +}; + +struct SortformerWeights { + // encoder_proj: Linear 512 -> 192. + ggml_tensor * enc_proj_w = nullptr; // [enc_d_model, tf_d_model] + ggml_tensor * enc_proj_b = nullptr; // [tf_d_model] + + std::vector tf_blocks; // tf_n_layers entries + + // Diar head (forward_speaker_sigmoids offline path). + ggml_tensor * fc1_w = nullptr; // [tf_d_model, tf_d_model] + ggml_tensor * fc1_b = nullptr; // [tf_d_model] + ggml_tensor * single_spk_head_w = nullptr; // [tf_d_model, max_speakers] + ggml_tensor * single_spk_head_b = nullptr; // [max_speakers] + // Streaming 2*hidden head (spkcache concat path); loaded but unused offline. + ggml_tensor * spk_head_w = nullptr; // [2*tf_d_model, max_speakers] + ggml_tensor * spk_head_b = nullptr; // [max_speakers] +}; + +// Read every required stt.sortformer.* / stt.frontend.* KV into hp. +transcribe_status read_sortformer_hparams(const gguf_context * gguf, SortformerHParams & hp); + +// Look up the Sortformer-specific tensors (encoder_proj + transformer + +// head) by name in ctx_meta, validate shapes against hp, store borrowed +// pointers. Returns TRANSCRIBE_ERR_GGUF (naming the tensor) on any +// missing / mis-shaped tensor. +transcribe_status build_sortformer_weights(ggml_context * ctx_meta, + const SortformerHParams & hp, + SortformerWeights & weights); + +} // namespace transcribe::sortformer diff --git a/src/transcribe-arch.cpp b/src/transcribe-arch.cpp index c75f478a..8dae20bf 100644 --- a/src/transcribe-arch.cpp +++ b/src/transcribe-arch.cpp @@ -79,6 +79,10 @@ namespace medasr { extern const Arch arch; } +namespace sortformer { +extern const Arch arch; +} + const Arch * find_arch(const char * name) { if (name == nullptr) { return nullptr; @@ -88,7 +92,7 @@ const Arch * find_arch(const char * name) { ¶keet::arch, &cohere::arch, &canary::arch, &qwen3_asr::arch, &voxtral::arch, &voxtral_realtime::arch, &canary_qwen::arch, &whisper::arch, &moonshine::arch, &moonshine_streaming::arch, &sensevoice::arch, &funasr_nano::arch, &gigaam::arch, &granite::arch, &granite_nar::arch, - &medasr::arch, &moss::arch, + &medasr::arch, &moss::arch, &sortformer::arch, }; constexpr size_t k_n = sizeof(k_archs) / sizeof(k_archs[0]); diff --git a/src/transcribe-mel.cpp b/src/transcribe-mel.cpp index eb60d63e..1a3b8d66 100644 --- a/src/transcribe-mel.cpp +++ b/src/transcribe-mel.cpp @@ -329,6 +329,13 @@ int MelFrontend::n_frames_for(size_t n_samples) const { static_cast(cfg_.hop_length)) + 1; } + // Newer NeMo (e.g. Sortformer) computes ceil(n / hop); the legacy + // families use floor(n / hop) + 1. They differ only when n is an exact + // multiple of hop, where ceil drops the trailing center frame. + if (cfg_.nemo_seq_len_ceil) { + return static_cast((n_samples + static_cast(cfg_.hop_length) - 1) / + static_cast(cfg_.hop_length)); + } // Matches NeMo features_lens = (waveforms_lens / hop_length) + 1. // The +1 accounts for the centered first frame. return static_cast(n_samples / static_cast(cfg_.hop_length)) + 1; @@ -351,11 +358,7 @@ transcribe_status MelFrontend::compute(const float * pcm, const int pad = n_fft / 2; const bool no_pad = (cfg_.pad_mode == "none"); - const int n_frames = - no_pad ? (static_cast(n_samples) < win ? - 0 : - static_cast((n_samples - static_cast(win)) / static_cast(hop)) + 1) : - static_cast(n_samples / static_cast(hop)) + 1; + const int n_frames = n_frames_for(n_samples); // Per-feature normalize divides by (n_frames - 1) and the // reflect pad needs at least pad+1 input samples to reflect diff --git a/src/transcribe-mel.h b/src/transcribe-mel.h index 9d2a19c7..697e79f7 100644 --- a/src/transcribe-mel.h +++ b/src/transcribe-mel.h @@ -98,6 +98,14 @@ struct MelConfig { // Optional checkpoint-provided window [win_length]. When non-empty, // used instead of computing a periodic Hann window. std::vector window; + + // NeMo seq-len semantics for the centered (reflect/constant) paths. + // Newer NeMo AudioToMelSpectrogramPreprocessor computes the feature + // length as ceil(n_samples / hop) rather than floor(n_samples / hop) + 1. + // The two agree except when n_samples is an exact multiple of hop, where + // ceil drops the extra trailing center frame. Sortformer's preprocessor + // uses the ceil form; leave false for the legacy +1 families. + bool nemo_seq_len_ceil = false; }; // Pure C++ log-mel extractor. Construct once, call compute() any diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bec7501c..e4b12330 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1085,6 +1085,26 @@ if(TRANSCRIBE_BUILD_REAL_MODEL_TESTS) COMMAND transcribe_qwen3_asr_e2e_smoke) set_tests_properties(transcribe_qwen3_asr_e2e_smoke PROPERTIES SKIP_RETURN_CODE 77) + + # Sortformer streaming operating-point run ext (SFST, RUN slot). + # Kind/slot probe, init stamping, pre-clear rejection with snapshot + # preservation, and ext-vs-env preset parity on the committed 2-spk + # oracle mix. Gated by TRANSCRIBE_SORTFORMER_GGUF (RC 77 skip). + add_executable(transcribe_sortformer_stream_ext_unit + sortformer_stream_ext_unit.cpp) + + target_link_libraries(transcribe_sortformer_stream_ext_unit + PRIVATE transcribe transcribe-common-example) + + target_compile_definitions(transcribe_sortformer_stream_ext_unit PRIVATE + "TRANSCRIBE_TEST_SAMPLES_DIR=\"${CMAKE_SOURCE_DIR}/samples\"") + + transcribe_apply_warnings(transcribe_sortformer_stream_ext_unit) + + add_test(NAME transcribe_sortformer_stream_ext_unit + COMMAND transcribe_sortformer_stream_ext_unit) + set_tests_properties(transcribe_sortformer_stream_ext_unit PROPERTIES + SKIP_RETURN_CODE 77) endif() # ----------------------------------------------------------------------------- diff --git a/tests/golden/sortformer/sortformer-2spk-mix.rttm b/tests/golden/sortformer/sortformer-2spk-mix.rttm new file mode 100644 index 00000000..1b4ff9c5 --- /dev/null +++ b/tests/golden/sortformer/sortformer-2spk-mix.rttm @@ -0,0 +1,4 @@ +SPEAKER sortformer-2spk-mix 1 0.000 3.000 spk_A +SPEAKER sortformer-2spk-mix 1 7.000 3.500 spk_A +SPEAKER sortformer-2spk-mix 1 3.500 3.000 spk_B +SPEAKER sortformer-2spk-mix 1 9.000 3.000 spk_B diff --git a/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json b/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json index deedcf5e..da7f6a74 100644 --- a/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json +++ b/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json @@ -6,7 +6,50 @@ "hf_repo": "nvidia/diar_streaming_sortformer_4spk-v2.1", "hf_revision": "fafaab5faa1617a0ca52d38dd3dc4bd636800d3d" }, + "reference": { + "kind": "nemo", + "source": "https://github.com/NVIDIA-NeMo/NeMo", + "revision": "6967f48fda2a", + "entrypoint": "scripts/dump_reference_sortformer_nemo.py" + }, "expected_dtype": "float32", "dtype_source": "manual", - "_skeleton": true + "frontend": { + "sample_rate": 16000, + "n_mels": 128, + "hop_length": 160, + "fft_size": 512, + "win_length": 400, + "window": "hann_periodic", + "normalization": "none", + "preemphasis": 0.97, + "dither": 1e-5 + }, + "tokenizer_summary": { + "type": "other", + "vocab_size": 0, + "special_tokens": {} + }, + "capabilities": { + "languages": ["en"], + "language_detection": false, + "translation": false, + "timestamps": [], + "streaming": true, + "speaker_diarization": true + }, + "diarization": { + "max_speakers": 4, + "frame_period_seconds": 0.08, + "output": "T x 4 sigmoid speaker-activity probabilities (arrival-order columns)", + "streaming_cfg_shipped": { + "chunk_len": 188, + "fifo_len": 0, + "spkcache_len": 188, + "spkcache_update_period": 188, + "chunk_right_context": 1 + } + }, + "tolerance_file": "tests/tolerances/sortformer.json", + "cases": ["sortformer-2spk-mix"] } diff --git a/tests/sortformer_stream_ext_unit.cpp b/tests/sortformer_stream_ext_unit.cpp new file mode 100644 index 00000000..e5c0d76d --- /dev/null +++ b/tests/sortformer_stream_ext_unit.cpp @@ -0,0 +1,191 @@ +// sortformer_stream_ext_unit.cpp - Sortformer streaming operating-point run +// extension (TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM, RUN slot). +// +// Covers, against a real GGUF (env-gated, RC 77 skip): +// +// 1. transcribe_model_accepts_ext_kind: SFST accepted on _RUN only; +// foreign kinds rejected. +// 2. transcribe_sortformer_stream_ext_init stamps size/kind/preset. +// 3. Pre-clear rejection: a wrong-kind ext and an out-of-range preset +// both fail with INVALID_ARG and PRESERVE the previous result +// (run_validate fires before clear_result). +// 4. Ext preset == env preset parity: running the committed 2-speaker +// oracle mix with ext=VERY_HIGH_LATENCY produces exactly the same +// speaker-segment rows as TRANSCRIBE_SORTFORMER_STREAM_PRESET= +// very_high_latency (the DER-validated env path), and a different +// geometry (LOW_LATENCY) is accepted and produces rows. +// +// Gated by TRANSCRIBE_SORTFORMER_GGUF (same pattern as the other +// real-model tests) because the preset resolution runs inside run() and +// needs real weights to produce comparable segments. + +#include "transcribe.h" +#include "transcribe/sortformer.h" +#include "wav.h" + +#include + +#include +#include +#include +#include + +namespace { + +int g_failures = 0; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + ++g_failures; \ + } \ + } while (0) + +bool file_exists(const std::string & path) { + struct stat st{}; + return ::stat(path.c_str(), &st) == 0; +} + +std::vector read_segments(const transcribe_session * session) { + std::vector rows; + const int n = transcribe_n_speaker_segments(session); + for (int i = 0; i < n; ++i) { + transcribe_speaker_segment row; + transcribe_speaker_segment_init(&row); + if (transcribe_get_speaker_segment(session, i, &row) == TRANSCRIBE_OK) { + rows.push_back(row); + } + } + return rows; +} + +bool same_segments(const std::vector & a, + const std::vector & b) { + if (a.size() != b.size()) { + return false; + } + for (size_t i = 0; i < a.size(); ++i) { + if (a[i].t0_ms != b[i].t0_ms || a[i].t1_ms != b[i].t1_ms || a[i].speaker_id != b[i].speaker_id) { + return false; + } + } + return true; +} + +} // namespace + +int main() { + const char * env = std::getenv("TRANSCRIBE_SORTFORMER_GGUF"); + if (env == nullptr || env[0] == '\0') { + std::fprintf(stderr, + "sortformer_stream_ext_unit: TRANSCRIBE_SORTFORMER_GGUF not set; skipping.\n" + "Re-run with TRANSCRIBE_SORTFORMER_GGUF=models/streaming-4spk-v2.1/" + "streaming-4spk-v2.1-F32.gguf\n"); + return 77; + } + const std::string gguf = env; + if (!file_exists(gguf)) { + std::fprintf(stderr, "sortformer_stream_ext_unit: file not found: %s\n", gguf.c_str()); + return 77; + } + const std::string wav_path = std::string(TRANSCRIBE_TEST_SAMPLES_DIR) + "/sortformer-2spk-mix.wav"; + std::vector pcm; + std::string wav_err; + if (!transcribe_cli::load_wav_mono_16k(wav_path, pcm, wav_err)) { + std::fprintf(stderr, "sortformer_stream_ext_unit: wav load: %s\n", wav_err.c_str()); + return 77; + } + + // The parity leg compares the ext path against the env path, so the + // environment must start clean of the validation overrides. + ::unsetenv("TRANSCRIBE_SORTFORMER_STREAM_PRESET"); + + transcribe_model_load_params mp; + transcribe_model_load_params_init(&mp); + mp.backend = TRANSCRIBE_BACKEND_CPU; // deterministic float order for the parity leg + struct transcribe_model * model = nullptr; + if (transcribe_model_load_file(gguf.c_str(), &mp, &model) != TRANSCRIBE_OK || model == nullptr) { + std::fprintf(stderr, "FAIL: model load\n"); + return EXIT_FAILURE; + } + + // 1. Kind+slot probe. + CHECK(transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM)); + CHECK(!transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_STREAM, + TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM)); + CHECK(!transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, 0x4E524857u /* WHRN */)); + + // 2. Init function stamps the header + default. + transcribe_sortformer_stream_ext ext; + transcribe_sortformer_stream_ext_init(&ext); + CHECK(ext.ext.size == sizeof(ext)); + CHECK(ext.ext.kind == TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM); + CHECK(ext.preset == TRANSCRIBE_SORTFORMER_PRESET_DEFAULT); + + struct transcribe_session * session = nullptr; + if (transcribe_session_init(model, nullptr, &session) != TRANSCRIBE_OK || session == nullptr) { + std::fprintf(stderr, "FAIL: session create\n"); + transcribe_model_free(model); + return EXIT_FAILURE; + } + + transcribe_run_params rp; + transcribe_run_params_init(&rp); + + // Baseline run (env preset path, the DER-validated harness route). + ::setenv("TRANSCRIBE_SORTFORMER_STREAM_PRESET", "very_high_latency", 1); + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_OK); + const std::vector env_rows = read_segments(session); + ::unsetenv("TRANSCRIBE_SORTFORMER_STREAM_PRESET"); + CHECK(!env_rows.empty()); // the oracle mix has two speakers + + // 3. Pre-clear rejection preserves the previous result. + { + transcribe_sortformer_stream_ext bad; + transcribe_sortformer_stream_ext_init(&bad); + bad.ext.kind = 0x4E524857u; // WHRN: wrong family kind + rp.family = &bad.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == + TRANSCRIBE_ERR_INVALID_ARG); + CHECK(same_segments(read_segments(session), env_rows)); // snapshot intact + + transcribe_sortformer_stream_ext oor; + transcribe_sortformer_stream_ext_init(&oor); + oor.preset = static_cast(99); + rp.family = &oor.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == + TRANSCRIBE_ERR_INVALID_ARG); + CHECK(same_segments(read_segments(session), env_rows)); + } + + // 4a. Ext preset == env preset parity (same operating point, same rows). + { + transcribe_sortformer_stream_ext vh; + transcribe_sortformer_stream_ext_init(&vh); + vh.preset = TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY; + rp.family = &vh.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_OK); + CHECK(same_segments(read_segments(session), env_rows)); + } + + // 4b. A different geometry is accepted and produces speaker rows. + { + transcribe_sortformer_stream_ext lo; + transcribe_sortformer_stream_ext_init(&lo); + lo.preset = TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY; + rp.family = &lo.ext; + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_OK); + CHECK(!read_segments(session).empty()); + } + + transcribe_session_free(session); + transcribe_model_free(model); + + if (g_failures != 0) { + std::fprintf(stderr, "sortformer_stream_ext_unit: %d failure(s)\n", g_failures); + return EXIT_FAILURE; + } + std::printf("sortformer_stream_ext_unit: OK\n"); + return 0; +} diff --git a/tests/tolerances/sortformer.json b/tests/tolerances/sortformer.json new file mode 100644 index 00000000..54dc1de1 --- /dev/null +++ b/tests/tolerances/sortformer.json @@ -0,0 +1,35 @@ +{ + "_comment": [ + "CORRECTNESS REGIME (Stage 4, offline path): reference-dtype F32 GGUF; KV f32; C++ MelFrontend real mel (NeMo ceil(n/hop) framing); --backend cpu --threads 1. Every number below is measured in that regime via validate.py all / compare_tensors.py.", + "Sortformer is a diarizer: diar.preds_offline is [T,4] sigmoid activity in [0,1] (arrival-order columns); enc.* are pre-sigmoid encoder activations. The model's actual output (diar.preds_offline) matches NeMo to max_abs 2.96e-4 / mean_abs 5.0e-6 (near bit-exact).", + "Dominant drift source: F32 accumulation over the 17-layer NEST FastConformer (reused verbatim from parakeet::build_encoder_graph) with normalize=none mel magnitudes (~16). Drift is uniform across frames (peaks mid-sequence, boundaries no worse) and DECREASES down the stack (fastconformer 3.3e-3 -> encoder_proj 9.8e-4 -> transformer 1.1e-3 -> preds 3.0e-4), confirming intrinsic accumulation, not a masking/framing bug. Mel values verified to 1.6e-4 (~1e-5 relative) vs NeMo.", + "Finalized = max(1.5 x observed, provisional). Widened vs Stage-2 provisional: fastconformer, encoder_proj, transformer, preds (F32 conformer accumulation; ~0.1% relative, well under the 1% investigate threshold).", + "enc.mel.in is shape_optional: NeMo pads the mel tensor to a multiple of 16 (pad_to=16, zero pad) which its own forward() trims off before the encoder; the C++ feeds the real ceil(n/hop) length. The real frames match to 1.6e-4; full padded-tensor parity is a Step-7 (frontend) item.", + "diar.probs (streaming AOSC/FIFO path) is now dumped by the C++ streaming forward (run_streaming) and gated. With the checkpoint-shipped cfg the short oracle clip is a single chunk, so diar.probs == diar.preds_offline math (empty spkcache/fifo -> concat is just the chunk); its tolerance tracks diar.preds_offline. Multi-chunk / compression parity is exercised via VALIDATE_SORTFORMER_PRESET=small (and the AMI DER acceptance)." + ], + "diar.preds_offline": { + "max_abs": 4.5e-04, + "mean_abs": 7.6e-06 + }, + "enc.encoder_proj.out": { + "max_abs": 1.5e-03, + "mean_abs": 1.9e-04 + }, + "enc.fastconformer.out": { + "max_abs": 4.9e-03, + "mean_abs": 2.1e-04 + }, + "enc.transformer.out": { + "max_abs": 1.7e-03, + "mean_abs": 2.0e-04 + }, + "enc.mel.in": { + "max_abs": 2.5e-04, + "mean_abs": 2.7e-06, + "shape_optional": true + }, + "diar.probs": { + "max_abs": 4.5e-04, + "mean_abs": 7.6e-06 + } +} From d42c3bbdfa2f63c37e5891e27de47a612d62f221 Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Tue, 21 Jul 2026 16:56:29 -0700 Subject: [PATCH 3/7] slug rename --- docs/porting/families/sortformer.md | 28 +++++++++---------- include/transcribe/sortformer.h | 2 +- .../intake.json | 2 +- reports/porting/sortformer/forward-map.md | 2 +- scripts/diar/run_cpp_sortformer.py | 10 +++---- scripts/diar/run_reference_sortformer_nemo.py | 4 +-- scripts/diar/score_der.py | 4 +-- scripts/dump_reference_sortformer_nemo.py | 4 +-- src/arch/sortformer/model.cpp | 2 +- ...eaming_sortformer_4spk-v2.1.manifest.json} | 2 +- tests/sortformer_stream_ext_unit.cpp | 4 +-- 11 files changed, 32 insertions(+), 32 deletions(-) rename reports/porting/sortformer/{streaming-4spk-v2.1 => diar_streaming_sortformer_4spk-v2.1}/intake.json (99%) rename tests/golden/sortformer/{streaming-4spk-v2.1.manifest.json => diar_streaming_sortformer_4spk-v2.1.manifest.json} (96%) diff --git a/docs/porting/families/sortformer.md b/docs/porting/families/sortformer.md index 4693b307..1d905629 100644 --- a/docs/porting/families/sortformer.md +++ b/docs/porting/families/sortformer.md @@ -32,7 +32,7 @@ dependency of the Parakeet multitalker speaker-attributed ASR path - Hugging Face revision: `fafaab5faa1617a0ca52d38dd3dc4bd636800d3d` - License: NVIDIA Open Model License (v2.1). Sibling `diar_streaming_sortformer_4spk-v2` is CC-BY-4.0. - Variants: - - `streaming-4spk-v2.1` — this port (best meeting DER; multitalker's named dependency). + - `diar_streaming_sortformer_4spk-v2.1` — this port (best meeting DER; multitalker's named dependency). - `streaming-4spk-v2` — architecturally identical, CC-BY-4.0. Deferred; add by dropping in weights once v2.1 is validated. ### Diarization scope policy @@ -112,7 +112,7 @@ uv run scripts/gen_sortformer_oracle_audio.py Reference dumps (NeMo, via scripts/envs/sortformer): ```bash -BASE=build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix +BASE=build/validate/sortformer/diar_streaming_sortformer_4spk-v2.1/sortformer-2spk-mix uv run --project scripts/envs/sortformer scripts/dump_reference_sortformer_nemo.py encoder \ --model nvidia/diar_streaming_sortformer_4spk-v2.1 --audio samples/sortformer-2spk-mix.wav --out $BASE/encoder/ref uv run --project scripts/envs/sortformer scripts/dump_reference_sortformer_nemo.py diarize \ @@ -122,7 +122,7 @@ uv run --project scripts/envs/sortformer scripts/dump_reference_sortformer_nemo. Conversion: ```bash -scripts/convert-sortformer.py -> models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf (arch `sortformer`). +scripts/convert-sortformer.py -> models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf (arch `sortformer`). ``` Validation: @@ -140,19 +140,19 @@ uv run --project scripts/envs/sortformer scripts/diar/run_reference_sortformer_n --manifest samples/diar/ami-ihm-test.manifest.jsonl \ --model nvidia/diar_streaming_sortformer_4spk-v2.1 --preset very_high_latency \ --postprocessing-yaml scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml \ - --pred-dir reports/diar/pred/streaming-4spk-v2.1-ami-ihm-test-PPdihard \ - --out reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test.PPdihard.jsonl + --pred-dir reports/diar/pred/diar_streaming_sortformer_4spk-v2.1-ami-ihm-test-PPdihard \ + --out reports/diar/diar_streaming_sortformer_4spk-v2.1-REF.ami-ihm-test.PPdihard.jsonl # C++ port (dumps diar.probs per meeting, applies the SAME dihard3 PP): uv run --project scripts/envs/sortformer scripts/diar/run_cpp_sortformer.py \ --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ - --gguf models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf --preset very_high_latency \ + --gguf models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf --preset very_high_latency \ --postprocessing-yaml scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml \ - --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ - --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test.jsonl --backend cpu + --pred-dir reports/diar/pred/diar_streaming_sortformer_4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/diar_streaming_sortformer_4spk-v2.1-CPP.ami-ihm-test.jsonl --backend cpu uv run scripts/diar/score_der.py \ --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ - --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ - --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test-fa.PPdihard.score.json + --pred-dir reports/diar/pred/diar_streaming_sortformer_4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/diar_streaming_sortformer_4spk-v2.1-CPP.ami-ihm-test-fa.PPdihard.score.json # -> REF DER 14.83% / JER 19.89% ; C++ DER 14.59% / JER 19.51% (within 0.24 pt). ``` @@ -186,7 +186,7 @@ TODO ## Reference baselines (Stage 2 Oracle) -- **Tensor parity oracle** (`build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix/`): mel + fastconformer + encoder_proj + transformer + offline preds + streaming `diar.probs` [150×4]. Offline preds == streaming probs on this short clip (single chunk). Tolerances in `tests/tolerances/sortformer.json` (provisional). +- **Tensor parity oracle** (`build/validate/sortformer/diar_streaming_sortformer_4spk-v2.1/sortformer-2spk-mix/`): mel + fastconformer + encoder_proj + transformer + offline preds + streaming `diar.probs` [150×4]. Offline preds == streaming probs on this short clip (single chunk). Tolerances in `tests/tolerances/sortformer.json` (provisional). - **Measured reference DER/JER** on AMI ihm test (16 meetings, 9.06 h), preset `very_high_latency`, collar 0.0s / overlap included (NVIDIA AMI protocol). The reference pipeline REPRODUCES the published number (15.90 @ very_high_latency AMI-IHM). Full 2x2 decomposition of the two protocol levers: | predictions | vs manual RTTMs (diarizers-community/ami) | vs forced-alignment RTTMs (nttcslab-sp) | @@ -194,14 +194,14 @@ TODO | no post-processing | 29.44% | 15.96% | | + dihard3 post-processing | 27.95% | **14.83%** | - - **Official gate = forced-alignment RTTMs + dihard3 PP: DER 14.83%, JER 19.89%** (missed 5.89%, FA 5.60%, confusion 3.34%). File: `reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test-fa.PPdihard.score.json`. Stage 4 / Stage 7 apply the SAME PP to the C++ probs and score vs the SAME forced-alignment RTTMs, gated against this number. + - **Official gate = forced-alignment RTTMs + dihard3 PP: DER 14.83%, JER 19.89%** (missed 5.89%, FA 5.60%, confusion 3.34%). File: `reports/diar/diar_streaming_sortformer_4spk-v2.1-REF.ami-ihm-test-fa.PPdihard.score.json`. Stage 4 / Stage 7 apply the SAME PP to the C++ probs and score vs the SAME forced-alignment RTTMs, gated against this number. - The RTTM source is ~13 pts of the published-vs-manual gap; post-processing another ~1 pt. Confusion stays ~3% throughout, so the diarization core is correct; the manual-RTTM inflation was recall against dense annotations (within-utterance pauses), exactly as NVIDIA's forced-alignment `only_words` labels avoid. - Ground truth: nttcslab-sp/diar-forced-alignment (ASRU 2025, card ref [7]), `only_words`, full-corpus-ASR partition — the labels NVIDIA used. PP: NeMo v2 `dihard3-dev` config (no AMI-specific or v2.1 PP is published; dihard3 is the meeting-domain proxy). - Diagnostic/context files also kept: `...ami-ihm-test.score.json` (no-PP/manual 29.44%), `...ami-ihm-test-fa.noPP.score.json` (no-PP/FA 15.96%). ## Conversion (Stage 3) -`scripts/convert-sortformer.py` -> `models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf` (F32, 471 MB, arch string `sortformer`). 971 tensors emitted; 19 skipped (2 `preprocessor.*` recomputed in the C++ frontend, 17 BN `num_batches_tracked` counters). Gate B all-PASS. The GGUF tensor-name contract Stage 4's `src/arch/sortformer/weights.cpp` must implement: +`scripts/convert-sortformer.py` -> `models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf` (F32, 471 MB, arch string `sortformer`). 971 tensors emitted; 19 skipped (2 `preprocessor.*` recomputed in the C++ frontend, 17 BN `num_batches_tracked` counters). Gate B all-PASS. The GGUF tensor-name contract Stage 4's `src/arch/sortformer/weights.cpp` must implement: - `enc.pre_encode.*` — dw_striding subsample (same as Parakeet). - `enc.blocks.{0..16}.*` — 17 Conformer/NEST blocks; identical suffix map to Parakeet's `ENCODER_BLOCK_TABLE` (macaron FF1/FF2, rel-pos self-attn with `pos_bias_u/v`, conv module with `conv.bn.{weight,bias,running_mean,running_var}`). `use_bias=true` (linear biases present); `conv_norm_type=batch_norm`. @@ -209,7 +209,7 @@ TODO - `tf.blocks.{0..17}.*` — 18 post-LN Transformer blocks: `norm_1` -> attn(`attn.{q,k,v,out}`) -> `norm_2` -> FFN(`ff.{in,out}`), ReLU, `pre_ln=false`. - `diar.fc1.*`, `diar.spk_head.*` (4x384 -> 4 sigmoid), `diar.single_spk_head.*` (4x192). -Tensor-name mapping decisions: Conformer names mirror `convert-parakeet.py` verbatim (shared NeMo ConformerEncoder). Transformer norms named `norm_1`/`norm_2` and BN kept as `.bn.` so `reference_dtype_for`/`policy.cpp` route them to F32; representatives registered in `scripts/lib/test_quant_policy_sync.py`. GGUF slug is the variant (`streaming-4spk-v2.1`), not the longer repo slug. +Tensor-name mapping decisions: Conformer names mirror `convert-parakeet.py` verbatim (shared NeMo ConformerEncoder). Transformer norms named `norm_1`/`norm_2` and BN kept as `.bn.` so `reference_dtype_for`/`policy.cpp` route them to F32; representatives registered in `scripts/lib/test_quant_policy_sync.py`. GGUF slug is the variant (`diar_streaming_sortformer_4spk-v2.1`), not the longer repo slug. ## Notes diff --git a/include/transcribe/sortformer.h b/include/transcribe/sortformer.h index 3899d2d4..751b11c8 100644 --- a/include/transcribe/sortformer.h +++ b/include/transcribe/sortformer.h @@ -5,7 +5,7 @@ * streaming-operating-point run extension, its kind constant, and its * init function. * - * Sortformer (streaming-4spk-v2.1) is a diarization-only model: a run + * Sortformer (diar_streaming_sortformer_4spk-v2.1) is a diarization-only model: a run * produces no text; the product is the who-spoke-when rows read back via * transcribe_n_speaker_segments / transcribe_get_speaker_segment * (TRANSCRIBE_FEATURE_DIARIZATION). The compute core is streaming diff --git a/reports/porting/sortformer/streaming-4spk-v2.1/intake.json b/reports/porting/sortformer/diar_streaming_sortformer_4spk-v2.1/intake.json similarity index 99% rename from reports/porting/sortformer/streaming-4spk-v2.1/intake.json rename to reports/porting/sortformer/diar_streaming_sortformer_4spk-v2.1/intake.json index 93bec331..d1fc1453 100644 --- a/reports/porting/sortformer/streaming-4spk-v2.1/intake.json +++ b/reports/porting/sortformer/diar_streaming_sortformer_4spk-v2.1/intake.json @@ -61,7 +61,7 @@ }, "variants": [ { - "name": "streaming-4spk-v2.1", + "name": "diar_streaming_sortformer_4spk-v2.1", "memory_gb": 2, "files": ["diar_streaming_sortformer_4spk-v2.1.nemo"] } diff --git a/reports/porting/sortformer/forward-map.md b/reports/porting/sortformer/forward-map.md index 18e154e9..6da2a73c 100644 --- a/reports/porting/sortformer/forward-map.md +++ b/reports/porting/sortformer/forward-map.md @@ -133,6 +133,6 @@ accumulation, no structural divergence) and end-to-end via AMI DER ## Variant Notes -- `streaming-4spk-v2.1`: family baseline (this port). +- `diar_streaming_sortformer_4spk-v2.1`: family baseline (this port). - `streaming-4spk-v2`: architecturally identical, CC-BY-4.0; add weights once v2.1 validated (sibling-variant shortcut, Step 1). diff --git a/scripts/diar/run_cpp_sortformer.py b/scripts/diar/run_cpp_sortformer.py index f9067ae9..0ba36a75 100644 --- a/scripts/diar/run_cpp_sortformer.py +++ b/scripts/diar/run_cpp_sortformer.py @@ -16,16 +16,16 @@ uv run --project scripts/envs/sortformer \ scripts/diar/run_cpp_sortformer.py \ --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ - --gguf models/streaming-4spk-v2.1/streaming-4spk-v2.1-F32.gguf \ + --gguf models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf \ --preset very_high_latency \ --postprocessing-yaml scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml \ - --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ - --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test.jsonl + --pred-dir reports/diar/pred/diar_streaming_sortformer_4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/diar_streaming_sortformer_4spk-v2.1-CPP.ami-ihm-test.jsonl uv run scripts/diar/score_der.py \ --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ - --pred-dir reports/diar/pred/streaming-4spk-v2.1-cpp-ami-ihm-test \ - --out reports/diar/streaming-4spk-v2.1-CPP.ami-ihm-test.score.json + --pred-dir reports/diar/pred/diar_streaming_sortformer_4spk-v2.1-cpp-ami-ihm-test \ + --out reports/diar/diar_streaming_sortformer_4spk-v2.1-CPP.ami-ihm-test.score.json """ from __future__ import annotations diff --git a/scripts/diar/run_reference_sortformer_nemo.py b/scripts/diar/run_reference_sortformer_nemo.py index 334272a6..1ec125ef 100644 --- a/scripts/diar/run_reference_sortformer_nemo.py +++ b/scripts/diar/run_reference_sortformer_nemo.py @@ -20,8 +20,8 @@ --manifest samples/diar/ami-ihm-test.manifest.jsonl \ --model nvidia/diar_streaming_sortformer_4spk-v2.1 \ --preset very_high_latency \ - --pred-dir reports/diar/pred/streaming-4spk-v2.1-ami-ihm-test \ - --out reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test.jsonl + --pred-dir reports/diar/pred/diar_streaming_sortformer_4spk-v2.1-ami-ihm-test \ + --out reports/diar/diar_streaming_sortformer_4spk-v2.1-REF.ami-ihm-test.jsonl """ from __future__ import annotations diff --git a/scripts/diar/score_der.py b/scripts/diar/score_der.py index 85d5a21b..43208438 100644 --- a/scripts/diar/score_der.py +++ b/scripts/diar/score_der.py @@ -25,8 +25,8 @@ Usage: uv run scripts/diar/score_der.py \ --manifest samples/diar/ami-ihm-test.manifest.jsonl \ - --pred-dir reports/diar/pred/streaming-4spk-v2.1-ami-ihm-test \ - --out reports/diar/streaming-4spk-v2.1-REF.ami-ihm-test.score.json + --pred-dir reports/diar/pred/diar_streaming_sortformer_4spk-v2.1-ami-ihm-test \ + --out reports/diar/diar_streaming_sortformer_4spk-v2.1-REF.ami-ihm-test.score.json """ from __future__ import annotations diff --git a/scripts/dump_reference_sortformer_nemo.py b/scripts/dump_reference_sortformer_nemo.py index bd3121b3..9233c5af 100644 --- a/scripts/dump_reference_sortformer_nemo.py +++ b/scripts/dump_reference_sortformer_nemo.py @@ -34,13 +34,13 @@ scripts/dump_reference_sortformer_nemo.py encoder \ --model nvidia/diar_streaming_sortformer_4spk-v2.1 \ --audio samples/sortformer-2spk-mix.wav \ - --out build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix/encoder/ref + --out build/validate/sortformer/diar_streaming_sortformer_4spk-v2.1/sortformer-2spk-mix/encoder/ref uv run --project scripts/envs/sortformer \ scripts/dump_reference_sortformer_nemo.py diarize \ --model nvidia/diar_streaming_sortformer_4spk-v2.1 \ --audio samples/sortformer-2spk-mix.wav \ - --out build/validate/sortformer/streaming-4spk-v2.1/sortformer-2spk-mix/diarize/ref + --out build/validate/sortformer/diar_streaming_sortformer_4spk-v2.1/sortformer-2spk-mix/diarize/ref """ from __future__ import annotations diff --git a/src/arch/sortformer/model.cpp b/src/arch/sortformer/model.cpp index 19351391..95af5d73 100644 --- a/src/arch/sortformer/model.cpp +++ b/src/arch/sortformer/model.cpp @@ -42,7 +42,7 @@ namespace pk = transcribe::parakeet; namespace conf = transcribe::conformer; static constexpr float kBnEps = 1e-5f; -static constexpr char k_default_variant[] = "streaming-4spk-v2.1"; +static constexpr char k_default_variant[] = "diar_streaming_sortformer_4spk-v2.1"; SortformerModel::~SortformerModel() { if (bn_fused_ctx != nullptr) { diff --git a/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json b/tests/golden/sortformer/diar_streaming_sortformer_4spk-v2.1.manifest.json similarity index 96% rename from tests/golden/sortformer/streaming-4spk-v2.1.manifest.json rename to tests/golden/sortformer/diar_streaming_sortformer_4spk-v2.1.manifest.json index da7f6a74..067726d5 100644 --- a/tests/golden/sortformer/streaming-4spk-v2.1.manifest.json +++ b/tests/golden/sortformer/diar_streaming_sortformer_4spk-v2.1.manifest.json @@ -1,7 +1,7 @@ { "schema": "transcribe-golden-manifest-v1", "family": "sortformer", - "variant": "streaming-4spk-v2.1", + "variant": "diar_streaming_sortformer_4spk-v2.1", "source_model": { "hf_repo": "nvidia/diar_streaming_sortformer_4spk-v2.1", "hf_revision": "fafaab5faa1617a0ca52d38dd3dc4bd636800d3d" diff --git a/tests/sortformer_stream_ext_unit.cpp b/tests/sortformer_stream_ext_unit.cpp index e5c0d76d..c823d247 100644 --- a/tests/sortformer_stream_ext_unit.cpp +++ b/tests/sortformer_stream_ext_unit.cpp @@ -80,8 +80,8 @@ int main() { if (env == nullptr || env[0] == '\0') { std::fprintf(stderr, "sortformer_stream_ext_unit: TRANSCRIBE_SORTFORMER_GGUF not set; skipping.\n" - "Re-run with TRANSCRIBE_SORTFORMER_GGUF=models/streaming-4spk-v2.1/" - "streaming-4spk-v2.1-F32.gguf\n"); + "Re-run with TRANSCRIBE_SORTFORMER_GGUF=models/diar_streaming_sortformer_4spk-v2.1/" + "diar_streaming_sortformer_4spk-v2.1-F32.gguf\n"); return 77; } const std::string gguf = env; From 4281d5d12f7b7544c726f12596497b4f9a5baee2 Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Wed, 22 Jul 2026 02:44:48 -0700 Subject: [PATCH 4/7] close to finalization --- README.md | 1 + .../diar_streaming_sortformer_4spk-v2.1.md | 217 ++++++++++++++++++ docs/porting/families/sortformer.md | 67 ++++-- .../diar_streaming_sortformer_4spk-v2.1.yaml | 86 +++++++ scripts/lib/quant_policy.py | 16 +- 5 files changed, 369 insertions(+), 18 deletions(-) create mode 100644 docs/models/diar_streaming_sortformer_4spk-v2.1.md create mode 100644 scripts/hf_cards/diar_streaming_sortformer_4spk-v2.1.yaml diff --git a/README.md b/README.md index e44ba798..7f13aac4 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ C/C++ speech-to-text inference library. Runs diverse STT model families via [GGU | Voxtral Realtime | `voxtral-mini-4b-realtime-2602` (streaming audio-LLM) | [docs/models/voxtral-realtime.md](docs/models/voxtral-realtime.md) | | MedASR | `medasr` (Conformer + CTC, English medical-dictation, gated) | [docs/models/medasr.md](docs/models/medasr.md) | | MOSS Transcribe-Diarize | `moss-transcribe-diarize` (audio-LLM; English + Chinese ASR with inline speaker diarization) | [docs/models/moss-transcribe-diarize.md](docs/models/moss-transcribe-diarize.md) | +| Sortformer | `diar_streaming_sortformer_4spk-v2.1` (streaming speaker diarizer, up to 4 speakers; no transcription) | [docs/models/diar_streaming_sortformer_4spk-v2.1.md](docs/models/diar_streaming_sortformer_4spk-v2.1.md) | Per-variant model cards live under [`docs/models/`](docs/models/). diff --git a/docs/models/diar_streaming_sortformer_4spk-v2.1.md b/docs/models/diar_streaming_sortformer_4spk-v2.1.md new file mode 100644 index 00000000..9c2f44d1 --- /dev/null +++ b/docs/models/diar_streaming_sortformer_4spk-v2.1.md @@ -0,0 +1,217 @@ +# Streaming Sortformer Diarizer 4spk v2.1 + +NVIDIA's [`nvidia/diar_streaming_sortformer_4spk-v2.1`](https://huggingface.co/nvidia/diar_streaming_sortformer_4spk-v2.1) +ported to transcribe.cpp. A FastConformer encoder with an 18-layer +Transformer head that emits per-frame speaker-activity probabilities for +up to 4 speakers, running online with an Arrival-Order Speaker Cache +(AOSC) + FIFO. + +## What it's for + +Speaker diarization: who spoke when. This is **not a transcription +model** — a run produces no text. It takes a 16 kHz mono WAV and emits +speaker segments (`t0_ms`, `t1_ms`, `speaker_id`), with speakers numbered +in order of first appearance. Hard architectural cap of 4 concurrent +speakers. It is the diarization supervisor for the Parakeet multitalker +speaker-attributed ASR path (future work). + +See NVIDIA's [model card](https://huggingface.co/nvidia/diar_streaming_sortformer_4spk-v2.1) +for training data, intended use, and upstream evaluation methodology. + +Licensed under the NVIDIA Open Model License. Ported from upstream commit +[`fafaab5`](https://huggingface.co/nvidia/diar_streaming_sortformer_4spk-v2.1/commit/fafaab5faa1617a0ca52d38dd3dc4bd636800d3d), +pinned 2026-07-19. + +## Download + +| Quantization | Download | Size | DER (AMI IHM test) | +| --- | --- | ---: | ---: | +| F32 | [diar_streaming_sortformer_4spk-v2.1-F32.gguf](https://huggingface.co/handy-computer/diar_streaming_sortformer_4spk-v2.1-gguf/resolve/main/diar_streaming_sortformer_4spk-v2.1-F32.gguf) | 471 MB | 14.59% | +| F16 | [diar_streaming_sortformer_4spk-v2.1-F16.gguf](https://huggingface.co/handy-computer/diar_streaming_sortformer_4spk-v2.1-gguf/resolve/main/diar_streaming_sortformer_4spk-v2.1-F16.gguf) | 237 MB | 14.23% | +| Q8_0 | [diar_streaming_sortformer_4spk-v2.1-Q8_0.gguf](https://huggingface.co/handy-computer/diar_streaming_sortformer_4spk-v2.1-gguf/resolve/main/diar_streaming_sortformer_4spk-v2.1-Q8_0.gguf) | 139 MB | 14.73% | + +DER is measured on the full AMI IHM test set (16 meetings, ~9 h) against +forced-alignment RTTMs with dihard3-dev post-processing, collar 0.0, +overlap scored, at the `very_high_latency` operating point. Our measured +NeMo reference under the identical protocol is **14.83% DER / 19.89% +JER**; the C++ F32 port scores 14.59% / 19.51%. (Published DER numbers +for this model vary with the RTTM source and post-processing; manual +RTTMs score ~13 points worse than forced-alignment RTTMs on the same +system output. Compare like with like.) + +Only near-reference tiers ship for this family. K-quant tiers were +evaluated and withdrawn: the model's output depends on discrete +speaker-cache decisions, and k-tier weight error can deterministically +flip a near-tie and permute speaker labels mid-stream (observed at +Q5_K_M on one AMI meeting; details in +`docs/porting/families/sortformer.md`, "Quant policy (Stage 7)"). + +## Quick Start + +```bash +cmake -B build +cmake --build build + +# Speaker segments as JSON (one line per file): +echo audio.wav > files.txt +build/bin/transcribe-cli \ + -m models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-Q8_0.gguf \ + --batch files.txt --batch-jsonl +# {"file":"audio.wav","text":"","speakers":[{"t0_ms":320,"t1_ms":2400,"speaker_id":1},...]} +``` + +If your audio is not already 16 kHz mono WAV, convert it first: + +```bash +ffmpeg -i input.mp3 -ar 16000 -ac 1 output.wav +``` + +From the C API, read results via `transcribe_n_speaker_segments` / +`transcribe_get_speaker_segment` (the transcript accessors return empty +text). The streaming operating point (latency / accuracy trade-off) is +selected with the run extension in `include/transcribe/sortformer.h`: + +```c +transcribe_sortformer_stream_ext ext; +transcribe_sortformer_stream_ext_init(&ext); /* DEFAULT = model cfg */ +ext.preset = TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY; +run_params.family = &ext.ext; +``` + +`VERY_HIGH_LATENCY` (~30 s lookahead) is the offline-file operating +point used for the DER numbers above; `LOW_LATENCY` (~1 s lookahead) is +the real-time point and costs substantially more compute per audio +second (many small windows). + +## Performance + +Cells are wall-clock latency (mean over 3 iterations after 1 warmup), +with speedup over realtime in parentheses. Default (model-config) +operating point. + +### Apple M4 + +| Backend | Sample | F16 | Q8_0 | +| ------- | ------------ | ------------: | ------------: | +| Metal | jfk (11.0s) | 69 ms (159×) | 65 ms (171×) | +| Metal | dots (35.3s) | 318 ms (111×) | 320 ms (111×) | +| CPU | jfk (11.0s) | 137 ms (80×) | 110 ms (100×) | +| CPU | dots (35.3s) | 796 ms (44×) | 687 ms (51×) | + +macOS 25.5.0, transcribe.cpp `d42c3bb`. + +Benchmark reproduction: + +```bash +uv run scripts/bench/run.py \ + --models diar_streaming_sortformer_4spk-v2.1 \ + --quants f16,q8_0 \ + --samples jfk,dots \ + --backends metal,cpu,vulkan \ + --iters 3 --warmup 1 \ + --name diar_streaming_sortformer_4spk-v2.1-publication +``` + +## Numerical Validation + +transcribe.cpp is validated tensor-by-tensor against NeMo on +`samples/sortformer-2spk-mix.wav` (a committed deterministic 2-speaker +mix with a 1.5 s overlap). All 6 checkpointed tensors fall within family +tolerance, and the streaming AOSC cache-compression internals were +additionally verified bit-exact against NeMo at the index level on a +full 39-minute AMI meeting (87 compression calls). Last validated at +commit `d42c3bb`. + +| Field | Value | +| --- | --- | +| Reference | NeMo, `nvidia/diar_streaming_sortformer_4spk-v2.1` | +| Dump script | `scripts/dump_reference_sortformer_nemo.py` | +| Manifest | `tests/golden/sortformer/diar_streaming_sortformer_4spk-v2.1.manifest.json` | +| Command | `uv run scripts/validate.py compare --family sortformer` | + +Selected tensors: + +| Tensor | Max abs diff | Mean abs diff | Notes | +| --- | ---: | ---: | --- | +| `enc.mel.in` | `0.000e+00` | `0.000e+00` | Exact (shape differs by NeMo pad_to=16, values identical) | +| `enc.fastconformer.out`| `3.263e-03` | `1.396e-04` | F32 accumulation over 17 Conformer blocks | +| `enc.encoder_proj.out` | `9.829e-04` | `1.242e-04` | Drift attenuates through the projection | +| `enc.transformer.out` | `1.128e-03` | `1.323e-04` | 18-layer Transformer head | +| `diar.preds_offline` | `2.961e-04` | `5.007e-06` | Final sigmoid probabilities (offline) | +| `diar.probs` | `2.961e-04` | `5.007e-06` | Streaming path output (== offline on a single-chunk clip) | + +## Known Limitations + +- **Maximum 4 speakers** (architectural cap). More than 4 concurrent + speakers will be merged into the 4 arrival-order slots. +- **No transcript.** Pair with an ASR model if you need speaker-attributed + text; native multitalker interop is planned, not shipped. +- **Speaker labels are arrival-order, not identities.** Labels are stable + within a recording, but there is no cross-recording speaker matching. +- **Perturbation-sensitive label continuity.** The streaming speaker + cache makes discrete near-tie decisions; different backends (Metal vs + CPU float order) can occasionally resolve a near-tie differently on a + given recording, changing label assignment mid-stream. Aggregate DER is + unaffected in our measurements; the shipped F16/Q8_0 tiers showed no + label-swap events on the 16-meeting acceptance set. +- **`LOW_LATENCY` is compute-heavy on CPU** (~1.2x realtime on Apple M4; + the 0.5 s chunks rebuild the compute graph often). Use Metal or a + higher-latency preset for offline files. +- **No batch fast path** (`run_batch`); multi-file CLI batches run + serially. + +## Reproduction + +### Convert + +Loads NVIDIA's NeMo checkpoint via `SortformerEncLabelModel.from_pretrained`. + +```bash +uv run --project scripts/envs/sortformer \ + scripts/convert-sortformer.py nvidia/diar_streaming_sortformer_4spk-v2.1 \ + --out models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf +``` + +### Quantize + +```bash +build/bin/transcribe-quantize \ + models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf \ + models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F16.gguf \ + --quant F16 +# repeat with Q8_0 +``` + +### Validate + +```bash +uv run scripts/validate.py all --family sortformer +``` + +### DER acceptance + +The full protocol (AMI ingest, forced-alignment RTTMs, reference run, +scoring) is documented with commands in +`docs/porting/families/sortformer.md`. Short form: + +```bash +uv run --project scripts/envs/sortformer scripts/diar/run_cpp_sortformer.py \ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ + --gguf models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf \ + --preset very_high_latency \ + --postprocessing-yaml scripts/diar/postprocessing/diar_streaming_sortformer_4spk-v2_dihard3-dev.yaml \ + --pred-dir reports/diar/pred/cpp-ami --out reports/diar/cpp-ami.jsonl +uv run scripts/diar/score_der.py \ + --manifest samples/diar/ami-ihm-test-fa.manifest.jsonl \ + --pred-dir reports/diar/pred/cpp-ami --out reports/diar/cpp-ami.score.json +``` + +### Run real-model tests + +```bash +cmake -B build -DTRANSCRIBE_BUILD_REAL_MODEL_TESTS=ON +cmake --build build + +TRANSCRIBE_SORTFORMER_GGUF=models/diar_streaming_sortformer_4spk-v2.1/diar_streaming_sortformer_4spk-v2.1-F32.gguf \ + ctest --test-dir build --output-on-failure -R sortformer +``` diff --git a/docs/porting/families/sortformer.md b/docs/porting/families/sortformer.md index 1d905629..118c2014 100644 --- a/docs/porting/families/sortformer.md +++ b/docs/porting/families/sortformer.md @@ -1,15 +1,25 @@ # Sortformer -Status: Stage 4 complete (offline + streaming) +Status: port complete (Stages 1-8; ship gate passed 2026-07-22) Intake signed off 2026-07-19: capability scope approved as drafted; acceptance set = AMI (DER + JER vs NeMo reference, plus prob-tensor parity). -Stages 1-4 done. Stage 4 offline checkpoint (tensor parity, 5/5) and streaming -checkpoint (AOSC/FIFO + `_compress_spkcache`, `diar.probs` parity, AMI DER) -both green. **C++ AMI DER 14.59% / JER 19.51%** (16 mtgs, very_high_latency, -forced-alignment RTTMs + dihard3 PP) reproduces the measured reference -14.83% / 19.89% within 0.24 pt (per-meeting median |Δ| ~0.02 pt). Next: Stage 5 -quants. +Stage 4 tensor parity 6/6; compression internals verified bit-exact vs a +neutral arbiter (residual #1). Stage 7 ref-dtype gate PASS: **C++ AMI DER +14.59% / JER 19.51%** (16 mtgs, very_high_latency, forced-alignment RTTMs + +dihard3 PP) vs measured reference 14.83% / 19.89% (gate: reference + 0.01). +Shipped matrix: **F32 + F16 + Q8_0** (k tiers withdrawn; see "Quant policy +(Stage 7)"). Private HF repo: +`handy-computer/diar_streaming_sortformer_4spk-v2.1-gguf`. + +Batch posture: **ACCEPTED GAP — no `run_batch()`** (user-approved Stage 4 +deferral; single-session `transcribe_run` is the shipped path; revisit with +multitalker interop). Streaming posture: **natively streaming — PASS**; the +chunk/lookahead contract is the preset menu in "Public API (run extension)" +(default = GGUF-shipped checkpoint cfg; very_high_latency ~30.4 s lookahead +... low_latency ~1.04 s), exposed via `transcribe_sortformer_stream_ext`. +Push-audio `transcribe_stream_*` entry point is future work (STREAM-slot +kind reserved by design). Streaming Sortformer is a **frame-level end-to-end neural speaker diarizer** (architecture pattern `encoder-diarizer`, new to this repo). It is NOT a @@ -84,6 +94,21 @@ rejected pre-clear (`run_validate`), preserving the previous result. Unit test: `tests/sortformer_stream_ext_unit.cpp` (`TRANSCRIBE_SORTFORMER_GGUF`-gated). +## Quant policy (Stage 7) + +Shipped matrix: **F32 (reference) + F16 + Q8_0** only +(`FAMILY_PRESETS["sortformer"]` in `scripts/lib/quant_policy.py`). The +k-quant tiers are withdrawn: this family's output depends on discrete +AOSC speaker-cache compression decisions, and k-tier weight error can +deterministically flip a near-tie pick and permute speaker labels +mid-stream. Observed at Q5_K_M on AMI TS3003b under the default CPU +operating point (9.47% -> 32.13% DER; deterministic across thread +counts, escaped by Metal float order and by high_latency). The flip is +chaotic in quant error (coarser Q4_K_M passed the same sample), so a +clean 16-meeting result cannot certify a k tier; the tiers also save +little disk (139 -> 92 MB) and are slower than Q8_0 on CPU. Full +evidence: `reports/diar/diar_streaming_sortformer_4spk-v2.1.ami-ihm-test-fa.summary.md`. + A future push-audio entry point (`transcribe_stream_begin/feed`, live diarization) registers a separate STREAM-slot kind taking the same preset enum; the multitalker interop path (raw T x 4 supervision into parakeet's @@ -156,10 +181,21 @@ uv run scripts/diar/score_der.py \ # -> REF DER 14.83% / JER 19.89% ; C++ DER 14.59% / JER 19.51% (within 0.24 pt). ``` -Benchmarks: +Benchmarks (publication scope; on-doc artifacts +`reports/perf/apple-m4/diar-streaming-sortformer-4spk-v2-1-publication_*.json`): ```bash -TODO +uv run scripts/bench/run.py \ + --models diar_streaming_sortformer_4spk-v2.1 \ + --quants f16,q8_0 \ + --samples jfk,dots \ + --backends metal,cpu,vulkan \ + --iters 3 --warmup 1 \ + --name diar_streaming_sortformer_4spk-v2.1-publication +# apple-m4: Metal ~170x (jfk) / ~110x (dots) rtf; CPU Q8_0 100x/51x, F16 80x/44x. +# Note: low_latency preset runs ~1.2x realtime on CPU (per-chunk graph +# rebuild dominates at 0.5 s chunks); the shipped default and +# very_high_latency are unaffected. ``` ## Capability Validation @@ -178,15 +214,16 @@ TODO | Streaming diarization (<=4 spk) | online, AOSC/FIFO cache | port streaming diarize vs NeMo reference at a fixed preset (e.g. 1.04s latency) | prob-tensor parity vs reference; DER + JER within tolerance on AMI | MUST PASS | PASS — `diar.probs` parity (single-chunk 2.96e-4; multi-chunk `small` preset ~3.9e-4 uniform); AMI DER 14.59% / JER 19.51% vs ref 14.83% / 19.89% | | Offline diarization | single large chunk | port diarize vs NeMo reference | prob-tensor parity vs reference | MUST PASS | PASS — `diar.preds_offline` 2.96e-4 (validate.py all 6/6) | | Speaker-activity tensor | `include_tensor_outputs` | T x 4 sigmoid probs, port vs reference on the same audio | max-abs-diff within tolerance, columns arrival-order aligned | MUST PASS | PASS — `diar.probs` [150x4] within tolerance | -| Multitalker interop | feed diar supervision | Sortformer T x 4 drives parakeet multitalker layer-0 speaker kernel | multitalker emits speaker-attributed transcript | OUT OF SCOPE — owned by the multitalker port; unblocked once both land | TODO | -| Transcription (text) | n/a | n/a | model produces no text output | OUT OF SCOPE — not a transcription model | TODO | -| Translation | n/a | n/a | n/a | OUT OF SCOPE — not a transcription model | TODO | -| Timestamps (transcription) | n/a | n/a | diarization segment times are intrinsic, not a transcript-timestamp capability | OUT OF SCOPE — not a transcription model | TODO | -| >4 speakers | n/a | n/a | hard cap at 4 speakers | OUT OF SCOPE — architectural 4-speaker cap | TODO | +| Multitalker interop | feed diar supervision | Sortformer T x 4 drives parakeet multitalker layer-0 speaker kernel | multitalker emits speaker-attributed transcript | OUT OF SCOPE — owned by the multitalker port; unblocked once both land | N/A (deferred to multitalker port) | +| Transcription (text) | n/a | n/a | model produces no text output | OUT OF SCOPE — not a transcription model | N/A | +| Translation | n/a | n/a | n/a | OUT OF SCOPE — not a transcription model | N/A | +| Timestamps (transcription) | n/a | n/a | diarization segment times are intrinsic, not a transcript-timestamp capability | OUT OF SCOPE — not a transcription model | N/A | +| >4 speakers | n/a | n/a | hard cap at 4 speakers | OUT OF SCOPE — architectural 4-speaker cap | N/A | +| Batch (`run_batch`) | n/a | n/a | parallel multi-file fast path | ACCEPTED GAP — user-approved Stage 4 deferral; single-session runs only | N/A (deferred) | ## Reference baselines (Stage 2 Oracle) -- **Tensor parity oracle** (`build/validate/sortformer/diar_streaming_sortformer_4spk-v2.1/sortformer-2spk-mix/`): mel + fastconformer + encoder_proj + transformer + offline preds + streaming `diar.probs` [150×4]. Offline preds == streaming probs on this short clip (single chunk). Tolerances in `tests/tolerances/sortformer.json` (provisional). +- **Tensor parity oracle** (`build/validate/sortformer/diar_streaming_sortformer_4spk-v2.1/sortformer-2spk-mix/`): mel + fastconformer + encoder_proj + transformer + offline preds + streaming `diar.probs` [150×4]. Offline preds == streaming probs on this short clip (single chunk). Tolerances in `tests/tolerances/sortformer.json` (finalized Stage 4; no `_provisional` flags). - **Measured reference DER/JER** on AMI ihm test (16 meetings, 9.06 h), preset `very_high_latency`, collar 0.0s / overlap included (NVIDIA AMI protocol). The reference pipeline REPRODUCES the published number (15.90 @ very_high_latency AMI-IHM). Full 2x2 decomposition of the two protocol levers: | predictions | vs manual RTTMs (diarizers-community/ami) | vs forced-alignment RTTMs (nttcslab-sp) | diff --git a/scripts/hf_cards/diar_streaming_sortformer_4spk-v2.1.yaml b/scripts/hf_cards/diar_streaming_sortformer_4spk-v2.1.yaml new file mode 100644 index 00000000..61a9ab43 --- /dev/null +++ b/scripts/hf_cards/diar_streaming_sortformer_4spk-v2.1.yaml @@ -0,0 +1,86 @@ +# Spec for the HF README of handy-computer/diar_streaming_sortformer_4spk-v2.1-gguf. +# Consumed by scripts/hf_cards/generate.py. +# +# Diarizer family: metric is DER (not WER); pipeline_tag follows the +# upstream NVIDIA card's diarization tagging rather than ASR. + +hf_repo: nvidia/diar_streaming_sortformer_4spk-v2.1 +target_repo: handy-computer/diar_streaming_sortformer_4spk-v2.1-gguf +transcribe_docs_url: https://github.com/handy-computer/transcribe.cpp/blob/main/docs/models/diar_streaming_sortformer_4spk-v2.1.md + +upstream_commit: fafaab5 +pin_date: 2026-07-19 + +# Validation pin for the most recent upload. Updated on each release — +# older HF revisions carry whatever value was current at their upload time. +validation: + reference: NeMo + commit: d42c3bb + date: 2026-07-22 + +license: other +license_display: NVIDIA Open Model License +pipeline_tag: voice-activity-detection +languages: + - en +tags: + - gguf + - transcribe.cpp + - speaker-diarization + - diarization + - streaming + - sortformer + - fastconformer + +metric: DER + +summary: | + Streaming speaker diarization: who spoke when, for up to 4 speakers. + A FastConformer encoder with an 18-layer Transformer head emitting + per-frame speaker-activity probabilities, running online with an + Arrival-Order Speaker Cache (AOSC) + FIFO. NOT a transcription model: + a run produces speaker segments (start, end, speaker id in arrival + order), no text. Takes 16 kHz mono WAV. + +default_quant_index: 2 # Q8_0 + +# Capability flags for the transcribe_cpp metadata block. +capabilities: + streaming: true + translate: false + lang_detect: false + timestamps: none # segment times are diarization output, not transcript timestamps + +# Speedup-over-realtime (×RT) per rig/backend, hand-authored from the bench; +# published raw as rtf_ in the metadata block. +perf: + m4: + cpu: 51 + metal: 110 + +wer: + source: AMI IHM test + metadata_key: ami_ihm_test + notes: | + DER measured on the full AMI IHM test set (16 meetings, ~9 h) against + forced-alignment RTTMs with dihard3-dev post-processing, collar 0.0, + overlap scored, at the very_high_latency operating point. Measured NeMo + reference under the identical protocol: 14.83% DER / 19.89% JER; the + C++ F32 port scores 14.59% / 19.51%. Published DER numbers vary with + RTTM source and post-processing; compare like with like. Only + near-reference tiers ship for this family (k-quant tiers withdrawn; + see the transcribe.cpp family doc, "Quant policy (Stage 7)"). + +quants: + - name: F32 + filename: diar_streaming_sortformer_4spk-v2.1-F32.gguf + size: 471 MB + wer: 14.59% + - name: F16 + filename: diar_streaming_sortformer_4spk-v2.1-F16.gguf + size: 237 MB + wer: 14.23% + - name: Q8_0 + filename: diar_streaming_sortformer_4spk-v2.1-Q8_0.gguf + size: 139 MB + wer: 14.73% diff --git a/scripts/lib/quant_policy.py b/scripts/lib/quant_policy.py index 2e85c2e1..472a3eef 100644 --- a/scripts/lib/quant_policy.py +++ b/scripts/lib/quant_policy.py @@ -26,9 +26,10 @@ # `general.architecture` KV string written by the family's converter # (NOT the family directory name; e.g. cohere converts to "cohere_asr"). # Used when k-quant tiers would degenerate into Q8_0 because the model's -# row sizes do not divide the k-quant super-block size (256). Shipping -# four near-duplicate Q-tier GGUFs is wasted disk; restrict the matrix -# to the tiers that actually differ for that architecture. +# row sizes do not divide the k-quant super-block size (256), or when a +# family's output is too sensitive to weight perturbation to certify the +# k tiers. Shipping four near-duplicate Q-tier GGUFs is wasted disk; +# restrict the matrix to the tiers that actually earn their place. FAMILY_PRESETS: dict[str, tuple[str, ...]] = { # moonshine-tiny: hidden=288 / intermediate=1152 / vocab=32768 — none # divide 256, so Q6_K/Q5_K_M/Q4_K_M all fall back to Q8_0 storage. @@ -38,6 +39,15 @@ # land within ~5% of Q8_0 file size (measured Stage 5). Drop the K # tiers to avoid shipping near-duplicate GGUFs. "moonshine_streaming": ("F16", "Q8_0"), + # sortformer (streaming diarizer): output depends on discrete AOSC + # speaker-cache compression decisions; k-quant weight error can + # deterministically flip a near-tie pick and permute speaker labels + # mid-stream (Stage 7: Q5_K_M swapped labels on AMI TS3003b at the + # default CPU operating point, 9.5%->32.1% DER; flip risk is chaotic + # in quant error, so clean-on-sample Q6_K/Q4_K_M cannot be certified + # either). K tiers also save little (Q8_0 139MB -> Q4_K_M 92MB) and + # are slower than Q8_0 on CPU. Ship only the near-reference tiers. + "sortformer": ("F16", "Q8_0"), } From 2d02b0b94fd0e96598742ffa62a9cc0f565230e9 Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Sat, 25 Jul 2026 01:26:13 -0700 Subject: [PATCH 5/7] clang format --- src/arch/sortformer/model.cpp | 219 +++++++++++++++------------ src/arch/sortformer/sortformer.h | 69 +++++---- src/arch/sortformer/stream.cpp | 122 ++++++++------- src/arch/sortformer/weights.cpp | 51 ++++--- src/arch/sortformer/weights.h | 62 ++++---- src/transcribe-arch.cpp | 6 +- tests/sortformer_stream_ext_unit.cpp | 13 +- 7 files changed, 295 insertions(+), 247 deletions(-) diff --git a/src/arch/sortformer/model.cpp b/src/arch/sortformer/model.cpp index 95af5d73..e59c746b 100644 --- a/src/arch/sortformer/model.cpp +++ b/src/arch/sortformer/model.cpp @@ -25,8 +25,8 @@ #include #include -#include #include +#include #include #include #include @@ -41,7 +41,7 @@ extern const Arch arch; namespace pk = transcribe::parakeet; namespace conf = transcribe::conformer; -static constexpr float kBnEps = 1e-5f; +static constexpr float kBnEps = 1e-5f; static constexpr char k_default_variant[] = "diar_streaming_sortformer_4spk-v2.1"; SortformerModel::~SortformerModel() { @@ -98,13 +98,13 @@ void fill_conformer_hp(const SortformerHParams & s, pk::ParakeetHParams & p) { p.enc_conv_norm_type = pk::ParakeetHParams::ConvNormType::BatchNorm; // Frontend fields build_encoder_graph's pre_encode geometry reads. - p.fe_num_mels = s.fe_num_mels; - p.fe_sample_rate = s.fe_sample_rate; - p.fe_n_fft = s.fe_n_fft; - p.fe_win_length = s.fe_win_length; - p.fe_hop_length = s.fe_hop_length; - p.fe_normalize = s.fe_normalize; - p.fe_dither = s.fe_dither; + p.fe_num_mels = s.fe_num_mels; + p.fe_sample_rate = s.fe_sample_rate; + p.fe_n_fft = s.fe_n_fft; + p.fe_win_length = s.fe_win_length; + p.fe_hop_length = s.fe_hop_length; + p.fe_normalize = s.fe_normalize; + p.fe_dither = s.fe_dither; p.fe_pre_emphasis = s.fe_pre_emphasis; } @@ -120,15 +120,16 @@ transcribe_status load_conformer_weights(ggml_context * ctx, const pk::ParakeetH } return t; }; -#define G(dst, nm) \ - do { \ - (dst) = get(nm); \ - if ((dst) == nullptr) return TRANSCRIBE_ERR_GGUF; \ +#define G(dst, nm) \ + do { \ + (dst) = get(nm); \ + if ((dst) == nullptr) \ + return TRANSCRIBE_ERR_GGUF; \ } while (0) -#define GL(dst, fmt, i) \ - do { \ +#define GL(dst, fmt, i) \ + do { \ std::snprintf(name, sizeof(name), fmt, i); \ - G(dst, name); \ + G(dst, name); \ } while (0) auto & pe = w.pre_encode; @@ -257,7 +258,7 @@ ggml_tensor * linear(ggml_context * ctx, ggml_tensor * W, ggml_tensor * x, ggml_ // One post-LN Transformer encoder block (no positional encoding, full // attention). x ne = [d, T]. ggml_tensor * tf_block(ggml_context * ctx, const SortformerTfBlock & b, ggml_tensor * x, int d, int n_head, int T) { - const int head_dim = d / n_head; + const int head_dim = d / n_head; const float attn_scale = std::sqrt(std::sqrt(static_cast(head_dim))); const float inv_scale = 1.0f / attn_scale; @@ -275,13 +276,13 @@ ggml_tensor * tf_block(ggml_context * ctx, const SortformerTfBlock & b, ggml_ten q = ggml_scale(ctx, q, inv_scale); k = ggml_scale(ctx, k, inv_scale); - ggml_tensor * kq = ggml_mul_mat(ctx, k, q); // [T_k, T_q, H] - kq = ggml_soft_max(ctx, kq); // softmax over keys (ne0) + ggml_tensor * kq = ggml_mul_mat(ctx, k, q); // [T_k, T_q, H] + kq = ggml_soft_max(ctx, kq); // softmax over keys (ne0) - ggml_tensor * v_t = ggml_cont(ctx, ggml_permute(ctx, v, 1, 0, 2, 3)); // [T, hd, H] - ggml_tensor * ctx_out = ggml_mul_mat(ctx, v_t, kq); // [hd, T_q, H] - ctx_out = ggml_cont(ctx, ggml_permute(ctx, ctx_out, 0, 2, 1, 3)); // [hd, H, T] - ctx_out = ggml_reshape_2d(ctx, ctx_out, d, T); // [d, T] + ggml_tensor * v_t = ggml_cont(ctx, ggml_permute(ctx, v, 1, 0, 2, 3)); // [T, hd, H] + ggml_tensor * ctx_out = ggml_mul_mat(ctx, v_t, kq); // [hd, T_q, H] + ctx_out = ggml_cont(ctx, ggml_permute(ctx, ctx_out, 0, 2, 1, 3)); // [hd, H, T] + ctx_out = ggml_reshape_2d(ctx, ctx_out, d, T); // [d, T] ggml_tensor * attn_out = linear(ctx, b.attn_o_w, ctx_out, b.attn_o_b); @@ -309,12 +310,12 @@ ggml_tensor * tf_block(ggml_context * ctx, const SortformerTfBlock & b, ggml_ten conf::ConvPolicy sf_conv_policy(const char * backend) { conf::ConvPolicy policy{}; - policy.direct_pw = conf::detect_direct_pw(backend); + policy.direct_pw = conf::detect_direct_pw(backend); // Parakeet block/pre_encode depthwise defaults (encoder.cpp:50-66). policy.direct_dw_in_block = conf::resolve_conv_direct("TRANSCRIBE_CONV_DIRECT_DW", "TRANSCRIBE_CONV_NO_DIRECT_DW", /*backend_default=*/true); - const bool is_metal = backend != nullptr && - (std::strstr(backend, "Metal") != nullptr || std::strstr(backend, "metal") != nullptr); + const bool is_metal = + backend != nullptr && (std::strstr(backend, "Metal") != nullptr || std::strstr(backend, "metal") != nullptr); policy.direct_dw_in_pre_encode = conf::resolve_conv_direct( "TRANSCRIBE_CONV_DIRECT_DW", "TRANSCRIBE_CONV_NO_DIRECT_DW", /*backend_default=*/!is_metal); policy.causal_pre_encode = false; // NEST offline subsample (Regular att) @@ -323,36 +324,62 @@ conf::ConvPolicy sf_conv_policy(const char * backend) { conf::PreEncodeView sf_pre_view(const pk::ParakeetPreEncode & pe) { conf::PreEncodeView v; - v.conv0_w = pe.conv0_w; v.conv0_b = pe.conv0_b; - v.conv2_w = pe.conv2_w; v.conv2_b = pe.conv2_b; - v.conv3_w = pe.conv3_w; v.conv3_b = pe.conv3_b; - v.conv5_w = pe.conv5_w; v.conv5_b = pe.conv5_b; - v.conv6_w = pe.conv6_w; v.conv6_b = pe.conv6_b; - v.out_w = pe.out_w; v.out_b = pe.out_b; + v.conv0_w = pe.conv0_w; + v.conv0_b = pe.conv0_b; + v.conv2_w = pe.conv2_w; + v.conv2_b = pe.conv2_b; + v.conv3_w = pe.conv3_w; + v.conv3_b = pe.conv3_b; + v.conv5_w = pe.conv5_w; + v.conv5_b = pe.conv5_b; + v.conv6_w = pe.conv6_w; + v.conv6_b = pe.conv6_b; + v.out_w = pe.out_w; + v.out_b = pe.out_b; return v; } conf::BlockView sf_block_view(const pk::ParakeetBlock & b) { conf::BlockView v; - v.norm_ff1_w = b.norm_ff1_w; v.norm_ff1_b = b.norm_ff1_b; - v.ff1_lin1_w = b.ff1_lin1_w; v.ff1_lin1_b = b.ff1_lin1_b; - v.ff1_lin2_w = b.ff1_lin2_w; v.ff1_lin2_b = b.ff1_lin2_b; - v.norm_attn_w = b.norm_attn_w; v.norm_attn_b = b.norm_attn_b; - v.attn_q_w = b.attn_q_w; v.attn_q_b = b.attn_q_b; - v.attn_k_w = b.attn_k_w; v.attn_k_b = b.attn_k_b; - v.attn_v_w = b.attn_v_w; v.attn_v_b = b.attn_v_b; - v.attn_out_w = b.attn_out_w; v.attn_out_b = b.attn_out_b; - v.attn_pos_w = b.attn_pos_w; v.attn_pos_u = b.attn_pos_u; v.attn_pos_v = b.attn_pos_v; - v.norm_conv_w = b.norm_conv_w; v.norm_conv_b = b.norm_conv_b; - v.conv_pw1_w = b.conv_pw1_w; v.conv_pw1_b = b.conv_pw1_b; - v.conv_dw_w = b.conv_dw_w; v.conv_dw_b = b.conv_dw_b; - v.conv_pw2_w = b.conv_pw2_w; v.conv_pw2_b = b.conv_pw2_b; - v.conv_bn_fused_scale = b.conv_bn_fused_scale; v.conv_bn_fused_bias = b.conv_bn_fused_bias; - v.conv_ln_w = b.conv_bn_w; v.conv_ln_b = b.conv_bn_b; - v.norm_ff2_w = b.norm_ff2_w; v.norm_ff2_b = b.norm_ff2_b; - v.ff2_lin1_w = b.ff2_lin1_w; v.ff2_lin1_b = b.ff2_lin1_b; - v.ff2_lin2_w = b.ff2_lin2_w; v.ff2_lin2_b = b.ff2_lin2_b; - v.norm_out_w = b.norm_out_w; v.norm_out_b = b.norm_out_b; + v.norm_ff1_w = b.norm_ff1_w; + v.norm_ff1_b = b.norm_ff1_b; + v.ff1_lin1_w = b.ff1_lin1_w; + v.ff1_lin1_b = b.ff1_lin1_b; + v.ff1_lin2_w = b.ff1_lin2_w; + v.ff1_lin2_b = b.ff1_lin2_b; + v.norm_attn_w = b.norm_attn_w; + v.norm_attn_b = b.norm_attn_b; + v.attn_q_w = b.attn_q_w; + v.attn_q_b = b.attn_q_b; + v.attn_k_w = b.attn_k_w; + v.attn_k_b = b.attn_k_b; + v.attn_v_w = b.attn_v_w; + v.attn_v_b = b.attn_v_b; + v.attn_out_w = b.attn_out_w; + v.attn_out_b = b.attn_out_b; + v.attn_pos_w = b.attn_pos_w; + v.attn_pos_u = b.attn_pos_u; + v.attn_pos_v = b.attn_pos_v; + v.norm_conv_w = b.norm_conv_w; + v.norm_conv_b = b.norm_conv_b; + v.conv_pw1_w = b.conv_pw1_w; + v.conv_pw1_b = b.conv_pw1_b; + v.conv_dw_w = b.conv_dw_w; + v.conv_dw_b = b.conv_dw_b; + v.conv_pw2_w = b.conv_pw2_w; + v.conv_pw2_b = b.conv_pw2_b; + v.conv_bn_fused_scale = b.conv_bn_fused_scale; + v.conv_bn_fused_bias = b.conv_bn_fused_bias; + v.conv_ln_w = b.conv_bn_w; + v.conv_ln_b = b.conv_bn_b; + v.norm_ff2_w = b.norm_ff2_w; + v.norm_ff2_b = b.norm_ff2_b; + v.ff2_lin1_w = b.ff2_lin1_w; + v.ff2_lin1_b = b.ff2_lin1_b; + v.ff2_lin2_w = b.ff2_lin2_w; + v.ff2_lin2_b = b.ff2_lin2_b; + v.norm_out_w = b.norm_out_w; + v.norm_out_b = b.norm_out_b; return v; } @@ -387,8 +414,8 @@ struct PreEncodeBuild { PreEncodeBuild build_pre_encode_graph(ggml_context * ctx, SortformerModel & m, int M) { PreEncodeBuild b{}; - const int n_mels = m.conformer_hp.fe_num_mels; - ggml_tensor * mel_in = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, M, n_mels, 1, 1); + const int n_mels = m.conformer_hp.fe_num_mels; + ggml_tensor * mel_in = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, M, n_mels, 1, 1); ggml_set_name(mel_in, "chunk.mel.in"); ggml_set_input(mel_in); const conf::ConvPolicy policy = sf_conv_policy(m.backend.c_str()); @@ -416,8 +443,8 @@ struct StreamInferBuild { StreamInferBuild build_stream_infer_graph(ggml_context * ctx, SortformerModel & m, int T_concat) { StreamInferBuild b{}; - const int ed = m.conformer_hp.enc_d_model; - ggml_tensor * concat_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, ed, T_concat); + const int ed = m.conformer_hp.enc_d_model; + ggml_tensor * concat_in = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, ed, T_concat); ggml_set_name(concat_in, "stream.concat.in"); ggml_set_input(concat_in); @@ -430,10 +457,10 @@ StreamInferBuild build_stream_infer_graph(ggml_context * ctx, SortformerModel & ggml_set_input(pos_emb_in); conf::BlockParams bp{}; - bp.d_model = ed; - bp.n_head = m.conformer_hp.enc_n_heads; - bp.conv_kernel = m.conformer_hp.enc_conv_kernel; - bp.kv_type = GGML_TYPE_F32; + bp.d_model = ed; + bp.n_head = m.conformer_hp.enc_n_heads; + bp.conv_kernel = m.conformer_hp.enc_conv_kernel; + bp.kv_type = GGML_TYPE_F32; bool enc_use_flash = true, dec_use_flash = false; transcribe::flash::apply_env_overrides(enc_use_flash, dec_use_flash); bp.use_flash = enc_use_flash; @@ -459,9 +486,9 @@ StreamInferBuild build_stream_infer_graph(ggml_context * ctx, SortformerModel & // Diar head (forward_speaker_sigmoids). All frames valid in sync mode, so // the encoder_mask multiply is a no-op. - ggml_tensor * h = ggml_relu(ctx, t); - h = linear(ctx, m.weights.fc1_w, h, m.weights.fc1_b); - h = ggml_relu(ctx, h); + ggml_tensor * h = ggml_relu(ctx, t); + h = linear(ctx, m.weights.fc1_w, h, m.weights.fc1_b); + h = ggml_relu(ctx, h); ggml_tensor * s = linear(ctx, m.weights.single_spk_head_w, h, m.weights.single_spk_head_b); ggml_tensor * preds = ggml_sigmoid(ctx, s); // [n_spk, T_concat] @@ -504,14 +531,14 @@ transcribe_status load(Loader & loader, const transcribe_model_load_params * par // Mel front-end (NeMo AudioToMelSpectrogramPreprocessor; normalize=none). { transcribe::MelConfig cfg{}; - cfg.sample_rate = m->hparams.fe_sample_rate; - cfg.num_mels = m->hparams.fe_num_mels; - cfg.n_fft = m->hparams.fe_n_fft; - cfg.win_length = m->hparams.fe_win_length; - cfg.hop_length = m->hparams.fe_hop_length; - cfg.pre_emphasis = m->hparams.fe_pre_emphasis; - cfg.normalize = m->hparams.fe_normalize; - cfg.pad_mode = "constant"; + cfg.sample_rate = m->hparams.fe_sample_rate; + cfg.num_mels = m->hparams.fe_num_mels; + cfg.n_fft = m->hparams.fe_n_fft; + cfg.win_length = m->hparams.fe_win_length; + cfg.hop_length = m->hparams.fe_hop_length; + cfg.pre_emphasis = m->hparams.fe_pre_emphasis; + cfg.normalize = m->hparams.fe_normalize; + cfg.pad_mode = "constant"; // NeMo AudioToMelSpectrogramPreprocessor uses ceil(n/hop) framing; // the mel tensor is pad_to=16 padded but forward() trims it back to // the real length before the encoder, so feeding the ceil length is @@ -521,8 +548,8 @@ transcribe_status load(Loader & loader, const transcribe_model_load_params * par } gguf_init_params init_params{}; - init_params.no_alloc = true; - init_params.ctx = &m->ctx_meta; + init_params.no_alloc = true; + init_params.ctx = &m->ctx_meta; gguf_context * gguf_data = gguf_init_from_file(loader.path().c_str(), init_params); if (gguf_data == nullptr) { return TRANSCRIBE_ERR_GGUF; @@ -571,12 +598,13 @@ transcribe_status load(Loader & loader, const transcribe_model_load_params * par } m->t_load_us = ggml_time_us() - t_load_start; - *out_model = m.release(); + *out_model = m.release(); return TRANSCRIBE_OK; } -transcribe_status init_context(transcribe_model * model, const transcribe_session_params * params, - transcribe_session ** out_ctx) { +transcribe_status init_context(transcribe_model * model, + const transcribe_session_params * params, + transcribe_session ** out_ctx) { if (model->arch != &arch) { return TRANSCRIBE_ERR_INVALID_ARG; } @@ -584,7 +612,7 @@ transcribe_status init_context(transcribe_model * model, const transcribe_sessio pc->model = model; pc->n_threads = params->n_threads; pc->kv_type = params->kv_type; - *out_ctx = pc.release(); + *out_ctx = pc.release(); return TRANSCRIBE_OK; } @@ -592,7 +620,7 @@ transcribe_status init_context(transcribe_model * model, const transcribe_sessio // the DER-grade dihard3 post-processing is a later, streaming-checkpoint // concern). probs is row-major [T, n_spk]. Emits one contiguous run per // speaker as a speaker_segment row. -static void probs_to_speaker_segments(transcribe_session * pc, +static void probs_to_speaker_segments(transcribe_session * pc, const std::vector & probs, int T, int n_spk, @@ -667,9 +695,9 @@ static transcribe_status run_offline_forward(SortformerSession * pc, SortformerM } ggml_tensor * tf_out = x; - ggml_tensor * h = ggml_relu(ctx, tf_out); - h = linear(ctx, pm->weights.fc1_w, h, pm->weights.fc1_b); - h = ggml_relu(ctx, h); + ggml_tensor * h = ggml_relu(ctx, tf_out); + h = linear(ctx, pm->weights.fc1_w, h, pm->weights.fc1_b); + h = ggml_relu(ctx, h); ggml_tensor * s = linear(ctx, pm->weights.single_spk_head_w, h, pm->weights.single_spk_head_b); ggml_tensor * preds = ggml_sigmoid(ctx, s); @@ -714,12 +742,16 @@ static transcribe_status run_offline_forward(SortformerSession * pc, SortformerM // proj + transformer + head) over the [spkcache|fifo|chunk] concat, and drives // the host streaming_update_sync / _compress_spkcache state machine. Emits the // accumulated T x n_spk probs as diar.probs and the speaker segments. -static transcribe_status run_streaming(SortformerSession * pc, SortformerModel * pm, int mel_n_mels, int mel_n_frames, - double ms_per_frame, transcribe_sortformer_preset preset) { - const SortformerStreamParams P = resolve_stream_params(pm->hparams, preset); - const int ed = pm->conformer_hp.enc_d_model; - const int n_spk = pm->hparams.max_speakers; - const int sub = pm->hparams.enc_subsampling_factor; +static transcribe_status run_streaming(SortformerSession * pc, + SortformerModel * pm, + int mel_n_mels, + int mel_n_frames, + double ms_per_frame, + transcribe_sortformer_preset preset) { + const SortformerStreamParams P = resolve_stream_params(pm->hparams, preset); + const int ed = pm->conformer_hp.enc_d_model; + const int n_spk = pm->hparams.max_speakers; + const int sub = pm->hparams.enc_subsampling_factor; const int feat_len = mel_n_frames; if (sub <= 0 || P.chunk_len <= 0) { @@ -744,8 +776,8 @@ static transcribe_status run_streaming(SortformerSession * pc, SortformerModel * const int M = win_hi - win_lo; // Diar-frame context: left_offset is a multiple of sub -> round is // exact; right_offset may be short on the final chunk -> ceil. - const int lc = (left_offset + sub / 2) / sub; - const int rc = (right_offset + sub - 1) / sub; + const int lc = (left_offset + sub / 2) / sub; + const int rc = (right_offset + sub - 1) / sub; if (pc->compute_ctx != nullptr) { ggml_free(pc->compute_ctx); @@ -829,8 +861,7 @@ static transcribe_status run_streaming(SortformerSession * pc, SortformerModel * return TRANSCRIBE_ERR_GGUF; } pc->stream_preds_host.resize(static_cast(T_concat) * n_spk); - ggml_backend_tensor_get(B.preds, pc->stream_preds_host.data(), 0, - pc->stream_preds_host.size() * sizeof(float)); + ggml_backend_tensor_get(B.preds, pc->stream_preds_host.data(), 0, pc->stream_preds_host.size() * sizeof(float)); transcribe::debug::pop_name_prefix(); @@ -858,7 +889,9 @@ static transcribe_status run_streaming(SortformerSession * pc, SortformerModel * return TRANSCRIBE_OK; } -transcribe_status run(transcribe_session * session, const float * pcm, int n_samples, +transcribe_status run(transcribe_session * session, + const float * pcm, + int n_samples, const transcribe_run_params * params) { auto * pc = static_cast(session); auto * pm = static_cast(session->model); @@ -891,8 +924,8 @@ transcribe_status run(transcribe_session * session, const float * pcm, int n_sam return mst; } - const double ms_per_frame = 1000.0 * static_cast(pm->hparams.frame_hop) / - static_cast(pm->hparams.fe_sample_rate); + const double ms_per_frame = + 1000.0 * static_cast(pm->hparams.frame_hop) / static_cast(pm->hparams.fe_sample_rate); // Offline forward: parity dumps only. Gated behind an explicit env // (set by validate.py cmd_cpp) because it runs full-context O(T^2) diff --git a/src/arch/sortformer/sortformer.h b/src/arch/sortformer/sortformer.h index 33e2cebf..9d044c34 100644 --- a/src/arch/sortformer/sortformer.h +++ b/src/arch/sortformer/sortformer.h @@ -13,11 +13,11 @@ #include "../parakeet/encoder.h" // build_encoder_graph #include "../parakeet/weights.h" // ParakeetHParams / ParakeetWeights (conformer) -#include "transcribe/sortformer.h" // public preset enum + run ext #include "transcribe-backend.h" #include "transcribe-mel.h" #include "transcribe-model.h" #include "transcribe-session.h" +#include "transcribe/sortformer.h" // public preset enum + run ext #include "weights.h" #include @@ -76,38 +76,38 @@ struct SortformerModel final : public transcribe_model { // change will expose the presets via a stream extension). All lengths are // in 80 ms diar frames. struct SortformerStreamParams { - int chunk_len = 188; - int chunk_left_context = 1; - int chunk_right_context = 1; - int fifo_len = 0; - int spkcache_len = 188; - int spkcache_update_period = 188; - int spkcache_sil_frames_per_spk = 3; - float sil_threshold = 0.2f; - float pred_score_threshold = 0.25f; - float scores_boost_latest = 0.05f; - float strong_boost_rate = 0.75f; - float weak_boost_rate = 1.5f; - float min_pos_scores_rate = 0.5f; - int max_index = 99999; + int chunk_len = 188; + int chunk_left_context = 1; + int chunk_right_context = 1; + int fifo_len = 0; + int spkcache_len = 188; + int spkcache_update_period = 188; + int spkcache_sil_frames_per_spk = 3; + float sil_threshold = 0.2f; + float pred_score_threshold = 0.25f; + float scores_boost_latest = 0.05f; + float strong_boost_rate = 0.75f; + float weak_boost_rate = 1.5f; + float min_pos_scores_rate = 0.5f; + int max_index = 99999; }; // Host-side streaming state (AOSC speaker cache + FIFO), mirroring NeMo's // StreamingSortformerState. Embeddings are 512-dim pre_encode outputs stored // row-major [n_frames, enc_d_model]; preds are [n_frames, n_spk]. struct SortformerStreamState { - std::vector spkcache; // [spkcache_n * D] - std::vector spkcache_preds; // [spkcache_n * S] - int spkcache_n = 0; + std::vector spkcache; // [spkcache_n * D] + std::vector spkcache_preds; // [spkcache_n * S] + int spkcache_n = 0; bool spkcache_preds_init = false; // NeMo: spkcache_preds is None until first compress - std::vector fifo; // [fifo_n * D] - std::vector fifo_preds; // [fifo_n * S] + std::vector fifo; // [fifo_n * D] + std::vector fifo_preds; // [fifo_n * S] int fifo_n = 0; - std::vector mean_sil_emb; // [D] + std::vector mean_sil_emb; // [D] int64_t n_sil_frames = 0; - std::vector total_preds; // [T_total * S], accumulated chunk preds - int total_n = 0; - int compress_count = 0; // # _compress_spkcache calls (parity-dump index) + std::vector total_preds; // [T_total * S], accumulated chunk preds + int total_n = 0; + int compress_count = 0; // # _compress_spkcache calls (parity-dump index) void reset(int emb_dim) { spkcache.clear(); @@ -120,7 +120,7 @@ struct SortformerStreamState { mean_sil_emb.assign(static_cast(emb_dim), 0.0f); n_sil_frames = 0; total_preds.clear(); - total_n = 0; + total_n = 0; compress_count = 0; } }; @@ -135,9 +135,16 @@ SortformerStreamParams resolve_stream_params(const SortformerHParams & hp, trans // Exact host ports of the NeMo sync-streaming primitives (batch=1). All // embeddings row-major [n_frames, emb_dim]; preds row-major [n_frames, n_spk]. // Defined in stream.cpp. -void streaming_update_sync(SortformerStreamState & st, const SortformerStreamParams & p, int n_spk, int emb_dim, - const std::vector & chunk_embs, int T_diar, - const std::vector & preds, int T_concat, int lc, int rc); +void streaming_update_sync(SortformerStreamState & st, + const SortformerStreamParams & p, + int n_spk, + int emb_dim, + const std::vector & chunk_embs, + int T_diar, + const std::vector & preds, + int T_concat, + int lc, + int rc); // Compress st.spkcache (spkcache_n > spkcache_len frames) in place down to // spkcache_len frames, matching sortformer_modules._compress_spkcache. @@ -157,9 +164,9 @@ struct SortformerSession final : public transcribe_session { // Streaming scratch (AOSC/FIFO path). SortformerStreamState stream; - std::vector chunk_mel_buf; // [n_mels * M] mel window for Graph A - std::vector chunk_embs_host; // [T_diar * enc_d_model] pre_encode readback - std::vector concat_host; // [T_concat * enc_d_model] Graph B input + std::vector chunk_mel_buf; // [n_mels * M] mel window for Graph A + std::vector chunk_embs_host; // [T_diar * enc_d_model] pre_encode readback + std::vector concat_host; // [T_concat * enc_d_model] Graph B input std::vector stream_preds_host; // [T_concat * n_spk] Graph B readback SortformerSession() = default; diff --git a/src/arch/sortformer/stream.cpp b/src/arch/sortformer/stream.cpp index 9e97ab39..643795b9 100644 --- a/src/arch/sortformer/stream.cpp +++ b/src/arch/sortformer/stream.cpp @@ -43,7 +43,7 @@ bool env_int(const char * name, int & out) { if (v == nullptr) { return false; } - char * end = nullptr; + char * end = nullptr; const long parsed = std::strtol(v, &end, 10); if (end == v) { return false; @@ -66,10 +66,10 @@ struct Preset { }; const Preset k_presets[] = { - { "very_high_latency", 340, 40, 40, 300, 188 }, - { "high_latency", 124, 1, 124, 124, 188 }, - { "low_latency", 6, 7, 188, 144, 188 }, - { "small", 20, 1, 10, 20, 24 }, + { "very_high_latency", 340, 40, 40, 300, 188 }, + { "high_latency", 124, 1, 124, 124, 188 }, + { "low_latency", 6, 7, 188, 144, 188 }, + { "small", 20, 1, 10, 20, 24 }, }; } // namespace @@ -77,11 +77,11 @@ const Preset k_presets[] = { SortformerStreamParams resolve_stream_params(const SortformerHParams & hp, transcribe_sortformer_preset preset) { SortformerStreamParams p; // score / boost params keep their (verified) defaults // GGUF-shipped streaming cfg is the baseline. - p.chunk_len = hp.stream_chunk_len > 0 ? hp.stream_chunk_len : p.chunk_len; - p.spkcache_len = hp.stream_spkcache_len > 0 ? hp.stream_spkcache_len : p.spkcache_len; - p.fifo_len = hp.stream_fifo_len; // 0 is a valid shipped value - p.spkcache_update_period = hp.stream_spkcache_update_period > 0 ? hp.stream_spkcache_update_period - : p.spkcache_update_period; + p.chunk_len = hp.stream_chunk_len > 0 ? hp.stream_chunk_len : p.chunk_len; + p.spkcache_len = hp.stream_spkcache_len > 0 ? hp.stream_spkcache_len : p.spkcache_len; + p.fifo_len = hp.stream_fifo_len; // 0 is a valid shipped value + p.spkcache_update_period = + hp.stream_spkcache_update_period > 0 ? hp.stream_spkcache_update_period : p.spkcache_update_period; // Public run-ext preset (DEFAULT keeps the GGUF cfg). Range-checked by // run_validate before the dispatcher clears the previous result. @@ -139,8 +139,13 @@ SortformerStreamParams resolve_stream_params(const SortformerHParams & hp, trans // ---- _get_silence_profile (sortformer_modules.py:636-667) ---- // File-local (only streaming_update_sync calls it). -static void get_silence_profile(SortformerStreamState & st, float sil_threshold, const float * pop_embs, - const float * pop_preds, int n, int n_spk, int emb_dim) { +static void get_silence_profile(SortformerStreamState & st, + float sil_threshold, + const float * pop_embs, + const float * pop_preds, + int n, + int n_spk, + int emb_dim) { if (n <= 0) { return; } @@ -168,7 +173,7 @@ static void get_silence_profile(SortformerStreamState & st, float sil_threshold, sil_sum += pop_embs[static_cast(i) * emb_dim + d]; } } - const float old_sum = st.mean_sil_emb[static_cast(d)] * static_cast(old_n); + const float old_sum = st.mean_sil_emb[static_cast(d)] * static_cast(old_n); st.mean_sil_emb[static_cast(d)] = (old_sum + sil_sum) / denom; } st.n_sil_frames = new_n; @@ -188,9 +193,9 @@ std::vector get_log_pred_scores(const float * preds, int n, int n_spk, fl l1sum += std::log(std::max(1.0f - p, thr)); } for (int s = 0; s < n_spk; ++s) { - const float p = preds[static_cast(i) * n_spk + s]; - const float lp = std::log(std::max(p, thr)); - const float l1 = std::log(std::max(1.0f - p, thr)); + const float p = preds[static_cast(i) * n_spk + s]; + const float lp = std::log(std::max(p, thr)); + const float l1 = std::log(std::max(1.0f - p, thr)); scores[static_cast(i) * n_spk + s] = lp - l1 + l1sum - log_half; } } @@ -219,7 +224,7 @@ void disable_low_scores(const float * preds, std::vector & scores, int n, } if (pos_count >= min_pos) { for (int i = 0; i < n; ++i) { - const size_t idx = static_cast(i) * n_spk + s; + const size_t idx = static_cast(i) * n_spk + s; const bool is_speech = preds[idx] > 0.5f; const bool is_pos = scores[idx] > 0.0f; if (is_speech && !is_pos) { @@ -239,20 +244,19 @@ void boost_topk_scores(std::vector & scores, int n, int n_spk, int k, flo } // Reference: scores[topk] -= scale * log(0.5). log(0.5) < 0, so the top-k // scores are increased (boosted). - const float delta = scale * std::log(0.5f); // negative + const float delta = scale * std::log(0.5f); // negative std::vector order(static_cast(n)); for (int s = 0; s < n_spk; ++s) { std::iota(order.begin(), order.end(), 0); const int kk = std::min(k, n); - std::partial_sort(order.begin(), order.begin() + kk, order.end(), - [&](int a, int b) { - const float va = scores[static_cast(a) * n_spk + s]; - const float vb = scores[static_cast(b) * n_spk + s]; - if (va != vb) { - return va > vb; // descending value - } - return a < b; // tie -> lower index first - }); + std::partial_sort(order.begin(), order.begin() + kk, order.end(), [&](int a, int b) { + const float va = scores[static_cast(a) * n_spk + s]; + const float vb = scores[static_cast(b) * n_spk + s]; + if (va != vb) { + return va > vb; // descending value + } + return a < b; // tie -> lower index first + }); for (int j = 0; j < kk; ++j) { scores[static_cast(order[static_cast(j)]) * n_spk + s] -= delta; } @@ -264,8 +268,8 @@ void boost_topk_scores(std::vector & scores, int n, int n_spk, int k, flo // ---- _compress_spkcache (sortformer_modules.py:838-896) ---- void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams & p, int n_spk, int emb_dim) { - const int N = st.spkcache_n; // n_frames (> spkcache_len) - const int L = p.spkcache_len; // target frames + const int N = st.spkcache_n; // n_frames (> spkcache_len) + const int L = p.spkcache_len; // target frames const int sil = p.spkcache_sil_frames_per_spk; const int per_spk = L / n_spk - sil; @@ -293,8 +297,8 @@ void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams boost_topk_scores(scores, N, n_spk, weak, /*scale=*/1.0f); // 4. append `sil` rows of +inf per speaker; n_frames = N + sil. - const int n_frames = N + sil; - const int n_frames_no_sil = N; + const int n_frames = N + sil; + const int n_frames_no_sil = N; std::vector scores_ext(static_cast(n_frames) * n_spk); std::copy(scores.begin(), scores.end(), scores_ext.begin()); for (int i = N; i < n_frames; ++i) { @@ -306,7 +310,7 @@ void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams // 5. _get_topk_indices: flatten permute(0,2,1) -> flat[s*n_frames + i]; // top-L largest (value desc, flat-idx asc), -inf picks -> max_index, sort // ascending, derive frame idx + is_disabled. - const int64_t M = static_cast(n_spk) * n_frames; + const int64_t M = static_cast(n_spk) * n_frames; std::vector flat(static_cast(M)); std::iota(flat.begin(), flat.end(), int64_t{ 0 }); auto flat_val = [&](int64_t f) -> float { @@ -315,19 +319,18 @@ void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams return scores_ext[static_cast(i) * n_spk + s]; }; const int kk = static_cast(std::min(L, M)); - std::partial_sort(flat.begin(), flat.begin() + kk, flat.end(), - [&](int64_t a, int64_t b) { - const float va = flat_val(a); - const float vb = flat_val(b); - if (va != vb) { - return va > vb; - } - return a < b; - }); + std::partial_sort(flat.begin(), flat.begin() + kk, flat.end(), [&](int64_t a, int64_t b) { + const float va = flat_val(a); + const float vb = flat_val(b); + if (va != vb) { + return va > vb; + } + return a < b; + }); std::vector picks(static_cast(L)); for (int j = 0; j < L; ++j) { if (j < kk) { - const int64_t f = flat[static_cast(j)]; + const int64_t f = flat[static_cast(j)]; picks[static_cast(j)] = (flat_val(f) == kNegInf) ? p.max_index : f; } else { picks[static_cast(j)] = p.max_index; // fewer than L finite picks @@ -375,26 +378,25 @@ void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams // can be compared index-for-index. Gated behind an explicit env on top of // the normal dump-enabled flag so DER/validate runs are unaffected. if (transcribe::debug::enabled() && env_get("TRANSCRIBE_SORTFORMER_COMPRESS_DUMP") != nullptr) { - const int k = st.compress_count; - char name[64]; + const int k = st.compress_count; + char name[64]; std::vector fidx(static_cast(L)), fdis(static_cast(L)); for (int j = 0; j < L; ++j) { fidx[static_cast(j)] = static_cast(frame_idx[static_cast(j)]); fdis[static_cast(j)] = is_disabled[static_cast(j)] ? 1.0f : 0.0f; } - const long long li = L; + const long long li = L; const long long shp_in[2] = { N, n_spk }; std::snprintf(name, sizeof(name), "compress.%03d.input_preds", k); - transcribe::debug::dump_host_f32(name, st.spkcache_preds.data(), static_cast(N) * n_spk, - shp_in, 2, "compress"); + transcribe::debug::dump_host_f32(name, st.spkcache_preds.data(), static_cast(N) * n_spk, shp_in, 2, + "compress"); std::snprintf(name, sizeof(name), "compress.%03d.frame_idx", k); transcribe::debug::dump_host_f32(name, fidx.data(), li, &li, 1, "compress"); std::snprintf(name, sizeof(name), "compress.%03d.is_disabled", k); transcribe::debug::dump_host_f32(name, fdis.data(), li, &li, 1, "compress"); const long long shp[2] = { L, n_spk }; std::snprintf(name, sizeof(name), "compress.%03d.spkcache_preds", k); - transcribe::debug::dump_host_f32(name, new_preds.data(), static_cast(L) * n_spk, shp, 2, - "compress"); + transcribe::debug::dump_host_f32(name, new_preds.data(), static_cast(L) * n_spk, shp, 2, "compress"); } ++st.compress_count; @@ -405,12 +407,19 @@ void compress_spkcache(SortformerStreamState & st, const SortformerStreamParams // ---- streaming_update (sync branch, sortformer_modules.py:526-609) ---- -void streaming_update_sync(SortformerStreamState & st, const SortformerStreamParams & p, int n_spk, int emb_dim, - const std::vector & chunk_embs, int T_diar, - const std::vector & preds, int /*T_concat*/, int lc, int rc) { - const int S = st.spkcache_n; // spkcache_len (local, pre-update) - const int F = st.fifo_n; // fifo_len (local, pre-update) - const int C = T_diar - lc - rc; // chunk_len (middle frames) +void streaming_update_sync(SortformerStreamState & st, + const SortformerStreamParams & p, + int n_spk, + int emb_dim, + const std::vector & chunk_embs, + int T_diar, + const std::vector & preds, + int /*T_concat*/, + int lc, + int rc) { + const int S = st.spkcache_n; // spkcache_len (local, pre-update) + const int F = st.fifo_n; // fifo_len (local, pre-update) + const int C = T_diar - lc - rc; // chunk_len (middle frames) // fifo_preds = preds[S : S+F] (rebuilt from the concat preds each call). st.fifo_preds.assign(preds.begin() + static_cast(S) * n_spk, @@ -431,8 +440,7 @@ void streaming_update_sync(SortformerStreamState & st, const SortformerStreamPar pop = std::min(pop, F + C); std::vector pop_embs(st.fifo.begin(), st.fifo.begin() + static_cast(pop) * emb_dim); - std::vector pop_preds(st.fifo_preds.begin(), - st.fifo_preds.begin() + static_cast(pop) * n_spk); + std::vector pop_preds(st.fifo_preds.begin(), st.fifo_preds.begin() + static_cast(pop) * n_spk); get_silence_profile(st, p.sil_threshold, pop_embs.data(), pop_preds.data(), pop, n_spk, emb_dim); diff --git a/src/arch/sortformer/weights.cpp b/src/arch/sortformer/weights.cpp index f9c8e3c1..bcd2de4e 100644 --- a/src/arch/sortformer/weights.cpp +++ b/src/arch/sortformer/weights.cpp @@ -53,12 +53,15 @@ transcribe_status read_sortformer_hparams(const gguf_context * g, SortformerHPar return TRANSCRIBE_ERR_INVALID_ARG; } -#define RD_U32(key, field) \ - if (const transcribe_status st = kv_u32(g, key, hp.field); st != TRANSCRIBE_OK) return st -#define RD_F32(key, field) \ - if (const transcribe_status st = kv_f32(g, key, hp.field); st != TRANSCRIBE_OK) return st -#define RD_STR(key, field) \ - if (const transcribe_status st = kv_str(g, key, hp.field); st != TRANSCRIBE_OK) return st +#define RD_U32(key, field) \ + if (const transcribe_status st = kv_u32(g, key, hp.field); st != TRANSCRIBE_OK) \ + return st +#define RD_F32(key, field) \ + if (const transcribe_status st = kv_f32(g, key, hp.field); st != TRANSCRIBE_OK) \ + return st +#define RD_STR(key, field) \ + if (const transcribe_status st = kv_str(g, key, hp.field); st != TRANSCRIBE_OK) \ + return st RD_U32("stt.sortformer.max_speakers", max_speakers); RD_U32("stt.sortformer.frame_hop", frame_hop); @@ -104,8 +107,8 @@ transcribe_status read_sortformer_hparams(const gguf_context * g, SortformerHPar #undef RD_STR if (hp.tf_n_heads == 0 || hp.tf_d_model % hp.tf_n_heads != 0) { - log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: tf_d_model %d not divisible by tf_n_heads %d", - hp.tf_d_model, hp.tf_n_heads); + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: tf_d_model %d not divisible by tf_n_heads %d", hp.tf_d_model, + hp.tf_n_heads); return TRANSCRIBE_ERR_GGUF; } if (hp.tf_pre_ln) { @@ -126,9 +129,8 @@ ggml_tensor * get_checked(ggml_context * ctx, const char * name, int64_t ne0, in return nullptr; } if ((ne0 >= 0 && t->ne[0] != ne0) || (ne1 >= 0 && t->ne[1] != ne1)) { - log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, - "sortformer: tensor %s shape mismatch: have [%lld,%lld] want [%lld,%lld]", name, - (long long) t->ne[0], (long long) t->ne[1], (long long) ne0, (long long) ne1); + log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "sortformer: tensor %s shape mismatch: have [%lld,%lld] want [%lld,%lld]", + name, (long long) t->ne[0], (long long) t->ne[1], (long long) ne0, (long long) ne1); return nullptr; } return t; @@ -137,16 +139,17 @@ ggml_tensor * get_checked(ggml_context * ctx, const char * name, int64_t ne0, in } // namespace transcribe_status build_sortformer_weights(ggml_context * ctx, const SortformerHParams & hp, SortformerWeights & w) { - const int64_t d = hp.tf_d_model; - const int64_t dff = hp.tf_d_ff; - const int64_t ed = hp.enc_d_model; - const int64_t spk = hp.max_speakers; + const int64_t d = hp.tf_d_model; + const int64_t dff = hp.tf_d_ff; + const int64_t ed = hp.enc_d_model; + const int64_t spk = hp.max_speakers; char name[128]; -#define GET(dst, nm, e0, e1) \ - do { \ - (dst) = get_checked(ctx, (nm), (e0), (e1)); \ - if ((dst) == nullptr) return TRANSCRIBE_ERR_GGUF; \ +#define GET(dst, nm, e0, e1) \ + do { \ + (dst) = get_checked(ctx, (nm), (e0), (e1)); \ + if ((dst) == nullptr) \ + return TRANSCRIBE_ERR_GGUF; \ } while (0) GET(w.enc_proj_w, "diar.encoder_proj.weight", ed, d); @@ -155,11 +158,11 @@ transcribe_status build_sortformer_weights(ggml_context * ctx, const SortformerH w.tf_blocks.resize(static_cast(hp.tf_n_layers)); for (int i = 0; i < hp.tf_n_layers; ++i) { SortformerTfBlock & b = w.tf_blocks[static_cast(i)]; -#define GETB(dst, suffix, e0, e1) \ - do { \ - std::snprintf(name, sizeof(name), "tf.blocks.%d.%s", i, suffix); \ - GET(dst, name, e0, e1); \ - } while (0) +#define GETB(dst, suffix, e0, e1) \ + do { \ + std::snprintf(name, sizeof(name), "tf.blocks.%d.%s", i, suffix); \ + GET(dst, name, e0, e1); \ + } while (0) GETB(b.norm1_w, "norm_1.weight", d, -1); GETB(b.norm1_b, "norm_1.bias", d, -1); GETB(b.attn_q_w, "attn.q.weight", d, d); diff --git a/src/arch/sortformer/weights.h b/src/arch/sortformer/weights.h index 014a9908..9b655e96 100644 --- a/src/arch/sortformer/weights.h +++ b/src/arch/sortformer/weights.h @@ -24,19 +24,19 @@ namespace transcribe::sortformer { struct SortformerHParams { // Diarization. - int32_t max_speakers = 4; // hard architectural cap - int32_t frame_hop = 0; // samples per diar frame (hop * subsampling) + int32_t max_speakers = 4; // hard architectural cap + int32_t frame_hop = 0; // samples per diar frame (hop * subsampling) // Conformer encoder (mirrors the parakeet fields; see convert KVs). - int32_t enc_n_layers = 0; - int32_t enc_d_model = 0; - int32_t enc_n_heads = 0; - int32_t enc_d_ff = 0; - int32_t enc_conv_kernel = 0; - int32_t enc_subsampling_factor = 0; - int32_t enc_subsampling_channels = 0; - int32_t enc_feat_in = 0; - int32_t enc_pos_emb_max_len = 0; + int32_t enc_n_layers = 0; + int32_t enc_d_model = 0; + int32_t enc_n_heads = 0; + int32_t enc_d_ff = 0; + int32_t enc_conv_kernel = 0; + int32_t enc_subsampling_factor = 0; + int32_t enc_subsampling_channels = 0; + int32_t enc_feat_in = 0; + int32_t enc_pos_emb_max_len = 0; std::string enc_conv_norm_type; // "batch_norm" // Transformer encoder (post-LN, no positional encoding, no final norm). @@ -60,9 +60,9 @@ struct SortformerHParams { // Shipped streaming defaults (deferred streaming path; read for KV // completeness and later use). - int32_t stream_chunk_len = 0; - int32_t stream_spkcache_len = 0; - int32_t stream_fifo_len = 0; + int32_t stream_chunk_len = 0; + int32_t stream_spkcache_len = 0; + int32_t stream_fifo_len = 0; int32_t stream_spkcache_update_period = 0; int32_t tf_head_dim() const { return tf_n_heads > 0 ? tf_d_model / tf_n_heads : 0; } @@ -70,39 +70,39 @@ struct SortformerHParams { // One post-LN Transformer encoder block. All linears carry bias. struct SortformerTfBlock { - ggml_tensor * norm1_w = nullptr; // [d] - ggml_tensor * norm1_b = nullptr; // [d] - ggml_tensor * attn_q_w = nullptr; // [d, d] - ggml_tensor * attn_q_b = nullptr; // [d] + ggml_tensor * norm1_w = nullptr; // [d] + ggml_tensor * norm1_b = nullptr; // [d] + ggml_tensor * attn_q_w = nullptr; // [d, d] + ggml_tensor * attn_q_b = nullptr; // [d] ggml_tensor * attn_k_w = nullptr; ggml_tensor * attn_k_b = nullptr; ggml_tensor * attn_v_w = nullptr; ggml_tensor * attn_v_b = nullptr; ggml_tensor * attn_o_w = nullptr; ggml_tensor * attn_o_b = nullptr; - ggml_tensor * norm2_w = nullptr; // [d] - ggml_tensor * norm2_b = nullptr; // [d] - ggml_tensor * ff_in_w = nullptr; // [d, d_ff] - ggml_tensor * ff_in_b = nullptr; // [d_ff] - ggml_tensor * ff_out_w = nullptr; // [d_ff, d] - ggml_tensor * ff_out_b = nullptr; // [d] + ggml_tensor * norm2_w = nullptr; // [d] + ggml_tensor * norm2_b = nullptr; // [d] + ggml_tensor * ff_in_w = nullptr; // [d, d_ff] + ggml_tensor * ff_in_b = nullptr; // [d_ff] + ggml_tensor * ff_out_w = nullptr; // [d_ff, d] + ggml_tensor * ff_out_b = nullptr; // [d] }; struct SortformerWeights { // encoder_proj: Linear 512 -> 192. - ggml_tensor * enc_proj_w = nullptr; // [enc_d_model, tf_d_model] - ggml_tensor * enc_proj_b = nullptr; // [tf_d_model] + ggml_tensor * enc_proj_w = nullptr; // [enc_d_model, tf_d_model] + ggml_tensor * enc_proj_b = nullptr; // [tf_d_model] std::vector tf_blocks; // tf_n_layers entries // Diar head (forward_speaker_sigmoids offline path). - ggml_tensor * fc1_w = nullptr; // [tf_d_model, tf_d_model] - ggml_tensor * fc1_b = nullptr; // [tf_d_model] + ggml_tensor * fc1_w = nullptr; // [tf_d_model, tf_d_model] + ggml_tensor * fc1_b = nullptr; // [tf_d_model] ggml_tensor * single_spk_head_w = nullptr; // [tf_d_model, max_speakers] ggml_tensor * single_spk_head_b = nullptr; // [max_speakers] // Streaming 2*hidden head (spkcache concat path); loaded but unused offline. - ggml_tensor * spk_head_w = nullptr; // [2*tf_d_model, max_speakers] - ggml_tensor * spk_head_b = nullptr; // [max_speakers] + ggml_tensor * spk_head_w = nullptr; // [2*tf_d_model, max_speakers] + ggml_tensor * spk_head_b = nullptr; // [max_speakers] }; // Read every required stt.sortformer.* / stt.frontend.* KV into hp. @@ -112,7 +112,7 @@ transcribe_status read_sortformer_hparams(const gguf_context * gguf, SortformerH // head) by name in ctx_meta, validate shapes against hp, store borrowed // pointers. Returns TRANSCRIBE_ERR_GGUF (naming the tensor) on any // missing / mis-shaped tensor. -transcribe_status build_sortformer_weights(ggml_context * ctx_meta, +transcribe_status build_sortformer_weights(ggml_context * ctx_meta, const SortformerHParams & hp, SortformerWeights & weights); diff --git a/src/transcribe-arch.cpp b/src/transcribe-arch.cpp index 8dae20bf..981dbeb1 100644 --- a/src/transcribe-arch.cpp +++ b/src/transcribe-arch.cpp @@ -89,9 +89,9 @@ const Arch * find_arch(const char * name) { } static const Arch * const k_archs[] = { - ¶keet::arch, &cohere::arch, &canary::arch, &qwen3_asr::arch, &voxtral::arch, - &voxtral_realtime::arch, &canary_qwen::arch, &whisper::arch, &moonshine::arch, &moonshine_streaming::arch, - &sensevoice::arch, &funasr_nano::arch, &gigaam::arch, &granite::arch, &granite_nar::arch, + ¶keet::arch, &cohere::arch, &canary::arch, &qwen3_asr::arch, &voxtral::arch, + &voxtral_realtime::arch, &canary_qwen::arch, &whisper::arch, &moonshine::arch, &moonshine_streaming::arch, + &sensevoice::arch, &funasr_nano::arch, &gigaam::arch, &granite::arch, &granite_nar::arch, &medasr::arch, &moss::arch, &sortformer::arch, }; constexpr size_t k_n = sizeof(k_archs) / sizeof(k_archs[0]); diff --git a/tests/sortformer_stream_ext_unit.cpp b/tests/sortformer_stream_ext_unit.cpp index c823d247..31bd1001 100644 --- a/tests/sortformer_stream_ext_unit.cpp +++ b/tests/sortformer_stream_ext_unit.cpp @@ -89,7 +89,7 @@ int main() { std::fprintf(stderr, "sortformer_stream_ext_unit: file not found: %s\n", gguf.c_str()); return 77; } - const std::string wav_path = std::string(TRANSCRIBE_TEST_SAMPLES_DIR) + "/sortformer-2spk-mix.wav"; + const std::string wav_path = std::string(TRANSCRIBE_TEST_SAMPLES_DIR) + "/sortformer-2spk-mix.wav"; std::vector pcm; std::string wav_err; if (!transcribe_cli::load_wav_mono_16k(wav_path, pcm, wav_err)) { @@ -103,7 +103,7 @@ int main() { transcribe_model_load_params mp; transcribe_model_load_params_init(&mp); - mp.backend = TRANSCRIBE_BACKEND_CPU; // deterministic float order for the parity leg + mp.backend = TRANSCRIBE_BACKEND_CPU; // deterministic float order for the parity leg struct transcribe_model * model = nullptr; if (transcribe_model_load_file(gguf.c_str(), &mp, &model) != TRANSCRIBE_OK || model == nullptr) { std::fprintf(stderr, "FAIL: model load\n"); @@ -112,8 +112,7 @@ int main() { // 1. Kind+slot probe. CHECK(transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM)); - CHECK(!transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_STREAM, - TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM)); + CHECK(!transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_STREAM, TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM)); CHECK(!transcribe_model_accepts_ext_kind(model, TRANSCRIBE_EXT_SLOT_RUN, 0x4E524857u /* WHRN */)); // 2. Init function stamps the header + default. @@ -146,16 +145,14 @@ int main() { transcribe_sortformer_stream_ext_init(&bad); bad.ext.kind = 0x4E524857u; // WHRN: wrong family kind rp.family = &bad.ext; - CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == - TRANSCRIBE_ERR_INVALID_ARG); + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_ERR_INVALID_ARG); CHECK(same_segments(read_segments(session), env_rows)); // snapshot intact transcribe_sortformer_stream_ext oor; transcribe_sortformer_stream_ext_init(&oor); oor.preset = static_cast(99); rp.family = &oor.ext; - CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == - TRANSCRIBE_ERR_INVALID_ARG); + CHECK(transcribe_run(session, pcm.data(), static_cast(pcm.size()), &rp) == TRANSCRIBE_ERR_INVALID_ARG); CHECK(same_segments(read_segments(session), env_rows)); } From c8331f873efff42121877346eaea6a0ec971f525 Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Sat, 25 Jul 2026 01:41:34 -0700 Subject: [PATCH 6/7] update abihash and bindings --- .../python/src/transcribe_cpp/__init__.py | 37 +++++++++++++ .../python/src/transcribe_cpp/_generated.py | 13 ++++- bindings/python/tests/test_family_ext.py | 14 +++++ bindings/rust/sys/src/transcribe_sys.rs | 38 ++++++++++++- bindings/rust/transcribe-cpp/src/family.rs | 55 +++++++++++++++++++ bindings/rust/transcribe-cpp/src/lib.rs | 3 +- .../swift/Sources/TranscribeCpp/ABIHash.swift | 2 +- .../swift/Sources/TranscribeCpp/Family.swift | 37 ++++++++++++- bindings/typescript/src/_generated.ts | 10 +++- bindings/typescript/src/ffi.ts | 3 + bindings/typescript/src/index.ts | 14 +++++ bindings/typescript/src/types.ts | 17 +++++- include/transcribe.abihash | 2 +- 13 files changed, 236 insertions(+), 9 deletions(-) diff --git a/bindings/python/src/transcribe_cpp/__init__.py b/bindings/python/src/transcribe_cpp/__init__.py index 92d9126e..946cf10f 100644 --- a/bindings/python/src/transcribe_cpp/__init__.py +++ b/bindings/python/src/transcribe_cpp/__init__.py @@ -54,6 +54,7 @@ Task = Literal["transcribe", "translate"] Timestamps = Literal["none", "auto", "segment", "word", "token"] Diarize = Literal["default", "off", "on"] +SortformerPreset = Literal["default", "very_high_latency", "high_latency", "low_latency"] CommitPolicy = Literal["auto", "on_finalize", "stable_prefix"] Feature = Literal[ "initial_prompt", "temperature_fallback", "long_form", @@ -81,8 +82,10 @@ "MoonshineStreamingOptions", "ParakeetStreamOptions", "ParakeetBufferedStreamOptions", + "SortformerStreamOptions", "VoxtralRealtimeStreamOptions", "Backend", + "SortformerPreset", "KVType", "Task", "Timestamps", @@ -799,6 +802,40 @@ def _apply(self, ext) -> None: ext.min_decode_interval_ms = self.min_decode_interval_ms +class SortformerStreamOptions(FamilyExtension): + """Sortformer streaming operating-point options (run slot). + + Sortformer is a diarizer: a run produces speaker segments, no text. + ``preset`` selects the latency / accuracy trade-off from the model's + published menu; ``"default"`` keeps the GGUF-shipped checkpoint + configuration. ``"very_high_latency"`` (~30 s lookahead) is the + offline-file operating point; ``"low_latency"`` (~1 s) is the + real-time point and costs substantially more compute per audio + second.""" + + _slot = "run" + _kind = _generated.TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM + _struct = _generated.transcribe_sortformer_stream_ext + _init = "transcribe_sortformer_stream_ext_init" + + _presets = { + "default": _generated.TRANSCRIBE_SORTFORMER_PRESET_DEFAULT, + "very_high_latency": _generated.TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY, + "high_latency": _generated.TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY, + "low_latency": _generated.TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY, + } + + def __init__(self, *, preset: SortformerPreset | None = None): + if preset is not None and preset not in self._presets: + raise ValueError(f"unknown sortformer preset {preset!r}; " + f"expected one of {sorted(self._presets)}") + self.preset = preset + + def _apply(self, ext) -> None: + if self.preset is not None: + ext.preset = self._presets[self.preset] + + # --- high-level handles --------------------------------------------------- diff --git a/bindings/python/src/transcribe_cpp/_generated.py b/bindings/python/src/transcribe_cpp/_generated.py index 90b75343..9c133653 100644 --- a/bindings/python/src/transcribe_cpp/_generated.py +++ b/bindings/python/src/transcribe_cpp/_generated.py @@ -13,7 +13,7 @@ # Stable digest of the ABI surface below (structs, enums, macros, layout, # prototypes). A native provider package echoes this back so the API # package can reject an ABI-mismatched provider before dlopen. -PUBLIC_HEADER_HASH = "d67a9bd78b964445" +PUBLIC_HEADER_HASH = "fb2e64791dcbb70a" # === enum constants === TRANSCRIBE_OK = 0 @@ -101,6 +101,10 @@ TRANSCRIBE_STREAM_COMMIT_AUTO = 0 TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE = 1 TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX = 2 +TRANSCRIBE_SORTFORMER_PRESET_DEFAULT = 0 +TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY = 1 +TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY = 2 +TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY = 3 TRANSCRIBE_WHISPER_PROMPT_FIRST_SEGMENT = 0 TRANSCRIBE_WHISPER_PROMPT_ALL_SEGMENTS = 1 @@ -108,6 +112,7 @@ TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM = 1414746957 TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM = 1396853584 TRANSCRIBE_EXT_KIND_PARAKEET_STREAM = 1414744912 +TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM = 1414743635 TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM = 1414746710 TRANSCRIBE_EXT_KIND_WHISPER_RUN = 1314015319 @@ -148,6 +153,8 @@ class transcribe_parakeet_stream_ext(_c.Structure): pass class transcribe_parakeet_buffered_stream_ext(_c.Structure): pass +class transcribe_sortformer_stream_ext(_c.Structure): + pass class transcribe_voxtral_realtime_stream_ext(_c.Structure): pass class transcribe_whisper_run_ext(_c.Structure): @@ -173,6 +180,7 @@ class transcribe_whisper_chunk_trace(_c.Structure): transcribe_moonshine_streaming_stream_ext._fields_ = [("ext", transcribe_ext), ("min_decode_interval_ms", _c.c_int32)] transcribe_parakeet_stream_ext._fields_ = [("ext", transcribe_ext), ("att_context_right", _c.c_int32)] transcribe_parakeet_buffered_stream_ext._fields_ = [("ext", transcribe_ext), ("left_ms", _c.c_int32), ("chunk_ms", _c.c_int32), ("right_ms", _c.c_int32)] +transcribe_sortformer_stream_ext._fields_ = [("ext", transcribe_ext), ("preset", _c.c_int)] transcribe_voxtral_realtime_stream_ext._fields_ = [("ext", transcribe_ext), ("num_delay_tokens", _c.c_int32), ("min_decode_interval_ms", _c.c_int32)] transcribe_whisper_run_ext._fields_ = [("ext", transcribe_ext), ("initial_prompt", _c.c_char_p), ("prompt_tokens", _c.POINTER(_c.c_int32)), ("n_prompt_tokens", _c.c_size_t), ("prompt_condition", _c.c_int), ("condition_on_prev_tokens", _c.c_bool), ("max_prev_context_tokens", _c.c_int32), ("temperature", _c.c_float), ("temperature_inc", _c.c_float), ("compression_ratio_thold", _c.c_float), ("logprob_thold", _c.c_float), ("no_speech_thold", _c.c_float), ("seed", _c.c_uint32), ("max_initial_timestamp", _c.c_float)] transcribe_whisper_chunk_trace._fields_ = [("struct_size", _c.c_uint64), ("t0_ms", _c.c_int64), ("t1_ms", _c.c_int64), ("temperature_used", _c.c_float), ("compression_ratio", _c.c_float), ("avg_logprob", _c.c_float), ("no_speech_prob", _c.c_float), ("no_speech_triggered", _c.c_bool), ("n_fallbacks", _c.c_int32)] @@ -217,6 +225,7 @@ class transcribe_whisper_chunk_trace(_c.Structure): 'transcribe_moonshine_streaming_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'min_decode_interval_ms': 16}}, 'transcribe_parakeet_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'att_context_right': 16}}, 'transcribe_parakeet_buffered_stream_ext': {'size': 32, 'align': 8, 'offsets': {'ext': 0, 'left_ms': 16, 'chunk_ms': 20, 'right_ms': 24}}, + 'transcribe_sortformer_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'preset': 16}}, 'transcribe_voxtral_realtime_stream_ext': {'size': 24, 'align': 8, 'offsets': {'ext': 0, 'num_delay_tokens': 16, 'min_decode_interval_ms': 20}}, 'transcribe_whisper_run_ext': {'size': 80, 'align': 8, 'offsets': {'ext': 0, 'initial_prompt': 16, 'prompt_tokens': 24, 'n_prompt_tokens': 32, 'prompt_condition': 40, 'condition_on_prev_tokens': 44, 'max_prev_context_tokens': 48, 'temperature': 52, 'temperature_inc': 56, 'compression_ratio_thold': 60, 'logprob_thold': 64, 'no_speech_thold': 68, 'seed': 72, 'max_initial_timestamp': 76}}, 'transcribe_whisper_chunk_trace': {'size': 48, 'align': 8, 'offsets': {'struct_size': 0, 't0_ms': 8, 't1_ms': 16, 'temperature_used': 24, 'compression_ratio': 28, 'avg_logprob': 32, 'no_speech_prob': 36, 'no_speech_triggered': 40, 'n_fallbacks': 44}}, @@ -365,6 +374,8 @@ def configure(lib): lib.transcribe_session_params_init.argtypes = [_c.POINTER(transcribe_session_params)] lib.transcribe_set_abort_callback.restype = None lib.transcribe_set_abort_callback.argtypes = [_c.c_void_p, _c.CFUNCTYPE(_c.c_bool, _c.c_void_p), _c.c_void_p] + lib.transcribe_sortformer_stream_ext_init.restype = None + lib.transcribe_sortformer_stream_ext_init.argtypes = [_c.POINTER(transcribe_sortformer_stream_ext)] lib.transcribe_speaker_segment_init.restype = None lib.transcribe_speaker_segment_init.argtypes = [_c.POINTER(transcribe_speaker_segment)] lib.transcribe_status_string.restype = _c.c_char_p diff --git a/bindings/python/tests/test_family_ext.py b/bindings/python/tests/test_family_ext.py index 9bccecae..3b0a433e 100644 --- a/bindings/python/tests/test_family_ext.py +++ b/bindings/python/tests/test_family_ext.py @@ -22,6 +22,7 @@ t.MoonshineStreamingOptions, t.ParakeetStreamOptions, t.ParakeetBufferedStreamOptions, + t.SortformerStreamOptions, t.VoxtralRealtimeStreamOptions, ] @@ -73,6 +74,19 @@ def test_parakeet_buffered_partial_overrides(): assert built.right_ms == fresh.right_ms +def test_sortformer_preset_maps_to_enum_value(): + built = t.SortformerStreamOptions(preset="very_high_latency")._build() + assert built.preset == _generated.TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY + # None keeps the init default (DEFAULT = GGUF-shipped cfg). + default = t.SortformerStreamOptions()._build() + assert default.preset == _generated.TRANSCRIBE_SORTFORMER_PRESET_DEFAULT + + +def test_sortformer_unknown_preset_rejected(): + with pytest.raises(ValueError, match="preset"): + t.SortformerStreamOptions(preset="ultra_low_latency") # type: ignore[arg-type] + + # --- model-gated: resolve_family validation + a real extension run ---------- diff --git a/bindings/rust/sys/src/transcribe_sys.rs b/bindings/rust/sys/src/transcribe_sys.rs index a391f55a..bb3b5a39 100644 --- a/bindings/rust/sys/src/transcribe_sys.rs +++ b/bindings/rust/sys/src/transcribe_sys.rs @@ -1,17 +1,18 @@ // @generated by `cargo xtask bindgen` from include/transcribe/extensions.h // DO NOT EDIT BY HAND. Regenerate: `cargo xtask bindgen`. -// Pinned to include/transcribe.abihash = d67a9bd78b964445 +// Pinned to include/transcribe.abihash = fb2e64791dcbb70a /// The public-ABI digest these bindings were generated against /// (sha256/16 over the normalized FFI surface). The load-time version /// gate and the CI drift check both anchor on this value. -pub const PUBLIC_HEADER_HASH: &str = "d67a9bd78b964445"; +pub const PUBLIC_HEADER_HASH: &str = "fb2e64791dcbb70a"; /* automatically generated by rust-bindgen 0.72.1 */ pub const TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM: u32 = 1414746957; pub const TRANSCRIBE_EXT_KIND_PARAKEET_STREAM: u32 = 1414744912; pub const TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM: u32 = 1396853584; +pub const TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM: u32 = 1414743635; pub const TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM: u32 = 1414746710; pub const TRANSCRIBE_EXT_KIND_WHISPER_RUN: u32 = 1314015319; impl transcribe_status { @@ -1156,6 +1157,39 @@ unsafe extern "C" { ext: *mut transcribe_parakeet_buffered_stream_ext, ); } +impl transcribe_sortformer_preset { + pub const TRANSCRIBE_SORTFORMER_PRESET_DEFAULT: transcribe_sortformer_preset = + transcribe_sortformer_preset(0); + pub const TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY: transcribe_sortformer_preset = + transcribe_sortformer_preset(1); + pub const TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY: transcribe_sortformer_preset = + transcribe_sortformer_preset(2); + pub const TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY: transcribe_sortformer_preset = + transcribe_sortformer_preset(3); +} +#[repr(transparent)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct transcribe_sortformer_preset(pub ::std::os::raw::c_uint); +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct transcribe_sortformer_stream_ext { + pub ext: transcribe_ext, + pub preset: transcribe_sortformer_preset, +} +#[allow(clippy::unnecessary_operation, clippy::identity_op)] +const _: () = { + ["Size of transcribe_sortformer_stream_ext"] + [::std::mem::size_of::() - 24usize]; + ["Alignment of transcribe_sortformer_stream_ext"] + [::std::mem::align_of::() - 8usize]; + ["Offset of field: transcribe_sortformer_stream_ext::ext"] + [::std::mem::offset_of!(transcribe_sortformer_stream_ext, ext) - 0usize]; + ["Offset of field: transcribe_sortformer_stream_ext::preset"] + [::std::mem::offset_of!(transcribe_sortformer_stream_ext, preset) - 16usize]; +}; +unsafe extern "C" { + pub fn transcribe_sortformer_stream_ext_init(ext: *mut transcribe_sortformer_stream_ext); +} #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct transcribe_voxtral_realtime_stream_ext { diff --git a/bindings/rust/transcribe-cpp/src/family.rs b/bindings/rust/transcribe-cpp/src/family.rs index 13dce620..7f72298b 100644 --- a/bindings/rust/transcribe-cpp/src/family.rs +++ b/bindings/rust/transcribe-cpp/src/family.rs @@ -60,11 +60,55 @@ pub struct VoxtralRealtimeStreamOptions { pub min_decode_interval_ms: Option, } +/// Sortformer streaming operating point (latency / accuracy trade-off). +/// The menu is discrete (jointly-tuned bundles), not a latency dial; +/// `Default` keeps the GGUF-shipped checkpoint configuration. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum SortformerPreset { + #[default] + Default, + /// ~30.4 s algorithmic lookahead; the offline-file operating point. + VeryHighLatency, + /// ~10.0 s lookahead. + HighLatency, + /// ~1.04 s lookahead; the real-time point (compute-heavy per audio + /// second — many small windows). + LowLatency, +} + +impl SortformerPreset { + fn to_sys(self) -> sys::transcribe_sortformer_preset { + match self { + SortformerPreset::Default => { + sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_DEFAULT + } + SortformerPreset::VeryHighLatency => { + sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY + } + SortformerPreset::HighLatency => { + sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY + } + SortformerPreset::LowLatency => { + sys::transcribe_sortformer_preset::TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY + } + } + } +} + +/// Sortformer diarizer run-extension knobs (run slot). Sortformer produces +/// speaker segments, no text; read results via the speaker-segment +/// accessors. `None` keeps the family default (the GGUF-shipped cfg). +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct SortformerStreamOptions { + pub preset: Option, +} + /// A family extension for the run slot (offline `run`/`run_batch`). #[derive(Debug, Clone, PartialEq)] #[non_exhaustive] pub enum RunExtension { Whisper(WhisperRunOptions), + Sortformer(SortformerStreamOptions), } /// A family extension for the stream slot. @@ -86,6 +130,7 @@ pub(crate) enum RunExtRaw { ext: Box, _prompt: Option, }, + Sortformer(Box), } impl RunExtRaw { @@ -95,6 +140,10 @@ impl RunExtRaw { RunExtRaw::Whisper { ext, .. } => { (&**ext) as *const sys::transcribe_whisper_run_ext as *const sys::transcribe_ext } + RunExtRaw::Sortformer(e) => { + (&**e) as *const sys::transcribe_sortformer_stream_ext + as *const sys::transcribe_ext + } } } } @@ -128,6 +177,12 @@ impl RunExtension { _prompt: prompt, }) } + RunExtension::Sortformer(o) => { + let mut ext: sys::transcribe_sortformer_stream_ext = unsafe { std::mem::zeroed() }; + unsafe { sys::transcribe_sortformer_stream_ext_init(&mut ext) }; + set(&mut ext.preset, o.preset.map(SortformerPreset::to_sys)); + Ok(RunExtRaw::Sortformer(Box::new(ext))) + } } } } diff --git a/bindings/rust/transcribe-cpp/src/lib.rs b/bindings/rust/transcribe-cpp/src/lib.rs index 9ab98c07..d2dc48c7 100644 --- a/bindings/rust/transcribe-cpp/src/lib.rs +++ b/bindings/rust/transcribe-cpp/src/lib.rs @@ -65,7 +65,8 @@ pub use cancel::CancelToken; pub use error::{Error, Result}; pub use family::{ MoonshineStreamingOptions, ParakeetBufferedStreamOptions, ParakeetStreamOptions, RunExtension, - StreamExtension, VoxtralRealtimeStreamOptions, WhisperRunOptions, + SortformerPreset, SortformerStreamOptions, StreamExtension, VoxtralRealtimeStreamOptions, + WhisperRunOptions, }; pub use logging::{disable_logging, init_logging}; pub use model::{Capabilities, Model, ModelOptions, SessionLimits, SessionOptions}; diff --git a/bindings/swift/Sources/TranscribeCpp/ABIHash.swift b/bindings/swift/Sources/TranscribeCpp/ABIHash.swift index ca44e1ad..500fbf03 100644 --- a/bindings/swift/Sources/TranscribeCpp/ABIHash.swift +++ b/bindings/swift/Sources/TranscribeCpp/ABIHash.swift @@ -13,7 +13,7 @@ import CTranscribe extension Transcribe { /// sha256/16 of the normalized public FFI surface, pinned to the value in /// include/transcribe.abihash at the time this binding was last reviewed. - public static let pinnedHeaderHash = "d67a9bd78b964445" + public static let pinnedHeaderHash = "fb2e64791dcbb70a" /// The public-ABI digest this binding was reviewed against (16 hex chars). public static func headerHash() -> String { pinnedHeaderHash } diff --git a/bindings/swift/Sources/TranscribeCpp/Family.swift b/bindings/swift/Sources/TranscribeCpp/Family.swift index f0d620da..59233a88 100644 --- a/bindings/swift/Sources/TranscribeCpp/Family.swift +++ b/bindings/swift/Sources/TranscribeCpp/Family.swift @@ -7,7 +7,7 @@ import CTranscribe // `transcribe_ext` pointer is handed to the run/begin call; the library copies // what it needs before returning. -// MARK: - Run-slot extensions (whisper) +// MARK: - Run-slot extensions (whisper, sortformer) public struct WhisperRunOptions: Sendable { public var initialPrompt: String? @@ -46,12 +46,42 @@ public struct WhisperRunOptions: Sendable { } } +/// Sortformer streaming operating point (latency / accuracy trade-off). +/// The menu is discrete (jointly-tuned bundles), not a latency dial; +/// `.default` keeps the GGUF-shipped checkpoint configuration. +/// `.veryHighLatency` (~30 s lookahead) is the offline-file operating +/// point; `.lowLatency` (~1 s) is the real-time point. +public enum SortformerPreset: Sendable { + case `default` + case veryHighLatency + case highLatency + case lowLatency + + var cValue: transcribe_sortformer_preset { + switch self { + case .default: return TRANSCRIBE_SORTFORMER_PRESET_DEFAULT + case .veryHighLatency: return TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY + case .highLatency: return TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY + case .lowLatency: return TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY + } + } +} + +/// Sortformer diarizer options (run slot). Sortformer produces speaker +/// segments, no text; read results via the speaker-segment accessors. +public struct SortformerStreamOptions: Sendable { + public var preset: SortformerPreset? + public init(preset: SortformerPreset? = nil) { self.preset = preset } +} + public enum RunExtension: Sendable { case whisper(WhisperRunOptions) + case sortformer(SortformerStreamOptions) var kind: UInt32 { switch self { case .whisper: return TRANSCRIBE_EXT_KIND_WHISPER_RUN + case .sortformer: return TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM } } } @@ -79,6 +109,11 @@ func withRunExtension( c.initial_prompt = prompt return try withUnsafePointer(to: &c.ext) { try body($0) } } + case .sortformer(let o): + var c = transcribe_sortformer_stream_ext() + transcribe_sortformer_stream_ext_init(&c) + if let v = o.preset { c.preset = v.cValue } + return try withUnsafePointer(to: &c.ext) { try body($0) } } } diff --git a/bindings/typescript/src/_generated.ts b/bindings/typescript/src/_generated.ts index 8089cb9f..6d316b12 100644 --- a/bindings/typescript/src/_generated.ts +++ b/bindings/typescript/src/_generated.ts @@ -11,7 +11,7 @@ // Stable digest of the ABI surface (structs, enums, macros, layout, // prototypes), computed by the Python oracle and pinned here so a header // ABI change turns this binding's drift check red for conscious review. -export const PUBLIC_HEADER_HASH = "d67a9bd78b964445"; +export const PUBLIC_HEADER_HASH = "fb2e64791dcbb70a"; // === enum constants === export const TRANSCRIBE_OK = 0; @@ -99,6 +99,10 @@ export const TRANSCRIBE_STREAM_FAILED = 3; export const TRANSCRIBE_STREAM_COMMIT_AUTO = 0; export const TRANSCRIBE_STREAM_COMMIT_ON_FINALIZE = 1; export const TRANSCRIBE_STREAM_COMMIT_STABLE_PREFIX = 2; +export const TRANSCRIBE_SORTFORMER_PRESET_DEFAULT = 0; +export const TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY = 1; +export const TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY = 2; +export const TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY = 3; export const TRANSCRIBE_WHISPER_PROMPT_FIRST_SEGMENT = 0; export const TRANSCRIBE_WHISPER_PROMPT_ALL_SEGMENTS = 1; @@ -106,6 +110,7 @@ export const TRANSCRIBE_WHISPER_PROMPT_ALL_SEGMENTS = 1; export const TRANSCRIBE_EXT_KIND_MOONSHINE_STREAMING_STREAM = 1414746957; export const TRANSCRIBE_EXT_KIND_PARAKEET_BUFFERED_STREAM = 1396853584; export const TRANSCRIBE_EXT_KIND_PARAKEET_STREAM = 1414744912; +export const TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM = 1414743635; export const TRANSCRIBE_EXT_KIND_VOXTRAL_REALTIME_STREAM = 1414746710; export const TRANSCRIBE_EXT_KIND_WHISPER_RUN = 1314015319; @@ -129,6 +134,7 @@ export const STRUCT_LAYOUT: Record = { 'transcribe_moonshine_streaming_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'min_decode_interval_ms': 16} }, 'transcribe_parakeet_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'att_context_right': 16} }, 'transcribe_parakeet_buffered_stream_ext': { size: 32, align: 8, offsets: {'ext': 0, 'left_ms': 16, 'chunk_ms': 20, 'right_ms': 24} }, + 'transcribe_sortformer_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'preset': 16} }, 'transcribe_voxtral_realtime_stream_ext': { size: 24, align: 8, offsets: {'ext': 0, 'num_delay_tokens': 16, 'min_decode_interval_ms': 20} }, 'transcribe_whisper_run_ext': { size: 80, align: 8, offsets: {'ext': 0, 'initial_prompt': 16, 'prompt_tokens': 24, 'n_prompt_tokens': 32, 'prompt_condition': 40, 'condition_on_prev_tokens': 44, 'max_prev_context_tokens': 48, 'temperature': 52, 'temperature_inc': 56, 'compression_ratio_thold': 60, 'logprob_thold': 64, 'no_speech_thold': 68, 'seed': 72, 'max_initial_timestamp': 76} }, 'transcribe_whisper_chunk_trace': { size: 48, align: 8, offsets: {'struct_size': 0, 't0_ms': 8, 't1_ms': 16, 'temperature_used': 24, 'compression_ratio': 28, 'avg_logprob': 32, 'no_speech_prob': 36, 'no_speech_triggered': 40, 'n_fallbacks': 44} }, @@ -173,6 +179,7 @@ export function defineTypes(koffi: any): Record { T['transcribe_moonshine_streaming_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], min_decode_interval_ms: 'int32_t' }); T['transcribe_parakeet_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], att_context_right: 'int32_t' }); T['transcribe_parakeet_buffered_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], left_ms: 'int32_t', chunk_ms: 'int32_t', right_ms: 'int32_t' }); + T['transcribe_sortformer_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], preset: 'int' }); T['transcribe_voxtral_realtime_stream_ext'] = koffi.struct({ ext: T['transcribe_ext'], num_delay_tokens: 'int32_t', min_decode_interval_ms: 'int32_t' }); T['transcribe_whisper_run_ext'] = koffi.struct({ ext: T['transcribe_ext'], initial_prompt: 'char *', prompt_tokens: 'void *', n_prompt_tokens: 'size_t', prompt_condition: 'int', condition_on_prev_tokens: 'bool', max_prev_context_tokens: 'int32_t', temperature: 'float', temperature_inc: 'float', compression_ratio_thold: 'float', logprob_thold: 'float', no_speech_thold: 'float', seed: 'uint32_t', max_initial_timestamp: 'float' }); T['transcribe_whisper_chunk_trace'] = koffi.struct({ struct_size: 'uint64_t', t0_ms: 'int64_t', t1_ms: 'int64_t', temperature_used: 'float', compression_ratio: 'float', avg_logprob: 'float', no_speech_prob: 'float', no_speech_triggered: 'bool', n_fallbacks: 'int32_t' }); @@ -251,6 +258,7 @@ export const FUNCTION_SIGNATURES: Record = { 'transcribe_session_limits_init': { ret: 'void', args: ['struct transcribe_session_limits *'] }, 'transcribe_session_params_init': { ret: 'void', args: ['struct transcribe_session_params *'] }, 'transcribe_set_abort_callback': { ret: 'void', args: ['struct transcribe_session *', 'transcribe_abort_callback', 'void *'] }, + 'transcribe_sortformer_stream_ext_init': { ret: 'void', args: ['struct transcribe_sortformer_stream_ext *'] }, 'transcribe_speaker_segment_init': { ret: 'void', args: ['struct transcribe_speaker_segment *'] }, 'transcribe_status_string': { ret: 'const char *', args: ['int'] }, 'transcribe_stream_begin': { ret: 'transcribe_status', args: ['struct transcribe_session *', 'const struct transcribe_run_params *', 'const struct transcribe_stream_params *'] }, diff --git a/bindings/typescript/src/ffi.ts b/bindings/typescript/src/ffi.ts index cab0f932..e0d1ec11 100644 --- a/bindings/typescript/src/ffi.ts +++ b/bindings/typescript/src/ffi.ts @@ -166,6 +166,9 @@ export function bindLibrary(libraryPath: string): Bound { voxtralRealtimeStreamExtInit: lib.func("transcribe_voxtral_realtime_stream_ext_init", "void", [ outp(T.transcribe_voxtral_realtime_stream_ext), ]), + sortformerStreamExtInit: lib.func("transcribe_sortformer_stream_ext_init", "void", [ + outp(T.transcribe_sortformer_stream_ext), + ]), // batch (offline) runBatch: lib.func("transcribe_run_batch", "int", [ diff --git a/bindings/typescript/src/index.ts b/bindings/typescript/src/index.ts index 3f812e3e..970ba670 100644 --- a/bindings/typescript/src/index.ts +++ b/bindings/typescript/src/index.ts @@ -588,6 +588,20 @@ const FAMILY: Record = { min_decode_interval_ms: o.minDecodeIntervalMs, }), }, + sortformer: { + slot: "run", + kind: g.TRANSCRIBE_EXT_KIND_SORTFORMER_STREAM, + type: "transcribe_sortformer_stream_ext", + init: "sortformerStreamExtInit", + map: (o) => ({ preset: SORTFORMER_PRESET[o.preset as string] }), + }, +}; + +const SORTFORMER_PRESET: Record = { + default: g.TRANSCRIBE_SORTFORMER_PRESET_DEFAULT, + very_high_latency: g.TRANSCRIBE_SORTFORMER_PRESET_VERY_HIGH_LATENCY, + high_latency: g.TRANSCRIBE_SORTFORMER_PRESET_HIGH_LATENCY, + low_latency: g.TRANSCRIBE_SORTFORMER_PRESET_LOW_LATENCY, }; /** diff --git a/bindings/typescript/src/types.ts b/bindings/typescript/src/types.ts index 9dc03e61..9c087d72 100644 --- a/bindings/typescript/src/types.ts +++ b/bindings/typescript/src/types.ts @@ -238,10 +238,25 @@ export interface VoxtralRealtimeStreamOptions { numDelayTokens?: number; minDecodeIntervalMs?: number; } +/** Sortformer streaming operating point (latency / accuracy trade-off). + * "default" keeps the GGUF-shipped checkpoint configuration; + * "very_high_latency" (~30 s lookahead) is the offline-file operating + * point; "low_latency" (~1 s) is the real-time point. */ +export type SortformerPreset = + | "default" + | "very_high_latency" + | "high_latency" + | "low_latency"; +/** Sortformer diarizer options (run slot). A run produces speaker + * segments, no text. */ +export interface SortformerStreamOptions { + preset?: SortformerPreset; +} export type FamilyExtension = | ({ kind: "whisper" } & WhisperRunOptions) | ({ kind: "moonshine" } & MoonshineStreamingOptions) | ({ kind: "parakeet" } & ParakeetStreamOptions) | ({ kind: "parakeet_buffered" } & ParakeetBufferedStreamOptions) - | ({ kind: "voxtral" } & VoxtralRealtimeStreamOptions); + | ({ kind: "voxtral" } & VoxtralRealtimeStreamOptions) + | ({ kind: "sortformer" } & SortformerStreamOptions); diff --git a/include/transcribe.abihash b/include/transcribe.abihash index 549ecfa6..8513799e 100644 --- a/include/transcribe.abihash +++ b/include/transcribe.abihash @@ -1 +1 @@ -d67a9bd78b964445 +fb2e64791dcbb70a From 76018c2ec983efcefc0f5d926763806f783c23e3 Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Sat, 25 Jul 2026 02:00:26 -0700 Subject: [PATCH 7/7] format --- bindings/rust/transcribe-cpp/src/family.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/rust/transcribe-cpp/src/family.rs b/bindings/rust/transcribe-cpp/src/family.rs index 7f72298b..78b7b877 100644 --- a/bindings/rust/transcribe-cpp/src/family.rs +++ b/bindings/rust/transcribe-cpp/src/family.rs @@ -141,8 +141,7 @@ impl RunExtRaw { (&**ext) as *const sys::transcribe_whisper_run_ext as *const sys::transcribe_ext } RunExtRaw::Sortformer(e) => { - (&**e) as *const sys::transcribe_sortformer_stream_ext - as *const sys::transcribe_ext + (&**e) as *const sys::transcribe_sortformer_stream_ext as *const sys::transcribe_ext } } }