diff --git a/docs/AUDIO.md b/docs/AUDIO.md new file mode 100644 index 0000000..8cf88bb --- /dev/null +++ b/docs/AUDIO.md @@ -0,0 +1,262 @@ +# Audio Input (Alpha) + +Granite Switch can accept **audio input** through a single vLLM model load — no +separate speech server, no change to how developers deploy or call the model. + +This is an **alpha**: a speech-to-text *cascade*. Audio is transcribed to text by +a small ASR model and the transcript is fed to the LLM as ordinary tokens. It is +intentionally simple and requires no training. The "proper" upgrade (feeding a +trained projection of a speech encoder's embeddings straight into the LLM) reuses +the same hooks — see [Design](#design) below. + +## Installing + +The audio path needs `soundfile` and `librosa` on top of the vLLM backend — they +decode and resample the incoming waveform. They live in the `audio` extra, which is +**not** part of `vllm`, so a plain `uv sync --extra vllm` gives you a checkpoint that +fails on any non-16 kHz input: + +```bash +# Serving an audio-enabled checkpoint +uv sync --extra vllm --extra audio # or --extra vllm20 --extra audio + +# Development / running the test suite (the dev groups include audio already) +uv sync --group dev # vLLM 0.19.x +uv sync --group dev-vllm20 # vLLM 0.20.x +``` + +## Building an audio-enabled checkpoint + +Add `--enable-audio` when composing: + +```bash +python -m granite_switch.composer.compose_granite_switch \ + --base-model ibm-granite/granite-4.0-micro \ + --built-in-adapters core \ + --enable-audio \ + --output ./granite-switch-audio +``` + +This adds the `<|audio|>` marker token to the tokenizer and writes the audio +settings into `config.json` so the checkpoint is self-describing: + +```json +{ "asr_enabled": true, "asr_model_id": null, "asr_device": "cpu" } +``` + +- `asr_model_id` — HF id of the speech-to-text model (default: a small built-in + `distil-whisper/distil-small.en`). Override with `--asr-model `, e.g. + `openai/whisper-small` for multilingual. +- `asr_device` — `cpu` (default) keeps vLLM's GPU KV-cache budget clean; set + `--asr-device cuda:0` to run transcription on GPU (watch GPU memory). +- `asr_dtype` — precision the ASR weights load in. Unset (default) derives it + from the device: `float16` on CUDA, `float32` on CPU. Half precision halves + the ASR weight footprint and is what the Whisper-family defaults expect, but + it is not universally safe — an encoder with **BatchNorm** layers raises + `Expected weight to have type Float but got Half`, since BatchNorm will not + promote a float16 weight against float32 features. Such a checkpoint needs + `--asr-dtype float32`. Accepted: `auto`, `float16`, `bfloat16`, `float32`. + +Audio capability is **gated per checkpoint** by `asr_enabled`: a checkpoint built +without `--enable-audio` reports no audio modality and never loads the ASR model. + +### Tuning the ASR model + +Two optional config fields let a checkpoint carry ASR tuning so no code change is +needed to swap or steer any HF `automatic-speech-recognition` model: + +- `asr_pipeline_kwargs` — extra kwargs merged into the `transformers.pipeline(...)` + **construction** (e.g. `chunk_length_s`, `batch_size`). These change how the + pipeline is built, so they are folded into the transcriber cache key. +- `asr_generate_kwargs` — **decode-time** defaults applied on every transcription + (e.g. `language`, `task` for a multilingual Whisper). Applied at call time, so + one loaded pipeline is reused. Ignored by non-generative backends (e.g. CTC). + +Set them at compose time (JSON), which writes them into `config.json`: + +```bash +python -m granite_switch.composer.compose_granite_switch \ + --adapters ... \ + --asr-model openai/whisper-large-v3 \ + --asr-pipeline-kwargs '{"chunk_length_s": 15}' \ + --asr-generate-kwargs '{"language": "de", "task": "transcribe"}' +``` + +Because they live in `config.json`, an existing audio checkpoint can be retuned by +editing that file directly — no re-compose and no patched package: + +```json +{ "asr_enabled": true, "asr_model_id": "openai/whisper-large-v3", + "asr_pipeline_kwargs": {"chunk_length_s": 15}, + "asr_generate_kwargs": {"language": "de", "task": "transcribe"} } +``` + +### Long audio & multiple clips + +The transcript is spliced into the prompt as ordinary text tokens — it is **not** +truncated to fit. A request behaves exactly like a long text request: if the +prompt plus the transcript(s) leaves no room for the answer within the served +`max_model_len`, vLLM rejects it with its standard prompt-length error (HTTP 400). +Shorten the audio or serve with a larger `--max-model-len`. Relevant config fields +(all optional, sensible defaults): + +- `asr_max_audio_clips` (default `32`) — how many audio clips one request may + carry; each is spliced at its own `<|audio|>` marker. `--limit-mm-per-prompt` + may lower this per deployment but cannot raise it above the declared value. + Clips cost no extra KV (transcripts are ordinary text tokens bounded by the + context); the ceiling guards against one request triggering an unbounded number + of synchronous transcriptions. + +**Long single clips** are handled two ways, selected by `asr_self_chunks`: + +- `asr_self_chunks: true` (default) — the backend chunks internally. The Whisper + pipeline does this via `chunk_length_s` with timestamp-based stitching, so our + chunker is bypassed. +- `asr_self_chunks: false` — route audio through the **encoder-agnostic** chunker: + split into overlapping windows (`asr_chunk_length_s`, default `30.0`; + `asr_chunk_overlap_s`, default `5.0`), transcribe each, and merge with + overlap de-duplication. Use this for a backend with a fixed input window (e.g. a + speech encoder that cannot self-chunk); the transcript stitching then lives + above the backend so any backend inherits long-audio support. + +These are settable at compose time and are equally editable in `config.json`: + +```bash +python -m granite_switch.composer.compose_granite_switch \ + --adapters ... --enable-audio \ + --asr-max-audio-clips 4 \ + --asr-no-self-chunks --asr-chunk-length-s 20 --asr-chunk-overlap-s 3 +``` + +### Per-request language (multilingual) + +For one deployment that serves many languages, a request can override the config +default via `mm_processor_kwargs`. Only `language` and `task` are honored from a +request (an allowlist — clients cannot inject arbitrary generation options); the +config default supplies everything else, and request values win: + +```python +out = llm.generate({ + "prompt": "Transcript of the audio: <|audio|>\nAnswer:", + "multi_modal_data": {"audio": [(audio, sr)]}, + "mm_processor_kwargs": {"language": "fr"}, # this request, French +}, SamplingParams(max_tokens=128)) +``` + +The same cached pipeline serves every language — the decode kwargs are applied per +call, so there is no per-language reload. + +## Calling it + +### Python (offline) + +```python +from granite_switch.vllm import register; register() +from vllm import LLM, SamplingParams +import soundfile as sf + +llm = LLM(model="./granite-switch-audio") # one model load +audio, sr = sf.read("question.wav") # numpy array + sample rate + +out = llm.generate({ + "prompt": "Transcript of the audio: <|audio|>\nAnswer:", + "multi_modal_data": {"audio": [(audio, sr)]}, +}, SamplingParams(max_tokens=128)) +print(out[0].outputs[0].text) +``` + +The `<|audio|>` marker is where the transcript is spliced in. + +### OpenAI-compatible server / chat API + +```bash +vllm serve ./granite-switch-audio --port 8000 +``` +```python +from openai import OpenAI +client = OpenAI(base_url="http://localhost:8000/v1", api_key="x") +resp = client.chat.completions.create( + model="granite-switch-audio", + messages=[{"role": "user", "content": [ + {"type": "text", "text": "Answer the question in the audio."}, + {"type": "input_audio", "input_audio": {"data": "", "format": "wav"}}, + ]}], +) +print(resp.choices[0].message.content) +``` + +The chat template emits the `<|audio|>` marker for audio content parts +(`audio` / `input_audio` / `audio_url`), so the processor splices the transcript +in automatically — callers send standard chat messages, no manual marker needed. + +## Design + +Per request, before the scheduler allocates KV cache: + +1. vLLM's multimodal pipeline hands the audio to our processor + (`granite_switch.vllm.audio`). +2. The processor runs ASR → transcript → token ids. +3. A `PromptReplacement` swaps the `<|audio|>` marker for those transcript token + ids. The scheduler then sizes KV for the **real** length — the audio "window" + is variable and decided at runtime, not reserved in advance. + A clip with no recognizable speech in it — silence, music, noise, or a clip + too short to hold a word — transcribes to the empty string. Since every audio + item has to occupy at least one prompt position (vLLM discards a zero-length + placeholder and then rejects the request), those clips are replaced with a + single space instead: the model sees an audio turn that said nothing, rather + than an error. +4. The model's `embed_multimodal` supplies embeddings for those positions. In the + alpha that is simply the transcript's own token embeddings (identical to + embedding them as text). **This is the seam the future encoder reuses:** swap + `embed_multimodal` to return `projection(speech_encoder(audio))` and the rest + of the machinery is unchanged. + +The decoder, switch, and LoRA paths are untouched — they only ever see text +tokens. + +## Limitations (alpha) + +- **Cascade, not end-to-end.** Prosody/emotion/uncertainty are lost; ASR errors + propagate to the LLM. Two models run sequentially (ASR then LLM). +- **English by default** (`distil-whisper/distil-small.en`). Use `--asr-model` + with a multilingual model and set the language via `asr_generate_kwargs` (or + per request via `mm_processor_kwargs`; see *Tuning the ASR model* above). +- **HF `pipeline` backends only.** Any `automatic-speech-recognition` pipeline + model works via config alone; a non-pipeline backend (cloud STT, faster-whisper, + a custom encoder) still needs a code-level plug point — tracked as future work. +- Multiple clips share one context window: the per-clip transcript budget is the + context split across the request's clips, so many/long clips together are bound + by `max_model_len` (see *Long audio & multiple clips* above). +- Chunk-merge de-duplication is text-level (word overlap at each seam); it can + mis-handle a phrase legitimately repeated across a window boundary. Whisper's + internal timestamp stitching (`asr_self_chunks: true`) is more precise. + +## Audio + adapters + +Audio requests route through adapters exactly like text requests. The model sets +`requires_raw_input_tokens = True` so vLLM passes the raw `input_ids` to the +forward pass on the multimodal path; the switch then detects adapter control +tokens as usual, and `embed_input_ids` applies the same token-exchange rewrite +(control → substitute id) used for text — so an audio request that activates an +adapter behaves identically to the text equivalent. + +## Tests + +Everything on the audio path carries the `audio` marker, so the whole tier selects +in one command regardless of where the tests live: + +```bash +# All audio tests (13 of them need a GPU and a real checkpoint) +pytest -m audio -v -s --tb=short + +# CPU tier only — runs in a few seconds +pytest -m "audio and not gpu" -v -s --tb=short +``` + +- `tests/unit/test_asr.py` — CPU unit tests for the ASR backend (audio coercion, + resampling, transcription with a mocked pipeline, pipeline-kwargs cache keying, + and per-request decode-kwargs resolution). No GPU/vLLM required. +- `tests/unit/test_config.py` — round-trips `asr_pipeline_kwargs` / + `asr_generate_kwargs` through save/load. +- End-to-end (GPU): compose an `--enable-audio` checkpoint, then an audio request + through vLLM produces an answer and text-only requests are unaffected. diff --git a/pyproject.toml b/pyproject.toml index f522bf5..9573571 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,10 @@ vllm = ["vllm>=0.19.1,<0.20.0"] vllm20 = ["vllm>=0.20.0,<0.21.0"] compose = ["huggingface_hub", "pyyaml", "tqdm", "safetensors"] build = ["huggingface_hub", "pyyaml", "tqdm", "safetensors"] # Backward compatibility alias for compose +# Audio (ASR) preprocessing — speech-to-text cascade in the vLLM backend. +# transformers ships the ASR model itself (already a core dep); these add +# audio decoding/resampling. +audio = ["librosa", "soundfile"] tutorials = [ "granite-switch[hf,vllm,compose]", "chromadb>=0.4.0", @@ -49,13 +53,17 @@ markers = [ "slow: takes > 30s", "deep: expensive code-theory tests (m=8 / 256-dim); run with: pytest -m deep", "requires_model: needs a real model checkpoint", + "audio: exercises the audio/ASR path; run with: pytest -m audio", ] [dependency-groups] vllm19 = ["vllm>=0.19.1,<0.20.0"] vllm20 = ["vllm>=0.20.0,<0.21.0"] -dev = ["pytest", "pytest-cov", { include-group = "vllm19" }, "granite-switch[hf,compose]"] -dev-vllm20 = ["pytest", "pytest-cov", { include-group = "vllm20" }, "granite-switch[hf,compose]"] +# `audio` is included so the audio tests can actually run: the ASR path needs +# soundfile/librosa at runtime, and no group pulled them in before (integration +# tests failed with ModuleNotFoundError on a synced pod). +dev = ["pytest", "pytest-cov", { include-group = "vllm19" }, "granite-switch[hf,compose,audio]"] +dev-vllm20 = ["pytest", "pytest-cov", { include-group = "vllm20" }, "granite-switch[hf,compose,audio]"] test = ["pytest", "pytest-cov", "bitsandbytes", "optimum-quanto", { include-group = "dev" }] [tool.uv] diff --git a/src/granite_switch/composer/compose_granite_switch.py b/src/granite_switch/composer/compose_granite_switch.py index 4757203..0517c25 100755 --- a/src/granite_switch/composer/compose_granite_switch.py +++ b/src/granite_switch/composer/compose_granite_switch.py @@ -61,10 +61,14 @@ from granite_switch.composer.compose_utils import GraniteSwitchComposer from granite_switch.composer.reporting import generate_compose_report, write_build_doc from granite_switch.composer.tokenizer_setup import ( + add_audio_token, add_control_tokens, + configure_audio_chat_template, configure_chat_template, get_alora_first_invocation_token_id, ) +from granite_switch.composer.validator import validate_control_lut +from granite_switch.config import ASR_DTYPES # --------------------------------------------------------------------------- # Utility helpers (kept local — not worth a separate module) @@ -566,6 +570,90 @@ def _compose_argparser(): default=False, help="Include debug fields (original_path) in adapter_index.json", ) + parser.add_argument( + "--enable-audio", + action="store_true", + default=False, + help="Enable the audio cascade: add the <|audio|> marker token and set " + "asr_enabled in the config so the vLLM backend transcribes audio.", + ) + parser.add_argument( + "--asr-model", + type=str, + default=None, + help="HF id of the speech-to-text model the audio preprocessor loads. " + "Implies --enable-audio. Defaults to a small built-in model when unset.", + ) + parser.add_argument( + "--asr-device", + type=str, + default="cpu", + help="Device the ASR model runs on (default: cpu). Use e.g. cuda:0 to " + "run transcription on GPU (watch vLLM's KV-cache memory budget).", + ) + parser.add_argument( + "--asr-dtype", + type=str, + default=None, + choices=ASR_DTYPES, + help="Precision the ASR weights load in. Default derives it from " + "--asr-device (float16 on CUDA, float32 on CPU); set float32 for an " + "encoder that cannot run in half precision (e.g. one with BatchNorm). " + "Implies --enable-audio.", + ) + parser.add_argument( + "--asr-pipeline-kwargs", + type=json.loads, + default=None, + help="JSON object of extra kwargs merged into the transformers ASR " + "pipeline() construction, e.g. '{\"chunk_length_s\": 15}'. Baked " + "into the checkpoint config. Implies --enable-audio.", + ) + parser.add_argument( + "--asr-generate-kwargs", + type=json.loads, + default=None, + help="JSON object of default decode kwargs applied on every " + 'transcription, e.g. \'{"language": "de", "task": ' + '"transcribe"}\' for multilingual Whisper. Per-request ' + "mm_processor_kwargs override these. Implies --enable-audio.", + ) + parser.add_argument( + "--asr-max-audio-clips", + type=int, + default=None, + help="Max audio clips accepted per request (default 32). Implies " + "--enable-audio.", + ) + parser.add_argument( + "--asr-self-chunks", + dest="asr_self_chunks", + action="store_true", + default=None, + help="Backend chunks long audio itself (Whisper default). Mutually " + "exclusive with --asr-no-self-chunks.", + ) + parser.add_argument( + "--asr-no-self-chunks", + dest="asr_self_chunks", + action="store_false", + help="Route long audio through the encoder-agnostic split/merge chunker " + "(for backends with a fixed input window). Implies --enable-audio.", + ) + parser.add_argument( + "--asr-chunk-length-s", + type=float, + default=None, + help="Chunker window length in seconds (default 30.0). Only used when " + "the backend does not self-chunk. Implies --enable-audio.", + ) + parser.add_argument( + "--asr-chunk-overlap-s", + type=float, + default=None, + help="Chunker window overlap in seconds (default 5.0). Only used when " + "the backend does not self-chunk. Implies --enable-audio.", + ) return parser @@ -771,12 +859,38 @@ def build(): adapter_token_ids, special_tokens = add_control_tokens(tokenizer, all_discovered) + # Audio cascade: add the <|audio|> marker token before the embedding resize. + # Enabled by --enable-audio or by naming an --asr-model. + audio_enabled = ( + args.enable_audio + or args.asr_model is not None + or args.asr_dtype is not None + or args.asr_pipeline_kwargs is not None + or args.asr_generate_kwargs is not None + or args.asr_max_audio_clips is not None + or args.asr_self_chunks is not None + or args.asr_chunk_length_s is not None + or args.asr_chunk_overlap_s is not None + ) + # The control tokens are re-passed so this call doesn't drop them from the + # tokenizer's additional-special-tokens list (add_special_tokens replaces + # that list rather than extending it). + audio_token_id = ( + add_audio_token(tokenizer, keep_special_tokens=special_tokens) + if audio_enabled + else None + ) + # Configure chat template with adapter mappings (Granite models only). # Non-Granite models preserve the upstream template verbatim because # the injection targets Granite-specific Jinja patterns. normalized_type = getattr(base_config, "model_type", "").replace("_switch", "") if normalized_type.startswith("granite"): configure_chat_template(tokenizer, all_discovered) + if audio_enabled: + # Make the chat template emit <|audio|> for audio content parts so + # the OpenAI server / chat() path works (the ASR processor replaces it). + configure_audio_chat_template(tokenizer) else: print(" Skipping chat template configuration (non-Granite model)") @@ -834,6 +948,41 @@ def build(): **optional_kwargs, ) + # Record audio-cascade settings in the config so the checkpoint is + # self-describing and the vLLM backend gates audio on asr_enabled. + if audio_enabled: + model.config.asr_enabled = True + model.config.asr_model_id = args.asr_model + model.config.asr_device = args.asr_device + model.config.asr_dtype = args.asr_dtype + # Optional pipeline-construction extras and default decode kwargs. Only + # set when provided so the config stays minimal for the common case. + if args.asr_pipeline_kwargs is not None: + model.config.asr_pipeline_kwargs = args.asr_pipeline_kwargs + if args.asr_generate_kwargs is not None: + model.config.asr_generate_kwargs = args.asr_generate_kwargs + # Long-audio / multi-clip knobs. Only set when explicitly given so the + # config keeps the constructor defaults otherwise. + if args.asr_max_audio_clips is not None: + model.config.asr_max_audio_clips = args.asr_max_audio_clips + if args.asr_self_chunks is not None: + model.config.asr_self_chunks = args.asr_self_chunks + if args.asr_chunk_length_s is not None: + model.config.asr_chunk_length_s = args.asr_chunk_length_s + if args.asr_chunk_overlap_s is not None: + model.config.asr_chunk_overlap_s = args.asr_chunk_overlap_s + print( + f" Audio cascade enabled " + f"(asr_model_id={args.asr_model or 'default'}, " + f"asr_device={args.asr_device}, " + f"asr_dtype={args.asr_dtype or 'auto'}, " + f"audio_token_id={audio_token_id}, " + f"pipeline_kwargs={args.asr_pipeline_kwargs or {}}, " + f"generate_kwargs={args.asr_generate_kwargs or {}}, " + f"max_audio_clips={args.asr_max_audio_clips or 'default'}, " + f"self_chunks={args.asr_self_chunks})" + ) + # Base model size (best effort) base_model_size_gb, _ = _get_directory_size(base_model_local_path) if base_model_size_gb is not None: @@ -870,6 +1019,23 @@ def build(): new_embed_size = model.model.embed_tokens.weight.shape[0] print(f"Embeddings resized: {old_embed_size} -> {new_embed_size}") + # The switch sized its control->substitute table from the pre-resize + # config.vocab_size (copied from the base model), so the resize above leaves + # it short of the config this checkpoint will ship with. Re-derive it, then + # assert the two agree — see validate_control_lut for why a mismatch is not + # something the loader can recover from. + switch = getattr(model.model, "switch", None) + if switch is not None: + lut = getattr(switch, "control_to_substitute_lut", None) + if lut is not None and lut.numel() != model.config.vocab_size: + old_lut_size = lut.numel() + switch.rebuild_control_to_substitute_lut(model.config) + print( + "Switch control LUT rebuilt: " + f"{old_lut_size} -> {switch.control_to_substitute_lut.numel()}" + ) + validate_control_lut(model) + print(f"\nStep 3 complete in {time.time() - step_start:.2f}s") return ( diff --git a/src/granite_switch/composer/tokenizer_setup.py b/src/granite_switch/composer/tokenizer_setup.py index 23fcc2d..4232a1f 100644 --- a/src/granite_switch/composer/tokenizer_setup.py +++ b/src/granite_switch/composer/tokenizer_setup.py @@ -94,6 +94,79 @@ def add_control_tokens( return adapter_token_ids, special_tokens +def add_audio_token( + tokenizer, + marker: str = "<|audio|>", + keep_special_tokens: list[str] | None = None, +) -> int: + """Add the audio placeholder marker token to the tokenizer. + + Used for the audio cascade: this single special token is placed in the + prompt and the vLLM ASR processor replaces it with the transcript tokens at + request time (see granite_switch.vllm.audio). Registering it as one special + token keeps the processor's prompt-replacement match clean. + + ``keep_special_tokens`` must list every token an earlier + ``add_special_tokens({"additional_special_tokens": ...})`` call registered — + in practice the adapter control tokens from :func:`add_control_tokens`. + That call *replaces* the additional-special-tokens list instead of appending + to it, and transformers exposes no way to read the current list back, so any + token not re-passed here silently drops out of ``all_special_tokens`` and + out of the saved ``tokenizer_config.json``. Re-passing an already-added + token is free: it keeps its id and does not grow the vocabulary. + + Must be called before the model's embedding resize so the new row is sized + in. Returns the marker's token id. + """ + print(f"\nAdding audio marker token: {marker}") + # Marker last so it takes the next free id and the kept tokens keep theirs. + kept = [t for t in (keep_special_tokens or []) if t != marker] + tokenizer.add_special_tokens({"additional_special_tokens": [*kept, marker]}) + token_id = tokenizer.convert_tokens_to_ids(marker) + print(f" {marker}: {token_id}") + if kept: + print(f" (preserved {len(kept)} existing special token(s))") + return token_id + + +def configure_audio_chat_template(tokenizer, marker: str = "<|audio|>") -> None: + """Make the chat template emit the audio marker for audio content parts. + + The Granite content-part loop only handles ``entry.type == 'text'`` and + silently drops other parts. vLLM passes multimodal chat content to the + template as a *list of parts*, so without this the ``<|audio|>`` marker never + reaches the rendered prompt and the ASR processor's prompt replacement fails + (``Failed to apply prompt replacement for mm_items['audio'][0]``). + + We inject an ``elif`` that appends the marker for any part whose ``type`` + contains ``'audio'`` (covers ``audio`` / ``input_audio`` / ``audio_url``). + Call after :func:`configure_chat_template`, gated on audio being enabled. + """ + template = tokenizer.chat_template + if template is None: + print("Warning: no chat template; skipping audio chat-template handling") + return + + # The text-only branch of the Granite content-part loop: + old = ( + " {%- set content.val = content.val + entry.text %}\n" + " {%- endif %}" + ) + if old not in template: + raise ValueError( + "Could not find the Granite content-part loop to inject audio " + "handling; the base chat template may have changed." + ) + new = ( + " {%- set content.val = content.val + entry.text %}\n" + " {%- elif 'audio' in entry.type %}\n" + " {%- set content.val = content.val + '" + marker + "' %}\n" + " {%- endif %}" + ) + tokenizer.chat_template = template.replace(old, new, 1) + print(f" Audio chat-template handling added (emits {marker} for audio parts)") + + def configure_chat_template( tokenizer, discovered_adapters: list[tuple[str | None, str, str, str | None]], diff --git a/src/granite_switch/composer/validator.py b/src/granite_switch/composer/validator.py index df3a594..55d52a5 100644 --- a/src/granite_switch/composer/validator.py +++ b/src/granite_switch/composer/validator.py @@ -13,6 +13,39 @@ from .arch import ArchDescriptor +def validate_control_lut(model) -> None: + """Check the switch's control->substitute table matches the shipped config. + + The table is a persistent buffer sized from ``config.vocab_size``, so a + checkpoint whose buffer length disagrees with its own ``config.json`` is + internally inconsistent. Loading one is not a graceful degradation: + ``from_pretrained`` discards the mismatched tensor and leaves the buffer as + uninitialised memory, which turns every token into a "control" token and + sends out-of-range ids into the embedding gather — surfacing as an opaque + CUDA ``srcIndex < srcSelectDimSize`` device-side assert far from the cause. + + Raises: + ValueError: if the table length differs from ``config.vocab_size``. + """ + switch = getattr(getattr(model, "model", None), "switch", None) + lut = getattr(switch, "control_to_substitute_lut", None) + if lut is None: + return # no token-exchange mapping on this model + + expected = getattr(model.config, "vocab_size", None) + if expected is None or lut.numel() == expected: + return + + raise ValueError( + f"control_to_substitute_lut has {lut.numel()} entries but " + f"config.vocab_size is {expected}. Saving this model would produce a " + f"checkpoint that cannot be loaded correctly. The table is derived from " + f"vocab_size and the adapter token ids, so rebuild it after any " + f"vocabulary change with " + f"switch.rebuild_control_to_substitute_lut(model.config)." + ) + + def validate_all_parameters( model, arch: ArchDescriptor, diff --git a/src/granite_switch/config.py b/src/granite_switch/config.py index 1162344..cc788d8 100644 --- a/src/granite_switch/config.py +++ b/src/granite_switch/config.py @@ -3,6 +3,9 @@ from transformers import GraniteMoeHybridConfig +# Accepted asr_dtype values. Keep in sync with vllm.audio.asr._ASR_DTYPE_NAMES. +ASR_DTYPES = ("auto", "float16", "bfloat16", "float32") + class GraniteSwitchConfig(GraniteMoeHybridConfig): """Configuration class for GraniteSwitch model. @@ -36,6 +39,39 @@ class GraniteSwitchConfig(GraniteMoeHybridConfig): lora_target_modules (List[str]): List of module GROUP names to apply LoRA to. Module groups: "qkv_proj", "o_proj", "shared_input_linear", "shared_output_linear". Default: all four groups + + Audio (ASR) preprocessing parameters (see docs/AUDIO.md): + asr_enabled (bool): Register the audio preprocessor that transcribes + audio and splices the transcript into the prompt. Default: False. + asr_model_id (Optional[str]): HF id of the speech-to-text model. None + falls back to a small built-in default. + asr_device (str): Device the ASR model runs on. Default "cpu" keeps + vLLM's GPU KV-cache budget clean. + asr_dtype (Optional[str]): Precision the ASR weights load in, one of + ASR_DTYPES. None/"auto" derives it from asr_device (float16 on + CUDA). An encoder with BatchNorm layers must set "float32". + Default: None. + asr_pipeline_kwargs (Optional[dict]): Extra kwargs merged into the + ``transformers.pipeline(...)`` construction, e.g. + ``{"chunk_length_s": 15}``. Baked into the transcriber cache key. + Default: None. + asr_generate_kwargs (Optional[dict]): Default decode-time kwargs, e.g. + ``{"language": "de"}``. Applied per call, so one pipeline is + reused; per-request ``mm_processor_kwargs`` override them. Ignored + by non-generative backends. Default: None. + asr_max_audio_clips (int): Max audio clips per request. Bounds the + synchronous transcriptions one request can trigger and the startup + profiling pass; ``--limit-mm-per-prompt`` may lower it, not raise + it. Default: 32. + asr_chunk_length_s (float): Chunker window length in seconds. Only + used when asr_self_chunks is False. Default: 30.0. + asr_chunk_overlap_s (float): Overlap in seconds between chunker + windows, de-duplicated by the transcript merge. Only used when + asr_self_chunks is False. Default: 5.0. + asr_self_chunks (bool): True when the backend chunks long audio + itself (Whisper's timestamp stitching beats our text-level merge), + bypassing our chunker. False routes audio through the + split/transcribe/merge chunker instead. Default: True. **kwargs: Additional arguments passed to GraniteConfig. """ @@ -54,6 +90,17 @@ def __init__( max_lora_rank: int = 8, adapter_ranks: list[int] | None = None, lora_target_modules: list[str] | None = None, + # Audio (ASR) preprocessing parameters + asr_enabled: bool = False, + asr_model_id: str | None = None, + asr_device: str = "cpu", + asr_dtype: str | None = None, + asr_pipeline_kwargs: dict | None = None, + asr_generate_kwargs: dict | None = None, + asr_max_audio_clips: int = 32, + asr_chunk_length_s: float = 30.0, + asr_chunk_overlap_s: float = 5.0, + asr_self_chunks: bool = True, # vLLM residual-norm convention (for bit-exact skinning equivalence) fused_add_norm: bool = False, # Parent class defaults (Granite 4 dense configuration) @@ -134,6 +181,33 @@ def __init__( self.switch_head_dim = switch_head_dim self.fused_add_norm = fused_add_norm + # Audio (ASR) preprocessing. The decoder is oblivious to audio; these + # fields make the checkpoint self-describing about its ASR front-end. + self.asr_enabled = asr_enabled + self.asr_model_id = asr_model_id + self.asr_device = asr_device + # Validated here so a typo fails at compose time, not in a vLLM worker. + if asr_dtype is not None and asr_dtype not in ASR_DTYPES: + raise ValueError( + f"asr_dtype must be one of {ASR_DTYPES} or None, got {asr_dtype!r}" + ) + self.asr_dtype = asr_dtype + self.asr_pipeline_kwargs = asr_pipeline_kwargs + self.asr_generate_kwargs = asr_generate_kwargs + if asr_max_audio_clips < 1: + raise ValueError( + f"asr_max_audio_clips must be >= 1, got {asr_max_audio_clips}" + ) + if asr_chunk_overlap_s >= asr_chunk_length_s: + raise ValueError( + f"asr_chunk_overlap_s ({asr_chunk_overlap_s}) must be < " + f"asr_chunk_length_s ({asr_chunk_length_s})" + ) + self.asr_max_audio_clips = asr_max_audio_clips + self.asr_chunk_length_s = asr_chunk_length_s + self.asr_chunk_overlap_s = asr_chunk_overlap_s + self.asr_self_chunks = asr_self_chunks + # Adapter names self.adapter_names = adapter_names diff --git a/src/granite_switch/hf/switch/single.py b/src/granite_switch/hf/switch/single.py index 8f73a9b..dba7e5c 100644 --- a/src/granite_switch/hf/switch/single.py +++ b/src/granite_switch/hf/switch/single.py @@ -19,6 +19,35 @@ from transformers.models.granite.modeling_granite import eager_attention_forward +def build_control_to_substitute_lut(config) -> torch.Tensor | None: + """Derive the control->substitute lookup table from *config*. + + Shape ``[max(vocab_size, max_ctrl_id + 1)]``: ``-1`` at every non-control id + and the substitute id at each control slot. The ``max`` keeps every control + id addressable even when ``vocab_size`` lags the tokenizer. + + Returns ``None`` when *config* carries no token-exchange mapping, in which + case the switch leaves ``input_ids`` untouched. + + Single source of truth for the sizing rule: the table is a pure function of + ``vocab_size``, ``adapter_token_ids`` and ``adapter_substitute_token_ids``, + so anything that changes those must re-derive it (see + :meth:`SingleSwitch.rebuild_control_to_substitute_lut`). + """ + if config is None: + return None + ctrl_ids = getattr(config, "adapter_token_ids", None) + sub_ids = getattr(config, "adapter_substitute_token_ids", None) + if not ctrl_ids or not sub_ids: + return None + + lut_size = max(getattr(config, "vocab_size", 0), max(ctrl_ids) + 1) + lut = torch.full((lut_size,), -1, dtype=torch.long) + for ctrl_id, sub_id in zip(ctrl_ids, sub_ids): + lut[ctrl_id] = sub_id + return lut + + class SingleSwitch(nn.Module): """Single-head attention-based switch for adapter selection. @@ -89,22 +118,44 @@ def __init__( # control-token positions carry the substitute id by the time the # decoder embeds them. The decoder is then oblivious — it just calls # embed_tokens(input_ids) and gets the right result by construction. - if ( - config is not None - and getattr(config, "adapter_token_ids", None) is not None - and getattr(config, "adapter_substitute_token_ids", None) is not None - ): - ctrl_ids = config.adapter_token_ids - sub_ids = config.adapter_substitute_token_ids - max_ctrl_id = max(ctrl_ids) - lut_size = max(getattr(config, "vocab_size", 0), max_ctrl_id + 1) - lut = torch.full((lut_size,), -1, dtype=torch.long) - for ctrl_id, sub_id in zip(ctrl_ids, sub_ids): - lut[ctrl_id] = sub_id + lut = build_control_to_substitute_lut(config) + if lut is not None: self.register_buffer("control_to_substitute_lut", lut) else: self.control_to_substitute_lut = None + def rebuild_control_to_substitute_lut(self, config=None) -> bool: + """Re-derive the control->substitute table after a vocabulary change. + + ``__init__`` sizes the table from ``config.vocab_size``, so anything that + grows the vocabulary afterwards — notably + ``resize_token_embeddings`` when compose adds control and marker tokens — + leaves the buffer shorter than the config it will be saved alongside. + + That matters because the buffer is persistent. On ``from_pretrained``, + a stored tensor whose shape disagrees with the freshly-constructed one is + discarded and the buffer is left as uninitialised memory (there is no + ``_init_weights`` rule for it), so every id reads as a control id and the + rewrite sends out-of-range ids into the embedding gather. Re-derive the + table before saving so the checkpoint and its config agree. + + Returns ``True`` if a table was rebuilt, ``False`` if this switch has no + token-exchange mapping to rebuild. + """ + lut = build_control_to_substitute_lut( + config if config is not None else self.config + ) + if lut is None: + return False + + existing = getattr(self, "control_to_substitute_lut", None) + if existing is not None: + lut = lut.to(device=existing.device) + self.control_to_substitute_lut = lut + else: + self.register_buffer("control_to_substitute_lut", lut) + return True + @property def num_cache_layers(self) -> int: """Number of cache slots used.""" diff --git a/src/granite_switch/vllm/audio/__init__.py b/src/granite_switch/vllm/audio/__init__.py new file mode 100644 index 0000000..feab7ce --- /dev/null +++ b/src/granite_switch/vllm/audio/__init__.py @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Audio (ASR) preprocessing for the Granite Switch vLLM backend. + +Speech-to-text cascade: audio is transcribed and the transcript tokens are +spliced into the prompt, so the decoder only ever sees text. See docs/AUDIO.md. +""" + +from .asr import DEFAULT_ASR_MODEL_ID, ASRTranscriber, transcribe + +__all__ = [ + "DEFAULT_ASR_MODEL_ID", + "ASRTranscriber", + "transcribe", +] diff --git a/src/granite_switch/vllm/audio/asr.py b/src/granite_switch/vllm/audio/asr.py new file mode 100644 index 0000000..b95085a --- /dev/null +++ b/src/granite_switch/vllm/audio/asr.py @@ -0,0 +1,346 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Speech-to-text backend for the audio cascade. + +Wraps a HuggingFace ASR pipeline. Free of any vLLM import so it unit-tests on +CPU. The model loads lazily and is cached per (model_id, device, dtype, +pipeline_kwargs), so a process loads each ASR model at most once. + +Device defaults to CPU to keep vLLM's GPU KV-cache budget clean; dtype follows +the device unless a checkpoint sets ``asr_dtype``. See docs/AUDIO.md. +""" + +from __future__ import annotations + +import threading +from collections.abc import Mapping +from typing import Any, Union + +import numpy as np + +# Small, CPU-friendly, English ASR model that emits text directly. Used when the +# checkpoint does not name its own (config.asr_model_id is None). +DEFAULT_ASR_MODEL_ID = "distil-whisper/distil-small.en" + +ASR_DTYPE_AUTO = "auto" + +# Keep in sync with config.ASR_DTYPES. +_ASR_DTYPE_NAMES = frozenset({"float16", "bfloat16", "float32"}) +_ASR_DTYPE_ALIASES = { + "fp16": "float16", + "half": "float16", + "bf16": "bfloat16", + "fp32": "float32", + "float": "float32", +} + + +def _resolve_torch_dtype(dtype: str | None, device: str) -> Any: + """Resolve an ``asr_dtype`` name to a ``torch.dtype``. + + None/"auto" derives it from the device: float16 on CUDA, float32 elsewhere + (CPU float16 is slow and partly unimplemented). Name a dtype explicitly for + an encoder that cannot run in half precision — BatchNorm raises on a float16 + weight against float32 features rather than promoting. + """ + import torch + + name = str(dtype or ASR_DTYPE_AUTO).lower() + name = _ASR_DTYPE_ALIASES.get(name, name) + if name == ASR_DTYPE_AUTO: + on_cuda = isinstance(device, str) and device.startswith("cuda") + return torch.float16 if on_cuda else torch.float32 + if name not in _ASR_DTYPE_NAMES: + raise ValueError( + f"Unsupported asr_dtype {dtype!r}. Expected {ASR_DTYPE_AUTO!r} (or " + f"None) to derive it from the device, or one of: " + f"{', '.join(sorted(_ASR_DTYPE_NAMES))}." + ) + return getattr(torch, name) + + +_CHUNKING = None + + +def _load_chunking(): + """Load the pure chunking helpers, memoized. + + Uses the normal relative import in production (running inside the + ``granite_switch.vllm.audio`` package). Falls back to a direct file-path load + when this module is imported standalone (the CPU unit tests load ``asr.py`` by + path to skip the vLLM-importing package ``__init__``). + """ + global _CHUNKING + if _CHUNKING is not None: + return _CHUNKING + try: + from . import chunking as _chunking # normal package import + except ImportError: + import importlib.util + import pathlib + + path = pathlib.Path(__file__).with_name("chunking.py") + spec = importlib.util.spec_from_file_location("gs_chunking", path) + _chunking = importlib.util.module_from_spec(spec) + spec.loader.exec_module(_chunking) + _CHUNKING = _chunking + return _CHUNKING + + +# Sample rate expected by Whisper-family feature extractors. +_TARGET_SAMPLE_RATE = 16_000 + +# Audio item shapes vLLM may pass to a multimodal processor. +AudioInput = Union[ + np.ndarray, + "list[float]", + tuple[np.ndarray, int | float], + "object", # torch.Tensor — typed loosely to avoid importing torch here +] + + +class ASRTranscriber: + """Lazily-loaded ASR model wrapper exposing :meth:`transcribe`.""" + + def __init__( + self, + model_id: str = DEFAULT_ASR_MODEL_ID, + device: str = "cpu", + pipeline_kwargs: Mapping[str, Any] | None = None, + dtype: str | None = None, + ) -> None: + self.model_id = model_id + self.device = device + self.dtype = dtype + self.pipeline_kwargs: dict[str, Any] = dict(pipeline_kwargs or {}) + self._pipeline = None + self._load_lock = threading.Lock() + + def load(self) -> None: + """Materialize the ASR pipeline if it has not been loaded yet.""" + if self._pipeline is not None: + return + with self._load_lock: + if self._pipeline is not None: + return + # Lazy: keeps this module importable without transformers' audio stack. + from transformers import pipeline + + kwargs: dict[str, Any] = { + "task": "automatic-speech-recognition", + "model": self.model_id, + "device": self.device, + "torch_dtype": _resolve_torch_dtype(self.dtype, self.device), + "chunk_length_s": 30, + } + # pipeline_kwargs last: a checkpoint may override any default above. + kwargs.update(self.pipeline_kwargs) + self._pipeline = pipeline(**kwargs) + + def transcribe( + self, + audio: AudioInput, + sampling_rate: int | None = None, + generate_kwargs: Mapping[str, Any] | None = None, + self_chunks: bool = True, + chunk_length_s: float = 30.0, + chunk_overlap_s: float = 5.0, + ) -> str: + """Transcribe one audio clip, stripped. Resampled to 16 kHz as needed. + + ``sampling_rate`` is required unless ``audio`` is an ``(array, rate)`` + tuple. ``generate_kwargs`` is passed only when non-empty, so CTC backends + are unaffected. ``self_chunks=False`` routes long audio through + :mod:`.chunking` using ``chunk_length_s``/``chunk_overlap_s``. + """ + samples, sr = _coerce_audio(audio, sampling_rate) + samples = _to_mono_float32(samples) + samples = _resample(samples, sr, _TARGET_SAMPLE_RATE) + + self.load() + + if self_chunks: + return self._run_pipeline(samples, generate_kwargs) + + chunking = _load_chunking() + segments = chunking.split_waveform( + samples, _TARGET_SAMPLE_RATE, chunk_length_s, chunk_overlap_s + ) + texts = [self._run_pipeline(seg, generate_kwargs) for seg in segments] + return chunking.merge_transcripts(texts).strip() + + def _run_pipeline( + self, + samples: np.ndarray, + generate_kwargs: Mapping[str, Any] | None = None, + ) -> str: + """Run the loaded pipeline over an already-resampled mono waveform.""" + call_kwargs: dict[str, Any] = {} + if generate_kwargs: + call_kwargs["generate_kwargs"] = dict(generate_kwargs) + result = self._pipeline( + {"raw": samples, "sampling_rate": _TARGET_SAMPLE_RATE}, + **call_kwargs, + ) + text = result["text"] if isinstance(result, dict) else str(result) + return text.strip() + + +# ── Module-level cache + convenience function ──────────────────────────────── + +# Keyed on what changes the constructed pipeline. generate_kwargs are excluded: +# they apply per transcribe() call, so one cached pipeline serves every language. +_TRANSCRIBERS: dict[tuple, ASRTranscriber] = {} +_CACHE_LOCK = threading.Lock() + + +# Allowlisted so a client cannot inject arbitrary generation options; everything +# else is fixed by the checkpoint in config.asr_generate_kwargs. +DEFAULT_ALLOWED_REQUEST_GENERATE_KEYS = frozenset({"language", "task"}) + + +def resolve_generate_kwargs( + config_defaults: Mapping[str, Any] | None, + request: Mapping[str, Any] | None = None, + allowed_keys: frozenset[str] = DEFAULT_ALLOWED_REQUEST_GENERATE_KEYS, +) -> dict[str, Any]: + """Merge config-default decode kwargs with allowlisted per-request overrides. + + ``request`` is vLLM's ``mm_processor_kwargs``: a top-level ``language`` or a + nested ``asr_generate_kwargs`` may override the defaults, but only for keys + in ``allowed_keys``. Request values win, so one model serves many languages. + """ + merged: dict[str, Any] = dict(config_defaults or {}) + if isinstance(request, Mapping): + nested = request.get("asr_generate_kwargs") + if isinstance(nested, Mapping): + merged.update({k: v for k, v in nested.items() if k in allowed_keys}) + language = request.get("language") + if language is not None: + merged["language"] = language + return merged + + +def _freeze(value: Any) -> Any: + """Recursively convert dicts/lists into a hashable, order-stable form.""" + if isinstance(value, Mapping): + return tuple(sorted((k, _freeze(v)) for k, v in value.items())) + if isinstance(value, (list, tuple)): + return tuple(_freeze(v) for v in value) + return value + + +def get_transcriber( + model_id: str | None = None, + device: str = "cpu", + pipeline_kwargs: Mapping[str, Any] | None = None, + dtype: str | None = None, +) -> ASRTranscriber: + """Return a process-wide cached :class:`ASRTranscriber`. + + Cached per ``(model_id, device, dtype, pipeline_kwargs)``. ``model_id`` of + None resolves to :data:`DEFAULT_ASR_MODEL_ID`. + """ + resolved = model_id or DEFAULT_ASR_MODEL_ID + key = (resolved, device, dtype, _freeze(pipeline_kwargs or {})) + transcriber = _TRANSCRIBERS.get(key) + if transcriber is None: + with _CACHE_LOCK: + transcriber = _TRANSCRIBERS.get(key) + if transcriber is None: + transcriber = ASRTranscriber( + model_id=resolved, + device=device, + pipeline_kwargs=pipeline_kwargs, + dtype=dtype, + ) + _TRANSCRIBERS[key] = transcriber + return transcriber + + +def transcribe( + audio: AudioInput, + sampling_rate: int | None = None, + *, + model_id: str | None = None, + device: str = "cpu", + pipeline_kwargs: Mapping[str, Any] | None = None, + dtype: str | None = None, + generate_kwargs: Mapping[str, Any] | None = None, + self_chunks: bool = True, + chunk_length_s: float = 30.0, + chunk_overlap_s: float = 5.0, +) -> str: + """Convenience wrapper: transcribe with the cached transcriber for the args.""" + return get_transcriber( + model_id=model_id, + device=device, + pipeline_kwargs=pipeline_kwargs, + dtype=dtype, + ).transcribe( + audio, + sampling_rate, + generate_kwargs=generate_kwargs, + self_chunks=self_chunks, + chunk_length_s=chunk_length_s, + chunk_overlap_s=chunk_overlap_s, + ) + + +# ── Audio coercion helpers ─────────────────────────────────────────────────── + + +def _coerce_audio( + audio: AudioInput, + sampling_rate: int | None, +) -> tuple[np.ndarray, int]: + """Normalize the various accepted audio shapes to ``(np.ndarray, sr)``.""" + # (array, sampling_rate) tuple — vLLM's AudioItem form. + if isinstance(audio, tuple): + if len(audio) != 2: + raise ValueError( + f"Tuple audio input must be (array, sampling_rate); got " + f"length {len(audio)}." + ) + array, sr = audio + return _as_numpy(array), int(sr) + + if sampling_rate is None: + raise ValueError( + "sampling_rate is required when audio is not an " + "(array, sampling_rate) tuple." + ) + return _as_numpy(audio), int(sampling_rate) + + +def _as_numpy(array: object) -> np.ndarray: + """Convert a numpy array, list, or torch tensor to a numpy array.""" + if isinstance(array, np.ndarray): + return array + # torch.Tensor without importing torch at module load. + if hasattr(array, "detach") and hasattr(array, "cpu"): + return array.detach().cpu().numpy() + return np.asarray(array) + + +def _to_mono_float32(samples: np.ndarray) -> np.ndarray: + """Downmix to mono and cast to float32.""" + if samples.ndim > 1: + # Average across channels. Assume the smaller axis is channels. + channel_axis = int(np.argmin(samples.shape)) + samples = samples.mean(axis=channel_axis) + return samples.astype(np.float32, copy=False) + + +def _resample(samples: np.ndarray, orig_sr: int, target_sr: int) -> np.ndarray: + """Resample to ``target_sr`` Hz. No-op when already at the target rate.""" + if orig_sr == target_sr: + return samples + try: + import librosa + except ImportError as exc: # pragma: no cover - exercised only without librosa + raise RuntimeError( + f"Audio sampled at {orig_sr} Hz must be resampled to {target_sr} Hz, " + "but librosa is not installed. Install the audio extra: " + "`uv sync --extra audio` (or `pip install librosa`)." + ) from exc + return librosa.resample(samples, orig_sr=orig_sr, target_sr=target_sr) diff --git a/src/granite_switch/vllm/audio/chunking.py b/src/granite_switch/vllm/audio/chunking.py new file mode 100644 index 0000000..2dfa7bf --- /dev/null +++ b/src/granite_switch/vllm/audio/chunking.py @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Encoder-agnostic long-audio chunking for the cascade. + +Splits a waveform into overlapping windows and stitches the per-window +transcripts, so a backend with a fixed input window inherits long-audio support. +Windows overlap because a hard cut can split a word; the overlap is therefore +transcribed twice and the merge keeps one copy. + +Pure numpy + stdlib (no torch/transformers) so it unit-tests on CPU. +""" + +from __future__ import annotations + +import re + +import numpy as np + +# Bounded seam search so the merge stays linear in transcript length. +_MAX_SEAM_WORDS = 60 + + +def split_waveform( + samples: np.ndarray, + sr: int, + window_s: float, + overlap_s: float, +) -> list[np.ndarray]: + """Split a 1-D mono waveform into overlapping windows, in order. + + Windows advance by ``window_s - overlap_s``. A clip shorter than one window + is returned as a single segment. + """ + if window_s <= 0: + raise ValueError(f"window_s must be > 0, got {window_s}") + if not (0 <= overlap_s < window_s): + raise ValueError( + f"overlap_s must satisfy 0 <= overlap_s < window_s; got " + f"overlap_s={overlap_s}, window_s={window_s}" + ) + + window = int(round(window_s * sr)) + step = int(round((window_s - overlap_s) * sr)) + window = max(1, window) + step = max(1, step) + + n = len(samples) + if n <= window: + return [samples] + + segments: list[np.ndarray] = [] + start = 0 + while start < n: + segments.append(samples[start : start + window]) + if start + window >= n: + break + start += step + return segments + + +def _norm_word(word: str) -> str: + """Lowercase and strip surrounding punctuation for seam comparison.""" + return re.sub(r"[^\w]+", "", word).lower() + + +def _seam_overlap(prev: list[str], nxt: list[str]) -> int: + """Longest k such that the last k words of ``prev`` match the first k of ``nxt``. + + Punctuation/case-insensitive: ASR renders the overlap slightly differently on + each side of a seam. Returns 0 when nothing matches. + """ + max_k = min(len(prev), len(nxt), _MAX_SEAM_WORDS) + for k in range(max_k, 0, -1): + a = [_norm_word(w) for w in prev[-k:]] + b = [_norm_word(w) for w in nxt[:k]] + if a == b: + return k + return 0 + + +def merge_transcripts(transcripts: list[str]) -> str: + """Concatenate per-window transcripts, de-duplicating the overlap at each seam.""" + merged: list[str] = [] + for text in transcripts: + words = text.split() + if not words: + continue + if not merged: + merged = words + continue + k = _seam_overlap(merged, words) + merged.extend(words[k:]) + return " ".join(merged) diff --git a/src/granite_switch/vllm/audio/processor.py b/src/granite_switch/vllm/audio/processor.py new file mode 100644 index 0000000..6fc968d --- /dev/null +++ b/src/granite_switch/vllm/audio/processor.py @@ -0,0 +1,300 @@ +# SPDX-License-Identifier: Apache-2.0 +"""vLLM multimodal processor for the audio cascade. + +``_call_hf_processor`` runs ASR and tokenizes the transcript; +``_get_prompt_updates`` then replaces the ``<|audio|>`` marker with the real +transcript token ids via ``PromptReplacement``, so the scheduler sizes KV for the +runtime-determined length rather than a fixed audio window. + +Modeled on vLLM 0.19.1's ``ultravox.py``. Audio is answered by the base model — +no adapter control tokens are placed, so the switch is not involved. +""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence + +import torch +from transformers import BatchFeature +from vllm.multimodal.inputs import ( + MultiModalFieldConfig, + MultiModalKwargsItems, +) +from vllm.multimodal.parse import ( + MultiModalDataDict, + MultiModalDataItems, + MultiModalDataParser, +) +from vllm.multimodal.processing import ( + BaseDummyInputsBuilder, + BaseMultiModalProcessor, + BaseProcessingInfo, + PromptReplacement, + PromptUpdate, +) + +from .asr import ( + DEFAULT_ALLOWED_REQUEST_GENERATE_KEYS, + DEFAULT_ASR_MODEL_ID, + get_transcriber, + resolve_generate_kwargs, +) + +AUDIO_MARKER = "<|audio|>" +_TARGET_SR = 16_000 +# Stands in for a transcript with no speech in it. Must tokenize to at least one +# token: vLLM discards a zero-length placeholder and then reports the item as +# missing, so every audio item has to contribute something to the prompt. +_EMPTY_TRANSCRIPT_TEXT = " " +# Keeps the transcript budget finite if max_model_len cannot be read. +_FALLBACK_CONTEXT_LEN = 8192 +_DUMMY_AUDIO_SECONDS = 5 + + +class GraniteSwitchASRProcessingInfo(BaseProcessingInfo): + """Static info vLLM needs about the audio modality.""" + + def _asr_enabled(self) -> bool: + return bool(getattr(self.get_hf_config(), "asr_enabled", False)) + + def get_supported_mm_limits(self) -> Mapping[str, int | None]: + # No modalities on a non-audio checkpoint, so vLLM never loads ASR. + if not self._asr_enabled(): + return {} + # Finite so vLLM can size KV for the worst case. --limit-mm-per-prompt + # may lower this ceiling, not raise it. + return {"audio": self._asr_max_audio_clips()} + + def get_mm_max_tokens_per_item( + self, + seq_len: int, + mm_counts: Mapping[str, int], + ) -> Mapping[str, int] | None: + if not self._asr_enabled(): + return {} + # Sizes the encoder cache and profiling pass only — does NOT bound + # requests (vLLM's prompt-length check does). A clip cannot exceed the + # whole context, so the per-clip share of it is the honest upper bound. + count = mm_counts.get("audio", 1) or 1 + return {"audio": max(1, seq_len // count)} + + def get_data_parser(self) -> MultiModalDataParser: + return MultiModalDataParser(target_sr=_TARGET_SR) + + # --- ASR config resolved from the model's GraniteSwitchConfig --- + + def _asr_model_id(self) -> str: + cfg = self.get_hf_config() + return getattr(cfg, "asr_model_id", None) or DEFAULT_ASR_MODEL_ID + + def _asr_device(self) -> str: + cfg = self.get_hf_config() + return getattr(cfg, "asr_device", "cpu") or "cpu" + + def _asr_dtype(self) -> str | None: + cfg = self.get_hf_config() + return getattr(cfg, "asr_dtype", None) + + def _asr_pipeline_kwargs(self) -> Mapping[str, object]: + cfg = self.get_hf_config() + return getattr(cfg, "asr_pipeline_kwargs", None) or {} + + def _asr_generate_kwargs(self) -> Mapping[str, object]: + cfg = self.get_hf_config() + return getattr(cfg, "asr_generate_kwargs", None) or {} + + def _asr_max_audio_clips(self) -> int: + cfg = self.get_hf_config() + return int(getattr(cfg, "asr_max_audio_clips", 32) or 32) + + def _asr_self_chunks(self) -> bool: + cfg = self.get_hf_config() + return bool(getattr(cfg, "asr_self_chunks", True)) + + def _asr_chunk_length_s(self) -> float: + cfg = self.get_hf_config() + return float(getattr(cfg, "asr_chunk_length_s", 30.0) or 30.0) + + def _asr_chunk_overlap_s(self) -> float: + cfg = self.get_hf_config() + return float(getattr(cfg, "asr_chunk_overlap_s", 5.0) or 0.0) + + def _max_model_len(self) -> int: + """The served context window, or a safe fallback. + + ``_call_hf_processor`` needs this at request time to size the transcript + budget; vLLM's profiling ``seq_len`` is not available there. + """ + model_config = getattr(self.ctx, "model_config", None) + max_len = getattr(model_config, "max_model_len", None) + if not max_len: + max_len = getattr(self.get_hf_config(), "max_position_embeddings", None) + return int(max_len) if max_len else _FALLBACK_CONTEXT_LEN + + +class GraniteSwitchASRDummyInputsBuilder( + BaseDummyInputsBuilder[GraniteSwitchASRProcessingInfo] +): + """Synthetic inputs for vLLM's startup memory-profiling pass.""" + + def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str: + return AUDIO_MARKER * mm_counts.get("audio", 0) + + def get_dummy_mm_data( + self, + seq_len: int, + mm_counts: Mapping[str, int], + mm_options: Mapping[str, object] | None = None, + ) -> MultiModalDataDict: + num_audios = mm_counts.get("audio", 0) + length = _DUMMY_AUDIO_SECONDS * _TARGET_SR + audio = torch.zeros(length, dtype=torch.float32).numpy() + return {"audio": [audio] * num_audios} + + +class GraniteSwitchASRMultiModalProcessor( + BaseMultiModalProcessor[GraniteSwitchASRProcessingInfo] +): + """Runs ASR and splices the transcript tokens into the prompt.""" + + def _transcribe( + self, + audio, + generate_kwargs: Mapping[str, object] | None = None, + ) -> list[int]: + """Transcribe one audio item to token ids. Never truncated here — an + oversized prompt is rejected by vLLM's own length check. + + Guaranteed non-empty: silence, music, non-speech or a clip too short to + contain a word all transcribe to ``""``, and a zero-length replacement + would make vLLM drop the placeholder and reject the request. Those clips + get :data:`_EMPTY_TRANSCRIPT_TEXT` instead, so the model sees an audio + turn that simply said nothing. + """ + transcriber = get_transcriber( + model_id=self.info._asr_model_id(), + device=self.info._asr_device(), + pipeline_kwargs=self.info._asr_pipeline_kwargs(), + dtype=self.info._asr_dtype(), + ) + # The data parser already resampled to _TARGET_SR. + text = transcriber.transcribe( + audio, + sampling_rate=_TARGET_SR, + generate_kwargs=generate_kwargs or None, + self_chunks=self.info._asr_self_chunks(), + chunk_length_s=self.info._asr_chunk_length_s(), + chunk_overlap_s=self.info._asr_chunk_overlap_s(), + ) + tokenizer = self.info.get_tokenizer() + if not text or not text.strip(): + text = _EMPTY_TRANSCRIPT_TEXT + ids = tokenizer.encode(text, add_special_tokens=False) + if not ids: + # Only reachable if the tokenizer drops _EMPTY_TRANSCRIPT_TEXT + # entirely. Fail loudly rather than emit a zero-length placeholder, + # which surfaces as an opaque "found 0 prompt placeholders". + raise ValueError( + f"Tokenizer produced no tokens for {text!r}; cannot build a " + "prompt placeholder for this audio item. Choose an " + "_EMPTY_TRANSCRIPT_TEXT this tokenizer encodes to >=1 token." + ) + return ids + + def _call_hf_processor( + self, + prompt: str, + mm_data: Mapping[str, object], + mm_kwargs: Mapping[str, object], + tok_kwargs: Mapping[str, object], + ) -> BatchFeature: + tokenizer = self.info.get_tokenizer() + audios = mm_data.get("audios", []) or [] + + if not audios: + input_ids = tokenizer.encode(prompt, add_special_tokens=False) + return BatchFeature(dict(input_ids=[input_ids]), tensor_type="pt") + + # Resolved once, then applied to every audio item in this request. + generate_kwargs = resolve_generate_kwargs( + self.info._asr_generate_kwargs(), + mm_kwargs, + DEFAULT_ALLOWED_REQUEST_GENERATE_KEYS, + ) + + input_ids = tokenizer.encode(prompt, add_special_tokens=False) + + # Concatenated flat, with per-item sizes to split them back. + per_item_ids = [self._transcribe(a, generate_kwargs) for a in audios] + sizes = [len(ids) for ids in per_item_ids] + flat_ids = [tid for ids in per_item_ids for tid in ids] + + return BatchFeature( + dict( + input_ids=[input_ids], + audio_token_ids=torch.tensor(flat_ids, dtype=torch.long), + audio_num_tokens=torch.tensor(sizes, dtype=torch.long), + ), + tensor_type="pt", + ) + + def _hf_processor_applies_updates( + self, + prompt_text: str, + mm_items: MultiModalDataItems, + hf_processor_mm_kwargs: Mapping[str, object], + tokenization_kwargs: Mapping[str, object], + ) -> bool: + """Always False: ``_call_hf_processor`` leaves the marker in place. + + The base implementation returns True for raw (non-embedding) items, + which tells vLLM the processor already expanded the placeholder itself — + so vLLM skips applying our ``PromptReplacement`` and merely *searches* + the returned prompt for the transcript token ids. They are not there, and + it raises ``Expected there to be 1 audio prompt placeholders ... found 0``. + + Unlike a real HF processor (e.g. Ultravox's), we tokenize the prompt with + the ``<|audio|>`` marker untouched and hand the transcript back out of + band, so the replacement must be applied by vLLM. + + Only the uncached path consults this hook; the cached path already + hardcodes False, which is why audio works with the default + ``mm_processor_cache_gb=4`` and breaks under ``--mm-processor-cache-gb 0``. + """ + return False + + def _get_mm_fields_config( + self, + hf_inputs: BatchFeature, + hf_processor_mm_kwargs: Mapping[str, object], + ) -> Mapping[str, MultiModalFieldConfig]: + num_tokens = hf_inputs.get("audio_num_tokens", torch.zeros(0)) + return dict( + audio_token_ids=MultiModalFieldConfig.flat_from_sizes("audio", num_tokens), + audio_num_tokens=MultiModalFieldConfig.batched("audio"), + ) + + def _get_prompt_updates( + self, + mm_items: MultiModalDataItems, + hf_processor_mm_kwargs: Mapping[str, object], + out_mm_kwargs: MultiModalKwargsItems, + ) -> Sequence[PromptUpdate]: + out = out_mm_kwargs.get_data() + num_tokens = out.get("audio_num_tokens", torch.zeros(0)) + starts = torch.cumsum(num_tokens, dim=0, dtype=torch.long) + starts = torch.cat([torch.tensor([0], dtype=torch.long), starts]) + all_ids = out.get("audio_token_ids", torch.zeros(0, dtype=torch.long)) + + def replacement(item_idx: int): + s = int(starts[item_idx]) + e = int(starts[item_idx + 1]) + return [int(t) for t in all_ids[s:e]] + + return [ + PromptReplacement( + modality="audio", + target=AUDIO_MARKER, + replacement=replacement, + ) + ] diff --git a/src/granite_switch/vllm/granite_switch_model.py b/src/granite_switch/vllm/granite_switch_model.py index ba86d9d..e24466a 100644 --- a/src/granite_switch/vllm/granite_switch_model.py +++ b/src/granite_switch/vllm/granite_switch_model.py @@ -36,6 +36,7 @@ HasInnerState, IsHybrid, SupportsLoRA, + SupportsMultiModal, SupportsPP, ) from vllm.model_executor.models.utils import ( @@ -43,10 +44,17 @@ make_layers, maybe_prefix, ) +from vllm.multimodal import MULTIMODAL_REGISTRY from vllm.sequence import IntermediateTensors from granite_switch.config import GraniteSwitchConfig +from .audio.processor import ( + AUDIO_MARKER, + GraniteSwitchASRDummyInputsBuilder, + GraniteSwitchASRMultiModalProcessor, + GraniteSwitchASRProcessingInfo, +) from .core import ( CompileFriendlyLoRAKernelMeta, GraniteSwitchDecoderLayer, @@ -285,18 +293,24 @@ def forward( # Step 1: Switch — determine adapter for each token and rewrite # control tokens via token-exchange. Only runs on first rank. if get_pp_group().is_first_rank: - if self.switch is not None: + if self.switch is not None and input_ids is not None: adapter_indices, modified_input_ids = self.switch( input_ids=input_ids, adapter_token_ids=self.adapter_token_ids, ) else: - # No switch — all tokens use base model (adapter_id = 0). - num_tokens = input_ids.shape[0] + # No switch, or the multimodal path (input_ids is None because + # vLLM pre-merged inputs_embeds): run on base, adapter_id 0. + if input_ids is not None: + num_tokens = input_ids.shape[0] + device = input_ids.device + else: + num_tokens = inputs_embeds.shape[0] + device = inputs_embeds.device adapter_indices = torch.zeros( num_tokens, dtype=torch.long, - device=input_ids.device, + device=device, ) modified_input_ids = input_ids @@ -385,10 +399,16 @@ def forward( return intermediate_tensors +@MULTIMODAL_REGISTRY.register_processor( + GraniteSwitchASRMultiModalProcessor, + info=GraniteSwitchASRProcessingInfo, + dummy_inputs=GraniteSwitchASRDummyInputsBuilder, +) class GraniteSwitchForCausalLM( nn.Module, HasInnerState, SupportsLoRA, + SupportsMultiModal, SupportsPP, IsHybrid, ): @@ -396,8 +416,28 @@ class GraniteSwitchForCausalLM( Granite model with switch for causal language modeling. This wraps GraniteSwitchModel with an LM head for token prediction. + + Multimodal (audio): the registered ASR processor transcribes audio and + replaces an ``<|audio|>`` marker with the transcript tokens before the + decoder runs (see granite_switch.vllm.audio). ``embed_multimodal`` supplies + the embeddings for those positions — for the alpha, the transcript's own + text embeddings, which is the seam a future trained audio encoder reuses. + Audio capability is gated per-checkpoint by ``config.asr_enabled`` (the + processor reports no audio modality when disabled). """ + supports_multimodal = True + + # Without this, the multimodal path passes only inputs_embeds and the switch + # cannot see control tokens — audio requests would bypass adapters. + requires_raw_input_tokens = True + + @classmethod + def get_placeholder_str(cls, modality: str, i: int): + if modality.startswith("audio"): + return AUDIO_MARKER + return None + # LoRA specific attributes supported_lora_modules = [ "qkv_proj", @@ -463,9 +503,54 @@ def __init__( ) self.sampler = None # Will be set by vLLM - def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor: - """Apply token embeddings to input_ids.""" - return self.model.embed_input_ids(input_ids) + def embed_multimodal(self, **kwargs) -> list: + """Embeddings for the audio placeholder positions (one tensor per item). + + ALPHA: the transcript token ids produced by the ASR processor are + embedded with the model's own token table — identical to embedding them + as ordinary text. Returned UN-scaled; the Granite embedding_multiplier + is applied later in the forward, so these rows scale consistently with + normal tokens. A future trained audio encoder swaps in here. + """ + audio_token_ids = kwargs.get("audio_token_ids") + if audio_token_ids is None: + return [] + embeds = self.model.embed_tokens(audio_token_ids) + num_tokens = kwargs.get("audio_num_tokens") + if num_tokens is None: + return [embeds] + sizes = [int(n) for n in num_tokens] + return list(torch.split(embeds, sizes)) + + def embed_input_ids( + self, + input_ids: torch.Tensor, + multimodal_embeddings=None, + *, + is_multimodal: torch.Tensor | None = None, + ) -> torch.Tensor: + """Embed token ids; scatter multimodal embeddings into their positions. + + Returns UN-scaled embeddings (the model forward applies the Granite + embedding_multiplier once over everything). + + Applies the switch's token-exchange rewrite (control -> substitute ids) + before embedding, so adapter control tokens get their in-distribution + embeddings exactly as on the text path. Adapter *detection* still runs in + forward on the raw input_ids (passed because requires_raw_input_tokens). + """ + ids = input_ids + switch = getattr(self.model, "switch", None) + if switch is not None: + ids = switch.apply_token_exchange(input_ids) + inputs_embeds = self.model.embed_tokens(ids) + if multimodal_embeddings is not None and is_multimodal is not None: + mm = multimodal_embeddings + if isinstance(mm, (list, tuple)): + mm = torch.cat(list(mm)) if len(mm) else None + if mm is not None: + inputs_embeds[is_multimodal] = mm.to(inputs_embeds.dtype) + return inputs_embeds def forward( self, diff --git a/src/granite_switch/vllm/switch/single.py b/src/granite_switch/vllm/switch/single.py index cdc3f7b..43803a6 100644 --- a/src/granite_switch/vllm/switch/single.py +++ b/src/granite_switch/vllm/switch/single.py @@ -212,11 +212,20 @@ def forward( # @support_torch_compile, which forbids `tensor.any()` branching. # `torch.where` runs every step; the cost is one indexed gather and # one elementwise select on the flat input. - if self.control_to_substitute_lut is not None: - sub_id_per_pos = self.control_to_substitute_lut[input_ids] - is_control = sub_id_per_pos >= 0 - modified_input_ids = torch.where(is_control, sub_id_per_pos, input_ids) - else: - modified_input_ids = input_ids + modified_input_ids = self.apply_token_exchange(input_ids) return adapter_indices, modified_input_ids + + def apply_token_exchange(self, input_ids: torch.Tensor) -> torch.Tensor: + """Rewrite control-token ids to their substitute ids (pure LUT lookup). + + This is the embedding-side half of the switch — no attention, no KV — + so it can be reused by the model's ``embed_input_ids`` on the multimodal + path, where vLLM precomputes ``inputs_embeds`` before ``forward`` runs. + Returns ``input_ids`` unchanged when no substitute LUT was built. + """ + if self.control_to_substitute_lut is None: + return input_ids + sub_id_per_pos = self.control_to_substitute_lut[input_ids] + is_control = sub_id_per_pos >= 0 + return torch.where(is_control, sub_id_per_pos, input_ids) diff --git a/tests/audio/test1.wav b/tests/audio/test1.wav new file mode 100644 index 0000000..3704e5c Binary files /dev/null and b/tests/audio/test1.wav differ diff --git a/tests/composer/test_chat_template.py b/tests/composer/test_chat_template.py index 8aededf..40a9c38 100644 --- a/tests/composer/test_chat_template.py +++ b/tests/composer/test_chat_template.py @@ -27,9 +27,13 @@ from types import SimpleNamespace from unittest.mock import patch +import pytest from jinja2 import Environment -from granite_switch.composer.tokenizer_setup import configure_chat_template +from granite_switch.composer.tokenizer_setup import ( + configure_audio_chat_template, + configure_chat_template, +) _PATCH_TARGET = "granite_switch.composer.tokenizer_setup._decode_alora_invocation_text" @@ -531,3 +535,104 @@ def test_mixed_adapters_from_adapter_config(self): assert "<|answerability|>" not in result_none assert "<|context_relevance|>" not in result_none assert "<|summarization|>" not in result_none + + +# ════════════════════════════════════════════════════════════════════ +# Audio: <|audio|> marker preservation, and coexistence with adapters +# ════════════════════════════════════════════════════════════════════ + + +def _audio_messages(text="transcribe this", audio_type="audio"): + """A user turn whose content is a parts list carrying an audio clip.""" + return [ + { + "role": "user", + "content": [ + {"type": "text", "text": text}, + {"type": audio_type, audio_type: "clip-placeholder"}, + ], + } + ] + + +@pytest.mark.audio +class TestAudioChatTemplate: + """configure_audio_chat_template emits the <|audio|> marker for audio parts.""" + + def test_audio_part_emits_marker(self): + tokenizer = _make_tokenizer() + configure_audio_chat_template(tokenizer) + result = _render( + tokenizer, messages=_audio_messages(), add_generation_prompt=True + ) + assert "<|audio|>" in result + assert "transcribe this" in result + + def test_marker_dropped_without_injection(self): + # Regression canary: the un-injected base template drops the audio part. + tokenizer = _make_tokenizer() + result = _render( + tokenizer, messages=_audio_messages(), add_generation_prompt=True + ) + assert "<|audio|>" not in result + assert "transcribe this" in result + + def test_input_audio_and_audio_url_types_emit_marker(self): + for audio_type in ("input_audio", "audio_url"): + tokenizer = _make_tokenizer() + configure_audio_chat_template(tokenizer) + result = _render( + tokenizer, + messages=_audio_messages(audio_type=audio_type), + add_generation_prompt=True, + ) + assert "<|audio|>" in result, f"marker missing for type={audio_type!r}" + + def test_custom_marker_string(self): + tokenizer = _make_tokenizer() + configure_audio_chat_template(tokenizer, marker="<|snd|>") + result = _render( + tokenizer, messages=_audio_messages(), add_generation_prompt=True + ) + assert "<|snd|>" in result + + def test_missing_anchor_raises(self): + tokenizer = SimpleNamespace(chat_template="{{ messages }}") + with pytest.raises(ValueError, match="content-part loop"): + configure_audio_chat_template(tokenizer) + + def test_none_template_is_noop(self): + tokenizer = SimpleNamespace(chat_template=None) + configure_audio_chat_template(tokenizer) + assert tokenizer.chat_template is None + + +@pytest.mark.audio +class TestAudioAndAdapterInjectionsCompose: + """Adapter and audio injections applied in sequence leave both intact.""" + + def test_lora_prefix_and_audio_marker_coexist(self): + tokenizer = _make_tokenizer() + configure_chat_template(tokenizer, [("/path/a", "ctx_rel", "lora")]) + configure_audio_chat_template(tokenizer) + + result = _render( + tokenizer, + messages=_audio_messages(), + add_generation_prompt=True, + adapter_name="ctx_rel", + ) + assert result.startswith("<|ctx_rel|>"), result[:80] + assert "<|audio|>" in result + assert "transcribe this" in result + + def test_no_adapter_still_emits_audio_marker(self): + tokenizer = _make_tokenizer() + configure_chat_template(tokenizer, [("/path/a", "ctx_rel", "lora")]) + configure_audio_chat_template(tokenizer) + + result = _render( + tokenizer, messages=_audio_messages(), add_generation_prompt=True + ) + assert "<|audio|>" in result + assert "<|ctx_rel|>" not in result diff --git a/tests/composer/test_tokenizer_setup.py b/tests/composer/test_tokenizer_setup.py index 0b099fb..b649c45 100644 --- a/tests/composer/test_tokenizer_setup.py +++ b/tests/composer/test_tokenizer_setup.py @@ -8,6 +8,7 @@ from granite_switch.composer.tokenizer_setup import ( _decode_alora_invocation_text, + add_audio_token, add_control_tokens, configure_chat_template, ) @@ -29,15 +30,21 @@ def __len__(self): return self._vocab_size def add_special_tokens(self, special_tokens_dict): - """Add special tokens and return count added.""" + """Add special tokens and return count added. + + Mirrors transformers: tokens new to the vocabulary are appended and keep + their id, but ``additional_special_tokens`` is *replaced* by the list + passed in rather than extended. A second call therefore drops whatever + the first one registered unless the caller re-passes it. + """ tokens = special_tokens_dict.get("additional_special_tokens", []) num_added = 0 for token in tokens: if token not in self._vocab: self._vocab[token] = self._vocab_size self._vocab_size += 1 - self._special_tokens.append(token) num_added += 1 + self._special_tokens = list(tokens) return num_added def convert_tokens_to_ids(self, token): @@ -160,6 +167,79 @@ def test_token_format(self, capsys): assert special_tokens[0] == "<|my_adapter|>" +class TestAddAudioToken: + """add_audio_token must not evict the control tokens added before it. + + ``add_special_tokens`` replaces ``additional_special_tokens`` instead of + extending it, and transformers exposes no way to read the current list back, + so the marker call has to re-pass whatever it wants to keep. + """ + + _ADAPTERS = [ + ("/path/to/rag", "rag", "alora"), + ("/path/to/code", "code", "lora"), + ] + + def test_marker_takes_the_next_free_id(self, capsys): + """The marker is added after the control tokens, so it gets the next id.""" + tokenizer = MockTokenizer(initial_vocab_size=100) + _, special_tokens = add_control_tokens(tokenizer, self._ADAPTERS) + + token_id = add_audio_token(tokenizer, keep_special_tokens=special_tokens) + + assert token_id == 102 + assert len(tokenizer) == 103 + + def test_control_tokens_survive_the_marker(self, capsys): + """Both the controls and the marker stay registered as special.""" + tokenizer = MockTokenizer(initial_vocab_size=100) + _, special_tokens = add_control_tokens(tokenizer, self._ADAPTERS) + + add_audio_token(tokenizer, keep_special_tokens=special_tokens) + + assert tokenizer._special_tokens == ["<|rag|>", "<|code|>", "<|audio|>"] + + def test_control_token_ids_are_unchanged(self, capsys): + """Re-passing an added token keeps its id and does not grow the vocab.""" + tokenizer = MockTokenizer(initial_vocab_size=100) + ctrl_ids, special_tokens = add_control_tokens(tokenizer, self._ADAPTERS) + + add_audio_token(tokenizer, keep_special_tokens=special_tokens) + + assert [tokenizer.convert_tokens_to_ids(t) for t in special_tokens] == ctrl_ids + + def test_omitting_keep_evicts_the_control_tokens(self, capsys): + """Witness for why keep_special_tokens exists. + + Without it the marker call drops every control token from the + additional-special-tokens list — silently, since the ids and the + vocabulary are untouched. + """ + tokenizer = MockTokenizer(initial_vocab_size=100) + add_control_tokens(tokenizer, self._ADAPTERS) + + add_audio_token(tokenizer) + + assert tokenizer._special_tokens == ["<|audio|>"] + + def test_marker_not_duplicated_when_already_kept(self, capsys): + """Passing the marker in keep_special_tokens must not register it twice.""" + tokenizer = MockTokenizer(initial_vocab_size=100) + + add_audio_token(tokenizer, keep_special_tokens=["<|audio|>", "<|rag|>"]) + + assert tokenizer._special_tokens == ["<|rag|>", "<|audio|>"] + assert len(tokenizer) == 102 + + def test_custom_marker(self, capsys): + """A non-default marker is honored.""" + tokenizer = MockTokenizer(initial_vocab_size=100) + + token_id = add_audio_token(tokenizer, marker="<|sound|>") + + assert tokenizer.convert_tokens_to_ids("<|sound|>") == token_id + + class TestConfigureChatTemplate: """Structural tests for configure_chat_template — verify template assembly.""" diff --git a/tests/composer/test_validator.py b/tests/composer/test_validator.py index 465f055..a253c72 100644 --- a/tests/composer/test_validator.py +++ b/tests/composer/test_validator.py @@ -1,12 +1,17 @@ # SPDX-License-Identifier: Apache-2.0 """Unit tests for post-build parameter validation.""" +from types import SimpleNamespace + import pytest import torch import torch.nn as nn from granite_switch.composer.arch import ArchDescriptor, ModuleDescriptor -from granite_switch.composer.validator import validate_all_parameters +from granite_switch.composer.validator import ( + validate_all_parameters, + validate_control_lut, +) @pytest.fixture @@ -273,3 +278,73 @@ def test_partial_adapter_coverage(self, simple_arch, capsys): captured = capsys.readouterr() # Both qkv and o_proj should be considered populated (by different adapters) assert "Parameter summary" in captured.out + + +class _MockSwitch(nn.Module): + """Switch carrying a control->substitute table of a chosen length.""" + + def __init__(self, lut_size: int | None): + super().__init__() + if lut_size is None: + self.control_to_substitute_lut = None + else: + self.register_buffer( + "control_to_substitute_lut", + torch.full((lut_size,), -1, dtype=torch.long), + ) + + +class _MockSwitchModel(nn.Module): + """Minimal stand-in exposing the attributes validate_control_lut reads.""" + + def __init__(self, vocab_size, lut_size: int | None, with_switch: bool = True): + super().__init__() + self.config = SimpleNamespace(vocab_size=vocab_size) + self.model = nn.Module() + if with_switch: + self.model.switch = _MockSwitch(lut_size) + + +class TestValidateControlLut: + """The table length must match the config.vocab_size the checkpoint ships. + + A mismatch is not recoverable at load time: from_pretrained discards the + stored tensor and leaves the buffer uninitialised, so every id reads as a + control id. Compose has to reject it rather than write it out. + """ + + def test_matching_length_passes(self): + validate_control_lut(_MockSwitchModel(vocab_size=200, lut_size=200)) + + def test_one_row_short_raises(self): + """The <|audio|> off-by-one: vocab grew after the switch was built.""" + model = _MockSwitchModel(vocab_size=100365, lut_size=100364) + + with pytest.raises(ValueError, match="control_to_substitute_lut") as exc: + validate_control_lut(model) + + # Both numbers must appear — the delta is the whole diagnosis. + assert "100364" in str(exc.value) + assert "100365" in str(exc.value) + + def test_longer_than_vocab_raises(self): + """Also catches the shrink direction (e.g. a padded base vocab).""" + with pytest.raises(ValueError): + validate_control_lut(_MockSwitchModel(vocab_size=100365, lut_size=102400)) + + def test_no_switch_is_not_an_error(self): + """Zero-adapter checkpoints have no switch to validate.""" + validate_control_lut( + _MockSwitchModel(vocab_size=200, lut_size=None, with_switch=False) + ) + + def test_switch_without_mapping_is_not_an_error(self): + """No token-exchange mapping configured -> nothing to check.""" + validate_control_lut(_MockSwitchModel(vocab_size=200, lut_size=None)) + + def test_config_without_vocab_size_is_skipped(self): + """Don't invent a failure when there is nothing to compare against.""" + model = _MockSwitchModel(vocab_size=200, lut_size=64) + del model.config.vocab_size + + validate_control_lut(model) diff --git a/tests/hf/test_token_exchange.py b/tests/hf/test_token_exchange.py index 234188f..7d4bbee 100644 --- a/tests/hf/test_token_exchange.py +++ b/tests/hf/test_token_exchange.py @@ -13,6 +13,7 @@ from granite_switch.config import GraniteSwitchConfig from granite_switch.hf import GraniteSwitchForCausalLM +from granite_switch.hf.switch.single import build_control_to_substitute_lut def _build(num_adapters=2, substitute_ids=(1, 7)): @@ -112,3 +113,101 @@ def test_adapter_indices_still_activate(self): assert adapter_indices[0, 2].item() == 1 assert adapter_indices[0, 3].item() == 1 assert adapter_indices[0, 4].item() == 1 + + +class TestControlLutSizing: + """build_control_to_substitute_lut is the single source of the sizing rule.""" + + def test_sized_to_vocab_when_vocab_is_larger(self): + lut = build_control_to_substitute_lut(_build()) + assert lut is not None + assert lut.numel() == 200 # vocab_size, control ids are 100/101 + + def test_sized_past_the_last_control_id_when_vocab_lags(self): + """Every control id stays addressable even if vocab_size is behind.""" + config = _build() + config.vocab_size = 50 # smaller than adapter_token_ids [100, 101] + + lut = build_control_to_substitute_lut(config) + + assert lut is not None + assert lut.numel() == 102 # max_ctrl_id + 1 + + def test_no_mapping_yields_no_table(self): + config = _build() + config.adapter_substitute_token_ids = None + + assert build_control_to_substitute_lut(config) is None + + def test_empty_adapter_ids_yield_no_table(self): + config = _build() + config.adapter_token_ids = [] + + assert build_control_to_substitute_lut(config) is None + + +class TestControlLutRebuildAfterResize: + """A vocabulary resize leaves the table stale; it must be re-derived. + + ``__init__`` sizes the table from ``config.vocab_size``, so compose growing + the vocabulary for control and marker tokens leaves the persistent buffer + shorter than the config it ships with. Loading such a checkpoint does not + degrade gracefully: the stored tensor is discarded and the buffer is left as + uninitialised memory, so every id reads as a control id and the rewrite + sends out-of-range ids into the embedding gather. + """ + + def test_resize_leaves_the_table_stale(self): + """Witness for why the rebuild is needed at all.""" + model = GraniteSwitchForCausalLM(_build(substitute_ids=(5, 7))).eval() + model.resize_token_embeddings(201) + + assert model.config.vocab_size == 201 + assert model.model.embed_tokens.weight.shape[0] == 201 + assert model.model.switch.control_to_substitute_lut.numel() == 200 + + def test_rebuild_restores_agreement_and_values(self): + model = GraniteSwitchForCausalLM(_build(substitute_ids=(5, 7))).eval() + model.resize_token_embeddings(201) + + assert model.model.switch.rebuild_control_to_substitute_lut(model.config) + + lut = model.model.switch.control_to_substitute_lut + assert lut.numel() == model.config.vocab_size == 201 + assert lut[100].item() == 5 + assert lut[101].item() == 7 + assert int((lut >= 0).sum()) == 2 # exactly the two control slots + + def test_rebuild_reports_false_without_a_mapping(self): + config = _build() + config.adapter_substitute_token_ids = None + model = GraniteSwitchForCausalLM(config).eval() + + assert model.model.switch.rebuild_control_to_substitute_lut(config) is False + + @torch.no_grad() + def test_rebuilt_table_survives_a_save_load_round_trip(self, tmp_path): + """The end-to-end guard: without the rebuild this load yields garbage. + + A stale buffer comes back as uninitialised memory, which makes every + position a control position and raises IndexError from the embedding + gather (a CUDA device-side assert on GPU). + """ + model = GraniteSwitchForCausalLM(_build(substitute_ids=(5, 7))).eval() + model.resize_token_embeddings(201) + model.model.switch.rebuild_control_to_substitute_lut(model.config) + model.save_pretrained(tmp_path / "ckpt") + + # Strict load: no ignore_mismatched_sizes to paper over a bad length. + loaded = GraniteSwitchForCausalLM.from_pretrained( + tmp_path / "ckpt", dtype=torch.float32 + ).eval() + + lut = loaded.model.switch.control_to_substitute_lut + assert lut.numel() == 201 + assert lut[100].item() == 5 + assert lut[101].item() == 7 + assert lut[10].item() == -1 + assert int((lut >= 0).sum()) == 2 + # And the rewrite it drives stays in bounds. + loaded(input_ids=torch.tensor([[10, 20, 100, 40]], dtype=torch.long)) diff --git a/tests/integration/fixtures/eiffel_tower_paris.wav b/tests/integration/fixtures/eiffel_tower_paris.wav new file mode 100644 index 0000000..d88341d Binary files /dev/null and b/tests/integration/fixtures/eiffel_tower_paris.wav differ diff --git a/tests/integration/test_adapter_routing_audio_enabled.py b/tests/integration/test_adapter_routing_audio_enabled.py new file mode 100644 index 0000000..0eae2b0 --- /dev/null +++ b/tests/integration/test_adapter_routing_audio_enabled.py @@ -0,0 +1,165 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Adapters + audio (issue #47): every default adapter routes to its own index +on an audio-enabled checkpoint. + +Asserts the switch maps ``adapter_token_ids[i]`` to index ``i + 1`` and leaves +pre-control positions on base (``0``) — enabling audio must not perturb the +token->index map. For granite-4.1-3b, 12 of the 13 defined adapters resolve +(context_relevance ships no 4.1-3b flavor). + +Markers: slow + requires_model + gpu (opt-in via -m). +""" + +import importlib.util +import json +import os + +import pytest + +pytestmark = [ + pytest.mark.audio, + pytest.mark.slow, + pytest.mark.requires_model, + pytest.mark.gpu, +] + +if importlib.util.find_spec("granite_switch.hf") is None: + pytest.skip("requires the HF backend ([hf] extra)", allow_module_level=True) + + +_DEFAULT_ADAPTER_LIBRARIES = [ + "ibm-granite/granitelib-rag-r1.0", + "ibm-granite/granitelib-core-r1.0", + "ibm-granite/granitelib-guardian-r1.0", +] +_DEFAULT_BASE_MODEL_PAIRS = [ + ("ibm-granite/granite-4.1-3b", _DEFAULT_ADAPTER_LIBRARIES), +] + + +def _load_experimental_pairs(): + raw = os.environ.get("GRANITE_SWITCH_EXPERIMENTAL_MODEL_PAIRS", "") + if not raw: + return [] + try: + entries = json.loads(raw) + except json.JSONDecodeError as e: + raise ValueError( + f"GRANITE_SWITCH_EXPERIMENTAL_MODEL_PAIRS is not valid JSON: {e}\n" + f'Expected format: \'[{{"base":"/path","adapter":"/path"}}, ...]\'' + ) + # adapter may be a single library or a list of libraries. + pairs = [] + for p in entries: + adapters = p["adapter"] + pairs.append( + (p["base"], adapters if isinstance(adapters, list) else [adapters]) + ) + return pairs + + +BASE_MODEL_PAIRS = _DEFAULT_BASE_MODEL_PAIRS + _load_experimental_pairs() + +COMPOSE_TIMEOUT_S = 1800 +_SEQ_LEN = 8 +_CTRL_POS = 1 +_FILLER_TOKENS = [791, 5679, 2766, 279, 893, 389, 813, 1450] + + +@pytest.fixture( + scope="module", + params=BASE_MODEL_PAIRS, + ids=lambda p: p[0].rsplit("/", 1)[-1], +) +def audio_switch_model(request, tmp_path_factory): + import subprocess + import sys + + import torch + + from granite_switch.hf import GraniteSwitchForCausalLM + + base_model, adapter_libraries = request.param + save_dir = tmp_path_factory.mktemp(base_model.rsplit("/", 1)[-1]) / "model" + + cmd = [ + sys.executable, + "-m", + "granite_switch.composer.compose_granite_switch", + "--base-model", + base_model, + "--adapters", + *adapter_libraries, + "--enable-audio", + "--output", + str(save_dir), + ] + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=COMPOSE_TIMEOUT_S + ) + if result.returncode != 0: + raise RuntimeError( + f"compose failed for base={base_model} adapters={adapter_libraries}\n" + f"--- STDOUT ---\n{result.stdout}\n--- STDERR ---\n{result.stderr}" + ) + + # ignore_mismatched_sizes: the <|audio|> token bumps vocab_size one past the + # switch's control_to_substitute_lut buffer, but that buffer is rebuilt from + # config in SingleSwitch.__init__, so the freshly-built one is kept intact. + model = ( + GraniteSwitchForCausalLM.from_pretrained( + str(save_dir), dtype=torch.bfloat16, ignore_mismatched_sizes=True + ) + .eval() + .cuda() + ) + return {"base_model": base_model, "model": model, "config": model.config} + + +def _adapter_indices_after_forward(model, control_token_id): + import torch + + seq = [_FILLER_TOKENS[i % len(_FILLER_TOKENS)] for i in range(_SEQ_LEN)] + seq[_CTRL_POS] = control_token_id + with torch.no_grad(): + model(input_ids=torch.tensor([seq], device="cuda")) + return model.model._last_adapter_indices[0] + + +def test_all_adapters_route_with_audio_enabled(audio_switch_model): + """Each adapter control token routes to its own index on an audio checkpoint.""" + config = audio_switch_model["config"] + base_model = audio_switch_model["base_model"] + + assert getattr(config, "asr_enabled", False) is True, ( + f"checkpoint is not audio-enabled (base_model={base_model})" + ) + + token_ids = list(getattr(config, "adapter_token_ids", None) or []) + names = list(getattr(config, "adapter_names", None) or []) + assert token_ids, f"composed checkpoint has no adapters (base_model={base_model})" + print(f"\n sweeping {len(token_ids)} adapters (base_model={base_model})") + + failures = [] + for i, token_id in enumerate(token_ids): + expected = i + 1 # adapter_token_ids[i] activates adapter i+1; 0 = base + name = names[i] if i < len(names) else f"adapter_{expected}" + ai = _adapter_indices_after_forward(audio_switch_model["model"], token_id) + + ok = bool((ai[:_CTRL_POS] == 0).all()) and bool( + (ai[_CTRL_POS:] == expected).all() + ) + print( + f" [{'ok' if ok else 'FAIL'}] idx {expected:>2} {name!r}: {ai.tolist()}" + ) + if not ok: + failures.append((expected, name, token_id, ai.tolist())) + + assert not failures, ( + f"{len(failures)} adapter(s) mis-routed with audio enabled " + f"(base_model={base_model}); expected pre-control=0, post-control=index:\n" + + "\n".join( + f" idx {idx} {name!r} (token {tok}): {indices}" + for idx, name, tok, indices in failures + ) + ) diff --git a/tests/integration/test_audio_serving_smoke.py b/tests/integration/test_audio_serving_smoke.py new file mode 100644 index 0000000..fbc22b4 --- /dev/null +++ b/tests/integration/test_audio_serving_smoke.py @@ -0,0 +1,208 @@ +# SPDX-License-Identifier: Apache-2.0 +"""End-to-end vLLM serving smoke for the audio cascade (issue #47). + +Drives text + adapter + audio x1/x2/x3 through one live engine — the serve-time +path the low-level tests bypass (CUDA graphs, the ASR processor inside vLLM's +EngineCore subprocess, the multi-clip splice, generation). + +Deliberately does NOT assert transcript content: WER and adapter-routing +correctness are separate boxes, covered by the eval harness and +test_switch_e2e_compose.py. Synthetic tones keep the test asset-free. + +Opt in explicitly: `pytest -m "slow and requires_model and gpu"`. +""" + +import importlib.util +import json +import os + +import pytest + +pytestmark = [ + pytest.mark.audio, + pytest.mark.slow, + pytest.mark.requires_model, + pytest.mark.gpu, +] + +if importlib.util.find_spec("vllm") is None: + pytest.skip("requires vLLM installed", allow_module_level=True) + + +# Kept in lockstep with test_switch_e2e_compose.py so both E2E files exercise the +# same model matrix. +_DEFAULT_BASE_MODEL_PAIRS = [ + ("ibm-granite/granite-4.0-micro", "ibm-granite/granitelib-core-r1.0"), + ("ibm-granite/granite-4.1-3b", "ibm-granite/granitelib-core-r1.0"), +] + + +def _load_experimental_pairs(): + """Local pairings from GRANITE_SWITCH_EXPERIMENTAL_MODEL_PAIRS. + + JSON array of {"base": str, "adapter": str}, HF ids or local paths. The + mechanism is committed; the values are not. + """ + raw = os.environ.get("GRANITE_SWITCH_EXPERIMENTAL_MODEL_PAIRS", "") + if not raw: + return [] + try: + entries = json.loads(raw) + except json.JSONDecodeError as e: + raise ValueError( + f"GRANITE_SWITCH_EXPERIMENTAL_MODEL_PAIRS is not valid JSON: {e}\n" + f'Expected format: \'[{{"base":"/path","adapter":"/path"}}, ...]\'' + ) + return [(p["base"], p["adapter"]) for p in entries] + + +BASE_MODEL_PAIRS = _DEFAULT_BASE_MODEL_PAIRS + _load_experimental_pairs() + +COMPOSE_TIMEOUT_S = 1800 # 30 min — matches test_switch_e2e_compose.py +_TARGET_SR = 16_000 + + +@pytest.fixture( + scope="module", + params=BASE_MODEL_PAIRS, + ids=lambda p: p[0].rsplit("/", 1)[-1], +) +def audio_checkpoint(request, tmp_path_factory): + """Compose one audio-enabled checkpoint per (base, adapter) pair. + + Goes through the compose CLI, never a hand-assembled config (CLAUDE.md + gotcha #5). Module scope amortizes the download across the pair's cases. + """ + import subprocess + import sys + + base_model, adapter_library = request.param + save_dir = tmp_path_factory.mktemp(base_model.rsplit("/", 1)[-1]) / "model" + + cmd = [ + sys.executable, + "-m", + "granite_switch.composer.compose_granite_switch", + "--base-model", + base_model, + "--adapters", + adapter_library, + "--enable-audio", + "--output", + str(save_dir), + ] + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=COMPOSE_TIMEOUT_S + ) + if result.returncode != 0: + raise RuntimeError( + f"compose failed for base={base_model} adapter={adapter_library}\n" + f"--- STDOUT ---\n{result.stdout}\n--- STDERR ---\n{result.stderr}" + ) + return {"base_model": base_model, "save_dir": save_dir} + + +@pytest.fixture(scope="module") +def served(audio_checkpoint): + """Boot vLLM once for the checkpoint and share it across smoke cases. + + Tokenizer init stays ON (unlike the argmax-equivalence test): the ASR + processor needs it to encode the prompt and the transcript. + """ + import gc + + import torch + + os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn") + from vllm import LLM + + llm = LLM( + model=str(audio_checkpoint["save_dir"]), + dtype="bfloat16", + gpu_memory_utilization=0.7, + enforce_eager=True, # smoke: skip CUDA-graph capture for a faster boot + ) + try: + yield {"llm": llm, "config": llm.llm_engine.model_config.hf_config} + finally: + del llm + gc.collect() + torch.cuda.empty_cache() + + +def _tone(seconds: float = 1.0, freq: float = 440.0): + """Deterministic mono 16 kHz waveform (no speech fixture needed).""" + import numpy as np + + t = np.arange(int(seconds * _TARGET_SR), dtype=np.float32) / _TARGET_SR + return (0.1 * np.sin(2 * np.pi * freq * t)).astype(np.float32) + + +def _one_completion(outputs): + """Assert a single RequestOutput carrying at least one generated token.""" + assert len(outputs) == 1 + completion = outputs[0].outputs[0] + assert len(completion.token_ids) >= 1 + return completion + + +def test_text_only_serving(served): + """Baseline: a plain text request serves normally (backward-compat).""" + from vllm import SamplingParams + + outputs = served["llm"].generate( + "The capital of France is", + SamplingParams(max_tokens=8, temperature=0.0), + ) + _one_completion(outputs) + + +def test_adapter_control_token_serving(served): + """An adapter control token routes through the switch under serving. + + Routing correctness is test_switch_e2e_compose.py's job; the bar here is + that the switch path runs in the live engine and still generates. + """ + from vllm import SamplingParams + from vllm.inputs import TokensPrompt + + config = served["config"] + if not getattr(config, "adapter_token_ids", None): + pytest.skip("composed checkpoint has no adapters") + + tokenizer = served["llm"].get_tokenizer() + text_ids = tokenizer.encode("Summarize the document.", add_special_tokens=False) + # LORA control tokens sit at the sequence start (CLAUDE.md gotcha #3). + prompt = TokensPrompt(prompt_token_ids=[config.adapter_token_ids[0], *text_ids]) + + outputs = served["llm"].generate( + prompt, SamplingParams(max_tokens=8, temperature=0.0) + ) + _one_completion(outputs) + + +@pytest.mark.parametrize("num_clips", [1, 2, 3]) +def test_audio_clip_serving(served, num_clips): + """Audio x1/x2/x3: N markers + N clips transcribe, splice, and generate. + + Content is not asserted; the bar is a completed, well-formed request. + """ + from vllm import SamplingParams + + ceiling = int(getattr(served["config"], "asr_max_audio_clips", 32) or 32) + if num_clips > ceiling: + pytest.skip(f"checkpoint clip ceiling {ceiling} < {num_clips}") + + marker = "<|audio|>" + prompt = { + "prompt": marker * num_clips + " What was said?", + "multi_modal_data": { + "audio": [ + (_tone(freq=220.0 * (i + 1)), _TARGET_SR) for i in range(num_clips) + ] + }, + } + outputs = served["llm"].generate( + prompt, SamplingParams(max_tokens=8, temperature=0.0) + ) + _one_completion(outputs) diff --git a/tests/integration/test_audio_uncached_processor.py b/tests/integration/test_audio_uncached_processor.py new file mode 100644 index 0000000..af83440 --- /dev/null +++ b/tests/integration/test_audio_uncached_processor.py @@ -0,0 +1,283 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Audio serving with vLLM's multimodal processor cache DISABLED. + +This is the configuration that actually exercises +``AudioMultiModalProcessor._hf_processor_applies_updates``. vLLM reaches the HF +processor two ways and only one of them consults that hook: + +* cached path — hardcodes ``is_update_applied = False``, never asks +* uncached path — takes ``is_update_applied`` from the hook + +Every other audio test runs vLLM's defaults, where the cache is on, so they pass +whether or not the hook is overridden. With the cache off and the base hook's +``True``, vLLM skips applying our ``PromptReplacement`` and then reports the item +as missing:: + + RuntimeError: Expected there to be 1 audio prompt placeholders corresponding + to 1 audio items, but instead found 0 prompt placeholders! + +Startup profiling passes a *string* prompt, so on the uncached path the engine +fails before it can serve anything — booting at all is a large part of the guard. + +Assertions here are structural (request completes, marker was replaced), never +transcript content: ASR output is not deterministic enough to assert on. + +Opt in explicitly: `pytest -m "slow and requires_model and gpu"`. +""" + +import importlib.util +import os + +import pytest + +pytestmark = [ + pytest.mark.audio, + pytest.mark.slow, + pytest.mark.requires_model, + pytest.mark.gpu, +] + +if importlib.util.find_spec("vllm") is None: + pytest.skip("requires vLLM installed", allow_module_level=True) + + +# One small model: this covers a *configuration* dimension, not a model matrix, +# and each engine boot here is expensive. +_BASE_MODEL = "ibm-granite/granite-4.0-micro" +_ADAPTER_LIBRARY = "ibm-granite/granitelib-core-r1.0" + +COMPOSE_TIMEOUT_S = 1800 # matches the sibling E2E fixtures +_TARGET_SR = 16_000 +_AUDIO_MARKER = "<|audio|>" + + +def _cache_disabling_kwargs(): + """LLM kwargs that turn off the multimodal processor cache. + + The knob was renamed across the vLLM range this project supports (0.19.x and + 0.20.x are both allowed in pyproject): older builds expose + ``disable_mm_preprocessor_cache``, newer ones ``mm_processor_cache_gb``. + Returns an empty dict when neither exists, so the caller can skip rather than + silently exercise the cached path. + """ + import dataclasses + + from vllm.engine.arg_utils import EngineArgs + + names = {f.name for f in dataclasses.fields(EngineArgs)} + if "mm_processor_cache_gb" in names: + return {"mm_processor_cache_gb": 0} + if "disable_mm_preprocessor_cache" in names: + return {"disable_mm_preprocessor_cache": True} + return {} + + +def _cache_disabled_state(llm): + """Whether the running engine really has the processor cache off. + + ``True``/``False`` when determinable, ``None`` when this vLLM exposes neither + setting where we look. The field moved: it lives on ``MultiModalConfig`` + (nested under ``model_config.multimodal_config``) in newer vLLM, so checking + only ``model_config`` silently answers "unknown" and the whole module would + look green while running the cached path — testing nothing. + """ + model_config = llm.llm_engine.model_config + for obj in (getattr(model_config, "multimodal_config", None), model_config): + if obj is None: + continue + if hasattr(obj, "mm_processor_cache_gb"): + return obj.mm_processor_cache_gb == 0 + if hasattr(obj, "disable_mm_preprocessor_cache"): + return bool(obj.disable_mm_preprocessor_cache) + return None + + +@pytest.fixture(scope="module") +def audio_checkpoint(tmp_path_factory): + """Compose one audio-enabled checkpoint through the compose CLI.""" + import subprocess + import sys + + save_dir = tmp_path_factory.mktemp(_BASE_MODEL.rsplit("/", 1)[-1]) / "model" + cmd = [ + sys.executable, + "-m", + "granite_switch.composer.compose_granite_switch", + "--base-model", + _BASE_MODEL, + "--adapters", + _ADAPTER_LIBRARY, + "--enable-audio", + "--output", + str(save_dir), + ] + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=COMPOSE_TIMEOUT_S + ) + if result.returncode != 0: + raise RuntimeError( + f"compose failed for base={_BASE_MODEL} adapter={_ADAPTER_LIBRARY}\n" + f"--- STDOUT ---\n{result.stdout}\n--- STDERR ---\n{result.stderr}" + ) + return save_dir + + +@pytest.fixture(scope="module") +def served_uncached(audio_checkpoint): + """Boot vLLM with the processor cache off, and prove it is off. + + Reaching the ``yield`` is itself the startup-profiling guard: profiling runs a + dummy audio item through full processing with a string prompt, exactly the + combination that fails when the hook is left at its default. + + The cache check lives here rather than in a test so it *gates* every case. As + a separate test it would only skip itself, leaving the rest of the module + green while silently running the cached path. + """ + import gc + + import torch + + cache_kwargs = _cache_disabling_kwargs() + if not cache_kwargs: + pytest.skip( + "installed vLLM exposes neither mm_processor_cache_gb nor " + "disable_mm_preprocessor_cache; cannot disable the processor cache" + ) + + os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn") + from vllm import LLM + + llm = LLM( + model=str(audio_checkpoint), + dtype="bfloat16", + gpu_memory_utilization=0.7, + enforce_eager=True, # boot speed; orthogonal to the cache setting + **cache_kwargs, + ) + try: + state = _cache_disabled_state(llm) + if state is False: + pytest.fail( + f"{cache_kwargs!r} was accepted but the processor cache is still " + f"enabled; this module would exercise the cached path instead" + ) + if state is None: + pytest.skip( + "cannot confirm the processor-cache setting on this vLLM, so this " + "module cannot establish that it is testing the uncached path" + ) + yield { + "llm": llm, + "config": llm.llm_engine.model_config.hf_config, + "tokenizer": llm.get_tokenizer(), + } + finally: + del llm + gc.collect() + torch.cuda.empty_cache() + + +@pytest.fixture(scope="module") +def marker_id(served_uncached): + """The ``<|audio|>`` token id, verified to be a real single token. + + ``convert_tokens_to_ids`` returns the *unk* id for an unregistered token + rather than None, so a checkpoint composed without audio would hand back a + plausible-looking id and the marker assertions would be meaningless. + """ + tokenizer = served_uncached["tokenizer"] + token_id = tokenizer.convert_tokens_to_ids(_AUDIO_MARKER) + unk_id = getattr(tokenizer, "unk_token_id", None) + assert token_id is not None and token_id >= 0, ( + f"{_AUDIO_MARKER} is not in the tokenizer" + ) + assert token_id != unk_id, ( + f"{_AUDIO_MARKER} resolved to the unk id ({unk_id}) — the checkpoint was " + f"not composed with --enable-audio" + ) + encoded = tokenizer.encode(_AUDIO_MARKER, add_special_tokens=False) + assert encoded == [token_id], ( + f"{_AUDIO_MARKER} does not encode to exactly one token: {encoded}" + ) + return token_id + + +def _tone(seconds: float = 1.0, freq: float = 440.0): + """Deterministic mono 16 kHz waveform (no speech fixture needed).""" + import numpy as np + + t = np.arange(int(seconds * _TARGET_SR), dtype=np.float32) / _TARGET_SR + return (0.1 * np.sin(2 * np.pi * freq * t)).astype(np.float32) + + +def _silence(seconds: float = 1.0): + """A clip with no speech in it at all.""" + import numpy as np + + return np.zeros(int(seconds * _TARGET_SR), dtype=np.float32) + + +def _generate_with_audio(llm, waveform): + from vllm import SamplingParams + + return llm.generate( + { + "prompt": f"{_AUDIO_MARKER} What was said?", + "multi_modal_data": {"audio": [(waveform, _TARGET_SR)]}, + }, + SamplingParams(max_tokens=8, temperature=0.0), + ) + + +def _assert_marker_replaced(outputs, marker_id): + """The request completed and the marker is gone from the final prompt. + + ``RequestOutput.prompt_token_ids`` is the post-processing prompt (vLLM builds + the engine request from the processed inputs), so an absent marker means the + prompt replacement really was applied. Length is not asserted: a clip with no + speech legitimately collapses to the one-token fallback. + """ + assert len(outputs) == 1 + assert len(outputs[0].outputs[0].token_ids) >= 1 + + prompt_ids = list(outputs[0].prompt_token_ids or []) + assert prompt_ids, "engine returned no prompt_token_ids to inspect" + assert marker_id not in prompt_ids, ( + f"marker id {marker_id} survived into the final prompt — the audio " + f"placeholder was not applied" + ) + + +def test_audio_request_splices_transcript_uncached(served_uncached, marker_id): + """An audio request completes and the marker is replaced, cache off. + + The direct positive signal for the hook override: with the base hook the + replacement is skipped and vLLM raises before returning anything. + """ + outputs = _generate_with_audio(served_uncached["llm"], _tone()) + _assert_marker_replaced(outputs, marker_id) + + +def test_silent_clip_uncached(served_uncached, marker_id): + """Silence still yields a usable placeholder on the uncached path. + + The empty-transcript fallback fires on both processor paths, so it is worth + pinning here too: a clip transcribing to "" must not collapse to a + zero-length placeholder. + """ + outputs = _generate_with_audio(served_uncached["llm"], _silence()) + _assert_marker_replaced(outputs, marker_id) + + +def test_text_only_request_uncached(served_uncached): + """Disabling the cache must not disturb ordinary text requests.""" + from vllm import SamplingParams + + outputs = served_uncached["llm"].generate( + "The capital of France is", + SamplingParams(max_tokens=8, temperature=0.0), + ) + + assert len(outputs) == 1 + assert len(outputs[0].outputs[0].token_ids) >= 1 diff --git a/tests/unit/test_asr.py b/tests/unit/test_asr.py new file mode 100644 index 0000000..9571907 --- /dev/null +++ b/tests/unit/test_asr.py @@ -0,0 +1,384 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Unit tests for the audio ASR backend (granite_switch.vllm.audio.asr). + +The module under test has no vLLM dependency, but it lives under the +``granite_switch.vllm`` package whose ``__init__`` imports vLLM. To keep this a +fast CPU-tier unit test that runs without the vLLM extra installed, we load the +leaf module directly by file path rather than through the package. +""" + +import contextlib +import importlib.util +import pathlib +from unittest import mock + +import numpy as np +import pytest + +# Load asr.py directly (bypasses granite_switch.vllm.__init__ -> vLLM import). +_ASR_PATH = ( + pathlib.Path(__file__).resolve().parents[2] / "src/granite_switch/vllm/audio/asr.py" +) +_spec = importlib.util.spec_from_file_location("gs_asr_under_test", _ASR_PATH) +asr = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(asr) + +pytestmark = pytest.mark.audio + + +class TestCoerceAudio: + def test_array_plus_rate(self): + a = np.zeros(1600, dtype=np.float32) + arr, sr = asr._coerce_audio(a, 16000) + assert sr == 16000 and arr is a + + def test_tuple_form(self): + a = np.zeros(800, dtype=np.float32) + arr, sr = asr._coerce_audio((a, 8000), None) + assert sr == 8000 and arr is a + + def test_list_input_becomes_ndarray(self): + arr, sr = asr._coerce_audio([0.0] * 10, 16000) + assert isinstance(arr, np.ndarray) and sr == 16000 + + def test_missing_sampling_rate_raises(self): + with pytest.raises(ValueError): + asr._coerce_audio(np.zeros(10, dtype=np.float32), None) + + def test_bad_tuple_length_raises(self): + with pytest.raises(ValueError): + asr._coerce_audio((np.zeros(10), 1, 2), None) + + +class TestAsNumpy: + def test_passthrough_ndarray(self): + a = np.arange(5) + assert asr._as_numpy(a) is a + + def test_list(self): + assert np.array_equal(asr._as_numpy([1, 2, 3]), np.array([1, 2, 3])) + + def test_duck_typed_tensor(self): + class FakeTensor: + def __init__(self, x): + self._x = x + + def detach(self): + return self + + def cpu(self): + return self + + def numpy(self): + return self._x + + ft = FakeTensor(np.arange(4)) + assert np.array_equal(asr._as_numpy(ft), np.arange(4)) + + +class TestMonoAndResample: + def test_downmix_to_mono_float32(self): + stereo = np.ones((2, 100), dtype=np.float64) + mono = asr._to_mono_float32(stereo) + assert mono.shape == (100,) and mono.dtype == np.float32 + + def test_resample_noop_at_target(self): + a = np.zeros(1600, dtype=np.float32) + assert asr._resample(a, 16000, 16000) is a + + def test_resample_without_librosa_raises_clear_error(self): + # When librosa is unavailable, a non-target rate must raise a clear error. + import builtins + + real_import = builtins.__import__ + + def fake_import(name, *args, **kwargs): + if name == "librosa": + raise ImportError("no librosa") + return real_import(name, *args, **kwargs) + + with mock.patch("builtins.__import__", side_effect=fake_import): + with pytest.raises(RuntimeError, match="librosa"): + asr._resample(np.zeros(800, dtype=np.float32), 8000, 16000) + + +class TestTranscriber: + def test_transcribe_strips_and_uses_target_rate(self): + t = asr.ASRTranscriber(model_id="x", device="cpu") + fake_pipe = mock.Mock(return_value={"text": " hello world "}) + t._pipeline = fake_pipe # inject so load() is a no-op + + out = t.transcribe(np.zeros(1600, dtype=np.float32), sampling_rate=16000) + assert out == "hello world" + passed = fake_pipe.call_args_list[-1][0][0] + assert passed["sampling_rate"] == 16000 + + def test_load_is_idempotent_when_pipeline_set(self): + # Once the pipeline is loaded, load() must early-return (no rebuild). + t = asr.ASRTranscriber(model_id="x", device="cpu") + sentinel = object() + t._pipeline = sentinel + t.load() + assert t._pipeline is sentinel + + +class TestChunkedTranscribe: + """self_chunks=False routes through the split/transcribe/merge chunker.""" + + def _fake_pipe_transcriber(self): + t = asr.ASRTranscriber(model_id="x", device="cpu") + # Each segment "transcribes" to a token tagged by its sample length, so we + # can see how many windows were produced and that merge stitched them. + t._pipeline = lambda inp, **k: {"text": f"seg{len(inp['raw'])}"} + return t + + def test_self_chunks_true_is_single_call(self): + t = asr.ASRTranscriber(model_id="x", device="cpu") + calls = [] + t._pipeline = lambda inp, **k: (calls.append(len(inp["raw"])) or {"text": "x"}) + # 70s clip; with self_chunks the whole thing goes in one call. + t.transcribe( + np.zeros(70 * 16000, dtype=np.float32), + sampling_rate=16000, + self_chunks=True, + ) + assert len(calls) == 1 + assert calls[0] == 70 * 16000 + + def test_non_self_chunking_splits_and_merges(self): + t = self._fake_pipe_transcriber() + # 70s @16k, 30s window, 5s overlap -> 3 windows: 480000, 480000, 320000 + # samples. The two identical 30s window texts collapse at the seam; the + # 20s remainder is appended. + out = t.transcribe( + np.zeros(70 * 16000, dtype=np.float32), + sampling_rate=16000, + self_chunks=False, + chunk_length_s=30.0, + chunk_overlap_s=5.0, + ) + assert out == "seg480000 seg320000" + + def test_short_clip_single_window(self): + t = self._fake_pipe_transcriber() + out = t.transcribe( + np.zeros(5 * 16000, dtype=np.float32), + sampling_rate=16000, + self_chunks=False, + chunk_length_s=30.0, + chunk_overlap_s=5.0, + ) + assert out == "seg80000" + + +class TestTranscriberCache: + def test_same_key_returns_same_instance(self): + a = asr.get_transcriber("m", "cpu") + b = asr.get_transcriber("m", "cpu") + assert a is b + + def test_default_model_id_resolution(self): + t = asr.get_transcriber(None, "cpu") + assert t.model_id == asr.DEFAULT_ASR_MODEL_ID + + def test_different_device_distinct_instance(self): + a = asr.get_transcriber("m", "cpu") + b = asr.get_transcriber("m", "cuda:0") + assert a is not b + + def test_pipeline_kwargs_stored_on_instance(self): + t = asr.get_transcriber("m", "cpu", pipeline_kwargs={"chunk_length_s": 15}) + assert t.pipeline_kwargs == {"chunk_length_s": 15} + + def test_pipeline_kwargs_are_part_of_cache_key(self): + # Different pipeline_kwargs → different cached pipeline (they change how + # the pipeline is constructed), same kwargs → same instance. + a = asr.get_transcriber("pk", "cpu", pipeline_kwargs={"chunk_length_s": 15}) + b = asr.get_transcriber("pk", "cpu", pipeline_kwargs={"chunk_length_s": 30}) + c = asr.get_transcriber("pk", "cpu", pipeline_kwargs={"chunk_length_s": 15}) + assert a is not b + assert a is c + + def test_pipeline_kwargs_key_is_order_independent(self): + a = asr.get_transcriber("pk2", "cpu", pipeline_kwargs={"x": 1, "y": 2}) + b = asr.get_transcriber("pk2", "cpu", pipeline_kwargs={"y": 2, "x": 1}) + assert a is b + + +class TestFreeze: + def test_dict_order_independent(self): + assert asr._freeze({"a": 1, "b": 2}) == asr._freeze({"b": 2, "a": 1}) + + def test_nested_and_list(self): + frozen = asr._freeze({"a": [1, 2], "b": {"c": 3}}) + # Result must be hashable (usable as a dict key). + assert hash(frozen) == hash(asr._freeze({"b": {"c": 3}, "a": [1, 2]})) + + +class TestGenerateKwargsPassthrough: + def test_generate_kwargs_forwarded_to_pipeline_call(self): + t = asr.ASRTranscriber(model_id="x", device="cpu") + fake_pipe = mock.Mock(return_value={"text": "hola"}) + t._pipeline = fake_pipe + t.transcribe( + np.zeros(1600, dtype=np.float32), + sampling_rate=16000, + generate_kwargs={"language": "es"}, + ) + # generate_kwargs is forwarded to the pipeline call as a kwarg. + assert fake_pipe.call_args_list[-1].kwargs["generate_kwargs"] == { + "language": "es" + } + + def test_empty_generate_kwargs_not_passed(self): + # CTC / non-generative backends must not receive a generate_kwargs kwarg. + t = asr.ASRTranscriber(model_id="x", device="cpu") + fake_pipe = mock.Mock(return_value={"text": "ok"}) + t._pipeline = fake_pipe + t.transcribe(np.zeros(1600, dtype=np.float32), sampling_rate=16000) + assert "generate_kwargs" not in fake_pipe.call_args_list[-1].kwargs + t.transcribe( + np.zeros(1600, dtype=np.float32), sampling_rate=16000, generate_kwargs={} + ) + assert "generate_kwargs" not in fake_pipe.call_args_list[-1].kwargs + + +class TestResolveGenerateKwargs: + def test_config_defaults_only(self): + out = asr.resolve_generate_kwargs({"language": "de", "task": "transcribe"}) + assert out == {"language": "de", "task": "transcribe"} + + def test_none_config_is_empty(self): + assert asr.resolve_generate_kwargs(None) == {} + + def test_top_level_language_overrides_config(self): + out = asr.resolve_generate_kwargs({"language": "de"}, {"language": "fr"}) + assert out == {"language": "fr"} + + def test_nested_request_allowlisted_keys_merge(self): + out = asr.resolve_generate_kwargs( + {"language": "de"}, + {"asr_generate_kwargs": {"task": "translate"}}, + ) + assert out == {"language": "de", "task": "translate"} + + def test_disallowed_request_keys_dropped(self): + # A client cannot inject arbitrary generation options. + out = asr.resolve_generate_kwargs( + {"language": "de"}, + {"asr_generate_kwargs": {"num_beams": 99, "task": "translate"}}, + ) + assert out == {"language": "de", "task": "translate"} + assert "num_beams" not in out + + def test_request_wins_over_config(self): + out = asr.resolve_generate_kwargs( + {"language": "de", "task": "transcribe"}, + {"asr_generate_kwargs": {"language": "ja"}}, + ) + assert out["language"] == "ja" + assert out["task"] == "transcribe" + + def test_config_not_mutated(self): + cfg = {"language": "de"} + asr.resolve_generate_kwargs(cfg, {"language": "fr"}) + assert cfg == {"language": "de"} + + +@contextlib.contextmanager +def _patched_pipeline(factory): + """Patch both lookup paths: `from transformers import pipeline` re-resolves to + transformers.pipelines.pipeline, but transformers caches it on the top-level + module after the first access, so a later test would get the real one.""" + with ( + mock.patch("transformers.pipelines.pipeline", factory), + mock.patch("transformers.pipeline", factory), + ): + yield + + +class TestResolveTorchDtype: + """asr_dtype resolution. float16-on-CUDA is the default, but overridable.""" + + def test_auto_on_cuda_is_float16(self): + torch = pytest.importorskip("torch") + assert asr._resolve_torch_dtype(None, "cuda:0") is torch.float16 + assert asr._resolve_torch_dtype("auto", "cuda") is torch.float16 + + def test_auto_on_cpu_is_float32(self): + torch = pytest.importorskip("torch") + assert asr._resolve_torch_dtype(None, "cpu") is torch.float32 + + def test_explicit_float32_overrides_cuda_default(self): + # The BatchNorm-encoder case: CUDA must not force half precision. + torch = pytest.importorskip("torch") + assert asr._resolve_torch_dtype("float32", "cuda:0") is torch.float32 + + def test_explicit_bfloat16(self): + torch = pytest.importorskip("torch") + assert asr._resolve_torch_dtype("bfloat16", "cpu") is torch.bfloat16 + + @pytest.mark.parametrize( + "name,attr", + [ + ("fp16", "float16"), + ("half", "float16"), + ("bf16", "bfloat16"), + ("fp32", "float32"), + ("FLOAT32", "float32"), + ], + ) + def test_aliases_and_case(self, name, attr): + torch = pytest.importorskip("torch") + assert asr._resolve_torch_dtype(name, "cpu") is getattr(torch, attr) + + def test_unknown_name_raises(self): + pytest.importorskip("torch") + with pytest.raises(ValueError, match="Unsupported asr_dtype"): + asr._resolve_torch_dtype("int8", "cpu") + + def test_dtype_is_part_of_cache_key(self): + a = asr.get_transcriber("dt", "cuda:0", dtype="float32") + b = asr.get_transcriber("dt", "cuda:0", dtype="float16") + c = asr.get_transcriber("dt", "cuda:0", dtype="float32") + assert a is not b + assert a is c + + def test_load_passes_resolved_dtype(self): + torch = pytest.importorskip("torch") + factory = mock.Mock(return_value=mock.Mock()) + with _patched_pipeline(factory): + asr.ASRTranscriber(model_id="m", device="cuda:0", dtype="float32").load() + assert factory.call_args.kwargs["torch_dtype"] is torch.float32 + + def test_pipeline_kwargs_torch_dtype_still_wins(self): + torch = pytest.importorskip("torch") + factory = mock.Mock(return_value=mock.Mock()) + with _patched_pipeline(factory): + asr.ASRTranscriber( + model_id="m", + device="cpu", + dtype="float32", + pipeline_kwargs={"torch_dtype": torch.bfloat16}, + ).load() + assert factory.call_args.kwargs["torch_dtype"] is torch.bfloat16 + + +class TestLoadMergesPipelineKwargs: + def test_pipeline_kwargs_override_defaults(self): + # load() must merge config-supplied pipeline_kwargs over the built-in + # defaults (e.g. override chunk_length_s, add extra kwargs). + pytest.importorskip("torch") + fake_pipe_factory = mock.Mock(return_value=mock.Mock()) + with _patched_pipeline(fake_pipe_factory): + t = asr.ASRTranscriber( + model_id="m", + device="cpu", + pipeline_kwargs={"chunk_length_s": 15, "batch_size": 4}, + ) + t.load() + kwargs = fake_pipe_factory.call_args.kwargs + assert kwargs["model"] == "m" + assert kwargs["task"] == "automatic-speech-recognition" + assert kwargs["chunk_length_s"] == 15 # overrode the default 30 + assert kwargs["batch_size"] == 4 # extra kwarg passed through diff --git a/tests/unit/test_chunking.py b/tests/unit/test_chunking.py new file mode 100644 index 0000000..8143c7c --- /dev/null +++ b/tests/unit/test_chunking.py @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: Apache-2.0 +"""Unit tests for the encoder-agnostic long-audio chunker. + +Pure numpy/stdlib logic (no vLLM), so — like ``test_asr.py`` — the leaf module is +loaded directly by file path to skip the vLLM-importing package ``__init__``. +""" + +import importlib.util +import pathlib + +import numpy as np +import pytest + +_CHUNKING_PATH = ( + pathlib.Path(__file__).resolve().parents[2] + / "src/granite_switch/vllm/audio/chunking.py" +) +_spec = importlib.util.spec_from_file_location("gs_chunking_under_test", _CHUNKING_PATH) +chunking = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(chunking) + +pytestmark = pytest.mark.audio + + +SR = 16_000 + + +class TestSplitWaveform: + def test_short_clip_returns_single_segment(self): + wav = np.zeros(5 * SR, dtype=np.float32) + segs = chunking.split_waveform(wav, SR, 30.0, 5.0) + assert len(segs) == 1 + assert len(segs[0]) == len(wav) + + def test_exact_window_is_one_segment(self): + wav = np.zeros(30 * SR, dtype=np.float32) + segs = chunking.split_waveform(wav, SR, 30.0, 5.0) + assert len(segs) == 1 + + def test_overlapping_windows_cover_and_step(self): + # 100s, 30s window, 5s overlap -> step 25s -> starts 0,25,50,75 + wav = np.arange(100 * SR, dtype=np.float32) + segs = chunking.split_waveform(wav, SR, 30.0, 5.0) + assert [round(len(s) / SR, 3) for s in segs] == [30.0, 30.0, 30.0, 25.0] + # Every sample is covered, and consecutive windows overlap by 5s. + assert segs[0][-1] > segs[1][0] # overlap: seg0 tail past seg1 head start + + def test_last_segment_is_remainder(self): + wav = np.zeros(70 * SR, dtype=np.float32) # 70s -> 30,30,20 (step 25) + segs = chunking.split_waveform(wav, SR, 30.0, 5.0) + assert [round(len(s) / SR, 3) for s in segs] == [30.0, 30.0, 20.0] + + def test_no_overlap_tiles(self): + wav = np.zeros(60 * SR, dtype=np.float32) + segs = chunking.split_waveform(wav, SR, 30.0, 0.0) + assert [round(len(s) / SR, 3) for s in segs] == [30.0, 30.0] + + def test_invalid_window_raises(self): + with pytest.raises(ValueError): + chunking.split_waveform(np.zeros(SR), SR, 0.0, 0.0) + + def test_overlap_ge_window_raises(self): + with pytest.raises(ValueError): + chunking.split_waveform(np.zeros(SR), SR, 30.0, 30.0) + + +class TestMergeTranscripts: + def test_empty_list(self): + assert chunking.merge_transcripts([]) == "" + + def test_single(self): + assert chunking.merge_transcripts(["hello world"]) == "hello world" + + def test_overlap_deduped(self): + out = chunking.merge_transcripts( + ["what is the capital of Israel", "the capital of Israel is Jerusalem"] + ) + assert out == "what is the capital of Israel is Jerusalem" + + def test_no_overlap_concatenates(self): + assert chunking.merge_transcripts(["hello world", "foo bar"]) == ( + "hello world foo bar" + ) + + def test_empty_segments_skipped(self): + assert chunking.merge_transcripts(["a b c", "", "c d e"]) == "a b c d e" + + def test_seam_is_punct_and_case_insensitive(self): + # "Store." vs "store," and "the" both normalize equal -> 2-word overlap. + out = chunking.merge_transcripts(["going to the Store.", "the store, i went"]) + assert out == "going to the Store. i went" + + def test_identical_adjacent_collapses(self): + assert chunking.merge_transcripts(["thank you", "thank you"]) == "thank you" + + def test_partial_overlap_keeps_tail(self): + out = chunking.merge_transcripts(["a b c d", "c d e f"]) + assert out == "a b c d e f" diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 32e31bb..7ecbab4 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -93,3 +93,111 @@ def test_zero_adapter_default(self): def test_projection_head_dim_inferred_from_hidden_size(self): cfg = GraniteSwitchConfig(**_valid_kwargs()) assert cfg.projection_head_dim == 64 // 4 + + +# ════════════════════════════════════════════════════════════════════ +# 3. Audio (ASR) preprocessing fields +# ════════════════════════════════════════════════════════════════════ + + +@pytest.mark.audio +class TestAudioConfig: + def test_asr_defaults_off(self): + cfg = GraniteSwitchConfig(num_adapters=0) + assert cfg.asr_enabled is False + assert cfg.asr_model_id is None + assert cfg.asr_device == "cpu" + assert cfg.asr_dtype is None + assert cfg.asr_pipeline_kwargs is None + assert cfg.asr_generate_kwargs is None + + def test_asr_dtype_round_trip(self, tmp_path): + cfg = GraniteSwitchConfig( + num_adapters=0, asr_enabled=True, asr_device="cuda:0", asr_dtype="float32" + ) + cfg.save_pretrained(tmp_path) + assert GraniteSwitchConfig.from_pretrained(tmp_path).asr_dtype == "float32" + + def test_invalid_asr_dtype_raises(self): + with pytest.raises(ValueError, match="asr_dtype"): + GraniteSwitchConfig(num_adapters=0, asr_dtype="fp8") + + def test_longaudio_defaults(self): + cfg = GraniteSwitchConfig(num_adapters=0) + assert cfg.asr_max_audio_clips == 32 + assert cfg.asr_chunk_length_s == 30.0 + assert cfg.asr_chunk_overlap_s == 5.0 + assert cfg.asr_self_chunks is True + + def test_longaudio_round_trip(self, tmp_path): + cfg = GraniteSwitchConfig( + num_adapters=0, + asr_enabled=True, + asr_max_audio_clips=4, + asr_chunk_length_s=20.0, + asr_chunk_overlap_s=3.0, + asr_self_chunks=False, + ) + cfg.save_pretrained(tmp_path) + loaded = GraniteSwitchConfig.from_pretrained(tmp_path) + assert loaded.asr_max_audio_clips == 4 + assert loaded.asr_chunk_length_s == 20.0 + assert loaded.asr_chunk_overlap_s == 3.0 + assert loaded.asr_self_chunks is False + + def test_invalid_max_audio_clips_raises(self): + with pytest.raises(ValueError, match="asr_max_audio_clips"): + GraniteSwitchConfig(num_adapters=0, asr_max_audio_clips=0) + + def test_overlap_ge_window_raises(self): + with pytest.raises(ValueError, match="asr_chunk_overlap_s"): + GraniteSwitchConfig( + num_adapters=0, asr_chunk_length_s=10.0, asr_chunk_overlap_s=10.0 + ) + + def test_asr_kwargs_round_trip(self, tmp_path): + # Pipeline/generate kwargs must survive save_pretrained → from_pretrained + # so the checkpoint stays self-describing about its ASR front-end. + cfg = GraniteSwitchConfig( + num_adapters=0, + asr_enabled=True, + asr_model_id="openai/whisper-large-v3", + asr_pipeline_kwargs={"chunk_length_s": 15, "batch_size": 4}, + asr_generate_kwargs={"language": "de", "task": "transcribe"}, + ) + cfg.save_pretrained(tmp_path) + loaded = GraniteSwitchConfig.from_pretrained(tmp_path) + assert loaded.asr_enabled is True + assert loaded.asr_model_id == "openai/whisper-large-v3" + assert loaded.asr_pipeline_kwargs == {"chunk_length_s": 15, "batch_size": 4} + assert loaded.asr_generate_kwargs == {"language": "de", "task": "transcribe"} + + def test_adapters_and_audio_coexist_round_trip(self, tmp_path): + """Adapters and the audio cascade coexist and survive save→load.""" + cfg = GraniteSwitchConfig( + **_valid_kwargs(num_adapters=3), + asr_enabled=True, + asr_model_id="distil-whisper/distil-small.en", + asr_max_audio_clips=4, + ) + assert cfg.num_adapters == 3 + assert cfg.adapter_token_ids == [500, 501, 502] + assert cfg.asr_enabled is True + + cfg.save_pretrained(tmp_path) + loaded = GraniteSwitchConfig.from_pretrained(tmp_path) + assert loaded.num_adapters == 3 + assert loaded.adapter_token_ids == [500, 501, 502] + assert loaded.adapter_names == ["adapter_0", "adapter_1", "adapter_2"] + assert loaded.adapter_ranks == [8, 8, 8] + assert loaded.adapter_substitute_token_ids == [1, 1, 1] + assert loaded.asr_enabled is True + assert loaded.asr_model_id == "distil-whisper/distil-small.en" + assert loaded.asr_max_audio_clips == 4 + + def test_adapter_token_ids_do_not_collide_with_reserved_audio_row(self, tmp_path): + """Enabling audio does not perturb adapter token ids.""" + before = GraniteSwitchConfig(**_valid_kwargs(num_adapters=2)) + after = GraniteSwitchConfig(**_valid_kwargs(num_adapters=2), asr_enabled=True) + assert before.adapter_token_ids == after.adapter_token_ids + assert before.adapter_substitute_token_ids == after.adapter_substitute_token_ids diff --git a/tests/vllm/test_audio_processor.py b/tests/vllm/test_audio_processor.py new file mode 100644 index 0000000..cd366c2 --- /dev/null +++ b/tests/vllm/test_audio_processor.py @@ -0,0 +1,655 @@ +# SPDX-License-Identifier: Apache-2.0 +"""vLLM-tier tests for the audio ASR multimodal processor. + +Needs vLLM importable (the base classes come from it) but no GPU and no real ASR +model — the transcriber is faked, so what is under test is the plumbing: modality +gating on ``asr_enabled``, the config accessors, and decode kwargs reaching the +transcriber. The merge logic itself is unit-tested in tests/unit/test_asr.py. +""" + +import importlib.util +from types import SimpleNamespace + +import numpy as np +import pytest + +_VLLM_AVAILABLE = importlib.util.find_spec("vllm") is not None + +pytestmark = [ + pytest.mark.audio, + pytest.mark.skipif(not _VLLM_AVAILABLE, reason="requires vLLM installed"), +] + +if _VLLM_AVAILABLE: + from granite_switch.vllm.audio import processor as proc_mod + from granite_switch.vllm.audio.asr import DEFAULT_ASR_MODEL_ID + from granite_switch.vllm.audio.processor import ( + GraniteSwitchASRMultiModalProcessor, + GraniteSwitchASRProcessingInfo, + ) + + +def _make_info(*, max_model_len=131072, **cfg_attrs): + """A ProcessingInfo whose get_hf_config() returns a stub config. + + Bypasses __init__, which needs a full vLLM InputProcessingContext. + """ + info = object.__new__(GraniteSwitchASRProcessingInfo) + cfg = SimpleNamespace(**cfg_attrs) + info.get_hf_config = lambda: cfg + info.ctx = SimpleNamespace( + model_config=SimpleNamespace(max_model_len=max_model_len) + ) + return info + + +class TestProcessingInfoGating: + def test_disabled_reports_no_modalities(self): + info = _make_info(asr_enabled=False) + assert info.get_supported_mm_limits() == {} + assert info.get_mm_max_tokens_per_item(128, {"audio": 1}) == {} + + def test_missing_flag_defaults_disabled(self): + # A pre-audio checkpoint has no asr_enabled key at all. + info = _make_info() + assert info.get_supported_mm_limits() == {} + + def test_enabled_reports_configurable_clip_limit(self): + # Default ceiling is 32 clips; no longer hard-capped at 1. + info = _make_info(asr_enabled=True) + assert info.get_supported_mm_limits() == {"audio": 32} + info3 = _make_info(asr_enabled=True, asr_max_audio_clips=3) + assert info3.get_supported_mm_limits() == {"audio": 3} + + def test_max_tokens_per_item_is_context_derived(self): + # Profiling/encoder-cache hint = per-clip share of the context (seq_len // + # clip_count), the worst case one clip can occupy. Not a request bound — + # an oversized transcript is rejected by vLLM's prompt-length check. + info = _make_info(asr_enabled=True) + assert info.get_mm_max_tokens_per_item(20000, {"audio": 1}) == {"audio": 20000} + assert info.get_mm_max_tokens_per_item(20000, {"audio": 4}) == { + "audio": 20000 // 4 + } + + +class TestProcessingInfoAsrAccessors: + def test_model_id_defaults_when_none(self): + info = _make_info(asr_enabled=True, asr_model_id=None) + assert info._asr_model_id() == DEFAULT_ASR_MODEL_ID + + def test_model_id_explicit(self): + info = _make_info(asr_enabled=True, asr_model_id="openai/whisper-small") + assert info._asr_model_id() == "openai/whisper-small" + + def test_device_default_cpu(self): + assert _make_info(asr_enabled=True)._asr_device() == "cpu" + + def test_pipeline_and_generate_kwargs_default_empty(self): + info = _make_info(asr_enabled=True) + assert info._asr_pipeline_kwargs() == {} + assert info._asr_generate_kwargs() == {} + + def test_pipeline_and_generate_kwargs_from_config(self): + info = _make_info( + asr_enabled=True, + asr_pipeline_kwargs={"chunk_length_s": 15}, + asr_generate_kwargs={"language": "de"}, + ) + assert info._asr_pipeline_kwargs() == {"chunk_length_s": 15} + assert info._asr_generate_kwargs() == {"language": "de"} + + def test_longaudio_accessor_defaults(self): + info = _make_info(asr_enabled=True) + assert info._asr_max_audio_clips() == 32 + assert info._asr_self_chunks() is True + assert info._asr_chunk_length_s() == 30.0 + assert info._asr_chunk_overlap_s() == 5.0 + + def test_longaudio_accessors_from_config(self): + info = _make_info( + asr_enabled=True, + asr_max_audio_clips=2, + asr_self_chunks=False, + asr_chunk_length_s=20.0, + asr_chunk_overlap_s=3.0, + ) + assert info._asr_max_audio_clips() == 2 + assert info._asr_self_chunks() is False + assert info._asr_chunk_length_s() == 20.0 + assert info._asr_chunk_overlap_s() == 3.0 + + def test_max_model_len_from_ctx(self): + assert ( + _make_info(asr_enabled=True, max_model_len=16000)._max_model_len() == 16000 + ) + + def test_max_model_len_falls_back_to_position_embeddings(self): + info = _make_info(asr_enabled=True, max_position_embeddings=4096) + info.ctx = SimpleNamespace(model_config=SimpleNamespace(max_model_len=None)) + assert info._max_model_len() == 4096 + + +def _make_processor(info, monkeypatch, capture): + """A processor whose transcriber is faked; records what it was called with.""" + info.get_tokenizer = lambda: SimpleNamespace( + encode=lambda text, add_special_tokens=False: [1, 2, 3] + ) + proc = object.__new__(GraniteSwitchASRMultiModalProcessor) + proc.info = info + + class FakeTranscriber: + def transcribe( + self, + audio, + sampling_rate=None, + generate_kwargs=None, + self_chunks=True, + chunk_length_s=30.0, + chunk_overlap_s=5.0, + ): + capture["sampling_rate"] = sampling_rate + capture["generate_kwargs"] = generate_kwargs + capture["self_chunks"] = self_chunks + capture["chunk_length_s"] = chunk_length_s + capture["chunk_overlap_s"] = chunk_overlap_s + return "hello world" + + def fake_get_transcriber( + model_id=None, device="cpu", pipeline_kwargs=None, dtype=None + ): + capture["model_id"] = model_id + capture["device"] = device + capture["pipeline_kwargs"] = pipeline_kwargs + capture["dtype"] = dtype + return FakeTranscriber() + + monkeypatch.setattr(proc_mod, "get_transcriber", fake_get_transcriber) + return proc + + +class TestTranscribeWiring: + def test_transcribe_forwards_pipeline_and_generate_kwargs(self, monkeypatch): + capture = {} + info = _make_info( + asr_enabled=True, + asr_model_id="whisper-x", + asr_device="cpu", + asr_pipeline_kwargs={"chunk_length_s": 15}, + asr_generate_kwargs={}, + ) + proc = _make_processor(info, monkeypatch, capture) + + ids = proc._transcribe(np.zeros(1600, dtype=np.float32), {"language": "fr"}) + assert ids == [1, 2, 3] + assert capture["model_id"] == "whisper-x" + assert capture["pipeline_kwargs"] == {"chunk_length_s": 15} + assert capture["generate_kwargs"] == {"language": "fr"} + assert capture["sampling_rate"] == proc_mod._TARGET_SR + + def test_dtype_forwarded_from_config(self, monkeypatch): + capture = {} + info = _make_info(asr_enabled=True, asr_device="cuda:0", asr_dtype="float32") + proc = _make_processor(info, monkeypatch, capture) + proc._transcribe(np.zeros(1600, dtype=np.float32), {}) + assert capture["dtype"] == "float32" + + def test_dtype_defaults_to_none(self, monkeypatch): + capture = {} + proc = _make_processor(_make_info(asr_enabled=True), monkeypatch, capture) + proc._transcribe(np.zeros(1600, dtype=np.float32), {}) + assert capture["dtype"] is None + + def test_empty_generate_kwargs_becomes_none(self, monkeypatch): + capture = {} + info = _make_info(asr_enabled=True, asr_model_id="w", asr_device="cpu") + proc = _make_processor(info, monkeypatch, capture) + proc._transcribe(np.zeros(1600, dtype=np.float32), {}) + assert capture["generate_kwargs"] is None + + +class TestCallHfProcessorMerge: + """The Level-2 seam end to end (short of a real model): config defaults + + allowlisted per-request override reach the transcriber.""" + + def test_request_language_overrides_config_default(self, monkeypatch): + capture = {} + info = _make_info( + asr_enabled=True, + asr_model_id="w", + asr_device="cpu", + asr_pipeline_kwargs=None, + asr_generate_kwargs={"task": "transcribe", "language": "de"}, + ) + proc = _make_processor(info, monkeypatch, capture) + + bf = proc._call_hf_processor( + prompt="<|audio|>", + mm_data={"audios": [np.zeros(1600, dtype=np.float32)]}, + mm_kwargs={"language": "fr"}, # per-request override + tok_kwargs={}, + ) + # config 'task' retained, request 'language' wins over config 'de'. + assert capture["generate_kwargs"] == {"task": "transcribe", "language": "fr"} + assert "audio_token_ids" in bf + + def test_disallowed_request_key_dropped(self, monkeypatch): + capture = {} + info = _make_info( + asr_enabled=True, + asr_model_id="w", + asr_device="cpu", + asr_pipeline_kwargs=None, + asr_generate_kwargs={"language": "de"}, + ) + proc = _make_processor(info, monkeypatch, capture) + + proc._call_hf_processor( + prompt="<|audio|>", + mm_data={"audios": [np.zeros(1600, dtype=np.float32)]}, + mm_kwargs={"asr_generate_kwargs": {"num_beams": 99, "task": "translate"}}, + tok_kwargs={}, + ) + gk = capture["generate_kwargs"] + assert gk == {"language": "de", "task": "translate"} + assert "num_beams" not in gk + + def test_text_only_request_does_not_transcribe(self, monkeypatch): + capture = {} + info = _make_info(asr_enabled=True, asr_model_id="w", asr_device="cpu") + proc = _make_processor(info, monkeypatch, capture) + + bf = proc._call_hf_processor( + prompt="just text", + mm_data={}, + mm_kwargs={}, + tok_kwargs={}, + ) + # No audio → transcriber never touched, no audio fields emitted. + assert capture == {} + assert "audio_token_ids" not in bf + + +class TestMultiClipAndBudget: + """Multiple clips, each transcribed in full and spliced at its marker.""" + + def test_two_clips_produce_two_transcripts(self, monkeypatch): + capture = {} + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor(info, monkeypatch, capture) + + bf = proc._call_hf_processor( + prompt="<|audio|> and <|audio|>", + mm_data={ + "audios": [ + np.zeros(1600, dtype=np.float32), + np.zeros(1600, dtype=np.float32), + ] + }, + mm_kwargs={}, + tok_kwargs={}, + ) + # Each faked transcript is [1,2,3]; two clips -> per-item sizes [3,3], + # flat length 6 (spliced at the two markers by _get_prompt_updates). + assert bf["audio_num_tokens"].tolist() == [3, 3] + assert len(bf["audio_token_ids"]) == 6 + + def test_transcribe_returns_full_transcript(self, monkeypatch): + # The transcript is never truncated here — it is spliced in full and an + # oversized prompt is rejected downstream by vLLM's length check. + capture = {} + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor(info, monkeypatch, capture) + info.get_tokenizer = lambda: SimpleNamespace( + encode=lambda text, add_special_tokens=False: [1, 2, 3, 4, 5] + ) + assert proc._transcribe(np.zeros(1600, dtype=np.float32), {}) == [1, 2, 3, 4, 5] + + def test_self_chunks_and_chunk_params_forwarded(self, monkeypatch): + capture = {} + info = _make_info( + asr_enabled=True, + asr_model_id="w", + asr_self_chunks=False, + asr_chunk_length_s=20.0, + asr_chunk_overlap_s=3.0, + ) + proc = _make_processor(info, monkeypatch, capture) + proc._transcribe(np.zeros(1600, dtype=np.float32), {}) + assert capture["self_chunks"] is False + assert capture["chunk_length_s"] == 20.0 + assert capture["chunk_overlap_s"] == 3.0 + + +def _make_processor_transcribing(info, monkeypatch, text, *, ids_for): + """Processor whose transcriber returns a fixed ``text``; ``ids_for`` maps it to ids.""" + info.get_tokenizer = lambda: SimpleNamespace( + encode=lambda t, add_special_tokens=False: ids_for(t) + ) + proc = object.__new__(GraniteSwitchASRMultiModalProcessor) + proc.info = info + + class FakeTranscriber: + def transcribe( + self, + audio, + sampling_rate=None, + generate_kwargs=None, + self_chunks=True, + chunk_length_s=30.0, + chunk_overlap_s=5.0, + ): + return text + + monkeypatch.setattr( + proc_mod, + "get_transcriber", + lambda model_id=None, device="cpu", pipeline_kwargs=None, dtype=None: ( + FakeTranscriber() + ), + ) + return proc + + +_BLANK_ID = 5 + + +class TestEmptyAndSilentClip: + """Silence transcribes to "" -> stands in as _EMPTY_TRANSCRIPT_TEXT. + + A zero-length replacement is not a legal placeholder: vLLM skips zero-length + content when locating placeholders and then rejects the request with + ``found 0 prompt placeholders``. So every clip must yield >=1 token, however + little was said in it. + """ + + def _ids_for(self, text): + if text == "": + return [] + if text == proc_mod._EMPTY_TRANSCRIPT_TEXT: + return [_BLANK_ID] + return [1, 2, 3] + + def test_empty_transcript_yields_blank_token(self, monkeypatch): + import torch + + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor_transcribing( + info, monkeypatch, "", ids_for=self._ids_for + ) + + bf = proc._call_hf_processor( + prompt="<|audio|>", + mm_data={"audios": [np.zeros(1600, dtype=np.float32)]}, + mm_kwargs={}, + tok_kwargs={}, + ) + # One token, not zero — the item is still a findable placeholder. + assert bf["audio_num_tokens"].tolist() == [1] + assert bf["audio_token_ids"].tolist() == [_BLANK_ID] + assert bf["audio_token_ids"].dtype == torch.long + + def test_whitespace_only_transcript_also_falls_back(self, monkeypatch): + # transcribe() strips, so "" is the norm; a backend that does not strip + # must not slip a whitespace-only transcript past the check. + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor_transcribing( + info, monkeypatch, " \n", ids_for=self._ids_for + ) + assert proc._transcribe(np.zeros(1600, dtype=np.float32), {}) == [_BLANK_ID] + + def test_untokenizable_fallback_raises_clearly(self, monkeypatch): + # If even the fallback encodes to nothing, fail with our message rather + # than vLLM's opaque placeholder-count error. + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor_transcribing( + info, monkeypatch, "", ids_for=lambda text: [] + ) + with pytest.raises(ValueError, match="no tokens"): + proc._transcribe(np.zeros(1600, dtype=np.float32), {}) + + def test_nonempty_transcript_untouched(self, monkeypatch): + # The fallback must not perturb a clip that did contain speech. + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor_transcribing( + info, monkeypatch, "words", ids_for=self._ids_for + ) + assert proc._transcribe(np.zeros(1600, dtype=np.float32), {}) == [1, 2, 3] + + def test_blank_clip_replacement_is_not_empty(self, monkeypatch): + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor_transcribing( + info, monkeypatch, "", ids_for=self._ids_for + ) + + class _Kwargs: + def __init__(self, data): + self._data = data + + def get_data(self): + return self._data + + import torch + + out = _Kwargs( + { + "audio_num_tokens": torch.tensor([1], dtype=torch.long), + "audio_token_ids": torch.tensor([_BLANK_ID], dtype=torch.long), + } + ) + updates = proc._get_prompt_updates(None, {}, out) + assert len(updates) == 1 + assert updates[0].replacement(0) == [_BLANK_ID] + + def test_mixed_blank_and_nonempty_clips(self, monkeypatch): + info = _make_info(asr_enabled=True, asr_max_audio_clips=4, asr_model_id="w") + texts = iter(["", "words"]) + info.get_tokenizer = lambda: SimpleNamespace( + encode=lambda t, add_special_tokens=False: self._ids_for(t) + ) + proc = object.__new__(GraniteSwitchASRMultiModalProcessor) + proc.info = info + + class FakeTranscriber: + def transcribe(self, audio, **kw): + return next(texts) + + monkeypatch.setattr( + proc_mod, + "get_transcriber", + lambda model_id=None, device="cpu", pipeline_kwargs=None, dtype=None: ( + FakeTranscriber() + ), + ) + + bf = proc._call_hf_processor( + prompt="<|audio|> <|audio|>", + mm_data={ + "audios": [ + np.zeros(1600, dtype=np.float32), + np.zeros(1600, dtype=np.float32), + ] + }, + mm_kwargs={}, + tok_kwargs={}, + ) + # The silent clip contributes 1 token instead of 0, so both items keep a + # distinct, non-empty span and neither gets dropped. + assert bf["audio_num_tokens"].tolist() == [1, 3] + assert bf["audio_token_ids"].tolist() == [_BLANK_ID, 1, 2, 3] + + +_MARKER_ID = 99 +_TRANSCRIPT_IDS = [7, 8] + + +class _MarkerTokenizer: + """Marker is one special id, the transcript two; anything else is per-char. + + A class, not a SimpleNamespace: vLLM's ``_seq2tokens`` goes through an + ``lru_cache`` keyed on the tokenizer, so it has to be hashable. + """ + + def encode(self, text, add_special_tokens=False): + if text == "hello world": + return list(_TRANSCRIPT_IDS) + # Split on the marker the way a fast tokenizer splits on a registered + # special token; everything else is one id per character. + ids = [] + for i, part in enumerate(text.split(proc_mod.AUDIO_MARKER)): + if i: + ids.append(_MARKER_ID) + ids.extend(ord(c) for c in part) + return ids + + def decode(self, ids): + return "".join( + proc_mod.AUDIO_MARKER if i == _MARKER_ID else chr(i) for i in ids + ) + + +class TestPromptUpdatesAreApplied: + """The marker must actually become transcript ids on the *uncached* path. + + ``_hf_processor_applies_updates`` is vLLM's "did you expand the placeholder + yourself?" hook. We do not — we leave ``<|audio|>`` in the prompt and return + the transcript out of band — so it must report False or vLLM skips our + ``PromptReplacement`` and then fails to find the transcript it was promised. + + Only the uncached path (``--mm-processor-cache-gb 0``) consults the hook, so + the default cache setting hid this. These tests drive vLLM's real + ``_apply_hf_processor_text_mm`` / ``_maybe_apply_prompt_updates`` rather than + asserting on the override in isolation. + """ + + def _audio_items(self, count=1): + from vllm.multimodal.parse import AudioProcessorItems, MultiModalDataItems + + clips = [np.zeros(1600, dtype=np.float32) for _ in range(count)] + return MultiModalDataItems({"audio": AudioProcessorItems(clips)}) + + def _proc(self, monkeypatch, count=1, text="hello world"): + info = _make_info(asr_enabled=True, asr_model_id="w") + proc = _make_processor_transcribing( + info, monkeypatch, text, ids_for=lambda t: [7, 8] + ) + # ids_for is unused: the marker tokenizer does the encoding, so the + # transcript text maps to ids the same way the real path would. + tokenizer = _MarkerTokenizer() + info.get_tokenizer = lambda: tokenizer + return proc, self._audio_items(count) + + def test_hook_reports_updates_not_applied(self, monkeypatch): + proc, items = self._proc(monkeypatch) + assert ( + proc._hf_processor_applies_updates( + prompt_text=proc_mod.AUDIO_MARKER, + mm_items=items, + hf_processor_mm_kwargs={}, + tokenization_kwargs={}, + ) + is False + ) + + def test_uncached_path_reports_updates_not_applied(self, monkeypatch): + # Real vLLM code: this is the call site that decides whether the + # replacement runs (processing/processor.py, _apply_hf_processor_text_mm). + proc, items = self._proc(monkeypatch) + prompt_ids, _, is_update_applied = proc._apply_hf_processor_text_mm( + prompt_text=proc_mod.AUDIO_MARKER, + mm_items=items, + hf_processor_mm_kwargs={}, + tokenization_kwargs={}, + ) + # Marker still un-expanded, hence False. + assert prompt_ids == [_MARKER_ID] + assert is_update_applied is False + + def _apply_uncached(self, proc, items, prompt_text): + """vLLM's ``apply()`` with cache=None, minus the hashing/cache plumbing.""" + from vllm.multimodal.inputs import MultiModalKwargsItems + + prompt_ids, processed, is_update_applied = proc._apply_hf_processor_text_mm( + prompt_text=prompt_text, + mm_items=items, + hf_processor_mm_kwargs={}, + tokenization_kwargs={}, + ) + mm_kwargs = MultiModalKwargsItems.from_hf_inputs( + processed, proc._get_mm_fields_config(processed, {}) + ) + updates = proc._get_mm_prompt_updates(items, {}, mm_kwargs) + return proc._maybe_apply_prompt_updates( + mm_items=items, + prompt_ids=prompt_ids, + mm_kwargs=mm_kwargs, + mm_prompt_updates=updates, + is_update_applied=is_update_applied, + ) + + def test_marker_becomes_transcript_ids(self, monkeypatch): + proc, items = self._proc(monkeypatch) + new_ids, placeholders = self._apply_uncached(proc, items, proc_mod.AUDIO_MARKER) + # Marker replaced, not merely searched for. + assert new_ids == _TRANSCRIPT_IDS + assert _MARKER_ID not in new_ids + # And the placeholder range points at the transcript. + (ph,) = placeholders["audio"] + assert (ph.start_idx, ph.length) == (0, len(_TRANSCRIPT_IDS)) + + def test_transcript_spliced_between_surrounding_text(self, monkeypatch): + proc, items = self._proc(monkeypatch) + prompt = proc_mod.AUDIO_MARKER + "Q" + new_ids, placeholders = self._apply_uncached(proc, items, prompt) + assert new_ids == [*_TRANSCRIPT_IDS, ord("Q")] + (ph,) = placeholders["audio"] + assert (ph.start_idx, ph.length) == (0, len(_TRANSCRIPT_IDS)) + + def test_silent_clip_still_yields_a_placeholder(self, monkeypatch): + # The other half of the same invariant: a clip with no speech in it used + # to produce a zero-length replacement, which vLLM discards and then + # rejects the request over. The fallback text keeps the span findable. + proc, items = self._proc(monkeypatch, text="") + new_ids, placeholders = self._apply_uncached(proc, items, proc_mod.AUDIO_MARKER) + assert new_ids == [ord(proc_mod._EMPTY_TRANSCRIPT_TEXT)] + (ph,) = placeholders["audio"] + assert (ph.start_idx, ph.length) == (0, 1) + + def test_silent_clip_among_speech_clips(self, monkeypatch): + proc, items = self._proc(monkeypatch, count=2, text="") + new_ids, placeholders = self._apply_uncached( + proc, items, proc_mod.AUDIO_MARKER * 2 + ) + assert new_ids == [ord(proc_mod._EMPTY_TRANSCRIPT_TEXT)] * 2 + # Both items keep their own placeholder; neither collapses into the other. + assert len(placeholders["audio"]) == 2 + assert [p.length for p in placeholders["audio"]] == [1, 1] + + def test_two_clips_each_get_their_own_placeholder(self, monkeypatch): + proc, items = self._proc(monkeypatch, count=2) + new_ids, placeholders = self._apply_uncached( + proc, items, proc_mod.AUDIO_MARKER * 2 + ) + assert new_ids == _TRANSCRIPT_IDS * 2 + # One placeholder per audio item, or _validate_mm_placeholders raises. + assert len(placeholders["audio"]) == 2 + + +class TestClipCeiling: + """get_supported_mm_limits publishes asr_max_audio_clips as the clip ceiling.""" + + def test_ceiling_equals_configured_max(self): + assert _make_info(asr_enabled=True).get_supported_mm_limits() == {"audio": 32} + assert _make_info( + asr_enabled=True, asr_max_audio_clips=1 + ).get_supported_mm_limits() == {"audio": 1} + assert _make_info( + asr_enabled=True, asr_max_audio_clips=8 + ).get_supported_mm_limits() == {"audio": 8} + + def test_per_item_bound_never_exceeds_context(self): + info = _make_info(asr_enabled=True, asr_max_audio_clips=32) + seq_len = 4096 + for count in (1, 2, 8, 32): + bound = info.get_mm_max_tokens_per_item(seq_len, {"audio": count})["audio"] + assert bound == seq_len // count + assert bound <= seq_len diff --git a/tutorials/notebooks/granite_speech_demo.ipynb b/tutorials/notebooks/granite_speech_demo.ipynb index 15a467e..c9fd1ea 100644 --- a/tutorials/notebooks/granite_speech_demo.ipynb +++ b/tutorials/notebooks/granite_speech_demo.ipynb @@ -4,35 +4,39 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Granite Speech Demo — full stack in Colab\n", + "# Granite Speech Demo — full stack in Colab (single audio model)\n", "\n", - "Spin up a real-time, validated voice assistant powered by IBM Granite 4.1 — entirely inside a Colab notebook. One cell brings up both vLLM model servers (Granite Speech 4.1 STT + Granite Switch 4.1 LLM), the Pipecat backend, and the Next.js frontend, then prints a public URL you open in your browser to start talking.\n", + "Spin up a real-time voice assistant powered by IBM Granite 4.1 — entirely inside a Colab notebook. One cell brings up a **single** vLLM server (an audio-enabled Granite Switch checkpoint), the Pipecat backend, and the Next.js frontend, then prints a public URL you open in your browser to start talking.\n", "\n", - "**Browser mic → WebRTC → Granite Speech STT → Mellea/Granite Switch LLM → Kokoro TTS → browser speaker.**\n", + "**Browser mic → WebRTC → Granite Switch (audio model) → Kokoro TTS → browser speaker.**\n", "\n", - "This notebook is a runnable companion to the [granite-speech-demo](https://github.com/generative-computing/mellea-demos/tree/main/2026-granite-speech) reference implementation.\n", + "This is the **single-model** variant of the [granite-speech-demo](https://github.com/generative-computing/mellea-demos/tree/main/2026-granite-speech) reference implementation (backend on the `asr` branch).\n", "\n", "## What this demo is\n", "\n", - "One WebRTC conversation in which every layer of the Granite 4.1 release does something load-bearing: **Granite Speech 4.1** transcribes the audio (with keyword biasing for terms like \"Granite\" and \"Mellea\"); **Granite Switch 4.1** answers, hot-swapping LoRA adapters from inside a single checkpoint via control tokens; the **Granite Libraries** — twelve task-specific adapters spanning Core (explainability and validation), RAG, and Guardian (safety) — score and shape each response, with this demo using `requirement_check` to validate candidates against plain-English requirements (\"no markdown\", \"natural spoken cadence\", \"relevant to IBM\", \"no code\"); **Mellea** orchestrates the turn with its Instruct-Validate-Repair pattern, generating Best-of-N candidates in parallel and only sending one that passes every check to TTS. Validation is on by default, with a UI toggle for plain streaming if you want to feel the latency difference.\n", + "One WebRTC conversation served by **one model**. The audio-enabled **Granite Switch** checkpoint takes the user's speech directly: it transcribes the audio internally (a small ASR model embedded in the same vLLM process) and generates the spoken answer in a single request — no separate speech-to-text server, no extra orchestration hop. The response streams token-by-token into Kokoro TTS so the assistant starts speaking before the full answer is generated.\n", + "\n", + "> This variant trades the previous two-model setup (separate Granite Speech STT + a Mellea-orchestrated Best-of-N/validation LLM) for the simplest possible path: **audio in → one model → answer out.** It showcases that Granite Switch can accept audio as a single deployable model.\n", "\n", "## Prerequisites\n", "\n", - "- **GPU runtime: A100 (Colab Pro) required.** Smaller GPUs won't have enough VRAM to hold both Granite models simultaneously.\n", - "- **HuggingFace read token.** Free; create one at https://huggingface.co/settings/tokens. Add it as a Colab Secret named `HF_TOKEN` (sidebar → 🔑 → New secret). Used for two things: downloading the Granite model weights, *and* minting per-session WebRTC TURN credentials so audio reaches your browser.\n", + "- **GPU runtime with ~16+ GiB free** (e.g. Colab A100/L4). The audio model = the LLM weights + a small embedded Whisper.\n", + "- **A composed, audio-enabled checkpoint.** Our audio model isn't on the Hub — compose it once with `--enable-audio` and point `MODEL_PATH` at it (see the configuration cell).\n", + "- **HuggingFace read token.** Free; create one at https://huggingface.co/settings/tokens. Add it as a Colab Secret named `HF_TOKEN`. Used for downloading model weights *and* minting per-session WebRTC TURN credentials.\n", "- **Browser:** Chrome, Edge, or Firefox. Safari may behave oddly with WebRTC.\n", "\n", "## How long this takes\n", "\n", - "- **First run on a fresh runtime: ~8–10 min** (model downloads dominate).\n", + "- **First run on a fresh runtime: ~6–8 min** (model download/compose dominates).\n", "- **Subsequent runs with weights cached: ~3 min.**\n", "\n", "## What to do\n", "\n", "1. Set the `HF_TOKEN` Colab Secret.\n", - "2. Switch the runtime to an A100 GPU (Runtime → Change runtime type → A100).\n", - "3. **Runtime → Run all.**\n", - "4. When the last cell prints a `*.trycloudflare.com` URL, open it, allow mic access, and start talking.\n", + "2. Switch the runtime to a GPU (Runtime → Change runtime type).\n", + "3. Make sure `MODEL_PATH` (configuration cell) points at your composed audio checkpoint.\n", + "4. **Runtime → Run all.**\n", + "5. When the last cell prints a `*.trycloudflare.com` URL, open it, allow mic access, and start talking.\n", "\n", "If anything goes wrong, scroll to the bottom — there's a troubleshooting section and a kill-switch cell." ] @@ -71,7 +75,9 @@ "sh(\"curl -fsSL https://deb.nodesource.com/setup_20.x | bash -\")\n", "sh(\"apt-get -qq install -y nodejs\")\n", "\n", - "sh(\"git clone https://github.com/generative-computing/mellea-demos\")\n", + "# The `asr` branch (on this fork) carries the single-model voice pipeline\n", + "# (audio -> answer in one request; no separate STT, no Mellea stage).\n", + "sh(\"git clone -b asr https://github.com/aviv1ron1/mellea-demos\")\n", "os.chdir(\"mellea-demos/2026-granite-speech\")\n", "print(\"cwd:\", os.getcwd())\n", "\n", @@ -83,39 +89,24 @@ "VENV_PY = os.path.abspath(\".venv/bin/python\")\n", "assert os.path.exists(VENV_PY), f\"venv missing: {VENV_PY}\"\n", "\n", - "# The install order below is load-bearing. Each step's pins can override\n", - "# the previous step's resolution; the final order leaves us with:\n", - "# - mellea 0.6.0 (provides register_embedded_adapter_model, missing in 0.4.2)\n", - "# - vllm 0.19.x with audio deps (Granite Speech needs librosa + soundfile)\n", - "# - granite_switch model architecture registered\n", - "# - transformers 5.5.1 (older versions truncate the requirement_check JSON;\n", - "# newer versions might or might not, so pin exactly what we tested)\n", - "\n", - "# 1. mellea 0.6.0 (0.4.2 release lacks APIs the demo uses)\n", + "# 1. mellea 0.6.0 — still installed (the package imports it elsewhere), but the\n", + "# single-model voice path no longer uses the Mellea LLM stage.\n", "sh(f\"uv pip install --python {VENV_PY} 'mellea[all]==0.6.0'\")\n", "\n", - "# 2. vllm + the right transformers floor + granite_switch model registration.\n", - "# The granite-switch repo's [vllm] extra pins vllm >=0.19.1,<0.20.0 and\n", - "# transformers >=5.5.1 — installing plain `pip install vllm` gives 0.21.0\n", - "# with an older transformers, which fails to recognize the architecture.\n", + "# 2. vllm + the right transformers floor + granite_switch model registration\n", + "# (the audio-enabled GraniteSwitch architecture + its ASR processor live here).\n", "sh(\n", - " \"git clone https://github.com/generative-computing/granite-switch /tmp/granite-switch\"\n", + " \"git clone -b asr-switch https://github.com/generative-computing/granite-switch /tmp/granite-switch\"\n", ")\n", "assert os.path.exists(\"/tmp/granite-switch/pyproject.toml\"), (\n", " \"granite-switch clone failed\"\n", ")\n", - "sh(f\"uv pip install --python {VENV_PY} -e '/tmp/granite-switch[vllm]'\")\n", + "sh(f\"uv pip install --python {VENV_PY} -e '/tmp/granite-switch[vllm,audio]'\")\n", "\n", - "# 3. vllm audio deps. We install librosa + soundfile directly instead of\n", - "# relying on `vllm[audio]` — uv sees vllm as already satisfied from step 2\n", - "# and skips re-resolving the [audio] extras, leaving librosa missing.\n", - "# Without these, /v1/chat/completions returns 500 with\n", - "# 'Please install vllm[audio] for audio support' on any audio input.\n", + "# 3. vllm audio deps (the ASR cascade needs librosa + soundfile to decode audio).\n", "sh(f\"uv pip install --python {VENV_PY} librosa soundfile\")\n", "\n", - "# 4. Final transformers pin. The earlier installs can leave us on 4.57.6\n", - "# (GPT2 tokenizer crashes on Granite Switch) or 5.0.0 (works for chat\n", - "# but truncates requirement_check JSON output). 5.5.1 is what we tested.\n", + "# 4. Final transformers pin (5.5.1 is what we tested against Granite Switch).\n", "sh(f\"uv pip install --python {VENV_PY} 'transformers==5.5.1'\")\n", "\n", "sh(\"cd frontend && npm install --silent\")\n", @@ -131,9 +122,6 @@ " f'{VENV_PY} -c \\'import transformers; v = transformers.__version__; assert v == \"5.5.1\", \"got \" + v + \", wanted 5.5.1\"; print(\"transformers OK:\", v)\\''\n", ")\n", "sh(\n", - " f'{VENV_PY} -c \\'from mellea.backends.openai import OpenAIBackend; assert hasattr(OpenAIBackend, \"register_embedded_adapter_model\"), \"mellea version too old\"; print(\"mellea OK\")\\''\n", - ")\n", - "sh(\n", " f'{VENV_PY} -c \\'import librosa, soundfile; print(\"vllm audio deps OK (librosa\", librosa.__version__, \"/ soundfile\", soundfile.__version__, \")\")\\''\n", ")\n", "\n", @@ -187,26 +175,29 @@ "source": [ "import os\n", "\n", - "# Edit these to point at your own prompt or docs.\n", - "# Both paths are resolved relative to the project root if not absolute.\n", + "# Path to the COMPOSED, audio-enabled Granite Switch checkpoint (the single\n", + "# model this demo serves). Built by patching granite-switch-4.1-3b-preview with\n", + "# the <|audio|> token + audio chat-template + asr_enabled config, and saved to\n", + "# COS — available on Vela at the mount below.\n", + "# (On Colab instead, point this at a local path you composed/uploaded.)\n", + "os.environ[\"MODEL_PATH\"] = \"/danieloh_cos/avivron/granite-switch/granite-switch-audio\"\n", + "\n", + "# System prompt for the assistant (sent with each audio request).\n", "os.environ[\"PROMPT_FILE\"] = \"prompts/granite.txt\"\n", - "os.environ[\"DOCUMENTS_DIR\"] = \"docs\"\n", "\n", - "print(f\"PROMPT_FILE = {os.environ['PROMPT_FILE']}\")\n", - "print(f\"DOCUMENTS_DIR = {os.environ['DOCUMENTS_DIR']}\")" + "print(f\"MODEL_PATH = {os.environ['MODEL_PATH']}\")\n", + "print(f\"PROMPT_FILE = {os.environ['PROMPT_FILE']}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## 4 · Launch vLLM model servers (~5-8 min cold, ~30s cached)\n", + "## 4 · Launch the vLLM model server (~2-4 min cold, ~30s cached)\n", "\n", - "Two vLLM processes:\n", - "- **Port 8083:** [`ibm-granite/granite-speech-4.1-2b`](https://huggingface.co/ibm-granite/granite-speech-4.1-2b) — STT.\n", - "- **Port 8000:** [`ibm-granite/granite-switch-4.1-3b-preview`](https://huggingface.co/ibm-granite/granite-switch-4.1-3b-preview) — chat LLM with `requirement_check` ALoRA intrinsics.\n", + "**One** vLLM process — your composed, audio-enabled **Granite Switch** checkpoint (`MODEL_PATH`) on **port 8000**. It transcribes the incoming audio *internally* (Whisper, on the `asr_device` baked into the checkpoint) and generates the answer in the same request, so there's no separate STT server.\n", "\n", - "Both run in the background; logs stream to `logs/vllm-*.log`. The cell blocks until both servers respond on `/v1/models`." + "Runs in the background; logs stream to `logs/vllm-audio.log`. The cell blocks until the server responds on `/v1/models`." ] }, { @@ -225,8 +216,14 @@ "VENV_VLLM = os.path.abspath(\".venv/bin/vllm\")\n", "assert os.path.exists(VENV_VLLM), f\"vllm not installed in venv: {VENV_VLLM}\"\n", "\n", - "# Pre-flight: kill any stale vllm processes from a prior failed run, then\n", - "# verify the GPU has enough free memory before we try again.\n", + "MODEL_PATH = os.environ.get(\"MODEL_PATH\", \"/content/granite-switch-audio\")\n", + "SERVED_NAME = \"granite-switch-audio\"\n", + "assert os.path.exists(os.path.join(MODEL_PATH, \"config.json\")), (\n", + " f\"No composed audio checkpoint at {MODEL_PATH}. Compose one with --enable-audio \"\n", + " \"first (see the configuration cell above).\"\n", + ")\n", + "\n", + "# Pre-flight: kill any stale vllm processes, verify enough free GPU memory.\n", "subprocess.run(\"pkill -9 -f vllm || true\", shell=True)\n", "time.sleep(3)\n", "free_mem = (\n", @@ -239,10 +236,10 @@ ")\n", "free_gib = int(free_mem) / 1024\n", "print(f\"GPU free memory: {free_gib:.1f} GiB\")\n", - "if free_gib < 22:\n", + "if free_gib < 16:\n", " raise RuntimeError(\n", - " f\"Only {free_gib:.1f} GiB free on the GPU — need >=22. Something else is using it.\\n\"\n", - " \"Run `!nvidia-smi` in a new cell to see which process. Kill it with `!kill -9 `.\"\n", + " f\"Only {free_gib:.1f} GiB free on the GPU — need >=16 for the audio model \"\n", + " \"(LLM + Whisper). Free the GPU (kill-switch cell) and retry.\"\n", " )\n", "\n", "\n", @@ -257,7 +254,7 @@ "def wait_for(\n", " url: str, name: str, proc: subprocess.Popen, log_path: str, timeout: int = 1200\n", ") -> None:\n", - " \"\"\"Poll until the URL returns 2xx. Bails out early if the process dies.\"\"\"\n", + " \"\"\"Poll until the URL responds. Bails out early if the process dies.\"\"\"\n", " start = time.time()\n", " last_err = None\n", " while time.time() - start < timeout:\n", @@ -273,16 +270,10 @@ " try:\n", " with urllib.request.urlopen(url, timeout=5) as r:\n", " if 200 <= r.status < 300:\n", - " elapsed = int(time.time() - start)\n", - " print(f\"✅ {name} ready ({elapsed}s)\")\n", + " print(f\"✅ {name} ready ({int(time.time() - start)}s)\")\n", " return\n", " except urllib.error.HTTPError as e:\n", - " # vllm returns 401 to unauth'd /v1/models polls when --api-key is set.\n", - " # The 401 proves the server is up and accepting requests, which is\n", - " # all we care about for readiness. Any HTTPError means the server\n", - " # is responding, so treat it as ready.\n", - " elapsed = int(time.time() - start)\n", - " print(f\"✅ {name} ready ({elapsed}s, status {e.code})\")\n", + " print(f\"✅ {name} ready ({int(time.time() - start)}s, status {e.code})\")\n", " return\n", " except (urllib.error.URLError, ConnectionError, TimeoutError) as e:\n", " last_err = e\n", @@ -293,65 +284,38 @@ " )\n", "\n", "\n", - "# Launch SEQUENTIALLY — wait for each to fully initialize before starting the next.\n", - "# Parallel launch causes vllm's memory-profiling assertion to fire because\n", - "# both processes are allocating/freeing GPU memory at the same time and each\n", - "# sees the other's churn as 'unexpected' free-memory deltas.\n", - "speech_log = open(\"logs/vllm-speech.log\", \"w\")\n", - "print(\"⏳ Starting Granite Speech vLLM (downloads weights on first run, ~4 min)...\")\n", - "speech_proc = subprocess.Popen(\n", - " [\n", - " VENV_VLLM,\n", - " \"serve\",\n", - " \"ibm-granite/granite-speech-4.1-2b\",\n", - " \"--api-key\",\n", - " \"token-abc123\",\n", - " \"--max-model-len\",\n", - " \"2048\",\n", - " \"--gpu-memory-utilization\",\n", - " \"0.4\",\n", - " \"--port\",\n", - " \"8083\",\n", - " ],\n", - " stdout=speech_log,\n", - " stderr=subprocess.STDOUT,\n", - ")\n", - "wait_for(\n", - " \"http://127.0.0.1:8083/v1/models\",\n", - " \"Granite Speech (STT)\",\n", - " speech_proc,\n", - " \"logs/vllm-speech.log\",\n", - " timeout=1200,\n", - ")\n", - "\n", - "switch_log = open(\"logs/vllm-switch.log\", \"w\")\n", - "print(\"⏳ Starting Granite Switch vLLM (downloads weights on first run, ~4 min)...\")\n", + "# ONE server now: the audio-enabled Granite Switch model. It transcribes the\n", + "# incoming audio internally (Whisper on the asr_device baked into the checkpoint\n", + "# config) and generates the answer — so there's no separate STT server.\n", + "audio_log = open(\"logs/vllm-audio.log\", \"w\")\n", + "print(\"⏳ Starting Granite Switch (audio) vLLM (loads weights + Whisper, ~2-4 min)...\")\n", "switch_proc = subprocess.Popen(\n", " [\n", " VENV_VLLM,\n", " \"serve\",\n", - " \"ibm-granite/granite-switch-4.1-3b-preview\",\n", + " MODEL_PATH,\n", + " \"--served-model-name\",\n", + " SERVED_NAME,\n", + " # One model now, so it can use more of the GPU than the old 0.4 split.\n", " \"--gpu-memory-utilization\",\n", - " \"0.4\",\n", - " # Cap context window so KV cache fits in our 0.4 GPU share. The default\n", - " # 131072 wants ~15 GiB of KV cache; voice turns need a tiny fraction of that.\n", + " \"0.6\",\n", " \"--max-model-len\",\n", " \"8192\",\n", " \"--port\",\n", " \"8000\",\n", " ],\n", - " stdout=switch_log,\n", + " stdout=audio_log,\n", " stderr=subprocess.STDOUT,\n", ")\n", "wait_for(\n", " \"http://127.0.0.1:8000/v1/models\",\n", - " \"Granite Switch (LLM)\",\n", + " \"Granite Switch (audio)\",\n", " switch_proc,\n", - " \"logs/vllm-switch.log\",\n", + " \"logs/vllm-audio.log\",\n", " timeout=1200,\n", ")\n", "\n", - "print(\"✅ Both vLLM servers are up\")" + "print(\"✅ Audio model server is up (one model — STT is built in)\")" ] }, { @@ -392,8 +356,12 @@ "backend_env = {**os.environ}\n", "backend_env.setdefault(\"HOST\", \"127.0.0.1\")\n", "backend_env.setdefault(\"PORT\", \"7860\")\n", - "# PROMPT_FILE and DOCUMENTS_DIR are set in the configuration cell above and\n", - "# inherited via os.environ.\n", + "# Point the backend's single audio service at our one model server. The\n", + "# AudioLLMService sends the user's audio (input_audio) straight here and streams\n", + "# the answer — no separate STT endpoint.\n", + "backend_env[\"LLM_URL\"] = \"http://127.0.0.1:8000/v1\"\n", + "backend_env[\"LLM_MODEL\"] = \"granite-switch-audio\"\n", + "# PROMPT_FILE is set in the configuration cell above and inherited via os.environ.\n", "\n", "backend_log = open(\"logs/backend.log\", \"w\")\n", "backend_proc = subprocess.Popen(\n", diff --git a/uv.lock b/uv.lock index d9a28e5..b5f69d9 100644 --- a/uv.lock +++ b/uv.lock @@ -3,26 +3,26 @@ revision = 3 requires-python = ">=3.11, <3.14" resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", @@ -171,20 +171,20 @@ resolution-markers = [ "python_full_version == '3.12.*' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", @@ -233,7 +233,8 @@ resolution-markers = [ "python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", ] conflicts = [[ { package = "granite-switch", group = "vllm19" }, @@ -511,6 +512,59 @@ 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 = "audioop-lts" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, +] + +[[package]] +name = "audioread" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "standard-aifc", marker = "python_full_version >= '3.13'" }, + { name = "standard-sunau", marker = "python_full_version >= '3.13'" }, +] +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 = "bcrypt" version = "5.0.0" @@ -902,56 +956,56 @@ wheels = [ [[package]] name = "coverage" -version = "7.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/23/82e910835ef4b8391047025e1d53aa48d66029f444eb8b25373c849bf503/coverage-7.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:003fff99412ea848c0aaebcc78ed2b6ce7d8a1227ed17e68470672770b78a02a", size = 220662, upload-time = "2026-07-02T13:08:39.205Z" }, - { url = "https://files.pythonhosted.org/packages/6d/0d/c7b213dde2f1579de5231062b386d8413f79c11667eb58c39319b25991da/coverage-7.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cbd804bf2784ce7b45114516050f346ecd50f960c4bb630a7ee9e1d78fa2118", size = 221168, upload-time = "2026-07-02T13:08:40.471Z" }, - { url = "https://files.pythonhosted.org/packages/33/77/d000aeedfac085088337b3c7becdad328474b1f8a9e4c9368a0c99605d68/coverage-7.15.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8773e15c23305b58882a4611fb9b2755977eae0dc2e515366a1b6c98866cc4c2", size = 251587, upload-time = "2026-07-02T13:08:42.033Z" }, - { url = "https://files.pythonhosted.org/packages/cc/e0/86787c56b9df17afd370d5e293515dd4d9a107a561d13054873eefad8ecc/coverage-7.15.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f50e40081494c1dc4239ebb202014cbcc3306ea96fb6302a34c8cc0967fc5ae8", size = 253497, upload-time = "2026-07-02T13:08:43.387Z" }, - { url = "https://files.pythonhosted.org/packages/3f/02/181bc917359299c07dead6270f94e411151c8b60cec905c33499da69afe6/coverage-7.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daf96f37f5fc3a7b6c6da862eb4aee61c426bd63da236ed4a73ef0e503b4bca5", size = 255607, upload-time = "2026-07-02T13:08:44.897Z" }, - { url = "https://files.pythonhosted.org/packages/b9/35/ca5e7427699913da6788c4f910e73ab16c5f4b59ec5d3a999dce2a45112f/coverage-7.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51aa20f6ae2788fd197747766edf4cd8234fd9423309b934257fa6b21a592723", size = 257563, upload-time = "2026-07-02T13:08:46.334Z" }, - { url = "https://files.pythonhosted.org/packages/0b/4d/b8220bacc2fc3c4e9078e27c32e99fb411479a4718a72bdd00036a9891c8/coverage-7.15.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03d1f922757662eb7af586e77834792274cff776bc7b1d1a0b66a49ea9d84735", size = 251726, upload-time = "2026-07-02T13:08:47.941Z" }, - { url = "https://files.pythonhosted.org/packages/c4/e4/2e145da1991d72189b9c3cf7eca05c716ee7080d099aaea6757cfc7df008/coverage-7.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a6d6acc9a7666245e6133dd15144ca038a85a9cd5026bb06d6bbae9e77440dc9", size = 253301, upload-time = "2026-07-02T13:08:49.5Z" }, - { url = "https://files.pythonhosted.org/packages/72/28/d2c841d698bf762e481f08bd4839d370246b6d9b61dab085a7b20b201a08/coverage-7.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ac2c4c27c7df851dc9a017c2d7de00b69147e84ba3d96f37a530b0b6fb51035", size = 251361, upload-time = "2026-07-02T13:08:51.304Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ed/55d9ffde994fba3897c0c783f77a7d053b0c18787f6892ed5b0aed73f469/coverage-7.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b761a1d504fd4bd1f20f418753964dca9f5862a511fc854dac58296b3b223671", size = 255129, upload-time = "2026-07-02T13:08:52.661Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c0/ecbf33b8c460ea2718aeb813e2df8140d0370e5f67261c31524ceb0a2a8d/coverage-7.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e43b045e11c16e897895758ae90e4a90cf99e93d58549e2f90c0e2272e155695", size = 251081, upload-time = "2026-07-02T13:08:54.188Z" }, - { url = "https://files.pythonhosted.org/packages/a9/de/fb87b4261f54448dd2b9504ef19a58be42cef0d9520595fbfe1219b15234/coverage-7.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589b54513e901739f4b4582c705ce96b80c96f57641b1464607e2367a270e540", size = 251988, upload-time = "2026-07-02T13:08:55.726Z" }, - { url = "https://files.pythonhosted.org/packages/df/27/3494d5f291b9a4cb868f73c11221a8bd2d5bd761a8f9acea61ff57128dd1/coverage-7.15.0-cp311-cp311-win32.whl", hash = "sha256:106781b8482749162d0b47056937ba0933508e5d9447f65a5e7d5c422f0d6bb4", size = 222754, upload-time = "2026-07-02T13:08:57.091Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ee/cd4847ebc9be6a9c0123d763645a6f1f3be6b8c58c962706368b79cbac07/coverage-7.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:821e92b3631d762a339695824cadbbc73020354eba2a23a551a99ad34938fbe6", size = 223225, upload-time = "2026-07-02T13:08:58.594Z" }, - { url = "https://files.pythonhosted.org/packages/57/37/5011581aa7f2be498b97dcc7c9902192442a42f4f9a748aeadb3d6506b42/coverage-7.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:309990eb5fb8014b9f67cb211f7fd41876ec8a88a88d3ae76de0ed1d611e3640", size = 222774, upload-time = "2026-07-02T13:09:00.074Z" }, - { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, - { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, - { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, - { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, - { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, - { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, - { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, - { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, - { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, - { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, - { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, - { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, - { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, - { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, - { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, - { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, - { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, - { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, - { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, - { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, - { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, - { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, - { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, - { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, - { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/3a/54536704f507d4573bf9161c4d0dd3dd59b6d85e48c664e901b6844d8e33/coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036", size = 221414, upload-time = "2026-07-15T18:53:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d9/8ba925d29743e3577b21e4d8c11a702b76bc93c41e7fdfd1177af63d4b8d/coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660", size = 221913, upload-time = "2026-07-15T18:53:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/a855f3aa0187f2b431ade4e4791b77b56282cfb5d201c83ec26a31b5b36a/coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589", size = 252332, upload-time = "2026-07-15T18:53:55.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d3/13ac97b4370640ba3452fc8559b06cc2f479ce3ba4a0b632a73e44c38a7d/coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee", size = 254243, upload-time = "2026-07-15T18:53:57.055Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/5eca144942d8d0659d3f55176517f4a59cdc65eefd17146a0770935a3ebd/coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0", size = 256352, upload-time = "2026-07-15T18:53:58.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ba/d3db2e01a50fc88cdb4c0f19542bcf6f61489e34dc9aa3538413e2459a38/coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487", size = 258313, upload-time = "2026-07-15T18:54:00.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/b3/aba83416e9177df28e5186d856c19158c59fc0e7e814aaa61a4a2354ad1b/coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f", size = 252449, upload-time = "2026-07-15T18:54:02.456Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a5/4b00ecac0194431ab451b0f6710f8e2517d04cef60f821b14dec4637d575/coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1", size = 254043, upload-time = "2026-07-15T18:54:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/75/b6/cfa209b4313ee7f1b34da47efcd789ea51c024ad35af390e00f5a3c10a2e/coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5", size = 252107, upload-time = "2026-07-15T18:54:06.745Z" }, + { url = "https://files.pythonhosted.org/packages/36/67/e8cac5a6954038c98d7fe7eb9802afe7ab3ecb637bb7cc00e69b4148b56d/coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0", size = 255873, upload-time = "2026-07-15T18:54:08.48Z" }, + { url = "https://files.pythonhosted.org/packages/2c/92/395cca9f330a86c3fe3471d73e2c102116c4c58fdc619dbbc125c6e93a54/coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad", size = 251826, upload-time = "2026-07-15T18:54:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/51/60/3e91b20295439652424f426b7086ec5bf4fbe3f604c73eda22b986c4fd6b/coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db", size = 252735, upload-time = "2026-07-15T18:54:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/8c07839005e5e3c6b3877d3a6e2a80ce766589f31dd2b6882b78d59a7b8c/coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9", size = 223500, upload-time = "2026-07-15T18:54:13.525Z" }, + { url = "https://files.pythonhosted.org/packages/2e/98/59d83c257cd59f0fbaf9d9ddb26b744a576760dfd1ae16e516408894a02b/coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688", size = 223973, upload-time = "2026-07-15T18:54:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/2d285c8bef5c4f695d120c1c96dc11715638aa8e134069f210bb6a62a9fe/coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934", size = 223519, upload-time = "2026-07-15T18:54:16.803Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/eb5bf42e531611a9f8d272556b1ed4de503f84a91413584094487cf69f8f/coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", size = 221587, upload-time = "2026-07-15T18:54:18.439Z" }, + { url = "https://files.pythonhosted.org/packages/06/d1/da99af464c335d4e023a6efcd7ec30f63b88a43c93745154ab74ffb31cea/coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", size = 221943, upload-time = "2026-07-15T18:54:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8a/13c42723d61ca447eafa18732e8141dd6a63f2732e1c7e1502c182dd88d7/coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", size = 253450, upload-time = "2026-07-15T18:54:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/29/99021303f98fbdcb63504b4d07bea4cc025b9b2dd907c4f07c85d50a0dab/coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", size = 256187, upload-time = "2026-07-15T18:54:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a8/fd503715ed6ca9c5d742923aa5209257340b367a867b2ced0c7d4ba8a0b9/coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", size = 257301, upload-time = "2026-07-15T18:54:25.183Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/3f4b8fb409810036ebc2857d36adc0498c6e957b5df0290c5036b2e143f1/coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", size = 259562, upload-time = "2026-07-15T18:54:27.204Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8a/9bdffbef47db77cce3d6b02a28f7e919b19f0106c4b080c2c2246040f885/coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", size = 253841, upload-time = "2026-07-15T18:54:29.134Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/9031efde019d31a06646261fce6dfc5c3c74e951e27a71e5c9a424563178/coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", size = 255221, upload-time = "2026-07-15T18:54:31.142Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/787acde872389fc84a9ef9d8cd1ccc658e391ab4cb5b28092a714426a394/coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", size = 253366, upload-time = "2026-07-15T18:54:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9b/6f57bc4b93c842eef1695f8cdaf2318e35e7ba54f5ba80d84be213ab7858/coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", size = 257434, upload-time = "2026-07-15T18:54:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/b3186a21b2acc83e451118978905c81c7072c3333707804db09a78c096a2/coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", size = 252935, upload-time = "2026-07-15T18:54:36.548Z" }, + { url = "https://files.pythonhosted.org/packages/20/c2/c9f3376b2e717ea69ed7a6e9a5fcab968fb0b290db6cf4bd9a1fc7541b75/coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", size = 254807, upload-time = "2026-07-15T18:54:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e1/dfc15401f4a8aaeb486e1ba3e9e3c40522a6e38bd0ecf0b3f29cb8082957/coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", size = 223641, upload-time = "2026-07-15T18:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/91/40/81b6d809d320cd366ec5bdf8176575e897dcb8efe7fb4b489ef9e93e4d13/coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", size = 224172, upload-time = "2026-07-15T18:54:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/ef/28/9f14ec438149f7de557f45518f09b4a7917b795cc37083aa7db482693f8c/coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", size = 223556, upload-time = "2026-07-15T18:54:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, ] [package.optional-dependencies] @@ -1223,48 +1277,49 @@ version = "13.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", ] dependencies = [ { name = "cuda-pathfinder", marker = "extra == 'extra-14-granite-switch-vllm20' or extra == 'group-14-granite-switch-dev-vllm20' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19')" }, @@ -1497,8 +1552,8 @@ version = "13.2.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version >= '3.13' and sys_platform != 'darwin'", + "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 == 'darwin'", "python_full_version < '3.12' and sys_platform != 'darwin'", @@ -2070,8 +2125,8 @@ version = "0.6.8.post1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version >= '3.13' and sys_platform != 'darwin'", + "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 == 'darwin'", "python_full_version < '3.12' and sys_platform != 'darwin'", @@ -2301,8 +2356,8 @@ version = "0.6.8.post1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version >= '3.13' and sys_platform != 'darwin'", + "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 == 'darwin'", "python_full_version < '3.12' and sys_platform != 'darwin'", @@ -2464,6 +2519,10 @@ dependencies = [ ] [package.optional-dependencies] +audio = [ + { name = "librosa" }, + { name = "soundfile" }, +] build = [ { name = "huggingface-hub" }, { name = "pyyaml" }, @@ -2505,20 +2564,20 @@ vllm20 = [ [package.dev-dependencies] dev = [ - { name = "granite-switch", extra = ["compose", "hf"], marker = "extra == 'group-14-granite-switch-dev' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "granite-switch", extra = ["audio", "compose", "hf"], marker = "extra == 'group-14-granite-switch-dev' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "vllm", version = "0.19.1", source = { registry = "https://pypi.org/simple" } }, ] dev-vllm20 = [ - { name = "granite-switch", extra = ["compose", "hf"], marker = "extra == 'group-14-granite-switch-dev-vllm20' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "granite-switch", extra = ["audio", "compose", "hf"], marker = "extra == 'group-14-granite-switch-dev-vllm20' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "vllm", version = "0.20.2", source = { registry = "https://pypi.org/simple" } }, ] test = [ { name = "bitsandbytes" }, - { name = "granite-switch", extra = ["compose", "hf"], marker = "extra == 'group-14-granite-switch-test' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "granite-switch", extra = ["audio", "compose", "hf"], marker = "extra == 'group-14-granite-switch-test' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, { name = "optimum-quanto" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -2541,6 +2600,7 @@ requires-dist = [ { name = "huggingface-hub", marker = "extra == 'build'" }, { name = "huggingface-hub", marker = "extra == 'compose'" }, { name = "ipython", marker = "extra == 'tutorials'", specifier = ">=8.10.0" }, + { name = "librosa", marker = "extra == 'audio'" }, { name = "mellea", marker = "extra == 'tutorials'", specifier = "==0.6.0" }, { name = "python-dotenv", marker = "extra == 'tutorials'", specifier = ">=1.0.0" }, { name = "pyyaml", marker = "extra == 'build'" }, @@ -2550,6 +2610,7 @@ requires-dist = [ { name = "safetensors", marker = "extra == 'build'" }, { name = "safetensors", marker = "extra == 'compose'" }, { name = "sentence-transformers", marker = "extra == 'tutorials'", specifier = ">=3.0.0" }, + { name = "soundfile", marker = "extra == 'audio'" }, { name = "torch", specifier = ">=2.10.0" }, { name = "tqdm", marker = "extra == 'build'" }, { name = "tqdm", marker = "extra == 'compose'" }, @@ -2557,24 +2618,24 @@ requires-dist = [ { name = "vllm", marker = "extra == 'vllm'", specifier = ">=0.19.1,<0.20.0" }, { name = "vllm", marker = "extra == 'vllm20'", specifier = ">=0.20.0,<0.21.0" }, ] -provides-extras = ["hf", "vllm", "vllm20", "compose", "build", "tutorials"] +provides-extras = ["hf", "vllm", "vllm20", "compose", "build", "audio", "tutorials"] [package.metadata.requires-dev] dev = [ - { name = "granite-switch", extras = ["hf", "compose"] }, + { name = "granite-switch", extras = ["hf", "compose", "audio"] }, { name = "pytest" }, { name = "pytest-cov" }, { name = "vllm", specifier = ">=0.19.1,<0.20.0" }, ] dev-vllm20 = [ - { name = "granite-switch", extras = ["hf", "compose"] }, + { name = "granite-switch", extras = ["hf", "compose", "audio"] }, { name = "pytest" }, { name = "pytest-cov" }, { name = "vllm", specifier = ">=0.20.0,<0.21.0" }, ] test = [ { name = "bitsandbytes" }, - { name = "granite-switch", extras = ["hf", "compose"] }, + { name = "granite-switch", extras = ["hf", "compose", "audio"] }, { name = "optimum-quanto" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -3046,6 +3107,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/61/f75cd1fa54d8434276126034aed54dd120747de9a8fa013cdd79545ccbeb/latex2sympy2_extended-1.11.0-py3-none-any.whl", hash = "sha256:aebb77d52ce269e25028e4bea89ddb14d242ba36bcf7b636496fb5fd9728d234", size = 209050, upload-time = "2026-01-10T01:43:19.458Z" }, ] +[[package]] +name = "lazy-loader" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, +] + +[[package]] +name = "librosa" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioread" }, + { name = "decorator" }, + { name = "joblib" }, + { name = "lazy-loader" }, + { name = "msgpack" }, + { name = "numba", version = "0.61.2", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, + { name = "numba", version = "0.65.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-vllm20' or extra == 'group-14-granite-switch-dev-vllm20' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20')" }, + { name = "pooch" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "soundfile" }, + { name = "soxr" }, + { name = "standard-aifc", marker = "python_full_version >= '3.13'" }, + { name = "standard-sunau", marker = "python_full_version >= '3.13'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/36/360b5aafa0238e29758729e9486c6ed92a6f37fa403b7875e06c115cdf4a/librosa-0.11.0.tar.gz", hash = "sha256:f5ed951ca189b375bbe2e33b2abd7e040ceeee302b9bbaeeffdfddb8d0ace908", size = 327001, upload-time = "2025-03-11T15:09:54.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/ba/c63c5786dfee4c3417094c4b00966e61e4a63efecee22cb7b4c0387dda83/librosa-0.11.0-py3-none-any.whl", hash = "sha256:0b6415c4fd68bff4c29288abe67c6d80b587e0e1e2cfb0aad23e4559504a7fa1", size = 260749, upload-time = "2025-03-11T15:09:52.982Z" }, +] + [[package]] name = "llguidance" version = "1.3.0" @@ -3280,12 +3382,50 @@ name = "llvmlite" version = "0.47.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and sys_platform != 'darwin'", - "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 != 'darwin'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", ] sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" } wheels = [ @@ -3631,6 +3771,47 @@ 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.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/23/6139781ca7aadf656fa8e384fa84693ffb13f299e6931b6526427fe5e297/msgpack-1.2.0.tar.gz", hash = "sha256:8e17af38197bf58e7e819041678f6178f4491493f5b8c8580414f40f7c2c3c41", size = 183017, upload-time = "2026-06-11T04:16:10.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/23/35de3182a647fcc84ab304160169edfa5dac7bbd8913fbed0a505ddc0d55/msgpack-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec35cd3f127f50806aa10c3f74bf27b749f13ddf1d2217964ada8f38042d1653", size = 82368, upload-time = "2026-06-11T04:14:53.57Z" }, + { url = "https://files.pythonhosted.org/packages/aa/79/8d9bfdab933b1c7a02aba9518605a81aa30d38e9efd4915ec1a6b2d55778/msgpack-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:317eb298297121bfad9173d748124a04a36af27b6ac39c2bbc1db1ce57608dcf", size = 82095, upload-time = "2026-06-11T04:14:54.784Z" }, + { url = "https://files.pythonhosted.org/packages/d2/e1/b5accbc1354edbcee107fb35ec247db0547e91c3f90e4fabdeaee500a5a6/msgpack-1.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50fe6434de89073273026dd032a62e8b63f8857a261d7a2df5b07c9e72f3a8f7", size = 413818, upload-time = "2026-06-11T04:14:56.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/31/1141cbbf7118d525834f20dcd614d1b85f1f2ffd33bc2a5ce710e6dd2516/msgpack-1.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106c6d333ff3d4eda075b7d4b9695d1752c5bcc635e40d0dbaf4e276c9ed80e1", size = 423790, upload-time = "2026-06-11T04:14:57.509Z" }, + { url = "https://files.pythonhosted.org/packages/04/e7/9582f2bd4d7546139fe297740de49bd1f7ef2d195eb0bb9fa5efeee88158/msgpack-1.2.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:67055a611e871cb1bd0acb732f2e9f64ca8155ca0bba1d0a5bb362e7209e5541", size = 387521, upload-time = "2026-06-11T04:14:59.08Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/5aadd08ff068bfd42e2ac0be6a20aa9819965df8622e87c1f0c6119c1c22/msgpack-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ceec7f8e633d5a4b4a32b0416bef90ee3cd1017ea36247f705e523072e576119", size = 406324, upload-time = "2026-06-11T04:15:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/39/ee/3041564f0cc4c2fe7c53315aec0edf3d84807fc9b9ea714e6ac07dbdb1db/msgpack-1.2.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7ec5851160a3c2c0f77d68ddec620318cd8e7d88d94f9c058190e8ce0dfa1d31", size = 384242, upload-time = "2026-06-11T04:15:02.121Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d4/de94b3dbc266229f4c2ce84485eeb221220351b7f1931029e875995bb232/msgpack-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dd7140f7b09dbe1984a0dff3189375d840247e3e4cf4ac45c5a499b3b599c8d2", size = 420392, upload-time = "2026-06-11T04:15:03.692Z" }, + { url = "https://files.pythonhosted.org/packages/f7/5d/c4a3fde69a292eecb202caaa87c29df7728644a65118614b821bcaddc05a/msgpack-1.2.0-cp311-cp311-win32.whl", hash = "sha256:cbfd54018d386da0951c7a2be13de0f58559d251313e613b2155e52ed1cbd8f1", size = 63976, upload-time = "2026-06-11T04:15:05.355Z" }, + { url = "https://files.pythonhosted.org/packages/18/fa/df47f83115375e7717c985265a30f3ba096c5331518e28fb647b55c46d31/msgpack-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:653373c4614c31463ba486a67776e4bb396af289921bd5353e209534b71467fa", size = 70273, upload-time = "2026-06-11T04:15:06.529Z" }, + { url = "https://files.pythonhosted.org/packages/54/d1/ffd02e54c064aa73b6b53aa08171f92dc406727077ff275d7050c6aca28a/msgpack-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:7a260aea1e5e7d6c7f1d9284c7360d29021627b61dc4dd7df144b81210810537", size = 64783, upload-time = "2026-06-11T04:15:07.677Z" }, + { url = "https://files.pythonhosted.org/packages/44/07/dcb13f37e670257c8d0e944f116c799c34ac6968ecb48c83619f7e91d8b5/msgpack-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e2d6047ccd11a12c96a69f2bfe026471abef67334c3d0494a93e5310e45140a2", size = 82888, upload-time = "2026-06-11T04:15:08.992Z" }, + { url = "https://files.pythonhosted.org/packages/84/5f/6643b2a6a36ca4bc73c7674831be1d4d581cceecc7eb019dba1915951739/msgpack-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0347e3ac0dfee99086d3b68fe959da3f5f657c0019ddbaeaaa259a85f8603422", size = 82223, upload-time = "2026-06-11T04:15:10.182Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c8/9e1668b9897358e5ab39a18142e38be3cf15807e643757782da9f4a53cb3/msgpack-1.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25552ff1f2ff3dc8333e27eabb94f702da5929ed0e07969688194a3e9f12e151", size = 409700, upload-time = "2026-06-11T04:15:11.441Z" }, + { url = "https://files.pythonhosted.org/packages/38/ed/b7728573156d70b6b094233b0f38d876fc37340826cf852347ec2c7ca8ca/msgpack-1.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0d94420d9d52c56568159a69200af7e45eadb29615fa9d09fada140de1c38c7", size = 420090, upload-time = "2026-06-11T04:15:12.868Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f7/5ea755a89868c04f9cdf6d96d2d99da4b3d198af10e76a6082dd0fceccc0/msgpack-1.2.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d16e1f2db4a9eebc07b7cc91898d71e710f2eed8358711a605fee802caff8923", size = 378538, upload-time = "2026-06-11T04:15:14.511Z" }, + { url = "https://files.pythonhosted.org/packages/80/2d/126e59332a439c94ffd682c38ca0102b23480e2784b3dac48d8959b0bbac/msgpack-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9cb2e700e85f1e27bbb5c9de6cc1c9a4bc5ac64d5404bdcbcb37a0dc7a947a3", size = 399468, upload-time = "2026-06-11T04:15:16.133Z" }, + { url = "https://files.pythonhosted.org/packages/da/f9/7abcef683a0ad2e5ab3a4940344aad9f20cdf1f42057ecb0982cf55085d6/msgpack-1.2.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:717d0b166dd176a5f786aeafff081f6439680acf5af193eb63e6266c12b04d3d", size = 374212, upload-time = "2026-06-11T04:15:17.536Z" }, + { url = "https://files.pythonhosted.org/packages/27/23/2d62cf0e971678e96f8a3cfa9bd77fb719ddb98da73790f63c53fd847ad8/msgpack-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e87c7a21654d18111eb1a89bd5c42baba42e61887365d9e89585e112b4203f9e", size = 414361, upload-time = "2026-06-11T04:15:18.99Z" }, + { url = "https://files.pythonhosted.org/packages/32/fb/f5c153f614037aaf802d291a4653ba1bb731f56feacba886f7c21c109e56/msgpack-1.2.0-cp312-cp312-win32.whl", hash = "sha256:967e0c891f5f23ab65762f2e5dc95922759c79f1ef99ef4c7e1fdd863e0d0af9", size = 64389, upload-time = "2026-06-11T04:15:20.237Z" }, + { url = "https://files.pythonhosted.org/packages/90/af/8aafce6e5544b43b84cb670aca40c8bea7eb5ae8f42bfcbdc7098739987a/msgpack-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:6c23e33cee28dcffa112ae205661da4636fd7b06bd9ad1559a890623b92d060b", size = 71185, upload-time = "2026-06-11T04:15:21.51Z" }, + { url = "https://files.pythonhosted.org/packages/ba/08/9cc94be1fc1fe3d1379d439326259aef0344274f64623a8138feb54dff68/msgpack-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:6eeb771571f63f68045433b1a35c0256b946f31ed62f006997e40b8ad8b735af", size = 64481, upload-time = "2026-06-11T04:15:22.639Z" }, + { url = "https://files.pythonhosted.org/packages/7d/26/2902c6946ab5c8fe1e46e40842dfc32b8824464ad5cd4725364fd83f7a58/msgpack-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3a1d30df1f302f2b7a7404afbac2ab76d510036c34cf34dffb01f704a7288e45", size = 82621, upload-time = "2026-06-11T04:15:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/c9/59/7e6b812629d2f919e586041bffc130e1af32079f71bb20699eed54ed6d92/msgpack-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:581e317112260d8ca488d490cad9290a5682276f309c41c7de237a85ed8799c8", size = 81866, upload-time = "2026-06-11T04:15:25.032Z" }, + { url = "https://files.pythonhosted.org/packages/31/13/8c291196e60aafdbae38f482205d79432297749ac5d412fe638154fb6f1d/msgpack-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6827d12eacc16873eba62408a1b7bbe8ecfb4a8f7ed78a631ae9bae6ad43cf2", size = 405618, upload-time = "2026-06-11T04:15:26.235Z" }, + { url = "https://files.pythonhosted.org/packages/fb/63/68f5d0ea81e167db5f59ddb94dc6f837667062113feff1c73fabf8907061/msgpack-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a186027e4279efa4c8bf06ce30605498d7d0d3af0fba0b9799dce85a3fd4a93c", size = 416468, upload-time = "2026-06-11T04:15:27.732Z" }, + { url = "https://files.pythonhosted.org/packages/73/58/567dddf5c5a2790f673bcd7d80c83466d68e5ee9a9674ebca3db8101c0c8/msgpack-1.2.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a96142c14a11cf1a509e8b9aaf72858a3b742b7613e095ce646913e88ce7bd99", size = 374464, upload-time = "2026-06-11T04:15:29.286Z" }, + { url = "https://files.pythonhosted.org/packages/0d/30/0c2342fc9092e4498045f5f60bca6ccbe4f4d87789778c2300e6fd6efe82/msgpack-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50c220579b68a6085b95408b2eaa486b259520f55d8e363ddc9b5d7ba5a6ac6d", size = 395879, upload-time = "2026-06-11T04:15:30.973Z" }, + { url = "https://files.pythonhosted.org/packages/b9/11/9565b29b58ce3c33e177b490478b7aaeb8f726ecaaeda26d815893c1db5a/msgpack-1.2.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4dcb9d12ab100ecacdfaaf37a3d72fe8392eacc7054afc1916b12d1b747c8446", size = 371749, upload-time = "2026-06-11T04:15:32.418Z" }, + { url = "https://files.pythonhosted.org/packages/f2/da/7bade19d60b73e2ef73fb76aaf4504c112a70cb760951b7202a0c64b5111/msgpack-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a804727188ab0ebb237fadb303b743f04925a69d8c3247292d1e33e679767c15", size = 410416, upload-time = "2026-06-11T04:15:34.053Z" }, + { url = "https://files.pythonhosted.org/packages/6d/14/c0c619571c02432208a5977a8dbdd3fc65fe1369f8226ca4b6d08cca87d8/msgpack-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1a1ac6ae1fe23298f79380e7b144c8a454e5d05616b0096584f353ba2d750114", size = 64357, upload-time = "2026-06-11T04:15:35.535Z" }, + { url = "https://files.pythonhosted.org/packages/50/a5/de06718460909aa965737fec4cfe8a15dedc6544a8c55feeb6956fa0d6e3/msgpack-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c3c80949d79578f9dc85fd9fb91edfe6694e8a729cd5744634d59d8455fdde3", size = 71057, upload-time = "2026-06-11T04:15:36.83Z" }, + { url = "https://files.pythonhosted.org/packages/c7/52/73446b0141c94a856e22b787c56709c0815fc34f185326577e15b26d8cfe/msgpack-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fcf8f76fa587c2395fd0057c7232dbf071241f9ad280b235adb7ab585289989e", size = 64490, upload-time = "2026-06-11T04:15:38.001Z" }, +] + [[package]] name = "msgspec" version = "0.21.1" @@ -3765,11 +3946,11 @@ wheels = [ [[package]] name = "narwhals" -version = "2.23.0" +version = "2.22.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/66ed1fc6e38a0c0f330627ec5c5d597990d6159b6712b82af0ad2c65f06c/narwhals-2.23.0.tar.gz", hash = "sha256:13e7ff5b4bb4a2f77b907c2e4d8a76e273dfc1323a3c997440a2f9fd26aed408", size = 656209, upload-time = "2026-07-01T11:21:53.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/3c/c4ef2164a71c1a63d7f1ae411c4082c5fa872405106db60a4b7114989ad7/narwhals-2.22.1.tar.gz", hash = "sha256:d62920805a0a43b7ff8b54b0c0d3142d796f8a9301836ada37e573d6a33cbcd9", size = 647493, upload-time = "2026-06-05T12:34:34.051Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/4e/afc8c31605cb8be1d3bb4438c4d979daa104dab6306cd2b87abe9c3a7299/narwhals-2.23.0-py3-none-any.whl", hash = "sha256:769e7b9ab102c93d8fa019f6b4cd1a657909b04a20bf6210e5a35aae06814ae9", size = 458938, upload-time = "2026-07-01T11:21:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/48/ca/36339329c4604adbcc99c899b7eb1ce1a555c499b6a6860757dc9bfed36d/narwhals-2.22.1-py3-none-any.whl", hash = "sha256:60567d774edf77db53906f89d9fbd164e66e56d66d388e1e6990f17ac33cfb53", size = 454815, upload-time = "2026-06-05T12:34:32.289Z" }, ] [[package]] @@ -4018,8 +4199,8 @@ resolution-markers = [ "python_full_version < '3.12' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", ] dependencies = [ - { name = "llvmlite", version = "0.44.0", source = { registry = "https://pypi.org/simple" } }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, + { name = "llvmlite", version = "0.44.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/a0/e21f57604304aa03ebb8e098429222722ad99176a4f979d34af1d1ee80da/numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d", size = 2820615, upload-time = "2025-04-09T02:58:07.659Z" } wheels = [ @@ -4045,17 +4226,55 @@ name = "numba" version = "0.65.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and sys_platform != 'darwin'", - "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 != 'darwin'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", ] dependencies = [ - { name = "llvmlite", version = "0.47.0", source = { registry = "https://pypi.org/simple" } }, + { name = "llvmlite", version = "0.47.0", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-vllm20' or extra == 'group-14-granite-switch-dev-vllm20' or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19')" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, - { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/49/61/7299643b9c18d669e04be7c5bcb64d985070d07553274817b45b049e7bfe/numba-0.65.0.tar.gz", hash = "sha256:edad0d9f6682e93624c00125a471ae4df186175d71fd604c983c377cdc03e68b", size = 2764131, upload-time = "2026-04-01T03:52:01.946Z" } wheels = [ @@ -4396,7 +4615,8 @@ resolution-markers = [ "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", ] 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 = [ @@ -5368,8 +5588,8 @@ version = "0.2.14" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version >= '3.13' and sys_platform != 'darwin'", + "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 == 'darwin'", "python_full_version < '3.12' and sys_platform != 'darwin'", @@ -5556,6 +5776,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -5565,6 +5794,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pooch" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "platformdirs" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/43/85ef45e8b36c6a48546af7b266592dc32d7f67837a6514d111bced6d7d75/pooch-1.9.0.tar.gz", hash = "sha256:de46729579b9857ffd3e741987a2f6d5e0e03219892c167c6578c0091fb511ed", size = 61788, upload-time = "2026-01-30T19:15:09.649Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2d/d4bf65e47cea8ff2c794a600c4fd1273a7902f268757c531e0ee9f18aa58/pooch-1.9.0-py3-none-any.whl", hash = "sha256:f265597baa9f760d25ceb29d0beb8186c243d6607b0f60b83ecf14078dbc703b", size = 67175, upload-time = "2026-01-30T19:15:08.36Z" }, +] + [[package]] name = "prometheus-client" version = "0.25.0" @@ -6577,9 +6820,10 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "joblib" }, { name = "narwhals" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and extra == 'extra-14-granite-switch-tutorials') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, - { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-14-granite-switch-tutorials') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20')" }, + { name = "scipy" }, { name = "threadpoolctl" }, ] 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" } @@ -6608,13 +6852,10 @@ wheels = [ name = "scipy" version = "1.17.1" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.12' and sys_platform == 'win32'", - "python_full_version < '3.12' and sys_platform == 'emscripten'", - "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20')" }, ] 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 = [ @@ -6660,45 +6901,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, ] -[[package]] -name = "scipy" -version = "1.18.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.13' and sys_platform == 'win32'", - "python_full_version >= '3.13' and sys_platform == 'emscripten'", - "python_full_version >= '3.13' and sys_platform != 'emscripten' and sys_platform != 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'win32'", - "python_full_version == '3.12.*' and sys_platform == 'emscripten'", - "python_full_version == '3.12.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", -] -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, -] -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" }, - { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, - { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, - { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, - { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, - { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, - { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, - { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, - { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, - { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, -] - [[package]] name = "sentence-transformers" version = "5.6.0" @@ -6707,8 +6909,7 @@ dependencies = [ { name = "huggingface-hub" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" } }, { name = "scikit-learn" }, - { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and extra == 'extra-14-granite-switch-tutorials') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, - { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and extra == 'extra-14-granite-switch-tutorials') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "scipy" }, { name = "torch", version = "2.10.0", source = { registry = "https://pypi.org/simple" } }, { name = "tqdm" }, { name = "transformers" }, @@ -6859,6 +7060,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] +[[package]] +name = "soundfile" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20')" }, + { name = "typing-extensions" }, +] +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" }, + { url = "https://files.pythonhosted.org/packages/d9/34/c9e80783d83eab739a9531fdee03675d53e0bf1b2ccb4bb3af5844675046/soundfile-0.14.0-py2.py3-none-win32.whl", hash = "sha256:0a6ae43c50c71b4e020cc55382925cb89451c1ed1a0c3d0f5d802da269226849", size = 902199, upload-time = "2026-06-06T08:58:43.289Z" }, + { url = "https://files.pythonhosted.org/packages/ed/97/b39c18ac1df45e755ca22b8b00e872929da5d107998a207a5e4ac831bfda/soundfile-0.14.0-py2.py3-none-win_amd64.whl", hash = "sha256:299491d3499460fb1b74bb4bd78b57ffc2d243a5fafa7b6ec1b264875c78453e", size = 1021480, upload-time = "2026-06-06T08:58:45.016Z" }, + { url = "https://files.pythonhosted.org/packages/f4/83/55c65e61cf457805ce2ec157c1c6ae17715d0851aa2374422de0538838ca/soundfile-0.14.0-py2.py3-none-win_arm64.whl", hash = "sha256:e090704718e124e7c844695236f1fce8d18a5e761eaf7c82dfcd124620805f98", size = 888858, upload-time = "2026-06-06T08:58:46.593Z" }, +] + +[[package]] +name = "soxr" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "extra == 'extra-14-granite-switch-tutorials' or extra == 'extra-14-granite-switch-vllm' or extra == 'group-14-granite-switch-dev' or extra == 'group-14-granite-switch-test' or extra == 'group-14-granite-switch-vllm19'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version < '3.13' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.13' and extra == 'extra-14-granite-switch-vllm20') or (python_full_version >= '3.13' and extra == 'group-14-granite-switch-dev-vllm20') or (python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20') or (extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20')" }, +] +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/8f/29/371467eb86c7ba6810df0bfe9409bcd9c52ec5615b111190fafe23e4d2e1/soxr-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:ae30c48ac795378cf23ba3c7c640b8ff794af714ac388b9fd6b31a40b39e6e86", size = 176779, upload-time = "2026-05-03T00:14:53.09Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/f3da7973b5f1b05d2d7e94d5376b881dcbc05297900cae6c3d33d95b209b/soxr-1.1.0-cp312-abi3-macosx_10_14_x86_64.whl", hash = "sha256:e0e09fa633ce2e67df08b298afced4d184f6e753fc330f241022250f1d0d61da", size = 204124, upload-time = "2026-05-03T00:14:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/03/dc/200013a74641f8774664bbcd2346c695c05c2e300ea792adcb40a293eed0/soxr-1.1.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6a7ad82b8d5f3fcc04b1d2ca055562b96af571e1d4fa7c6c61d0fb509ac43b4", size = 165457, upload-time = "2026-05-03T00:14:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/88/2b/2e5eba817a762a2ec589ff165b8bc5955b25a0ad140045f7cd8e45410543/soxr-1.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf98c0d7b7d5ef5bf072fee8d3020e8b664f2d195933ea7bc5089267c2e22a06", size = 206529, upload-time = "2026-05-03T00:14:57.646Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f1/0e55195893228609c9a08c3b13b7a83a46c3a992cd00d3304f0f320cfb07/soxr-1.1.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b033078e86f3c4a658e5697fac8995764fad9e799563616b630136b613167f1", size = 240413, upload-time = "2026-05-03T00:14:59.363Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4d/621e4150e4815246ad552d215a8a294a90143fedd19ee442cf82d3b3abc8/soxr-1.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:6ae2a174bffea94e8ead857dad85999d3f49f091774dbad5b046c0417d7092f4", size = 174357, upload-time = "2026-05-03T00:15:00.724Z" }, +] + [[package]] name = "sse-starlette" version = "3.4.4" @@ -6886,6 +7133,40 @@ 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 = "standard-aifc" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "standard-chunk", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/53/6050dc3dde1671eb3db592c13b55a8005e5040131f7509cef0215212cb84/standard_aifc-3.13.0.tar.gz", hash = "sha256:64e249c7cb4b3daf2fdba4e95721f811bde8bdfc43ad9f936589b7bb2fae2e43", size = 15240, upload-time = "2024-10-30T16:01:31.772Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/52/5fbb203394cc852334d1575cc020f6bcec768d2265355984dfd361968f36/standard_aifc-3.13.0-py3-none-any.whl", hash = "sha256:f7ae09cc57de1224a0dd8e3eb8f73830be7c3d0bc485de4c1f82b4a7f645ac66", size = 10492, upload-time = "2024-10-30T16:01:07.071Z" }, +] + +[[package]] +name = "standard-chunk" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/06/ce1bb165c1f111c7d23a1ad17204d67224baa69725bb6857a264db61beaf/standard_chunk-3.13.0.tar.gz", hash = "sha256:4ac345d37d7e686d2755e01836b8d98eda0d1a3ee90375e597ae43aaf064d654", size = 4672, upload-time = "2024-10-30T16:18:28.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/90/a5c1084d87767d787a6caba615aa50dc587229646308d9420c960cb5e4c0/standard_chunk-3.13.0-py3-none-any.whl", hash = "sha256:17880a26c285189c644bd5bd8f8ed2bdb795d216e3293e6dbe55bbd848e2982c", size = 4944, upload-time = "2024-10-30T16:18:26.694Z" }, +] + +[[package]] +name = "standard-sunau" +version = "3.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/e3/ce8d38cb2d70e05ffeddc28bb09bad77cfef979eb0a299c9117f7ed4e6a9/standard_sunau-3.13.0.tar.gz", hash = "sha256:b319a1ac95a09a2378a8442f403c66f4fd4b36616d6df6ae82b8e536ee790908", size = 9368, upload-time = "2024-10-30T16:01:41.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/ae/e3707f6c1bc6f7aa0df600ba8075bfb8a19252140cd595335be60e25f9ee/standard_sunau-3.13.0-py3-none-any.whl", hash = "sha256:53af624a9529c41062f4c2fd33837f297f3baa196b0cfceffea6555654602622", size = 7364, upload-time = "2024-10-30T16:01:28.003Z" }, +] + [[package]] name = "starlette" version = "0.52.1" @@ -7327,48 +7608,49 @@ version = "2.11.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version >= '3.13' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version == '3.12.*' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform == 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", "python_full_version < '3.12' and sys_platform != 'darwin' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", - "extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version >= '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", + "python_full_version < '3.13' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'extra-14-granite-switch-vllm20' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-dev-vllm20' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19' and extra != 'group-14-granite-switch-vllm20'", ] dependencies = [ { name = "cuda-bindings", version = "13.2.0", source = { registry = "https://pypi.org/simple" }, marker = "(sys_platform == 'linux' and extra == 'extra-14-granite-switch-vllm20') or (sys_platform == 'linux' and extra == 'group-14-granite-switch-dev-vllm20') or (sys_platform == 'linux' and extra != 'extra-14-granite-switch-tutorials' and extra != 'extra-14-granite-switch-vllm' and extra != 'group-14-granite-switch-dev' and extra != 'group-14-granite-switch-test' and extra != 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-dev') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'extra-14-granite-switch-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-test') or (extra == 'group-14-granite-switch-dev-vllm20' and extra == 'group-14-granite-switch-vllm19') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-tutorials' and extra == 'group-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'extra-14-granite-switch-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'extra-14-granite-switch-vllm' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-dev-vllm20') or (extra == 'group-14-granite-switch-dev' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-test' and extra == 'group-14-granite-switch-vllm20') or (extra == 'group-14-granite-switch-vllm19' and extra == 'group-14-granite-switch-vllm20')" }, @@ -7652,8 +7934,8 @@ version = "2.11.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version >= '3.13' and sys_platform != 'darwin'", + "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 == 'darwin'", "python_full_version < '3.12' and sys_platform != 'darwin'", @@ -7902,8 +8184,8 @@ version = "0.26.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version >= '3.13' and sys_platform != 'darwin'", + "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 == 'darwin'", "python_full_version < '3.12' and sys_platform != 'darwin'", @@ -8369,8 +8651,8 @@ version = "0.20.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'darwin'", - "python_full_version == '3.12.*' and sys_platform == 'darwin'", "python_full_version >= '3.13' and sys_platform != 'darwin'", + "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 == 'darwin'", "python_full_version < '3.12' and sys_platform != 'darwin'", @@ -8624,97 +8906,97 @@ wheels = [ [[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/a6/a3/60157acecc307b238d3651c2483168e224b48b23a36ae6d6903588341d80/xxhash-3.8.1-cp311-cp311-win32.whl", hash = "sha256:5013be3bea7612852c62a7437f3302c1cfb91ca7e703b194459db0b2b2e0d792", size = 31936, upload-time = "2026-07-06T10:44:36.542Z" }, - { url = "https://files.pythonhosted.org/packages/59/5c/ef70c418d878d187b8da56d4cdc06aea6cf5e456b301e96e51e1d2cc8625/xxhash-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:f377012b86c0a23a1df0cf5a1b05aa7187649e472f71c7892e5f2c2815bbe74f", size = 32724, upload-time = "2026-07-06T10:44:38.177Z" }, - { url = "https://files.pythonhosted.org/packages/2c/25/f008db952cec6b2a26445b456eeed2ebebd65e08e848ebe09ed6ac0634e6/xxhash-3.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:836f11d4474d3228e9909d97216faa4f7505df41cfaf3927eb29809de785a78d", size = 29212, upload-time = "2026-07-06T10:44:39.577Z" }, - { 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/78/dd/b5295a9f97484e7a1c2b283a742ca45e3104991c55a1ef670dde161829ba/xxhash-3.8.1-cp312-cp312-win32.whl", hash = "sha256:bf28f55e427e0483acb1f666bd0d869b6d5e5a716680c216ad7befe3d4cfba2e", size = 31970, upload-time = "2026-07-06T10:45:07.823Z" }, - { url = "https://files.pythonhosted.org/packages/ec/31/3fa0b807d7e21515cd975e7fe5c039d52ac3e9401a96d6ad68dae6305215/xxhash-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:2256e80e4960ee282f63428adb349cb7f8bd8efe4db770d88eb815f4b9860724", size = 32741, upload-time = "2026-07-06T10:45:09.42Z" }, - { url = "https://files.pythonhosted.org/packages/b8/05/86feada74e239600e6875aa507afb40482a89b92700aa74a92da83bdcb77/xxhash-3.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:9df56e6df96a60590935e22373041cccc91fd55858763dcffb55bf63b3a2b396", size = 29234, upload-time = "2026-07-06T10:45:10.809Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8c/446bb782cd0d27007a917b5569a08dd73219c3e8d6e459014db104b27bdb/xxhash-3.8.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:3c682fcd96eb4bf64be32a4d95f96107e1588005831bd8a741b324fdda01b913", size = 38562, upload-time = "2026-07-06T10:45:12.425Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ec/c0c45627eaa6be7a5d6117423adf8f7a15b17ee74b4b17072cca5959a225/xxhash-3.8.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:036a024d8b9c01f70782e09ed98d532e76fd23f950ae7154bd950fe94e90ebec", size = 36656, upload-time = "2026-07-06T10:45:13.932Z" }, - { url = "https://files.pythonhosted.org/packages/f6/94/8324c04cc7597154caaeba6c094e01fbd2e7601d01e7a13eea9f5420e77b/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d6a5c0bce213b23b0166fe0d35bcbbe23ce4b968f257cc7eb6fd57cb8e1e6297", size = 31169, upload-time = "2026-07-06T10:45:15.687Z" }, - { url = "https://files.pythonhosted.org/packages/40/a4/beb6bb26e1184e126dbe7a5682330214ef54dcfbf882078aa9f4b5428d42/xxhash-3.8.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5177aa44eddaa97c6ef0cc00c6d540edb64d51781d2f8fb941612ec61a92c9ed", size = 32177, upload-time = "2026-07-06T10:45:17.035Z" }, - { url = "https://files.pythonhosted.org/packages/56/0f/fc4c92a5a528f839b34b6419b2e53c8597f2a629d5a1f5d721f65bfa1fd6/xxhash-3.8.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7801b7223db017b9c0c9ccf37e44524edb35a1544a1c032add22c061c6af0276", size = 34642, upload-time = "2026-07-06T10:45:18.39Z" }, - { url = "https://files.pythonhosted.org/packages/d4/58/edbfb141d4000767ac6a9694f8ac0763e2c2e983e65c9e31620ba56e2667/xxhash-3.8.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e80238259655bf69d7bcd08226a970d7f42605f3157786bfa76dd13472d7fa0", size = 34684, upload-time = "2026-07-06T10:45:20.033Z" }, - { url = "https://files.pythonhosted.org/packages/07/3f/5072f1f0f5714186f0ac2a0b5a4929ce30d4b845e94886b6c01b6ebda0be/xxhash-3.8.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bcab50a389cc04d87f90092af78a6adba2ab3deca63175a3344ca83514045315", size = 32401, upload-time = "2026-07-06T10:45:21.414Z" }, - { url = "https://files.pythonhosted.org/packages/49/c7/802ea2f9c2ed59219934d6d65c470d502b1788043eae277a52af8658bda6/xxhash-3.8.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a2489d3a776fa380cb8e71f54c7fda268a9baf3de9b1395093fd280f95735907", size = 220617, upload-time = "2026-07-06T10:45:23.234Z" }, - { url = "https://files.pythonhosted.org/packages/99/a8/e10488efd31fcb13fcd6acbc6e788f10c6f8e3a0cc4ae3eb89dc19c55a12/xxhash-3.8.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ab1e5432690276e71192be7401b55f96db2d0eedea5d44eb1f164505669cc0", size = 241295, upload-time = "2026-07-06T10:45:25.364Z" }, - { url = "https://files.pythonhosted.org/packages/18/cc/14180b17d44892a631f8ae7323c30bfbb1328efc8209e528a480293528ac/xxhash-3.8.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b30e01a0b97a4bc3f519a4d7a82da3dc53251fb0de5eeea8660dcd4ff094c0c2", size = 264688, upload-time = "2026-07-06T10:45:27.09Z" }, - { url = "https://files.pythonhosted.org/packages/a9/72/a14019d0c5f6c41ee407a503036ae32787c91325ca218a96a9b5627be651/xxhash-3.8.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f44275ddb0978b67a58a951501903f04d49335a91f7681c9ce122ecb8ccb329", size = 242740, upload-time = "2026-07-06T10:45:28.753Z" }, - { url = "https://files.pythonhosted.org/packages/68/08/92550e556c6fcfcb96c6a336945eb53a431ed43120ed749636debb16c5cf/xxhash-3.8.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3b87cbd974512c0c5fc7b469c36b2cdc9ee6d76e4ec78bccb2c7184611c49b0", size = 473599, upload-time = "2026-07-06T10:45:30.524Z" }, - { url = "https://files.pythonhosted.org/packages/29/83/e361d3c1acd1b21e1d489616de6fa4aaf843365d8179f612e3743eac20a9/xxhash-3.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98ee81b4b7f3023c9cb04a78cc67610baffcb5812d92f2096cb5a5efc6f19437", size = 220559, upload-time = "2026-07-06T10:45:32.979Z" }, - { url = "https://files.pythonhosted.org/packages/05/01/006a4243c2c2a6831827f9999f6d1c23feeef100eb023c1f886022a00bf3/xxhash-3.8.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2666f059a1588a99267e33605365ed89cea92f424b3522806a9f4bd8ad2e3d62", size = 310383, upload-time = "2026-07-06T10:45:35.875Z" }, - { url = "https://files.pythonhosted.org/packages/d8/20/af388e8bf9f9a0f89eeef7d2a1935d176ee1c20bc6adeda05035879379cf/xxhash-3.8.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0093cf7eeb91b84776e8742113afa4bdf47533d36cf719179aaaf1f56f6f8bf", size = 238228, upload-time = "2026-07-06T10:45:38.02Z" }, - { url = "https://files.pythonhosted.org/packages/63/6b/4666579a87eebd1744663c404297355fa0658617b015cedfa58810ee7036/xxhash-3.8.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3a800912a2e5e975d4128969d645c4a2a80aa886ccd6c9b1c6f44529e327e8cf", size = 269137, upload-time = "2026-07-06T10:45:39.954Z" }, - { url = "https://files.pythonhosted.org/packages/de/d3/e963a8a46f900a137d91b02144d8ea07a8f812971b138204a3b2f8b8e55c/xxhash-3.8.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0fe37f72a207223d22a4eddc3149d4298993385aa9daef25c039246ca5a309f3", size = 225068, upload-time = "2026-07-06T10:45:41.718Z" }, - { url = "https://files.pythonhosted.org/packages/aa/80/9d181dbcde4b0fe48375f48833a5832d4b8cd2b349b15110c92ee472d874/xxhash-3.8.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5db43f249b4be9f99ef4b967863f37094fb40e67effafb78ba4f0356b6396104", size = 240874, upload-time = "2026-07-06T10:45:43.414Z" }, - { url = "https://files.pythonhosted.org/packages/39/15/ce3ab5a1cd27ead25a5196e55a7284220f6ad6e316da494ffd900b2b600f/xxhash-3.8.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c4ed42965c2cd9081f011be22f69d0e65d3b6165fe7734072fd0c232840bbd4e", size = 300702, upload-time = "2026-07-06T10:45:45.135Z" }, - { url = "https://files.pythonhosted.org/packages/96/c0/2281a8ab5f2a62dbf57a23c58a01ccc1d98abf40f71193c8a81f59e759b5/xxhash-3.8.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3557bec8fcb11738a8920eeb68974bc76b75262f6947998d3147954ce0a4b893", size = 443351, upload-time = "2026-07-06T10:45:47.188Z" }, - { url = "https://files.pythonhosted.org/packages/81/2e/071a58c1a53a52d4f7a3aa0987be0c396dffd40da8204805fe1b130a81f4/xxhash-3.8.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00de40f3b42240db23a82a5c682b55d7263d84a26a953240c1aee463409660e3", size = 217396, upload-time = "2026-07-06T10:45:48.925Z" }, - { url = "https://files.pythonhosted.org/packages/68/44/36ab58134badd9d3433fc7b53c4ca8d113d8e807782885628640f8297a4d/xxhash-3.8.1-cp313-cp313-win32.whl", hash = "sha256:b5196cc2574cfec572a5f3fb7cfa5ade27305ae3d06516a082132441aff4c83a", size = 31974, upload-time = "2026-07-06T10:45:50.591Z" }, - { url = "https://files.pythonhosted.org/packages/96/2a/2a0b84798448e766f7b89ceed073cb0cb5a43fc9ebbacbdea74a38de18e3/xxhash-3.8.1-cp313-cp313-win_amd64.whl", hash = "sha256:538f5f865df6cd8c32dd63158a0e5b4f5dd08d732a7da8b7228a5a0776c8ce55", size = 32739, upload-time = "2026-07-06T10:45:52.221Z" }, - { url = "https://files.pythonhosted.org/packages/d4/60/bb51dbf7c363ff88a7cbd50b7959718219577ef44d7cf255929ffc4a2194/xxhash-3.8.1-cp313-cp313-win_arm64.whl", hash = "sha256:a6617f30641ba0d8baa1635fbefb1dffc5165ec36d26921bd5cee13497cd937a", size = 29239, upload-time = "2026-07-06T10:45:53.714Z" }, - { url = "https://files.pythonhosted.org/packages/56/d3/827ca123c2ee5443a6aaed3c5dd199237dc2f010e2bebd7ec09ef36f3a5f/xxhash-3.8.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:bfcd82852c62a60e314670a9602de354c4460f8adad916e2e42a20860c7870bc", size = 34964, upload-time = "2026-07-06T10:45:55.535Z" }, - { url = "https://files.pythonhosted.org/packages/05/67/67ae2a3ccdeb8b8ef025d35aee9edd1d26c3abe5051d47da9286232afbf8/xxhash-3.8.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:08ea2081f5e88615fec8622a9f87fbe21b8ea58d88cfc02163ca11026ee62a92", size = 32697, upload-time = "2026-07-06T10:45:57.288Z" }, - { url = "https://files.pythonhosted.org/packages/38/5a/3d3994346e1f45493679cb5c1ffc2bf454e410e9d1e8a662d253becee91e/xxhash-3.8.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2e32855b6f9e5b18f449e59d45e3d5778bdeb660632ef2693cca267a11246c75", size = 225954, upload-time = "2026-07-06T10:45:58.897Z" }, - { url = "https://files.pythonhosted.org/packages/3f/2c/53169270309b7cd8e05504e07fe123bac053b89d00ac63617faacf0a2ec0/xxhash-3.8.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6e088bd7870775624256a0d84c2a6714afd223b2eeb56b0ca58398e52a32fda", size = 249776, upload-time = "2026-07-06T10:46:00.977Z" }, - { url = "https://files.pythonhosted.org/packages/70/e0/5c551d8d592f944506f7c5185e210255c15e672a3c6008c156a1bd9b775e/xxhash-3.8.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:72eb5ae575cc7ae2b23f6f8064a8b10f638c7149819ae9cc6d20ebd4d37a1629", size = 274776, upload-time = "2026-07-06T10:46:02.869Z" }, - { url = "https://files.pythonhosted.org/packages/a0/2a/d3a762270cee2d7bcd0e25e28c623e5f3f5c0dc637b66e3e47dd5b0bb3f0/xxhash-3.8.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d0b48cdf690a64cedf7258c3dc9506cc41fc86edd7739c40e3098952265dc068", size = 252056, upload-time = "2026-07-06T10:46:04.688Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8f/b78e4373b2cb6d1c42af60ea2d7e9146ad0710b239ac7f706d5d31d5bb98/xxhash-3.8.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb9e256a357dfcede7818c6d34e70db2d6b664394803d1de4b6984d2de76c0f1", size = 482108, upload-time = "2026-07-06T10:46:06.498Z" }, - { url = "https://files.pythonhosted.org/packages/e6/0d/642d923336ea61a15f8ce64fc7e078729e6e06c3a026e517fa79b2c23b7a/xxhash-3.8.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51f71a6e2ad071e70c937e41fcb6c19f82c3f9f49831eba850ed4a106ffbb647", size = 226739, upload-time = "2026-07-06T10:46:08.598Z" }, - { url = "https://files.pythonhosted.org/packages/a6/0a/a37d6da6427d45a8d23e3ee3a0ca9c9d4a90364849c6637fe2963a755f9b/xxhash-3.8.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4a6443968c4e8dc69967e12776776a5952c119cc1bd94168ad1c5ad667c2be1", size = 319658, upload-time = "2026-07-06T10:46:10.504Z" }, - { url = "https://files.pythonhosted.org/packages/4a/51/ebbd40da8a3f1bc53b4b7a9a87f8e28bd95c5f21bc14b8a57860cf367d1b/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:714503083a1f2065c9ad15340dd49ac8a8e948a505a705ffa1750cb951519113", size = 246059, upload-time = "2026-07-06T10:46:12.634Z" }, - { url = "https://files.pythonhosted.org/packages/24/4c/d9014030147e1f0bb26e7da47aa240dd9ec61c763c573e558111d869f8e1/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:77f74e45a1e5574bbbf80181c8027b3a4c65c2248fffbd557bd596fff13102f9", size = 275535, upload-time = "2026-07-06T10:46:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/84/86/caee2db41fadcd5a25aa4323213f9afec5a8586d4e419241e3d659362bd7/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:4e0e1b0fb0259c1b75d1251ac0bb4d7ab675d36f7a6bf4ba6aa630dae94f9ffa", size = 231292, upload-time = "2026-07-06T10:46:16.452Z" }, - { url = "https://files.pythonhosted.org/packages/0b/60/f52f08bcdc904c4514ea5c25caa19e9f3214144434a6ff96dc82dc1cbddd/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:10e4393ec33633c2f05ad01869e546ad080b1a18f2650503731f153774608b31", size = 250490, upload-time = "2026-07-06T10:46:18.318Z" }, - { url = "https://files.pythonhosted.org/packages/24/a0/94dc7ae310838f250669c6ad7168e6d6fca17d49dac1053f06dc232c4a56/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b3ba794c3d885803db6c3116686923f1ec13bc86e621e169a375282b63ea1cc6", size = 309861, upload-time = "2026-07-06T10:46:20.503Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f9/adeead7d0eb28cdfc2832544ea639ffbc6749ccde47a8e228d667459182e/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:57189a69c0891e4818853feaa521c972d22c880a001453addea015f48e3c3398", size = 448739, upload-time = "2026-07-06T10:46:22.79Z" }, - { url = "https://files.pythonhosted.org/packages/04/a4/22ec0e07db57d901c9298ae98aa3cf2be45bafded6f07c13131e85b89032/xxhash-3.8.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d59e71153fe9ff85648d00e18649b07e9b22c797291abb7e27274fa06df8b838", size = 223657, upload-time = "2026-07-06T10:46:24.831Z" }, - { url = "https://files.pythonhosted.org/packages/94/32/8a9531f37b59e5a013003db7cb7414baf4ce7e0e1268e0d5947cd3d6a2df/xxhash-3.8.1-cp313-cp313t-win32.whl", hash = "sha256:5b96f0024e9840f449bd91b2d005c921a4b666055a0d1b6492463799f32aae22", size = 32377, upload-time = "2026-07-06T10:46:26.86Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/2ca45fd7f671de5f81fc297ef1c95080b40c86ec6be0cc6034b8f7707ac8/xxhash-3.8.1-cp313-cp313t-win_amd64.whl", hash = "sha256:37d5a56c36dcc0b9a87b814cd992598d33863ff683749de6c86081f278d5e629", size = 33274, upload-time = "2026-07-06T10:46:28.39Z" }, - { url = "https://files.pythonhosted.org/packages/5a/54/20d7163463ddb6438b73a427d1655a77a502cf9b9b0c3ada3599629d9c0a/xxhash-3.8.1-cp313-cp313t-win_arm64.whl", hash = "sha256:6696c8752aded28ff3b16f33ef28ce28fb5d209b80c206746f943199fcf5fd65", size = 29375, upload-time = "2026-07-06T10:46:29.962Z" }, - { 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" }, - { url = "https://files.pythonhosted.org/packages/6b/57/5c6e0908a47f61dca96d01c8ee6fce01ed1050611eb779083ba8758fed81/xxhash-3.8.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:345b07b78e2bf583d71682aa34ae5b5fab575f7a1cb31e10263ebbc6f89f8c42", size = 32869, upload-time = "2026-07-06T10:49:55.972Z" }, +version = "3.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/2f/e183a1b407002f5af81822bee18b61cdb94b8670208ef34734d8d2b8ebe9/xxhash-3.7.0.tar.gz", hash = "sha256:6cc4eefbb542a5d6ffd6d70ea9c502957c925e800f998c5630ecc809d6702bae", size = 82022, upload-time = "2026-04-25T11:10:32.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/f4/7bd35089ff1f8e2c96baa2dce05775a122aacd2e3830a73165e27a4d0848/xxhash-3.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fdc7d06929ae28dda98297a18eef7b0fd38991a3b405d8d7b55c9ef24c296958", size = 33423, upload-time = "2026-04-25T11:05:47.628Z" }, + { url = "https://files.pythonhosted.org/packages/a3/26/4e00c88a6a2c8a759cfb77d2a9a405f901e8aa66e60ef1fd0aeb35edda48/xxhash-3.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea6daa712f4e094a30830cf01e9b47d03b24d05cc9dab8609f0d9a9db8454712", size = 30857, upload-time = "2026-04-25T11:05:49.189Z" }, + { url = "https://files.pythonhosted.org/packages/82/2f/eeb942c17a5a761a8f01cb9180a0b76bfb62a2c39e6f46b1f9001899027a/xxhash-3.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9e6c0d843f1daf85ea23aeb053579135552bde575b7b98af20bfc667b6e4548d", size = 194702, upload-time = "2026-04-25T11:05:50.457Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/96f132c08b1e5951c68691d3b9ec351ec2edc028f6a01fcd294f46b9d9f0/xxhash-3.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:363c139bf15e1ac5f136b981d3c077eb551299b1effede7f12faa010b8590a60", size = 213613, upload-time = "2026-04-25T11:05:52.571Z" }, + { url = "https://files.pythonhosted.org/packages/82/89/d4e92b796c5ed052d29ed324dbfc1dc1188e0c4bf64bebbf0f8fc20698df/xxhash-3.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a778b25874cb0f862eaab5986bff4ca49ffb0def7c0a34c237b948b3c6c775b2", size = 236726, upload-time = "2026-04-25T11:05:54.395Z" }, + { url = "https://files.pythonhosted.org/packages/40/f1/81fc4361921dc6e557a9c60cb3712f36d244d06eeeb71cd2f4252ac42678/xxhash-3.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e1860f1e43d40e9d904cf22d93e587ea42e010ebce4160877e46bcab4bc232a", size = 212443, upload-time = "2026-04-25T11:05:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d0/afeddd4cff50a332f50d4b8a2e8857673153ab0564ef472fcdeb0b5430df/xxhash-3.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9122ad6f867c4a0f5e655f5c3bdf89103852009dbb442a3d23e688b9e699e800", size = 445793, upload-time = "2026-04-25T11:05:58.953Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/3c91e4e6a05ca4d7df8e39ec3a75b713609258ec84705ab34be6430826a1/xxhash-3.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d9110d0c3fb02679972837a033251fd186c529aa62f19c132fc909c74052b8", size = 193937, upload-time = "2026-04-25T11:06:00.546Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3a/a6b0772d9801dd4bea4ca4fd34734d6e9b51a711c8a611a24a79de26a878/xxhash-3.7.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:347a93f2b4ce67ce61959665e32a7447c380f8347e55e100daa23766baacf0e5", size = 285188, upload-time = "2026-04-25T11:06:01.96Z" }, + { url = "https://files.pythonhosted.org/packages/6c/f8/cf8e31fd7282230fe7367cd501a2e75b4b67b222bfc7eacccfc20d2652cb/xxhash-3.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:acbb48679ddf3852c45280c10ff10d52ca2cd1da2e552fb81db1ff786c75d0e4", size = 210966, upload-time = "2026-04-25T11:06:03.453Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/fd36cc4a81bf52ee5633275daae2b93dd958aace67fd4f5d466ec83b5f35/xxhash-3.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:fe14c356f8b23ad811dc026077a6d4abccdaa7bce5ca98579605550657b6fcfb", size = 241994, upload-time = "2026-04-25T11:06:05.264Z" }, + { url = "https://files.pythonhosted.org/packages/08/e1/67f5d9c9369be42eaf99ba02c01bf14c5ecd67087b02567960bfcee43b63/xxhash-3.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f420ad3d41e38194353a498bbc9561fd5a9973a27b536ce46d8583479cf44335", size = 198707, upload-time = "2026-04-25T11:06:07.044Z" }, + { url = "https://files.pythonhosted.org/packages/50/17/a4c865ca22d2da6b1bc7d739bf88cab209533cf52ba06ca9da27c3039bee/xxhash-3.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:693d02c6dc7d1aa0a45921d54cd8c1ff629e09dfdc2238471507af1f7a1c6f04", size = 210917, upload-time = "2026-04-25T11:06:08.853Z" }, + { url = "https://files.pythonhosted.org/packages/49/8b/453b35810d697abac3c96bde3528bece685869227da274eb80a4a4d4a119/xxhash-3.7.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:14bf7a54e43825ec131ee7fe3c60e142e7c2c1e676ad0f93fc893432d15414af", size = 275772, upload-time = "2026-04-25T11:06:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ad/4eed7eab07fd3ee6678f416190f0413d097ab5d7c1278906bf1e9549d789/xxhash-3.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:ae3a39a4d96bdb6f8d154fd7f490c4ad06f0532fcd2bb656052a9a7762cf5d31", size = 414068, upload-time = "2026-04-25T11:06:12.511Z" }, + { url = "https://files.pythonhosted.org/packages/d3/4e/fd6f8a680ba248fdb83054fa71a8bfa3891225200de1708b888ef2c49829/xxhash-3.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1cc07c639e3a77ef1d32987464d3e408565b8a3be57b545d3542b191054d9923", size = 191459, upload-time = "2026-04-25T11:06:14.07Z" }, + { url = "https://files.pythonhosted.org/packages/50/7c/8cb34b3bed4f44ca6827a534d50833f9bc6c006e83b0eb410ac9fa0793bd/xxhash-3.7.0-cp311-cp311-win32.whl", hash = "sha256:3281ba1d1e60ee7a382a7b958513ba03c2c0d5fcbd9a6f7517c0a81251a23422", size = 30628, upload-time = "2026-04-25T11:06:15.802Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/a49767bd7b40782bedae9ff0721bfe1d7e4dd9dc1585dea684e57ba67c20/xxhash-3.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:a7f25baec4c5d851d40718d6fae52285b31683093d4ff5207e63ab306ccf14a5", size = 31461, upload-time = "2026-04-25T11:06:17.104Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c6/3957bfacfb706bd687be246dfa8dd60f8df97c44186d229f7fd6e26c4b7e/xxhash-3.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:4c2454448ce847c72635827bb75c15c5a3434b03ee1afd28cb6dc6fb2597d830", size = 27746, upload-time = "2026-04-25T11:06:18.716Z" }, + { url = "https://files.pythonhosted.org/packages/f2/8a/51a14cdef4728c6c2337db8a7d8704422cc65676d9199d77215464c880af/xxhash-3.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:082c87bfdd2b9f457606c7a4a53457f4c4b48b0cdc48de0277f4349d79bb3d7a", size = 33357, upload-time = "2026-04-25T11:06:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/0c2c933809421ffd9bf42b59315552c143c755db5d9a816b2f1ae273e884/xxhash-3.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5e7ce913b61f35b0c1c839a49ac9c8e75dd8d860150688aed353b0ce1bf409d8", size = 30869, upload-time = "2026-04-25T11:06:21.989Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/89d5fdd6ee12d70ba99451de46dd0e8010167468dcd913ec855653f4dd50/xxhash-3.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3beb1de3b1e9694fcdd853e570ee64c631c7062435d2f8c69c1adf809bc086f0", size = 194100, upload-time = "2026-04-25T11:06:23.586Z" }, + { url = "https://files.pythonhosted.org/packages/87/ee/2f9f2ed993e77206d1e66991290a1ebe22e843351ca3ebec8e49e01ba186/xxhash-3.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3e7b689c3bce16699efcf736066f5c6cc4472c3840fe4b22bd8279daf4abdac", size = 212977, upload-time = "2026-04-25T11:06:25.019Z" }, + { url = "https://files.pythonhosted.org/packages/de/60/5a91644615a9e9d4e42c2e9925f1908e3a24e4e691d9de7340d565bea024/xxhash-3.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a6545e6b409e3d5cbafc850fb84c55a1ca26ed15a6b11e3bf07a0e0cd84517c8", size = 236373, upload-time = "2026-04-25T11:06:26.482Z" }, + { url = "https://files.pythonhosted.org/packages/22/c0/f3a9384eaaed9d14d4d062a5d953aa0da489bfe9747877aa994caa87cd0b/xxhash-3.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:31ab1461c77a11461d703c88eb949e132a1c6515933cf675d97ec680f4bd18de", size = 212229, upload-time = "2026-04-25T11:06:28.065Z" }, + { url = "https://files.pythonhosted.org/packages/2e/67/02f07a9fd79726804190f2172c4894c3ed9a4ebccaca05653c84beb58025/xxhash-3.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c4d596b7676f811172687ec567cbafb9e4dea2f9be1bbb4f622410cb7f40f40", size = 445462, upload-time = "2026-04-25T11:06:30.048Z" }, + { url = "https://files.pythonhosted.org/packages/40/37/558f5a90c0672fc9b4402dc25d87ac5b7406616e8969430c9ca4e52ee74d/xxhash-3.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13805f0461cba0a857924e70ff91ae6d52d2598f79a884e788db80532614a4a1", size = 193932, upload-time = "2026-04-25T11:06:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/aaa09cd58661d32044dbbad7df55bbe22a623032b810e7ed3b8c569a2a6f/xxhash-3.7.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d398f372496152f1c6933a33566373f8d1b37b98b8c9d608fa6edc0976f23b2", size = 284807, upload-time = "2026-04-25T11:06:33.697Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f3/53df3719ab127a02c174f0c1c74924fcd110866e89c966bc7909cfa8fa84/xxhash-3.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d610aa62cdb7d4d497740741772a24a794903bf3e79eaa51d2e800082abe11e5", size = 210445, upload-time = "2026-04-25T11:06:35.488Z" }, + { url = "https://files.pythonhosted.org/packages/72/33/d219975c0e8b6fa2eb9ccd486fe47e21bf1847985b878dd2fbc3126e0d5c/xxhash-3.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:073c23900a9fbf3d26616c17c830db28af9803677cd5b33aea3224d824111514", size = 241273, upload-time = "2026-04-25T11:06:37.24Z" }, + { url = "https://files.pythonhosted.org/packages/3e/50/49b1afe610eb3964cedcb90a4d4c3d46a261ee8669cbd4f060652619ae3c/xxhash-3.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:418a463c3e6a590c0cdc890f8be19adb44a8c8acd175ca5b2a6de77e61d0b386", size = 197950, upload-time = "2026-04-25T11:06:39.148Z" }, + { url = "https://files.pythonhosted.org/packages/c6/75/5f42a1a4c78717d906a4b6a140c6dbf837ab1f547a54d23c4e2903310936/xxhash-3.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:03f8ff4474ee61c845758ce00711d7087a770d77efb36f7e74a6e867301000b8", size = 210709, upload-time = "2026-04-25T11:06:40.958Z" }, + { url = "https://files.pythonhosted.org/packages/8a/85/237e446c25abced71e9c53d269f2cef5bab8a82b3f88a12e00c5368e7368/xxhash-3.7.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:44fba4a5f1d179b7ddc7b3dc40f56f9209046421679b57025d4d8821b376fd8d", size = 275345, upload-time = "2026-04-25T11:06:42.525Z" }, + { url = "https://files.pythonhosted.org/packages/62/34/c2c26c0a6a9cc739bc2a5f0ae03ba8b87deb12b8bce35f7ac495e790dc6d/xxhash-3.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31e3516a0f829d06ded4a2c0f3c7c5561993256bfa1c493975fb9dc7bfa828a1", size = 414056, upload-time = "2026-04-25T11:06:44.343Z" }, + { url = "https://files.pythonhosted.org/packages/a0/aa/5c58e9bc8071b8afd8dcf297ff362f723c4892168faba149f19904132bf4/xxhash-3.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b59ee2ac81de57771a09ecad09191e840a1d2fae1ef684208320591055768f83", size = 191485, upload-time = "2026-04-25T11:06:46.262Z" }, + { url = "https://files.pythonhosted.org/packages/d4/69/a929cf9d1e2e65a48b818cdce72cb6b69eab2e6877f21436d0a1942aff43/xxhash-3.7.0-cp312-cp312-win32.whl", hash = "sha256:74bbd92f8c7fcc397ba0a11bfdc106bc72ad7f11e3a60277753f87e7532b4d81", size = 30671, upload-time = "2026-04-25T11:06:48.039Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1b/104b41a8947f4e1d4a66ce1e628eea752f37d1890bfd7453559ca7a3d950/xxhash-3.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:7bd7bc82dd4f185f28f35193c2e968ef46131628e3cac62f639dadf321cba4d1", size = 31514, upload-time = "2026-04-25T11:06:49.279Z" }, + { url = "https://files.pythonhosted.org/packages/98/a0/1fd0ea1f1b886d9e7c73f0397571e22333a7d79e31da6d7127c2a4a71d75/xxhash-3.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:7d7148180ec99ba36585b42c8c5de25e9b40191613bc4be68909b4d25a77a852", size = 27761, upload-time = "2026-04-25T11:06:50.448Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ca/d5174b4c36d10f64d4ca7050563138c5a599efb01a765858ddefc9c1202a/xxhash-3.7.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:4b6d6b33f141158692bd4eafbb96edbc5aa0dabdb593a962db01a91983d4f8fa", size = 36813, upload-time = "2026-04-25T11:06:51.73Z" }, + { url = "https://files.pythonhosted.org/packages/41/d0/abc6c9d347ba1f1e1e1d98125d0881a0452c7f9a76a9dd03a7b5d2197f23/xxhash-3.7.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:845d347df254d6c619f616afa921331bada8614b8d373d58725c663ba97c3605", size = 35121, upload-time = "2026-04-25T11:06:53.048Z" }, + { url = "https://files.pythonhosted.org/packages/bf/11/4cc834eb3d79f2f2b3a6ef7324195208bcdfbdcf7534d2b17267aa5f3a8f/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:fddbbb69a6fff4f421e7a0d1fa28f894b20112e9e3fab306af451e2dfd0e459b", size = 29624, upload-time = "2026-04-25T11:06:54.311Z" }, + { url = "https://files.pythonhosted.org/packages/23/83/e97d3e7b635fe73a1dfb1e91f805324dd6d930bb42041cbf18f183bc0b6d/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:54876a4e45101cec2bf8f31a973cda073a23e2e108538dad224ba07f85f22487", size = 30638, upload-time = "2026-04-25T11:06:55.864Z" }, + { url = "https://files.pythonhosted.org/packages/f4/40/d84951d80c35db1f4c40a29a64a8520eea5d56e764c603906b4fe763580f/xxhash-3.7.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:0c72fe9c7e3d6dfd7f1e21e224a877917fa09c465694ba4e06464b9511b65544", size = 33323, upload-time = "2026-04-25T11:06:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/c7dc6558d97e9ab023f663d69ab28b340ed9bf4d2d94f2c259cf896bb354/xxhash-3.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a6d73a830b17ef49bc04e00182bd839164c1b3c59c127cd7c54fcb10c7ed8ee8", size = 33362, upload-time = "2026-04-25T11:06:58.656Z" }, + { url = "https://files.pythonhosted.org/packages/2a/6e/46b84017b1301d54091430353d4ad5901654a3e0871649877a416f7f1644/xxhash-3.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91c3b07cf3362086d8f126c6aecd8e5e9396ad8b2f2219ea7e49a8250c318acd", size = 30874, upload-time = "2026-04-25T11:06:59.834Z" }, + { url = "https://files.pythonhosted.org/packages/df/5e/8f9158e3ab906ad3fec51e09b5ea0093e769f12207bfa42a368ca204e7ab/xxhash-3.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50e879ebbac351c81565ca108db766d7832f5b8b6a5b14b8c0151f7190028e3d", size = 194185, upload-time = "2026-04-25T11:07:01.658Z" }, + { url = "https://files.pythonhosted.org/packages/f3/29/a804ded9f5d3d3758292678d23e7528b08fda7b7e750688d08b052322475/xxhash-3.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:921c14e93817842dd0dd9f372890a0f0c72e534650b6ab13c5be5cd0db11d47e", size = 213033, upload-time = "2026-04-25T11:07:03.606Z" }, + { url = "https://files.pythonhosted.org/packages/8b/91/1ce5a7d2fdc975267320e2c78fc1cecfe7ab735ccbcf6993ec5dd541cb2c/xxhash-3.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e64a7c9d7dfca3e0fafcbc5e455519090706a3e36e95d655cec3e04e79f95aaa", size = 236140, upload-time = "2026-04-25T11:07:05.396Z" }, + { url = "https://files.pythonhosted.org/packages/34/04/fd595a4fd8617b05fa27bd9b684ecb4985bfed27917848eea85d54036d06/xxhash-3.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2220af08163baf5fa36c2b8af079dc2cbe6e66ae061385267f9472362dfd53c6", size = 212291, upload-time = "2026-04-25T11:07:06.966Z" }, + { url = "https://files.pythonhosted.org/packages/03/fb/f1a379cbc372ae5b9f4ab36154c48a849ca6ebe3ac477067a57865bf3bc6/xxhash-3.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f14bb8b22a4a91325813e3d553b8963c10cf8c756cff65ee50c194431296c655", size = 445532, upload-time = "2026-04-25T11:07:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/65/59/172424b79f8cfd4b6d8a122b2193e6b8ad4b11f7159bb3b6f9b3191329bb/xxhash-3.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:496736f86a9bedaf64b0dc70e3539d0766df01c71ea22032698e88f3f04a1ce9", size = 193990, upload-time = "2026-04-25T11:07:10.315Z" }, + { url = "https://files.pythonhosted.org/packages/b9/19/aeac22161d953f139f07ba5586cb4a17c5b7b6dff985122803bb12933500/xxhash-3.7.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0ff71596bd79816975b3de7130ab1ff4541410285a3c084584eeb1c8239996fd", size = 284876, upload-time = "2026-04-25T11:07:12.15Z" }, + { url = "https://files.pythonhosted.org/packages/77/d5/4fd0b59e7a02242953da05ff679fbb961b0a4368eac97a217e11dae110c1/xxhash-3.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1ad86695c19b1d46fe106925db3c7a37f16be37669dcf58dcc70a9dd6e324676", size = 210495, upload-time = "2026-04-25T11:07:13.952Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fb/976a3165c728c7faf74aa1b5ab3cf6a85e6d731612894741840524c7d28c/xxhash-3.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:970f9f8c50961d639cbd0d988c96f80ddf66006de93641719282c4fe7a87c5e6", size = 241331, upload-time = "2026-04-25T11:07:15.557Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2c/6763d5901d53ac9e6ba296e5717ae599025c9d268396e8faa8b4b0a8e0ac/xxhash-3.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5886ad85e9e347911783760a1d16cb6b393e8f9e3b52c982568226cb56927bdc", size = 198037, upload-time = "2026-04-25T11:07:17.563Z" }, + { url = "https://files.pythonhosted.org/packages/61/2b/876e722d533833f5f9a83473e6ba993e48745701096944e77bbecf29b2c3/xxhash-3.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6e934bbae1e0ec74e27d5f0d7f37ef547ce5ff9f0a7e63fb39e559fc99526734", size = 210744, upload-time = "2026-04-25T11:07:19.055Z" }, + { url = "https://files.pythonhosted.org/packages/21/e6/d7e7baef7ce24166b4668d3c48557bb35a23b92ecadcac7e7718d099ab69/xxhash-3.7.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:3b6b3d28228af044ebcded71c4a3dd86e1dbd7e2f4645bf40f7b5da65bb5fb5a", size = 275406, upload-time = "2026-04-25T11:07:20.908Z" }, + { url = "https://files.pythonhosted.org/packages/92/fe/198b3763b2e01ca908f2154969a2352ec99bda892b574a11a9a151c5ede4/xxhash-3.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:6be4d70d9ab76c9f324ead9c01af6ff52c324745ea0c3731682a0cf99720f1fe", size = 414125, upload-time = "2026-04-25T11:07:23.037Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6d/019a11affd5a5499137cacca53808659964785439855b5aa40dfd3412916/xxhash-3.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:151d7520838d4465461a0b7f4ae488b3b00de16183dd3214c1a6b14bf89d7fb6", size = 191555, upload-time = "2026-04-25T11:07:24.991Z" }, + { url = "https://files.pythonhosted.org/packages/76/21/b96d58568df2d01533244c3e0e5cbdd0c8b2b25c4bec4d72f19259a292d7/xxhash-3.7.0-cp313-cp313-win32.whl", hash = "sha256:d798c1e291bffb8e37b5bbe0dda77fc767cd19e89cadaf66e6ed5d0ff88c9fe6", size = 30668, upload-time = "2026-04-25T11:07:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/99/57/d849a8d3afa1f8f4bc6a831cd89f49f9706fbbad94d2975d6140a171988c/xxhash-3.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:875811ba23c543b1a1c3143c926e43996eb27ebb8f52d3500744aa608c275aed", size = 31524, upload-time = "2026-04-25T11:07:27.92Z" }, + { url = "https://files.pythonhosted.org/packages/81/52/bacc753e92dee78b058af8dcef0a50815f5f860986c664a92d75f965b6a5/xxhash-3.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:54a675cb300dda83d71daae2a599389d22db8021a0f8db0dd659e14626eb3ecc", size = 27768, upload-time = "2026-04-25T11:07:29.113Z" }, + { url = "https://files.pythonhosted.org/packages/1c/47/ddbd683b7fc7e592c1a8d9d65f73ce9ab513f082b3967eee2baf549b8fc6/xxhash-3.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a3b19a42111c4057c1547a4a1396a53961dca576a0f6b82bfa88a2d1561764b2", size = 33576, upload-time = "2026-04-25T11:07:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/07/f2/36d3310161db7f72efb4562aadde0ed429f1d0531782dd6345b12d2da527/xxhash-3.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8f4608a06e4d61b7a3425665a46d00e0579122e1a2fae97a0c52953a3aad9aa3", size = 31123, upload-time = "2026-04-25T11:07:31.989Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3f/75937a5c69556ed213021e43cbedd84c8e0279d0d74e7d41a255d84ba4b1/xxhash-3.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ad37c7792479e49cf96c1ab25517d7003fe0d93687a772ba19a097d235bbe41e", size = 196491, upload-time = "2026-04-25T11:07:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/f10d7ff8c7a733d4403a43b9de18c8fabc005f98cec054644f04418659ee/xxhash-3.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc026e3b89d98e30a8288c95cb696e77d150b3f0fb7a51f73dcd49ee6b5577fa", size = 215793, upload-time = "2026-04-25T11:07:34.919Z" }, + { url = "https://files.pythonhosted.org/packages/8b/fd/778f60aa295f58907938f030a8b514611f391405614a525cccd2ffc00eb5/xxhash-3.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9b31ab1f28b078a6a1ac1a54eb35e7d5390deddd56870d0be3a0a733d1c321c", size = 237993, upload-time = "2026-04-25T11:07:36.638Z" }, + { url = "https://files.pythonhosted.org/packages/70/f5/736db5de387b4a540e37a05b84b40dc58a1ce974bfd2b4e5754ce29b68c3/xxhash-3.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3bb5fd680c038fd5229e44e9c493782f90df9bef632fd0499d442374688ff70b", size = 214887, upload-time = "2026-04-25T11:07:38.564Z" }, + { url = "https://files.pythonhosted.org/packages/4d/aa/09a095f22fdb9a27fbb716841fbff52119721f9ca4261952d07a912f7839/xxhash-3.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:030c0fd688fce3569fbb49a2feefd4110cbb0b650186fb4610759ecfac677548", size = 448407, upload-time = "2026-04-25T11:07:40.552Z" }, + { url = "https://files.pythonhosted.org/packages/74/8a/b745efeeca9e34a91c26fdc97ad8514c43d5a81ac78565cba80a1353870a/xxhash-3.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b1bde10324f4c31812ae0d0502e92d916ae8917cad7209353f122b8b8f610c3", size = 196119, upload-time = "2026-04-25T11:07:42.101Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5c/0cfceb024af90c191f665c7933b1f318ee234f4797858383bebd1881d52f/xxhash-3.7.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:503722d52a615f2604f5e7611de7d43878df010dc0053094ef91cb9a9ac3d987", size = 286751, upload-time = "2026-04-25T11:07:43.568Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0a/0793e405dc3cf8f4ebe2c1acec1e4e4608cd9e7e50ea691dabbc2a95ccbb/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c72500a3b6d6c30ebfc135035bcace9eb5884f2dc220804efcaaba43e9f611dd", size = 212961, upload-time = "2026-04-25T11:07:45.388Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7e/721118ffc63bfff94aa565bcf2555a820f9f4bdb0f001e0d609bdfad70de/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:43475925a766d01ca8cd9a857fd87f3d50406983c8506a4c07c4df12adcc867f", size = 243703, upload-time = "2026-04-25T11:07:47.053Z" }, + { url = "https://files.pythonhosted.org/packages/6e/18/16f6267160488b8276fd3d449d425712512add292ba545c1b6946bfdb7dd/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8d09dfd2ab135b985daf868b594315ebe11ad86cd9fea46e6c69f19b28f7d25a", size = 200894, upload-time = "2026-04-25T11:07:48.657Z" }, + { url = "https://files.pythonhosted.org/packages/2d/94/80ba841287fd97e3e9cac1d228788c8ef623746f570404961eec748ecb5c/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c50269d0055ac1faecfd559886d2cbe4b730de236585aba0e873f9d9dadbe585", size = 213357, upload-time = "2026-04-25T11:07:50.257Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/106d4067130c59f1e18a55ffadcd876d8c68534883a1e02685b29d3d8153/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1910df4756a5ab58cfad8744fc2d0f23926e3efcc346ee76e87b974abab922f4", size = 277600, upload-time = "2026-04-25T11:07:51.745Z" }, + { url = "https://files.pythonhosted.org/packages/c5/86/a081dd30da71d720b2612a792bfd55e45fa9a07ac76a0507f60487473c25/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d006faf3b491957efcb433489be3c149efe4787b7063d5cddb8ddaefdc60e0c1", size = 416980, upload-time = "2026-04-25T11:07:53.504Z" }, + { url = "https://files.pythonhosted.org/packages/35/29/1a95221a029a3c1293773869e1ab47b07cbbdd82444a42809e8c60156626/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:abb65b4e947e958f7b3b0d71db3ce447d1bc5f37f5eab871ce7223bda8768a04", size = 193840, upload-time = "2026-04-25T11:07:55.103Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/db909dd0823285de2286f67e10ee4d81e96ad35d7d8e964ecb07fccd8af9/xxhash-3.7.0-cp313-cp313t-win32.whl", hash = "sha256:178959906cb1716a1ce08e0d69c82886c70a15a6f2790fc084fdd146ca30cd49", size = 30966, upload-time = "2026-04-25T11:07:56.524Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ff/d705b15b22f21ee106adce239cb65d35067a158c630b240270f09b17c2e6/xxhash-3.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2524a1e20d4c231d13b50f7cf39e44265b055669a64a7a4b9a2a44faa03f19b6", size = 31784, upload-time = "2026-04-25T11:07:57.758Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1f/b2cf83c3638fd0588e0b17f22e5a9400bdfb1a3e3755324ac0aee2250b88/xxhash-3.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:37d994d0ffe81ef087bb330d392caa809bb5853c77e22ea3f71db024a0543dba", size = 27932, upload-time = "2026-04-25T11:07:59.109Z" }, + { url = "https://files.pythonhosted.org/packages/54/c1/e57ac7317b1f58a92bab692da6d497e2a7ce44735b224e296347a7ecc754/xxhash-3.7.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad3aa71e12ee634f22b39a0ff439357583706e50765f17f05550f92dbf128a23", size = 31232, upload-time = "2026-04-25T11:10:21.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4e/075559bd712bc62e84915ea46bbee859f935d285659082c129bdbff679dd/xxhash-3.7.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5de686e73690cdaf72b96d4fa083c230ec9020bcc2627ce6316138e2cf2fe2d1", size = 28553, upload-time = "2026-04-25T11:10:23.1Z" }, + { url = "https://files.pythonhosted.org/packages/92/ca/a9c78cb384d4b033b0c58196bd5c8509873cabe76389e195127b0302a741/xxhash-3.7.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7fbec49f5341bbdea0c471f7d1e2fb41ae8925af9b6f28025c28defd8eb94274", size = 41109, upload-time = "2026-04-25T11:10:25.022Z" }, + { url = "https://files.pythonhosted.org/packages/bd/b1/dfe2629f7c77eb2fa234c72ff537cdd64939763df704e256446ed364a16d/xxhash-3.7.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48b542c347c2089f43dc5a6db31d2a6f3cdb04ee33505ec6e9f653834dbb0bde", size = 36307, upload-time = "2026-04-25T11:10:26.949Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f7/5a484afce0f48dd8083208b42e4911f290a82c7b52458ef2927e4d421a45/xxhash-3.7.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a169a036bed0995e090d1493b283cc2cc8a6f5046821086b843abefff80643bc", size = 32534, upload-time = "2026-04-25T11:10:29.01Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5f/4acfcd490db9780cf36c58534d828003c564cde5350220a1c783c4d10776/xxhash-3.7.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ec101643395d7f21405b640f728f6f627e6986557027d740f2f9b220955edafe", size = 31552, upload-time = "2026-04-25T11:10:30.727Z" }, ] [[package]]