From 65e79eee5b53f62af931fcebaecb34c86638bacd Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:34:05 +0200 Subject: [PATCH 01/55] feat(ltx-2.5): port the prompt-side AdaLN, and stop the opt-in from clearing it (#654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restores the prompt-side AdaLN path. `use_prompt_adaln_single` was set FALSE unconditionally at `ltx2_loader.cpp:988` while both references default it TRUE (`transformer_ltx2.py:1185`; `model_configurator.py:76,138`). The shipped DiT carries the tensors, our loader refused them by name, and a real render needs `allow_unported=1` — which silently cleared the flag. So every render ran with the timestep-conditioned half of the text conditioning removed, keeping only the static `prompt_scale_shift_table`. Measured on the SHIPPED checkpoint by the reviewer, not on the synthetic fixture: the timestep term is **1439% (video) / 1593% (audio)** of the static table by RMS. The term dominates; the table is the perturbation. Pre-row renders applied `context*(1 + ~0.018 rms) + ~0.018` where upstream applies `context*(1 + ~0.25 rms) + ~0.25`. The producer is `modality.sigma`, not `timesteps` (`transformer_args.py:274-277`) — a difference no shape check can see, so it has its own mutation. `allow_unported` is fixed structurally rather than documented: the three `= false` assignments are gone, replaced by an invariant that the resolved flag must equal whether the file carries `prompt_adaln_single`. A config disagreeing with the shapes is refused instead of forced into agreement, so the opt-in can no longer disable a ported feature. Five mutations RED on the right case each, plus two novel ones from the reviewer proving host and device are independently instrumented. Reviewed PASS. Operator gate on the merged tree: BUILD_EXIT=0, zero warnings, 423 of 423 ran, all four known -j flakes green serially. Owed as follow-up (#644): the spec and commit still quote the synthetic-fixture ratios; `ltx2.h:47-49` asserts the checkpoint does not carry `keyframes_abs_pos_embedding`, which is false for the FP8 DiT; and the model-matrix row still calls the prompt K/V timestep-INDEPENDENT. Issue: #644, #435 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/roadmap_v1.md | 1 + .agents/specs/ltx25-prompt-adaln.md | 361 +++++++++++++++ docs/FEATURES.md | 2 +- docs/USAGE.md | 11 +- include/vllm/model_executor/models/ltx2.h | 51 ++- .../vllm/model_executor/models/ltx2_loader.h | 45 +- include/vllm/multimodal/ltx2_video.h | 17 +- scripts/gen-ltx2-goldens.py | 151 +++++- src/vllm/model_executor/models/ltx2.cpp | 16 +- .../model_executor/models/ltx2_device.cpp | 79 +++- src/vllm/model_executor/models/ltx2_dit.cpp | 90 +++- .../model_executor/models/ltx2_loader.cpp | 67 ++- tests/vllm/models/ltx2_goldens.inc | 432 +++++++++++++++++- tests/vllm/models/test_ltx2.cpp | 141 ++++++ tests/vllm/models/test_ltx2_device.cpp | 88 +++- tests/vllm/models/test_ltx2_loader.cpp | 184 ++++++-- tests/vllm/multimodal/ltx2_video_fixture.h | 9 +- tests/vllm/multimodal/test_ltx2_video.cpp | 18 +- 18 files changed, 1640 insertions(+), 123 deletions(-) create mode 100644 .agents/specs/ltx25-prompt-adaln.md diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index dad79a7b9..90cacc594 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -147,6 +147,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#435](https://github.com/mudler/vllm.cpp/issues/435) | `ROAD-V1-LTX25` | LTX-2.5: port the 21B joint video+audio DiT and generalize the video seam | feature | | [#560](https://github.com/mudler/vllm.cpp/issues/560) | `ROAD-V1-LTX25` | Stabilizing constants unreachable by any gate: FIVE instances found by sweep, incl. the DiT's own `norm_eps` field default | bug | | [#567](https://github.com/mudler/vllm.cpp/issues/567) | `ROAD-V1-LTX25` | `OpNameImpl` makes a SECOND file every new `OpId` must edit; collapse the enum and its names into one derived list | bug | +| [#644](https://github.com/mudler/vllm.cpp/issues/644) | `ROAD-V1-LTX25` | LTX-2.5 FULL PORT campaign. Row 0 `LTX25-PROMPT-ADALN` (spec [`ltx25-prompt-adaln.md`](specs/ltx25-prompt-adaln.md)): `use_prompt_adaln_single` defaults TRUE in BOTH references and the shipped DiT carries its 18 tensors, but `ltx2_loader.cpp:988` cleared the flag unconditionally, so every render dropped the timestep-conditioned half of the prompt K/V modulation — invisible to every shape and finiteness check | feature | | [#238](https://github.com/mudler/vllm.cpp/issues/238) | `SAMPLE-LOGPROB-TOKEN-IDS` | `logprobs_mode`: three of four modes are runtime-refused stubs | bug | | [#264](https://github.com/mudler/vllm.cpp/issues/264) | `SAMPLE-LOGPROB-TOKEN-IDS` | `logprob_token_ids`: generative scoring over an explicit token set is unported | feature | | [#365](https://github.com/mudler/vllm.cpp/issues/365) | `PERF-27B-DENSE-MARLIN-GATEUP` | 27B gap decomposed vs vLLM's own decode profile; dense W4A16 MLP bypassed the fused gate_up seam (spec `specs/perf-27b-dense-marlin-gateup.md`) | bug | diff --git a/.agents/specs/ltx25-prompt-adaln.md b/.agents/specs/ltx25-prompt-adaln.md new file mode 100644 index 000000000..a24f127ce --- /dev/null +++ b/.agents/specs/ltx25-prompt-adaln.md @@ -0,0 +1,361 @@ +# LTX-2.5 — the prompt-side AdaLN path (`use_prompt_adaln_single`) + +Row: `LTX25-PROMPT-ADALN`. Campaign: [`ltx-2-5.md`](ltx-2-5.md) (operator-owned; not +edited by this row). Issue: +[#644](https://github.com/mudler/vllm.cpp/issues/644), row 0. + +Upstream pins: + +| Reference | Revision | +|---|---| +| Lightricks/LTX-2 (`packages/ltx-core`) | `fd4ded7f` | +| huggingface/diffusers | `3a2f35d4` | + +Both are read from local checkouts at those revisions, and the golden generator +IMPORTS and EXECUTES the LTX-2 modules rather than restating them. + +## 0. What is wrong today + +`src/vllm/model_executor/models/ltx2_loader.cpp:988` sets, unconditionally: + +```cpp +declared.use_prompt_adaln_single = false; +``` + +and `:573` / `:626` do the same on the two manifest paths. The flag defaults +**TRUE** in both references: + +- diffusers `src/diffusers/models/transformers/transformer_ltx2.py:1185` — + `use_prompt_adaln_single: bool = True` +- LTX-2 `packages/ltx-core/src/ltx_core/model/transformer/model.py:77` — same + default, and `model_configurator.py:76` / `:138` read it as + `config.get("use_prompt_adaln_single", True)` + +The shipped FP8 DiT carries the 18 tensors the flag builds (12 +`prompt_adaln_single.*`, 6 `audio_prompt_adaln_single.*`; see +`tests/vllm/models/ltx2_fp8_dit_manifest.inc:232-240,286-294`), so the flag is +TRUE for the checkpoint this campaign renders. `ltx2.cpp:274-276` refuses those +tensors by name, so a real render needs `allow_unported_modules=1` +(`src/vllm/multimodal/ltx2_video.cpp:570`) — which reaches the loader lines above +and **silently clears the flag**. + +Net effect: every render drops the timestep-conditioned half of the prompt K/V +modulation, keeping only the static `prompt_scale_shift_table`. Nothing observes +it: shapes are unchanged, values stay finite, and the goldens were generated with +`use_prompt_adaln_single=False` (`scripts/gen-ltx2-goldens.py:149`), so the gate +agrees with the defect. + +Campaign history this row does **not** re-derive: `ltx-2-5.md` §1.2 already +RETRACTED the "prompt K/V carry no timestep term" claim and recorded that the +shipped checkpoint carries a `[4096, 256]` prompt timestep embedder. What was +never closed is *using* the tensors. + +## 1. What upstream does, with anchors + +### 1.1 The module + +`model.py:222-227` (video) and `:252-257` (audio): + +```python +self.prompt_adaln_single = ( + AdaLayerNormSingle(self.inner_dim, embedding_coefficient=2) + if self.cross_attention_adaln and self.use_prompt_adaln_single + else None +) +``` + +`AdaLayerNormSingle` (`adaln.py:19-45`) is the same brick the port already has +(`Ltx2AdaLayerNormSingle`): `emb.timestep_embedder.linear_1 [dim, 256]`, +`linear_2 [dim, dim]`, `linear [coefficient * dim, dim]`. Coefficient **2** here, +not `adaln_embedding_coefficient()` — shift and scale for the K/V only. + +diffusers twin: `transformer_ltx2.py:1255-1259`, `num_mod_params=2`. + +Registration order inside `_init_video` puts it between `adaln_single` and +`proj_out`, which is where `EnumerateLtx2DitTensors` already reserves its slot +(the `VT_CHECK` at `ltx2.cpp:274-276`). + +### 1.2 The producer + +`transformer_args.py:274-277`, inside `TransformerArgsPreprocessor.prepare`: + +```python +prompt_timestep = None +if self.prompt_adaln is not None: + prompt_timestep, _ = self._prepare_timestep( + modality.sigma, self.prompt_adaln, batch_size, modality.latent.dtype + ) +``` + +Three things this fixes in one line, each of which a shape check cannot see: + +1. The input is **`modality.sigma`**, `(B,)` (`modality.py:54`) — the per-sample + scalar noise level — **not** `modality.timesteps`, which is per-token `(B, T)`. +2. `_prepare_timestep` (`transformer_args.py:173-186`) multiplies by + `timestep_scale_multiplier` before the embedder, exactly as the port's + `PrepareTimestep` already does for the main AdaLN. +3. The result is viewed to `(B, -1, 2 * dim)`, i.e. `(B, 1, 2 * dim)` — one row + broadcast over the prompt tokens. + +Wired into both preprocessor kinds at `model.py:313`, `:333` (multimodal) and +`:348`, `:364` (single-modality). + +diffusers twin: `transformer_ltx2.py:1536-1547`. diffusers passes `sigma` already +scaled from the pipeline (`pipeline_ltx2_image2video.py:1481` — `sigma=timestep`, +and `timestep` is the scheduler's 0..1000 value), so the two references agree on +the value reaching the embedder; only the place the x1000 happens differs. This +port mirrors LTX-2, so the multiply happens here. + +### 1.3 The consumer + +`transformer.py:427-447` (`apply_cross_attention_adaln`): + +```python +kv_modulation = prompt_scale_shift_table[None, None].to(...) # :441 +if prompt_timestep is not None: # :442 + kv_modulation = kv_modulation + prompt_timestep.reshape( + batch_size, prompt_timestep.shape[1], 2, -1) # :443 +shift_kv, scale_kv = kv_modulation.unbind(dim=2) # :444 +... +encoder_hidden_states = context * (1 + scale_kv) + shift_kv # :446 +``` + +Reached from `_apply_text_cross_attention` (`:223-251`), which is called for the +video stream at `:288-296` and the audio stream at `:317-325`, passing +`video.prompt_timestep` / `audio.prompt_timestep`. Every block, both streams. + +diffusers twin: `transformer_ltx2.py:677-693` (`get_mod_params` over +`prompt_scale_shift_table`), threaded at `:1648-1649`. + +Layout consequence: the flat `[B, 1, 2 * dim]` row is read as `[2, dim]` with +**shift first, scale second** — the same order the static table already uses in +`ModulateContext` (`ltx2_dit.cpp:118-129`). + +## 2. Scope + +**In.** + +1. `Ltx2DitParams::use_prompt_adaln_single` is honoured end to end: contract, + binding, host forward, device forward. +2. The 18 tensors enter `EnumerateLtx2DitTensors` / `BindLtx2DitWeights` when + `cross_attention_adaln && use_prompt_adaln_single`. +3. `temb_prompt` / `temb_prompt_audio` computed from each stream's own `sigma` + and threaded into every block's text cross-attention on both the host + (`ltx2_dit.cpp`) and device (`ltx2_device.cpp`) paths. +4. The three loader `= false` assignments are deleted, and replaced by a guard + (§3.2) that makes a future silent clear impossible. +5. The `ltx2.cpp:274-276` refusal is deleted for these two families. +6. Goldens executed from upstream at reduced dims, with a mutation proving the + new term is load-bearing, and a measured magnitude. + +**Out.** Anything the campaign already records as owed: keyframe absolute +position embedding (still genuinely unported, still what +`allow_unported_modules` is for), the caption projections, guidance +perturbations, and the bf16/FP8/NVFP4 stream dtypes on the host forward. + +## 3. Design + +### 3.1 The seam + +`Ltx2DitWeights` gains two `Ltx2AdaLayerNormSingleWeights` members; +`Ltx2BlockArgs` / `BlockArgsDev` gain a `[batch, 1, 2 * width]` prompt-modulation +pointer per stream, `nullptr` when the flag is off. `ModulateContext` and its +device twin take that pointer and add `prompt_mod[b, {0,1} * width + c]` to the +table row before applying `context * (1 + scale) + shift`. `nullptr` gives the +existing static-only behaviour byte-for-byte, which is what keeps every current +golden valid. + +The **order of the two additions** is upstream's: the table and the timestep row +are summed FIRST (`:443`), and only then does `(1 + scale)` apply. Folding it the +other way would round differently — the same trap `ProcessOutput` +(`ltx2_dit.cpp:530-540`) already documents. + +### 3.2 The prompt-K/V cache, and what replaces the cleared flag + +`Ltx2DitForward` already refuses a cache when the flag is on +(`ltx2_dit.cpp:672-676`); with the flag no longer cleared, that refusal becomes +*reachable* rather than dead, and it is correct: the K/V now carry a timestep +term. Nothing in the shipped pipeline passes a cache (`grep` over +`ltx2_pipeline.cpp` finds none), so no caller regresses. + +**What replaced the `= false`.** The loader clearing existed so +`EnumerateLtx2DitTensors` would not throw. With the tensors ported the contract +simply includes them, so the assignment has no job left. In its place the loader +asserts the invariant the clearing used to violate: + +> the resolved `use_prompt_adaln_single` must equal whether the FILE carries +> `prompt_adaln_single.linear.weight` + +A future edit that re-clears the flag then hits a named refusal instead of +quietly dropping 18 tensors. This is deliberately an *equality*, not a one-sided +check: clearing the flag with the tensors present is the defect this row fixes, +and setting it with the tensors absent would bind missing weights. + +### 3.3 `allow_unported_modules` + +After this row it is scoped to genuinely-unported modules only: the sole flag it +still clears in a config copy is `use_keyframes_abs_pos_embedding` +(`ltx2_loader.cpp:979-984`), whose module really is unported. The guard in §3.2 +is what makes that scoping structural rather than a comment — the extra `= false` +cannot come back without going red. + +`Ltx2AdoptDeclaredDitParams`'s contract-equality check becomes load-bearing in a +second way: a checkpoint whose config declares `use_prompt_adaln_single=false` +while its shapes carry the tensors now produces two DIFFERENT contracts and is +refused, instead of both sides being forced to the same cleared value. + +## 4. Memory format + +Mirrors the existing L2 parity forward exactly: f32 host, f32/bf16 device stream +with the `prompt_scale_shift_table` read at F32 (`ltx2_device.cpp:508-511`). The +prompt modulation is one `[batch, 1, 2 * width]` buffer per stream per forward — +`2 * 4096 * batch` floats for the video stream at full size, computed once +outside the block loop, not per block. No new per-token buffer, so no per-token +byte cost. + +## 5. Tests + +1. `EnumerateLtx2DitTensors` with the flag ON reproduces upstream + `named_parameters()` verbatim — names, order, ranks, dims — for a model built + with `use_prompt_adaln_single=True`. This is the 18-tensor contract. +2. Full dual-stream DiT forward, flag ON, against upstream's executed output. +3. **The mutation**: the same forward with the prompt-AdaLN contribution zeroed + must go RED against that golden. A term that is present but inert is not a + port. +4. The flag-OFF goldens stay byte-identical, proving the new path is off when + upstream's is off. +5. The prompt-K/V cache stays refused with the flag on (existing case). + +## 6. Measured magnitude + +Recorded in §Outcome: the relative change in the modulated prompt context and in +the DiT's outputs, flag ON vs OFF, at the reduced dimensions. This is the answer +to "does this matter"; a number below round-off would mean the term is inert and +the whole row is decoration. + +## 7. Risks + +- **The goldens agree with the defect.** Every existing LTX golden was generated + with the flag off, so no existing case can fail whatever this row does. The new + flag-ON case is the only instrument, which is why §5.3 mutates it rather than + asserting it. +- **`sigma` vs `timesteps`.** Using the per-token `timesteps` instead of the + per-sample `sigma` produces a same-shaped, finite, wrong result at batch 1 with + uniform timesteps. The golden runs a batch of 2 with per-token timesteps that + differ from sigma, so the two are distinguishable. +- **Order within the `[2, dim]` row.** Swapping shift and scale is finite and + same-shaped. The golden's random weights make it observable. + +## 8. Stop conditions + +- The flag-ON forward cannot be made to match upstream to the existing + `kRoundOff` bound: stop and report rather than widening the bound. +- The mutation in §5.3 stays green: the path is not reached, and the row is not + done. Escalate the mutation's magnitude before concluding anything about + reachability (issue #604). + +## Outcome + +### What was measured + +The generator emits these into `tests/vllm/models/ltx2_goldens.inc` and prints +them on stderr, from the SAME shared weight stream on both arms (keyed by +parameter name, so every common weight is bit-identical and the difference is the +term and nothing else): + +| Quantity | Flag ON vs OFF | +|---|---| +| **timestep term vs the static table it is added to** | `max\|term\|` 0.0252 vs `max\|table\|` 0.0487 — **51.7%** | +| **block-0 modulated prompt K/V** | `max\|on-off\|` 0.0310 — **5.82%** of `max\|off\|` | +| DiT video output (2 blocks) | 1.4567e-4 — 0.04% of `max\|off\|`, **73x** the gate's 2e-6 floor | +| DiT audio output (2 blocks) | 7.367e-5 — 0.03%, **37x** the floor | + +**The answer to "does this matter" is the first two rows.** Roughly half the +magnitude of the prompt K/V modulation is the timestep-conditioned term, and +including it moves the modulated prompt context by ~6%. Every render before this +row discarded that. + +The two output rows are the GATE's floor, not a claim about the trained +checkpoint: they are bounded by the generator's synthetic weight scale (0.05) and +by a 2-block stack rather than 48. They are reported because a mutation must be +shown to move something, and 73x/37x above round-off is what makes the mutation +below meaningful. + +### The mutations + +All five run on the committed head, restored byte-for-byte afterwards (source +md5s re-checked). Exit status is the authority; assertion COUNTS are recorded +because doctest's summary and the exit code disagree in both directions. + +| # | Mutation | Result | +|---|---|---| +| M1 | host `ModulateContext` ignores `prompt_mod` | RED — 3/35 cases, 6/2435 assertions, exit 1 | +| M2 | device `TextCrossAttentionDev` takes the static-only branch always | RED — 1/15 cases, 6/523 assertions, exit 1 | +| M3 | re-add `use_prompt_adaln_single = false` before the loader guard | RED — the guard throws by name; assertion count DROPS 4826 → 4815, exit 1 | +| M4 | prompt AdaLN driven by `m.timesteps` instead of `m.sigma` | RED — 2/35 cases, 4/2435 assertions, exit 1 | +| M5 | shift and scale rows swapped within the `[2, width]` row | RED — 2/35 cases, 4/2435 assertions, exit 1 | + +M3 is the one that cannot be reached by any INPUT, and that is stated rather than +papered over: `ParseLtx2DitParamsFromManifest` derives the flag from the same +manifest the guard reads, so they agree by construction unless an assignment +intervenes — which is exactly the edit the guard exists to catch. The +input-driven half of the same rule lives in `Ltx2AdoptDeclaredDitParams`, where a +config that disagrees with the shapes now produces two different contracts and is +refused; that one is gated by a test with real inputs in both directions. + +### The gate + +`BUILD_EXIT=0` on every build; build logs grepped for `No space left|BFD assertion` +(0 hits) and `df -h /` logged (88% used, 52G free at the end). Case AND assertion +counts against the `cefacd2d0` baseline, measured by reverting the working tree to +HEAD, rebuilding the four targets and running them, then re-applying the diff and +re-checking its md5 (`03324d42…`, identical before and after): + +| Suite | HEAD `cefacd2d0` | this row | delta | +|---|---|---|---| +| `test_ltx2` | 30 cases / 1627 assertions | 35 / 2435 | +5 cases, +808 assertions | +| `test_ltx2_loader` | 24 / 4817 | 26 / 4826 | +2 cases, +9 (new cases minus the assertions the retired unported-family claims took with them) | +| `test_ltx2_device` | 13 / 498 | 15 / 523 | +2 cases, +25 assertions | +| `test_ltx2_video` | 30 / 502 | 30 / 502 | unchanged — the fixture now carries the module, and no assertion counted it | + +The `test_ltx2_video` fixture had to move: it declared a config that omits +`use_prompt_adaln_single` (mirroring the shipped NVFP4 DiT) while its SHAPES said +false, so the config/shape equality check refused it — correctly. It now carries +the module, which is the shipped shape and puts the whole video engine on the new +path. + +### What was rejected + +- **Widening `modulate`'s kernel contract** with a `rows_per_src_row` divisor, to + express "one row per batch element broadcast over that element's tokens" in one + launch. Rejected: it changes a kernel's semantics for a dimension that is 1 or 2 + in every shipped call, and would owe its own red-before evidence. The device + path loops over batch and offsets the pointers instead. +- **Narrowing the static prompt table to the stream dtype** on the flag-ON path, + which is literally what upstream's `.to(dtype=x_normed.dtype)` does. Rejected as + out of scope: the existing static-only path deliberately keeps the table at F32 + (`ltx2_device.cpp`, "a narrowed table would be the dtype rule applied + backwards"), and changing that polarity is a separate decision. The flag-ON path + routes the sum through `ada_value`, which stores at the stream dtype — the same + rounding every other table+modulation sum in that file already has. + +### Why the defaults are what they are + +`Ltx2DitParams::use_prompt_adaln_single` keeps its `true` default, which is now +honoured rather than overwritten. It matches `model.py:77`, +`model_configurator.py:76`/`:138` and diffusers `transformer_ltx2.py:1185`, and +it matches both shipped DiTs: the FP8 file carries no config at all (so the +default decides), and the NVFP4 file's config OMITS the key — verified by reading +both headers off the NAS. So neither shipped checkpoint is refused by the new +config/shape equality check. + +`allow_unported_modules` keeps existing, because +`keyframes_abs_pos_embedding` is genuinely unported and a real render still needs +the opt-in for it. What changed is that it can no longer switch a ported feature +off: the loader asserts the flag against the file instead of clearing it, and +`Ltx2AdoptDeclaredDitParams` clears exactly one flag, for a module nothing +applies. + +## Now + +`DONE` — landed on `row/LTX25-PROMPT-ADALN`. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 7586e32ac..3c7ba3e68 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -161,7 +161,7 @@ in `ltx2_text_encoder.cpp` is the call that would have to change. | Voxtral audio (`VoxtralForConditionalGeneration`) | Voxtral-Mini-3B-2507 | near-tie-robust 16/16 vs vLLM 0.25.0 | decode 0.97x (beats vLLM); encoder FORWARD 15.90x of vLLM's whole TTFT (pin 46.02 ms), or 2.89x with opt-in `VT_WHISPER_ENC_FA2=1` (costs 3 near-tie divergences vs 0). Not a TTFT ratio. Pending | | Whisper audio encoder | openai/whisper-small; whisper-large-v3 (Voxtral cfg) | encoder tower 77/77; large-v3 tower 203/203 | pending | | MiniMax-H3 DiT (`MiniMaxH3DiTModel`, vllm-omni lane) | MiniMax-H3 (33.1B video+audio) | portable 79/79; all three modalities COHERENT on Q4_K_M (§8.20); PRUNED ckpts run, Q8_0 seam 0.9941 (§8.21); ref2va grid was NVFP4 quant error, §8.9 REFUTED; GGUF/NVFP4/bf16 shards stream | FP4/Marlin landed; speed pending; no bf16 render yet. Render from the Q4_K_M GGUF, not the NVFP4 arm. Krea 2 text-to-image (roadmap C11) is scoped to reuse these DiT seams | -| LTX-2.5 DiT (`LTX2VideoTransformer3DModel`, Lightricks lane) | LTX-2.5 (21.00B video+audio) | `SPIKE`. DiT, VAEs+ENCODERS, conditioning, pipeline, quant loaders gated at reduced dims. Typed prompt to Gemma-4 to cross-attn, FIXTURE-gated. The 320x192/25f scene was register-conditioned; a prompted render is OWED | Family `ltx-2.5` via `ltx2-gen`. ~29 GB NVFP4/GB10, FP8 ~44 GB, +~24 GB tower. FP8, torchao and first-party NVFP4 all load. DiffVAE, LoRA, image conditioning refused AT THE ENGINE. Speed PENDING | +| LTX-2.5 DiT (`LTX2VideoTransformer3DModel`, Lightricks lane) | LTX-2.5 (21.00B video+audio) | `SPIKE`. DiT, VAEs+ENCODERS, conditioning, pipeline, quant loaders gated at reduced dims. Prompt-side AdaLN ported, host+device. Typed prompt to Gemma-4 to cross-attn, FIXTURE-gated. A prompted render is OWED | Family `ltx-2.5`, `ltx2-gen`. ~29 GB NVFP4/GB10, FP8 ~44 GB, +~24 GB tower. FP8/torchao/1st-party NVFP4 load; `keyframes_abs_pos_embedding` alone needs `allow_unported`. DiffVAE, LoRA, image cond refused. Speed PENDING | | MTP speculator | Qwen3.6-27B, Qwen3.6-35B-A3B | token-identical to vLLM `mtp` at c1 | ~4% faster c1; +16% output tput (MoE) | | DFlash block-diffusion | Qwen3 (DFlash draft) | near-tie e2e 27/27 vs vLLM | 2.9x over spec-off, 1.003x vs vLLM DFlash-on | | DeepSeek-V4 MTP | DeepSeek-V4-Flash (nextn head) | lossless 5/5; real-model weight-blocked | pending | diff --git a/docs/USAGE.md b/docs/USAGE.md index 142cd6a17..4f80a43ef 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -2073,11 +2073,14 @@ other marker-less NVFP4 checkpoint as unsupported until it is. See `.agents/specs/nvfp4-nibble-order.md`. Two behaviours a caller has to know. `Ltx2LoadDitFromSafetensors` REFUSES the -shipped DiT by default, because that file carries **three** module families phase -L2 does not port (`prompt_adaln_single`, `audio_prompt_adaln_single` and -`keyframes_abs_pos_embedding`); pass `Ltx2DitLoadOptions::allow_unported_modules` +shipped DiT by default, because that file carries **one** module family this port +does not carry (`keyframes_abs_pos_embedding`); pass +`Ltx2DitLoadOptions::allow_unported_modules` to load the ported subset, which still reports every one of them in -`Ltx2DitCheckpoint::unported`. The two `*_embeddings_connector` towers are +`Ltx2DitCheckpoint::unported`. `prompt_adaln_single` and +`audio_prompt_adaln_single` were on that list until 2026-08-13 and are now +PORTED, so a checkpoint carrying them needs no opt-in on their account, and the +opt-in no longer disables them. The two `*_embeddings_connector` towers are **not** among them and never will be: `UnportedFamilies` filters them out at `ltx2_loader.cpp:439` (`LoadedElsewhere`), `RefuseUnported`'s own message says so in capitals at `ltx2_loader.cpp:461-464`, diff --git a/include/vllm/model_executor/models/ltx2.h b/include/vllm/model_executor/models/ltx2.h index f71a1a404..99898406b 100644 --- a/include/vllm/model_executor/models/ltx2.h +++ b/include/vllm/model_executor/models/ltx2.h @@ -51,8 +51,14 @@ // checkpoint: `caption_proj_before_connector=true` puts them in the TEXT // ENCODER, so the DiT has none (model_configurator.py:199-219). They are // phase L3. -// - `prompt_adaln_single` (model.py:223-227). LTX-2.5 sets -// `use_prompt_adaln_single=false`; see Ltx2PromptKvCache. +// +// PORTED 2026-08-13 — `prompt_adaln_single` / `audio_prompt_adaln_single` +// (model.py:222-227, :252-257), which this list previously carried as unported on +// the strength of "LTX-2.5 sets use_prompt_adaln_single=false". It does not: the +// flag defaults TRUE in both references (model.py:77, +// model_configurator.py:76/:138, diffusers transformer_ltx2.py:1185) and the +// shipped DiT carries the module's tensors. See +// .agents/specs/ltx25-prompt-adaln.md and issue #644. #pragma once #include @@ -112,8 +118,12 @@ struct Ltx2DitParams { bool double_precision_rope = false; bool apply_gated_attention = false; bool cross_attention_adaln = false; - // model_configurator.py:74-76. FALSE on LTX-2.5, which is what makes the - // cross-attention K/V timestep-independent — see Ltx2PromptKvCache. + // model_configurator.py:74-76 (`config.get("use_prompt_adaln_single", True)`), + // model.py:77, diffusers transformer_ltx2.py:1185 — TRUE by default in every + // reference, and TRUE for the shipped LTX-2.5 DiT, which carries the module's + // tensors. When true a prompt-side AdaLN MLP adds a timestep term to the + // cross-attention K/V modulation (transformer.py:441-443), which is what makes + // those K/V timestep-DEPENDENT and so uncacheable — see Ltx2PromptKvCache. bool use_prompt_adaln_single = true; // model_configurator.py:77-80. LTX-2.5 (gemma4) sets ff_bias=false and leaves // audio_ff_bias at its true default; the checkpoint's shapes agree. @@ -214,6 +224,11 @@ struct Ltx2BlockWeights { struct Ltx2DitWeights { Ltx2LinearWeight patchify_proj, proj_out; Ltx2AdaLayerNormSingleWeights adaln_single; + // model.py:222-227 / :252-257 — built only when `cross_attention_adaln AND + // use_prompt_adaln_single`, with embedding_coefficient 2 (shift + scale for the + // prompt K/V), NOT `adaln_embedding_coefficient()`. Left unbound otherwise. + Ltx2AdaLayerNormSingleWeights prompt_adaln_single; + Ltx2AdaLayerNormSingleWeights audio_prompt_adaln_single; vt::Tensor scale_shift_table; // [2, dim] — the OUTPUT table (:230), not the block's [9, dim] Ltx2LinearWeight audio_patchify_proj, audio_proj_out; Ltx2AdaLayerNormSingleWeights audio_adaln_single; @@ -295,13 +310,19 @@ Ltx2AdalnOut Ltx2AdaLayerNormSingle(vt::Device device, const Ltx2AdaLayerNormSin std::vector Ltx2FeedForward(vt::Device device, const Ltx2FeedForwardWeights& w, const float* x, int64_t rows, int64_t dim, int64_t inner); -// The K/V half of Attention.forward, split out because LTX-2.5 can CACHE it: -// with `use_prompt_adaln_single=false` the prompt modulation carries no timestep -// term (transformer.py:441), so `to_k`/`to_v` over the modulated context — and -// their k_norm, and the absence of RoPE on the text path — depend only on the -// prompt. The denoise loop computes them ONCE PER REQUEST and reuses them for -// every step. Layout: k/v are [batch * context_tokens, heads * dim_head], held -// exactly as the attention op consumes them (post-norm, post-RoPE). +// The K/V half of Attention.forward, split out because a checkpoint that sets +// `use_prompt_adaln_single=false` can CACHE it: the prompt modulation then +// carries no timestep term (transformer.py:441-443), so `to_k`/`to_v` over the +// modulated context — and their k_norm, and the absence of RoPE on the text path +// — depend only on the prompt. The denoise loop computes them ONCE PER REQUEST +// and reuses them for every step. Layout: k/v are +// [batch * context_tokens, heads * dim_head], held exactly as the attention op +// consumes them (post-norm, post-RoPE). +// +// THIS DOES NOT APPLY TO THE SHIPPED LTX-2.5 DiT, which sets the flag TRUE +// (.agents/specs/ltx-2-5.md §1.2, and .agents/specs/ltx25-prompt-adaln.md). The +// mechanism stays here, gated bit-identical, for a checkpoint that does set it +// false; `Ltx2DitForward` refuses a cache when the flag is on. struct Ltx2CrossKv { std::vector k, v; }; @@ -463,6 +484,14 @@ struct Ltx2BlockArgs { // Per-token AdaLN modulation, [batch, tokens, coefficient * dim]. const float* video_timestep_modulation = nullptr; const float* audio_timestep_modulation = nullptr; + // The PROMPT-side AdaLN modulation, [batch, 1, 2 * dim] — shift then scale, one + // row per batch element broadcast over the prompt tokens (transformer.py:443, + // whose `prompt_timestep` has token dimension 1 because `_prepare_timestep` ran + // on the modality's per-sample `sigma`). `nullptr` is upstream's + // `prompt_timestep is None`, i.e. `use_prompt_adaln_single=false`, in which case + // only the static per-block table applies (:441). + const float* video_prompt_modulation = nullptr; + const float* audio_prompt_modulation = nullptr; // Audio<->video cross-attention AdaLN inputs (transformer_args.py:388-411). const float* video_cross_scale_shift = nullptr; // [batch, video tokens, 4 * dim] const float* video_cross_gate = nullptr; // [batch, 1, dim] diff --git a/include/vllm/model_executor/models/ltx2_loader.h b/include/vllm/model_executor/models/ltx2_loader.h index 1c2da38a5..46aa57faa 100644 --- a/include/vllm/model_executor/models/ltx2_loader.h +++ b/include/vllm/model_executor/models/ltx2_loader.h @@ -98,27 +98,38 @@ // holds both copies at once, which is what wedged the box during MiniMax-H3's // port (minimax_h3.h:1598-1606). Same shape, same reason. // -// ─── WHAT THE SHIPPED DiT CARRIES THAT PHASE L2 DOES NOT PORT ──────────────── +// ─── WHAT THE SHIPPED DiT CARRIES THAT THIS PORT DOES NOT CARRY ────────────── // // MEASURED 2026-08-12 from the FP8 checkpoint's own header, and reported rather -// than absorbed. The file carries four families outside the L2 contract, and -// they fall into TWO groups that this comment used to conflate — corrected -// 2026-08-13, because the conflation is what made a downstream refusal state -// something untrue about the tree for a whole phase. +// than absorbed. The file carries four families outside the ORIGINAL L2 contract, +// and they fall into THREE groups. Two of them this comment once conflated +// (corrected 2026-08-13, because the conflation made a downstream refusal state +// something untrue about the tree for a whole phase); the third is a family that +// has since been ported. // // UNPORTED. `Ltx2LoadDitFromSafetensors` REFUSES the load by naming these, and // only an explicit `allow_unported_modules` — which exists so the ported subset // stays gateable — proceeds, still reporting every one of them in `unported`: // -// prompt_adaln_single.*, audio_prompt_adaln_single.* -// Upstream builds these only when `cross_attention_adaln AND -// use_prompt_adaln_single` (model.py:222-226, :253-257). Their presence -// means the shipped LTX-2.5 sets `use_prompt_adaln_single = TRUE`, which -// contradicts .agents/specs/ltx-2-5.md §1.2 and ltx2.h:115-117 — and with -// it the prompt-K/V "free win", whose whole premise is that the prompt -// modulation carries no timestep term. // keyframes_abs_pos_embedding [1, 4096] // So `use_keyframes_abs_pos_embedding = TRUE`, contradicting ltx2.h:47-49. +// This is now the ONLY flag `Ltx2AdoptDeclaredDitParams` clears in its +// config copy, and it must stay that way: a flag cleared there is invisible +// to the contract-equality check, so clearing a PORTED one silently drops +// its tensors. +// +// PORTED 2026-08-13 — no longer named in that refusal: +// +// prompt_adaln_single.*, audio_prompt_adaln_single.* +// Upstream builds these exactly when `cross_attention_adaln AND +// use_prompt_adaln_single` (model.py:222-226, :252-256), and the flag +// defaults TRUE in every reference (model.py:77, +// model_configurator.py:76/:138, diffusers transformer_ltx2.py:1185). Their +// presence means the shipped LTX-2.5 sets it TRUE — which is what +// .agents/specs/ltx-2-5.md §1.2 established and what row LTX25-PROMPT-ADALN +// (.agents/specs/ltx25-prompt-adaln.md, issue #644) implemented. The +// prompt-K/V cache stays correct-and-inapplicable for this checkpoint, and +// is refused by name rather than served. // // LOADED ELSEWHERE — NOT UNPORTED, and never named in that refusal: // @@ -515,11 +526,17 @@ nlohmann::json Ltx2ReadCheckpointConfig(const SafetensorsFile& file); // `Ltx2StreamDitToDevice` directly and therefore owes the same adoption. // // `allow_unported_modules` clears `use_keyframes_abs_pos_embedding` IN A COPY of -// the config before parsing, mirroring what the loader does for -// `use_prompt_adaln_single`: the flag is cleared for the CONTRACT, the module +// the config before parsing: the flag is cleared for the CONTRACT, the module // stays unported, and the checkpoint's `unported` list still names it. Without // the opt-in `ParseLtx2DitParams` throws, which is the refusal. // +// IT CLEARS EXACTLY THAT ONE FLAG, and the scoping is the rule, not an accident. +// It also used to clear `use_prompt_adaln_single`, whose module has been ported +// since 2026-08-13 — so the opt-in a real render REQUIRES was silently turning a +// correctness setting off, and the contract-equality check below could not see it +// because both sides had been forced to the same cleared value. A ported module's +// flag belongs in the contract; only a module nothing applies may be cleared here. +// // `source` names the config in every refusal, so a reader knows whether the // checkpoint declared it or a caller supplied it. Ltx2DitParams Ltx2AdoptDeclaredDitParams(const nlohmann::json& config, diff --git a/include/vllm/multimodal/ltx2_video.h b/include/vllm/multimodal/ltx2_video.h index fb683ce54..0953aebff 100644 --- a/include/vllm/multimodal/ltx2_video.h +++ b/include/vllm/multimodal/ltx2_video.h @@ -170,11 +170,20 @@ inline constexpr char kLtx2ModelVersionExtra[] = "model_version"; // belonging to another checkpoint is refused rather than bound. inline constexpr char kLtx2DitConfigPathExtra[] = "dit_config_path"; -// Proceed past the module families the L2 contract does not carry — -// `prompt_adaln_single` / `audio_prompt_adaln_single` and +// Proceed past the module families this port does not carry — // `keyframes_abs_pos_embedding` (ltx2_loader.h). "1" opts in; anything else -// leaves the loader's refusal in place. The shipped DiTs all carry at least one -// of them, so this is the flag that says "gate the ported subset knowingly". +// leaves the loader's refusal in place. The shipped DiTs carry it, so this is the +// flag that says "gate the ported subset knowingly". +// +// IT MUST NEVER DISABLE A PORTED FEATURE, and until 2026-08-13 it did: +// `prompt_adaln_single` / `audio_prompt_adaln_single` were on this list, and +// setting the extra reached three loader assignments that cleared +// `use_prompt_adaln_single`, so every real render dropped the timestep half of +// the prompt K/V modulation — finite, same-shaped, and invisible to every gate. +// Those families are ported now (.agents/specs/ltx25-prompt-adaln.md, issue +// #644), the loader asserts the flag against the file rather than clearing it, +// and this extra is scoped to the one module nothing applies. Adding a family +// here only ever means "the forward genuinely has no code for this". // // The two `*_embeddings_connector` families are NOT in that set and this extra // has nothing to do with them: they are outside the DiT contract by design and diff --git a/scripts/gen-ltx2-goldens.py b/scripts/gen-ltx2-goldens.py index 367cc4ddb..4c5d3e54b 100644 --- a/scripts/gen-ltx2-goldens.py +++ b/scripts/gen-ltx2-goldens.py @@ -316,10 +316,19 @@ def build_modalities(masked: bool, audio_enabled: bool = True, dense_self_mask: return video, audio -def build_model(rope_type_name: str, double_rope: bool): +def build_model(rope_type_name: str, double_rope: bool, prompt_adaln: bool | None = None): + """`prompt_adaln` overrides ARCH's `use_prompt_adaln_single` for the flag-ON arm. + + Every OTHER parameter keeps its name, and the deterministic stream is keyed by + NAME alone, so the two arms share their common weights bit-for-bit. That is + what makes the flag-ON and flag-OFF forwards directly subtractable, which is + how the magnitude in section 6 is measured. + """ from ltx_core.model.transformer.model import LTXModel, LTXModelType # noqa: PLC0415 from ltx_core.model.transformer.rope import LTXRopeType # noqa: PLC0415 + if prompt_adaln is None: + prompt_adaln = ARCH["use_prompt_adaln_single"] model = LTXModel( model_type=LTXModelType.AudioVideo, num_attention_heads=ARCH["num_attention_heads"], @@ -344,7 +353,7 @@ def build_model(rope_type_name: str, double_rope: bool): double_precision_rope=double_rope, apply_gated_attention=ARCH["apply_gated_attention"], cross_attention_adaln=ARCH["cross_attention_adaln"], - use_prompt_adaln_single=ARCH["use_prompt_adaln_single"], + use_prompt_adaln_single=prompt_adaln, ff_bias=ARCH["ff_bias"], audio_ff_bias=ARCH["audio_ff_bias"], ) @@ -617,12 +626,15 @@ def emit_bricks(out, model) -> None: def emit_forward( out, tag: str, rope_type_name: str, double_rope: bool, masked: bool, audio_enabled: bool = True, dense_self_mask: bool = False, + prompt_adaln: bool | None = None, ) -> None: out.write( f"// --- forward case {tag}: rope={rope_type_name} float64_freqs={double_rope} " - f"masked={masked} audio_enabled={audio_enabled} dense_self_mask={dense_self_mask} ---\n" + f"masked={masked} audio_enabled={audio_enabled} dense_self_mask={dense_self_mask} " + f"prompt_adaln={ARCH['use_prompt_adaln_single'] if prompt_adaln is None else prompt_adaln}" + " ---\n" ) - model = build_model(rope_type_name, double_rope) + model = build_model(rope_type_name, double_rope, prompt_adaln) video, audio = build_modalities(masked, audio_enabled, dense_self_mask) with torch.no_grad(): vx, ax = model(video=video, audio=audio, perturbations=None) @@ -631,6 +643,129 @@ def emit_forward( return model +def emit_prompt_adaln(out) -> None: + """Section 6 — the PROMPT-SIDE AdaLN arm (`use_prompt_adaln_single=True`). + + This is upstream's DEFAULT (model.py:77, model_configurator.py:76 and :138; + diffusers transformer_ltx2.py:1185) and what the shipped LTX-2.5 DiT carries. + Everything above this point runs the flag OFF, so nothing above can observe a + port that drops the term — which is exactly what happened. + + Emitted here: + * the flag-ON parameter list, i.e. the 18-tensor contract the flag adds + (12 video + 6 audio), in upstream's own registration order; + * the prompt AdaLN MLP run STANDALONE on `sigma * timestep_scale_multiplier` + (transformer_args.py:274-277 -> :173-186), so a failure localizes to the + MLP rather than to the threading; + * two full forwards, unmasked and masked; + * the MAGNITUDE: flag-ON minus flag-OFF over the same shared weights. + """ + out.write("// --- section 6: the prompt-side AdaLN arm (use_prompt_adaln_single=TRUE) ---\n") + model = build_model("split", False, prompt_adaln=True) + + names, ranks, dims = [], [], [] + for name, param in model.named_parameters(): + names.append(name) + ranks.append(len(param.shape)) + dims.extend(int(d) for d in param.shape) + out.write("inline constexpr const char* kLtx2PromptAdalnParamNames[] = {\n") + for name in names: + out.write(f' "{name}",\n') + out.write("};\n\n") + emit_i64(out, "kLtx2PromptAdalnParamRanks", ranks) + emit_i64(out, "kLtx2PromptAdalnParamDims", dims) + emit_scalar(out, "kLtx2PromptAdalnParamCount", len(names)) + out.write("\n") + + # The MLP alone. `_prepare_timestep` scales by timestep_scale_multiplier and + # feeds the modality's SIGMA -- (B,), one scalar per sample -- not its + # per-token `timesteps`. Emitting both halves keeps that distinction gateable. + scale = float(ARCH["timestep_scale_multiplier"]) + vsigma = rand_input("input.video.sigma", (BATCH,), 0.25, 0.5) * scale + asigma = rand_input("input.audio.sigma", (BATCH,), 0.25, 0.5) * scale + vmod, _ = model.prompt_adaln_single(vsigma.flatten(), hidden_dtype=torch.float32) + amod, _ = model.audio_prompt_adaln_single(asigma.flatten(), hidden_dtype=torch.float32) + emit_f32(out, "kLtx2PromptAdalnVideoTimesteps", tensor(vsigma)) + emit_f32(out, "kLtx2PromptAdalnAudioTimesteps", tensor(asigma)) + emit_f32(out, "kLtx2PromptAdalnVideoModulation", tensor(vmod)) + emit_f32(out, "kLtx2PromptAdalnAudioModulation", tensor(amod)) + + emit_forward(out, "PromptAdaln", "split", False, False, prompt_adaln=True) + emit_forward(out, "PromptAdalnMasked", "split", False, True, prompt_adaln=True) + + +def measure_prompt_adaln_magnitude() -> str: + """How far the conditioning MOVES when the term is included, vs when it is not. + + Both arms share every common parameter bit-for-bit (the stream is keyed by + parameter NAME), so the difference below is the term itself and nothing else. + Reported as a comment in the generated header AND on stderr, because "does + this matter" is a number, not an argument. + """ + lines = [] + off = build_model("split", False, prompt_adaln=False) + on = build_model("split", False, prompt_adaln=True) + video, audio = build_modalities(False) + with torch.no_grad(): + vx_off, ax_off = off(video=video, audio=audio, perturbations=None) + vx_on, ax_on = on(video=video, audio=audio, perturbations=None) + + def rel(a, b): + a32 = a.to(torch.float32) + b32 = b.to(torch.float32) + denom = float(a32.abs().max()) + return float((b32 - a32).abs().max()), ( + float((b32 - a32).abs().max()) / denom if denom > 0 else float("nan") + ) + + vabs, vrel = rel(vx_off, vx_on) + aabs, arel = rel(ax_off, ax_on) + lines.append(f"// DiT video output: max|on-off| = {vabs:.6g} ({vrel * 100:.2f}% of max|off|)") + lines.append(f"// DiT audio output: max|on-off| = {aabs:.6g} ({arel * 100:.2f}% of max|off|)") + + # The modulated prompt context of block 0, which is where the term enters. + scale = float(ARCH["timestep_scale_multiplier"]) + with torch.no_grad(): + vmod, _ = on.prompt_adaln_single( + (video.sigma * scale).flatten(), hidden_dtype=torch.float32 + ) + vmod = vmod.view(BATCH, -1, vmod.size(-1)) + table = on.transformer_blocks[0].prompt_scale_shift_table[None, None] + static = table.expand(BATCH, 1, 2, video_dim()) + full = static + vmod.reshape(BATCH, vmod.shape[1], 2, -1) + shift_off, scale_off = static.unbind(dim=2) + shift_on, scale_on = full.unbind(dim=2) + ctx = video.context + kv_off = ctx * (1 + scale_off) + shift_off + kv_on = ctx * (1 + scale_on) + shift_on + kabs, krel = rel(kv_off, kv_on) + lines.append( + f"// block 0 modulated prompt K/V: max|on-off| = {kabs:.6g} " + f"({krel * 100:.2f}% of max|off|)" + ) + # How much of the K/V modulation is timestep-conditioned at all: the MLP row + # against the static per-block table it is added to (transformer.py:441-443). + static_max = float(table.abs().max()) + term_max = float(vmod.abs().max()) + lines.append( + f"// timestep term vs static table: max|term| = {term_max:.6g} vs " + f"max|table| = {static_max:.6g} ({term_max / static_max * 100:.1f}%)" + ) + lines.append( + "// The two output rows are bounded by this generator's SYNTHETIC weight scale" + ) + lines.append( + "// (0.05, param_spec above) and by a 2-block stack; they are the FLOOR the gate" + ) + lines.append( + "// needs, not a claim about the trained checkpoint. The K/V row is where the" + ) + lines.append("// term actually enters and is the number that answers 'does this matter'.") + text = "\n".join(lines) + print("prompt-AdaLN magnitude:\n" + text, file=sys.stderr) + return text + + def emit_masks(out) -> None: out.write("// --- section 5: the prompt and self-attention masks the masked case runs ---\n") emit_i64(out, "kLtx2VideoContextMask", video_context_mask()) @@ -680,6 +815,14 @@ def main() -> int: # only this case — separates a kernel that indexes the bias by query from # one that reads bias row 0 for every query. emit_forward(out, "DenseMask", "split", False, True, dense_self_mask=True) + # Upstream's DEFAULT arm, and the one the shipped checkpoint runs. + emit_prompt_adaln(out) + out.write( + "// --- the MEASURED magnitude of the prompt-AdaLN term ---\n" + "// Same shared weights, same inputs, flag ON vs OFF:\n" + + measure_prompt_adaln_magnitude() + + "\n" + ) out.write("} // namespace vllm_test\n") print(f"wrote {args.out}", file=sys.stderr) return 0 diff --git a/src/vllm/model_executor/models/ltx2.cpp b/src/vllm/model_executor/models/ltx2.cpp index 7e5a4cffc..3d9af69ce 100644 --- a/src/vllm/model_executor/models/ltx2.cpp +++ b/src/vllm/model_executor/models/ltx2.cpp @@ -268,17 +268,21 @@ std::vector EnumerateLtx2DitTensors(const Ltx2DitParams& p) { out.push_back({"scale_shift_table", {2, dim}}); out.push_back({"audio_scale_shift_table", {2, adim}}); + // model.py:222-226 / :252-256 — built only when BOTH flags hold, and always + // with embedding_coefficient 2 (shift + scale for the prompt K/V), never + // `adaln_embedding_coefficient()`. + const bool prompt_adaln = p.cross_attention_adaln && p.use_prompt_adaln_single; + // _init_video (model.py:202-232), in child-registration order. PushLinear(out, "patchify_proj", dim, p.in_channels, true); PushAdaLayerNormSingle(out, "adaln_single", dim, coefficient); - VT_CHECK(!p.use_prompt_adaln_single, - "ltx2: use_prompt_adaln_single=true adds a prompt AdaLN MLP (model.py:223-227) whose " - "timestep term makes the cross-attention K/V uncacheable; not ported in phase L2"); + if (prompt_adaln) PushAdaLayerNormSingle(out, "prompt_adaln_single", dim, 2); PushLinear(out, "proj_out", p.out_channels, dim, true); // _init_audio (model.py:234-262). PushLinear(out, "audio_patchify_proj", adim, p.audio_in_channels, true); PushAdaLayerNormSingle(out, "audio_adaln_single", adim, coefficient); + if (prompt_adaln) PushAdaLayerNormSingle(out, "audio_prompt_adaln_single", adim, 2); PushLinear(out, "audio_proj_out", p.audio_out_channels, adim, true); // _init_audio_video (model.py:264-287); num_scale_shift_values is 4 (:133). @@ -482,9 +486,15 @@ Ltx2DitWeights BindLtx2DitWeights(const Ltx2DitParams& p, w.audio_scale_shift_table = Lookup(t, "audio_scale_shift_table"); w.patchify_proj = BindLinear(t, "patchify_proj", true); w.adaln_single = BindAdaln(t, "adaln_single"); + // model.py:222-226 / :252-256 — bound only when the module exists, exactly as + // the optional block tables above are. Left default-constructed otherwise, and + // the forward reads it only when `use_prompt_adaln_single` says it is there. + const bool prompt_adaln = p.cross_attention_adaln && p.use_prompt_adaln_single; + if (prompt_adaln) w.prompt_adaln_single = BindAdaln(t, "prompt_adaln_single"); w.proj_out = BindLinear(t, "proj_out", true); w.audio_patchify_proj = BindLinear(t, "audio_patchify_proj", true); w.audio_adaln_single = BindAdaln(t, "audio_adaln_single"); + if (prompt_adaln) w.audio_prompt_adaln_single = BindAdaln(t, "audio_prompt_adaln_single"); w.audio_proj_out = BindLinear(t, "audio_proj_out", true); w.av_ca_video_scale_shift = BindAdaln(t, "av_ca_video_scale_shift_adaln_single"); w.av_ca_audio_scale_shift = BindAdaln(t, "av_ca_audio_scale_shift_adaln_single"); diff --git a/src/vllm/model_executor/models/ltx2_device.cpp b/src/vllm/model_executor/models/ltx2_device.cpp index 3408007ab..e54436e4e 100644 --- a/src/vllm/model_executor/models/ltx2_device.cpp +++ b/src/vllm/model_executor/models/ltx2_device.cpp @@ -458,6 +458,10 @@ struct BlockArgsDev { bool video_enabled = true, audio_enabled = true; const Tensor* video_timestep_modulation = nullptr; const Tensor* audio_timestep_modulation = nullptr; + // The prompt-side AdaLN modulation, [batch, 2 * width] — nullptr is upstream's + // `prompt_timestep is None` (transformer.py:442), i.e. the flag is off. + const Tensor* video_prompt_modulation = nullptr; + const Tensor* audio_prompt_modulation = nullptr; const Tensor* video_cross_scale_shift = nullptr; const Tensor* video_cross_gate = nullptr; const Tensor* audio_cross_scale_shift = nullptr; @@ -480,6 +484,7 @@ struct BlockArgsDev { // device twin of ltx2_dit.cpp's TextCrossAttention. void TextCrossAttentionDev(Ctx& c, const Ltx2AttentionWeights& attn, const Tensor& sst, const Tensor& prompt_table, const Tensor& modulation, + const Tensor* prompt_modulation, const Tensor& x_normed, const Tensor& context, const Tensor* context_bias, int64_t batch, int64_t tokens, int64_t context_tokens, int64_t width, int64_t heads, @@ -501,18 +506,44 @@ void TextCrossAttentionDev(Ctx& c, const Ltx2AttentionWeights& attn, const Tenso c.s, c.s); // apply_cross_attention_adaln (transformer.py:420-447): the STATIC [2, dim] - // per-block table, with no timestep term at all when use_prompt_adaln_single is - // false (:441-443). `src_row_stride = 0` is that broadcast, and the table is - // read at F32 while the stream stays at c.s — which is the whole reason - // `modulate` carries a separate src_dtype. + // per-block table (:441), plus the prompt-side AdaLN row when the flag is on + // (:442-443). VT_CHECK(prompt_table.dtype == DType::kF32, "ltx2 device: the prompt scale-shift table is F32 in the checkpoint and is read as " "F32 here; a narrowed table would be the dtype rule applied backwards"); DBuf encoder(c.d, c.s, {batch * context_tokens, width}); c.d.b.Copy(c.d.q, encoder.ptr(), context.data, encoder.bytes()); - auto* table = prompt_table.Ptr(); - c.k->modulate(c.d.q, encoder.t().data, table + width, table, batch * context_tokens, width, 0, - c.s, DType::kF32); + if (prompt_modulation == nullptr) { + // No timestep term at all. `src_row_stride = 0` is the broadcast of the + // table's single row over every token, and the table is read at F32 while the + // stream stays at c.s — which is the whole reason `modulate` carries a + // separate src_dtype. + auto* table = prompt_table.Ptr(); + c.k->modulate(c.d.q, encoder.t().data, table + width, table, batch * context_tokens, width, 0, + c.s, DType::kF32); + } else { + // `kv_modulation = table[None, None] + prompt_timestep.reshape(B, 1, 2, -1)` + // (:441-443) is EXACTLY `ada_value`'s `table[row] + modulation[r, row]` over + // a 2-parameter modulation with one row per BATCH element, so the sum is + // formed here the way every other table+modulation sum in this file is — + // before `(1 + scale)` applies, which is the order upstream rounds in. + DBuf shift_kv = AdaValueDev(c, prompt_table, *prompt_modulation, batch, width, + /*num_params=*/2, /*index=*/0); + DBuf scale_kv = AdaValueDev(c, prompt_table, *prompt_modulation, batch, width, + /*num_params=*/2, /*index=*/1); + // `modulate`'s `src_row_stride` is a single stride, so it can broadcast ONE + // row over every token (stride 0) or give every row its own (stride width) — + // but not "row b for this batch element's context_tokens rows". The batch + // loop supplies that offset rather than widening the kernel's contract for a + // dimension that is 1 or 2 in every shipped call. + const int64_t elem = static_cast(vt::SizeOf(c.s)); + for (int64_t b = 0; b < batch; ++b) { + void* dst = static_cast(encoder.ptr()) + b * context_tokens * width * elem; + const void* sc = static_cast(scale_kv.t().data) + b * width * elem; + const void* sh = static_cast(shift_kv.t().data) + b * width * elem; + c.k->modulate(c.d.q, dst, sc, sh, context_tokens, width, 0, c.s, c.s); + } + } AttnArgsDev a; a.batch = batch; @@ -575,7 +606,8 @@ void BlockForwardDev(Ctx& c, const Ltx2BlockWeights& w, const BlockArgsDev& args RmsNormNoWeight(c, vx_normed->t(), *video_x, rows, dim); TextCrossAttentionDev(c, w.attn2, w.scale_shift_table, w.prompt_scale_shift_table, - *args.video_timestep_modulation, vx_normed->t(), *args.video_context, + *args.video_timestep_modulation, args.video_prompt_modulation, + vx_normed->t(), *args.video_context, args.video_context_bias, batch, tv, args.video_context_tokens, dim, c.p->num_attention_heads, c.p->attention_head_dim, *video_x); } @@ -609,6 +641,7 @@ void BlockForwardDev(Ctx& c, const Ltx2BlockWeights& w, const BlockArgsDev& args TextCrossAttentionDev(c, w.audio_attn2, w.audio_scale_shift_table, w.audio_prompt_scale_shift_table, *args.audio_timestep_modulation, + args.audio_prompt_modulation, ax_normed->t(), *args.audio_context, args.audio_context_bias, batch, ta, args.audio_context_tokens, adim, c.p->audio_num_attention_heads, c.p->audio_attention_head_dim, *audio_x); @@ -740,6 +773,8 @@ struct PreparedStreamDev { DevFreqs pe, cross_pe; std::optional cross_scale_shift; std::optional cross_gate; + // [batch, 2 * width] — empty when use_prompt_adaln_single is false. + std::optional prompt_modulation; }; // _prepare_timestep (transformer_args.py:173-186) + AdaLayerNormSingle. @@ -757,6 +792,7 @@ PreparedStreamDev PrepareStreamDev(Ctx& c, const Ltx2LinearWeight& patchify, const Ltx2AdaLayerNormSingleWeights& adaln, const Ltx2AdaLayerNormSingleWeights& cross_scale_shift_adaln, const Ltx2AdaLayerNormSingleWeights& cross_gate_adaln, + const Ltx2AdaLayerNormSingleWeights* prompt_adaln, const Ltx2ModalityInput& m, int64_t width, int64_t in_channels, int64_t n_pos_dims, const std::vector& max_pos, int64_t heads, const Ltx2ModalityInput* cross, @@ -774,6 +810,19 @@ PreparedStreamDev PrepareStreamDev(Ctx& c, const Ltx2LinearWeight& patchify, out.modulation = std::move(ada.modulation); out.embedded = std::move(ada.embedded); + // transformer_args.py:274-277 — the PROMPT-side AdaLN runs on this modality's + // own SIGMA, [batch], not on its per-token `timesteps`. + if (prompt_adaln != nullptr) { + VT_CHECK(m.sigma != nullptr, + "ltx2: use_prompt_adaln_single=true needs this modality's sigma " + "(transformer_args.py:274-277); it drives the prompt-side AdaLN MLP whose output is " + "added to the cross-attention K/V modulation, and a missing sigma would silently " + "fall back to the static table"); + AdalnOutDev prompt = PrepareTimestepDev(c, *prompt_adaln, m.sigma, m.batch, width, + c.p->timestep_scale_multiplier); + out.prompt_modulation = std::move(prompt.modulation); + } + if (m.context != nullptr && m.context_tokens > 0) { out.context = UploadStream(c.d, c.s, m.context, {m.batch * m.context_tokens, context_dim}); } @@ -892,6 +941,10 @@ void CheckWeightsResident(const Ltx2DitWeights& w, vt::Device dev) { CheckLinearResident(w.audio_proj_out, dev, "audio_proj_out"); CheckAdalnResident(w.adaln_single, dev, "adaln_single"); CheckAdalnResident(w.audio_adaln_single, dev, "audio_adaln_single"); + // Bound only when use_prompt_adaln_single is on; `CheckResident` no-ops on an + // unbound view, so this needs no flag and cannot go stale against one. + CheckAdalnResident(w.prompt_adaln_single, dev, "prompt_adaln_single"); + CheckAdalnResident(w.audio_prompt_adaln_single, dev, "audio_prompt_adaln_single"); CheckAdalnResident(w.av_ca_video_scale_shift, dev, "av_ca_video_scale_shift"); CheckAdalnResident(w.av_ca_audio_scale_shift, dev, "av_ca_audio_scale_shift"); CheckAdalnResident(w.av_ca_a2v_gate, dev, "av_ca_a2v_gate"); @@ -1044,14 +1097,18 @@ Ltx2DitOutputs Ltx2DitForwardDevice(vt::Queue& queue, const Ltx2DitParams& param OnesCache ones(d); Ctx c{d, compute_dtype, Glue(d), ¶ms, &ones}; + // model.py:222-226 / :252-256 — the module exists only when BOTH flags hold. + const bool prompt_adaln = params.cross_attention_adaln && params.use_prompt_adaln_single; PreparedStreamDev vs = PrepareStreamDev( c, weights.patchify_proj, weights.adaln_single, weights.av_ca_video_scale_shift, - weights.av_ca_a2v_gate, *video, dim, params.in_channels, 3, + weights.av_ca_a2v_gate, prompt_adaln ? &weights.prompt_adaln_single : nullptr, *video, dim, + params.in_channels, 3, params.positional_embedding_max_pos, params.num_attention_heads, audio, params.cross_attention_dim); PreparedStreamDev as = PrepareStreamDev( c, weights.audio_patchify_proj, weights.audio_adaln_single, weights.av_ca_audio_scale_shift, - weights.av_ca_v2a_gate, *audio, adim, params.audio_in_channels, 1, + weights.av_ca_v2a_gate, prompt_adaln ? &weights.audio_prompt_adaln_single : nullptr, *audio, + adim, params.audio_in_channels, 1, params.audio_positional_embedding_max_pos, params.audio_num_attention_heads, video, params.audio_cross_attention_dim); @@ -1066,6 +1123,8 @@ Ltx2DitOutputs Ltx2DitForwardDevice(vt::Queue& queue, const Ltx2DitParams& param a.audio_enabled = audio->enabled; a.video_timestep_modulation = &vs.modulation->t(); a.audio_timestep_modulation = &as.modulation->t(); + a.video_prompt_modulation = vs.prompt_modulation ? &vs.prompt_modulation->t() : nullptr; + a.audio_prompt_modulation = as.prompt_modulation ? &as.prompt_modulation->t() : nullptr; a.video_cross_scale_shift = vs.cross_scale_shift ? &vs.cross_scale_shift->t() : nullptr; a.video_cross_gate = vs.cross_gate ? &vs.cross_gate->t() : nullptr; a.audio_cross_scale_shift = as.cross_scale_shift ? &as.cross_scale_shift->t() : nullptr; diff --git a/src/vllm/model_executor/models/ltx2_dit.cpp b/src/vllm/model_executor/models/ltx2_dit.cpp index 0abd15fe3..b21655c64 100644 --- a/src/vllm/model_executor/models/ltx2_dit.cpp +++ b/src/vllm/model_executor/models/ltx2_dit.cpp @@ -114,18 +114,45 @@ void PostSelfAttention(float* x, const float* y, const std::vector& gate, } // apply_cross_attention_adaln (transformer.py:420-447). `prompt_table` is the -// STATIC [2, dim] per-block table; with use_prompt_adaln_single=false there is no -// timestep term at all (:441-443), which is exactly what makes the resulting K/V -// cacheable across denoise steps. +// STATIC [2, width] per-block table (:441). `prompt_mod` is the prompt-side AdaLN +// MLP's output for this stream, [batch, 2 * width] — shift row then scale row, +// one row per BATCH element because `_prepare_timestep` ran on the modality's +// per-sample `sigma` (transformer_args.py:274-277). It is nullptr exactly when +// upstream's `prompt_timestep is None` (:442), i.e. use_prompt_adaln_single=false +// — which is what makes the resulting K/V cacheable across denoise steps. +// +// ORDER. Upstream sums the table and the timestep row FIRST and only then +// applies `(1 + scale)` (:441-446). Folding the two additions the other way round +// would round differently, so the sum is materialized here as upstream forms it. std::vector ModulateContext(const float* context, const vt::Tensor& prompt_table, - int64_t rows, int64_t width) { - const float* shift_kv = prompt_table.Ptr(); - const float* scale_kv = prompt_table.Ptr() + width; - std::vector out(static_cast(rows * width)); - for (int64_t r = 0; r < rows; ++r) { - const float* src = context + r * width; - float* dst = out.data() + r * width; - for (int64_t c = 0; c < width; ++c) dst[c] = src[c] * (1.0f + scale_kv[c]) + shift_kv[c]; + const float* prompt_mod, int64_t batch, + int64_t context_tokens, int64_t width) { + const float* table_shift = prompt_table.Ptr(); + const float* table_scale = prompt_table.Ptr() + width; + std::vector out(static_cast(batch * context_tokens * width)); + std::vector shift_kv(static_cast(width)); + std::vector scale_kv(static_cast(width)); + for (int64_t b = 0; b < batch; ++b) { + for (int64_t c = 0; c < width; ++c) { + shift_kv[static_cast(c)] = table_shift[c]; + scale_kv[static_cast(c)] = table_scale[c]; + } + if (prompt_mod != nullptr) { + const float* m = prompt_mod + b * 2 * width; + for (int64_t c = 0; c < width; ++c) { + shift_kv[static_cast(c)] += m[c]; + scale_kv[static_cast(c)] += m[width + c]; + } + } + for (int64_t s = 0; s < context_tokens; ++s) { + const int64_t r = b * context_tokens + s; + const float* src = context + r * width; + float* dst = out.data() + r * width; + for (int64_t c = 0; c < width; ++c) { + dst[c] = src[c] * (1.0f + scale_kv[static_cast(c)]) + + shift_kv[static_cast(c)]; + } + } } return out; } @@ -148,7 +175,8 @@ void AddGatedBroadcast(float* x, const std::vector& y, const std::vector< void TextCrossAttention(vt::Device device, const Ltx2DitParams& params, const Ltx2AttentionWeights& attn, const vt::Tensor& sst, const vt::Tensor& prompt_table, const float* modulation, - const float* x_normed, const float* context, const float* context_bias, + const float* prompt_modulation, const float* x_normed, + const float* context, const float* context_bias, int64_t batch, int64_t tokens, int64_t context_tokens, int64_t width, int64_t heads, int64_t dim_head, const Ltx2CrossKv* kv_in, Ltx2CrossKv* kv_out, float* x) { @@ -174,7 +202,8 @@ void TextCrossAttention(vt::Device device, const Ltx2DitParams& params, // The modulated context is only needed when the K/V are actually recomputed. std::vector encoder; if (kv_in == nullptr) { - encoder = ModulateContext(context, prompt_table, batch * context_tokens, width); + encoder = ModulateContext(context, prompt_table, prompt_modulation, batch, context_tokens, + width); } Ltx2AttentionArgs a; @@ -250,7 +279,8 @@ void Ltx2TransformerBlockForward(vt::Device device, const Ltx2DitParams& params, PostSelfAttention(video_x, msa.data(), gate, batch * tv, dim, eps, &vx_normed); TextCrossAttention(device, params, w.attn2, w.scale_shift_table, w.prompt_scale_shift_table, - args.video_timestep_modulation, vx_normed.data(), args.video_context, + args.video_timestep_modulation, args.video_prompt_modulation, + vx_normed.data(), args.video_context, args.video_context_bias, batch, tv, args.video_context_tokens, dim, params.num_attention_heads, params.attention_head_dim, args.prompt_kv_filled ? args.video_prompt_kv : nullptr, @@ -287,6 +317,7 @@ void Ltx2TransformerBlockForward(vt::Device device, const Ltx2DitParams& params, TextCrossAttention(device, params, w.audio_attn2, w.audio_scale_shift_table, w.audio_prompt_scale_shift_table, args.audio_timestep_modulation, + args.audio_prompt_modulation, ax_normed.data(), args.audio_context, args.audio_context_bias, batch, ta, args.audio_context_tokens, adim, params.audio_num_attention_heads, params.audio_attention_head_dim, @@ -432,6 +463,7 @@ struct PreparedStream { Ltx2FreqsCis cross_pe; std::vector cross_scale_shift; // [batch, tokens, 4 * width] std::vector cross_gate; // [batch, 1, width] + std::vector prompt_modulation; // [batch, 1, 2 * width], empty when the flag is off }; // _prepare_timestep (transformer_args.py:173-186) + AdaLayerNormSingle. @@ -451,6 +483,7 @@ PreparedStream PrepareStream(vt::Device device, const Ltx2DitParams& params, const Ltx2AdaLayerNormSingleWeights& adaln, const Ltx2AdaLayerNormSingleWeights& cross_scale_shift_adaln, const Ltx2AdaLayerNormSingleWeights& cross_gate_adaln, + const Ltx2AdaLayerNormSingleWeights* prompt_adaln, const Ltx2ModalityInput& m, int64_t width, int64_t in_channels, int64_t n_pos_dims, const std::vector& max_pos, int64_t heads, const Ltx2ModalityInput* cross) { @@ -478,6 +511,21 @@ PreparedStream PrepareStream(vt::Device device, const Ltx2DitParams& params, PrepareTimestep(device, adaln, m.timesteps, rows, width, params.timestep_scale_multiplier, &out.modulation, &out.embedded); + // transformer_args.py:274-277 — the PROMPT-side AdaLN runs on this modality's + // own SIGMA, [batch], not on its per-token `timesteps`. `_prepare_timestep` + // applies the same timestep_scale_multiplier, and the result views to + // [batch, 1, 2 * width]: one row per sample, broadcast over the prompt tokens. + if (prompt_adaln != nullptr) { + VT_CHECK(m.sigma != nullptr, + "ltx2: use_prompt_adaln_single=true needs this modality's sigma " + "(transformer_args.py:274-277); it drives the prompt-side AdaLN MLP whose output is " + "added to the cross-attention K/V modulation, and a missing sigma would silently " + "fall back to the static table"); + std::vector unused; + PrepareTimestep(device, *prompt_adaln, m.sigma, m.batch, width, + params.timestep_scale_multiplier, &out.prompt_modulation, &unused); + } + if (m.context_mask != nullptr) { out.context_bias = Ltx2PrepareContextMask(m.context_mask, m.batch, m.context_tokens); } @@ -675,19 +723,25 @@ Ltx2DitOutputs Ltx2DitForward(vt::Device device, const Ltx2DitParams& params, "timestep term and caching them would be wrong"); } + // model.py:222-226 / :252-256 — the module exists only when BOTH flags hold, and + // `prompt_adaln=getattr(self, "prompt_adaln_single", None)` (:313, :333) is how + // upstream turns its absence into `prompt_timestep is None`. + const bool prompt_adaln = params.cross_attention_adaln && params.use_prompt_adaln_single; const bool have_both = video != nullptr && audio != nullptr; PreparedStream vs, as; if (video != nullptr) { VT_CHECK(video->context_tokens == 0 || video->context != nullptr, "ltx2: the video stream needs a context when context_tokens > 0"); vs = PrepareStream(device, params, weights.patchify_proj, weights.adaln_single, - weights.av_ca_video_scale_shift, weights.av_ca_a2v_gate, *video, dim, + weights.av_ca_video_scale_shift, weights.av_ca_a2v_gate, + prompt_adaln ? &weights.prompt_adaln_single : nullptr, *video, dim, params.in_channels, 3, params.positional_embedding_max_pos, params.num_attention_heads, have_both ? audio : nullptr); } if (audio != nullptr) { as = PrepareStream(device, params, weights.audio_patchify_proj, weights.audio_adaln_single, - weights.av_ca_audio_scale_shift, weights.av_ca_v2a_gate, *audio, adim, + weights.av_ca_audio_scale_shift, weights.av_ca_v2a_gate, + prompt_adaln ? &weights.audio_prompt_adaln_single : nullptr, *audio, adim, params.audio_in_channels, 1, params.audio_positional_embedding_max_pos, params.audio_num_attention_heads, have_both ? video : nullptr); } @@ -719,6 +773,10 @@ Ltx2DitOutputs Ltx2DitForward(vt::Device device, const Ltx2DitParams& params, a.audio_enabled = audio != nullptr && audio->enabled; a.video_timestep_modulation = vs.modulation.empty() ? nullptr : vs.modulation.data(); a.audio_timestep_modulation = as.modulation.empty() ? nullptr : as.modulation.data(); + a.video_prompt_modulation = + vs.prompt_modulation.empty() ? nullptr : vs.prompt_modulation.data(); + a.audio_prompt_modulation = + as.prompt_modulation.empty() ? nullptr : as.prompt_modulation.data(); a.video_cross_scale_shift = vs.cross_scale_shift.empty() ? nullptr : vs.cross_scale_shift.data(); a.video_cross_gate = vs.cross_gate.empty() ? nullptr : vs.cross_gate.data(); a.audio_cross_scale_shift = as.cross_scale_shift.empty() ? nullptr : as.cross_scale_shift.data(); diff --git a/src/vllm/model_executor/models/ltx2_loader.cpp b/src/vllm/model_executor/models/ltx2_loader.cpp index 946771019..bfc85075c 100644 --- a/src/vllm/model_executor/models/ltx2_loader.cpp +++ b/src/vllm/model_executor/models/ltx2_loader.cpp @@ -540,18 +540,53 @@ std::vector ContractOf(const Ltx2DitParams& params) { list += families[i]; } Fail( - "the checkpoint carries modules phase L2 does NOT port: " + list + - ". They are not dropped silently: prompt_adaln_single / " - "audio_prompt_adaln_single mean use_prompt_adaln_single is TRUE, which " - "contradicts .agents/specs/ltx-2-5.md section 1.2 and voids the prompt-K/V " - "cache's premise; keyframes_abs_pos_embedding contradicts ltx2.h:47-49. The " - "two *_embeddings_connector families are NOT in this list and never will be " - "— they are outside the DiT contract by design and are loaded by " + "the checkpoint carries modules this port does NOT carry: " + list + + ". They are not dropped silently: keyframes_abs_pos_embedding means " + "use_keyframes_abs_pos_embedding is TRUE, and nothing here applies it. " + "prompt_adaln_single / audio_prompt_adaln_single are NO LONGER in this list " + "— they were ported by row LTX25-PROMPT-ADALN " + "(.agents/specs/ltx25-prompt-adaln.md, issue #644) and are now part of the " + "contract whenever the checkpoint carries them. The two " + "*_embeddings_connector families are not in this list either and never will " + "be — they are outside the DiT contract by design and are loaded by " "Ltx2LoadConnectorWeights, which is what the video engine calls. Pass " "Ltx2DitLoadOptions::allow_unported_modules to load the ported SUBSET, which " "still reports every one of them."); } +// THE GUARD THAT REPLACED THREE `use_prompt_adaln_single = false` ASSIGNMENTS. +// +// Those assignments existed only so `EnumerateLtx2DitTensors` would not throw on +// a module this port did not carry. The module is carried now, so the contract +// simply includes it — and the assignments would have become a silent DROP of the +// module's 12 parameters (18 entries in the shipped FP8 manifest, which carries a +// `weight_scale` per quantized weight), reachable through +// `allow_unported_modules=1`, which is exactly the defect issue #644 row 0 fixes. +// +// So the invariant they violated is asserted instead: the resolved flag must say +// what the FILE says. Deliberately an EQUALITY. Clearing it with the tensors +// present is the old defect; setting it with them absent would bind weights that +// are not there. Either way this refuses by name rather than rendering. +void CheckPromptAdalnAgreesWithFile(const DitPlan& plan, const Ltx2DitParams& params, + const char* where) { + bool file_has = false; + for (const Ltx2TensorSpec& spec : plan.manifest) { + if (spec.name == "prompt_adaln_single.linear.weight") { + file_has = true; + break; + } + } + if (file_has == params.use_prompt_adaln_single) return; + Fail(std::string(where) + ": use_prompt_adaln_single resolved to " + + (params.use_prompt_adaln_single ? "TRUE" : "FALSE") + " while the file " + + (file_has ? "DOES" : "does NOT") + + " carry prompt_adaln_single. Upstream builds that module exactly when the flag is set " + "(model.py:222-226), so the two cannot disagree. A FALSE flag over a file that carries " + "the module would drop the timestep term from every cross-attention K/V modulation " + "(transformer.py:441-443) and render with only the static table — finite, same-shaped, " + "and invisible to every gate, which is why this is checked rather than assumed."); +} + } // namespace Ltx2DitParams Ltx2ParseDitParamsFromCheckpoint(const SafetensorsFile& file, @@ -568,9 +603,7 @@ Ltx2DitCheckpoint Ltx2LoadDitFromSafetensors(const SafetensorsFile& file, out.quant = plan.quant; out.checkpoint_params = ParseLtx2DitParamsFromManifest(plan.manifest); out.params = out.checkpoint_params; - // The one flag whose module this port does not carry. Cleared for the CONTRACT - // only; `checkpoint_params` keeps what the file actually says. - out.params.use_prompt_adaln_single = false; + CheckPromptAdalnAgreesWithFile(plan, out.params, "Ltx2LoadDitFromSafetensors"); const std::vector contract = ContractOf(out.params); out.unported = UnportedFamilies(plan, contract); @@ -623,7 +656,7 @@ Ltx2DitCheckpoint Ltx2StreamDitToDevice(vt::Queue& queue, const SafetensorsFile& out.quant = plan.quant; out.checkpoint_params = ParseLtx2DitParamsFromManifest(plan.manifest); out.params = out.checkpoint_params; - out.params.use_prompt_adaln_single = false; + CheckPromptAdalnAgreesWithFile(plan, out.params, "Ltx2StreamDitToDevice"); const std::vector contract = ContractOf(out.params); out.unported = UnportedFamilies(plan, contract); @@ -976,16 +1009,20 @@ Ltx2DitParams Ltx2AdoptDeclaredDitParams(const nlohmann::json& config, // first-party LTX-2.5 DiT declares it, so reading the declared config verbatim // would refuse a real checkpoint the loader has just accepted under // `allow_unported_modules`. + // + // EXACTLY ONE FLAG, and that is now structural rather than a comment. This block + // also cleared `use_prompt_adaln_single`, whose module IS ported + // (.agents/specs/ltx25-prompt-adaln.md, issue #644) — so `allow_unported=1`, + // which a real render needs, silently turned off a correctness setting. Whatever + // is cleared here must be a module nothing below applies; a ported one belongs + // in the contract, where the equality check further down can see it. if (allow_unported_modules && copy.contains("transformer") && copy["transformer"].is_object()) { copy["transformer"]["use_keyframes_abs_pos_embedding"] = false; } nlohmann::json wrapper; wrapper["config"] = copy; - Ltx2DitParams declared = ParseLtx2DitParams(wrapper); - // The one flag the L2 contract clears, mirroring the manifest path above - // ("cleared for the CONTRACT only"). - declared.use_prompt_adaln_single = false; + const Ltx2DitParams declared = ParseLtx2DitParams(wrapper); const std::vector a = EnumerateLtx2DitTensors(from_shapes); const std::vector b = EnumerateLtx2DitTensors(declared); diff --git a/tests/vllm/models/ltx2_goldens.inc b/tests/vllm/models/ltx2_goldens.inc index ff1c964eb..964047788 100644 --- a/tests/vllm/models/ltx2_goldens.inc +++ b/tests/vllm/models/ltx2_goldens.inc @@ -2583,7 +2583,7 @@ inline constexpr float kLtx2AudioSelfMaskDense[] = { }; // --- section 4: the full DiT forward --- -// --- forward case Split: rope=split float64_freqs=False masked=False audio_enabled=True dense_self_mask=False --- +// --- forward case Split: rope=split float64_freqs=False masked=False audio_enabled=True dense_self_mask=False prompt_adaln=False --- inline constexpr float kLtx2ForwardSplitVideo[] = { -0.112128824f, -0.0224671941f, -0.0198019743f, -0.0801180974f, -0.00492008496f, -0.0997069329f, -0.141085327f, -0.110280119f, 0.242049828f, 0.0161605272f, 0.0691268668f, -0.128027037f, @@ -2622,7 +2622,7 @@ inline constexpr float kLtx2ForwardSplitAudio[] = { -0.0946981385f, 0.0834118277f, 0.000482047704f, -0.244372234f, 0.0668168291f, 0.076439999f, }; -// --- forward case Interleaved: rope=interleaved float64_freqs=False masked=False audio_enabled=True dense_self_mask=False --- +// --- forward case Interleaved: rope=interleaved float64_freqs=False masked=False audio_enabled=True dense_self_mask=False prompt_adaln=False --- inline constexpr float kLtx2ForwardInterleavedVideo[] = { -0.111679666f, -0.0226908159f, -0.019702062f, -0.0793902054f, -0.00504480582f, -0.0992663726f, -0.141819194f, -0.110411108f, 0.240116999f, 0.0159209836f, 0.0681923628f, -0.130362436f, @@ -2661,7 +2661,7 @@ inline constexpr float kLtx2ForwardInterleavedAudio[] = { -0.0936588645f, 0.0829930827f, -0.000500017079f, -0.244381592f, 0.0684131756f, 0.0764773339f, }; -// --- forward case Double: rope=split float64_freqs=True masked=False audio_enabled=True dense_self_mask=False --- +// --- forward case Double: rope=split float64_freqs=True masked=False audio_enabled=True dense_self_mask=False prompt_adaln=False --- inline constexpr float kLtx2ForwardDoubleVideo[] = { -0.112128653f, -0.0224669855f, -0.0198022574f, -0.0801180974f, -0.00491989311f, -0.0997067019f, -0.141085491f, -0.110280029f, 0.242049828f, 0.0161606614f, 0.0691268519f, -0.128027067f, @@ -2700,7 +2700,7 @@ inline constexpr float kLtx2ForwardDoubleAudio[] = { -0.0946978033f, 0.0834115744f, 0.000482207892f, -0.244371802f, 0.0668166652f, 0.0764404312f, }; -// --- forward case Masked: rope=split float64_freqs=False masked=True audio_enabled=True dense_self_mask=False --- +// --- forward case Masked: rope=split float64_freqs=False masked=True audio_enabled=True dense_self_mask=False prompt_adaln=False --- inline constexpr float kLtx2ForwardMaskedVideo[] = { -0.110146172f, -0.0195347276f, -0.0187677145f, -0.0824092105f, -0.00312865246f, -0.102393486f, -0.140672266f, -0.111012168f, 0.238716885f, 0.0195573885f, 0.0744788125f, -0.129781172f, @@ -2739,7 +2739,7 @@ inline constexpr float kLtx2ForwardMaskedAudio[] = { -0.0945852846f, 0.0829883665f, 0.000665711937f, -0.24394846f, 0.0671078935f, 0.0765393525f, }; -// --- forward case AudioOff: rope=split float64_freqs=False masked=False audio_enabled=False dense_self_mask=False --- +// --- forward case AudioOff: rope=split float64_freqs=False masked=False audio_enabled=False dense_self_mask=False prompt_adaln=False --- inline constexpr float kLtx2ForwardAudioOffVideo[] = { -0.11211022f, -0.0225408655f, -0.0197366029f, -0.0800089315f, -0.00495309103f, -0.099537693f, -0.141150832f, -0.110280804f, 0.242115393f, 0.0160462279f, 0.0691971257f, -0.127955094f, @@ -2778,7 +2778,7 @@ inline constexpr float kLtx2ForwardAudioOffAudio[] = { -0.0867422372f, 0.0840105489f, -0.000306644681f, -0.244602948f, 0.0686045066f, 0.0746730566f, }; -// --- forward case DenseMask: rope=split float64_freqs=False masked=True audio_enabled=True dense_self_mask=True --- +// --- forward case DenseMask: rope=split float64_freqs=False masked=True audio_enabled=True dense_self_mask=True prompt_adaln=False --- inline constexpr float kLtx2ForwardDenseMaskVideo[] = { -0.112229049f, -0.0196566191f, -0.0203980505f, -0.08254648f, -0.00351214409f, -0.102313384f, -0.139395878f, -0.10990762f, 0.244468585f, 0.0156435464f, 0.0681985766f, -0.126972139f, @@ -2817,4 +2817,424 @@ inline constexpr float kLtx2ForwardDenseMaskAudio[] = { -0.0944952741f, 0.0835703015f, 6.12687072e-05f, -0.244034111f, 0.0671091005f, 0.0762160644f, }; +// --- section 6: the prompt-side AdaLN arm (use_prompt_adaln_single=TRUE) --- +inline constexpr const char* kLtx2PromptAdalnParamNames[] = { + "scale_shift_table", + "audio_scale_shift_table", + "patchify_proj.weight", + "patchify_proj.bias", + "adaln_single.emb.timestep_embedder.linear_1.weight", + "adaln_single.emb.timestep_embedder.linear_1.bias", + "adaln_single.emb.timestep_embedder.linear_2.weight", + "adaln_single.emb.timestep_embedder.linear_2.bias", + "adaln_single.linear.weight", + "adaln_single.linear.bias", + "prompt_adaln_single.emb.timestep_embedder.linear_1.weight", + "prompt_adaln_single.emb.timestep_embedder.linear_1.bias", + "prompt_adaln_single.emb.timestep_embedder.linear_2.weight", + "prompt_adaln_single.emb.timestep_embedder.linear_2.bias", + "prompt_adaln_single.linear.weight", + "prompt_adaln_single.linear.bias", + "proj_out.weight", + "proj_out.bias", + "audio_patchify_proj.weight", + "audio_patchify_proj.bias", + "audio_adaln_single.emb.timestep_embedder.linear_1.weight", + "audio_adaln_single.emb.timestep_embedder.linear_1.bias", + "audio_adaln_single.emb.timestep_embedder.linear_2.weight", + "audio_adaln_single.emb.timestep_embedder.linear_2.bias", + "audio_adaln_single.linear.weight", + "audio_adaln_single.linear.bias", + "audio_prompt_adaln_single.emb.timestep_embedder.linear_1.weight", + "audio_prompt_adaln_single.emb.timestep_embedder.linear_1.bias", + "audio_prompt_adaln_single.emb.timestep_embedder.linear_2.weight", + "audio_prompt_adaln_single.emb.timestep_embedder.linear_2.bias", + "audio_prompt_adaln_single.linear.weight", + "audio_prompt_adaln_single.linear.bias", + "audio_proj_out.weight", + "audio_proj_out.bias", + "av_ca_video_scale_shift_adaln_single.emb.timestep_embedder.linear_1.weight", + "av_ca_video_scale_shift_adaln_single.emb.timestep_embedder.linear_1.bias", + "av_ca_video_scale_shift_adaln_single.emb.timestep_embedder.linear_2.weight", + "av_ca_video_scale_shift_adaln_single.emb.timestep_embedder.linear_2.bias", + "av_ca_video_scale_shift_adaln_single.linear.weight", + "av_ca_video_scale_shift_adaln_single.linear.bias", + "av_ca_audio_scale_shift_adaln_single.emb.timestep_embedder.linear_1.weight", + "av_ca_audio_scale_shift_adaln_single.emb.timestep_embedder.linear_1.bias", + "av_ca_audio_scale_shift_adaln_single.emb.timestep_embedder.linear_2.weight", + "av_ca_audio_scale_shift_adaln_single.emb.timestep_embedder.linear_2.bias", + "av_ca_audio_scale_shift_adaln_single.linear.weight", + "av_ca_audio_scale_shift_adaln_single.linear.bias", + "av_ca_a2v_gate_adaln_single.emb.timestep_embedder.linear_1.weight", + "av_ca_a2v_gate_adaln_single.emb.timestep_embedder.linear_1.bias", + "av_ca_a2v_gate_adaln_single.emb.timestep_embedder.linear_2.weight", + "av_ca_a2v_gate_adaln_single.emb.timestep_embedder.linear_2.bias", + "av_ca_a2v_gate_adaln_single.linear.weight", + "av_ca_a2v_gate_adaln_single.linear.bias", + "av_ca_v2a_gate_adaln_single.emb.timestep_embedder.linear_1.weight", + "av_ca_v2a_gate_adaln_single.emb.timestep_embedder.linear_1.bias", + "av_ca_v2a_gate_adaln_single.emb.timestep_embedder.linear_2.weight", + "av_ca_v2a_gate_adaln_single.emb.timestep_embedder.linear_2.bias", + "av_ca_v2a_gate_adaln_single.linear.weight", + "av_ca_v2a_gate_adaln_single.linear.bias", + "transformer_blocks.0.scale_shift_table", + "transformer_blocks.0.audio_scale_shift_table", + "transformer_blocks.0.scale_shift_table_a2v_ca_audio", + "transformer_blocks.0.scale_shift_table_a2v_ca_video", + "transformer_blocks.0.prompt_scale_shift_table", + "transformer_blocks.0.audio_prompt_scale_shift_table", + "transformer_blocks.0.attn1.q_norm.weight", + "transformer_blocks.0.attn1.k_norm.weight", + "transformer_blocks.0.attn1.to_q.weight", + "transformer_blocks.0.attn1.to_q.bias", + "transformer_blocks.0.attn1.to_k.weight", + "transformer_blocks.0.attn1.to_k.bias", + "transformer_blocks.0.attn1.to_v.weight", + "transformer_blocks.0.attn1.to_v.bias", + "transformer_blocks.0.attn1.to_gate_logits.weight", + "transformer_blocks.0.attn1.to_gate_logits.bias", + "transformer_blocks.0.attn1.to_out.0.weight", + "transformer_blocks.0.attn1.to_out.0.bias", + "transformer_blocks.0.attn2.q_norm.weight", + "transformer_blocks.0.attn2.k_norm.weight", + "transformer_blocks.0.attn2.to_q.weight", + "transformer_blocks.0.attn2.to_q.bias", + "transformer_blocks.0.attn2.to_k.weight", + "transformer_blocks.0.attn2.to_k.bias", + "transformer_blocks.0.attn2.to_v.weight", + "transformer_blocks.0.attn2.to_v.bias", + "transformer_blocks.0.attn2.to_gate_logits.weight", + "transformer_blocks.0.attn2.to_gate_logits.bias", + "transformer_blocks.0.attn2.to_out.0.weight", + "transformer_blocks.0.attn2.to_out.0.bias", + "transformer_blocks.0.ff.net.0.proj.weight", + "transformer_blocks.0.ff.net.2.weight", + "transformer_blocks.0.audio_attn1.q_norm.weight", + "transformer_blocks.0.audio_attn1.k_norm.weight", + "transformer_blocks.0.audio_attn1.to_q.weight", + "transformer_blocks.0.audio_attn1.to_q.bias", + "transformer_blocks.0.audio_attn1.to_k.weight", + "transformer_blocks.0.audio_attn1.to_k.bias", + "transformer_blocks.0.audio_attn1.to_v.weight", + "transformer_blocks.0.audio_attn1.to_v.bias", + "transformer_blocks.0.audio_attn1.to_gate_logits.weight", + "transformer_blocks.0.audio_attn1.to_gate_logits.bias", + "transformer_blocks.0.audio_attn1.to_out.0.weight", + "transformer_blocks.0.audio_attn1.to_out.0.bias", + "transformer_blocks.0.audio_attn2.q_norm.weight", + "transformer_blocks.0.audio_attn2.k_norm.weight", + "transformer_blocks.0.audio_attn2.to_q.weight", + "transformer_blocks.0.audio_attn2.to_q.bias", + "transformer_blocks.0.audio_attn2.to_k.weight", + "transformer_blocks.0.audio_attn2.to_k.bias", + "transformer_blocks.0.audio_attn2.to_v.weight", + "transformer_blocks.0.audio_attn2.to_v.bias", + "transformer_blocks.0.audio_attn2.to_gate_logits.weight", + "transformer_blocks.0.audio_attn2.to_gate_logits.bias", + "transformer_blocks.0.audio_attn2.to_out.0.weight", + "transformer_blocks.0.audio_attn2.to_out.0.bias", + "transformer_blocks.0.audio_ff.net.0.proj.weight", + "transformer_blocks.0.audio_ff.net.0.proj.bias", + "transformer_blocks.0.audio_ff.net.2.weight", + "transformer_blocks.0.audio_ff.net.2.bias", + "transformer_blocks.0.audio_to_video_attn.q_norm.weight", + "transformer_blocks.0.audio_to_video_attn.k_norm.weight", + "transformer_blocks.0.audio_to_video_attn.to_q.weight", + "transformer_blocks.0.audio_to_video_attn.to_q.bias", + "transformer_blocks.0.audio_to_video_attn.to_k.weight", + "transformer_blocks.0.audio_to_video_attn.to_k.bias", + "transformer_blocks.0.audio_to_video_attn.to_v.weight", + "transformer_blocks.0.audio_to_video_attn.to_v.bias", + "transformer_blocks.0.audio_to_video_attn.to_gate_logits.weight", + "transformer_blocks.0.audio_to_video_attn.to_gate_logits.bias", + "transformer_blocks.0.audio_to_video_attn.to_out.0.weight", + "transformer_blocks.0.audio_to_video_attn.to_out.0.bias", + "transformer_blocks.0.video_to_audio_attn.q_norm.weight", + "transformer_blocks.0.video_to_audio_attn.k_norm.weight", + "transformer_blocks.0.video_to_audio_attn.to_q.weight", + "transformer_blocks.0.video_to_audio_attn.to_q.bias", + "transformer_blocks.0.video_to_audio_attn.to_k.weight", + "transformer_blocks.0.video_to_audio_attn.to_k.bias", + "transformer_blocks.0.video_to_audio_attn.to_v.weight", + "transformer_blocks.0.video_to_audio_attn.to_v.bias", + "transformer_blocks.0.video_to_audio_attn.to_gate_logits.weight", + "transformer_blocks.0.video_to_audio_attn.to_gate_logits.bias", + "transformer_blocks.0.video_to_audio_attn.to_out.0.weight", + "transformer_blocks.0.video_to_audio_attn.to_out.0.bias", + "transformer_blocks.1.scale_shift_table", + "transformer_blocks.1.audio_scale_shift_table", + "transformer_blocks.1.scale_shift_table_a2v_ca_audio", + "transformer_blocks.1.scale_shift_table_a2v_ca_video", + "transformer_blocks.1.prompt_scale_shift_table", + "transformer_blocks.1.audio_prompt_scale_shift_table", + "transformer_blocks.1.attn1.q_norm.weight", + "transformer_blocks.1.attn1.k_norm.weight", + "transformer_blocks.1.attn1.to_q.weight", + "transformer_blocks.1.attn1.to_q.bias", + "transformer_blocks.1.attn1.to_k.weight", + "transformer_blocks.1.attn1.to_k.bias", + "transformer_blocks.1.attn1.to_v.weight", + "transformer_blocks.1.attn1.to_v.bias", + "transformer_blocks.1.attn1.to_gate_logits.weight", + "transformer_blocks.1.attn1.to_gate_logits.bias", + "transformer_blocks.1.attn1.to_out.0.weight", + "transformer_blocks.1.attn1.to_out.0.bias", + "transformer_blocks.1.attn2.q_norm.weight", + "transformer_blocks.1.attn2.k_norm.weight", + "transformer_blocks.1.attn2.to_q.weight", + "transformer_blocks.1.attn2.to_q.bias", + "transformer_blocks.1.attn2.to_k.weight", + "transformer_blocks.1.attn2.to_k.bias", + "transformer_blocks.1.attn2.to_v.weight", + "transformer_blocks.1.attn2.to_v.bias", + "transformer_blocks.1.attn2.to_gate_logits.weight", + "transformer_blocks.1.attn2.to_gate_logits.bias", + "transformer_blocks.1.attn2.to_out.0.weight", + "transformer_blocks.1.attn2.to_out.0.bias", + "transformer_blocks.1.ff.net.0.proj.weight", + "transformer_blocks.1.ff.net.2.weight", + "transformer_blocks.1.audio_attn1.q_norm.weight", + "transformer_blocks.1.audio_attn1.k_norm.weight", + "transformer_blocks.1.audio_attn1.to_q.weight", + "transformer_blocks.1.audio_attn1.to_q.bias", + "transformer_blocks.1.audio_attn1.to_k.weight", + "transformer_blocks.1.audio_attn1.to_k.bias", + "transformer_blocks.1.audio_attn1.to_v.weight", + "transformer_blocks.1.audio_attn1.to_v.bias", + "transformer_blocks.1.audio_attn1.to_gate_logits.weight", + "transformer_blocks.1.audio_attn1.to_gate_logits.bias", + "transformer_blocks.1.audio_attn1.to_out.0.weight", + "transformer_blocks.1.audio_attn1.to_out.0.bias", + "transformer_blocks.1.audio_attn2.q_norm.weight", + "transformer_blocks.1.audio_attn2.k_norm.weight", + "transformer_blocks.1.audio_attn2.to_q.weight", + "transformer_blocks.1.audio_attn2.to_q.bias", + "transformer_blocks.1.audio_attn2.to_k.weight", + "transformer_blocks.1.audio_attn2.to_k.bias", + "transformer_blocks.1.audio_attn2.to_v.weight", + "transformer_blocks.1.audio_attn2.to_v.bias", + "transformer_blocks.1.audio_attn2.to_gate_logits.weight", + "transformer_blocks.1.audio_attn2.to_gate_logits.bias", + "transformer_blocks.1.audio_attn2.to_out.0.weight", + "transformer_blocks.1.audio_attn2.to_out.0.bias", + "transformer_blocks.1.audio_ff.net.0.proj.weight", + "transformer_blocks.1.audio_ff.net.0.proj.bias", + "transformer_blocks.1.audio_ff.net.2.weight", + "transformer_blocks.1.audio_ff.net.2.bias", + "transformer_blocks.1.audio_to_video_attn.q_norm.weight", + "transformer_blocks.1.audio_to_video_attn.k_norm.weight", + "transformer_blocks.1.audio_to_video_attn.to_q.weight", + "transformer_blocks.1.audio_to_video_attn.to_q.bias", + "transformer_blocks.1.audio_to_video_attn.to_k.weight", + "transformer_blocks.1.audio_to_video_attn.to_k.bias", + "transformer_blocks.1.audio_to_video_attn.to_v.weight", + "transformer_blocks.1.audio_to_video_attn.to_v.bias", + "transformer_blocks.1.audio_to_video_attn.to_gate_logits.weight", + "transformer_blocks.1.audio_to_video_attn.to_gate_logits.bias", + "transformer_blocks.1.audio_to_video_attn.to_out.0.weight", + "transformer_blocks.1.audio_to_video_attn.to_out.0.bias", + "transformer_blocks.1.video_to_audio_attn.q_norm.weight", + "transformer_blocks.1.video_to_audio_attn.k_norm.weight", + "transformer_blocks.1.video_to_audio_attn.to_q.weight", + "transformer_blocks.1.video_to_audio_attn.to_q.bias", + "transformer_blocks.1.video_to_audio_attn.to_k.weight", + "transformer_blocks.1.video_to_audio_attn.to_k.bias", + "transformer_blocks.1.video_to_audio_attn.to_v.weight", + "transformer_blocks.1.video_to_audio_attn.to_v.bias", + "transformer_blocks.1.video_to_audio_attn.to_gate_logits.weight", + "transformer_blocks.1.video_to_audio_attn.to_gate_logits.bias", + "transformer_blocks.1.video_to_audio_attn.to_out.0.weight", + "transformer_blocks.1.video_to_audio_attn.to_out.0.bias", +}; + +inline constexpr int64_t kLtx2PromptAdalnParamRanks[] = { + 2, 2, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, + 2, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 2, 1, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, + 2, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 2, 1, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 1, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1, + 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, +}; + +inline constexpr int64_t kLtx2PromptAdalnParamDims[] = { + 2, 32, 2, 16, 32, 8, 32, 32, 256, 32, 32, 32, + 32, 288, 32, 288, 32, 256, 32, 32, 32, 32, 64, 32, + 64, 8, 32, 8, 16, 6, 16, 16, 256, 16, 16, 16, + 16, 144, 16, 144, 16, 256, 16, 16, 16, 16, 32, 16, + 32, 6, 16, 6, 32, 256, 32, 32, 32, 32, 128, 32, + 128, 16, 256, 16, 16, 16, 16, 64, 16, 64, 32, 256, + 32, 32, 32, 32, 32, 32, 32, 16, 256, 16, 16, 16, + 16, 16, 16, 16, 9, 32, 9, 16, 5, 16, 5, 32, + 2, 32, 2, 16, 32, 32, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 4, 32, 4, 32, 32, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 4, 32, 4, 32, + 32, 32, 128, 32, 32, 128, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 4, 16, 4, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 4, 16, + 4, 16, 16, 16, 64, 16, 64, 16, 64, 16, 16, 16, + 16, 32, 16, 16, 16, 16, 16, 16, 16, 4, 32, 4, + 32, 16, 32, 16, 16, 16, 16, 16, 16, 32, 16, 16, + 32, 16, 4, 16, 4, 16, 16, 16, 9, 32, 9, 16, + 5, 16, 5, 32, 2, 32, 2, 16, 32, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 4, 32, 4, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, + 4, 32, 4, 32, 32, 32, 128, 32, 32, 128, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 4, 16, 4, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 4, 16, 4, 16, 16, 16, 64, 16, 64, 16, + 64, 16, 16, 16, 16, 32, 16, 16, 16, 16, 16, 16, + 16, 4, 32, 4, 32, 16, 32, 16, 16, 16, 16, 16, + 16, 32, 16, 16, 32, 16, 4, 16, 4, 16, 16, 16, +}; + +inline constexpr int64_t kLtx2PromptAdalnParamCount = 226; + +inline constexpr float kLtx2PromptAdalnVideoTimesteps[] = { + 619.314514f, 424.717133f, +}; + +inline constexpr float kLtx2PromptAdalnAudioTimesteps[] = { + 473.940033f, 471.390289f, +}; + +inline constexpr float kLtx2PromptAdalnVideoModulation[] = { + 0.00287261885f, 0.0172509346f, 0.0149077997f, 0.00213927543f, 0.016918283f, 0.00749109872f, + 0.00878333207f, -0.00887233205f, -0.0209352672f, 0.0127304699f, 0.0109749483f, -0.0130382832f, + -0.00827212445f, -0.00580324279f, -0.0134638241f, 0.00964590162f, -0.016003741f, -0.0158182271f, + -0.00942324009f, -0.0159717295f, -0.00885281898f, -0.00412264559f, -0.00142920704f, -0.0168404803f, + 0.0133440131f, -0.0136787146f, 0.0163368266f, 0.00325167179f, 0.0156327151f, 0.000542653725f, + -0.0113121318f, -0.00703316741f, -0.0195021238f, 0.0151473908f, 0.0180853736f, 0.0211514644f, + 0.000123613572f, -0.00695834123f, -0.00497911917f, -0.0171213523f, 0.0199449155f, -0.0122921225f, + 0.0164053645f, 0.000113433227f, 0.00943795405f, -0.0121485656f, 0.00261268346f, 0.00783557631f, + -0.00460897852f, 0.0153093971f, 0.0194506291f, 0.014570266f, -0.0162125602f, -0.00318641192f, + -0.013586767f, 0.00904892385f, -0.0157831647f, -0.0182319973f, 0.0170140117f, 0.00303793233f, + -0.0181853306f, -0.00718653016f, 0.0158703439f, 0.00020046602f, 0.00623062998f, 0.0252011884f, + 0.0163403824f, 0.000290016062f, 0.014817901f, 0.00552449375f, 0.00866025314f, -0.00905592274f, + -0.0175954159f, 0.0110378982f, 0.0159901753f, -0.0115343379f, -0.0125738885f, -0.00668510282f, + -0.0144314552f, 0.0128096947f, -0.0149616096f, -0.0173609052f, -0.0133896163f, -0.0159855876f, + -0.00701024011f, -0.00505556632f, -0.0011741718f, -0.0174259786f, 0.00958774798f, -0.0130143911f, + 0.0113152023f, 0.00158250146f, 0.0137644717f, 0.00236273953f, -0.0104373507f, -0.000455798348f, + -0.0210349094f, 0.0129983025f, 0.0220505167f, 0.0202870686f, 0.000495404762f, -0.00149141857f, + -0.00326095428f, -0.0143762864f, 0.0244831145f, -0.01158146f, 0.00986448769f, 0.000591582619f, + 0.013319497f, -0.0125091095f, 0.00394352386f, 0.0113571035f, -0.00364819146f, 0.013945261f, + 0.0238441341f, 0.0148018515f, -0.0194255728f, -0.00321829435f, -0.0185660385f, 0.0112335477f, + -0.0152186789f, -0.0156262573f, 0.0185506362f, 0.00343540264f, -0.0173967108f, -0.00660126004f, + 0.0198770091f, -0.000762573676f, +}; + +inline constexpr float kLtx2PromptAdalnAudioModulation[] = { + 0.0187722091f, -0.00105935254f, -0.00853663217f, 0.00841550902f, -0.0137612084f, -0.0173807591f, + -0.0139704188f, -0.0109588308f, 0.00186792947f, -0.00266267685f, -0.00698805135f, -0.0129980808f, + 0.00670515466f, -0.0131825786f, 0.0064191781f, 0.00632017246f, -0.0122615304f, 0.00804066937f, + 0.0122477906f, 0.0157948378f, 0.0128126917f, -0.00316316052f, -0.00883637834f, -0.0100117959f, + -0.0133155594f, 0.00456165429f, 0.0183124971f, -0.0130120357f, 0.018200513f, 0.00201772549f, + 0.0119188903f, -0.0103679141f, 0.0201762542f, -0.000246715499f, -0.00900617056f, 0.00820883084f, + -0.0132663827f, -0.0180532821f, -0.0145739987f, -0.0113234669f, 0.00138104265f, -0.00278934441f, + -0.00686988048f, -0.013545204f, 0.00675359881f, -0.0138096726f, 0.00569557305f, 0.00596735114f, + -0.0114674894f, 0.00765935751f, 0.0126606431f, 0.0161463916f, 0.0141690942f, -0.00382941356f, + -0.0089492118f, -0.00970011577f, -0.0132083837f, 0.00381002505f, 0.0185851343f, -0.0123645896f, + 0.0179123916f, 0.00177870644f, 0.0127155371f, -0.0096385479f, +}; + +// --- forward case PromptAdaln: rope=split float64_freqs=False masked=False audio_enabled=True dense_self_mask=False prompt_adaln=True --- +inline constexpr float kLtx2ForwardPromptAdalnVideo[] = { + -0.112144604f, -0.0224400591f, -0.0198658258f, -0.0801219419f, -0.00492565054f, -0.0996806622f, + -0.141095862f, -0.110312767f, 0.242095038f, 0.0162332151f, 0.0690327138f, -0.127996832f, + -0.327992439f, -0.067813158f, -0.0181498341f, 0.0133159151f, -0.285277247f, 0.0288095567f, + -0.103010967f, 0.0120137185f, 0.175781652f, -0.133603424f, -0.20695813f, -0.182875797f, + -0.275936812f, -0.0216972958f, 0.194740638f, -0.250197232f, -0.196145147f, -0.0676094294f, + -0.115069151f, -0.0885243863f, -0.0418822765f, 0.120427184f, -0.0467535444f, -0.166882023f, + -0.145086944f, -0.127097756f, -0.125268698f, -0.0937946662f, -0.10143815f, -0.0346935503f, + 0.347262383f, -0.0154392701f, 0.0253714621f, -0.151571572f, 0.0893032998f, -0.217105299f, + -0.113787532f, 0.107526913f, 0.111182854f, -0.0174661856f, 0.0259091742f, -0.199445173f, + -0.0387741439f, -0.157782555f, 0.159147635f, 0.0321634449f, 0.232984737f, -0.122400187f, + -0.27637139f, -0.0666981712f, 0.0339653119f, -0.140525416f, 0.0675620735f, -0.120439962f, + 0.0212921053f, 0.112628169f, 0.190132841f, -0.144298837f, -0.0116734616f, -0.159263715f, + 0.0784623772f, -0.179399416f, -0.328205913f, -0.0860405788f, -0.0919508785f, -0.0483957306f, + -0.30280292f, 0.158935979f, -0.127778456f, -0.174414024f, 0.315983474f, -0.0398868211f, + -0.11850293f, 0.0652811453f, -0.0993542522f, -0.234619945f, -0.284468323f, -0.183528736f, + 0.225024208f, -0.049902156f, -0.0650495142f, -0.0445851982f, -0.193018377f, -0.149498582f, + -0.0479458123f, -0.0666453466f, -0.0906915069f, 0.0227309261f, 0.0560047664f, -0.146587655f, + -0.172089651f, -0.0502583086f, 0.0590079799f, 0.19477278f, 0.218966454f, -0.112773709f, + -0.16504398f, -0.111266837f, 0.15254502f, -0.120294176f, 0.114031032f, -0.0505410358f, + -0.150275424f, -0.024936026f, -0.139517337f, -0.00708480086f, -0.160885945f, 0.0312023032f, + 0.27590549f, 0.0162312202f, -0.389412105f, -0.066493772f, -0.005505112f, -0.114737555f, + -0.136138007f, 0.0470205061f, +}; + +inline constexpr float kLtx2ForwardPromptAdalnAudio[] = { + -0.00688289292f, 0.0557257049f, -0.169716612f, 0.136525676f, -0.0794674978f, -0.106710114f, + -0.109623641f, 0.0357984938f, -0.0222658273f, -0.0561756417f, 0.145198658f, 0.0478727296f, + 0.0297143031f, 0.0544307008f, -0.205607757f, 0.122264318f, -0.105997331f, -0.191682264f, + -0.0355675966f, 0.0435401984f, -0.107154176f, 0.192362309f, -0.0537107848f, -0.080251269f, + -0.0339412056f, -0.0676042289f, -0.161326066f, -0.0567146167f, 0.166392133f, -0.0363745391f, + -0.00992792659f, 0.0993538871f, -0.196374968f, 0.146272391f, -0.0314919427f, -0.147439599f, + 0.00373091619f, 0.119020537f, -0.16169925f, 0.149572372f, -0.0535437092f, -0.17913793f, + -0.0729758665f, 0.0560201257f, -0.103747681f, 0.0615522824f, 0.147424534f, -0.0767256021f, + -0.0455891043f, -0.074104555f, -0.0908109695f, 0.14269349f, 0.00909528229f, 0.000857369974f, + -0.094678238f, 0.0833884105f, 0.000516908942f, -0.244370729f, 0.0668335408f, 0.0764729604f, +}; + +// --- forward case PromptAdalnMasked: rope=split float64_freqs=False masked=True audio_enabled=True dense_self_mask=False prompt_adaln=True --- +inline constexpr float kLtx2ForwardPromptAdalnMaskedVideo[] = { + -0.110144161f, -0.0195061471f, -0.0188482404f, -0.0824035481f, -0.00313981529f, -0.102331489f, + -0.140647486f, -0.111037448f, 0.238786951f, 0.019598389f, 0.0744047314f, -0.129760861f, + -0.326935619f, -0.0698587522f, -0.0180070139f, 0.0129257301f, -0.282629013f, 0.0306178499f, + -0.100754961f, 0.00948145799f, 0.175529778f, -0.135220498f, -0.206774727f, -0.182601333f, + -0.275893211f, -0.0184815805f, 0.198321715f, -0.251868665f, -0.194811657f, -0.0697973669f, + -0.114349291f, -0.089783825f, -0.0404667407f, 0.121526636f, -0.0449657179f, -0.170914561f, + -0.144608453f, -0.129623294f, -0.126025274f, -0.0928756446f, -0.0987707227f, -0.0303842705f, + 0.349234581f, -0.0163635593f, 0.0282549523f, -0.152388036f, 0.0889601111f, -0.216030046f, + -0.112322196f, 0.108959578f, 0.112902433f, -0.0188521128f, 0.0273712836f, -0.20055984f, + -0.0387175046f, -0.156802967f, 0.156722441f, 0.0369930752f, 0.239927039f, -0.124359809f, + -0.273358971f, -0.0692257583f, 0.0352873057f, -0.141093776f, 0.0673692524f, -0.120450936f, + 0.0237387791f, 0.111967109f, 0.187116802f, -0.139871866f, -0.011316698f, -0.153532162f, + 0.0786181018f, -0.177252695f, -0.331035018f, -0.0842280909f, -0.0938364938f, -0.0409911275f, + -0.301013172f, 0.167324454f, -0.124323212f, -0.174904421f, 0.316507936f, -0.0429520756f, + -0.120540164f, 0.067215845f, -0.101051688f, -0.225887731f, -0.286542743f, -0.182181612f, + 0.224588186f, -0.049729839f, -0.0685416833f, -0.0383053645f, -0.189322844f, -0.138665244f, + -0.0496713445f, -0.068905741f, -0.093425855f, 0.0234508831f, 0.0515494831f, -0.140059441f, + -0.166952908f, -0.041921705f, 0.0591960102f, 0.194034085f, 0.216313154f, -0.111525752f, + -0.165100992f, -0.10801211f, 0.152435049f, -0.115520097f, 0.110954985f, -0.0485403165f, + -0.152800724f, -0.0228928309f, -0.138780624f, -0.00368307461f, -0.157897815f, 0.0359253138f, + 0.277324349f, 0.0178819038f, -0.393882304f, -0.0665938482f, -0.00776502956f, -0.109243833f, + -0.134122461f, 0.0536325052f, +}; + +inline constexpr float kLtx2ForwardPromptAdalnMaskedAudio[] = { + -0.00623655878f, 0.0550939925f, -0.169768706f, 0.134756476f, -0.0800285041f, -0.106801011f, + -0.109707221f, 0.0349297225f, -0.0223909412f, -0.0580404438f, 0.145440862f, 0.0479248241f, + 0.0300626066f, 0.0543725975f, -0.205051288f, 0.121049382f, -0.106699832f, -0.19155553f, + -0.0357390642f, 0.043708574f, -0.106640562f, 0.191323146f, -0.0545797087f, -0.0801633745f, + -0.0338453911f, -0.067704387f, -0.161020443f, -0.0588866882f, 0.16616787f, -0.0362463593f, + -0.00972275995f, 0.0983785167f, -0.197862387f, 0.146195069f, -0.0301159211f, -0.147994176f, + 0.00437853113f, 0.118179567f, -0.162635818f, 0.14993386f, -0.0523924604f, -0.179551288f, + -0.0724292099f, 0.0543062873f, -0.103864521f, 0.0613403916f, 0.14883852f, -0.0763024017f, + -0.0452010371f, -0.0744607076f, -0.0918518007f, 0.142673746f, 0.0106131276f, 5.35193831e-05f, + -0.09456487f, 0.0829611197f, 0.000701400219f, -0.243942767f, 0.0671276823f, 0.0765738413f, +}; + +// --- the MEASURED magnitude of the prompt-AdaLN term --- +// Same shared weights, same inputs, flag ON vs OFF: +// DiT video output: max|on-off| = 0.000145674 (0.04% of max|off|) +// DiT audio output: max|on-off| = 7.36713e-05 (0.03% of max|off|) +// block 0 modulated prompt K/V: max|on-off| = 0.0309528 (5.82% of max|off|) +// timestep term vs static table: max|term| = 0.0252012 vs max|table| = 0.0487142 (51.7%) +// The two output rows are bounded by this generator's SYNTHETIC weight scale +// (0.05, param_spec above) and by a 2-block stack; they are the FLOOR the gate +// needs, not a claim about the trained checkpoint. The K/V row is where the +// term actually enters and is the number that answers 'does this matter'. } // namespace vllm_test diff --git a/tests/vllm/models/test_ltx2.cpp b/tests/vllm/models/test_ltx2.cpp index dd6a8e077..f00771f38 100644 --- a/tests/vllm/models/test_ltx2.cpp +++ b/tests/vllm/models/test_ltx2.cpp @@ -135,6 +135,16 @@ Ltx2DitParams ReducedParams(Ltx2RopeType rope_type, bool double_precision) { return p; } +// Upstream's DEFAULT arm (model.py:77, model_configurator.py:76/:138, diffusers +// transformer_ltx2.py:1185) and the one the shipped LTX-2.5 DiT carries: the +// prompt-side AdaLN MLP is built, so the cross-attention K/V modulation carries a +// timestep term on top of the static per-block table (transformer.py:441-443). +Ltx2DitParams ReducedParamsPromptAdaln(Ltx2RopeType rope_type, bool double_precision) { + Ltx2DitParams p = ReducedParams(rope_type, double_precision); + p.use_prompt_adaln_single = true; + return p; +} + // A materialized weight set: owned f32 storage plus the views the forward takes. struct WeightSet { std::map> storage; @@ -831,6 +841,137 @@ TEST_CASE("ltx2 forward: a disabled audio stream still feeds audio->video") { /*audio_enabled=*/false); } +// --------------------------------------------------------------------------- +// The prompt-side AdaLN arm — upstream's DEFAULT +// (.agents/specs/ltx25-prompt-adaln.md, issue #644) +// --------------------------------------------------------------------------- + +namespace { + +// The same forward, run with `use_prompt_adaln_single = true`. Kept separate from +// CheckForward rather than folded into it, because the two arms have DIFFERENT +// weight contracts (12 extra parameters) and sharing one helper would hide which +// contract a case ran under. +vllm::Ltx2DitOutputs RunPromptAdalnForward(const Ltx2DitParams& p, WeightSet& set, + Modalities* m, bool masked) { + BuildModalities(m, masked); + return Ltx2DitForward(Cpu(), p, set.weights, &m->video, &m->audio, vt::DType::kF32); +} + +void CheckPromptAdalnForward(bool masked, const float* want_video, const float* want_audio, + const char* label) { + INFO("prompt-AdaLN forward case: " << std::string(label)); + const Ltx2DitParams p = ReducedParamsPromptAdaln(Ltx2RopeType::kSplit, false); + WeightSet set = BuildWeights(p); + Modalities m; + const vllm::Ltx2DitOutputs out = RunPromptAdalnForward(p, set, &m, masked); + const size_t vcount = static_cast(m.video.batch * m.video.tokens * p.out_channels); + const size_t acount = + static_cast(m.audio.batch * m.audio.tokens * p.audio_out_channels); + const double vdiff = MaxAbsDiff(out.video, want_video, vcount); + const double adiff = MaxAbsDiff(out.audio, want_audio, acount); + MESSAGE("max|diff| video=" << vdiff << " audio=" << adiff); + CHECK(vdiff < kRoundOff); + CHECK(adiff < kRoundOff); +} + +} // namespace + +// The 12 parameters the flag adds (6 per stream: three linears x weight+bias), +// which the shipped FP8 checkpoint carries as 18 manifest entries because each +// quantized weight also has a `weight_scale`. Order matters as much as presence: +// upstream registers `prompt_adaln_single` between `adaln_single` and `proj_out` +// inside `_init_video` (model.py:222-232), and the audio twin in `_init_audio` +// (:252-262). +TEST_CASE("ltx2 layout: the flag-ON contract matches upstream named_parameters()") { + const Ltx2DitParams p = ReducedParamsPromptAdaln(Ltx2RopeType::kSplit, false); + const std::vector manifest = EnumerateLtx2DitTensors(p); + REQUIRE(static_cast(manifest.size()) == vllm_test::kLtx2PromptAdalnParamCount); + // The flag is the ONLY difference, so the count delta is exactly the module. + CHECK(vllm_test::kLtx2PromptAdalnParamCount - vllm_test::kLtx2ParamCount == 12); + size_t dim_cursor = 0; + for (size_t i = 0; i < manifest.size(); ++i) { + CAPTURE(i); + CAPTURE(manifest[i].name); + CHECK(manifest[i].name == std::string(vllm_test::kLtx2PromptAdalnParamNames[i])); + const int64_t rank = vllm_test::kLtx2PromptAdalnParamRanks[i]; + REQUIRE(static_cast(manifest[i].shape.size()) == rank); + for (int64_t d = 0; d < rank; ++d) { + CHECK(manifest[i].shape[static_cast(d)] == + vllm_test::kLtx2PromptAdalnParamDims[dim_cursor]); + ++dim_cursor; + } + } +} + +// The MLP on its own, so a failure localizes here rather than in the threading. +// The input is the modality's SIGMA scaled by timestep_scale_multiplier +// (transformer_args.py:274-277 -> :173-186) — one scalar per batch element, NOT +// the per-token `timesteps` the main AdaLN consumes. +TEST_CASE("ltx2 brick: the prompt AdaLN MLP runs on sigma, not on the per-token timesteps") { + const Ltx2DitParams p = ReducedParamsPromptAdaln(Ltx2RopeType::kSplit, false); + WeightSet set = BuildWeights(p); + const int64_t b = vllm_test::kLtx2Batch; + const int64_t dim = p.inner_dim(); + const int64_t adim = p.audio_inner_dim(); + + std::vector vts(vllm_test::kLtx2PromptAdalnVideoTimesteps, + vllm_test::kLtx2PromptAdalnVideoTimesteps + b); + const vllm::Ltx2AdalnOut vout = vllm::Ltx2AdaLayerNormSingle( + Cpu(), set.weights.prompt_adaln_single, vts.data(), b, dim); + REQUIRE(vout.modulation.size() == static_cast(b * 2 * dim)); + CHECK(MaxAbsDiff(vout.modulation, vllm_test::kLtx2PromptAdalnVideoModulation, + static_cast(b * 2 * dim)) < kRoundOff); + + std::vector ats(vllm_test::kLtx2PromptAdalnAudioTimesteps, + vllm_test::kLtx2PromptAdalnAudioTimesteps + b); + const vllm::Ltx2AdalnOut aout = vllm::Ltx2AdaLayerNormSingle( + Cpu(), set.weights.audio_prompt_adaln_single, ats.data(), b, adim); + REQUIRE(aout.modulation.size() == static_cast(b * 2 * adim)); + CHECK(MaxAbsDiff(aout.modulation, vllm_test::kLtx2PromptAdalnAudioModulation, + static_cast(b * 2 * adim)) < kRoundOff); +} + +TEST_CASE("ltx2 forward: the prompt-side AdaLN arm") { + CheckPromptAdalnForward(false, vllm_test::kLtx2ForwardPromptAdalnVideo, + vllm_test::kLtx2ForwardPromptAdalnAudio, "prompt AdaLN"); +} + +TEST_CASE("ltx2 forward: the prompt-side AdaLN arm, with both masks") { + CheckPromptAdalnForward(true, vllm_test::kLtx2ForwardPromptAdalnMaskedVideo, + vllm_test::kLtx2ForwardPromptAdalnMaskedAudio, + "prompt AdaLN + masks"); +} + +// THE INSTRUMENT THAT MAKES THE ARM ABOVE MEAN SOMETHING. +// +// Both goldens come from the same deterministic weight stream, keyed by parameter +// NAME, so every weight the two arms share is bit-identical and the ONLY thing +// separating `kLtx2ForwardPromptAdaln*` from `kLtx2ForwardSplit*` is the timestep +// term. A port that accepted the flag, bound the 12 tensors and then never added +// their output would reproduce the flag-OFF numbers exactly and pass nothing here. +// +// Measured on this fixture (generator stderr, and the comment block at the end of +// ltx2_goldens.inc): the term is 51.7% the magnitude of the static per-block +// table it is added to, moves the block-0 modulated prompt K/V by 5.82%, and +// moves the DiT's own output by 1.46e-4 — 73x the kRoundOff floor. The bound +// below is set at 20x kRoundOff so it is comfortably inside the measured signal +// and comfortably outside f32 noise. +TEST_CASE("ltx2 forward: the prompt-AdaLN term is LOAD-BEARING, not decoration") { + const Ltx2DitParams p = ReducedParamsPromptAdaln(Ltx2RopeType::kSplit, false); + WeightSet set = BuildWeights(p); + Modalities m; + const vllm::Ltx2DitOutputs out = RunPromptAdalnForward(p, set, &m, false); + const size_t vcount = static_cast(m.video.batch * m.video.tokens * p.out_channels); + const size_t acount = + static_cast(m.audio.batch * m.audio.tokens * p.audio_out_channels); + const double vdiff = MaxAbsDiff(out.video, vllm_test::kLtx2ForwardSplitVideo, vcount); + const double adiff = MaxAbsDiff(out.audio, vllm_test::kLtx2ForwardSplitAudio, acount); + MESSAGE("flag-ON vs flag-OFF: video=" << vdiff << " audio=" << adiff); + CHECK(vdiff > 20.0 * kRoundOff); + CHECK(adiff > 20.0 * kRoundOff); +} + TEST_CASE("ltx2 forward: a single-stream model type is REFUSED") { const Ltx2DitParams p = ReducedParams(Ltx2RopeType::kSplit, false); WeightSet set = BuildWeights(p); diff --git a/tests/vllm/models/test_ltx2_device.cpp b/tests/vllm/models/test_ltx2_device.cpp index 46bea0b78..cafef9ceb 100644 --- a/tests/vllm/models/test_ltx2_device.cpp +++ b/tests/vllm/models/test_ltx2_device.cpp @@ -304,8 +304,9 @@ struct CaseResult { CaseResult RunDeviceCase(vt::Queue& q, vt::DType stream, Ltx2RopeType rope_type, bool double_precision, bool masked, const float* want_video, const float* want_audio, bool audio_enabled = true, - bool dense_self_mask = false) { - const Ltx2DitParams p = ReducedParams(rope_type, double_precision); + bool dense_self_mask = false, bool prompt_adaln = false) { + Ltx2DitParams p = ReducedParams(rope_type, double_precision); + p.use_prompt_adaln_single = prompt_adaln; WeightSet set = BuildWeights(p); // Staged at the SAME dtype the stream computes in. Staging f32 weights under a // bf16 stream would compare a DIFFERENT MODEL, not a different dtype policy. @@ -406,6 +407,74 @@ void CheckBf16Stream(vt::Queue& q, const char* label) { CHECK(bf16.video > kDeviceRoundOff); } +// The prompt-side AdaLN arm — upstream's DEFAULT and what the shipped DiT runs +// (.agents/specs/ltx25-prompt-adaln.md, issue #644). The device path forms +// `kv_modulation = table + prompt_timestep` (transformer.py:441-443) through +// `ada_value` and then broadcasts one row per BATCH element over that element's +// prompt tokens, which is a DIFFERENT `modulate` call shape from the static +// arm — so it needs its own case rather than riding on the loop above. +void CheckPromptAdalnCases(vt::Queue& q, const char* label) { + { + INFO(label << " / prompt AdaLN, f32"); + const CaseResult r = + RunDeviceCase(q, vt::DType::kF32, Ltx2RopeType::kSplit, false, false, + vllm_test::kLtx2ForwardPromptAdalnVideo, + vllm_test::kLtx2ForwardPromptAdalnAudio, /*audio_enabled=*/true, + /*dense_self_mask=*/false, /*prompt_adaln=*/true); + MESSAGE("prompt AdaLN f32: max|diff| video=" << r.video << " audio=" << r.audio); + CHECK(r.video < kDeviceRoundOff); + CHECK(r.audio < kDeviceRoundOff); + } + { + // The masked case: the prompt mask and the prompt modulation act on the SAME + // context tensor, and a per-batch broadcast written as a per-token one would + // survive the unmasked case at batch 1. + INFO(label << " / prompt AdaLN + masks, f32"); + const CaseResult r = + RunDeviceCase(q, vt::DType::kF32, Ltx2RopeType::kSplit, false, true, + vllm_test::kLtx2ForwardPromptAdalnMaskedVideo, + vllm_test::kLtx2ForwardPromptAdalnMaskedAudio, /*audio_enabled=*/true, + /*dense_self_mask=*/false, /*prompt_adaln=*/true); + MESSAGE("prompt AdaLN masked f32: max|diff| video=" << r.video << " audio=" << r.audio); + CHECK(r.video < kDeviceRoundOff); + CHECK(r.audio < kDeviceRoundOff); + } + { + // AND IT IS LOAD-BEARING. The flag-ON and flag-OFF goldens share every common + // weight bit-for-bit, so a device path that bound the module and never added + // its output would reproduce the flag-OFF numbers exactly. + // + // The bound is `kDeviceRoundOff` itself, and that is the precise statement: + // the flag-ON run must miss the flag-OFF golden by MORE than the tolerance + // the flag-OFF case is held to, or a dropped term would pass that gate. + // Measured here: video 1.46e-4 (7.3x the bound), audio 7.37e-5 (3.7x). + INFO(label << " / prompt AdaLN is load-bearing"); + const CaseResult r = + RunDeviceCase(q, vt::DType::kF32, Ltx2RopeType::kSplit, false, false, + vllm_test::kLtx2ForwardSplitVideo, vllm_test::kLtx2ForwardSplitAudio, + /*audio_enabled=*/true, /*dense_self_mask=*/false, + /*prompt_adaln=*/true); + MESSAGE("prompt AdaLN vs flag-OFF golden: video=" << r.video << " audio=" << r.audio); + CHECK(r.video > kDeviceRoundOff); + CHECK(r.audio > kDeviceRoundOff); + } + { + // The bf16 PRODUCTION stream on the same arm: `ada_value` stores the + // table+timestep sum at the stream dtype, so this is where a bf16 store of the + // new sum is exercised at all. + INFO(label << " / prompt AdaLN, bf16"); + const CaseResult r = + RunDeviceCase(q, vt::DType::kBF16, Ltx2RopeType::kSplit, false, false, + vllm_test::kLtx2ForwardPromptAdalnVideo, + vllm_test::kLtx2ForwardPromptAdalnAudio, /*audio_enabled=*/true, + /*dense_self_mask=*/false, /*prompt_adaln=*/true); + MESSAGE("prompt AdaLN bf16: max|diff| video=" << r.video << " audio=" << r.audio); + CHECK(r.video < kBf16RoundOff); + CHECK(r.audio < kBf16RoundOff); + CHECK(r.video > kDeviceRoundOff); + } +} + vt::Backend* TryCuda() { return vt::TryGetBackend(vt::DeviceType::kCUDA); } } // namespace @@ -527,6 +596,21 @@ TEST_CASE("ltx2 device: the DEVICE-RESIDENT forward matches upstream on CUDA") { CheckAllForwardCases(q, "cuda"); } +TEST_CASE("ltx2 device: the prompt-side AdaLN arm matches upstream (CPU backend)") { + vt::Queue q{Cpu(), nullptr}; + CheckPromptAdalnCases(q, "cpu-backend"); +} + +TEST_CASE("ltx2 device: the prompt-side AdaLN arm matches upstream on CUDA") { + vt::Backend* cuda = TryCuda(); + if (cuda == nullptr) { + MESSAGE("SKIP: no CUDA backend registered"); + return; + } + vt::Queue q = cuda->CreateQueue(); + CheckPromptAdalnCases(q, "cuda"); +} + TEST_CASE("ltx2 device: the bf16 PRODUCTION stream matches upstream (CPU backend)") { vt::Queue q{Cpu(), nullptr}; CheckBf16Stream(q, "cpu-backend bf16"); diff --git a/tests/vllm/models/test_ltx2_loader.cpp b/tests/vllm/models/test_ltx2_loader.cpp index 654ecd134..e191d958b 100644 --- a/tests/vllm/models/test_ltx2_loader.cpp +++ b/tests/vllm/models/test_ltx2_loader.cpp @@ -827,8 +827,10 @@ TEST_CASE("ltx2 loader: the shipped FP8 DiT manifest is fully accounted for") { CHECK(bf16 == 2284); CHECK(f32 == 290); // the tables; the 1775 scalar scales were counted above - // The four families outside the phase-L2 contract, named so their absence - // from the port cannot be discovered later. + // The five families the file carries beyond the ORIGINAL L2 contract, named so + // their status cannot be discovered later. Two are ported (the prompt AdaLN + // pair), two are loaded elsewhere (the connectors), one is genuinely unported + // (keyframes). CHECK(families.count("prompt_adaln_single") == 1); CHECK(families.count("audio_prompt_adaln_single") == 1); CHECK(families.count("keyframes_abs_pos_embedding") == 1); @@ -886,18 +888,19 @@ TEST_CASE("ltx2 loader: the L2 contract's every name is present in the shipped D CHECK(p.cross_attention_adaln); CHECK_FALSE(p.ff_bias); CHECK(p.audio_ff_bias); - // MEASURED, and it contradicts .agents/specs/ltx-2-5.md section 1.2 and - // ltx2.h:115-117: the SHIPPED checkpoint carries prompt_adaln_single, which - // upstream builds only when use_prompt_adaln_single is TRUE (model.py:222-226). - // The prompt-K/V cache's premise does not hold for this checkpoint. + // MEASURED: the SHIPPED checkpoint carries prompt_adaln_single, which upstream + // builds only when use_prompt_adaln_single is TRUE (model.py:222-226), so the + // prompt-K/V cache's premise does not hold for this checkpoint + // (.agents/specs/ltx-2-5.md §1.2) and the module is PORTED + // (.agents/specs/ltx25-prompt-adaln.md, issue #644). CHECK(p.use_prompt_adaln_single); - // Enumerate the contract for the subset this port DOES carry, and require - // every one of its names in the file. - Ltx2DitParams contract = p; - contract.use_prompt_adaln_single = false; - const std::vector want = vllm::EnumerateLtx2DitTensors(contract); - CHECK(want.size() == 4078); + // Enumerate the contract AS THE FILE DESCRIBES IT — no flag is forced here any + // more — and require every one of its names in the file. This line used to read + // `contract.use_prompt_adaln_single = false`, which is the shape of the defect: + // the gate agreed with the port about a module they were both dropping. + const std::vector want = vllm::EnumerateLtx2DitTensors(p); + CHECK(want.size() == 4078 + 12); int64_t missing = 0; std::string first_missing; for (const Ltx2TensorSpec& spec : want) { @@ -913,15 +916,16 @@ TEST_CASE("ltx2 loader: the L2 contract's every name is present in the shipped D // ... and account for every name the file has that the contract does not, so // "the rest is fine" is a counted claim rather than a hope. // 258 = 2 connectors x (8 blocks x 16 + 1 learnable_registers) - // 12 = prompt_adaln_single + audio_prompt_adaln_single, 6 tensors each // 1 = keyframes_abs_pos_embedding + // The 12 prompt-AdaLN tensors are no longer here: they moved INTO `want` when + // the module was ported, which is the whole delta this row landed. std::set want_set; for (const Ltx2TensorSpec& spec : want) want_set.insert(spec.name); int64_t extra = 0; for (const std::string& name : present) { if (want_set.count(name) == 0) ++extra; } - CHECK(extra == 258 + 12 + 1); + CHECK(extra == 258 + 1); } // =========================================================================== @@ -1542,8 +1546,7 @@ TEST_CASE("ltx2 loader: the unported families are refused by name, not absorbed" const Ltx2DitParams p = TinyParams(); const SyntheticDit syn = BuildSyntheticDit( p, Ltx2DitQuant::kFp8, - {"prompt_adaln_single.linear.weight", "audio_prompt_adaln_single.linear.weight", - "keyframes_abs_pos_embedding", "video_embeddings_connector.learnable_registers", + {"keyframes_abs_pos_embedding", "video_embeddings_connector.learnable_registers", "audio_embeddings_connector.learnable_registers"}); const std::string path = TmpPath("unported"); WriteSafetensors(syn.entries, path); @@ -1557,12 +1560,18 @@ TEST_CASE("ltx2 loader: the unported families are refused by name, not absorbed" } const std::string what_msg = "what: " + what; INFO(what_msg); - // THREE families now, not five. `audio_prompt_adaln_single` is here because it - // goes through the identical generic FamilyOf path as its video twin and - // covering it literally is one line. - CHECK(what.find("prompt_adaln_single") != std::string::npos); - CHECK(what.find("audio_prompt_adaln_single") != std::string::npos); - CHECK(what.find("keyframes_abs_pos_embedding") != std::string::npos); + // ONE family in the LIST now, not five. `prompt_adaln_single` / + // `audio_prompt_adaln_single` left it when they were PORTED (issue #644); the + // case below proves a checkpoint carrying them needs no opt-in at all. + // + // The list is asserted as a whole rather than by substring, because the message + // deliberately goes on to NAME the families that are not in it — a + // `find(...) == npos` over the whole message would only test the prose. + const std::string head = "does NOT carry: "; + const size_t at = what.find(head); + REQUIRE(at != std::string::npos); + const std::string list = what.substr(at + head.size(), what.find('.', at) - at - head.size()); + CHECK(list == "keyframes_abs_pos_embedding"); // THE TWO CONNECTOR FAMILIES ARE NOT UNPORTED AS OF PHASE L9c. They are // outside the DiT's contract by design — upstream loads them into the text @@ -1574,17 +1583,15 @@ TEST_CASE("ltx2 loader: the unported families are refused by name, not absorbed" CHECK(what.find("video_embeddings_connector") == std::string::npos); CHECK(what.find("audio_embeddings_connector") == std::string::npos); - // The opt-in still REPORTS every one of the three; it does not make them vanish. + // The opt-in still REPORTS it; it does not make it vanish. Ltx2DitLoadOptions options; options.allow_unported_modules = true; const vllm::Ltx2DitCheckpoint ck = vllm::Ltx2LoadDitFromSafetensors(file, options); - CHECK(ck.unported.size() == 3); + CHECK(ck.unported.size() == 1); for (const std::string& family : ck.unported) { CHECK(family != "video_embeddings_connector"); CHECK(family != "audio_embeddings_connector"); } - CHECK(ck.checkpoint_params.use_prompt_adaln_single); - CHECK_FALSE(ck.params.use_prompt_adaln_single); std::remove(path.c_str()); // AND THE CONNECTOR-ONLY CHECKPOINT LOADS WITH NO OPT-IN AT ALL, which is the @@ -1604,6 +1611,131 @@ TEST_CASE("ltx2 loader: the unported families are refused by name, not absorbed" std::remove(conn_path.c_str()); } +// THE REGRESSION GATE FOR ISSUE #644 ROW 0. +// +// A checkpoint that carries `prompt_adaln_single` — which the shipped LTX-2.5 DiT +// does — used to be refused without `allow_unported_modules=1`, and setting that +// extra reached three assignments that CLEARED `use_prompt_adaln_single`. So the +// only way to load a real DiT was also the way to silently drop the timestep half +// of every cross-attention K/V modulation. +// +// Both halves are asserted here, and neither is true by construction: the load +// with no opt-in exercises the contract, and the resolved flag is read back off +// the checkpoint the loader actually bound. +TEST_CASE("ltx2 loader: a DiT carrying prompt_adaln_single loads with NO opt-in") { + Ltx2DitParams p = TinyParams(); + p.use_prompt_adaln_single = true; + const SyntheticDit syn = BuildSyntheticDit(p, Ltx2DitQuant::kFp8, {}); + const std::string path = TmpPath("prompt_adaln"); + WriteSafetensors(syn.entries, path); + const SafetensorsFile file = SafetensorsFile::Open(path); + + // No options at all: the families are in the contract, so nothing is unported. + const vllm::Ltx2DitCheckpoint ck = vllm::Ltx2LoadDitFromSafetensors(file); + CHECK(ck.unported.empty()); + CHECK(ck.checkpoint_params.use_prompt_adaln_single); + // The flag the FORWARD reads. This is the assertion the defect broke: it was + // false while `checkpoint_params` said true. + CHECK(ck.params.use_prompt_adaln_single); + // And the weights are bound, not merely enumerated — a contract that listed the + // tensors while `BindLtx2DitWeights` left the views null would render the same + // wrong picture with a different failure mode. + CHECK(ck.weights.prompt_adaln_single.linear.weight.data != nullptr); + CHECK(ck.weights.audio_prompt_adaln_single.linear.weight.data != nullptr); + CHECK(ck.weights.prompt_adaln_single.linear.weight.shape[0] == 2 * p.inner_dim()); + CHECK(ck.weights.audio_prompt_adaln_single.linear.weight.shape[0] == 2 * p.audio_inner_dim()); + // The 12 tensors, counted rather than assumed. + Ltx2DitParams off = p; + off.use_prompt_adaln_single = false; + CHECK(vllm::EnumerateLtx2DitTensors(p).size() - + vllm::EnumerateLtx2DitTensors(off).size() == + 12); + std::remove(path.c_str()); + + // AND THE OPT-IN CANNOT UNDO IT. `allow_unported_modules` is what a real render + // still passes for `keyframes_abs_pos_embedding`; it must leave a ported feature + // alone. Same bytes, opt-in set, same resolved flag. + const std::string path2 = TmpPath("prompt_adaln_optin"); + WriteSafetensors(syn.entries, path2); + const SafetensorsFile file2 = SafetensorsFile::Open(path2); + Ltx2DitLoadOptions options; + options.allow_unported_modules = true; + const vllm::Ltx2DitCheckpoint ck2 = vllm::Ltx2LoadDitFromSafetensors(file2, options); + CHECK(ck2.params.use_prompt_adaln_single); + CHECK(ck2.weights.prompt_adaln_single.linear.weight.data != nullptr); + std::remove(path2.c_str()); +} + +// The DECLARED config path. `Ltx2AdoptDeclaredDitParams` no longer forces both +// sides of its comparison to a cleared flag, so a config that disagrees with the +// file's shapes about `use_prompt_adaln_single` now produces two DIFFERENT +// contracts and is refused — which is an INPUT-driven gate, not a mutation-only +// one. Both directions are checked, because the equality is the point. +TEST_CASE("ltx2 loader: a config that disagrees about use_prompt_adaln_single is REFUSED") { + Ltx2DitParams shapes = TinyParams(); + shapes.use_prompt_adaln_single = true; + + nlohmann::json t; + t["num_attention_heads"] = shapes.num_attention_heads; + t["attention_head_dim"] = shapes.attention_head_dim; + t["in_channels"] = shapes.in_channels; + t["out_channels"] = shapes.out_channels; + t["num_layers"] = shapes.num_layers; + t["cross_attention_dim"] = shapes.cross_attention_dim; + t["audio_num_attention_heads"] = shapes.audio_num_attention_heads; + t["audio_attention_head_dim"] = shapes.audio_attention_head_dim; + t["audio_in_channels"] = shapes.audio_in_channels; + t["audio_out_channels"] = shapes.audio_out_channels; + t["audio_cross_attention_dim"] = shapes.audio_cross_attention_dim; + t["apply_gated_attention"] = true; + t["cross_attention_adaln"] = true; + t["ff_bias"] = false; + t["audio_ff_bias"] = true; + t["rope_type"] = "split"; + // The checks ParseLtx2DitParams asserts verbatim (model_configurator.py:26-44). + t["dropout"] = 0.0; + t["attention_bias"] = true; + t["num_vector_embeds"] = nullptr; + t["activation_fn"] = "gelu-approximate"; + t["num_embeds_ada_norm"] = 1000; + t["use_linear_projection"] = false; + t["only_cross_attention"] = false; + t["cross_attention_norm"] = true; + t["double_self_attention"] = false; + t["upcast_attention"] = false; + t["standardization_norm"] = "rms_norm"; + t["norm_elementwise_affine"] = false; + t["qk_norm"] = "rms_norm"; + t["positional_embedding_type"] = "rope"; + t["use_audio_video_cross_attention"] = true; + t["share_ff"] = false; + t["av_cross_ada_norm"] = true; + t["use_middle_indices_grid"] = true; + t["caption_proj_before_connector"] = true; + + nlohmann::json agreeing; + agreeing["transformer"] = t; + agreeing["transformer"]["use_prompt_adaln_single"] = true; + // Agreement is adopted, and carries the flag through. + const Ltx2DitParams adopted = + vllm::Ltx2AdoptDeclaredDitParams(agreeing, shapes, false, "the test config"); + CHECK(adopted.use_prompt_adaln_single); + + nlohmann::json disagreeing; + disagreeing["transformer"] = t; + disagreeing["transformer"]["use_prompt_adaln_single"] = false; + CHECK_THROWS(vllm::Ltx2AdoptDeclaredDitParams(disagreeing, shapes, false, "the test config")); + // The opt-in must not rescue it: clearing a PORTED flag in the config copy is + // exactly what made this comparison blind before. + CHECK_THROWS(vllm::Ltx2AdoptDeclaredDitParams(disagreeing, shapes, true, "the test config")); + + // And the other direction: shapes WITHOUT the module against a config that + // declares it. + Ltx2DitParams shapes_off = shapes; + shapes_off.use_prompt_adaln_single = false; + CHECK_THROWS(vllm::Ltx2AdoptDeclaredDitParams(agreeing, shapes_off, false, "the test config")); +} + TEST_CASE("ltx2 loader: the f32 widening is OPT-IN and bit-exact over bf16") { const Ltx2DitParams p = TinyParams(); const SyntheticDit syn = BuildSyntheticDit(p, Ltx2DitQuant::kFp8, {}); diff --git a/tests/vllm/multimodal/ltx2_video_fixture.h b/tests/vllm/multimodal/ltx2_video_fixture.h index e2191ed9b..ec3fca7af 100644 --- a/tests/vllm/multimodal/ltx2_video_fixture.h +++ b/tests/vllm/multimodal/ltx2_video_fixture.h @@ -248,7 +248,14 @@ inline vllm::Ltx2DitParams ReducedDitParams() { p.use_middle_indices_grid = true; p.apply_gated_attention = true; // every LTX-2.5 attention is gated p.cross_attention_adaln = true; // the shipped config sets it - p.use_prompt_adaln_single = false; + // TRUE, exactly as both shipped DiTs resolve it: the FP8 file carries no + // config at all and the NVFP4 file's config OMITS the key, so upstream's + // default (model.py:77, model_configurator.py:76) decides — and both files + // carry `prompt_adaln_single`'s tensors. `ReducedDitTransformerConfig` below + // likewise omits the key, so this fixture reproduces the shipped shape rather + // than a configuration nothing ships, and the whole video engine renders + // through the prompt-side AdaLN path (.agents/specs/ltx25-prompt-adaln.md). + p.use_prompt_adaln_single = true; p.ff_bias = false; // LTX-2.5 (gemma4) sets ff_bias=false p.audio_ff_bias = true; return p; diff --git a/tests/vllm/multimodal/test_ltx2_video.cpp b/tests/vllm/multimodal/test_ltx2_video.cpp index e8eaf1220..85b47025a 100644 --- a/tests/vllm/multimodal/test_ltx2_video.cpp +++ b/tests/vllm/multimodal/test_ltx2_video.cpp @@ -927,10 +927,12 @@ TEST_CASE("ltx2 video: the SHIPPED Lightricks checkpoints parse and load") { const vllm::SafetensorsFile file = vllm::SafetensorsFile::Open(path); vllm::Ltx2DitQuant quant = vllm::Ltx2DitQuant::kFp8; vllm::Ltx2DitParams from_shapes = vllm::Ltx2ParseDitParamsFromCheckpoint(file, &quant); - // The manifest parser leaves `use_prompt_adaln_single` at its default; the - // LOADER clears it for the contract (ltx2_loader.cpp), and so does the - // engine. Mirror that here so the two contracts are compared like for like. - from_shapes.use_prompt_adaln_single = false; + // MEASURED from this file's own header: it carries `prompt_adaln_single`, so + // the manifest parser resolves `use_prompt_adaln_single = TRUE` — and nothing + // clears it any more (.agents/specs/ltx25-prompt-adaln.md, issue #644). This + // line used to force it false on BOTH sides of the comparison below, which is + // what made a config/shape disagreement about it unobservable. + CHECK(from_shapes.use_prompt_adaln_single); CHECK(quant == vllm::Ltx2DitQuant::kNvfp4); CHECK(from_shapes.num_layers == 48); CHECK(from_shapes.inner_dim() == 4096); @@ -950,8 +952,12 @@ TEST_CASE("ltx2 video: the SHIPPED Lightricks checkpoints parse and load") { config["transformer"]["use_keyframes_abs_pos_embedding"] = false; nlohmann::json wrapper; wrapper["config"] = config; - vllm::Ltx2DitParams declared = vllm::ParseLtx2DitParams(wrapper); - declared.use_prompt_adaln_single = false; + const vllm::Ltx2DitParams declared = vllm::ParseLtx2DitParams(wrapper); + // The shipped config OMITS `use_prompt_adaln_single`, so it resolves to + // upstream's TRUE default (model_configurator.py:76) — which is what the + // file's own tensors say. Asserted rather than forced: the two sides of the + // contract comparison below must AGREE about it, not be made to. + CHECK(declared.use_prompt_adaln_single); CHECK(declared.double_precision_rope); // frequencies_precision float64 CHECK(declared.av_ca_timestep_scale_multiplier == 1000); CHECK(declared.apply_gated_attention); From 67e53e716005561fd31a1158763a4806959c1b55 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:37:01 +0200 Subject: [PATCH 02/55] record(TOOLS-PARSER-BREADTH): backfill the 41 parsers already shipped, and rescope W1/W2 (#608, #649) (#643) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL The row said "Qwen-Coder XML, Mistral, pythonic, and remaining parsers", "Our code: -", INVENTORIED, with a spec that was never written. All three parsers it named by title were implemented, alongside 38 others. This is a RECORD REPAIR, not an achievement: nothing new ships here. Three counts were wrong and two of them cancelled, which is why they survived a spec, a merge and a report. Ours is 41, not 40 -- the audit's regex used [a-z0-9_]+ and silently dropped the hyphenated granite-20b-fc, while test_detect.cpp:221 has asserted 41 since MODEL-MUSE-GLIMMER-W7. Upstream is 44, not 43. And two of OUR names exist in no upstream registry at the pin (qwen3, a local alias, and muse_glimmer, the off-pin unmerged-PR exception recorded in porting-inventory.md §16). 44 - 5 = 39 shared, + 2 = 41. The old "40 = 43 - 3" balanced only because two errors cancelled. The five upstream-only names are confirmed, but the characterisation of them was wrong in the dangerous direction -- "plain Python" reads as portable. Reading the method bodies rather than the imports: GptOssToolParser is a declared stub whose methods raise NotImplementedError and defer to Harmony; both Cohere classes hard-ImportError without the out-of-tree cohere_melody package and delegate all parsing to it. So only `inkling` is portable from vLLM source. W1/W2 are RESCOPED accordingly -- one ordinary port, one from-scratch write gated on the upstream test, and two names that each owe a recorded decision before any code. State INVENTORIED -> PARTIAL, not ANCHOR-BACKFILL: that state asserts a missing leaf spike and the spec is committed, while PARTIAL's "known to omit upstream behavior" is exactly true of the five. Summary counts re-derived from the rows. Reviewed twice. The first review FAILed it on four record findings -- a docs/USAGE.md anchor that rotted during this branch's own rebase (#641 moved that file by 572 lines), the portability characterisation, a defect flagged only in a PR body, and a backwards precedent citation. All four repaired by a fresh implementer; #649 filed for the stale TOOLS-CALLING-CORE counts. The scoped re-review returned PASS with no findings. Records only: 4 markdown files, no src/, no tests/, no checker weakened. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/engine-matrix.md | 6 +- .agents/roadmap_v1.md | 3 +- .agents/specs/tool-parser-breadth.md | 159 ++++++++++++++++++++++----- docs/STATUS.md | 2 +- 4 files changed, 138 insertions(+), 32 deletions(-) diff --git a/.agents/engine-matrix.md b/.agents/engine-matrix.md index 33d9885d6..b9bc071e8 100644 --- a/.agents/engine-matrix.md +++ b/.agents/engine-matrix.md @@ -42,13 +42,13 @@ forensics: roadmap_v1.md and the parity ledger. | KV cache and memory | 22 | 7 | 3 | 2 | 2 | 1 | 2 | 0 | 5 | | Parallelism | 6 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 5 | | Sampling and generation | 15 | 4 | 2 | 0 | 0 | 4 | 0 | 1 | 4 | -| Structured output and tools | 7 | 0 | 3 | 0 | 0 | 2 | 0 | 0 | 2 | +| Structured output and tools | 7 | 0 | 4 | 0 | 0 | 2 | 0 | 0 | 1 | | Speculative decoding | 21 | 0 | 0 | 1 | 0 | 5 | 0 | 4 | 10 | | Serving, API, CLI, library | 33 | 10 | 2 | 0 | 2 | 10 | 2 | 3 | 4 | | LoRA and adapters | 2 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | | Long context and attention | 10 | 5 | 0 | 0 | 1 | 0 | 1 | 0 | 3 | | Loading, tokenizer, config | 10 | 3 | 3 | 0 | 0 | 1 | 1 | 1 | 1 | -| **Total** | **153** | **35** | **16** | **4** | **9** | **30** | **8** | **11** | **39** | +| **Total** | **153** | **35** | **17** | **4** | **9** | **30** | **8** | **11** | **38** | ## Engine core and scheduling @@ -155,7 +155,7 @@ lifecycle are unchanged. | `TOOLS-GUIDANCE-OUTLINES` | Guidance, outlines, and LM-format-enforcer backends | T2 | `vllm/v1/structured_output/__init__.py:140-159` | - | - | `planned: specs/guidance-outlines-backends.md` | `INVENTORIED` | - | | `TOOLS-CALLING-CORE` | Auto, required, named tool choice; streaming deltas; Hermes and Qwen3 parsers | T0 | `vllm/entrypoints/openai/chat_completion/serving.py:428,688,872`; `vllm/tool_parsers/hermes_tool_parser.py:34`; `vllm/tool_parsers/qwen3_engine_tool_parser.py:7` | `src/vllm/entrypoints/openai/serving_chat.cpp:98,224,239`; `src/vllm/entrypoints/openai/tool_parsers/hermes.cpp:124,180`; `src/vllm/entrypoints/openai/tool_parsers/qwen3.cpp:13` | `tests/vllm/entrypoints/openai/tool_parsers/test_tool_parsers.cpp:27,121,159`; `tests/vllm/entrypoints/openai/tool_parsers/test_tool_choice_grammar.cpp:248,260,280`; `tests/vllm/entrypoints/openai/test_serving.cpp:625,741,903`; **SELECTION SURFACE 2026-07-24 (`CLAIM-DOCS-T2-FIXES`)** — the bundled OpenAI server no longer hardcodes `"hermes"`/`""`: `--tool-call-parser` and `--reasoning-parser` (vLLM's own flag names) select any registered dialect, `auto` runs the same chat-template detection the C ABI uses, `none` disables, and an unknown name aborts startup listing the registry. The name lists are ENUMERATED from the factories (`tool_parser_names()` 40 names / `reasoning_parser_names()` 7), not hand-written at the flag; the whole flag behaviour lives in `ResolveToolParserName` / `ResolveReasoningParserName` so it is unit-tested without a server. DEFAULTS REPRODUCE THE OLD HARDCODE EXACTLY (`hermes` / disabled), so an invocation naming neither flag is unchanged. Anchors: `examples/server/main.cpp` (flags), `src/vllm/entrypoints/openai/tool_parsers/{abstract,detect}.cpp`, `src/vllm/entrypoints/openai/reasoning_parsers/{abstract,detect}.cpp`; tests `tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp`, `tests/vllm/entrypoints/openai/reasoning_parsers/test_detect.cpp` | `planned: specs/tool-calling.md` | `PARTIAL` | - | | `TOOLS-STREAMING-PARSER` | Unified streaming parser engine for reasoning and tool calls, including token-ID scanning, coalesced deltas, replay adapters and parser-specific configurations | T1 | `vllm/parser/engine/streaming_parser_engine.py:89`; `vllm/parser/engine/token_id_scanner.py:29`; `vllm/parser/engine/incremental_lexer.py:80`; `vllm/parser/engine/events.py:11,22`; `vllm/parser/qwen3.py:88`; `vllm/parser/kimi_k2.py:52`; `vllm/parser/engine/parser_engine.py:79` (assembly, residual); `tests/parser/engine/test_engine.py`, `test_token_id_scanner.py`, `test_qwen3.py` @ `555967922` | CORE engine landed 2026-07-27 (`CLAIM-ROADMAP-C8-PARSER`): `include/vllm/parser/engine/{events,parser_engine_config,incremental_lexer,token_id_scanner,streaming_parser_engine,configs,registry}.h` + `src/vllm/parser/engine/{incremental_lexer,token_id_scanner,streaming_parser_engine,configs,registry}.cpp` (scanner + prefix-buffering lexer + transition state machine + JSON-arg brace hold-back + drop-info + qwen3/seed_oss/kimi_k2 configs + unified name->config registry). ASSEMBLY landed 2026-07-27 (`CLAIM-ROADMAP-C8-ASSEMBLY`): `include/vllm/parser/engine/{parser_engine,py_json}.h` + `src/vllm/parser/engine/parser_engine.cpp` (`ParserEngine`: SemanticEvent -> streaming `DeltaMessage` + one-shot `ExtractedToolCallInformation`, held-back streaming-arg prefix, tool_index++, finish() flush, qwen3 `` arg-converter) + `include/vllm/parser/{kimi_k2,parser_manager}.h` + `src/vllm/parser/{kimi_k2,parser_manager}.cpp` (kimi native-header id/name overrides + name->parser dispatch) + assembly fields on `parser_engine_config.h`/`configs.cpp`. SERVING-SSE dispatch swap landed 2026-07-27 (`CLAIM-ROADMAP-C8-SERVING`): `src/vllm/entrypoints/openai/serving_chat.cpp` (`ShapeChatDeltaEngine`/`ShapeChatMessageEngine`/`MakeParserEngine` + 3 name-selected drive-site branches + `ChatSseStream` engine member) + `include/vllm/entrypoints/openai/serving_chat.h`; faithful `include_reasoning` request field on `protocol.{h,cpp}`. CONFIG FAMILIES landed 2026-07-27 (`CLAIM-ROADMAP-C8-CONFIGS`): 5 more engine-backed families ported as additive `ParserEngineConfig` builders in `src/vllm/parser/engine/configs.cpp` (`minimax_m2_config`, `glm47_moe_config`, `deepseek_v4_config`, `deepseek_v32_config`, `nemotron_v3_config` + `_minimax_m2`/`_glm47`/`_dsml` std::regex arg-converters) + `registry.cpp`/`parser_manager.cpp` dispatch + `include/vllm/parser/glm47_moe.{h,cpp}` (name-`.strip()` over the existing hooks). CONFIG FAMILIES C8-2 landed 2026-07-27 (`CLAIM-ROADMAP-C8-CONFIGS-2`): the last 2 deferred families gemma4 + inkling PORTED — added 4 additive assembly-core virtual seams (default-inert for the other 8 families) `preprocess_feed` (parser_engine.py:210), virtual `events_to_delta` (:706), virtual `single_pass_parse` (:645), `args_wrapper_keys` from `_extract_args_value` (:1064) + virtual `reset`/`extract_reasoning`; `src/vllm/parser/engine/configs.cpp` (`gemma4_config` + `_gemma4_arg_converter` key:value scanner; `inkling_config` + `_inkling_arg_converter` JSON-span carver) + `include/vllm/parser/{gemma4,inkling}.{h,cpp}` (gemma4 `_preprocess_feed` channel-injection + `_events_to_delta` `thought\n`-strip + `extract_reasoning`; inkling `args_wrapper_keys` unwrap + `_single_pass_parse` trailing flush) + `registry.cpp`/`parser_manager.cpp` dispatch. JSON-SCHEMA ARG-TYPE COERCION landed 2026-07-28 (`CLAIM-C8-ARG-COERCION`): `_fix_arg_types` / `_streamable_string_keys` / `find_tool_properties` (parser_engine.py:227,269,365,348) ported over the ALREADY-ported `extract_types_from_schema` / `coerce_to_schema_type` helpers (`tool_parsers/utils.cpp`) — `ParserTool` now carries the function `parameters` JSON-schema (threaded from `serving_chat.cpp` `ToParserRequest`), so a request whose tools declare typed params (int/number/bool/string/array/null) has its assembled `tool_calls[].function.arguments` coerced to the declared types in BOTH streaming (`parse_delta`) and one-shot (`extract_tool_calls`/`parse`); no-schema/absent-tools = identity (byte-identical). Additive to `parser_engine.{h,cpp}` (recursive `_coerce_dict`/`_coerce_value` + `find_tool_properties`) + `serving_chat.cpp` schema threading; no other TU changed — anchor `src/vllm/parser/engine/streaming_parser_engine.cpp:143` | `tests/vllm/parser/engine/test_streaming_parser_engine.cpp` (586/586, 8 scenarios) + `test_parser_engine_assembly.cpp` (`..._goldens.inc`): 5038/5038 field-for-field over 30 scenarios (streaming DeltaMessage + one-shot extract_tool_calls + non-streaming parse()) vs vLLM 0.26 assembly — scenarios 27-30 (`CLAIM-C8-ARG-COERCION`) add qwen3 typed-schema coercion (whole+char, days`5`/active`true`/temp`3.14`/tags`[1,2,3]` coerced, unit stays string), qwen3 schema-mismatch (uncoercible `"abc"` left as-is + nullable `"null"`->null), kimi_k2 JSON-native `"5"`->int in extract (converter-less: streaming stays raw, extract coerces — divergence gated); RED-first proven (38 asserts, first boundary `qwen3_typed_schema_wholedelta extract tc[0] arguments`: identity `{"days": "5", …}` vs coerced `{"days": 5, "unit": "celsius", "active": true, "temp": 3.14, "tags": [1, 2, 3]}`); scenarios 10-19 (`CLAIM-ROADMAP-C8-CONFIGS`) add minimax_m2 / glm47_moe / deepseek_v4 / deepseek_v32 / nemotron_v3, scenarios 20-26 (`CLAIM-ROADMAP-C8-CONFIGS-2` 2026-07-27) add gemma4 (explicit + elided channel, whole+char) + inkling (think/tool/trailing-text + non-object-args fallback, whole+char), each whole-delta AND char-by-char; RED-first proven for all seams (32 asserts `_safe_arg_prefix`; 2 asserts glm47 name-`.strip()`; 13 asserts gemma4 `_events_to_delta` at `gemma4_channel_tool_wholedelta delta[0] reasoning`; 5 asserts gemma4 `_preprocess_feed` at `gemma4_elided_channel_wholedelta delta[0] content`; 4 asserts inkling `args_wrapper_keys` at `inkling_nonobject_args_wholedelta extract tc[0] arguments`; 2 asserts inkling `_single_pass_parse` at `inkling_think_tool_text_wholedelta parse content`); serving `tests/vllm/entrypoints/openai/test_serving_chat_stream.cpp` (`..._goldens.inc`): 210/210 chunk-for-chunk SSE parity over the 9 scenarios vs vLLM 0.26 `chat_completion_stream_generator` (role frame + per-delta reasoning/content/tool-call deltas + terminal `tool_calls` flip + name-selected dispatch), RED-first proven (6 CHECKs, first boundary `chunk[1]` reasoning-vs-raw-content); goldens byte-reproduced by `tools/parity/dump_{streaming_parser_engine,parser_engine_assembly,serving_chat_stream}.py` — anchor `tests/vllm/parser/engine/test_streaming_parser_engine.cpp:99` | [specs/streaming-parser-engine.md](specs/streaming-parser-engine.md), [specs/parser-assembly-c8.md](specs/parser-assembly-c8.md) | `ACTIVE` | `CLAIM-ROADMAP-C8-PARSER`, `CLAIM-ROADMAP-C8-ASSEMBLY`, `CLAIM-ROADMAP-C8-SERVING`, `CLAIM-ROADMAP-C8-CONFIGS`, `CLAIM-ROADMAP-C8-CONFIGS-2`, `CLAIM-C8-ARG-COERCION` | -| `TOOLS-PARSER-BREADTH` | Qwen-Coder XML, Mistral, pythonic, and remaining parsers | T1 | `vllm/tool_parsers/__init__.py:16-206` | - | - | [tool-parser-breadth.md](specs/tool-parser-breadth.md) | `INVENTORIED` | - | +| `TOOLS-PARSER-BREADTH` | Qwen-Coder XML, Mistral, pythonic, and the remaining `--tool-call-parser` dialects. **RECORD BACKFILL 2026-08-13 (#608 W0) — NO code shipped in this move; the surface below landed incrementally under other rows and was never recorded here, the same defect `SAMPLE-REASONING` carried before its own W0.** Landed: **41 accepted parser names over 37 parser families**, the count PINNED by the registry test rather than asserted in prose. Aliases account for the difference: `llama3_json`/`llama4_json` → one class, `qwen3_coder`/`qwen3_xml`/`mimo` → one, `glm45`/`glm47` → one. All three families this row names by title are among them (`qwen3_coder`, `mistral`, `pythonic`). Selection is three-way — explicit `--tool-call-parser NAME`, `auto` template sniffing over a **27-row ordered marker table**, or `none`; an unknown name throws enumerating the whole registry. **Known omission vs the pin, which is why this is `PARTIAL` and not further:** upstream's lazy registry carries **44** names to our 41, and **exactly 5 are upstream-only** — `openai` (`GptOssToolParser`), `inkling` (`InklingEngineToolParser`, ParserEngine-backed), `minimax_m3` (`MinimaxM3ToolParser`, the ONLY Rust-backed one of the five), `cohere_command3` + `cohere_command4` (plain Python, referenced by ZERO recipes, so a usage-driven audit cannot see them). Conversely **2 of our names are absent from upstream's registry at the pin**: `qwen3` (our local alias for the Hermes-JSON Qwen dialect) and `muse_glimmer` (`muse_glimmer` exists in NO vLLM revision in the pin's ancestry — it is decorator-registered at `muse_glimmer_tool_parser.py:183` on the UNMERGED [vllm#51655](https://github.com/vllm-project/vllm/pull/51655), head `075d645af`, the off-pin anchor exception recorded in [porting-inventory.md](porting-inventory.md) §16) — so 44 − 5 = 39 shared, 39 + 2 = 41. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry (post-pin; they arrive with the pin advance). W1 (`openai`, `inkling`, `minimax_m3`), W2 (both Cohere) and W3 (port `ToolParserTestConfig` as a shared harness) remain owed | T1 | registry `vllm/tool_parsers/__init__.py:24-201` (the 44-name `_TOOL_PARSERS_TO_REGISTER` dict) + `:204,210` (`register_lazy_tool_parsers`); manager `vllm/tool_parsers/abstract_tool_parser.py:223,236,318` (`ToolParserManager`, `get_tool_parser`, `register_module`); the 5 upstream-only classes `gptoss_tool_parser.py:17`, `inkling_tool_parser.py:7`, `minimax_m3_tool_parser.py:7`, `cohere_command_tool_parser.py:125,138` | factory `src/vllm/entrypoints/openai/tool_parsers/abstract.cpp:72` (`get_tool_parser`, 41 name branches → 37 classes) + `:269` (`tool_parser_names`, the enumeration the flag's error message is built from); autodetect `src/vllm/entrypoints/openai/tool_parsers/detect.cpp:65` (`kToolParserMarkers`, 27 ordered rows) + `:17` (the `ORDER MATTERS` collision analysis recording which families are deliberately EXPLICIT-ONLY) + `:100` (`DetectToolParser`, first-match with hermes fallback) + `:124` (`ResolveToolParserName`, `auto`/`none`/explicit plus the enumerating throw); per-family parsers under `src/vllm/entrypoints/openai/tool_parsers/` | registry pin `tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp:206` — every enumerated name resolves and every marker-table name is registered — with `:221` pinning `names.size() == 41`, so a factory branch added without listing its name fails the suite instead of silently shipping an unreachable dialect; **38 test files** under `tests/vllm/entrypoints/openai/tool_parsers/` = 34 per-family + 4 cross-cutting (`test_detect.cpp`, `test_tool_parsers.cpp:27` covering hermes+qwen3, `test_tool_choice_grammar.cpp`, `test_structural_tags.cpp`), and `deepseek_v31` shares `test_deepseek.cpp`, so all 37 families are covered. NOT a ported common suite: every file is hand-written per parser, so the floor differs per parser — upstream's `ToolParserTestConfig` is unported (W3) | [tool-parser-breadth.md](specs/tool-parser-breadth.md) | `PARTIAL` | - | ## Speculative decoding diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 90cacc594..b868f97c0 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -182,8 +182,9 @@ issue is not yet placed. Keyed record: update in place, never append. | [#558](https://github.com/mudler/vllm.cpp/issues/558) | — | `tests/parity/hf_snapshot.h` has no guard against declaration-order breaks: the C++ build catches them, but the records-only lane that broke it never builds C++, and all 14 TUs that include the header are checkpoint-gated so `ctest` reports the break as `***Not Run`. `fafa16f0f` (#546, #551) fixed the ordering and carried no guard | bug | | [#606](https://github.com/mudler/vllm.cpp/issues/606) | — | `vllm-serve` aborts on `--enable-auto-tool-choice` (89/157 recipes) and `--trust-remote-code` (82/157), both of which are no-ops for us, so a copy-pasted official recipe command never reaches model load. Needs an accepted-and-inert seam with a per-flag reason; no row owns serve CLI recipe compatibility | feature | | [#607](https://github.com/mudler/vllm.cpp/issues/607) | — | No `--language-model-only`: 43 recipes skip the vision encoder to hand its VRAM to the KV cache and we load the tower unconditionally. The flag appears in this repo only in `tools/bench/run_serve_low.py`, which passes it to the ORACLE — a grep reads as coverage and is not | feature | -| [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | Six `--tool-call-parser` names missing (`openai`, `inkling`, `minimax_m3`, `nemotron_json`, `kimi_k3`, `ling3`), closing the last 8 of 90 official-recipe uses; four are portable at the pin, two are post-pin | feature | +| [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | **W0 (record backfill) landed 2026-08-13; row `INVENTORIED` → `PARTIAL`, 41 shipped names / 37 families recorded.** Re-derived from the two REGISTRIES rather than from recipe usage: **five** `--tool-call-parser` names are upstream-only at the pin — `openai`, `inkling`, `minimax_m3` (W1, recipe demand) and `cohere_command3`, `cohere_command4` (W2, ZERO recipe uses, so usage-driven audits miss them). **Only `inkling` is PORTABLE from vLLM source**: `minimax_m3` is backed by the Rust crate, `openai` is a declared Harmony stub that raises on both methods, and both Cohere names are shims over the out-of-tree `cohere_melody` package, so W1/W2 each owe a recorded decision before code rather than a text port. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry and arrive with the pin advance, not here. W3 ports upstream's shared `ToolParserTestConfig` harness. The earlier "six missing" framing was usage-derived: it listed `nemotron_json` as portable (it is not registered at the pin) and missed both Cohere entries | feature | | [#647](https://github.com/mudler/vllm.cpp/issues/647) | — | Oracle policy had no fallback and no pin concept: five upstreams beyond vLLM are already compared against (vLLM-Omni, SGLang, llama.cpp, `transformers`, tt-forge) with their pins scattered across individual specs or absent entirely. AGENTS.md now admits a named secondary oracle where vLLM implements nothing, `.agents/oracles/.md` pins each one file-per-oracle, and `check-oracle-pins.py` enforces both directions. The gateability debts for `sglang`, `diffusers` and `tt-forge` stay open on this issue | feature | +| [#649](https://github.com/mudler/vllm.cpp/issues/649) | `TOOLS-CALLING-CORE` | That row's prose still records `tool_parser_names()` 40 / `reasoning_parser_names()` 7; both enumerations have grown since 2026-07-24 and are now **41** (`tool_parsers/abstract.cpp:269`) and **12** (`reasoning_parsers/abstract.cpp:72`). Code and tests are correct — `test_detect.cpp:221` already pins 41 — only the record drifted. Halves belong to two other rows (#608, #605), so it is filed rather than repaired inside #643 (found while implementing #643's review findings) | bug | ## Top-level portfolio diff --git a/.agents/specs/tool-parser-breadth.md b/.agents/specs/tool-parser-breadth.md index 513083d93..5d5a05885 100644 --- a/.agents/specs/tool-parser-breadth.md +++ b/.agents/specs/tool-parser-breadth.md @@ -1,4 +1,4 @@ -# TOOLS-PARSER-BREADTH — close the tool-parser mirror, and record the 40 already shipped +# TOOLS-PARSER-BREADTH — close the tool-parser mirror, and record the 41 already shipped Issue: [#608](https://github.com/mudler/vllm.cpp/issues/608) Row: `TOOLS-PARSER-BREADTH` ([engine-matrix.md](../engine-matrix.md)) @@ -13,28 +13,58 @@ The row reads: All three parsers it names by title are **implemented**. `qwen3_coder`, `mistral` and `pythonic` are all in -`src/vllm/entrypoints/openai/tool_parsers/abstract.cpp`, alongside 37 others — -**40 registered names** with **38 test files** under +`src/vllm/entrypoints/openai/tool_parsers/abstract.cpp`, alongside 38 others — +**41 registered names** over 37 families, with **38 test files** under `tests/vllm/entrypoints/openai/tool_parsers/`. The row records none of it, and -its spec was never written. +its spec was never written. (This paragraph originally said 40; W0 re-derived it +and corrected it — see `## Now`.) This is the shape `SAMPLE-REASONING` carried before its W0 ("the seam shipped under `da933828` but this row was never advanced"). **Backfill precedes extension**: without it, adding five parsers looks like a change that delivers -forty. +forty-one. ## The actual gap, from the registries — not from usage -Upstream `vllm/tool_parsers/__init__.py` at the pin `5559679` registers **43** -names. We register **40**. Exactly **five** are upstream-only: - -| Name | Upstream module · class | Recipe uses | -|---|---|---:| -| `openai` | `gptoss_tool_parser` · `GptOssToolParser` | 2 | -| `inkling` | `inkling_tool_parser` · `InklingEngineToolParser` | 2 | -| `minimax_m3` | `minimax_m3_tool_parser` · `MinimaxM3ToolParser` | 1 | -| `cohere_command3` | `cohere_command_tool_parser` · `CohereCommand3ToolParser` | **0** | -| `cohere_command4` | `cohere_command_tool_parser` · `CohereCommand4ToolParser` | **0** | +Upstream `vllm/tool_parsers/__init__.py:24-201` at the pin `5559679` registers +**44** names. We register **41**. Exactly **five** are upstream-only (and two of +ours are not in upstream's registry there — see `## Now`): + +| Name | Upstream module · class | Recipe uses | What backs it upstream | +|---|---|---:|---| +| `openai` | `gptoss_tool_parser` · `GptOssToolParser` | 2 | Harmony — the class is a declared **stub** that raises | +| `inkling` | `inkling_tool_parser` · `InklingEngineToolParser` | 2 | ParserEngine adapter — **the only one portable from vLLM source** | +| `minimax_m3` | `minimax_m3_tool_parser` · `MinimaxM3ToolParser` | 1 | the **Rust** tool-parser crate | +| `cohere_command3` | `cohere_command_tool_parser` · `CohereCommand3ToolParser` | **0** | out-of-tree **`cohere_melody`** package | +| `cohere_command4` | `cohere_command_tool_parser` · `CohereCommand4ToolParser` | **0** | out-of-tree **`cohere_melody`** package | + +### Only one of the five is a port. Read this before scoping W1 or W2 + +"Upstream-only name" does not mean "an upstream text parser we have not typed +out yet". For four of these five there is **no grammar in vLLM's Python source +at all**. Verified by reading the pinned checkout at +`5559679229bc961848b121ccdeaa8fa5d79bec98`: + +- **`openai` is Harmony-backed by explicit declaration.** `GptOssToolParser` + (`vllm/tool_parsers/gptoss_tool_parser.py:17`) is a stub whose docstring says + "All output parsing is handled by HarmonyParser. This stub exists as a + capability declaration via HarmonyParser.tool_parser_cls." **Both** methods + raise — `:31` and `:45` — with `NotImplementedError("GptOssToolParser is a + stub. Use HarmonyParser for tool parsing.")`. Registering the name is not + mirroring the behaviour; the behaviour lives in Harmony. +- **Both Cohere classes are shims over `cohere_melody`.** + `vllm/tool_parsers/cohere_command_tool_parser.py:6-13` imports + `PyFilter`/`PyFilterOptions` from that out-of-tree package and raises a hard + `ImportError` when it is absent; `BaseCohereCommandToolParser` (`:34`), + `CohereCommand3ToolParser` (`:125`) and `CohereCommand4ToolParser` (`:138`) + delegate all parsing to it. **Nothing in vLLM source describes the dialect.** +- **`minimax_m3` is Rust-backed.** `minimax_m3_tool_parser.py:7` subclasses + `RustToolParser` and only sets `rust_parser_name = "MinimaxM3ToolParser"` + (`:18`); the grammar lives in the Rust crate. Its class docstring and its + upstream test are the only executable descriptions available to us. +- **`inkling` is a ParserEngine adapter** over `InklingParserToolAdapter` + (`inkling_tool_parser.py:4,7`), and we already carry the `inkling` engine + config. This is the one ordinary port of the five. Three names the recipes DO reference are in **neither** registry — `nemotron_json`, `kimi_k3`, `ling3`. They are post-pin and land with the pin @@ -69,20 +99,43 @@ empty arguments, surrounding text, escaped strings, and a list of malformed inputs. We have no equivalent — our 38 test files are hand-written per parser, so the floor differs per parser and nothing enforces one. -Porting that harness raises the floor for all 40 existing parsers and gives the +Porting that harness raises the floor for all 41 existing parsers and gives the five new ones a bar that is upstream's rather than invented. That is W3 below; it is the highest-value item here and deliberately does not block W1. ## Work breakdown -- **W0 — backfill the row to reality.** Record the 40 registered names, the - factory anchor (`tool_parsers/abstract.cpp`), the autodetect table - (`tool_parsers/detect.cpp`), and the 38 existing test files. Move the state off - `INVENTORIED` to what that evidence backs, and reconcile the summary counts in - the same change (`scripts/check-agent-record.py` is CI-enforced). No new parser. -- **W1 — the three with recipe demand**: `openai`, `inkling`, `minimax_m3`. - `minimax_m3` ports its upstream test; the other two author one. -- **W2 — complete the mirror**: `cohere_command3`, `cohere_command4`. +- **W0 — backfill the row to reality. DONE 2026-08-13, see `## Now`.** Record the + 41 registered names, the factory anchor (`tool_parsers/abstract.cpp`), the + autodetect table (`tool_parsers/detect.cpp`), and the 38 existing test files. + Move the state off `INVENTORIED` to what that evidence backs, and reconcile the + summary counts in the same change (`scripts/check-agent-record.py` is + CI-enforced). No new parser. +- **W1 — the three with recipe demand**, which are three different jobs, not one + batch (see "Only one of the five is a port" above): + - `inkling` — the ordinary one. A ParserEngine adapter port over the + `inkling` engine config we already carry. Test authored from scratch. + - `minimax_m3` — the grammar is in the Rust crate, so the implementation is + written from the wire format its docstring describes and is recorded + from-scratch in `porting-inventory.md` §9. Its upstream test + (`test_minimax_m3_tool_parser.py`) ports and is the fidelity gate. + - `openai` — **there is no gpt-oss text parser upstream to port.** The + registry entry is a stub that raises on both methods. Mirroring the name + alone ships a name that resolves and then refuses. Decide before writing + code, and record the decision here: either mirror the Harmony seam (large, + and a different row), or register the name with an explicit refusal naming + the missing piece, per AGENTS.md's "an arm that is not implemented is + refused with a message naming the missing piece and recorded as owed". Do + **not** plan it as an ordinary text parser. +- **W2 — complete the mirror**: `cohere_command3`, `cohere_command4` — **also + not a text port.** All the grammar lives in `cohere_melody`, an out-of-tree + package we do not vendor and upstream does not ship; there is nothing in vLLM + source to read, so "port its tests in the same change" and "cite the upstream + `file:line` you ported from" both have no referent for the dialect itself. W2 + therefore owes a decision recorded here before code: reimplement the dialect + from the model's own chat template / wire format (from-scratch, §9), or + refuse both names explicitly and record the external dependency as the + blocker. It stays in scope as a mirror gap either way. - **W3 — port `ToolParserTestConfig`** as a shared C++ harness and retrofit it across the existing parsers, one batch per change. - **Deferred to the pin advance**: `nemotron_json`, `kimi_k3`, `ling3`. @@ -101,7 +154,12 @@ parser whose marker collides with an existing row must stay explicit — a wrong autodetect is worse than no autodetect, because it silently mis-parses a model the user never named. -`docs/USAGE.md:878` states the parser-name count and must move with each wave. +`docs/USAGE.md:1126` states the parser-name count (already correct at 41 over 37 +families) and must move with each wave, as must the `docs/STATUS.md` row. +Re-derive that line rather than trusting it: it read `:902` when W0 was written +and rotted to `:1126` when this branch rebased onto `cefacd2d0` (#641), which +changed `docs/USAGE.md` by 572 lines. Nothing gates anchor line numbers (#632), +so a rebase moves them silently and a re-run preflight still passes. ## Gates @@ -130,5 +188,52 @@ new test did. ## Now -`SPIKE` — spec committed, implementation not started. Next: a fresh implementer -takes W0 alone, because a backfill mixed with a port cannot be reviewed cleanly. +`PARTIAL` — **W0 landed 2026-08-13 (#608).** The row now records the shipped +surface (41 names / 37 families, the factory and `tool_parser_names()` anchors, +the 27-row autodetect table, the 38 test files and the count-pinning registry +test), and moved `INVENTORIED` → `PARTIAL`; the engine-matrix summary counts and +`docs/STATUS.md` moved with it. No parser was ported and no `src/` behaviour +changed — this was a record repair. + +`PARTIAL` rather than `ANCHOR-BACKFILL` because that state means "code and tests +but no leaf spike", and this spec is committed; what remains true is the other +half, "the implementation is also known to omit upstream behavior" — the five +upstream-only names below. + +**W0 corrected three counts this spec had wrong**, from the registries at +`5559679`: + +- We register **41** names, not 40. `muse_glimmer` took it 40 → 41 on + 2026-08-10 under `MODEL-MUSE-GLIMMER-W7`; `test_detect.cpp:221` already pinned + 41, so the spec was stale against a committed test. +- Upstream registers **44**, not 43. +- The five upstream-only names are confirmed exactly as listed. But **two of our + names are absent from upstream's registry at the pin** — `qwen3` (a local + alias) and `muse_glimmer`. 44 − 5 = 39 shared; 39 + 2 = 41. The earlier + "40 = 43 − 3" arithmetic worked only because two errors cancelled. + +`muse_glimmer` is not a drifted anchor but the **off-pin exception** recorded in +[porting-inventory.md](../porting-inventory.md) §16: it exists in NO vLLM +revision in the pin's ancestry (absent at `555967922` AND at the older +`e24d1b24`), and is decorator-registered only at +`muse_glimmer_tool_parser.py:183` on the unmerged +[vllm#51655](https://github.com/vllm-project/vllm/pull/51655), head `075d645af`. +So the honest reading of "44 vs 41" is that our 41 spans two upstream sources, +and a future pin advance that lands #51655 folds `muse_glimmer` into the shared +set rather than adding a name. + +**W0's characterisation of the five was itself wrong, and is corrected here +(2026-08-13, review of #643).** W0 wrote "only `minimax_m3` is Rust-backed; +`inkling` is a ParserEngine adapter and `openai`/`cohere_command3`/ +`cohere_command4` are plain Python". The COUNT correction it made stands — five +upstream-only names, not the "three Rust/Harmony-backed ones" `docs/STATUS.md` +claimed. The characterisation does not: **plain Python is not the same as +portable**, and `openai` plus both Cohere names delegate outside vLLM's source +entirely. `openai` is Harmony-backed by explicit declaration (a stub that raises +on both methods) and the Cohere pair are shims over the out-of-tree +`cohere_melody` package. Of the five, **only `inkling` is portable from vLLM +source**. See "Only one of the five is a port" above for the read anchors; W1 +and W2 are scoped to that reading, not to the "plain Python" one. + +Next: W1 (`openai`, `inkling`, `minimax_m3`), then W2 (both Cohere), then W3 +(the `ToolParserTestConfig` harness) — each its own change and its own review. diff --git a/docs/STATUS.md b/docs/STATUS.md index 2acbe72f1..211ef56e3 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -162,7 +162,7 @@ token-for-token correctness against the pinned oracle. | KV-cache events (for external routers) | Built, off by default; generation and payload gated, live ZMQ transport deferred | Block store/remove/clear events (`BlockStored`/`BlockRemoved`/`AllBlocksCleared`) emitted at the prefix-cache sites when enabled, with a `msgpack` payload byte-identical to vLLM's `msgspec` encoding. Behind a publisher seam faithful to `--kv-events-config`; the live ZMQ transport is not wired yet. Off by default, so the prefix-cache path is byte-identical. | | Sampling | Supported | Greedy, temperature, top-k/p, min-p, presence/frequency/repetition penalties, seed, stop/stop_token_ids, min_tokens, logit_bias, allowed_token_ids, bad_words, in vLLM's exact order. Custom logits processors are supported through a per-request C-ABI callback (`vllm_logits_processor`, ABI v8), absent by default (byte-identical). Sample logprobs are emitted end-to-end for `/v1/completions` and `/v1/chat/completions` (`logprobs`/`top_logprobs`). A request may instead score an EXPLICIT set of vocab ids (`logprob_token_ids`, generative scoring): exactly those ids plus the sampled token, whose rank is still over the full vocab, and no full-vocab sort — library surface only, the OpenAI request field is not wired yet. Parallel sampling (`n>1`) is supported, returned as n indexed `choices` (`n==1` byte-identical). Beam search is supported through the `BeamSearch` driver — an outer engine loop that scores beams by cumulative logprob with a length penalty and returns the top `beam_width` sequences (deterministic, token-exact vs vLLM's algorithm); it is wired on the OpenAI `use_beam_search` request field for `/v1/completions` and `/v1/chat/completions` over BOTH the synchronous engine AND the production AsyncLLM HTTP server (an async `BeamSearchAsync` driver, token-identical); the C-ABI beam params and streaming beam are not exposed yet; per-beam concurrent stepping is a named residual — beams are stepped sequentially). `best_of` is supported on both endpoints (`best_of==n` is the default no-op). `prompt_logprobs` is computed and returned on `RequestOutput`; the OpenAI `echo` serialization is not wired yet. All four `logprobs_mode` values work (`processed_*` shows the top-k mask); in-library only. | | Structured output | Supported (subset), engine-enforced; xgrammar backend W1 (CPU, not yet production-wired) | JSON schema, JSON object, regex, choice, GBNF grammar. Constrained decoding runs in the production engine (native grammar backend, per-step logits bitmask) and is reachable from OpenAI `response_format` and the C ABI (ABI v2 `structured_*` fields). A second, xgrammar-faithful backend (`XgrammarStructuredOutputBackend`, vLLM's default `auto`) is built behind the same seam: it reuses the native pushdown-FSM/trie matcher (xgrammar's own algorithm) and adds the xgrammar JSON-schema→EBNF converter that preserves property declaration order + `any_whitespace` + the `basic_*` grammar, closing the key-order/whitespace/exotic-schema parity gap. CPU-gated (`test_backend_xgrammar` 6/6, RED-first); production wiring + the `auto` fallback + GPU oracle parity are the named residuals (see `.agents/specs/xgrammar-backend.md`) | -| Tool-call parsing | 36 parser families / 40 accepted names, streaming | Every vLLM tool parser at the pin except the three Rust/Harmony-backed ones: pure-text parsers ported 1:1, the six engine-backed families reimplemented from their wire formats, all held to the upstream test suites. Selection via `--tool-call-parser` (server), `tool_parser` (C ABI), or template auto-detection; native-syntax forced tool_choice where expressible. Tables: docs/BENCHMARKS.md | +| Tool-call parsing (`TOOLS-PARSER-BREADTH`, PARTIAL) | 37 parser families / 41 accepted names, streaming | 39 of upstream's 44 registered names at the pin, plus 2 that upstream's registry does not carry there (`qwen3`, our alias for the Hermes-JSON Qwen dialect, and `muse_glimmer`) = 41 accepted names; pure-text parsers ported 1:1, the engine-backed families reimplemented from their wire formats. FIVE upstream names are NOT ported, and only ONE of them is a port we could make from vLLM source: `inkling`, a ParserEngine adapter. `minimax_m3` is backed by upstream's Rust crate; `openai` (`GptOssToolParser`) is a declared Harmony stub that raises on both methods; `cohere_command3` and `cohere_command4` are shims over the out-of-tree `cohere_melody` package. So for four of the five there is no grammar in vLLM source to mirror — tracked as W1/W2 in `.agents/specs/tool-parser-breadth.md`, which records the decision each one owes. Upstream's shared `ToolParserTestConfig` harness is also unported, so the per-parser test floor is set file by file rather than enforced (W3). Selection via `--tool-call-parser` (server), `tool_parser` (C ABI), or template auto-detection over a 27-row ordered marker table; native-syntax forced tool_choice where expressible. Tables: docs/BENCHMARKS.md | | Reasoning parsing (`SAMPLE-REASONING`, ACTIVE, partial coverage) | 12 names, streaming | think_auto (auto-detect default: content unless markers appear), deepseek_r1, deepseek_v3 (passthrough) / holo2 (thinking→R1), mistral ([THINK]), minimax_m2 (+append_think), step3, olmo3, muse_glimmer, and qwen3 / mimo - reasoning split engine-side BEFORE tool parsing, streamed as `reasoning` deltas in the chat chunks. qwen3+mimo are the first ENGINE-BACKED adapter (one reasoning face over the shared `src/vllm/parser/engine/` parser, so `` ends reasoning with no ``); the rest are text parsers. Coverage: 12 of upstream's ~28 registered names (remaining engine-backed adapters + text families tracked as W3/W2 in specs/reasoning-parsers.md); each ported parser doctest-gated vs its tests/reasoning case | | Unified streaming parser engine | Core, assembly, serving-SSE dispatch landed, gated; all 10 engine-backed families ported (family parity closed); JSON-schema tool-arg type coercion landed | The vLLM 0.26 declarative `parser/engine/` (shared state machine plus all 10 configs: qwen3, seed_oss, kimi_k2, minimax_m2, glm47_moe, deepseek_v4/v32, nemotron_v3, gemma4, inkling) and assembly layer, gated field-for-field vs vLLM 0.26. An engine-backed `--tool-call-parser` name drives the live chat SSE chunks, off by default. When a request's tools declare typed parameters, the assembled tool-call arguments are coerced to the declared JSON types (int/number/bool/string/array/null) 1:1 with vLLM `_fix_arg_types`, in both streaming and one-shot; no schema means the arguments pass through as strings unchanged. Details: .agents/specs/parser-assembly-c8.md | | OpenAI server | Subset; v0.0.2 publishes eight server bundles; Windows v0.0.3-pre.1 pending | Completion/chat (SSE), models, health/version/ping, metrics, tokenize/detokenize, tokenizer/server info, prefix-cache reset, abort, and Sora-shaped video creation/content. Tokenizer info and abort are flag-gated; cache reset lacks live async backing. Details: docs/USAGE.md | From 48412ae2d843d2e5bb7cca5a5f4efed76868b2a4 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 20:40:57 +0200 Subject: [PATCH 03/55] spec(ENG-MM-INPUT-PIPELINE): map the mm CONFIG seam, including the REFUSAL it carries (#607) (#642) FOLLOWING_AGENTS_PROTOCOL The multimodal seam map covered the input pipeline and the tower but not the config that decides whether either runs. 43 of the 157 official recipes pass --language-model-only and we reject it. I filed #607 saying the flag "skips loading the vision encoder", implying a boolean we could expose through an existing seam. The pinned oracle says otherwise, and the correction changes the port. multimodal.py:78's own docstring: "disables all multimodal inputs by setting all modality limits to 0. Equivalent to setting --limit-mm-per-prompt to 0 for every modality." The encoder skip is a CONSEQUENCE, at interfaces.py:293, which builds the tower inside no_init_weights + StageMissingLayer when ALL limits are zero; upstream's own docstring at :268 says so. Any route to zero limits gets it, so porting the boolean alone would invent a path upstream does not have. A fresh review then found the map still incomplete on the half that changes BEHAVIOUR. validate_num_items (context.py:409-428) raises VLLMValidationError from parse_mm_data (:461) and chat_utils.py:662, folded through allowed_mm_limits (:392-405) -- so upstream --language-model-only REFUSES every multimodal request, absent --enable-mm-embeds. That undercut my own claim that "L2 without L3 is honest and shippable": as first written, L2 would have accepted an image request upstream rejects, on the flag's PRIMARY observable axis, while my caveat guarded only "frees VRAM". The refusal now sits in L1, where the limits' own semantics belong, and L2's honesty is conditional on it rather than asserted. Our baseline is nothing, not partial: no MultimodalConfig, no limit_per_prompt, nothing gating tower construction, verified by searching for the behaviour rather than the name. This is a port, and #607's title now says so. Placed on ENG-MM-INPUT-PIPELINE rather than a new row -- that row is already "the C++ mirror of vllm/multimodal/". Open PRs were diffed as well as issues before writing; no branch touched either symbol. One second-order finding worth the record: qwen3_next.py:325 proves the flag is not purely a memory knob -- upstream uses it to select the FUSED QK-norm+RoPE+gate path while ours is unconditionally fused. That is the serving-side twin of #414. Spec plus two roadmap lines. No code, no row-state change. Reviewed twice; the scoped re-review returned PASS. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/roadmap_v1.md | 4 +- .agents/specs/multimodal-track.md | 95 +++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index b868f97c0..759da0349 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -181,7 +181,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#469](https://github.com/mudler/vllm.cpp/issues/469) | — | `test_ops_glue.cpp:190`'s `CHECK_THROWS` is satisfied by the CPU kernel's second guard, not the dispatch guard it names — mutation M8 survives. Behavior is correct; test strength only | bug | | [#558](https://github.com/mudler/vllm.cpp/issues/558) | — | `tests/parity/hf_snapshot.h` has no guard against declaration-order breaks: the C++ build catches them, but the records-only lane that broke it never builds C++, and all 14 TUs that include the header are checkpoint-gated so `ctest` reports the break as `***Not Run`. `fafa16f0f` (#546, #551) fixed the ordering and carried no guard | bug | | [#606](https://github.com/mudler/vllm.cpp/issues/606) | — | `vllm-serve` aborts on `--enable-auto-tool-choice` (89/157 recipes) and `--trust-remote-code` (82/157), both of which are no-ops for us, so a copy-pasted official recipe command never reaches model load. Needs an accepted-and-inert seam with a per-flag reason; no row owns serve CLI recipe compatibility | feature | -| [#607](https://github.com/mudler/vllm.cpp/issues/607) | — | No `--language-model-only`: 43 recipes skip the vision encoder to hand its VRAM to the KV cache and we load the tower unconditionally. The flag appears in this repo only in `tools/bench/run_serve_low.py`, which passes it to the ORACLE — a grep reads as coverage and is not | feature | +| [#607](https://github.com/mudler/vllm.cpp/issues/607) | `ENG-MM-INPUT-PIPELINE` | **Premise corrected 2026-08-13, see `specs/multimodal-track.md` §1.5.** Not "skip the vision encoder": `--language-model-only` sets every modality limit to **0** (`multimodal.py:78,321-327`) and is sugar over `--limit-mm-per-prompt`. Two consequences follow, and we have NEITHER — upstream then **refuses every multimodal request** (`processing/context.py:409-428` raises "At most 0 image(s) may be provided in one prompt", from `:461` and `chat_utils.py:662`), and builds the tower uninitialised (`interfaces.py:293`). We have no `MultimodalConfig`, no `limit_per_prompt` and no enforcement at all, so this is a PORT of the limits mechanism (L1-L4), not the exposure of a boolean. 43 of 157 recipes pass the flag and we abort on it. The flag appears in this repo only in `tools/bench/run_serve_low.py`, which passes it to the ORACLE — a grep reads as coverage and is not | feature | | [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | **W0 (record backfill) landed 2026-08-13; row `INVENTORIED` → `PARTIAL`, 41 shipped names / 37 families recorded.** Re-derived from the two REGISTRIES rather than from recipe usage: **five** `--tool-call-parser` names are upstream-only at the pin — `openai`, `inkling`, `minimax_m3` (W1, recipe demand) and `cohere_command3`, `cohere_command4` (W2, ZERO recipe uses, so usage-driven audits miss them). **Only `inkling` is PORTABLE from vLLM source**: `minimax_m3` is backed by the Rust crate, `openai` is a declared Harmony stub that raises on both methods, and both Cohere names are shims over the out-of-tree `cohere_melody` package, so W1/W2 each owe a recorded decision before code rather than a text port. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry and arrive with the pin advance, not here. W3 ports upstream's shared `ToolParserTestConfig` harness. The earlier "six missing" framing was usage-derived: it listed `nemotron_json` as portable (it is not registered at the pin) and missed both Cohere entries | feature | | [#647](https://github.com/mudler/vllm.cpp/issues/647) | — | Oracle policy had no fallback and no pin concept: five upstreams beyond vLLM are already compared against (vLLM-Omni, SGLang, llama.cpp, `transformers`, tt-forge) with their pins scattered across individual specs or absent entirely. AGENTS.md now admits a named secondary oracle where vLLM implements nothing, `.agents/oracles/.md` pins each one file-per-oracle, and `check-oracle-pins.py` enforces both directions. The gateability debts for `sglang`, `diffusers` and `tt-forge` stay open on this issue | feature | | [#649](https://github.com/mudler/vllm.cpp/issues/649) | `TOOLS-CALLING-CORE` | That row's prose still records `tool_parser_names()` 40 / `reasoning_parser_names()` 7; both enumerations have grown since 2026-07-24 and are now **41** (`tool_parsers/abstract.cpp:269`) and **12** (`reasoning_parsers/abstract.cpp:72`). Code and tests are correct — `test_detect.cpp:221` already pins 41 — only the record drifted. Halves belong to two other rows (#608, #605), so it is filed rather than repaired inside #643 (found while implementing #643's review findings) | bug | @@ -598,7 +598,7 @@ degraded run — it is no run at all. | `--tool-call-parser` | 41 names, **82/90 recipe uses (91%)** — the healthy axis | [#608](https://github.com/mudler/vllm.cpp/issues/608) for the last 8 | | `--reasoning-parser` | 10 of 28 names, **15/76 uses (20%)**; `qwen3` (18) rejected on our own gate models | [#605](https://github.com/mudler/vllm.cpp/issues/605) | | `--enable-auto-tool-choice`, `--trust-remote-code` | no-ops for us, yet **abort startup** on 89 and 82 recipes | [#606](https://github.com/mudler/vllm.cpp/issues/606) | -| `--language-model-only` | absent; 43 recipes use it to free encoder VRAM | [#607](https://github.com/mudler/vllm.cpp/issues/607) | +| `--language-model-only` | absent; 43 recipes use it. It zeroes every modality limit — the freed encoder VRAM **and** the refusal of every mm request both follow from that | [#607](https://github.com/mudler/vllm.cpp/issues/607) | | `--kv-cache-dtype` | not a serve flag; residual on the `KV-FP8` row | — | | `--speculative-config` | MTP + DFlash land; `eagle`/`eagle3` (7 uses) do not | — | | TP / EP / multi-node (`--tensor-parallel-size`, `--enable-expert-parallel`, `--mm-encoder-tp-mode`) | absent by scope, not by defect — single-box engine | see the TP W-plan above | diff --git a/.agents/specs/multimodal-track.md b/.agents/specs/multimodal-track.md index 2c58abd65..262f976ea 100644 --- a/.agents/specs/multimodal-track.md +++ b/.agents/specs/multimodal-track.md @@ -205,6 +205,101 @@ unchanged. --- +### 1.5 Multimodal CONFIG and input limits — the seam this map missed (#607, 2026-08-13) + +The seam map above covers the input *pipeline* and the tower, but not the +**config that decides whether either runs**. That gap surfaced from the +`recipes.vllm.ai` sweep: `--language-model-only` is used by **43 of 157** official +recipes, and we reject it. + +| vLLM seam | file:line @ `555967922` | What we build | +|---|---|---| +| `MultiModalConfig.limit_per_prompt` | `vllm/config/multimodal.py:81` | NEW per-modality input-count limits on the model config | +| `get_limit_per_prompt(modality)` | `vllm/config/multimodal.py:321-336` (returns **0** when `language_model_only`, else the map, else the 999 default) | NEW accessor; the single place every consumer asks | +| `language_model_only` | `vllm/config/multimodal.py:78` | NEW flag — **sugar**, see below | +| `--limit-mm-per-prompt` | `vllm/engine/arg_utils.py:556,1279,1692` | serve flag — **the primary one**; the limits are the mechanism | +| `--language-model-only` | `vllm/engine/arg_utils.py:555,1276,1691` | serve flag over the boolean | +| **`validate_num_items` — what makes a limit a limit** | `vllm/multimodal/processing/context.py:409-428` — raises `VLLMValidationError("At most {limit} {modality}(s) may be provided in one prompt.")`, appending `" Set --limit-mm-per-prompt to increase this limit."` when the MODEL supports more (`:425-426`) | NEW — the **enforcement** point; without it a limit is a number nothing reads | +| its two call sites | `context.py:461` inside `parse_mm_data` (`:430`), and `vllm/entrypoints/chat_utils.py:662` per tracked item, whose only escape is `enable_mm_embeds` + a `*_embeds` modality at limit 0 (`chat_utils.py:653-660`) | NEW — mirror both, including that escape | +| `allowed_mm_limits` | `vllm/multimodal/processing/context.py:392-405` — folds the USER limit with the model's own `supported_mm_limits` by `min()` | NEW — a user limit never raises a model's ceiling | +| further limit consumers | `vllm/multimodal/registry.py:126` (every supported modality at limit 0 ⇒ mm processing disabled, unless `enable_mm_embeds`); `vllm/v1/worker/encoder_cudagraph.py:139` (`video` limit 0 ⇒ `max_frames_per_batch = 0`) | NEW — the limits reach past the tower | +| **tower skip when all limits are 0** | `vllm/model_executor/models/interfaces.py:293` — builds the tower inside `no_init_weights(..., StageMissingLayer)` when `all(get_limit_per_prompt(m) == 0)` | the memory win; a CONSEQUENCE of zero limits, reachable by either flag | +| kernel gate | `vllm/model_executor/models/qwen3_next.py:325` — `text_only` feeds `use_fused_qk_norm_rope_gate` | our fused path is currently unconditional; see below | +| LoRA interaction | `vllm/lora/model_manager.py:233` | deferred with `LORA-RUNTIME` | + +**The correction that matters.** `--language-model-only` is **not** a +"skip the encoder" boolean. Its own docstring is explicit: *"disables all +multimodal inputs by setting all modality limits to 0. Equivalent to setting +`--limit-mm-per-prompt` to 0 for every modality."* The encoder skip falls out of +`interfaces.py:293` because the limits are zero — **any** route to zero limits +gets it. So porting the boolean alone would be a bespoke path that does not exist +upstream, which the mirror rule forbids: port `limit_per_prompt` + +`get_limit_per_prompt` first, and the flag becomes three lines on top. + +**The consequence that matters more: upstream `--language-model-only` REFUSES +every multimodal request.** This was missing from the first draft of this +section, which mapped the memory consequence and the kernel gate but not the +enforcement. Follow the chain: `get_limit_per_prompt` returns **0** for every +modality (`multimodal.py:321-327`), so `validate_num_items` computes `limit = 0` +and raises `VLLMValidationError` for any request carrying one or more items +(`context.py:409-428`), on both the `parse_mm_data` path (`:461`) and the OpenAI +chat path (`chat_utils.py:662`). The user-visible behaviour of the flag is not +"the same server, minus some VRAM" — it is a server that answers an image +request with *"At most 0 image(s) may be provided in one prompt."* + +That is what makes the limits the mechanism and the flag the sugar, and it is +the half a port can most easily leave out, because omitting it breaks nothing +that a text-only workload would notice. + +**Our baseline is nothing.** `grep -rn 'limit_per_prompt\|MultimodalConfig' src/ +include/` returns no hits; no multimodal config surface exists, and nothing gates +tower construction on config. This is a port, not an exposure. + +**A second-order consequence worth naming.** `qwen3_next.py:325` proves the flag +is not purely a memory knob — upstream uses it to select the *fused* QK-norm+RoPE+ +gate path. Ours takes the fused route unconditionally (`qwen3_5.cpp`, "true for +BOTH the 27B and the 35B"). That asymmetry is the serving-side twin of +[#414](https://github.com/mudler/vllm.cpp/issues/414), which found our published +ratios flattered because the oracle ran unfused while we ran fused. Any gate +comparing the two arms must set the flag on both sides or state that it did not. + +**Waves** (additive to §3; none blocks M1's pipeline work): + +- **L1** — `limit_per_prompt` + `get_limit_per_prompt` on the model config, with + upstream's precedence exactly: `language_model_only` ⇒ 0, else the explicit map, + else 999 — **and the refusal that gives those numbers effect**: mirror + `allowed_mm_limits` (`min()` against the model's supported limits) and + `validate_num_items`, message text included, at both call sites, with + upstream's `enable_mm_embeds` escape. Unit-gated, no serve surface yet. The + refusal belongs here, not in L3: it is the limits' own semantics, and a limit + nothing enforces is not a limit. +- **L2** — `--limit-mm-per-prompt` and `--language-model-only` serve flags plus + the C-ABI field, over L1. This is the point at which the 43 recipes stop + aborting. +- **L3** — the tower skip: construct-without-initialising when every limit is 0, + gated on **measured** RSS reduction against a multimodal checkpoint, plus + token-exactness of the text path with and without the flag. +- **L4** — mirror the kernel gate, or record an explicit tracked exception with + the #414 cross-reference. + +**L2 is shippable without L3 only because L1 carries the refusal.** The first +draft of this section said "L2 without L3 is honest and shippable: the flag +would be accepted and would correctly zero the limits". That was too generous, +and the caveat it carried guarded the wrong thing — it guarded "frees VRAM" +while leaving a much larger divergence unguarded. Zeroing a limit that nothing +enforces is not correct behaviour: we would ACCEPT an image request that +upstream REFUSES, which is the flag's main observable effect, and the flag would +be inert on exactly the axis a user would test first. + +So the honest statement is conditional. **With L1's refusal, L2 ships honestly** +and only the memory win is owed; it must still NOT be described as "frees VRAM" +until L3 lands and is measured. **Without the refusal, L2 is not shippable at +all** — it is a flag that is accepted and inert, which is worse than the abort +it replaces, because an abort is visible and a silently-served image request is +not. + +--- + ## 2. Structured contract ### Scope From 11cc1d5896b480a1b652db9249319242053aca93 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:16:06 +0200 Subject: [PATCH 04/55] fix(main-red): route LTX-2.5's device question through the platform seam (#553, #603, #651, #652) main is RED on the DSR ratchet and has been since the LTX-2.5 landing: two kCUDA sites at ltx2_video.cpp:531-551 put kcuda at 2 against a baseline of 0, so every branch cut from main since inherits the red and each session re-derives that it is pre-existing. PROVENANCE, stated rather than claimed: this repair was written by a third session and left unpushed - git ls-remote showed zero refs, so it was invisible to gh pr list and to every ownership question any of us could ask. A peer found it, verified it independently, and ceded it. I did not write it. It is adopted here through the protocol to unblock main, with a fresh review by someone who is neither its author nor its finder. THE LOAD-BEARING CHECK. kcuda goes 2 -> 0 while check-device-leakage.py (md5 9a4617a690e383d5a5c628ac3ec48111) and device-leakage-baseline.json (md5 45ec32f4e08ab1c5ccf1f6ef236895b0) are BYTE-IDENTICAL to origin/main, and git grep ltx2 over the checker is empty. So this is a repair, not a raised threshold - the checker's own message forbids the latter in terms, and most "green now" claims do not separate the two. vt_ifdef stays 32; total 34 -> 32. It takes the platform-seam route the checker's message prefers (CurrentPlatform() / device_type()), not the ALLOWLIST escape. Only vt::Device{kCUDA, index} becomes vt::Device{accelerator, index}; the CreateQueue/multi-GPU labelling comment at :539-556 is untouched. MUTATIONS, four, each anchor asserted count == 1 and the tree restored by md5: * revert the seam -> RED, kcuda=3 * --write-baseline after it -> REFUSES: "REFUSING to write a HIGHER baseline" * hand-edit the baseline JSON to 35/3 -> checker PASSES. The ratchet's only defence against a raised baseline is a human noticing a 2-line diff, which is why md5 identity and not the exit code is the real check. Filed. * drop the accelerator == kCPU clause, keeping only TryGetBackend(...) == nullptr -> test_ltx2_video.cpp:386 FATAL, 502 -> 501 assertions. TryGetBackend(kCPU) is non-null, so without that clause a CPU-only build SILENTLY ACCEPTS device = 1. The guard is real and is now measured. Gate: ctest -N registers 423, the serial run examined 423/423, 0 failed, BUILD_EXIT=0, zero warnings. Registration is byte-identical to main, so the denominator cannot have drifted. agent-preflight.sh on committed HEAD: all gates green. test_cpu_x86_llamacpp_floor fails under load on unmodified main too and passes at low load; environmental, reproduced on both sides. Also corrects a defect in the original commit message: the M3a precedent is src/vllm/entrypoints/model_loader.cpp:75-104, not model_executor/ - which is ambiguous rather than merely unqualified, since a model_executor/model_loader/ directory also exists. DELIBERATELY EXCLUDED: the author's live tip 8f26ee99c additionally carries an unaligned-load change to ltx2_loader.cpp (#627) that arrived mid-review and was never gated here. That file has four live rows on it right now, and its comment claims fifteen casts across nine loaders while LoadUnaligned appears in four files and six call sites. It wants its own scoped review rather than riding a red-main repair. Owed and filed, neither blocking: #659, the repair adopts M3a's seam but not its companion supports_model_architecture guard, so a partial backend that could decline by name now fails later in a kernel bind; #660, the kcuda bucket is the token grep \bkCUDA\b, so minimax_h3_video.cpp:221-226 spelling it static_cast(1) counts as zero and launders the same defect past the same gate. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-opus-5 [ClaudeCode] --- .agents/model-matrix.md | 33 +++++++--- .agents/roadmap_v1.md | 6 ++ docs/FEATURES.md | 2 +- src/vllm/multimodal/ltx2_video.cpp | 63 +++++++++++++------ tests/scripts/test_agent_record.py | 13 ++-- tests/support/test_env.h | 75 +++++++++++++++++++++++ tests/vllm/multimodal/test_ltx2_video.cpp | 38 ++++++++---- tests/vt/test_backend_cross_device.cpp | 7 ++- 8 files changed, 183 insertions(+), 54 deletions(-) create mode 100644 tests/support/test_env.h diff --git a/.agents/model-matrix.md b/.agents/model-matrix.md index e63964f10..496eb398c 100644 --- a/.agents/model-matrix.md +++ b/.agents/model-matrix.md @@ -19,20 +19,33 @@ a practical unit that one agent can spike without silently dropping aliases. ## Architecture-support checklist At-a-glance view of which architectures we have actually engaged, and how far. -**326 architecture rows are inventoried at the pin, plus 10 rows that the pinned -registry does not contain = 336 total.** Those 10 are, by why they are not at the -pin: `KimiK3ForConditionalGeneration` and `MuseGlimmerForConditionalGeneration`, -both released after the pin; `BailingMoeV3ForCausalLM`, a pin-lag row registered -on vLLM `main` and absent only at the pin (#609); `MiniMaxH3DiTModel`, a DIFFUSION architecture living in the -separate `vllm-omni` repository; the four TTS / audio-generation architectures +**355 architecture rows are inventoried at the pin, plus 11 rows that the pinned +registry does not contain = 366 architecture rows.** Those 11 are, by why they +are not at the pin: `KimiK3ForConditionalGeneration` and +`MuseGlimmerForConditionalGeneration`, both released after the pin; +`BailingMoeV3ForCausalLM`, a pin-lag row registered +on vLLM `main` and absent only at the pin (#609); `MiniMaxH3DiTModel` and +`LTX2VideoTransformer3DModel`, both DIFFUSION architectures released after the +pin and absent from the vLLM repository entirely, reached through the +separate `vllm-omni` repository (#435); the four TTS / audio-generation architectures registered in that same `vllm-omni` repository — `MossTTSDelayModel`, `MossTTSRealtime`, `Qwen3TTSForConditionalGeneration` and `HiggsMultimodalQwen3ForConditionalGeneration`; and two whose upstream target is still pending because the exact `config.json` architecture string is registered in neither core vLLM `main` nor `vllm-omni` — `VoxtralRealtimeForConditionalGeneration` and -`BailingMMNativeForConditionalGeneration` (#610). Of the 336, 45 are -past `INVENTORIED` (engaged), the remaining 291 are known-but-not-started long tail. Every mark +`BailingMMNativeForConditionalGeneration` (#610). + +The rollup below counts **370 rows**, which is those 366 plus the four that are +not architectures at all and say so in their own sections: +`MODEL-FACTORY-registry` (the cross-cutting registry contract), the two +`MODEL-AUDIO` encoder-component rows, and the one `MODEL-HFDYNAMIC` row (dynamic +Transformers compatibility is capability-driven and excluded from finite counts). +Of those 370, 49 are past `INVENTORIED` (engaged) and the remaining 321 are the +known-but-not-started long tail — the same two numbers the rollup table states, +and the way to re-derive every count here is to re-run +[`scripts/check-agent-record.py`](../scripts/check-agent-record.py)'s parser over +this file rather than to carry any of them forward. Every mark below is grounded in the row's lifecycle `State` cell plus its ledger evidence, and this section is CI-enforced against those rows by [`scripts/check-model-checklist.py`](../scripts/check-model-checklist.py): a mark @@ -62,7 +75,7 @@ Rollup by lifecycle state (must equal the detailed per-state row counts): | GATING | 1 | | **Total** | **370** | -Engaged architectures (the 48 non-`INVENTORIED` rows): +Engaged architectures (the 49 non-`INVENTORIED` rows): | Support | Architecture | Family / example | Status | Row | |---|---|---|---|---| @@ -493,7 +506,7 @@ Transformers compatibility is capability-driven and excluded from finite counts. Rows here are audio TOWERS that vLLM ships as a component of some registry architecture rather than as one themselves, so they have no `registry.py` entry -of their own and are NOT part of the 328 architecture rows counted above. +of their own and are NOT part of the 366 architecture rows counted above. | ID | Item / architecture aliases | Upstream | Task / modality | Dependencies | Spike/spec | State | Our code + tests/evidence | Owner | |---|---|---|---|---|---|---|---|---| diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 759da0349..c335bd797 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -179,9 +179,15 @@ issue is not yet placed. Keyed record: update in place, never append. | [#505](https://github.com/mudler/vllm.cpp/issues/505) | `MODEL-TEXT-deepseek-v4-deepseek-v4-for-causal-lm` | `DsaTopkKernel` sizes `chosen[512]`/`picked[64]` by literal while `index_topk` is 512 (Flash) / 1024 (Pro); latent behind `dsa_dense` today, silent stack overflow once the real-geometry DSA residual lands (found while assessing #504) | bug | | [#552](https://github.com/mudler/vllm.cpp/issues/552) | `MODEL-TEXT-deepseek-v4-deepseek-v4-for-causal-lm` | DSA top-k review findings: the `w < topk` guard comment overclaims what it defends, the window clamps and non-positive `topk` are ungated, and `DsaTopkLaunch` swallows its launch error (spec `specs/dsa-topk-bounds.md` §7) | bug | | [#469](https://github.com/mudler/vllm.cpp/issues/469) | — | `test_ops_glue.cpp:190`'s `CHECK_THROWS` is satisfied by the CPU kernel's second guard, not the dispatch guard it names — mutation M8 survives. Behavior is correct; test strength only | bug | +| [#553](https://github.com/mudler/vllm.cpp/issues/553) | — | `check-device-leakage` is RED on `main`: `ltx2_video.cpp` names `DeviceType::kCUDA` twice in the device-agnostic shared layer, where the `kcuda` baseline is 0 | bug | | [#558](https://github.com/mudler/vllm.cpp/issues/558) | — | `tests/parity/hf_snapshot.h` has no guard against declaration-order breaks: the C++ build catches them, but the records-only lane that broke it never builds C++, and all 14 TUs that include the header are checkpoint-gated so `ctest` reports the break as `***Not Run`. `fafa16f0f` (#546, #551) fixed the ordering and carried no guard | bug | +| [#603](https://github.com/mudler/vllm.cpp/issues/603) | — | `windows-msvc-cpu` / `windows-msvc-vulkan` are RED on `main`: `test_backend_cross_device.cpp` calls POSIX `setenv`/`unsetenv`, which MSVC does not provide | bug | | [#606](https://github.com/mudler/vllm.cpp/issues/606) | — | `vllm-serve` aborts on `--enable-auto-tool-choice` (89/157 recipes) and `--trust-remote-code` (82/157), both of which are no-ops for us, so a copy-pasted official recipe command never reaches model load. Needs an accepted-and-inert seam with a per-flag reason; no row owns serve CLI recipe compatibility | feature | | [#607](https://github.com/mudler/vllm.cpp/issues/607) | `ENG-MM-INPUT-PIPELINE` | **Premise corrected 2026-08-13, see `specs/multimodal-track.md` §1.5.** Not "skip the vision encoder": `--language-model-only` sets every modality limit to **0** (`multimodal.py:78,321-327`) and is sugar over `--limit-mm-per-prompt`. Two consequences follow, and we have NEITHER — upstream then **refuses every multimodal request** (`processing/context.py:409-428` raises "At most 0 image(s) may be provided in one prompt", from `:461` and `chat_utils.py:662`), and builds the tower uninitialised (`interfaces.py:293`). We have no `MultimodalConfig`, no `limit_per_prompt` and no enforcement at all, so this is a PORT of the limits mechanism (L1-L4), not the exposure of a boolean. 43 of 157 recipes pass the flag and we abort on it. The flag appears in this repo only in `tools/bench/run_serve_low.py`, which passes it to the ORACLE — a grep reads as coverage and is not | feature | +| [#651](https://github.com/mudler/vllm.cpp/issues/651) | — | `test_agent_record`'s MODEL-ratchet docstring is two contradictory paragraphs spliced together, and the surviving half records a pin transition that never happened | bug | +| [#652](https://github.com/mudler/vllm.cpp/issues/652) | — | `model-matrix.md` prose counters drifted: LTX-2.5 reached the rows and the CI-enforced rollup but none of the five sentences that count them | bug | +| [#659](https://github.com/mudler/vllm.cpp/issues/659) | — | LTX-2.5 device select adopts M3a's platform seam but not its companion capability guard: `ltx2_video.cpp` asks `CurrentPlatform().device_type()` and `TryGetBackend(...)` but never `supports_model_architecture`, so a PARTIAL backend (Metal 15/75 ops, Tenstorrent) is handed a queue and dies in a kernel bind where it used to be refused BY NAME (found while reviewing #553 for landing) | bug | +| [#660](https://github.com/mudler/vllm.cpp/issues/660) | — | `check-device-leakage`'s `kcuda` bucket is the token grep `\bkCUDA\b`, so `minimax_h3_video.cpp:221-226`'s `static_cast(device)` hardcodes CUDA as enum value 1 and counts as 0. Gate strength plus an enum-ordering hazard; the H3 video lane should ask the same seam `ltx2_video.cpp` now does (found while reviewing #553) | bug | | [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | **W0 (record backfill) landed 2026-08-13; row `INVENTORIED` → `PARTIAL`, 41 shipped names / 37 families recorded.** Re-derived from the two REGISTRIES rather than from recipe usage: **five** `--tool-call-parser` names are upstream-only at the pin — `openai`, `inkling`, `minimax_m3` (W1, recipe demand) and `cohere_command3`, `cohere_command4` (W2, ZERO recipe uses, so usage-driven audits miss them). **Only `inkling` is PORTABLE from vLLM source**: `minimax_m3` is backed by the Rust crate, `openai` is a declared Harmony stub that raises on both methods, and both Cohere names are shims over the out-of-tree `cohere_melody` package, so W1/W2 each owe a recorded decision before code rather than a text port. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry and arrive with the pin advance, not here. W3 ports upstream's shared `ToolParserTestConfig` harness. The earlier "six missing" framing was usage-derived: it listed `nemotron_json` as portable (it is not registered at the pin) and missed both Cohere entries | feature | | [#647](https://github.com/mudler/vllm.cpp/issues/647) | — | Oracle policy had no fallback and no pin concept: five upstreams beyond vLLM are already compared against (vLLM-Omni, SGLang, llama.cpp, `transformers`, tt-forge) with their pins scattered across individual specs or absent entirely. AGENTS.md now admits a named secondary oracle where vLLM implements nothing, `.agents/oracles/.md` pins each one file-per-oracle, and `check-oracle-pins.py` enforces both directions. The gateability debts for `sglang`, `diffusers` and `tt-forge` stay open on this issue | feature | | [#649](https://github.com/mudler/vllm.cpp/issues/649) | `TOOLS-CALLING-CORE` | That row's prose still records `tool_parser_names()` 40 / `reasoning_parser_names()` 7; both enumerations have grown since 2026-07-24 and are now **41** (`tool_parsers/abstract.cpp:269`) and **12** (`reasoning_parsers/abstract.cpp:72`). Code and tests are correct — `test_detect.cpp:221` already pins 41 — only the record drifted. Halves belong to two other rows (#608, #605), so it is filed rather than repaired inside #643 (found while implementing #643's review findings) | bug | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 3c7ba3e68..7326e7338 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -192,7 +192,7 @@ on the committed fixture); reranking/classify models are not yet registered. | Image | ✅ correctness-gated | ✅ | ✅ | ◐ | | Video | ✅ correctness-gated | ✅ | ✅ | ☐ | | Audio | ✅ correctness-gated | ✅ | ◐ | ◐ | -| Video+audio GENERATION (MiniMax-H3 DiT, vLLM-Omni lane) | ◐ all three modalities COHERENT on Q4_K_M (t2va, fl2va, ref2va; §8.20); the NVFP4 arm carries the patch grid; GGUF/NVFP4/bf16 loaders, unpruned AND pruned (§8.21); ABI v12 `vllm_video_*` | ✅ (vllm-omni, BF16-only, no quantized H3 arm) | ☐ | ☐ | +| Video+audio GENERATION (MiniMax-H3 DiT, LTX-2.5 DiT) | ◐ H3: all three modalities COHERENT on Q4_K_M (t2va, fl2va, ref2va; §8.20); the NVFP4 arm carries the patch grid; GGUF/NVFP4/bf16 loaders, pruned too (§8.21). LTX-2.5: a second lane, `SPIKE`, gated at reduced dims | ✅ H3 (vllm-omni, BF16-only, no quantized arm); LTX-2.5 only through the generic diffusers adapter, no native recipe ([vllm-omni#6066](https://github.com/vllm-project/vllm-omni/issues/6066)) | ☐ | ☐ | | Speech / audio GENERATION (TTS, vLLM-Omni lane) | ☐ not started. The six architectures reached by the nine official `vllm-project/recipes` TTS models are inventoried in `.agents/model-matrix.md` (#610); an inventoried row is not a supported one | ✅ (vllm-omni: MOSS-TTS, Qwen3-TTS, Higgs Audio v3, Voxtral TTS) | not assessed | not assessed | | Multimodal over the OpenAI server | ◐ image request path wired, forward pending | ✅ | ✅ | ◐ | diff --git a/src/vllm/multimodal/ltx2_video.cpp b/src/vllm/multimodal/ltx2_video.cpp index a50f184c0..c0eaf389f 100644 --- a/src/vllm/multimodal/ltx2_video.cpp +++ b/src/vllm/multimodal/ltx2_video.cpp @@ -34,6 +34,7 @@ #include "vllm/model_executor/models/ltx2_upsampler.h" #include "vllm/model_executor/models/ltx2_video_vae.h" #include "vllm/model_executor/models/minimax_h3.h" +#include "vllm/platforms/interface.h" // CurrentPlatform() — which accelerator, if any #include "vllm/tokenizer/tokenizer.h" namespace vllm::multimodal { @@ -418,9 +419,10 @@ bool DetectLtx2Video(const VideoModelParams& params) { struct Ltx2VideoEngine::Impl { VideoModelParams params; - // CPU, or the CUDA device `params.device - 1` names. Phase L8 made the second - // real: the DiT is staged with `Ltx2StreamDitToDevice` and driven by - // `Ltx2DitForwardDevice`, so a CUDA handle now denotes a CUDA forward. + // CPU, or the accelerator index `params.device - 1` names on whichever device + // type the platform seam resolves. Phase L8 made the second real: the DiT is + // staged with `Ltx2StreamDitToDevice` and driven by `Ltx2DitForwardDevice`, so + // a non-zero handle now denotes a device-resident forward. vt::Device device; // The stream dtype the DiT was staged at, and the one the forward computes in. // bf16 on an accelerator — upstream resolves ONE model dtype and every layer @@ -517,24 +519,46 @@ std::unique_ptr Ltx2VideoEngine::Load(const VideoModelParams& p // ── where this engine runs (phase L8) ───────────────────────────────────── // - // `device` is 0 for the CPU and 1 + for an accelerator, which is - // the mapping the seam already documents. Phase L7 REFUSED anything but 0, - // because the f32-only forward and the bf16-only staging did not meet; phase - // L8 is the forward that closes that, so the refusal is gone and the handle - // now means what it says. + // `device` is 0 for the CPU and 1 + for an accelerator, + // which is the mapping the seam already documents. Phase L7 REFUSED anything + // but 0, because the f32-only forward and the bf16-only staging did not meet; + // phase L8 is the forward that closes that, so the refusal is gone and the + // handle now means what it says. // - // What is NOT gone is the refusal to fake it: if the CUDA backend is not - // registered in this build, the load is refused BY NAME rather than served the - // CPU forward behind a CUDA-looking handle. That substitution is exactly what - // would make every later timing and every "it ran on the GPU" claim false. + // WHICH accelerator is the PLATFORM's question, not this model file's. This + // asked `TryGetBackend(kCUDA)` — the same defect work row M3a repaired in + // `SelectQueueForModel` (src/vllm/entrypoints/model_loader.cpp:75-104 — the + // full path matters, there is also a src/vllm/model_executor/model_loader/ + // DIRECTORY and the bare file name sends a reader there), where a hardcoded + // `GetBackend(kCUDA)` was the one line standing between a complete non-NVIDIA + // backend and running a model. `CurrentPlatform()` walks the probe order + // {kCUDA, kROCM, kXPU, kVULKAN, kMETAL, kTENSTORRENT, kCPU} + // (src/vllm/platforms/platform.cpp:62-64) and returns the first one REGISTERED + // (:91-98) — and a platform registers only where its own probe found a device, + // e.g. src/vllm/platforms/cuda.cpp:136-138 returns early on a box with the CUDA + // toolkit and no usable GPU — so on a CUDA box this resolves EXACTLY the device + // the hardcoded lookup did. + // Nothing below this line names a device either: `Ltx2StreamDitToDevice` and + // `Ltx2DitForwardDevice` drive `vt::Queue` and the op table, so a backend that + // registers those ops reaches this forward with no edit here. + // + // What is NOT gone is the refusal to fake it: if this build registers no + // accelerator backend, the load is refused BY NAME rather than served the CPU + // forward behind an accelerator-looking handle. That substitution is exactly + // what would make every later timing and every "it ran on the GPU" claim false. im.on_device = params.device != 0; if (im.on_device) { - vt::Backend* backend = vt::TryGetBackend(vt::DeviceType::kCUDA); - if (backend == nullptr) { + const vt::DeviceType accelerator = + vllm::platforms::CurrentPlatform().device_type(); + if (accelerator == vt::DeviceType::kCPU || + vt::TryGetBackend(accelerator) == nullptr) { Fail("device " + std::to_string(params.device) + - " asks for CUDA, but no CUDA backend is registered in this build. The LTX-2.5 " - "device-resident forward is present (Ltx2DitForwardDevice); what is missing is " - "the backend. Refusing rather than running the CPU forward behind a CUDA handle."); + " asks for an accelerator, but no accelerator backend is registered in this " + "build (the platform seam resolves to '" + + std::string(vt::DeviceTypeName(accelerator)) + + "'). The LTX-2.5 device-resident forward is present (Ltx2DitForwardDevice); " + "what is missing is the backend. Refusing rather than running the CPU forward " + "behind an accelerator handle."); } // `vt::CreateQueue(Device)`, NOT `Backend::CreateQueue()`. backend.h:212-217 // records the method as a "temporary index-0 migration shim" and says new @@ -546,9 +570,10 @@ std::unique_ptr Ltx2VideoEngine::Load(const VideoModelParams& p // has one GPU so it cannot be reached there, which is exactly why it has to // be right before a second device exists. const int32_t index = static_cast(params.device - 1); - im.device = vt::Device{vt::DeviceType::kCUDA, index}; + im.device = vt::Device{accelerator, index}; if (vt::TryGetBackend(im.device) == nullptr) { - Fail("device " + std::to_string(params.device) + " names CUDA device index " + + Fail("device " + std::to_string(params.device) + " names " + + std::string(vt::DeviceTypeName(accelerator)) + " device index " + std::to_string(index) + ", and no backend is registered for it. Refusing rather than creating a queue " "on device 0 and labelling it with an index nothing runs on."); diff --git a/tests/scripts/test_agent_record.py b/tests/scripts/test_agent_record.py index 30ebe54b3..2c2c86830 100644 --- a/tests/scripts/test_agent_record.py +++ b/tests/scripts/test_agent_record.py @@ -274,14 +274,11 @@ def test_model_row_ratchet_is_load_bearing(self) -> None: Mirrors the ENGINE ratchet above, for the same reason and with more force: the MODEL count is the one that actually moves, because every new - architecture re-pins it by hand. Muse Glimmer took it 361 -> 362; the - seven recipe architectures that had no row at all took it 362 -> 369 - (#609, #610). Without this, bumping the number to silence a failure is - indistinguishable from bumping it because a row really landed. - architecture re-pins it by hand. Muse Glimmer took it 361 -> 362, and - LTX-2.5 took it 362 -> 363. Without - this, bumping the number to silence a failure is indistinguishable from - bumping it because a row really landed. + architecture re-pins it by hand. Muse Glimmer took it 361 -> 362 + (`c8fc24a50`); the seven recipe architectures that had no row at all took + it 362 -> 369 (#609, #610, `eba6ab7c7`); LTX-2.5 took it 369 -> 370 + (#435, `cefacd2d0`). Without this, bumping the number to silence a + failure is indistinguishable from bumping it because a row really landed. """ clean: list[str] = [] agent_record.check_matrices(clean) diff --git a/tests/support/test_env.h b/tests/support/test_env.h new file mode 100644 index 000000000..efb9e3804 --- /dev/null +++ b/tests/support/test_env.h @@ -0,0 +1,75 @@ +// vllm.cpp original (test harness); no upstream mirror. +// +// ONE portable environment-variable setter for the whole test tree. +// +// WHY IT EXISTS (issue #603). `setenv`/`unsetenv` are POSIX. MSVC provides +// neither, so a test that flips a `VT_*` knob mid-process does not fail on +// Windows — it does not COMPILE, and it takes the whole `windows-msvc-*` lane +// down with it: +// +// tests\vt\test_backend_cross_device.cpp(1004,5): error C3861: 'setenv': +// identifier not found +// +// Three files had already grown their own private `_putenv_s` branch before this +// header existed — `tests/vllm/test_gguf.cpp` (`SetEnvironment`), +// `tests/vllm/v1/kv_offload/lmcache/test_lmcache_client.cpp` and +// `tests/parity/test_qwen27_dense_lmhead_fp4.cpp` — which is exactly why #603 +// asks for the shim to land ONCE rather than per site. New env-flipping tests +// include this header; the three private copies predate it and are left alone +// here so this repair stays reviewable, not because they are correct to keep. +// +// WHAT IS DELIBERATELY NOT DONE: `#ifdef`-ing the offending case out on MSVC. +// That turns a compile error into silently missing coverage on the one platform +// nobody builds locally, which is strictly worse than the error. +// +// SEMANTICS, and the one point the two platforms cannot be made to agree on: +// `_putenv_s(name, "")` DELETES the variable on Windows, while +// `setenv(name, "", 1)` defines it as the empty string on POSIX. `SetEnv` maps an +// empty value to a DELETE on both, so callers see one behaviour everywhere. A +// test that genuinely needs a defined-but-empty variable cannot use this helper +// and has to say so at its own call site. +#pragma once + +#include +#include +#include + +namespace vllm_test { + +// Removes `name` from the environment. Throws rather than returning a status: a +// test whose environment did not take effect is not running the arm it claims. +inline void UnsetEnv(const char* name) { +#if defined(_WIN32) + const int result = ::_putenv_s(name, ""); +#else + const int result = ::unsetenv(name); +#endif + if (result != 0) { + throw std::runtime_error(std::string("failed to unset test environment variable ") + + name); + } +} + +// Defines `name` as `value`, overwriting any previous definition. An EMPTY (or +// null) `value` removes it — see the header comment. Throws on failure. +inline void SetEnv(const char* name, const char* value) { + if (value == nullptr || value[0] == '\0') { + UnsetEnv(name); + return; + } +#if defined(_WIN32) + const int result = ::_putenv_s(name, value); +#else + const int result = ::setenv(name, value, /*overwrite=*/1); +#endif + if (result != 0) { + throw std::runtime_error(std::string("failed to set test environment variable ") + + name); + } +} + +inline void SetEnv(const char* name, const std::string& value) { + SetEnv(name, value.c_str()); +} + +} // namespace vllm_test diff --git a/tests/vllm/multimodal/test_ltx2_video.cpp b/tests/vllm/multimodal/test_ltx2_video.cpp index 85b47025a..76c8f8e31 100644 --- a/tests/vllm/multimodal/test_ltx2_video.cpp +++ b/tests/vllm/multimodal/test_ltx2_video.cpp @@ -40,6 +40,7 @@ #include "vllm/model_executor/models/ltx2_text_encoder.h" #include "vllm/model_executor/models/ltx2_upsampler.h" #include "vllm/model_executor/models/ltx2_video_vae.h" +#include "vllm/platforms/interface.h" // CurrentPlatform() — the seam the engine asks #include "vllm.h" #include "vt/backend.h" #include "vt/device.h" @@ -358,26 +359,35 @@ TEST_CASE("ltx2 video: the second phase upsamples, and refuses when it cannot") // L7 had to refuse `device = 1` outright: the forward was f32-only by // declaration and the staging was bf16 and refused to widen, so no combination // put the DiT on an accelerator. L8 is the device-resident forward that closes -// that (`Ltx2DitForwardDevice`), so a CUDA handle now denotes a CUDA forward and -// the load must SUCCEED where a CUDA backend exists. +// that (`Ltx2DitForwardDevice`), so a non-zero handle now denotes a +// device-resident forward and the load must SUCCEED where an accelerator backend +// exists. // -// What must never come back is the substitution: on a build with no CUDA backend -// the load is still refused, and the refusal must name the missing BACKEND. If it -// ever again names the f32/bf16 gap, the device forward has been un-wired; if it -// silently succeeds with a CPU device, the engine is lying about where it ran. -TEST_CASE("ltx2 video: device 1 runs on CUDA, and is refused by name without it") { +// The DSR repair (#553) changed WHICH accelerator from a hardcoded `kCUDA` to +// the platform seam's own answer, so this case now asks the seam the same +// question the engine asks instead of naming a device itself. On a CUDA box that +// resolves to kCUDA and the assertions below are the ones L8 shipped. +// +// What must never come back is the substitution: on a build with no accelerator +// backend the load is still refused, and the refusal must name the missing +// BACKEND. If it ever again names the f32/bf16 gap, the device forward has been +// un-wired; if it silently succeeds with a CPU device, the engine is lying about +// where it ran. +TEST_CASE("ltx2 video: device 1 runs on the resolved accelerator, refused by name without one") { Workspace ws; vllm::multimodal::VideoModelParams mp = FixtureParams(ws.paths); mp.device = 1; - vt::Backend* cuda = vt::TryGetBackend(vt::DeviceType::kCUDA); - if (cuda == nullptr) { + const vt::DeviceType accelerator = vllm::platforms::CurrentPlatform().device_type(); + const bool have_accelerator = accelerator != vt::DeviceType::kCPU && + vt::TryGetBackend(accelerator) != nullptr; + if (!have_accelerator) { try { (void)vllm::multimodal::LoadVideoEngine(mp); - FAIL("a CUDA load must be refused when no CUDA backend is registered"); + FAIL("a device-1 load must be refused when no accelerator backend is registered"); } catch (const std::exception& e) { const std::string msg = e.what(); INFO(msg); - CHECK(msg.find("no CUDA backend") != std::string::npos); + CHECK(msg.find("no accelerator backend") != std::string::npos); // The L7 gap must NOT be what is named any more; naming it would mean the // device forward is no longer wired in. CHECK(msg.find("kF32") == std::string::npos); @@ -387,8 +397,10 @@ TEST_CASE("ltx2 video: device 1 runs on CUDA, and is refused by name without it" const std::unique_ptr engine = vllm::multimodal::LoadVideoEngine(mp); REQUIRE(engine != nullptr); - // The handle means what it says: a CUDA device, not a CPU one behind it. - CHECK(engine->device().type == vt::DeviceType::kCUDA); + // The handle means what it says: the accelerator the seam resolved, never a + // CPU device behind it. + CHECK(engine->device().type == accelerator); + CHECK(engine->device().type != vt::DeviceType::kCPU); CHECK(engine->family() == std::string(vllm::multimodal::kLtx2VideoFamily)); } diff --git a/tests/vt/test_backend_cross_device.cpp b/tests/vt/test_backend_cross_device.cpp index a56116eb4..186500e77 100644 --- a/tests/vt/test_backend_cross_device.cpp +++ b/tests/vt/test_backend_cross_device.cpp @@ -34,6 +34,7 @@ #include #include +#include "support/test_env.h" // SetEnv/UnsetEnv — MSVC has no setenv (#603) #include "vt/backend.h" #include "vt/op_provider.h" #include "vt/ops.h" @@ -1001,7 +1002,7 @@ TEST_CASE("FusedChain matches the CPU oracle within NMSE <= 5e-4 (both tiers)") for (int tier : {0, 1}) { CAPTURE(tier); - setenv("VT_FUSED_TIER", tier == 0 ? "0" : "1", 1); + vllm_test::SetEnv("VT_FUSED_TIER", tier == 0 ? "0" : "1"); // ASSERT the tier actually took effect rather than trusting the log: doctest // CAPTURE is lazily stringified, so a mis-set environment would silently // run the same path twice and still look like two-tier coverage. @@ -1045,9 +1046,9 @@ TEST_CASE("FusedChain matches the CPU oracle within NMSE <= 5e-4 (both tiers)") } if (had_prev) { - setenv("VT_FUSED_TIER", saved.c_str(), 1); + vllm_test::SetEnv("VT_FUSED_TIER", saved); } else { - unsetenv("VT_FUSED_TIER"); + vllm_test::UnsetEnv("VT_FUSED_TIER"); } } From eb58fd58f3912079bbbfb282215aa946bc4ac07c Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 13 Aug 2026 20:31:29 +0000 Subject: [PATCH 05/55] fix(ltx-2.5): the keyframe refusal named a FALSE reason, and a test pinned it (#644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review findings on #657. Two blocking, four minor. F1 — the last-frame keyframe refusal blamed `keyframes_abs_pos_embedding`, and that is not what blocks it at pin `fd4ded7f`. Three anchors refute it: * `keyframe_cond.py:84-86` appends a SUPPLIED keyframe with `marked=False`, commented "given keyframe content is ordinary image guidance, not a generated keyframe slot, so it carries no keyframe marker"; * its sole consumer is `transformer_args.py:42-43` (called once at `:269`), `mask = (keyframes_mask > 0); return hidden_states + mask * embedding` — so with `marked=False` it adds NOTHING, and porting it would not serve the arm; * the contrast was backwards. `tools.py:184` + `_first_frame_keyframes_mask` (`:186-195`) mark the target's FIRST latent frame unconditionally, so the tokens that actually reach the embedding are the ones the SERVED first-frame arm writes into — which the message called "needs none of that". That gap is real and is issue #658; it is not fixed here and nothing here contradicts it. The named gate did not fire either: the refusal lives in `ltx2.cpp`, and `Ltx2AdoptDeclaredDitParams` clears the flag on every real load under `allow_unported_modules` because the shipped DiT does carry the tensor. Worse, `test_ltx2_video.cpp` had been written to REQUIRE the wrong reason by name. The message now names the token-append machinery — extended `positions` (`keyframe_cond.py:46-59`), `update_attention_mask` (`:68-76`), extended `clean_latent` / `denoise_mask` (`:79-82`) and `clear_conditioning` trimming back (`tools.py:88-105`) — against a phase loop fixed at the target grid's token count, and says why the first-frame arm needs none of it. The assertion now requires THAT, and permits the refuted reason only where it is labelled as ruled out and paired with #658. Local anchors are symbols, not same-file line numbers, which is how the previous citation went stale. Sixth refusal in this campaign whose stated reason was false; the header comment now says six and says why. RED old message, new assertions: 4 failed, exit 1 GREEN new message: 32 cases / 565 assertions, exit 0 F2 — spec section 8.5 names "conditioning stage 1 only would let stage 2 re-noise the pinned frame away" as the REASON the design conditions inside the phase loop, and gated it with nothing. MEASURED: `if (wants_image && phase_index == 0)` left the suite at 32 cases / 550 assertions / exit 0 — `image_digest` / `image_tokens` are overwritten each phase and the only assertion was `image_tokens > 0`. This is the shape of the obvious optimization (hoist the per-phase decode+encode out of the loop), so it would have landed green. The trace now pins the LAST phase's per-latent-frame token count, and a `max_phase = 0` engine over the same request pins stage 1's, with the 4x relation checked between two MEASURED values rather than two compile-time constants. RED mutant: `CHECK( 1 == 4 )` twice plus the digest equality, 6 failed, exit 1 GREEN 32 cases / 565 assertions, exit 0 F3 — nine mis-cited anchors, each re-verified against the pin before changing. `blocks.py:576-580` is the TEARDOWN, so the conditioning-order claim now cites `create_noised_state` (`helpers.py:428-445`) and the video-before-audio claim cites `blocks.py:554-563`. Also corrected: `ImageConditioner` build `:985-986` / build-and-free `:988-991`; `helpers.py:274-275` are parameter declarations, with the per-stage h/w at `distilled.py:251, :255-256, :285-286`; `model_configurator.py:41-43`→`:41-42`; decoder padding default `:90`→`:92`; `reference_video_cond.py:74, 80`→`:36-37` applied at `:65-77`; `reference_audio_cond.py:33`→`:34`; `detect_params` `:166-177`→`:166-179`. F4 — `Ltx2VideoVaeEncoderKeyRules` claimed "rule for rule". Upstream's `SDOps` (`loader/sd_ops.py:101-122`) is an `any()` admission over four matchings plus a chained substring replace over three replacements; this port is one first-match prefix pass, so its fourth rule is an identity carrying upstream's fourth matching. The comment now says behaviourally equivalent, and names the case where the two shapes would part. F5 — `ParseEncoderBlocks` defaulted `num_layers` to 1 while upstream SUBSCRIPTS `block_config["num_layers"]` (`video_vae.py:55`) and raises `KeyError`, and two lines below it `multiplier`'s sentinel is deliberately strict. A `res_x` block without `num_layers` is now refused; no other block kind reads the key, so requiring it everywhere would have refused configs upstream loads, and both halves are gated. RED guard removed: "did NOT throw at all!", 1 failed, exit 1 GREEN 15 cases / 223 assertions, exit 0 F6 — the three assertions comparing compile-time constants are left as they are, correctly labelled documentation. Noted so the count is read honestly; the new F2 assertions deliberately compare two measured values instead. USAGE carried the same false keyframe reason and is corrected, with the correction dated and #658 named so a reader can check it. USAGE also gains the `res_x` / `num_layers` requirement. FEATURES keeps its keyed row within budget. Gate, at this tree, CPU-only Release, CUDA=OFF: BUILD_EXIT=0, no ENOSPC, no BFD assertion ctest -N 424 ctest -j 8 423/424; `test_engine_core_proc` is the documented -j starvation and passes serially, exit 0 test_ltx2_video 32 cases / 565 assertions, exit 0 (was 32 / 550) test_ltx2_image_cond 15 cases / 223 assertions, exit 0 (was 15 / 198) test_ltx2_vae 36 / 3039 | test_ltx2 35 / 2435 | test_ltx2_loader 26 / 4826 test_ltx2_pipeline 37 / 2382 | test_ltx2_device 15 / 523 — all unchanged, exit 0 `LTX2_CHECKPOINT_ROOT` is unset here, so `test_ltx2_video`'s shipped-checkpoint subcase SKIPPED and is not part of those counts. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/specs/ltx25-image-conditioning.md | 66 ++++++++++++--- docs/FEATURES.md | 2 +- docs/USAGE.md | 22 +++-- .../models/ltx2_video_vae_encoder.h | 4 +- .../models/ltx2_video_vae_encoder_load.cpp | 44 ++++++++-- src/vllm/multimodal/ltx2_video.cpp | 82 +++++++++++++------ .../vllm/multimodal/test_ltx2_image_cond.cpp | 26 +++++- tests/vllm/multimodal/test_ltx2_video.cpp | 77 +++++++++++++++-- 8 files changed, 266 insertions(+), 57 deletions(-) diff --git a/.agents/specs/ltx25-image-conditioning.md b/.agents/specs/ltx25-image-conditioning.md index a3857447c..1ccd5766a 100644 --- a/.agents/specs/ltx25-image-conditioning.md +++ b/.agents/specs/ltx25-image-conditioning.md @@ -70,7 +70,7 @@ codec. |---|---|---| | encoder key filter | `ltx-core/.../video_vae/model_configurator.py:267-276` (`VAE_ENCODER_COMFY_KEYS_FILTER`) | `Ltx2VideoVaeEncoderKeyRules` | | encoder config | `model_configurator.py:37-69` (`_prepare_video_encoder_kwargs`) + `:72-78` (`VideoEncoderConfigurator`) | `Ltx2ParseConvVideoEncoderConfig` | -| encoder lifecycle | `ltx-pipelines/.../utils/blocks.py:936-993` (`ImageConditioner`) | engine load path, `ltx2_video.cpp` | +| encoder lifecycle | `ltx-pipelines/.../utils/blocks.py:936-991` (`ImageConditioner`; build `:985-986`, build-and-free `:988-991`) | engine load path, `ltx2_video.cpp` | | CRF resolution | `blocks.py:966-983` + `constants.py:36-37, 124, 130-133` | `Ltx2ResolveDefaultImageCrf` | | CRF round trip | `decode.py:413-435`, `encode_single_frame:386-400` | **REFUSED BY NAME** (§3.4) | | image decode | `decode.py:139-170` (`decode_image`: EXIF rotate, ICC→sRGB, uint8 RGB) | `Ltx2DecodePpmRgb` (PPM only, §3.2) | @@ -86,8 +86,14 @@ codec. ### 3.1 The encoder load path `Ltx2VideoVaeEncoderKeyRules()` mirrors `VAE_ENCODER_COMFY_KEYS_FILTER` -(`model_configurator.py:267-276`) in the same first-match-wins prefix form the -already-gated `Ltx2VideoVaeDecoderKeyRules()` uses: +(`model_configurator.py:267-276`) in the first-match-wins prefix form the +already-gated `Ltx2VideoVaeDecoderKeyRules()` uses. It is a TRANSLATION, not a +rule-for-rule copy: upstream's `SDOps` (`loader/sd_ops.py:101-122`) admits a key +by `any()` over four matchings and then chains three substring replacements, +while this port matches and replaces in one pass — so the fourth rule below is an +identity that exists to carry upstream's fourth matching. Equivalent on every key +a shipped checkpoint carries; see the comment on the function for exactly when +they would part. ``` {"vae.encoder.", ""} @@ -180,7 +186,10 @@ to the diffusers form. Placement in `Generate`: the conditioning is applied to the video `StreamState` AFTER `clean` is seeded from the patchified initial volume and BEFORE -`ApplyGaussianNoise` — which is `blocks.py:576-580` / `helpers.py:428-447` order. +`ApplyGaussianNoise` — which is `create_noised_state` order (`helpers.py:428-445`: +initial state, then the conditioning items, then the noiser). NOT `blocks.py:576-580`, +which this spec cited until the review of #657: those lines are the TEARDOWN +(`clear_conditioning` + `unpatchify`) and say nothing about conditioning order. It is applied on EVERY phase, because every phase rebuilds its state from the recipe and a conditioning dropped on phase 2 would be re-noised away. @@ -203,10 +212,31 @@ whose stated reason went stale; each message below names the exact symbol or 2. **No encoder in the checkpoint.** Names `Ltx2VideoVaeEncoderKeyRules` and says the file carried no `encoder.*` / `vae.encoder.*` tensors. 3. **Keyframes** (`last_frame_path`, or an image at a non-zero frame index). - `Ltx2ConditionVideoByKeyframe` EXISTS and is gated; what is missing is that - the DiT's `keyframes_abs_pos_embedding` module is unported and is refused by - `ParseLtx2DitParams`, so the appended keyframe tokens would carry no - positional embedding. + `Ltx2ConditionVideoByKeyframe` EXISTS and is gated; what is missing is the + TOKEN-APPEND machinery. `VideoConditionByKeyframeIndex.apply_to` + (`keyframe_cond.py:36-90`) appends tokens — concatenating onto `latent`, + `denoise_mask`, `positions` and `clean_latent` (`:79-82`), giving them their + own coordinates offset to `frame_idx` (`:46-59`) and rebuilding the attention + mask via `update_attention_mask` (`:68-76`) — and `clear_conditioning` + (`ltx_core/tools.py:88-105`) trims them back before unpatchify. `Ltx2LatentState` + carries no attention mask, and the engine's phase loop is fixed at the target + grid's token count from one `Ltx2VideoTokenCount` through the sigma schedule, + the `Ltx2ModalityInput` and `Ltx2VideoUnpatchify`. The first-frame arm needs + none of it because `VideoConditionByLatentIndex` REPLACES existing tokens. + + **This spec and the shipped message previously named + `keyframes_abs_pos_embedding` as the blocker. That was FALSE at pin + `fd4ded7f`, and a test had been written to assert it by name.** A supplied + keyframe is appended with `marked=False` (`keyframe_cond.py:84-86`), and its + sole consumer adds `mask * embedding` with `mask = keyframes_mask > 0` + (`model/transformer/transformer_args.py:42-43`, called once at `:269`), so the + embedding contributes nothing to those tokens and porting it would not serve + this arm. The tokens that DO reach it are the target's own first latent frame, + marked unconditionally by `_first_frame_keyframes_mask` + (`ltx_core/tools.py:184-196`) — the frame the SERVED first-frame arm writes + into. That is a real gap on the served arm and is tracked as + [#658](https://github.com/mudler/vllm.cpp/issues/658); it is not what blocks a + last-frame keyframe. 4. **Reference video / reference image / reference audio.** `Ltx2ConditionVideoByReference` and `Ltx2ConditionAudioByReference` also EXIST; what is missing is that both need the IC-LoRA's `downscale_factor` / @@ -222,7 +252,13 @@ whose stated reason went stale; each message below names the exact symbol or and still produces a latent. * **The encoder's `zeros` vs the decoder's `reflect` padding default.** Same checkpoint key, different defaults, and they only diverge when the key is - absent. + absent (`model_configurator.py:63-68` vs `:92`). +* **Defaulting a `res_x` block's `num_layers`.** Upstream subscripts + `block_config["num_layers"]` (`video_vae.py:55`) and raises `KeyError`; no + other block kind reads the field. `ParseEncoderBlocks` defaulted it to 1, which + builds a one-layer `UNetMidBlock3D` out of a config upstream refuses. Made + strict in the review of #657, matching how `multiplier`'s sentinel two lines + below already treats an absent value. * **Normalizing before resizing.** Same answer to ~1e-7 — and §8.1 records the outcome: NO golden here sees it either, because the two orders are algebraically equal. Mirrored because it is upstream's, and written down @@ -381,12 +417,20 @@ only because of that change. * **`image_crf` has no default that renders.** Absent resolves 18 and refuses. A default of 0 would silently condition every request out of distribution. * **The encoder stays RESIDENT**, where `ImageConditioner` builds and frees it - per call (`blocks.py:988-993`). A conditioning image arrives per request and + per call (built at `blocks.py:985-986`, built-and-freed around `fn` at + `:988-991`). A conditioning image arrives per request and the encoder is small next to the DiT; the divergence is lifecycle only. * **The conditioning is applied PER PHASE**, because the two-stage recipe renders its stages at different resolutions and upstream passes each stage's own height/width. One encode would either be re-noised away or placed at the wrong - scale. + scale. **This reason was gated by nothing until the review of #657.** MEASURED: + changing the guard to `wants_image && phase_index == 0` left `test_ltx2_video` + at 32 cases / 550 assertions / exit 0, because `image_tokens` and `image_digest` + are overwritten each phase and the suite only asked `image_tokens > 0`. The + trace now pins the LAST phase's per-latent-frame token count (4 in the fixture, + 1 at phase 0) and contrasts it with a `max_phase = 0` engine over the same + request, which REDs that mutant at `1 == 4`. It matters because hoisting the + per-phase decode+encode out of the loop is the obvious optimization. ## 9. Now diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 2510acefb..352c263aa 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -161,7 +161,7 @@ in `ltx2_text_encoder.cpp` is the call that would have to change. | Voxtral audio (`VoxtralForConditionalGeneration`) | Voxtral-Mini-3B-2507 | near-tie-robust 16/16 vs vLLM 0.25.0 | decode 0.97x (beats vLLM); encoder FORWARD 15.90x of vLLM's whole TTFT (pin 46.02 ms), or 2.89x with opt-in `VT_WHISPER_ENC_FA2=1` (costs 3 near-tie divergences vs 0). Not a TTFT ratio. Pending | | Whisper audio encoder | openai/whisper-small; whisper-large-v3 (Voxtral cfg) | encoder tower 77/77; large-v3 tower 203/203 | pending | | MiniMax-H3 DiT (`MiniMaxH3DiTModel`, vllm-omni lane) | MiniMax-H3 (33.1B video+audio) | portable 79/79; all three modalities COHERENT on Q4_K_M (§8.20); PRUNED ckpts run, Q8_0 seam 0.9941 (§8.21); ref2va grid was NVFP4 quant error, §8.9 REFUTED; GGUF/NVFP4/bf16 shards stream | FP4/Marlin landed; speed pending; no bf16 render yet. Render from the Q4_K_M GGUF, not the NVFP4 arm. Krea 2 text-to-image (roadmap C11) is scoped to reuse these DiT seams | -| LTX-2.5 DiT (`LTX2VideoTransformer3DModel`, Lightricks lane) | LTX-2.5 (21.00B video+audio) | `SPIKE`. DiT, VAEs+ENCODERS, conditioning, pipeline, quant loaders gated at reduced dims. Prompt-side AdaLN ported; image chain PPM->resize->encode->place->noise vs executed upstream. A prompted render is OWED | Family `ltx-2.5`, `ltx2-gen`. ~29 GB NVFP4/GB10, FP8 ~44 GB, +~24 GB tower. FP8/torchao/1st-party NVFP4 load. IMAGE cond SERVED at `image_crf=0`. DiffVAE, LoRA, keyframe/ref refused. Speed PENDING | +| LTX-2.5 DiT (`LTX2VideoTransformer3DModel`, Lightricks lane) | LTX-2.5 (21.00B video+audio) | `SPIKE`. DiT, VAEs+ENCODERS, conditioning, pipeline, quant loaders gated at reduced dims. Prompt-side AdaLN ported; image chain PPM->resize->encode->place->noise vs upstream. A prompted render is OWED | `ltx-2.5`, `ltx2-gen`. ~29 GB NVFP4/GB10, FP8 ~44 GB, +24 GB tower. FP8/torchao/NVFP4 load; `keyframes_abs_pos_embedding` needs `allow_unported`. IMAGE cond `crf=0`; keyframe/ref/DiffVAE/LoRA refused. Speed PENDING | | MTP speculator | Qwen3.6-27B, Qwen3.6-35B-A3B | token-identical to vLLM `mtp` at c1 | ~4% faster c1; +16% output tput (MoE) | | DFlash block-diffusion | Qwen3 (DFlash draft) | near-tie e2e 27/27 vs vLLM | 2.9x over spec-off, 1.003x vs vLLM DFlash-on | | DeepSeek-V4 MTP | DeepSeek-V4-Flash (nextn head) | lossless 5/5; real-model weight-blocked | pending | diff --git a/docs/USAGE.md b/docs/USAGE.md index b6a92100c..bed2f5688 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -404,15 +404,25 @@ it is stated rather than applied silently. Keyframe, reference-image, reference-video and reference-audio conditioning are still refused, each naming a different missing piece: a last-frame keyframe needs -the DiT's unported `keyframes_abs_pos_embedding`; the reference arms need the -IC-LoRA's scale factors, which live in LoRA metadata this project does not read; -reference audio additionally needs the AUDIO VAE's encoder key filter, which is -not built. Two encoder-level limits are worth +the token-APPEND machinery — a keyframe is appended to the sequence with its own +positions and a rebuilt attention mask, then trimmed back off, and this engine's +phase loop is fixed at the target grid's token count — while the served +first-frame arm only REPLACES tokens that already exist; the reference arms need +the IC-LoRA's scale factors, which live in LoRA metadata this project does not +read; reference audio additionally needs the AUDIO VAE's encoder key filter, +which is not built. (Until 2026-08-13 this said a last-frame keyframe needs the +DiT's unported `keyframes_abs_pos_embedding`. That was wrong: a supplied keyframe +is appended unmarked, so the embedding never applies to it. Where the embedding +does bite is the FIRST latent frame of every render, which is a separate gap, +tracked as issue #658.) Three encoder-level limits are worth stating in advance because they are refusals rather than approximations. A reference waveform whose sample rate differs from the audio VAE's is refused rather than resampled, since upstream uses a polyphase kaiser resampler this -project does not carry. And a VAE configured with `latent_log_var: none` is -refused, because upstream itself raises on it. +project does not carry. A VAE configured with `latent_log_var: none` is +refused, because upstream itself raises on it. And a video-VAE `res_x` encoder +block that declares no `num_layers` is refused rather than defaulted, because +upstream subscripts that key and raises `KeyError` on it; no other encoder block +kind reads it. **A typed prompt works.** `--encoder` names the Gemma-4 12B text tower and `--prompt` carries the words. The tower tokenizes them with its OWN embedded diff --git a/include/vllm/model_executor/models/ltx2_video_vae_encoder.h b/include/vllm/model_executor/models/ltx2_video_vae_encoder.h index cbde9c232..b7fb8640c 100644 --- a/include/vllm/model_executor/models/ltx2_video_vae_encoder.h +++ b/include/vllm/model_executor/models/ltx2_video_vae_encoder.h @@ -28,7 +28,7 @@ // // ─── THE FIVE THINGS THAT FAIL SILENTLY ────────────────────────────────────── // * THE ENCODER'S DEFAULT SPATIAL PADDING IS `zeros`; THE DECODER'S IS -// `reflect` (model_configurator.py:63-67 vs :90). They read the SAME +// `reflect` (model_configurator.py:63-68 vs :92). They read the SAME // checkpoint key `spatial_padding_mode` on a flat CausalVideoAutoencoder // config, so they only diverge when the key is ABSENT — and then they diverge // silently, by a half-pixel border, in opposite directions. @@ -218,7 +218,7 @@ bool Ltx2CheckpointHasVideoEncoder(const std::vector& tensor_names) // (:41-43). On a nested `CausalDiffusionVAE` config it is // `vae.encoder.out_channels` (:46-49) — the same spelling, the other object. // * `spatial_padding_mode`. The ENCODER's default is `zeros`; the decoder's is -// `reflect` (:63-67 vs :90). Both read the same checkpoint key on a flat +// `reflect` (:63-68 vs :92). Both read the same checkpoint key on a flat // config, so they diverge only when the key is ABSENT, silently, by a // half-pixel border, in opposite directions. Ltx2ConvVideoEncoderConfig Ltx2ParseConvVideoEncoderConfig(const nlohmann::json& config); diff --git a/src/vllm/model_executor/models/ltx2_video_vae_encoder_load.cpp b/src/vllm/model_executor/models/ltx2_video_vae_encoder_load.cpp index 975bd0360..baf1cf98f 100644 --- a/src/vllm/model_executor/models/ltx2_video_vae_encoder_load.cpp +++ b/src/vllm/model_executor/models/ltx2_video_vae_encoder_load.cpp @@ -110,6 +110,21 @@ std::vector ParseEncoderBlocks(const nlohmann::json& bloc Ltx2VideoEncoderBlock block; block.name = entry[0].get(); const nlohmann::json& params = entry[1]; + // `num_layers` is REQUIRED, and required by exactly one block kind. Upstream + // SUBSCRIPTS it — `num_layers=block_config["num_layers"]` (video_vae.py:55) — + // which raises `KeyError` when it is absent, and no other branch of + // `_make_encoder_block` reads it at all (:61-145). This defaulted to 1, so a + // `res_x` config upstream refuses outright built a silent one-layer + // `UNetMidBlock3D` here — the same class of wrong-shape-without-a-word defect + // that `multiplier`'s sentinel two lines below exists to prevent, resolved the + // opposite way. Made consistent 2026-08-13 (review of #657, row LTX25-IMAGE-COND). + if (block.name == "res_x" && !params.contains("num_layers")) { + Fail("a '" + where + ".encoder_blocks' res_x entry carries no 'num_layers': " + + entry.dump() + + ". Upstream subscripts it (video_vae.py:55) and raises KeyError, so a checkpoint " + "without it is one upstream cannot load either; the layer count is not derivable " + "from anything else in the config"); + } block.num_layers = ConfigGet(params, "num_layers", 1, "encoder_block"); // 0 is the sentinel `Ltx2VideoEncoderBlock` documents for "the upstream // default for this block kind" (2 for every `*_x_y` / `*_res`). An ABSENT @@ -125,11 +140,30 @@ std::vector ParseEncoderBlocks(const nlohmann::json& bloc } // namespace std::vector Ltx2VideoVaeEncoderKeyRules() { - // model_configurator.py:267-276, rule for rule. ORDER MATTERS in this port's - // first-match-wins loop the way it does in upstream's replacement chain: the - // `vae.`-prefixed spellings must be tried before their bare twins, or - // `vae.encoder.conv_in...` would fall through to no rule at all (it does not - // start with `encoder.`) and be dropped. + // `VAE_ENCODER_COMFY_KEYS_FILTER` (model_configurator.py:267-276), TRANSLATED + // into this port's rule shape — not copied rule for rule, and the difference is + // worth stating rather than glossing. + // + // Upstream's `SDOps` is two passes (loader/sd_ops.py:101-122): an `any()` over + // its four `with_matching` PREFIXES decides whether the key is admitted at all, + // and then every `with_replacement` is applied in order as a chained SUBSTRING + // replace. Upstream declares four matchings and only three replacements, + // because the bare `per_channel_statistics.` form needs admitting but not + // rewriting. + // + // This port has ONE pass: a first-match-wins prefix loop where the matched + // rule's replacement is substituted (`Ltx2LoadVaeTensors`). So the fourth rule + // below is an IDENTITY — it exists to carry upstream's fourth matching, since a + // key admitted by no rule here is dropped. ORDER MATTERS for the same reason + // upstream lists its `vae.`-prefixed spellings first: `vae.encoder.conv_in...` + // does not start with `encoder.`, so a bare-first ordering would drop it. + // + // The two shapes agree on every key any shipped checkpoint carries: all four + // upstream matchings are prefixes, no admitted key contains a second + // replacement's substring after the first has fired, and no replacement is a + // substring of another's output. They would diverge on a key needing TWO + // rewrites in one pass, which no LTX-2 VAE file produces — behaviourally + // equivalent, therefore, not verbatim. return { {"vae.encoder.", ""}, {"vae.per_channel_statistics.", "per_channel_statistics."}, diff --git a/src/vllm/multimodal/ltx2_video.cpp b/src/vllm/multimodal/ltx2_video.cpp index f5a2d4c20..35111cb65 100644 --- a/src/vllm/multimodal/ltx2_video.cpp +++ b/src/vllm/multimodal/ltx2_video.cpp @@ -792,7 +792,7 @@ std::unique_ptr Ltx2VideoEngine::Load(const VideoModelParams& p std::to_string(im.dit.params.in_channels) + ". `_prepare_video_encoder_kwargs` reads this from `vae.latent_channels`, never " "from the top-level `vae.out_channels`, which is the DECODER's RGB count " - "(video_vae/model_configurator.py:41-43)."); + "(video_vae/model_configurator.py:41-42, and the flat-layout read at :52)."); } // And its INPUT width, for the same reason in the other direction: the // encoder takes RGB, and a config declaring otherwise would silently @@ -1180,31 +1180,60 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { // silently ignored renders an unconditioned clip that looks like the feature // not working. // - // THESE MESSAGES ARE WRITTEN TO BE RE-CHECKABLE. Five refusals in this - // campaign have had their stated reason go stale, including the one that stood - // here: it said no encoder weights could be materialized, which was true when - // written and is what this row fixed. So each message below names the exact - // symbol or `file:line` that would have to change for it to become false, - // rather than a category. + // THESE MESSAGES ARE WRITTEN TO BE RE-CHECKABLE, and the count is now SIX + // refusals in this campaign whose stated reason turned out to be false or + // stale. Two of the six stood right here. The first said no encoder weights + // could be materialized — true when written, and what this row fixed. The + // second replaced it and blamed `keyframes_abs_pos_embedding`, which was + // verifiably NOT the blocker at the pin (see the last-frame message below for + // the three anchors that refute it), and a test had been written to assert + // that wrong reason by name. + // + // So: name the exact symbol or upstream `file:line` that would have to change + // for the refusal to become false, never a category — and where a plausible + // reason has already been ruled OUT, say so and cite what ruled it out, so the + // next reader re-checks the claim instead of re-deriving the refutation. Local + // anchors are SYMBOLS, not line numbers in this file: same-file line numbers + // drift on every edit, which is how the previous message's citation went stale. const bool wants_image = !gen.first_frame_path.empty() || !gen.first_frame_ppm.empty(); if (!gen.last_frame_path.empty()) { Fail( - "a LAST-frame keyframe is not served. What is missing is no longer the encoder or the " - "placement — `Ltx2ConvVideoEncode` and `Ltx2ConditionVideoByKeyframe` are both ported " - "and gated, and this engine now materializes encoder weights through " - "Ltx2VideoVaeEncoderKeyRules. It is the DiT module: a keyframe is APPENDED as extra " - "tokens carrying their own positions (conditioning/types/keyframe_cond.py:39-90), and " - "the transformer reads those through `keyframes_abs_pos_embedding`, which " - "`ParseLtx2DitParams` refuses by name as unported (ltx2_loader.h). Conditioning on the " - "FIRST frame needs none of that — it REPLACES tokens that already exist — which is why " - "that arm is served and this one is not."); + "a LAST-frame keyframe is not served. What is missing is the TOKEN-APPEND machinery. " + "`Ltx2ConvVideoEncode` and `Ltx2ConditionVideoByKeyframe` are both ported and gated, " + "and this engine materializes encoder weights through Ltx2VideoVaeEncoderKeyRules, so " + "none of those is the gap. The gap is that `VideoConditionByKeyframeIndex.apply_to` " + "(conditioning/types/keyframe_cond.py:36-90) APPENDS tokens to the sequence: it " + "concatenates onto `latent`, `denoise_mask`, `positions` and `clean_latent` (:79-82), " + "gives the appended tokens their own pixel coordinates offset to `frame_idx` (:46-59), " + "and rebuilds the attention mask through `update_attention_mask` (:68-76) — and then " + "`clear_conditioning` (ltx_core/tools.py:88-105) trims those extra tokens back off " + "before unpatchify. This engine cannot do any of that yet: `Ltx2LatentState` has no " + "attention-mask field at all (see the note on its declaration in ltx2_conditioning.h), " + "and the phase loop is fixed at the target grid's token count — one " + "`Ltx2VideoTokenCount(vshape, 1)` feeds the sigma schedule, the `Ltx2ModalityInput` " + "handed to the DiT, and `Ltx2VideoUnpatchify`, with the clear step an explicit identity " + "because nothing was ever appended. Serving this arm means growing that sequence " + "through the DiT and trimming it back. Conditioning on the FIRST frame needs none of " + "it, which is why that arm IS served: `VideoConditionByLatentIndex` REPLACES tokens " + "that already exist (conditioning/types/latent_cond.py:38-39) and the token count never " + "changes. WHAT IS *NOT* THE REASON, because this refusal used to say it was: " + "`keyframes_abs_pos_embedding`. A SUPPLIED keyframe is appended with `marked=False` " + "(keyframe_cond.py:84-86, whose comment says given keyframe content carries no keyframe " + "marker), and its sole consumer adds `mask * embedding` with `mask = keyframes_mask > 0` " + "(model/transformer/transformer_args.py:42-43, called once at :269) — so on exactly " + "these tokens the embedding contributes nothing, and porting it would not serve this " + "arm. The tokens that DO reach it are the target's own first latent frame, marked " + "unconditionally by `_first_frame_keyframes_mask` (ltx_core/tools.py:184-196) — which " + "is the frame the SERVED first-frame arm writes into. That omission is real and is " + "tracked as issue #658; it is not what blocks a last-frame keyframe."); } if (!gen.ref_image_paths.empty() || !gen.ref_video_dir.empty()) { Fail( "reference-image / reference-video conditioning is not served. The encoder and the " "placement are both here — `Ltx2ConditionVideoByReference` is ported and gated — but " "it takes a `downscale_factor` and a `temporal_scale_factor` that must match what the " - "IC-LoRA was TRAINED with (conditioning/types/reference_video_cond.py:74, 80), and " + "IC-LoRA was TRAINED with (conditioning/types/reference_video_cond.py:36-37, applied at " + ":65-77), and " "upstream carries those in the LoRA's own metadata, which this project does not read. " "A guessed pair places the reference plausibly and wrongly, which no output check can " "see, so it is refused instead. Use first_frame_ppm / first_frame_path for " @@ -1213,7 +1242,7 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { if (!gen.ref_audio_path.empty() || !gen.ref_audio_wav.empty()) { Fail( "reference-AUDIO conditioning is not served. `Ltx2ConditionAudioByReference` is ported " - "and gated (conditioning/types/reference_audio_cond.py:33-65), and what it needs is an " + "and gated (conditioning/types/reference_audio_cond.py:34-65), and what it needs is an " "encoded waveform: `encode_audio` through the audio VAE's ENCODER " "(ltx-pipelines/utils/helpers.py:264-269). This row built the VIDEO encoder's load " "path only — there is no AUDIO_VAE_ENCODER key filter — so nothing can turn a WAV into " @@ -1221,7 +1250,7 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { } // The CRF, resolved the way `ImageConditioner.resolve_crf` resolves it - // (blocks.py:966-983) over `detect_params` (utils/constants.py:166-177): from + // (blocks.py:966-983) over `detect_params` (utils/constants.py:166-179): from // the CHECKPOINT's own generation when the caller left it unset. For LTX-2.5 // that is 18, and 18 is not ported — so the DEFAULT REFUSES and a caller has // to ask for 0 knowingly. Resolved and checked BEFORE any pixel is read, so an @@ -1391,7 +1420,7 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { video_initial = up.data; } - // ── build the two states (create_noised_state, helpers.py:428-447) ─────── + // ── build the two states (create_noised_state, helpers.py:428-445) ─────── StreamState video; video.width = vshape.channels; // patch_size 1 (VideoLatentPatchifier(1)) video.tokens = Ltx2VideoTokenCount(vshape, 1); @@ -1448,7 +1477,8 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { // ── the image conditioning (issue #644) ───────────────────────────────── // // BEFORE THE NOISER AND AFTER THE STATE, which is upstream's order - // (blocks.py:576-580 -> helpers.py:428-447) and is not interchangeable: the + // (`create_noised_state`, helpers.py:428-445: initial state, THEN the + // conditioning items, THEN the noiser) and is not interchangeable: the // item writes ONLY `clean_latent` and `denoise_mask` (latent_cond.py:38-39) // and the noiser is what composes them into the noisy tensor // (components/noisers.py:31-34). Applying it afterwards leaves the @@ -1457,8 +1487,11 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { // // PER PHASE, and encoded per phase, because the two-stage recipe renders its // stages at DIFFERENT resolutions (`phase.spatial_downscale`) and upstream - // passes each stage's own height/width to `combined_image_conditionings` - // (helpers.py:274-275). Conditioning stage 1 only would let stage 2 re-noise + // passes each stage's own height/width to `combined_image_conditionings`, + // whose `height` / `width` are per-call parameters (helpers.py:274-275) that + // distilled.py fills differently per stage: `stage_1_w, stage_1_h = width // + // 2, height // 2` at :251 passed at :255-256, against the full-resolution + // `height` / `width` at :285-286. Conditioning stage 1 only would let stage 2 re-noise // the pinned frame away; conditioning stage 2 with stage 1's latent would // place a half-resolution image into a full-resolution grid. if (wants_image) { @@ -1526,7 +1559,8 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { } // The noiser draws VIDEO first, AUDIO second, from one generator - // (blocks.py:576-580 builds the video state before the audio one). + // (blocks.py:554-563 builds the video state before the audio one; :576-580, + // which this used to cite, is the TEARDOWN and proves nothing about order). const float noise_scale = static_cast(phase.noise_scale); ApplyGaussianNoise(video, state_noise.Draw(static_cast(video.latent.size())), noise_scale); diff --git a/tests/vllm/multimodal/test_ltx2_image_cond.cpp b/tests/vllm/multimodal/test_ltx2_image_cond.cpp index 3dc88ccac..9fd2bde9f 100644 --- a/tests/vllm/multimodal/test_ltx2_image_cond.cpp +++ b/tests/vllm/multimodal/test_ltx2_image_cond.cpp @@ -572,7 +572,7 @@ TEST_CASE("ltx2 image cond: the encoder config reads the LATENT width, not the R CHECK(cfg.encoder_blocks[1].multiplier == 2); // THE DEFAULT THAT DIVERGES FROM THE DECODER'S. `spatial_padding_mode` is // absent here, so the encoder must take `zeros` while the decoder takes - // `reflect` (model_configurator.py:63-67 vs :90). + // `reflect` (model_configurator.py:63-68 vs :92). CHECK(cfg.spatial_padding_mode == vllm::Ltx2PaddingMode::kZeros); CHECK(cfg.latent_log_var == vllm::Ltx2LogVarianceType::kUniform); @@ -606,6 +606,30 @@ TEST_CASE("ltx2 image cond: the encoder config reads the LATENT width, not the R config["vae"]["latent_log_var"] = "gaussian"; CHECK_THROWS_AS(vllm::Ltx2ParseConvVideoEncoderConfig(config), std::runtime_error); } + SUBCASE("a res_x block WITHOUT num_layers is refused, as upstream's subscript is") { + // `_make_encoder_block` reads `block_config["num_layers"]` — a SUBSCRIPT + // (video_vae.py:55) — so a `res_x` block that omits it is a config upstream + // cannot load either. This parser used to default it to 1, which silently + // built a one-layer `UNetMidBlock3D` instead; two lines below it, an absent + // `multiplier` is deliberately kept at a sentinel for exactly that reason. + // The two are consistent as of the review of #657. + config["vae"]["encoder_blocks"][0][1].erase("num_layers"); + CHECK_THROWS_WITH_AS(vllm::Ltx2ParseConvVideoEncoderConfig(config), + doctest::Contains("num_layers"), std::runtime_error); + } + SUBCASE("only res_x needs num_layers; no other block kind reads it") { + // The other half of the mirror, and the half a bare "make it strict" would + // get wrong: `_make_encoder_block`'s remaining branches (video_vae.py:61-145) + // never touch `num_layers`, so requiring it everywhere would refuse configs + // upstream loads. + config["vae"]["encoder_blocks"][1][1].erase("multiplier"); + const vllm::Ltx2ConvVideoEncoderConfig lean = + vllm::Ltx2ParseConvVideoEncoderConfig(config); + REQUIRE(lean.encoder_blocks.size() == 2); + CHECK(lean.encoder_blocks[1].name == "compress_all_res"); + CHECK(lean.encoder_blocks[1].num_layers == 1); + CHECK(lean.encoder_blocks[1].multiplier == 0); + } } // ─── sections 3-5: the chain ──────────────────────────────────────────────── diff --git a/tests/vllm/multimodal/test_ltx2_video.cpp b/tests/vllm/multimodal/test_ltx2_video.cpp index c20c3e91d..5ff4292bb 100644 --- a/tests/vllm/multimodal/test_ltx2_video.cpp +++ b/tests/vllm/multimodal/test_ltx2_video.cpp @@ -687,7 +687,9 @@ std::string ConditioningPpm(int height, int width, unsigned seed) { // BOTH phases, which for image conditioning is not a detail: the two-stage // recipe renders its stages at DIFFERENT resolutions, so the image is decoded, // resized and encoded once per phase against that phase's own height and width -// (ltx-pipelines/utils/helpers.py:274-275). A `max_phase = 0` fixture would +// (ltx-pipelines/utils/helpers.py:274-275 are the parameters; distilled.py:251, +// :255-256 and :285-286 are where the two stages pass different values). A +// `max_phase = 0` fixture would // leave the second encode — and the whole reason the conditioning lives inside // the phase loop — untested. vllm::multimodal::VideoModelParams ConditioningParams(const ltx2_fixture::Paths& paths) { @@ -724,18 +726,36 @@ TEST_CASE("ltx2 video: keyframe and reference conditioning is refused BY WHAT IS } }; - SUBCASE("a LAST-frame keyframe names the unported DiT module") { + SUBCASE("a LAST-frame keyframe names the TOKEN-APPEND machinery, not the embedding") { const std::string msg = refusal("a last-frame keyframe", [](vllm::multimodal::VideoGenParams& g, const Workspace& w) { g.last_frame_path = w.paths.video_embeds; }); INFO(msg); - // The encoder is NOT what is missing any more, and the message must not say - // it is. What IS missing is the positional-embedding module the appended - // keyframe tokens would be read through. - CHECK(msg.find("keyframes_abs_pos_embedding") != std::string::npos); + // THIS ASSERTION USED TO PIN A FALSE REASON. It required the message to + // blame `keyframes_abs_pos_embedding`, and at pin `fd4ded7f` that is not + // what blocks a supplied keyframe: `apply_to` appends it with + // `marked=False` (keyframe_cond.py:84-86) and the sole consumer adds + // `mask * embedding` (transformer_args.py:42-43, called at :269), so the + // embedding contributes exactly nothing to those tokens. Porting it would + // not serve this arm. The gate enforced the wrong thing, which is worse + // than not gating the message at all. + // + // What actually blocks it is the append: extended `positions`, + // `update_attention_mask`, extended `clean_latent` / `denoise_mask`, and + // `clear_conditioning` trimming back — none of which this engine's + // fixed-length phase loop can express. + CHECK(msg.find("update_attention_mask") != std::string::npos); + CHECK(msg.find("clear_conditioning") != std::string::npos); CHECK(msg.find("keyframe_cond.py") != std::string::npos); CHECK(msg.find("VAE_ENCODER_COMFY_KEYS_FILTER") == std::string::npos); + // The refuted reason may still be NAMED — it is worth telling a reader that + // it was ruled out — but never as the thing that is missing, and only next + // to the issue that tracks where the embedding really does bite (#658). + if (msg.find("keyframes_abs_pos_embedding") != std::string::npos) { + CHECK(msg.find("NOT* THE REASON") != std::string::npos); + CHECK(msg.find("#658") != std::string::npos); + } } SUBCASE("a reference video names the IC-LoRA metadata this project does not read") { const std::string msg = refusal("a reference video", @@ -817,7 +837,21 @@ TEST_CASE("ltx2 video: an image at crf 0 conditions the render, and the ENCODER CHECK(trace.completed); CHECK(trace.image_crf == 0); CHECK(trace.image_strength == 1.0); // noise_aug defaults to 1.0 => the frame is PINNED - CHECK(trace.image_tokens > 0); + // WHICH PHASE this describes is the claim, and `image_tokens > 0` did not make + // it. MEASURED: changing the guard to `wants_image && phase_index == 0` — the + // shape of an obvious refactor that hoists the per-phase decode+encode out of + // the loop — left this whole binary at 32 cases / 550 assertions / exit 0 + // while `refine`, the phase whose latent is actually rendered, ran with the + // pinned frame re-noised away. The design's own reason for living inside the + // loop (spec section 8.5) was gated by nothing. + // + // So the count is pinned to the LAST phase's per-latent-frame token count. + // This fixture's two-stage recipe runs `generate_lowres` at + // `spatial_downscale = 2` and `refine` at 1, so the latent grid doubles in + // each spatial dimension and the placed count is 1 then 4 — a per-phase value, + // which is exactly why `image_tokens == 4` falsifies a stage-1-only build. + constexpr int64_t kRefineImageTokens = 4; + CHECK(trace.image_tokens == kRefineImageTokens); CHECK(trace.image_digest != 0); // A conditioning that collapsed to zeros would give every image the same // digest and still satisfy every check below it, so the magnitude is asked for @@ -826,6 +860,35 @@ TEST_CASE("ltx2 video: an image at crf 0 conditions the render, and the ENCODER // The render still produced its artifacts; conditioning is not a bypass. CHECK(result.frame_count == 9); + SUBCASE("the trace describes the LAST phase, and stage 1 is a different count") { + // The other half of the same claim, and the half a literal cannot make: the + // number above is not a constant of the fixture, it TRACKS the phase that + // ran last. Same request, capped at phase 0, must report stage 1's smaller + // count — and the ratio is checked between two MEASURED values rather than + // between two compile-time constants, which would assert nothing. + // `max_phase` is a LOAD-time extra, not a per-generation one, so the cap + // needs its own engine over the same fixture. + vllm::multimodal::VideoModelParams capped = ConditioningParams(ws.paths); + capped.extras[vllm::multimodal::kLtx2MaxPhaseExtra] = "0"; + const std::unique_ptr stage1_engine = + vllm::multimodal::LoadVideoEngine(capped); + auto* stage1_ltx2 = + dynamic_cast(stage1_engine.get()); + REQUIRE(stage1_ltx2 != nullptr); + vllm::multimodal::VideoGenParams lowres = FixtureGen(ws.root + "/img_stage1"); + lowres.first_frame_ppm = ConditioningPpm(20, 28, 1); + lowres.extras[vllm::multimodal::kLtx2ImageCrfExtra] = "0"; + (void)stage1_engine->Generate(lowres); + const vllm::multimodal::Ltx2ConditioningTrace stage1 = stage1_ltx2->last_conditioning(); + CHECK(stage1.image_tokens == 1); + CHECK(trace.image_tokens == 4 * stage1.image_tokens); + // And it is a DIFFERENT encode, not the same one carried forward: the image + // is resized and encoded against each phase's own height and width + // (ltx-pipelines/utils/helpers.py:274-275, per-stage h/w at + // distilled.py:251, 255-256, 285-286). + CHECK(stage1.image_digest != trace.image_digest); + } + SUBCASE("a DIFFERENT image is a different conditioning") { vllm::multimodal::VideoGenParams other = FixtureGen(ws.root + "/img2"); other.first_frame_ppm = ConditioningPpm(20, 28, 2); From dd41622ab84efaa9148fc4cf71f740418898861d Mon Sep 17 00:00:00 2001 From: localai-org-maint-bot Date: Thu, 13 Aug 2026 22:38:19 +0200 Subject: [PATCH 06/55] docs: align quantization and registry counts (#575) BUILD still marked MXFP4 as planned and said all GPU GGUF weights expand. The implemented Marlin and keep-quant paths make both claims stale. STATUS also retained the old 30-entry registry count after the guarded public list grew to 37. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Codex:gpt-5 [Codex] Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com> --- docs/BUILD.md | 4 ++-- docs/STATUS.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/BUILD.md b/docs/BUILD.md index 8f1272013..393acce77 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -270,9 +270,9 @@ of operations. Per-op detail is in the |---|---| | NVFP4 W4A4 / W4A16 | Both gate-model paths run on GB10, token-exact. FP4 tactics match vLLM; Marlin NVFP4 W4A16 grouped-MoE is the 35B expert path | | compressed-tensors NVFP4A16 (W4A16), dense | Correctness-complete via the Marlin weight-only path; speed not yet measured | -| GGUF F32 / F16 / Q4_0 / Q8_0 / Q3_K / Q4_K / Q5_K / Q6_K | Supported. On CPU the six block encodings compute directly on the compressed blocks (`VT_GGUF_KEEP_QUANT=0` disables it). GPU builds still expand GGUF weights | +| GGUF F32 / F16 and block quantization | Supported. CPU keeps the supported Q, IQ, and MXFP4 blocks compressed through the matrix multiply. CUDA also keeps the supported Q8_K-activation formats compressed; other formats fall back to expansion or CPU compute. Set `VT_GGUF_KEEP_QUANT=0` to disable the direct path. See [STATUS.md](STATUS.md) for the exact format and backend coverage | | FP8 (W8A8) | The 35B ModelOpt static per-tensor projection slice is implemented; generic FP8 modes and FP8 KV remain open | -| MXFP4 / MXFP8 | Planned | +| compressed-tensors MXFP4 (W4A16) | Qwen3 dense weights load and run through the Marlin path on CUDA. Qwen3-8B is correctness-gated and benchmarked against vLLM; c1 passes the speed floor, while c2-c8 remain below it. MXFP8 compute remains open | ## Environment variables diff --git a/docs/STATUS.md b/docs/STATUS.md index 211ef56e3..c93061da2 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -93,7 +93,7 @@ are `VOID`; no product behavior changed. Supported-model registry guard (2026-08-06): the public per-architecture list in [FEATURES](FEATURES.md) is CI-bound to the C++ registry by -`scripts/check-supported-models.py` (+ mutation test), so the 30 +`scripts/check-supported-models.py` (+ mutation test), so the 37 `REGISTER_VLLM_MODEL` architectures and the FEATURES rows cannot drift. GCC 12 production-library maintenance (2026-07-31): the two known `-Werror` From 7ba9a675f49106ce6e964af3a3d5fe0c4bd46c25 Mon Sep 17 00:00:00 2001 From: Justin Card Date: Thu, 13 Aug 2026 16:41:29 -0400 Subject: [PATCH 07/55] feat(rocm): implement the vt::Backend graph-capture seam on hipGraph (W1, #332) (#473) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the vt::Backend graph-capture seam on hipGraph (W1 of #332), mirroring src/vt/cuda/cuda_backend.cu call for call. Merged with the row's performance rationale REFUTED and recorded as such. W3 measured capture at +3.2% / +0.6% / -1.0%, not the ~2.2-3x §1 predicted, and the spec's D7 plus the inline note in §1 now say so rather than leaving a live rationale for the next agent to re-derive. That refutation was reported by the contributor against their own interest, which is the behaviour this protocol exists to produce. Merged anyway on the seam argument, which is independent of the decode number: graph capture had exactly one real implementation (CUDA), and a one-implementation abstraction is unproven. hipGraph is the cheapest available second. Runtime cost today is zero -- RocmPlatform does not override support_static_graph_mode(), so nothing in the engine reaches the new code. Review verified isolation three ways: every decode-graph call site ANDs SupportsGraphCapture() with support_static_graph_mode(); rocm_backend.hip appears zero times in a CPU build's compile_commands.json; and there is zero drift on every touched file across the 194-commit gap. A mutation of the EndCaptureGraph seam signature turns the new test red, so it genuinely guards seam drift on machines with no AMD hardware. Known-broken windows-msvc-* are the PR-only arm (#584), not this change. Carried forward: #523's per-call hipMalloc/hipFree and hipStreamSynchronize are illegal under hipGraph capture and must be reconciled before this capability could ever be switched on. --- .agents/roadmap_v1.md | 1 + .agents/specs/rocm-decode-graph.md | 120 ++++++++++++++++++++-- src/vllm/platforms/rocm.cpp | 10 +- src/vt/rocm/rocm_backend.hip | 98 +++++++++++++++++- tests/vt/test_rocm_backend.cpp | 160 +++++++++++++++++++++++++++++ 5 files changed, 375 insertions(+), 14 deletions(-) diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index c335bd797..0ba99a349 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -47,6 +47,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#201](https://github.com/mudler/vllm.cpp/issues/201) | `BACKEND-ROCM` | `hipblasGemmEx` overload mismatch in `rocm_matmul_hipblaslt.hip` | bug | | [#269](https://github.com/mudler/vllm.cpp/issues/269) | `BACKEND-ROCM` | ROCm gfx1200: Gemma-3 is 48/48 exact vs two vLLM-ROCm oracles; Qwen3-0.6B exposes a deterministic cross-version near-tie, not a backend defect | verification | | [#332](https://github.com/mudler/vllm.cpp/issues/332) | `BACKEND-ROCM` | ROCm: no decode-graph capture — the hipGraph seam is unimplemented, costing ~3x decode throughput vs vLLM on gfx1200 | perf | +| [#444](https://github.com/mudler/vllm.cpp/issues/444) | `BACKEND-ROCM` | ROCm: main does not build for gfx1200/gfx1201 — `rocm_paged_attn.hip` includes rocwmma unconditionally on arch, not availability | bug | | [#125](https://github.com/mudler/vllm.cpp/issues/125) | `BACKEND-VULKAN` | Vulkan on AMD Strix Halo (gfx1151) does not load | bug | | [#203](https://github.com/mudler/vllm.cpp/issues/203) | `BACKEND-VULKAN` | Vulkan on unified memory holds TWO copies of the weights: 27B peaks at 100.8 GiB RSS and OOM-reboots a Spark | bug | | [#310](https://github.com/mudler/vllm.cpp/issues/310) | `BACKEND-VULKAN` | docs/FEATURES.md understates Vulkan: says decode 4.24 vs 4.35 where the binding figure is 4.36 vs 4.35 | bug | diff --git a/.agents/specs/rocm-decode-graph.md b/.agents/specs/rocm-decode-graph.md index 60fec59e1..743d37773 100644 --- a/.agents/specs/rocm-decode-graph.md +++ b/.agents/specs/rocm-decode-graph.md @@ -59,13 +59,16 @@ like a confound but are not — launches and compute both scale with layers, so the layer count cancels and `L/C` depends only on per-layer width (`1 / (hidden x intermediate)`). All three sit on one curve. -**It also bounds the win.** Fitting `ratio = alpha + beta / (hidden x inter)` -across the three points gives **alpha ~= 1.36x** (size-independent: kernel -quality, inductor fusion, the Triton attention path) and **beta ~= 1.65** in -units where 0.6B's `hidden x inter` = 1 — an overhead contribution of ~1.65x at -0.6B, ~0.41x at 1.7B, ~0.21x at 4B. So roughly half the 0.6B gap is fixed -overhead, and the expected outcome is all three sizes converging on **~1.36x**: -a real win, and **not parity**. A ~1.4x residual would remain. +**It also bounded the win — WRONGLY; this paragraph is the falsified +prediction, preserved verbatim rather than quietly reworded.** Fitting +`ratio = alpha + beta / (hidden x inter)` across the three points gave +**alpha ~= 1.36x** (size-independent: kernel quality, inductor fusion, the +Triton attention path) and **beta ~= 1.65** in units where 0.6B's +`hidden x inter` = 1 — an overhead contribution of ~1.65x at 0.6B, ~0.41x at +1.7B, ~0.21x at 4B. The reasoning WAS that roughly half the 0.6B gap is fixed +overhead, so the expected outcome WAS all three sizes converging on **~1.36x**. +**That did not happen.** D7 (§8) has the measurement: capture moved throughput +0-2%, indistinguishable from zero, not the predicted convergence. Treat the fit as provisional. An earlier two-point version gave `alpha ~= 1.54x`; Qwen3-4B then measured 1.46x, below that asymptote, which a curve cannot do, so @@ -258,6 +261,13 @@ mutations that must turn it red, in a scratch copy, restored byte-for-byte: fixed-overhead term is smaller than the fit implies, and redirect to D4. **Do not describe any outcome as parity**; ~1.36x is the predicted floor for this change alone. + + **SUPERSEDED.** This is the pre-registered prediction, kept verbatim per + AGENTS.md ("never trade correctness for throughput" applies equally to + quietly rewriting a call before the result). W2/W3 ran it: capture engaged + correctly on all three sizes and moved throughput 0-2%, not toward ~1.36x. + See D7 (§8) for the measurement and why. Any future W2/W3 claim on this row + starts from D7, not from this table. 6. **`GetReferenceTierHits()` == 0** in any perf measurement — structurally impossible on a discrete board, assert anyway. 7. **Records green:** `agent-preflight.sh --staged`, `check-agent-record.py`, @@ -280,6 +290,30 @@ capture, which should grow `cap` to its high-water mark. *If it does not:* direction. **W1 verifies this rather than trusting it**; a shape appearing only at capture time would be a latent, board-specific trap. +**VERIFIED in W1 on gfx1200, and it is worse than written above — there are TWO +lazy initialisations, not one.** Capturing a cold `MatmulBT` ([1,2048] x +[2048,2048]^T) fails with all three of: + +```text +vt rocm: hipMalloc: operation not permitted when stream is capturing +vt rocm: hipFree: operation not permitted when stream is capturing +vt rocm: matmul: hipblasCreate: hipblas 6 (INTERNAL_ERROR) +``` + +`hipblasCreate` was not anticipated: the handle initialises on first use in the +same path, so a pre-warm must cover **handle creation as well as workspace +growth**. Both fail loudly; neither corrupts. Running the identical GEMM once +beforehand clears both, and the captured graph then replays numerically correct +(0.409606 vs 0.409600 expected, f32). Pinned by `ROCm backend: a pre-warmed GEMM +captures and replays` in `test_rocm_backend.cpp`, which asserts the MITIGATION +rather than the hazard — a future capture-safe allocator would be an +improvement, and a test forbidding it would ratchet the wrong way. + +*Consequence for W2:* the decode-graph pre-warm must reach every GEMM shape the +captured region will execute, including the first call that creates the handle. +A shape reached only under capture still aborts, so W2's gate 4 replay count is +what proves the pre-warm was complete. + **D2 — the Qwen3-0.6B near-tie may move.** Covered by gate 3. Separate because it is the one outcome that could look like a regression while being nothing of the kind, and quietly re-baselining a golden is what "never weaken a checker" @@ -306,6 +340,78 @@ the four #41 boards are likelier-supported RDNA3/CDNA parts, but none has run this. Records say gfx1200; the other boards stay `PENDING-community` exactly as the W1 approach-(b) delta does today. +**D6 — `DestroyGraph`/`EndCapture` `Check()` the destroy where CUDA silently +ignores it.** `rocm_backend.hip`'s `DestroyGraph` and the prior-`exec_` destroy +inside `EndCapture` both `Check()` `hipGraphExecDestroy`'s return and throw on +failure; the CUDA leg ignores it. Destroying (or recapturing over) an exec still +in flight would throw on HIP where CUDA would succeed silently. Not a W1 defect +— the model-level path is not engaged (`support_static_graph_mode()` is false +until W2), and W1's tests always `Synchronize` before destroying or recapturing. +Found in the W1 review (`review-rocm-decode-graph-w1.md`, INFO-1). + +*Consequence for W2:* the decode-graph class must synchronize before destroying +or recapturing an exec on HIP — a teardown or column-change recapture that races +an in-flight replay is exactly the case this asymmetry would surface as a thrown +`Check()` instead of a silent no-op. + +**D7 — the §1/gate-5 rationale is REFUTED. W2 and W3 ran (on a follow-on +branch, not shipped in this PR) and the pre-registered convergence prediction +did not hold.** Recorded here so the next agent to open this spec does not +re-derive a closed negative result from a still-live-looking prediction. + +Same-binary A/B on gfx1200, 128in/128out batch 8, capture ON vs +`VLLM_CPP_CUDAGRAPH=0`, gate 4's `VT_DECODE_GRAPH_STATS=1` confirming capture +engaged (**126 replays over 128 decode steps**, one capture at padded size +`S=8`, not eager fallback): + +| Model | capture ON | capture OFF | delta | +|---|---|---|---| +| Qwen3-0.6B | 190-194 tok/s | 188-192 tok/s | **0-2%**, indistinguishable from zero | +| Qwen3-1.7B | 148.72 tok/s | 147.80 tok/s | +0.6% | +| Qwen3-4B | 100.22 tok/s | 101.27 tok/s | -1.0% | + +Qwen3-0.6B needed a second pass: the first same-binary run measured +3.2% +(193.67 vs 187.62), but an independent reviewer's own 3-rep A/B measured ++0.7%. The discrepancy traced to one low outlier in the original OFF arm +(reps 191.88 / 179.29 / 191.70); dropping it moves the original delta to ++1.0%, and pooling both reviewers' reps gives +2.0%. **0-2% is the honest +figure; +3.2% must not be quoted as a measured win.** + +Not batch-size dilution: single-stream is capture's best case and shows no +gain either (TPOT 13.99 ms OFF vs 13.82-14.98 ms ON). And the host/device +split says why capture isn't paying off here — it removes no host CPU work: + +```console +capture ON wall 11.31s user 13.81s sys 14.09s 247% CPU +capture OFF wall 11.36s user 13.78s sys 13.54s 240% CPU +``` + +Collapsing hundreds of per-step launches into one call should cut `sys` time +visibly; it is marginally *higher*. §1's scaling-curve argument — that roughly +half the 0.6B gap is fixed launch overhead recoverable by capture — is +directly refuted by this intervention. `cuda_backend.cu`'s +"88%-of-wall host-API overhead" figure is a CUDA measurement and does not +transfer to this board. + +Against §1's oracle figures the ratios are 2.85x / 1.93x / 1.43x versus +2.99x / 1.90x / 1.46x before capture — **unmoved**. §10's stop condition +(Qwen3-0.6B below ~2.2x) was not met; W3 stopped there per the spec's own +rule, rather than iterating blind. + +**Not a ceiling claim.** The gap is unexplained, not irreducible. Next +hypotheses, in order: (1) where the ~14 ms decode step actually goes on the +device — same-tool `rocprof` both sides, the trace D4 already flagged as +missing; (2) what burns ~2.5 cores of host CPU to produce ~50 tok/s with `sys` +exceeding wall. + +**What this leaves standing, unaffected by the refutation:** the seam itself +— mirrored call-for-call, mutation-tested, behaviour-neutral across four +models (Qwen3-0.6B/1.7B/4B dense, Qwen3.5-0.8B GDN hybrid) and a 6.7x +parameter range, capture ON vs OFF byte-identical. What died is the reason it +was built, not the code. Whether to carry an unused capability given the +refuted rationale is a product call, not a technical one — see the row's PR +discussion for that decision; it does not belong in this spec. + ## 9. Work breakdown - **W0 — DONE.** [#332](https://github.com/mudler/vllm.cpp/issues/332) filed and diff --git a/src/vllm/platforms/rocm.cpp b/src/vllm/platforms/rocm.cpp index 43ce027df..295f32bbd 100644 --- a/src/vllm/platforms/rocm.cpp +++ b/src/vllm/platforms/rocm.cpp @@ -64,9 +64,13 @@ class RocmPlatform final : public Platform { // supports_fp8() stays false: gfx942/gfx950 have hardware fp8 and rocm.py lists // "fp8" in supported_quantization (rocm.py:457-467), but we have no ROCm fp8 // kernel, and this predicate gates a fused path that would then not exist. - // support_static_graph_mode() stays false: hipGraph is the mapping and is not - // implemented. Capture that bakes a wrong address is a silent correctness - // bug, so this flips only alongside a real capture implementation. + // support_static_graph_mode() stays false: the vt::Backend hipGraph capture + // seam is implemented as of BACKEND-ROCM W1 (rocm_backend.hip; see + // .agents/specs/rocm-decode-graph.md) and the address-baking concern that + // used to justify leaving this false is now an assertion, not a worry — + // the mutate-src-then-replay test step fails if replay ever returns a + // snapshot. This flag still stays false because flipping it to engage a + // real model's decode-graph path is W2, not W1. // needs_weight_staging() stays false: this is the memory-model POLICY that // selects the device-resident forward over the host-resident reference path. // HIP's programming model does stage (hipMalloc hands back a distinct diff --git a/src/vt/rocm/rocm_backend.hip b/src/vt/rocm/rocm_backend.hip index 9239f313e..0ee462aff 100644 --- a/src/vt/rocm/rocm_backend.hip +++ b/src/vt/rocm/rocm_backend.hip @@ -19,10 +19,14 @@ // backend, written out by hand so a reader can diff the two. // // SCOPE / what is NOT here, stated plainly: -// * `SupportsGraphCapture()` stays FALSE. hipGraph exists and is the eventual -// mapping, but a graph that captures a wrong stream is a silent correctness -// bug (see .agents/ on CUDA-graph capture baking stack addresses), so it is -// not something to write blind. +// * `SupportsGraphCapture()` is TRUE as of BACKEND-ROCM W1 — see the hipGraph +// capture/replay block below and .agents/specs/rocm-decode-graph.md. The +// concern that kept it false (a capture that bakes addresses instead of +// re-executing over persistent buffers is a SILENT correctness bug) is now +// an assertion rather than a worry: the mutate-src-then-replay step in +// tests/vt/test_rocm_backend.cpp fails if replay ever returns a snapshot. +// What is NOT claimed here is the model-level path — Platform's +// support_static_graph_mode() still gates that, and flipping it is W2. // * The async-output primitives (AllocPinned / events) inherit the vt::Backend // defaults. src/vt/backend.cpp documents those as already correct for // unified-memory backends; on a discrete AMD card they are correct but @@ -218,6 +222,89 @@ class RocmBackend final : public Backend { Check(hipStreamSynchronize(AsStream(q)), "hipStreamSynchronize"); } + // --- hipGraph capture/replay (BACKEND-ROCM W1) ------------------------------ + // The hipGraph mirror of cuda_backend.cu's capture block, call for call. Every + // name below is a long-stable HIP runtime API with the same signature and + // semantics as its CUDA counterpart — the property that lets upstream compile + // csrc/ for both through hipify. + // + // Capture contract, identical to the CUDA one (the caller must honour it, else + // capture aborts): + // * every op in the region runs ASYNC on THIS stream (no Synchronize, no + // null-stream work, no host<->device blocking copies); + // * NO hipMalloc/hipFree inside the region — the scratch pool must be + // pre-warmed so every allocation is a pool hit. hipBLASLt's workspace is + // the known hazard here: LtWorkspace() in rocm_matmul_hipblaslt.hip grows + // it lazily inside the GEMM path, so a shape first seen during capture + // allocates and invalidates the capture. It fails loudly at + // hipStreamEndCapture rather than corrupting, which is the acceptable + // direction (spec .agents/specs/rocm-decode-graph.md, D1); + // * captured pointers stay valid + FIXED across replays (persistent + // buffers); only their CONTENTS change between replays. + // + // hipStreamCaptureModeThreadLocal matches CUDA's choice deliberately: it makes + // an illegal op during capture a loud failure on THIS thread rather than a + // process-wide mode change. + bool SupportsGraphCapture() const override { return true; } + void BeginCapture(Queue& q) override { + Check(hipStreamBeginCapture(AsStream(q), hipStreamCaptureModeThreadLocal), + "hipStreamBeginCapture"); + } + void EndCapture(Queue& q) override { + hipGraph_t graph = nullptr; + Check(hipStreamEndCapture(AsStream(q), &graph), "hipStreamEndCapture"); + if (exec_ != nullptr) { + Check(hipGraphExecDestroy(exec_), "hipGraphExecDestroy"); + exec_ = nullptr; + } + Check(hipGraphInstantiate(&exec_, graph, nullptr, nullptr, 0), + "hipGraphInstantiate"); + Check(hipGraphDestroy(graph), "hipGraphDestroy"); + } + void Replay(Queue& q) override { + Check(hipGraphLaunch(exec_, AsStream(q)), "hipGraphLaunch"); + } + + // Multi-graph handle API (batched decode graph): instantiate the just-captured + // stream graph and hand the exec back as an opaque handle the caller owns and + // selects per padded batch size. Unlike EndCapture, nothing is stored here. + void* EndCaptureGraph(Queue& q) override { + hipGraph_t graph = nullptr; + Check(hipStreamEndCapture(AsStream(q), &graph), "hipStreamEndCapture"); + hipGraphExec_t exec = nullptr; + Check(hipGraphInstantiate(&exec, graph, nullptr, nullptr, 0), + "hipGraphInstantiate"); + Check(hipGraphDestroy(graph), "hipGraphDestroy"); + return reinterpret_cast(exec); + } + // NOT ported: the CUDA leg's VT_BENCH_PROFILE_CONTROL block, which drives + // cudaProfilerStart/Stop around a chosen replay. A rocprofiler equivalent is + // later work and deliberately out of W1's scope (spec §2). + void ReplayGraph(Queue& q, void* graph) override { + Check(hipGraphLaunch(reinterpret_cast(graph), AsStream(q)), + "hipGraphLaunch"); + } + // hipError_t itself — not hipGraphExecDestroy specifically — is + // [[nodiscard]] at C++17+ ($ROCM_PATH/include/hip/hip_runtime_api.h:293-305, + // __HIP_NODISCARD on the `typedef enum ... hipError_t` line; the function + // declaration at line ~8321 carries no attribute of its own). CUDA's + // cudaError_t has no such attribute, so the mirrored bare call diagnoses + // here (-Wunused-value) and not there. This project's -Werror does NOT + // reach it, though: vllm_cpp_set_warnings (cmake/CompilerWarnings.cmake) + // gates -Werror on COMPILE_LANGUAGE CXX/OBJCXX/CUDA only, no HIP branch, so + // a bare call here would warn, not fail the build (verified against the + // actual compile_commands.json entry for this file, and by building a + // scratch bare-call mutation through this project's own cmake target). + // Checked anyway, matching Free/FreePinned above: a failing destroy is a + // leak this backend would rather report than swallow, independent of what + // the build flags happen to enforce. + void DestroyGraph(void* graph) override { + if (graph != nullptr) { + Check(hipGraphExecDestroy(reinterpret_cast(graph)), + "hipGraphExecDestroy"); + } + } + // THE LOAD-BEARING BOOL. This is what decides whether the portable CPU // reference tier installs for kROCM (include/vt/op_provider.h:197-201): a CPU // kernel dereferences HOST pointers, so it is correct only where host and @@ -263,6 +350,9 @@ class RocmBackend final : public Backend { bool managed_alloc_ = false; int major_ = 0; int minor_ = 0; + // Single-graph slot for the EndCapture/Replay pair. The handle API + // (EndCaptureGraph) stores nothing here — its caller owns the exec. + hipGraphExec_t exec_ = nullptr; }; // Registers every visible AMD GPU at its own Device{kROCM, i} slot, mirroring diff --git a/tests/vt/test_rocm_backend.cpp b/tests/vt/test_rocm_backend.cpp index a59ff0a52..28aa93886 100644 --- a/tests/vt/test_rocm_backend.cpp +++ b/tests/vt/test_rocm_backend.cpp @@ -284,3 +284,163 @@ TEST_CASE("the ROCm platform self-registers and is selected over CPU") { // is the reminder to update it deliberately. CHECK(rocm.get_attn_backend_priority({}).empty()); } + +// Mirrors "CUDA backend: graph capture/replay re-executes captured ops" in +// tests/vt/test_cuda_backend.cpp assertion for assertion. Same shape, same +// persistent-buffer contract, hipGraph underneath. +// +// Step 5 is the load-bearing one and the reason this test exists. Replaying +// must RE-EXECUTE the captured copy over the persistent buffers, not replay a +// snapshot of their contents — that is precisely how a decode graph picks up +// each new token's inputs. A capture that bakes values instead of addresses +// passes step 4 and fails step 5, which is the silent-correctness-bug shape +// rocm_backend.hip's scope note warns about. +TEST_CASE("ROCm backend: graph capture/replay re-executes captured ops") { + if (NoDevice()) return; + Backend& rocm = vt::GetBackend(DeviceType::kROCM); + CHECK(rocm.SupportsGraphCapture()); + + Queue q = rocm.CreateQueue(); + constexpr size_t kBytes = 64 * 1024; + + // Allocated ONCE; the pointers stay fixed across every replay below. Only + // their CONTENTS change — the capture contract. + void* src = rocm.Alloc(kBytes); + void* dst = rocm.Alloc(kBytes); + + std::vector pattern_a(kBytes, 0x11); + std::vector pattern_b(kBytes, 0x22); + std::vector back(kBytes, 0); + + rocm.Copy(q, src, pattern_a.data(), kBytes); + rocm.Memset(q, dst, 0, kBytes); + rocm.Synchronize(q); + + // Recorded, NOT executed: dst must still be zero after EndCapture. + rocm.BeginCapture(q); + rocm.Copy(q, dst, src, kBytes); + rocm.EndCapture(q); + rocm.Copy(q, back.data(), dst, kBytes); + rocm.Synchronize(q); + CHECK(back.front() == 0x00); + + // Replay #1 -> pattern A. Proves the graph ran at all. + rocm.Replay(q); + rocm.Synchronize(q); + rocm.Copy(q, back.data(), dst, kBytes); + rocm.Synchronize(q); + CHECK(back.front() == 0x11); + CHECK(back.back() == 0x11); + + // Mutate src in place (SAME address) -> replay must observe the new contents. + rocm.Copy(q, src, pattern_b.data(), kBytes); + rocm.Replay(q); + rocm.Synchronize(q); + rocm.Copy(q, back.data(), dst, kBytes); + rocm.Synchronize(q); + CHECK(back.front() == 0x22); + CHECK(back.back() == 0x22); + + // Handle variant — the path decode graphs actually take, since they keep one + // exec per padded batch size rather than a single stored graph. Captures + // into a DIFFERENT destination (dst2) than the stored-graph path above + // (dst), not the same op replayed twice: a mutation that returns the stale + // member exec_ (still the stored-graph path's Copy(dst, src)) instead of the + // freshly captured local exec must be distinguishable from the correct + // behaviour, and only fails here because the two graphs write different + // buffers. + void* dst2 = rocm.Alloc(kBytes); + rocm.Copy(q, src, pattern_a.data(), kBytes); + rocm.Memset(q, dst2, 0, kBytes); + rocm.Synchronize(q); + + rocm.BeginCapture(q); + rocm.Copy(q, dst2, src, kBytes); + void* graph = rocm.EndCaptureGraph(q); + REQUIRE(graph != nullptr); + + rocm.ReplayGraph(q, graph); + rocm.Synchronize(q); + rocm.Copy(q, back.data(), dst2, kBytes); + rocm.Synchronize(q); + CHECK(back.front() == 0x11); + + rocm.Copy(q, src, pattern_b.data(), kBytes); + rocm.ReplayGraph(q, graph); + rocm.Synchronize(q); + rocm.Copy(q, back.data(), dst2, kBytes); + rocm.Synchronize(q); + CHECK(back.front() == 0x22); + CHECK(back.back() == 0x22); + + rocm.DestroyGraph(graph); + rocm.Free(src); + rocm.Free(dst); + rocm.Free(dst2); + rocm.DestroyQueue(q); +} + +// The capture contract's allocation clause, asserted rather than assumed +// (.agents/specs/rocm-decode-graph.md D1). hipBLASLt sizes its workspace lazily +// inside the GEMM path — LtWorkspace() in rocm_matmul_hipblaslt.hip does +// hipFree+hipMalloc when a shape needs more than the current high-water mark — +// and hipblasCreate() likewise initialises on first use. Both are illegal +// mid-capture. +// +// MEASURED on gfx1200 during W1: capturing a cold GEMM fails loudly, with +// `hipMalloc: operation not permitted when stream is capturing` (and hipFree, +// and hipblasCreate INTERNAL_ERROR) — never silent corruption. Running the +// identical GEMM once beforehand grows the workspace and creates the handle, so +// the in-capture call is a pure pool hit. +// +// This case pins the MITIGATION, not the hazard: it asserts that a pre-warmed +// GEMM captures and replays correctly. Deliberately not asserting that the cold +// path throws — a future capture-safe allocator would be an improvement, and a +// test that forbade it would be a ratchet in the wrong direction. +TEST_CASE("ROCm backend: a pre-warmed GEMM captures and replays") { + if (NoDevice()) return; + Backend& rocm = vt::GetBackend(DeviceType::kROCM); + if (!rocm.SupportsGraphCapture()) return; + + Queue q = rocm.CreateQueue(); + const Device dev{DeviceType::kROCM, 0}; + constexpr int kM = 1, kN = 2048, kK = 2048; // a decode-shaped GEMM + + const std::vector ha(kM * kK, 0.01f); + const std::vector hb(kN * kK, 0.02f); + const float expect = 0.01f * 0.02f * static_cast(kK); + + void* da = rocm.Alloc(ha.size() * sizeof(float)); + void* db = rocm.Alloc(hb.size() * sizeof(float)); + void* dc = rocm.Alloc(kM * kN * sizeof(float)); + rocm.Copy(q, da, ha.data(), ha.size() * sizeof(float)); + rocm.Copy(q, db, hb.data(), hb.size() * sizeof(float)); + rocm.Synchronize(q); + + Tensor ta = Tensor::Contiguous(da, DType::kF32, dev, {kM, kK}); + Tensor tb = Tensor::Contiguous(db, DType::kF32, dev, {kN, kK}); + Tensor tc = Tensor::Contiguous(dc, DType::kF32, dev, {kM, kN}); + + // Pre-warm: grows LtWorkspace's cap and creates the hipBLAS handle. + vt::MatmulBT(q, tc, ta, tb); + rocm.Synchronize(q); + rocm.Memset(q, dc, 0, kM * kN * sizeof(float)); + rocm.Synchronize(q); + + rocm.BeginCapture(q); + vt::MatmulBT(q, tc, ta, tb); + rocm.EndCapture(q); + + rocm.Replay(q); + rocm.Synchronize(q); + std::vector back(kM * kN, 0.0f); + rocm.Copy(q, back.data(), dc, back.size() * sizeof(float)); + rocm.Synchronize(q); + CHECK(back.front() == doctest::Approx(expect).epsilon(0.01)); + CHECK(back.back() == doctest::Approx(expect).epsilon(0.01)); + + rocm.Free(da); + rocm.Free(db); + rocm.Free(dc); + rocm.DestroyQueue(q); +} From 7965f12bf4bc88c93ffeeee9877c6da9b6c46ce9 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:50:25 +0200 Subject: [PATCH 08/55] =?UTF-8?q?fix(GATE-PR-SIZE-BINARY):=20retire=20the?= =?UTF-8?q?=20fail-closed=20binary=20guard=20=E2=80=94=20a=20golden=20is?= =?UTF-8?q?=20binary,=20so=20no=20golden-bearing=20PR=20could=20merge=20(#?= =?UTF-8?q?615)=20(#619)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Retires the fail-closed binary guard in scripts/check-pr-size.py (#615), and classifies .agents/oracles/* (#668). WHY THE BINARY GUARD GOES. It errored on any path git reports as binary, with no exemption route. Parity goldens ARE binary, so no PR capturing an oracle golden could merge -- blocking #431 and every future golden across the ROCm, Tenstorrent and Nemotron lanes. The guard also contradicted this checker's own model (the SITE_ASSET note says binaries "take the `asset` class the same way any other shipped artwork does"), post-dated the golden precedent it rejected by one day (450a1b696 2026-08-10 vs 971d55063 2026-08-09), and a commit already on main (5080983fd, 16 .npy files) trips it. A rule nothing in the tree can satisfy is not enforcing a standard. Explicit classification is KEPT and still refuses an unclassified binary, now with a message naming the real defect instead of an unfixable property of the file. No allowlist was added: a roster of blessed binary paths is a shared must-write surface, which is the lock AGENTS.md forbids. This removes no size rule because none was left -- the per-class budgets were retired 2026-08-10. Nothing in this file measures a diff. Reviewed by a fresh agent that did not write it: every claimed number reproduced, all four sabotage mutations were caught (reintroduce the guard, break classification, disable the evidence contract, break the PR-required check), and seven adversarial commits confirmed the checker still refuses an unclassified binary, a checker change without evidence, and a binary posing as its own evidence. Its scope finding -- that goldens classify as `product`, so this admits binaries to src/ and scripts/ too -- is recorded in the spec as an accepted trade rather than argued away. ALSO FIXES #668. The secondary-oracle registry landed .agents/oracles/.md with no pattern here, so all eight files were unclassified and classify_path RAISED on unmodified main -- meaning any PR recording an oracle pin was refused by a required check. Takes the same `procedure` class as .agents/specs/ and .agents/claims/, which it is structurally identical to. Deliberately a pattern, not a directory exemption: a non-.md or nested path there still fails closed. device-leakage and sanitize-cpu are red on main itself and inherited by the merge; this PR touches no C++. Both are being fixed separately. Closes #615. Closes #668. --- .agents/roadmap_v1.md | 1 + .agents/specs/gate-pr-size-binary.md | 178 +++++++++++++++++++++++++++ .github/workflows/ci.yml | 9 +- scripts/check-pr-size.py | 32 +++-- tests/scripts/test_check_pr_size.py | 89 ++++++++++++-- website/README.md | 9 +- 6 files changed, 290 insertions(+), 28 deletions(-) create mode 100644 .agents/specs/gate-pr-size-binary.md diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 0ba99a349..c7202ee46 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -149,6 +149,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#560](https://github.com/mudler/vllm.cpp/issues/560) | `ROAD-V1-LTX25` | Stabilizing constants unreachable by any gate: FIVE instances found by sweep, incl. the DiT's own `norm_eps` field default | bug | | [#567](https://github.com/mudler/vllm.cpp/issues/567) | `ROAD-V1-LTX25` | `OpNameImpl` makes a SECOND file every new `OpId` must edit; collapse the enum and its names into one derived list | bug | | [#644](https://github.com/mudler/vllm.cpp/issues/644) | `ROAD-V1-LTX25` | LTX-2.5 FULL PORT campaign. Row 0 `LTX25-PROMPT-ADALN` (spec [`ltx25-prompt-adaln.md`](specs/ltx25-prompt-adaln.md)): `use_prompt_adaln_single` defaults TRUE in BOTH references and the shipped DiT carries its 18 tensors, but `ltx2_loader.cpp:988` cleared the flag unconditionally, so every render dropped the timestep-conditioned half of the prompt K/V modulation — invisible to every shape and finiteness check | feature | +| [#615](https://github.com/mudler/vllm.cpp/issues/615) | `GATE-PR-SIZE-BINARY` | `check-pr-size` fail-closes on every binary path with no exemption route, so no golden-bearing PR can merge: it blocks #431 and post-dates the golden precedent it rejects, spec [`gate-pr-size-binary.md`](specs/gate-pr-size-binary.md) | bug | | [#238](https://github.com/mudler/vllm.cpp/issues/238) | `SAMPLE-LOGPROB-TOKEN-IDS` | `logprobs_mode`: three of four modes are runtime-refused stubs | bug | | [#264](https://github.com/mudler/vllm.cpp/issues/264) | `SAMPLE-LOGPROB-TOKEN-IDS` | `logprob_token_ids`: generative scoring over an explicit token set is unported | feature | | [#365](https://github.com/mudler/vllm.cpp/issues/365) | `PERF-27B-DENSE-MARLIN-GATEUP` | 27B gap decomposed vs vLLM's own decode profile; dense W4A16 MLP bypassed the fused gate_up seam (spec `specs/perf-27b-dense-marlin-gateup.md`) | bug | diff --git a/.agents/specs/gate-pr-size-binary.md b/.agents/specs/gate-pr-size-binary.md new file mode 100644 index 000000000..f8d49abc3 --- /dev/null +++ b/.agents/specs/gate-pr-size-binary.md @@ -0,0 +1,178 @@ +# GATE-PR-SIZE-BINARY — retire the fail-closed binary guard + +**Row:** `GATE-PR-SIZE-BINARY` +**Issue:** [#615](https://github.com/mudler/vllm.cpp/issues/615) +**Base:** `origin/main` `7572b0f4e` +**Status:** ACTIVE, 2026-08-13 + +## 1. Scope + +One behavioural change to `scripts/check-pr-size.py`: remove the error raised +for a changed path that git reports as binary. + +**In scope.** The `change.lines is None` branch in `change_errors`, the two +places in that file that advertise it, the two cases in +`tests/scripts/test_check_pr_size.py` that pin it, and the three descriptions of +the guard that live outside it: the `pr-size` job comment in +`.github/workflows/ci.yml` and the font rationale in `website/README.md`. + +The `SITE_ASSET` comment is deliberately **not** edited: it never described the +guard, and it reads correctly once the guard is gone. + +**Out of scope.** Explicit path classification, the checker-evidence contract, +the role checks, and the retired line budget. None of them changes. This is not +a size rule and it does not reopen one — the per-class budgets were retired on +2026-08-10 by developer decision and stay retired. + +## 2. Anchors + +Local, not upstream — this is a project governance checker with no vLLM +counterpart. + +| What | Where | +|---|---| +| The guard | `scripts/check-pr-size.py:480-481` | +| Its advertisement | `scripts/check-pr-size.py:56-57` | +| The classifier that already protects us | `scripts/check-pr-size.py` `classify_path`, raises `ValueError` on any unclassified path | +| The `asset` class the guard contradicts | `scripts/check-pr-size.py:160-165` (`SITE_ASSET`) | +| The guard's landing commit | `450a1b696`, 2026-08-10 | +| The golden precedent it post-dates | `971d55063`, 2026-08-09 | +| Blocked work | [#431](https://github.com/mudler/vllm.cpp/pull/431) | + +## 3. Design + +`change_errors` currently short-circuits on binaries before any class-specific +rule runs: + +```python +if change.lines is None: + errors.append(f"binary change {change.path!r} is not reviewable as text") + continue +``` + +Delete the branch. Everything downstream already tolerates `lines is None` — +the checker-evidence contract tests `evidence_change.lines is None` explicitly +rather than assuming an int, so a binary simply cannot serve as mutation +evidence, which remains correct. + +Classification runs *before* this branch and is unchanged, so the ordering after +the edit is: classify (raise on unknown) → class-specific rules. An unclassified +binary is still refused, by the classifier, with the message that names the real +defect — an unclassified path — instead of one that names an unfixable property +of the file. + +**Why the guard is not load-bearing.** Its stated job is that a binary "is not +reviewable as text". True, and irrelevant: nothing else in this checker reviews +text either. It classifies paths and enforces an evidence contract. The property +that keeps an unreviewable blob out of the tree is that it must first earn a +class, and that check is the one being kept. + +**Why not an exemption list instead.** An allowlist of blessed binary paths is a +shared must-write surface — every golden-bearing PR would edit it, which is +precisely the lock AGENTS.md forbids. Classification already partitions these +paths by *where they live*, which is the derived-at-read-time shape. + +## 4. Risks and decisions + +| Risk | Assessment | +|---|---| +| **The retirement is wider than the problem it solves** | Accepted, and the operator should merge knowing it. Goldens live under `tests/`, so they classify as `product` — permitting them necessarily permits binaries across `src/`, `scripts/`, `tools/` and `benchmarks/`, and the same delete admits them to `procedure`, `project_record`, `ci` and `vendored_dependency` too. Measured before/after, not inferred: an opaque blob at `src/vllm/blob.dat`, and binary bytes replacing `.agents/workflow.md` or `ci.yml`, were refused at BASE and pass at HEAD. What still refuses them: `.gitignore` eats the realistic accidental blob, every change arrives on a PR, and a binary in a diff is maximally visible to a reviewer. Judged an acceptable trade because the guard could not tell a golden from a rootkit, so keeping it meant blocking correctness evidence — which a correctness-first project should not do. | +| A narrower fix was available | True, and it is the honest limit of this change. A derived regex class for `tests/parity/goldens/…` would be the same derived-at-read-time shape this spec praises in `SITE_ASSET`, and would not admit binaries to `src/`. It was not taken because it leaves the `asset` and `website/static/` cases still refused and would need a second class the next time a lane needs a binary — but it is a real alternative, and §3's earlier appeal to the `SITE_ASSET` comment over-generalized: that comment speaks for `website/static/` only, not for every class. | +| This reads as weakening a gate to go green | It is a deliberate retirement, argued in the commit message per the no-waiver-registry rule, not a repair of a red run. No PR of mine is unblocked by it; the beneficiaries are #431 and future golden work. | +| Goldens become unreviewable in practice | Unchanged by this edit — they are unreviewable as text either way. Golden provenance is enforced by the parity gates and the oracle-identity requirements, which is where it belongs. | +| The retirement is silently reversed later | The RED-first test in §5 asserts the new behaviour directly, so a reintroduction turns it red. | + +## 5. Tests + +RED-first, in `tests/scripts/test_check_pr_size.py`: + +1. `test_a_classified_binary_is_accepted` — a binary at a classified path + (`tests/parity/goldens/.../our_ids.npy`, and a `website/static/` asset) + produces **no** error. **RED before the change** for the intended reason: + the guard fires. +2. `test_an_unclassified_binary_is_still_refused` — a binary at an unclassified + path still errors, and the error names classification, not binaryness. This + is the guard rail that keeps the retirement scoped. Green both before and + after (the classifier raises first), so it is a regression pin, not evidence. +3. `test_retiring_the_budget_did_not_retire_the_other_contracts` **drops its + binary clause entirely**, because cases 1 and 2 now carry that coverage + directly and duplicating it inside a multi-contract test would hide which + contract failed. Its classification and checker-evidence clauses are + untouched and still bite. (Drafted as "rewrite the clause"; deleting it was + the better shape once 1 and 2 existed, and this line records what shipped.) +4. Delete `test_binary_changes_fail_closed_instead_of_becoming_free`, which + states the retired rule and cannot survive it. + +## 6. Gates + +- `python3 -m pytest tests/scripts/test_check_pr_size.py` green, with case 1 + shown RED on the unmodified checker first. +- `python3 scripts/check-pr-size.py --base --head ` classifies this + PR's own change without error. +- `scripts/agent-preflight.sh --staged` clean. +- The checker-evidence contract must be satisfied *by this very PR*: it changes + a `governance_checker`, so it must ship executable mutation evidence in + `tests/scripts/test_check_pr_size.py`. It does. + +## 7. Evidence + +**RED before**, on the unmodified checker, for the intended reason — all four +subtests of `test_a_classified_binary_is_accepted` die on the guard: + +``` +AssertionError: Lists differ: + ["binary change 'website/static/fonts/sora-700.woff2' is not reviewable as text"] != [] +4 failed, 43 passed, 119 subtests passed +``` + +`test_an_unclassified_binary_is_still_refused` was already green here, as §5 +predicted — classification runs first, so it is a rail and not the evidence. + +**GREEN after:** `43 passed, 123 subtests passed`. Wider governance suite +(`test_check_pr_size` + `test_agent_record`): `92 passed, 125 subtests`. + +**The retirement does what it is for**, checked directly against a +golden-bearing change: + +``` +golden-bearing PR errors -> NONE (was: 2 refusals) +unclassified binary -> ["unclassified repository path 'junk/blob.bin'"] +``` + +**The checker accepts its own diff**, satisfying the evidence contract it +enforces on `governance_checker` paths: +`check-pr-size.py --base 7572b0f4e --head ` → `OK`, exit 0. +`check-commit-trailers.py` → `OK: commit trailer contract`, exit 0. + +**Stop condition §8 checked, not assumed.** `python3 -m pytest tests/scripts/ +--ignore=tests/scripts/test_cpu_kernel_bench.py`, run sequentially on both +trees: + +| tree | result | +|---|---| +| HEAD | `9 failed, 1276 passed, 3 skipped, 1510 subtests` | +| BASE `7572b0f4e` | `9 failed, 1275 passed, 3 skipped, 1506 subtests` | + +The **failure sets are identical** — the same six `test_gen_vulkan_spirv` shader +subfailures, the same `test_check_windows_portability` subfailure, and the same +`test_mlx_system_headers` and `test_now_render` failures. All pre-existing; this +change adds no failure. The `+1 passed / +4 subtests` delta is exactly this +change: one test deleted, two added, four new subtests. + +`test_cpu_kernel_bench.py` fails collection on unmodified main too (it wants a +built benchmark binary). `test_cpu_x86_llamacpp_floor` is order-dependent under +a loaded parallel run and passes sequentially on both trees. + +An earlier draft of this section recorded `8 failed, 20 passed, 2 skipped` for +this check. That was a **three-file subset** mislabelled as the whole suite; no +stated command produced it. The substantive claim it was offered for — identical +failure sets before and after — is unchanged and is what the table above shows. + +## 8. Stop conditions + +- If removing the branch turns any other case in the suite red for a reason not + named in §5, stop — that is a load-bearing use of the guard this spec did not + find, and the design in §3 is wrong. +- If the checker cannot classify its own diff after the edit, stop. +- If a reviewer judges that classification alone does not carry the protection, + stop and escalate rather than widening the change. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dbf80e43..59fdd0bf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -412,10 +412,11 @@ jobs: echo "OK: all new commits carry FOLLOWING_AGENTS_PROTOCOL." pr-size: # PR-only. The per-class LINE BUDGETS this job used to enforce were retired - # 2026-08-10 (see scripts/check-pr-size.py); the job name is kept because it - # is a required check. What it enforces now: explicit path classification, - # the fail-closed binary guard, the checker-change mutation-evidence - # contract, and the role check that keeps product paths on a PR. + # 2026-08-10, and the fail-closed BINARY GUARD was retired 2026-08-13 (see + # scripts/check-pr-size.py for both); the job name is kept because it is a + # required check. What it enforces now: explicit path classification, the + # checker-change mutation-evidence contract, and the role check that keeps + # product paths on a PR. Nothing here measures the size of a diff. if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: diff --git a/scripts/check-pr-size.py b/scripts/check-pr-size.py index 0618320ab..1b3eebaf0 100755 --- a/scripts/check-pr-size.py +++ b/scripts/check-pr-size.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 """Enforce explicit path classification and the checker-evidence contract. -The per-class LINE BUDGETS this file used to enforce were retired 2026-08-10; -see the note where they stood. What remains: every changed path must classify -explicitly, binaries fail closed, a governance-checker change must carry -executable mutation evidence, and product paths must arrive on a PR.""" +The per-class LINE BUDGETS this file used to enforce were retired 2026-08-10, +and the fail-closed BINARY GUARD was retired 2026-08-13; see the notes where +each stood. What remains: every changed path must classify explicitly, a +governance-checker change must carry executable mutation evidence, and product +paths must arrive on a PR. Nothing here measures the size of a diff.""" from __future__ import annotations @@ -54,8 +55,8 @@ # # Everything else this checker enforces is unchanged and is NOT a size rule: # explicit path classification (no blanket directory exemptions), the -# fail-closed binary guard, the checker-change mutation-evidence contract, and -# the role checks that keep product paths on a PR. +# checker-change mutation-evidence contract, and the role checks that keep +# product paths on a PR. # Machine-generated artifacts, each of which MUST be (a) emitted by a tracked # generator in this repository, (b) reproduced byte-for-byte by a gate that runs @@ -173,6 +174,12 @@ # this. A claim in its own file has one writer and cannot collide. Classified # with the other per-row records it now resembles. CLAIM = re.compile(r"\.agents/claims/[A-Za-z0-9_.-]+\.md\Z") +# One file per secondary oracle (AGENTS.md, "When vLLM has no implementation"). +# Same shape and therefore the same class as SPEC and CLAIM: a per-key record +# globbed for reading, deliberately NOT a shared table every change must write. +# Absent until #668 -- the registry landed with no pattern here, so every one of +# its files was unclassified and a required check refused any PR touching a pin. +ORACLE = re.compile(r"\.agents/oracles/[A-Za-z0-9_.-]+\.md\Z") # Retired state evidence, moved wholesale under completed/ when history became # git. It is archived evidence, classified like every other completed record. COMPLETED_STATE_EVENT = re.compile( @@ -367,6 +374,7 @@ def classify_path(path: str) -> str: path in PROCEDURE_FILES or SPEC.fullmatch(path) or CLAIM.fullmatch(path) + or ORACLE.fullmatch(path) or COMPLETED.fullmatch(path) or COMPLETED_STATE_EVENT.fullmatch(path) ): @@ -477,9 +485,15 @@ def change_errors( except ValueError as exc: errors.append(str(exc)) continue - if change.lines is None: - errors.append(f"binary change {change.path!r} is not reviewable as text") - continue + # NO BINARY GUARD. A `lines is None` path used to error here as "not + # reviewable as text" (GATE-PR-SIZE-BINARY, #615). Retired 2026-08-13: + # it made every golden-bearing PR unmergeable by construction -- parity + # goldens are binary by nature -- while adding nothing, because the + # protection that matters is classification, which runs directly above + # and still refuses any path without a class. The classifier was always + # built to give binaries a class; see the `SITE_ASSET` note. Note + # `lines is None` still matters downstream: the evidence contract below + # tests it, so a binary cannot serve as mutation evidence. if path_class == "governance_checker": evidence = recognized_evidence(change.path) evidence_change = changed_paths.get(evidence) diff --git a/tests/scripts/test_check_pr_size.py b/tests/scripts/test_check_pr_size.py index f66ac0f58..e14cdae75 100755 --- a/tests/scripts/test_check_pr_size.py +++ b/tests/scripts/test_check_pr_size.py @@ -341,27 +341,92 @@ def test_no_line_budget_is_enforced_for_any_class(self) -> None: self.assertEqual(checker.change_errors([huge]), []) def test_retiring_the_budget_did_not_retire_the_other_contracts(self) -> None: - """The three rules that share this checker must still bite. + """The rules that share this checker must still bite. - Dropping a size gate is not licence to drop classification, the binary - guard, or checker-evidence with it, which is exactly the kind of thing - that goes unnoticed when a constant is deleted. + Dropping a size gate is not licence to drop classification or + checker-evidence with it, which is exactly the kind of thing that goes + unnoticed when a constant is deleted. """ unknown = checker.ChangedPath("no/such/surface.txt", 1, 0) self.assertTrue(checker.change_errors([unknown])) - # Asserted on the ERROR, not its wording: this is a regression guard - # that must hold on both sides of the retirement, so it must not be - # coupled to a message string that the retirement itself reworded. - binary = checker.ChangedPath("assets/logo.png", None, None) - self.assertTrue(checker.change_errors([binary])) lone_checker = self.change("scripts/check-pr-size.py", 10) self.assertTrue( any("mutation evidence" in e for e in checker.change_errors([lone_checker])) ) - def test_binary_changes_fail_closed_instead_of_becoming_free(self) -> None: - errors = checker.change_errors([checker.ChangedPath("docs/image.png", None, None)]) - self.assertTrue(any("binary" in error for error in errors), errors) + def test_every_secondary_oracle_file_classifies(self) -> None: + """One file per oracle must classify (GATE-PR-SIZE-BINARY follow-on, #668). + + RED before the fix on EVERY tracked file under .agents/oracles/: the + secondary-oracle registry landed with no pattern in the checker, so a + required check refused any PR that recorded a pin -- which is the one + thing the registry exists to make cheap. Asserted on the whole tracked + set rather than a sample, so a ninth oracle added without a class is + caught here and not in someone's PR. + """ + tracked = subprocess.run( + ["git", "ls-files", ".agents/oracles/"], + capture_output=True, text=True, check=True, cwd=checker.ROOT, + ).stdout.split() + self.assertTrue(tracked, "expected tracked .agents/oracles/ files") + for path in tracked: + with self.subTest(path=path): + self.assertEqual(checker.classify_path(path), "procedure") + + def test_oracles_is_a_pattern_not_a_blanket_directory_exemption(self) -> None: + """The class is earned by shape, not by living under .agents/oracles/. + + AGENTS.md forbids hiding mutable files behind a blanket directory + exemption, so a non-.md file or a nested path there must still fail + closed rather than inherit `procedure`. + """ + for path in ( + ".agents/oracles/pin.txt", + ".agents/oracles/vllm.json", + ".agents/oracles/nested/dir.md", + ): + with self.subTest(path=path): + with self.assertRaises(ValueError): + checker.classify_path(path) + + def test_a_classified_binary_is_accepted(self) -> None: + """A binary at a classified path is not an error (GATE-PR-SIZE-BINARY, #615). + + RED before the retirement: `change_errors` short-circuited on every + `lines is None` path, so a captured parity golden could not reach main + at all and #431 was unmergeable by construction. The classifier was + always built to give binaries a class -- the `SITE_ASSET` comment says + so in as many words -- and the guard refused them anyway. + """ + for path in ( + "tests/parity/goldens/qwen3_greedy_0_6b/our_ids.npy", + "tests/parity/goldens/qwen35_greedy_0_8b/neartie_gap_mnats.npy", + "tests/parity/goldens/qwen3_greedy_0_6b/p0_prompt.i32", + "website/static/fonts/sora-700.woff2", + ): + with self.subTest(path=path): + # Asserted through classify_path rather than a hardcoded class + # so this stays true if a golden is later reclassified. + checker.classify_path(path) + binary = checker.ChangedPath(path, None, None) + self.assertEqual(checker.change_errors([binary]), []) + + def test_an_unclassified_binary_is_still_refused(self) -> None: + """Retiring the guard must not turn an unclassified path into a free one. + + This is the rail that keeps the retirement scoped: the protection was + never "binaries are unreviewable", it was "every path earns a class". + Green on both sides of the change -- classification runs first -- so it + is a regression pin, not the evidence for the retirement. + """ + errors = checker.change_errors([checker.ChangedPath("no/such/surface.png", None, None)]) + self.assertTrue(errors) + # The message must name the real defect. "Not reviewable as text" told + # the author to fix something about the file; an unclassified path is + # something they can actually act on. + self.assertFalse( + any("not reviewable as text" in error for error in errors), errors + ) def test_checker_change_requires_its_recognized_mutation_test(self) -> None: changed = [ diff --git a/website/README.md b/website/README.md index fc5da46f4..e4601c94f 100644 --- a/website/README.md +++ b/website/README.md @@ -57,9 +57,12 @@ The site loads nothing from another host. No CDN, no font service, no analytics. Typography is font stacks only: a serif does the display work and the monospace carries labels, kickers and table headers. -Shipping woff2 files was considered and rejected: `check-pr-size.py` refuses -binary changes outright ("binary change has no reviewable line budget"), and a -waiver is for one-time migrations with an expiry, not for a permanent asset. +Shipping woff2 files was considered and rejected, though no longer for the +original reason: `check-pr-size.py` used to refuse binary changes outright, and +that guard was retired 2026-08-13 (#615), so a woff2 under `website/static/` +would now pass the gate as an `asset`. It stays rejected on its own merits — +a self-hosted face is bytes every visitor pays for and a licence someone has to +keep tracking, and the stacks below already carry the design. The logos and favicon are **mounted** from the repository's `assets/`, not copied here, the same rule as `docs/`: a logo refreshed upstream must not From 782264c0c7e513bb8ba869b905881516d77faa7f Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:10:49 +0200 Subject: [PATCH 09/55] spec(ENG-UPSTREAM-OMNI-PIN): pin vLLM-Omni onto the oracle registry (#633) (#635) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL The pin protocol covered one repository, so every architecture living only in `vllm-project/vllm-omni` had no oracle it could be gated against. #650 landed the oracle registry mid-flight and superseded this row's original design (a second pin block inside `upstream-sync.md`), so the spec was REWRITTEN onto the registry rather than patched, and says so at the top. `.agents/oracles/vllm-omni.md` is deliberately untouched: it already reads `pin = UNPINNED`, `gateable = no`, `evidence = #633`, which is exactly true. `upstream-sync.md` now points at the registry and carries only what the registry file cannot state on its own — that the two pins may legitimately DISAGREE (vllm-omni requires vLLM 0.27.0+ against our 0.26.0.dev0 parity pin), that an omni-gated number is therefore never evidence about the core pin's surface, and that an omni pin advance does not re-open the vLLM-side binding grids provided the omni oracle is isolated in its own virtualenv. `core_commit_used` is deliberately NOT added to the checker-enforced schema: the pin is UNPINNED, so there is no value to record. It lands with the measurement. `ENGINE_ROWS` 153 -> 154, with mutation evidence that is load-bearing against the exact bad merge this branch hit: main bumped the same constant 152 -> 153 for a different row, so both sides read 153 and the merge looked clean. Drop the row, rewind the constant and the rollup until every count agrees, and 1 of 53 tests fails: that one. NO product code. This is a spec and its records. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/engine-matrix.md | 5 +- .agents/roadmap_v1.md | 1 + .agents/specs/upstream-omni-pin.md | 171 +++++++++++++++++++++++++++++ .agents/upstream-sync.md | 22 ++++ scripts/check-agent-record.py | 10 +- tests/scripts/test_agent_record.py | 22 ++++ 6 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 .agents/specs/upstream-omni-pin.md diff --git a/.agents/engine-matrix.md b/.agents/engine-matrix.md index b9bc071e8..ae56ecf48 100644 --- a/.agents/engine-matrix.md +++ b/.agents/engine-matrix.md @@ -44,11 +44,11 @@ forensics: roadmap_v1.md and the parity ledger. | Sampling and generation | 15 | 4 | 2 | 0 | 0 | 4 | 0 | 1 | 4 | | Structured output and tools | 7 | 0 | 4 | 0 | 0 | 2 | 0 | 0 | 1 | | Speculative decoding | 21 | 0 | 0 | 1 | 0 | 5 | 0 | 4 | 10 | -| Serving, API, CLI, library | 33 | 10 | 2 | 0 | 2 | 10 | 2 | 3 | 4 | +| Serving, API, CLI, library | 34 | 10 | 2 | 0 | 3 | 10 | 2 | 3 | 4 | | LoRA and adapters | 2 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | | Long context and attention | 10 | 5 | 0 | 0 | 1 | 0 | 1 | 0 | 3 | | Loading, tokenizer, config | 10 | 3 | 3 | 0 | 0 | 1 | 1 | 1 | 1 | -| **Total** | **153** | **35** | **17** | **4** | **9** | **30** | **8** | **11** | **38** | +| **Total** | **154** | **35** | **17** | **4** | **10** | **30** | **8** | **11** | **38** | ## Engine core and scheduling @@ -218,6 +218,7 @@ claims it. | `ENG-TRAILER-MERGE-ARTIFACTS` | The trailer gate rejects CORRECT commits because of paragraph placement, and that is why `main` is red on `agent-record`. `check-commit-trailers.py` reads trailers via `git interpret-trailers --parse`, which treats ONLY the final paragraph as the block; GitHub appends `Co-authored-by:` as a SEPARATE trailing paragraph on a squash merge, so a complete correct block becomes invisible and the gate reports it missing. MEASURED: piping `git show -s --format=%B dbd0d51c` into `git interpret-trailers --parse` prints nothing but the co-author line, and 13 of the last 30 commits on `main` fail the check -- unnoticed only because those runs were cancelled (#274), which HID the defect rather than causing it. FIX: fuse consecutive trailing TRAILER-SHAPED paragraphs before parsing. Nothing is relaxed -- the block must still exist, the marker must still sit above it, each declaration must still appear exactly once, and an AI co-author is still forbidden; the block is merely FOUND where the merge tool left it. A prose paragraph still terminates it. REJECTED IN FLIGHT and recorded because it is the more instructive half: a first attempt also collapsed identical duplicate trailers to fix the multi-commit-squash shape, which relaxes the uniqueness rule an existing test already pins. Rewriting that assertion to suit the change is what AGENTS.md forbids, and the distinction is real -- a doubled block is genuinely malformed and fixable at source, whereas the co-author case is a correct commit defeated by the parser. Reverted in full. SCOPE LIMIT, stated rather than implied: this fixes ONE of five observed shapes. `f64f2b71` (bot co-author) is a REAL violation the parse had been hiding and now correctly fails; `87308dea` (GitHub's `---------` separator), `b8293c88` (squash doubled the block) and `b580452d` (merge button, no trailers) stay red by design. Closing those is a merge-method change, not a checker change | T0 | NO vLLM analogue -- local protocol machinery, so the mirror rule does not apply and there is no upstream `file:line` to port from. Governed by `AGENTS.md` §"Changing the rules or a checker" | `scripts/check-commit-trailers.py:60` (`join_trailing_trailer_paragraphs`, `_is_trailer_paragraph`, and the fused `parsed_trailers`) | `tests/scripts/test_check_commit_trailers.py:1` 21 cases -- the RED-BEFORE appended-co-author case plus four GUARDS that keep the fusion bounded (doubled block still fails, contradictory declarations still fail, a no-trailer merge message still fails, prose after the block still fails), all four green before and after; closure [parity-ledger.md#L941](parity-ledger.md#L941) | [trailer-merge-artifacts.md](specs/trailer-merge-artifacts.md); issue [#406](https://github.com/mudler/vllm.cpp/issues/406) | `DONE` | `157080c8` | | `ENG-FORGE-COAUTHOR` | The forbidden-AI-trailer rule was catching ATTRIBUTION rather than an authorship claim, which is why bot-opened PRs red `main` on merge. GitHub composes the squash message itself and appends the account that opened the PR — `Co-authored-by: localai-org-maint-bot <...@users.noreply.github.com>` — and most PRs here are opened by a bot, so nearly every squash trips the AI-identity check. Real instance `f64f2b71`, invisible until #406 repaired the parse, which is why it reads as a new failure and is not one. The rule exists so an AI cannot claim it WROTE the code, and that stays; GitHub is recording who pressed the button, and the AI-involvement claim is already carried separately by `AI-Assisted` and `Assisted-by` in the same block. FIX: accept a `Co-authored-by` at a GitHub account noreply address even when the name matches an AI identity token, keyed on the FORGE'S OWN DOMAIN rather than the name so the exemption cannot be borrowed. A hand-written `Co-authored-by: Claude ` still fails; `Signed-off-by` is excluded from the exemption entirely, because a sign-off is a legal assertion about provenance rather than attribution. `AGENTS.md` records the same distinction in the same change so prose and checker cannot drift | T0 | NO vLLM analogue -- local protocol machinery, so the mirror rule does not apply and there is no upstream `file:line` to port from. Governed by `AGENTS.md` §"Changing the rules or a checker" | `scripts/check-commit-trailers.py:38` (`FORGE_ACCOUNT_EMAIL` and the forbidden-trailer skip) | `tests/scripts/test_check_commit_trailers.py:1` 25 cases -- the RED-BEFORE forge-bot case plus THREE guards that matter more than the relaxation because this LOOSENS a rule: a hand-written AI co-author still fails, `Signed-off-by` at the same noreply address still fails, and a human co-author still passes; all three green before and after. Real commit `f64f2b71` re-verified per commit | [forge-coauthor-attribution.md](specs/forge-coauthor-attribution.md); issue [#418](https://github.com/mudler/vllm.cpp/issues/418) | `ACTIVE` | `CLAIM-ENG-FORGE-COAUTHOR` | | `ENG-NOW-DERIVED` | W1-W5 remove the per-row `.agents/NOW.md` write: each moved row's own spec carries `## Now`, `scripts/now.py` renders the live roster offline-first, and the digest cannot regrow a row table. Implementation merge `dbd0d51c`; progressive legacy-spec backfill is the selected compatibility policy, not remaining work. Runtime/performance/parity are `VOID` because this is local protocol machinery | T0 | No vLLM analogue; governed by `AGENTS.md` §Changing the rules or a checker | `scripts/now.py:163`; `scripts/check-doc-checkpoint.py:181`; `scripts/check-now-current.py:57` | `tests/scripts/test_doc_checkpoint.py:142`; `tests/scripts/test_now_render.py:34`; `tests/scripts/test_check_now_current.py:1`; closure [parity-ledger.md#L939](parity-ledger.md#L939) | [now-derived.md](specs/now-derived.md); issue [#374](https://github.com/mudler/vllm.cpp/issues/374) | `DONE` | `dbd0d51c` | +| `ENG-UPSTREAM-OMNI-PIN` | Pin `vllm-project/vllm-omni`, the SEPARATE repository holding every omni-only architecture (MiniMax-H3, LTX-2.5, and the ~40-module TTS family incl. IndexTTS-2.5). Its pin now lives in the oracle registry [`.agents/oracles/vllm-omni.md`](oracles/vllm-omni.md) landed by #650, which records the truth today: `pin = UNPINNED`, `gateable = no`, `evidence = #633`. So the RECORD is no longer missing; the PIN is, and with it any oracle-run gate for those lanes. What this row still owes is what the registry file cannot state on its own: the two pins may legitimately DISAGREE (vllm-omni requires vLLM 0.27.0+ against our 0.26.0.dev0 parity pin, so forcing them equal would move every gated row to suit a lane that touches none), an omni-gated number is therefore labeled with BOTH commits and is NEVER evidence about the core pin's surface, and an omni pin advance does NOT re-open the vLLM-side binding grids provided the omni oracle is isolated in its own virtualenv and touches neither `${VLLM_SOURCE}` nor the environment the parity pin measures itself from. NOTE this spec was rewritten mid-flight: its first draft proposed a second pin block inside `upstream-sync.md`, which #650 superseded with the one-file-per-oracle registry, and the row now proposes no record format at all | T1 | `vllm-project/vllm-omni` `vllm_omni/model_executor/models/registry.py`; source-audited at `a4ea67a2` (v0.26.0) and at `bbe6ccc512a404a2df8c977ea29003002f2683e8` (#609, #610) -- neither is a pin, both read source | none -- protocol change, no product code | `tests/scripts/test_agent_record.py` `test_omni_pin_row_is_inside_the_engine_ratchet` (RED-first vs the exact bad merge: row dropped, `ENGINE_ROWS` rewound, rollup rewound, every count agreeing -- 1 of 53 tests fails and it is that one). W3-W5 assertions owed | [upstream-omni-pin.md](specs/upstream-omni-pin.md); issue [#633](https://github.com/mudler/vllm.cpp/issues/633) | `READY` | unassigned | | `SERVE-CLI-CHAT` | Interactive `chat` and `complete` commands against a running OpenAI-compatible server, plus preservation of the existing local-model completion invocation | T1 | registration `vllm/entrypoints/cli/main.py:17-37,73-98`; client/model resolution + stream shaping `vllm/entrypoints/cli/openai.py:30-100`; chat `:155-234`; complete `:237-312` at `5559679229` | current in-process completion only `examples/cli/main.cpp:1-207`; remote command implementation absent | C-ABI stream baseline `tests/capi/test_capi.cpp:567-711`; chat-template baseline `tests/capi/test_chat_prompt.cpp:37-89`; command/fake-server tests absent | [cli-chat-complete.md](specs/cli-chat-complete.md) | `ANCHOR-BACKFILL` | `CLAIM-SERVE-CLI-CHAT-SPIKE` | | `SERVE-RECIPE-ARGS` | Accepted-and-inert serve arguments: an enumerated table of flags that published recipes pass, that are no-ops for this engine, and that must therefore not abort argument parsing. Not a catch-all — anything unlisted still aborts, and mirrored validation still fires. Found by the 2026-08-13 recipe-surface sweep: `vllm-serve` rejects unknown arguments (`src/vllm/entrypoints/openai/server_main.cpp:440`), so `--enable-auto-tool-choice` (89/157 official recipes) and `--trust-remote-code` (82/157) stop the server before model load even though neither means anything here — including for models we ship token-exact and gated | T1 | `vllm/entrypoints/openai/cli_args.py:105` (`enable_auto_tool_choice` default), `:395` (requires `--tool-call-parser`, a `TypeError` otherwise — mirrored, not dropped); threading `vllm/entrypoints/openai/api_server.py:426,441,529,544` at `5559679229` | `src/vllm/entrypoints/openai/server_main.cpp:289` (`kAcceptedInertArgs`, the enumerated table), `:312` (`FindAcceptedInertArg` — returns `nullptr` for anything unlisted, so the existing `unknown argument` abort is untouched), `:505` (the parse branch + the per-flag notice), `:560` (the mirrored `cli_args.py:395` validation); `docs/USAGE.md` § "Accepted for recipe compatibility" | `tests/vllm/entrypoints/openai/test_serve_recipe_args.cpp:141` (listed flags reach model load), `:165` (an unlisted flag STILL aborts — the load-bearing case), `:188` (the mirrored `cli_args.py:395` refusal), `:212` (the per-flag notice); registered `tests/CMakeLists.txt:877`. 4 cases / 58 asserts GREEN; RED-first against the pre-change binary (17 failed, `Status: FAILURE!`). Each case re-execs the binary into the REAL `VllmServerMain`. MUTATION PROVEN in a scratch copy: widening `FindAcceptedInertArg` into a catch-all turns exactly `:165` RED (8 asserts) and leaves the other three GREEN | [serve-recipe-args.md](specs/serve-recipe-args.md) | `ACTIVE` | `CLAIM-SERVE-RECIPE-ARGS` | | `SERVE-POOLING-ENDPOINTS` | Embeddings, pooling, score, rerank, classify HTTP surface (`/v1/embeddings`, `/pooling`, `/score`, `/rerank`, `/classify`). **SPIKED 2026-07-28 (`CLAIM-POOLING`):** the whole pooling task class is scoped in [pooling-task-class.md](specs/pooling-task-class.md). **`/v1/embeddings` LIVE 2026-08-08 (ARCH-ONE-SURFACE ROW 6, `CLAIM-EMBEDDINGS-ONE-SURFACE`):** task-conditional registration (embed/api_router.py:22-28 mirror; the route exists ONLY on a pooling-model server, and the generate routes do not — both directions socket-404-pinned), OpenAI request/response shape (string-or-array input; `dimensions`/base64/token-arrays are named-residual 400s), handler drives the ONE engine path (`LoadedEngine -> LLMEngine::embed -> registry forward -> PoolingRunner`) — the same path `vllm_embed` (ABI v15) drives. RESIDUALS: `/pooling`, `/score`, `/rerank`, `/classify` (need a classify arch) | T2 | `vllm/entrypoints/pooling/embed/api_router.py:28`; `vllm/entrypoints/pooling/embed/protocol.py:34,173-185`; `vllm/entrypoints/pooling/scoring/api_router.py:37,71`; `vllm/entrypoints/pooling/classify/api_router.py:26` | `src/vllm/entrypoints/openai/api_server.cpp` `handle_embeddings` + the `if (embedder_)` route gate; `examples/server/main.cpp` pooling task dispatch | `tests/vllm/entrypoints/openai/test_api_server.cpp` embeddings section (dispatch shape + socket smoke + BOTH-direction 404 pins) | [embeddings-one-surface.md](specs/embeddings-one-surface.md) | `ACTIVE` | `CLAIM-EMBEDDINGS-ONE-SURFACE` | diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index c7202ee46..91219dbf9 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -90,6 +90,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#322](https://github.com/mudler/vllm.cpp/issues/322) | `ENG-RELEASE-BINARIES` | Release handoff collides with tracked checkout `assets` directory | bug | | [#406](https://github.com/mudler/vllm.cpp/issues/406) | `ENG-TRAILER-MERGE-ARTIFACTS` | The trailer gate fails on how commits LAND: GitHub's Co-authored-by displaces the trailer block | bug | | [#467](https://github.com/mudler/vllm.cpp/issues/467) | `ENG-TRAILER-MERGE-ARTIFACTS` | `agent-preflight.sh` prints "trailer suites ok / All gates green" over a range where `check-commit-trailers.py` reds on 3 rules: it runs the checker's own MUTATION SUITE, never the checker over the actual range, so git-generated merge messages are the one commit class nothing checks | bug | +| [#633](https://github.com/mudler/vllm.cpp/issues/633) | `ENG-UPSTREAM-OMNI-PIN` | vLLM-Omni has no parity pin: H3 W3+, LTX-2.5 and ~40 omni-only architectures (the whole TTS family included) cannot be gated against any oracle | feature | | [#466](https://github.com/mudler/vllm.cpp/issues/466) | `GATE-27B-FP8-TOWER-GOLDEN` | The SACRED 27B gate cannot execute the FP8 tower: it pins unsloth@890bdef7 (zero FP8 tensors) while every fp8 lever targets nvidia@0893e160 | bug | | [#470](https://github.com/mudler/vllm.cpp/issues/470) | `GATE-27B-FP8-TOWER-GOLDEN` | `PackedGdnDecodeEnvSelected` mirrors only the ENV; the real predicate also requires `in_proj_qkv_fp8.Empty()`, so the 27B gate throws for the wrong reason on any fp8 tower | bug | | [#476](https://github.com/mudler/vllm.cpp/issues/476) | `GATE-27B-FP8-TOWER-GOLDEN` | The 27n fp8-tower arm cannot see a dequant fallback on `out_proj_fp8` or the four `self_attn.*_proj_fp8`: `GdnFp8InProjDebugStats` counts GDN `in_proj` only, and `6603356a` proves that defect is token-invisible | bug | diff --git a/.agents/specs/upstream-omni-pin.md b/.agents/specs/upstream-omni-pin.md new file mode 100644 index 000000000..c6aacadfa --- /dev/null +++ b/.agents/specs/upstream-omni-pin.md @@ -0,0 +1,171 @@ +# SPEC — pinning vLLM-Omni + +**Row:** `ENG-UPSTREAM-OMNI-PIN` +**Issue:** [#633](https://github.com/mudler/vllm.cpp/issues/633) +**State:** `READY` — protocol change, no product code. + +## Scope + +Make the vLLM-Omni oracle *pinnable*, and state the two rules that follow from +the fact that it is a second pin rather than a second value of the vLLM one. + +**This spec was rewritten after [#650](https://github.com/mudler/vllm.cpp/pull/650) +landed.** Its first draft proposed an `omni-parity-pin` block inside +[upstream-sync.md](../upstream-sync.md). #650 adopted the oracle registry — +one file per oracle under [`.agents/oracles/`](../oracles/), a fenced +`oracle-pin` block, and `scripts/check-oracle-pins.py` enforcing it — which is a +better answer to the same problem and explicitly rejects the shared-surface shape +the first draft had. So the registry is now the home of the pin, and this row no +longer proposes a record format at all. + +In scope: the rules governing how the omni pin and the vLLM parity pin interact; +the answer to whether an omni pin advance re-opens the vLLM-side binding grids; +and the work owed to actually take the pin. + +Out of scope: advancing the vLLM core pin to 0.27.0+; inventorying the omni model +surface; porting any omni architecture; re-stating anything AGENTS.md +§"When vLLM has no implementation" or [`.agents/oracles/README.md`](../oracles/README.md) +already says. + +## Upstream chain + +`vllm-project/vllm-omni` — a separate repository from `vllm-project/vllm`, on its +own release cadence, registry at `vllm_omni/model_executor/models/registry.py`. +Source-audited twice: `a4ea67a2` (v0.26.0) for MiniMax-H3 +([minimax-h3.md](minimax-h3.md) §8.2), and +`bbe6ccc512a404a2df8c977ea29003002f2683e8`, which the Moss-TTS, Qwen3-TTS and +Higgs-Audio rows in [model-matrix.md](../model-matrix.md) anchor to (#609, #610). + +Neither audit is a pin. Both read source, and a source read establishes what +exists, never what runs. + +## Our baseline + +[`.agents/oracles/vllm-omni.md`](../oracles/vllm-omni.md) exists and already +records the state honestly: `pin = UNPINNED`, `gateable = no`, `evidence = #633`. +So the *record* is no longer missing. What is still missing is the pin itself, +and with it any oracle-run gate for MiniMax-H3 W3+, LTX-2.5 and the whole TTS +family including IndexTTS-2.5 ([#634](https://github.com/mudler/vllm.cpp/issues/634)). + +vLLM-Omni requires vLLM **0.27.0+**; our parity pin is `555967922` (0.26.0.dev0). +That conflict is the reason this cannot be handled by advancing one number. + +## Port map + +Nothing is ported. Two record changes, and one deliberate non-change: + +| Change | Where | +|---|---| +| The omni pin's home is the oracle registry, not this file; do not add a second pin block here | [upstream-sync.md](../upstream-sync.md) | +| The two omni rules (below) | [upstream-sync.md](../upstream-sync.md) §Rules | +| **No edit** to `.agents/oracles/vllm-omni.md` | it already says `UNPINNED` / `gateable = no` / `evidence = #633`, which is true | + +**Rule 1 — the two pins may legitimately disagree, and an omni-gated number is +labeled with both.** vLLM-Omni needs 0.27.0+ while the parity pin is 0.26.0.dev0. +Forcing them equal would mean advancing the core pin — touching every gated row +and every binding number — to satisfy a lane that touches none of them. + +**Rule 2 — an omni-gated result is never evidence about the core pin's surface.** +Not in a vLLM-side parity claim, not in a binding grid, not in a +`docs/BENCHMARKS.md` row owned by a core-pinned row. Rule 1 is what makes Rule 2 +necessary: once the two pins can differ, a number carried across is a number +measured against a different dependency tree. + +Per-architecture gateability is NOT restated here: AGENTS.md already requires an +oracle to demonstrably build and run the model, and +[`.agents/oracles/README.md`](../oracles/README.md) already binds `gateable = yes` +to that. The only omni-specific consequence worth recording is that one +`gateable = yes` does not generalize across ~40 architectures, which is why the +demonstration is recorded on the owning row rather than the oracle file. + +## Tests to port + +None from upstream — local protocol machinery, no vLLM analogue, so the mirror +rule does not apply. + +| Assertion | Proves | Owed by | +|---|---|---| +| the `ENG-UPSTREAM-OMNI-PIN` row and the `ENGINE_ROWS` bump arrive together | a count moved for a row, not to silence a failure | this PR (landed) | +| an omni-gated evidence row cited by a core-pinned row goes red | Rule 2 is enforced by a checker, not by prose | W3 | +| a `core_commit_used` value that does not match the `vllm.__version__` in the same interpreter goes red | the isolation condition under Gates actually fires | W4 | + +`check-oracle-pins.py` already covers the block's own shape, and this row does +not duplicate it. + +## Gates + +**Does advancing the omni pin re-open the vLLM-side binding grids? No — +conditional on isolation.** The conditions: + +- the omni oracle lives in its own virtualenv, never the venv the parity pin's + oracle runs from; +- no core-pinned row's evidence cites an omni-gated number (Rule 2); +- the omni install mutates neither the shared checkout, `${VLLM_SOURCE}`, nor the + `vllm-oracle-next` environment the parity pin measures itself from. + +Fail any condition and the environments are entangled: what moved is the +denominator's own dependency tree, so every binding grid measured afterwards is +suspect and the advance is re-validated as a core sync cycle. + +The isolation is asserted, not assumed. The identity check reads +`vllm_omni.__version__` **and** the `vllm.__version__` visible in the same +interpreter. Recording the latter needs a key the `oracle-pin` schema does not +have yet — deliberately not invented here, because the pin is `UNPINNED` and +there is no value to record. Adding `core_commit_used` to the schema, and +teaching `check-oracle-pins.py` about it with its own mutation evidence, is part +of W4 and lands with the measurement. + +## Dependencies + +An eligible host, and nothing else in this repository. The pin values are +**PENDING**: they require installing vLLM-Omni somewhere and measuring +`vllm_omni.__version__` there, and an unavailable value stays `PENDING` rather +than becoming an assumption. + +Exact handoff: create a dedicated venv (never `vllm-oracle-next`), install +vllm-omni at the chosen commit with the vLLM version it requires, record `pin`, +`pin_label`, `pinned_on` and the new `core_commit_used` from the running +interpreter, and prove gateability for at least one architecture by generating +output from it. + +Note for that operator, current as of 2026-08-13: dgx `/home` was reported at 99% +with two reboots that morning under multi-session GPU load. ENOSPC on that host +has previously produced **false policy refusals citing a retired rule**, so a red +result there is evidence about the disk until it is back under 95%. + +## Work breakdown + +| W | Work | Depends on | +|---|---|---| +| W1 | Point `upstream-sync.md` at the registry; land the two omni rules | — | +| W2 | ~~Scope the exhaustive-inventory claim~~ — **superseded**, #609/#610/#650 did it | — | +| W3 | Checker: an omni-gated number cited by a core-pinned row goes red | W1 | +| W4 | Measure the pin; extend the `oracle-pin` schema with `core_commit_used` and teach the checker, with mutation evidence | W1, a host | +| W5 | Prove gateability for one architecture by generating output from it | W4 | + +W1 is this PR. W3-W5 are owed and unclaimed. + +## Risks/decisions + +- **Scope creep into a core pin advance.** vLLM-Omni wanting 0.27.0+ is a + standing pull toward moving the parity pin, which touches every gated row and + every binding number. **Stop** if the work cannot proceed without moving it, + and reconcile that as its own row. +- **Two pins read as one.** The failure Rule 2 exists to prevent. W3 gives it a + test; until then it is prose, and prose drifts. +- **A pin that cannot run anything.** Pinning a commit whose models do not run on + our hardware records a revision and unblocks nothing. `gateable` is what keeps + the pin from being mistaken for capability. +- **This spec was already superseded once, mid-flight.** #650 landed the registry + while this row was open, and the first draft's central proposal became the + wrong shape. Re-verify against `main` before implementing W3-W5; that is the + rule this row has already been caught by once. + +## Now + +`READY`, unclaimed. W1 lands with this spec; W2 is superseded; W3-W5 are not +started and the pin values are PENDING an eligible host. + +Downstream: [#634](https://github.com/mudler/vllm.cpp/issues/634) (IndexTTS-2.5) +is planned behind this row; MiniMax-H3 W3+ and LTX-2.5 carry the same blocker and +unblock together. diff --git a/.agents/upstream-sync.md b/.agents/upstream-sync.md index 148741346..cce831e16 100644 --- a/.agents/upstream-sync.md +++ b/.agents/upstream-sync.md @@ -42,6 +42,16 @@ from a release number. If a future pin is genuinely a released wheel, give the commit its own asserted field first; do not delete the assertion to make the block parse. +**vLLM-Omni's pin does NOT live here.** It is a separate repository, and under +AGENTS.md §"When vLLM has no implementation" every oracle carries its own file: +[`.agents/oracles/vllm-omni.md`](oracles/vllm-omni.md), whose `oracle-pin` block +is the one place its revision is recorded. Do not add a second pin block to this +file; one file per oracle, read by glob, is what keeps a pin from becoming a +surface every change has to write. What belongs HERE is only the part that is +about the relationship between the two, which the oracle file cannot state on its +own: see the omni rules under §Rules, and +[specs/upstream-omni-pin.md](specs/upstream-omni-pin.md) (#633) for why they hold. + **Prior cycle (2026-07-12):** audited target v0.25.0 `702f4814fe54`; report [`sync/2026-07-12-702f481.md`](sync/2026-07-12-702f481.md). The exact 145-commit `e24d1b24..702f481` delta was classified (94 `INVENTORY`, 51 `IGNORE`, no @@ -68,6 +78,10 @@ superseded it at **55/124 axes pass, 69 fail**; the current binding is `9ecd9d0` against. Gaps vs it are normal and tracked in the inventory, not hidden. - **Parity pin (post-MVP)** — one repo-wide vLLM commit. "We have feature X" always means "X as of the pin". Never compare against a moving target. +- **Omni parity pin** — the same idea for `vllm-project/vllm-omni`, recorded in + [`.agents/oracles/vllm-omni.md`](oracles/vllm-omni.md), not here. It is a + second pin rather than a second value of this one: it names the vLLM commit + *it* ran against, which need not be ours. - **Per-file pins** — every ported file's header records the upstream path + the upstream commit it matches. Normally equal to the parity pin; a file may be temporarily ahead (hot-fix port) but never behind without a ledger note. @@ -116,6 +130,14 @@ superseded it at **55/124 axes pass, 69 fail**; the current binding is `9ecd9d0` ## Rules - Ledger and inventory updates are part of the cycle, not optional follow-ups. +- An omni-gated number is labeled with BOTH commits and is never cited in a + vLLM-side parity claim, a binding grid, or a `docs/BENCHMARKS.md` row owned by + a core-pinned row. +- Advancing the omni pin does not re-open the vLLM-side binding grids PROVIDED + the omni oracle is installed in its own virtualenv and touches neither + `${VLLM_SOURCE}` nor the environment the core pin measures itself from. If that + isolation does not hold, the advance is a core sync cycle and is re-validated + as one — the dependency tree under the denominator moved. - Never mix a sync cycle with feature work in the same commit. - If an upstream change conflicts with a recorded deviation (inventory §9), the deviation doc gets updated in the same cycle — deviations must always diff --git a/scripts/check-agent-record.py b/scripts/check-agent-record.py index 8dc1b001c..0e8aeb40a 100644 --- a/scripts/check-agent-record.py +++ b/scripts/check-agent-record.py @@ -411,7 +411,15 @@ # `main` is squash-only, so a justification framed at an intermediate commit would # ship as a comment that is false about the file it sits in. # Bumped for a real new row, never to make a failing state transition pass. -ENGINE_ROWS = 153 +# 154 since 2026-08-13: +`ENG-UPSTREAM-OMNI-PIN` (a parity pin for the separate +# `vllm-project/vllm-omni` repository). A genuinely-new protocol capability, not a +# restatement of the vLLM pin: it is a SECOND pin that may legitimately disagree +# with the first, because vllm-omni requires vLLM 0.27.0+ against our 0.26.0.dev0 +# core pin. Landed the same day as the 153 bump above and merged against it: both +# rows are real and neither replaces the other, which is why this line reads 154 +# rather than restating 153. `READY`, spec `specs/upstream-omni-pin.md`, issue #633. +# Bumped for a real new row, never to make a failing state transition pass. +ENGINE_ROWS = 154 ENGINE_SUMMARY_SECTIONS = ( ("Engine and scheduling", "Engine core and scheduling"), diff --git a/tests/scripts/test_agent_record.py b/tests/scripts/test_agent_record.py index 2c2c86830..549781aae 100644 --- a/tests/scripts/test_agent_record.py +++ b/tests/scripts/test_agent_record.py @@ -269,6 +269,28 @@ def test_serve_recipe_args_row_is_inside_the_engine_ratchet(self) -> None: self.assertEqual(len(recipe), 1) self.assertEqual(recipe[0].path.name, "engine-matrix.md") + def test_omni_pin_row_is_inside_the_engine_ratchet(self) -> None: + """The #633 row and its 153 -> 154 ratchet bump are one semantic change. + + Same shape as the #117 and #606 assertions above, and it carries one + extra hazard worth pinning. This bump COLLIDED: `main` took the constant + 152 -> 153 for `SERVE-RECIPE-ARGS` while the omni-pin branch took the + same 152 -> 153 for its own row, so both sides read 153 and the merge + looked clean. Resolving it by keeping either 153 would have dropped a + real row while leaving the matrix internally consistent, which is + exactly the state no other assertion here can see. Naming BOTH rows is + what makes 154 checkable rather than plausible. + """ + + errors: list[str] = [] + rows, _ = agent_record.check_matrices(errors) + self.assertEqual([error for error in errors if "engine rows" in error], []) + + for item_id in ("ENG-UPSTREAM-OMNI-PIN", "SERVE-RECIPE-ARGS"): + found = [row for row in rows if row.item_id == item_id] + self.assertEqual(len(found), 1, item_id) + self.assertEqual(found[0].path.name, "engine-matrix.md", item_id) + def test_model_row_ratchet_is_load_bearing(self) -> None: """The MODEL row pin must catch a row appearing or vanishing. From b338e9335ce0c1bc76fc2ea5829200aeb64ecd3d Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:13:08 +0200 Subject: [PATCH 10/55] spec(MODEL-MM-indextts2): scope IndexTTS-2.5, the first audio-generating lane (#634) (#637) FOLLOWING_AGENTS_PROTOCOL vLLM-Omni supports IndexTTS-2.5 and we had no record of it. Every audio path we ship CONSUMES audio (Parakeet, Voxtral, `audio_processor.cpp`); nothing synthesizes it. Rows both registered architectures -- `IndexTTS2TalkerForConditionalGeneration` (stage 0, ~0.8B GPT-2 AR talker) and `IndexTTS2S2MelDecoder` (stage 1: EnhancedCodec -> S2Mel CFM/DiT -> BigVGAN, 22.05 kHz) -- at `registry.py` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8`. NO product code. This is a spec and its records; W1-W7 are all unstarted. Nothing runs, no audio is generated, and the lane is not reachable from the ABI or the server. That unreachability is now SCOPE rather than a footnote, verified in the tree: `include/vllm.h` is at ABI v18 with zero speech entry points, and `audio/speech`/`audio/voices` have zero hits across `src/`, `include/` and `examples/`. The spec mirrors the video lane that solved the same problem for a generative modality -- a `SpeechEngine` seam like `VideoEngine`, ABI v19 with `vllm_synthesize` named symmetrically to the existing `vllm_transcribe`, both routes through that seam, the example as a thin ABI client -- and the seam must admit the ~10 other omni TTS architectures, not just this one. Three things pinned down rather than left to be discovered: reference audio is MANDATORY (upstream has no text-only synthesis, so w2v-bert-2.0 + MaskGCT + CAMPPlus are required surface); no token-exact e2e gate is possible (a seed drives both AR sampling and per-request CFM noise), so the binding gate is per-stage numerics vs the checkpoint's own remote code plus token-exact mel codes at fixed seed/c1/fixed batch composition, with an ASR-round-trip ratchet that is explicitly neither a correlation gate nor a count-based tolerance; and we mirror vLLM-Omni even though it itself deviates from IndexTeam's `num_beams=3` default. Two reuse claims were CORRECTED after LTX-2.5 merged: it generalized the VIDEO engine, not the vocoder, and the BigVGAN 1-D core is already shared by H3 and LTX-2.5 from `minimax_h3.h`. This lane would be the THIRD consumer, so W1 shrank from "generalize" to "relocate to a neutral home". Both rows land `INVENTORIED`, unclaimed and blocked on the UNPINNED vllm-omni oracle. MODEL 370 -> 372, with mutation evidence for the two-architecture shape. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/model-matrix.md | 21 +-- .agents/roadmap_v1.md | 1 + .agents/specs/indextts-2-5.md | 239 +++++++++++++++++++++++++++++ docs/FEATURES.md | 2 +- scripts/check-agent-record.py | 8 +- tests/scripts/test_agent_record.py | 27 ++++ 6 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 .agents/specs/indextts-2-5.md diff --git a/.agents/model-matrix.md b/.agents/model-matrix.md index 496eb398c..cd53f75c3 100644 --- a/.agents/model-matrix.md +++ b/.agents/model-matrix.md @@ -19,29 +19,30 @@ a practical unit that one agent can spike without silently dropping aliases. ## Architecture-support checklist At-a-glance view of which architectures we have actually engaged, and how far. -**355 architecture rows are inventoried at the pin, plus 11 rows that the pinned -registry does not contain = 366 architecture rows.** Those 11 are, by why they +**355 architecture rows are inventoried at the pin, plus 13 rows that the pinned +registry does not contain = 368 architecture rows.** Those 13 are, by why they are not at the pin: `KimiK3ForConditionalGeneration` and `MuseGlimmerForConditionalGeneration`, both released after the pin; `BailingMoeV3ForCausalLM`, a pin-lag row registered on vLLM `main` and absent only at the pin (#609); `MiniMaxH3DiTModel` and `LTX2VideoTransformer3DModel`, both DIFFUSION architectures released after the pin and absent from the vLLM repository entirely, reached through the -separate `vllm-omni` repository (#435); the four TTS / audio-generation architectures +separate `vllm-omni` repository (#435); the six TTS / audio-generation architectures registered in that same `vllm-omni` repository — `MossTTSDelayModel`, -`MossTTSRealtime`, `Qwen3TTSForConditionalGeneration` and -`HiggsMultimodalQwen3ForConditionalGeneration`; and two whose upstream target is +`MossTTSRealtime`, `Qwen3TTSForConditionalGeneration`, +`HiggsMultimodalQwen3ForConditionalGeneration`, and IndexTTS-2.5's two halves +`IndexTTS2TalkerForConditionalGeneration` and `IndexTTS2S2MelDecoder` (#634); and two whose upstream target is still pending because the exact `config.json` architecture string is registered in neither core vLLM `main` nor `vllm-omni` — `VoxtralRealtimeForConditionalGeneration` and `BailingMMNativeForConditionalGeneration` (#610). -The rollup below counts **370 rows**, which is those 366 plus the four that are +The rollup below counts **372 rows**, which is those 368 plus the four that are not architectures at all and say so in their own sections: `MODEL-FACTORY-registry` (the cross-cutting registry contract), the two `MODEL-AUDIO` encoder-component rows, and the one `MODEL-HFDYNAMIC` row (dynamic Transformers compatibility is capability-driven and excluded from finite counts). -Of those 370, 49 are past `INVENTORIED` (engaged) and the remaining 321 are the +Of those 372, 49 are past `INVENTORIED` (engaged) and the remaining 323 are the known-but-not-started long tail — the same two numbers the rollup table states, and the way to re-derive every count here is to re-run [`scripts/check-agent-record.py`](../scripts/check-agent-record.py)'s parser over @@ -65,7 +66,7 @@ Rollup by lifecycle state (must equal the detailed per-state row counts): | State | Rows | |---|---| -| INVENTORIED | 321 | +| INVENTORIED | 323 | | PARTIAL | 20 | | ACTIVE | 9 | | SPIKE | 8 | @@ -73,7 +74,7 @@ Rollup by lifecycle state (must equal the detailed per-state row counts): | DONE | 3 | | READY | 3 | | GATING | 1 | -| **Total** | **370** | +| **Total** | **372** | Engaged architectures (the 49 non-`INVENTORIED` rows): @@ -438,6 +439,8 @@ Transformers compatibility is capability-driven and excluded from finite counts. | `MODEL-DIFFUSION-minimax-h3-mini-max-h3-dit` | `MiniMaxH3DiTModel` | **BEYOND-PIN AND OUT-OF-REPO** — not in `555967922` (H3 released after the pin) and not in the vLLM repository at all: it lives in `vllm-project/vllm-omni`, `vllm_omni/diffusion/models/minimax_h3/` (`minimax_h3_transformer.py`, `packed_sequence.py`, `packed_tokens.py`, `scheduling_minimax_h3_euler_ancestral.py`, `denoise_loop.py`, `vae.py`, `encoder.py`, `pipeline_minimax_h3.py`) | diffusion generation / video + audio (text/image/video/audio in) | flow-matching denoise loop; packed varlen NON-CAUSAL attention; AdaLN modulation; 3D MM-RoPE; video VAE + audio VAE (checkpoint REMOTE CODE); Qwen3-VL-derived encoder; MP4 muxing; Ulysses sequence parallelism | [minimax-h3 spike](specs/minimax-h3.md) | `PARTIAL` | **W0-W2 LANDED (2026-08-03, `CLAIM-MINIMAX-H3-W0-W2`, DERIVE-AND-SHIP).** H3 is NOT an autoregressive LLM: it is a CFG-distilled joint video+audio DIFFUSION transformer (50 blocks, H=5376, 56 MHA heads x 128, SwiGLU 14336, AdaLN 6x3xH, 3D RoPE rotating 96 of 128 dims, video row width 96, audio latent 32) forwarded ONCE PER STEP of a 50-step flow-matching loop — no KV cache, no sampler, no logits, so the SACRED token-exact methodology does not apply. **HW VERDICT: e2e is IMPOSSIBLE on this project's hardware** — ~354 GB checkpoint (DiT 66.3 GB + Qwen3-VL-derived encoder 51.5 GB + video VAE ~10 GB + audio VAE ~0.6 GB), upstream validates on **4x NVIDIA B300 at ~133 GB peak per rank**, vs ONE GB10 with 119 GiB UNIFIED memory; CPU offload does not help because the pool IS host RAM. **WHAT IS GATED (and it is exact):** upstream's modules are pure Python, so they are executed at REDUCED DIMENSIONS on CPU as the oracle (`scripts/gen-minimax-h3-goldens.py` imports them by file path and freezes their outputs; both sides rebuild weights/inputs from an identical FNV-1a + splitmix64 stream, so no weight byte is checked in). Results: fl2va + ref2va packed layouts EXACT including the **fp64 position grid BIT-EXACT** (it feeds RoPE — the port reproduces numpy's `linspace(endpoint=False)` evaluation order and upstream's deliberately-split pairwise vs sequential span summations, `packed_sequence.py:101-113`); patchify/unpatchify/audio pack EXACT + round-trip; scheduler EXACT; **full DiT forward max abs diff 1.6e-7 (video) / 1.5e-7 (audio)** — f32 round-off. **REUSE:** the packed varlen non-causal attention routes through the SHARED `vt::DFlashBlockAttention(causal=false)` (its per-document bidirectional contract IS upstream's varlen FA call) and every projection through `vt::MatmulBT` — NO new kernel was added. **NOT-YET-BUILT (honest):** device-resident/bf16 forward + fusion folds (W2b), H3-Encoder on our existing Qwen3-VL tower (W3), the two VAEs — which are **checkpoint REMOTE CODE** under `trust_remote_code` and must be reimplemented in C++, not adapted (W4/W5) — pipeline/tasks (W6), `/v1/videos` + MP4 muxing, which needs a NEW dependency decision (W7), and USP multi-GPU (W8). No speed number is claimed; upstream reports the DiT at 88% of request latency. Code: [minimax_h3.h](../include/vllm/model_executor/models/minimax_h3.h#L1-L333), [minimax_h3.cpp](../src/vllm/model_executor/models/minimax_h3.cpp#L410-L640) (`MiniMaxH3DitForward`), [minimax_h3_packing.cpp](../src/vllm/model_executor/models/minimax_h3_packing.cpp#L259-L400) (`BuildMiniMaxH3PackedSequence`). Test: [test_minimax_h3.cpp](../tests/vllm/models/test_minimax_h3.cpp#L376-L470) (DiT forward parity; 10/10 cases / 2539 assertions, clean CPU build) + generator [gen-minimax-h3-goldens.py](../scripts/gen-minimax-h3-goldens.py#L1-L60); [ledger](parity-ledger.md#L889). **W6A+W9 LANDED (2026-08-03, `CLAIM-MINIMAX-H3-W6A-W9`) + HW VERDICT CORRECTED.** (a) the **bf16 PRODUCTION dtype policy** now runs (upstream's cast points with the fp32 islands preserved; gated vs a bf16 upstream golden at max abs diff 2.4e-3); (b) **request planning** — 17n+5 frame snapping, video/audio latent shapes, the rectified-flow time-shift sigma schedule, canvas resolution and t2va/fl2va/ref2va dispatch — ported and EXACT vs `time_request.py` + `pipeline_minimax_h3.py:121-122,207-222,374-434`; (c) the **ComfyUI-GGUF arm**: the name map is the IDENTITY and every one of the **535 real tensors** of `MiniMax-H3-FL2VA-Q3_K_M.gguf` resolves onto our contract, with the geometry derived from SHAPES ALONE equal to the shipped config (gated on a manifest read from the file's own header by range request — no payload downloaded). Two shape rules recorded: GGUF `ne` is reversed vs torch, and `comfy.gguf.orig_shape.` overrides it where ComfyUI reshaped a tensor for quant-block alignment (the 50 AdaLN projections: logical [96768, 2688], 2688 not a multiple of the 256-element Q3_K block). **★ HARDWARE VERDICT CORRECTED — the earlier 'e2e is IMPOSSIBLE on this hardware' was WRONG** because it reasoned from the bf16 release alone: quantized H3 checkpoints exist and FIT (GGUF DiT Q3_K_M 15.6 GB + Qwen3-VL encoder Q4_K_M 14.6 GB + VAEs ~11 GB ~= **41 GB** in a 119 GiB pool; `lilcheaty/MiniMax-H3-NVFP4` likewise). So e2e AND a speed comparison are REACHABLE; NVFP4 is the likely speed path (sm_121 native FP4 tensor cores + our tuned NVFP4 stack). **W5 LANDED (audio VAE)**: H3's VAEs are checkpoint REMOTE CODE under `trust_remote_code`, so a no-Python engine must REIMPLEMENT them — the DAC-lineage BigVGAN audio decoder (weight-norm materialization (w = g*v divided by the row norm), anti-aliased SnakeBeta with kaiser-sinc up/down resampling, replicate padding, final clamp) is ported and gated against the checkpoint's OWN modules at **max abs diff 4.2e-9**. The VIDEO VAE (`klvae.py` ~48 KB + CNN/ViT + tiling) is the largest remaining brick. **W10 GROUNDED + W4 SCOPED from REAL manifests** (safetensors headers captured by range request, no payload downloaded): the NVFP4 checkpoint's 1051 tensors are textbook compressed-tensors NVFP4 (U8 packed 2-per-byte + E4M3 `weight_scale` at group 16 + F32 scalar `weight_scale_2`; 258 quantized projections; fp32/bf16 islands left unquantized; names identical to our contract) — i.e. EXACTLY the layout our tuned NVFP4 stack already consumes, so W10 is loader wiring, not a new quant scheme. The video VAE's 560 tensors show its ENCODER is the 3D CNN (rank-5 Conv3d) while its DECODER — the half generation needs — is a 36-block TRANSFORMER (to_qkv/to_out, ff.w1/w2, 2 norms + 2 learned residual scales per block, x_embedder/mask_token/register_tokens/proj_out), materially smaller than klvae.py's 48 KB suggested. **W4 BLOCK LANDED**: the video-VAE decoder's repeated `TransformerBlock` is ported and gated at **6.0e-8** against the checkpoint's OWN remote code — RMSNorm + per-head RMS qk-norm (no affine) + full attention + gated-SiLU FF + LEARNED PER-CHANNEL residual scales, and critically the PER-HEAD-INTERLEAVED qkv layout ([head][q,k,v], NOT the DiT's [q_all,k_all,v_all]) that would otherwise produce a plausible-but-wrong image. **W4 DECODER DONE**: the FULL ViT3D video-VAE decoder — pack, x_embedder, register/cls tokens, 3D RoPE (RotaryEmbeddingND, length-normalized ids, angle scale 2pi, tiled freq blocks), the 36-block stack, LayerNorm norm_out, proj_out and unpatchify — is ported and gated at **8.9e-8** against the checkpoint's own `ViT3DDecoder`, at its real hyperparameters (36 layers, 32 heads x 64, rope_theta 100, rope_dim_ratio 0.75). BOTH VAE decoders are now done (audio 4.2e-9). **W3 TEXT TOWER DONE**: the H3-Encoder's truncated Qwen3-VL text tower — gated at **1.2e-7** vs upstream — with all three H3 deltas exercised: layer truncation (min(num_hidden_layers, 50)), the UNNORMALIZED layer-49 output (NO final RMSNorm, unlike stock Qwen3-VL — applying one silently shifts every conditioning vector), and DeepStack visual injection into the first N layers; plus interleaved M-RoPE, fused QKV, per-head q/k RMSNorm, causal GQA and the gated-SiLU MLP. **W6 t2va ASSEMBLED — the WHOLE PATH COMPOSES**: `MiniMaxH3GenerateT2va` wires packed layout -> rectified-flow sigma schedules -> the multi-step denoise loop of DiT forwards -> unpatchify + audio unpack -> per-channel denormalize -> BOTH VAE decoders, producing correctly-shaped, finite frames and a stereo waveform in [-1, 1]; gated by a structural end-to-end test at reduced dimensions with random weights (NOT a quality result). Assembling it also caught a real gap: the audio decode needed the checkpoint's `dec_in_proj` (Conv1d k=1, vae_latent_channels -> num_mels) ahead of BigVGAN. **W9 GGUF ARM DONE**: `LoadMiniMaxH3DitFromGguf` materializes the DiT from a ComfyUI-format GGUF — dequantizing every tensor through the SHARED GGUF dequant entry point (so the Q2_K/Q3_K/Q4_K families the H3 GGUFs use are covered by the same code every other GGUF model uses), recovering the geometry from shapes alone, and binding the forward's views with missing tensors throwing BY NAME rather than reading as zeros; gated by a synthetic-file load-AND-RUN test (a real DiT forward executes off the loaded weights). **W10 LOADER DONE**: `LoadMiniMaxH3DitFromNvfp4` materializes the DiT from an NVFP4 compressed-tensors checkpoint — the U8-packed [out, in/2] weight plus its E4M3 group-16 `weight_scale` and F32 scalar `weight_scale_2` go through the project's EXISTING NVFP4 dequant (no new quant code), sidecars are excluded from the model tensor set, and the logical [out, in] shape is recovered; gated by a synthetic-file load-AND-RUN test. BOTH quantized loaders are now done. **W3 VISION BLOCK DONE** (6.0e-8): the repeated unit of the encoder's Qwen3-VL vision tower, which differs from the text tower in every way that matters numerically — LayerNorm WITH BIAS (not RMSNorm), a [q_all, k_all, v_all] qkv layout (not the video VAE ViT's per-head interleave), fp32 rotary, NON-CAUSAL attention segmented by `cu_seqlens` (the test asserts a perturbation in one packed image leaves the other's outputs BIT-IDENTICAL), and the TANH-approximate GELU. **W3 ENCODER COMPLETE**: the FULL vision tower also lands — Conv3d patch embed (kernel == stride, so a linear over the flattened patch), BILINEAR resampling of the learned position grid into spatial-merge order, the 2D rotary table, per-frame `cu_seqlens`, and both merger flavours (the final merger norms the PRE-shuffle width while the DeepStack mergers norm the POST-shuffle width, and both use exact-erf GELU unlike the block MLP's tanh approximation), gated over a RAGGED two-image batch. Only the MM processor remains on the encoder. **CONDITION-NOISE augmentation DONE** (fl2va/ref2va): the noised-anchor mix plus its ROW ACCOUNTING — each visual condition draws noise of length `target_latent_t + imgvid_cond_num_frames` and slices the PREFIX matching its own latent_t, every condition restarts the SAME seed (so concatenating and drawing once would differ for multi-reference requests), and rows advance by that condition's own patchified count. Gated EXACT with the noise supplied, so the comparison isolates the accounting from torch's RNG. **REFERENCE-VIDEO geometry + FRAME SCHEDULE DONE** (the pure-math half of `reference_video.py`): the canvas pipeline (aspect clamp -> 768 short edge -> max-pixel rescale -> nearest multiple of 32) and the 24-to-2 FPS frame resample with per-temporal-patch block timestamps, both EXACT. NOTE the rest of that module (probe, transcode, frame extraction, audio decode) shells out to ffmpeg and is blocked on the SAME external dependency decision as `/v1/videos` MP4 muxing — one decision unlocks reference-video INPUT decode and generated-video OUTPUT encode together. **VIDEO VAE TILING DONE**: the tile plan (smallest tile count whose MINIMUM overlaps still cover the axis, leftover slack distributed in whole `vae_ratio` units ROUND-ROBIN across the seams) plus the linear seam cross-fade, both EXACT. Shipped config tile 256 / overlap 64 / vae_ratio 16 (= prod(space_down), the 'f16' in f16t4). Getting the slack distribution wrong shifts every tile after the first and surfaces as seam artifacts rather than an error. **PRESENTATION TOKEN TAGS DONE** — the fl2va vision-span override the denoise loop requires callers to have applied: a vision block is `vision_start + pad*count + vision_end` and the WHOLE block, MARKERS INCLUDED, is tagged VIDEO; tagging only the pads would leave two markers as TEXT and shift every AdaLN modulation index after them. Gated EXACT, with the test proving each VIDEO run is a whole vision block. **VAE 3D-CNN ENCODER PRIMITIVES DONE**: causal Conv3d (all temporal padding on the LEFT so a frame never sees the future, `reflect` spatial padding), GroupNorm3D (32 groups, eps 1e-6, statistics spanning TIME as well as space) and ResnetBlock3D, gated EXACT — with CAUSALITY proven directly on the bare convolution (a change to the last frame provably cannot reach earlier frames). **Downsample3D DONE** too: the strided inter-level conv, whose subtlety is the ASYMMETRIC pre-pad — one pixel on the RIGHT of W and the BOTTOM of H before a stride-2 conv with padding (1,0,0); padding symmetrically instead shifts the whole sampling lattice by half a pixel, which is a silent wrong latent rather than an error. Only the EncoderFCN3D level-loop assembly remains on the VAE encoder. **VIDEO VAE COMPLETE — encoder AND decoder**: the whole 3D-CNN encoder level loop (conv_in -> per level [ResnetBlock3D x N then Downsample3D or a 1x1x1 channel match] -> GroupNorm -> SiLU -> conv_out) is gated EXACT. **MM PROCESSOR = REUSE, gated**: H3's `FL2VA/processor` is a stock `Qwen3VLProcessor`, so the multimodal front end is this project's EXISTING Qwen3-VL processor rather than a new port; H3's own config is parsed and driven through it (patch 16 / temporal 2 / merge 2, **0.5 normalization rather than CLIP statistics**, a 32-pixel grid, the 768x1344 default canvas proven an IDENTITY under smart_resize, and VIDEO bounds deliberately looser than the image ones). **With this every PORTABLE piece of the lane is done**; what remains is one dependency decision (ffmpeg media I/O + MP4 muxing) and GPU-blocked work (the device-resident FP4 forward and any speed number). **WAV OUTPUT** added: the decoded stereo waveform serializes to RIFF/WAVE 16-bit PCM, converting the VAE's CHANNEL-MAJOR layout to INTERLEAVED (getting that backwards yields audio that plays but with the channels time-smeared) and clamping rather than wrapping. Deliberately dependency-free, and required under EITHER outcome of the open MP4/muxer decision. **VIDEO OUTPUT PATH DONE**: PPM frame serialization (planar [C,T,H,W] -> row-major interleaved RGB, [-1,1] -> [0,255] clamped) plus the MP4 mux argv (h264/yuv420p + AAC, `-shortest`, `+faststart`). The built argv was RUN through real ffmpeg 6.1.1 and produced a VALID MP4 (ffprobe: h264 yuv420p video + AAC stereo at 32 kHz). The library never spawns a process — `src/vllm/` has no subprocess precedent — so it builds the artifacts and the command while the example/server layer invokes it. **`/v1/videos` API LOGIC DONE**: the request contract (H3 defaults — 50 steps, flow shift 12 video / 3 audio; both the vLLM-Omni `extra_params` nesting and a flat top-level spelling accepted; malformed input rejected with a reason rather than silently defaulted) and the job store (queued -> running -> succeeded/failed, illegal transitions throw, unknown ids reported so the route can 404, status JSON omits fields that do not apply, and concurrent creation is thread-safe). Remaining is mechanical glue: route registration + runner injection, with the ffmpeg call in `examples/` per the ratified boundary. Test: 34/34 + video-api 4/4 (9233 + 63 assertions). OPEN: there is no vllm-omni parity PIN — the upstream-sync protocol covers only the vLLM repo | `CLAIM-MINIMAX-H3-W0-W2`, `CLAIM-MINIMAX-H3-W6A-W9` | | `MODEL-MM-moss-tts-moss-tts-delay-talker-for-generation` | `MossTTSDelayModel` | **OUT-OF-REPO** — not in `555967922` and not in the vLLM repository at all (absent from every dictionary of `registry.py` on `main` as well): it is registered by the separate `vllm-project/vllm-omni` repository, `vllm_omni/model_executor/models/registry.py:350-354` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8` → module `vllm_omni/model_executor/models/moss_tts/modeling_moss_tts_talker.py`, class `MossTTSDelayTalkerForGeneration`, with the shared stage-1 codec decoder at `registry.py:368-372` → module `moss_tts/modeling_moss_tts_codec.py`, class `MossTTSCodecDecoder`. Module and class are given as separate fields rather than in the compact colon-pair form, as the `MiniMaxH3DiTModel` row does: that form marks a target inside the PINNED vLLM registry inventory, and this target is in another repository entirely. Documented at `vllm-omni` `docs/models/supported_models.md:78`. Official recipes: `OpenMOSS-Team/MOSS-TTS`, `MOSS-TTSD-v1.0`, `MOSS-SoundEffect`, `MOSS-VoiceGenerator` — four of the 157 recipes at `vllm-project/recipes` `86c7777a`, all four declaring `architectures: ["MossTTSDelayModel"]`. Tracked by [#610](https://github.com/mudler/vllm.cpp/issues/610) | conditional generation / text+audio in, AUDIO OUT (TTS) | MM processor; audio detokenizer / codec decoder; delay-pattern multi-codebook decoding; reference-audio speaker encoder; streaming audio output. **We serve no audio-generation output modality at all today** | ☐ required | `INVENTORIED` | none. There is no `vllm-omni` parity PIN — the upstream-sync protocol covers only the vLLM repository — so an oracle for this row has to be established before it can advance | unassigned | | `MODEL-MM-moss-tts-moss-tts-realtime-talker-for-generation` | `MossTTSRealtime` | **OUT-OF-REPO** — not in `555967922`, and absent from `registry.py` on vLLM `main`; registered by `vllm-project/vllm-omni` at `vllm_omni/model_executor/models/registry.py:356-360` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8` → module `vllm_omni/model_executor/models/moss_tts/modeling_moss_tts_talker.py`, class `MossTTSRealtimeTalkerForGeneration` (separate module and class fields rather than the compact colon-pair form, per the out-of-repo convention) — the SAME module as `MossTTSDelayModel` but a different class, so the two are separate targets rather than aliases. Documented at `vllm-omni` `docs/models/supported_models.md:79`. Official recipe: `OpenMOSS-Team/MOSS-TTS-Realtime` (1.7B). Tracked by [#610](https://github.com/mudler/vllm.cpp/issues/610) | conditional generation / text+audio in, AUDIO OUT (realtime/streaming TTS) | MM processor; audio detokenizer / codec decoder; realtime full-duplex streaming; incremental audio decode | ☐ required | `INVENTORIED` | none. Same missing-`vllm-omni`-pin blocker as the other omni rows | unassigned | +| `MODEL-MM-indextts2-index-tts2-talker-for-conditional-generation` | `IndexTTS2TalkerForConditionalGeneration` | **OUT-OF-REPO** — not in `555967922` and not in the vLLM repository at all: registered by the separate `vllm-project/vllm-omni` at `vllm_omni/model_executor/models/registry.py` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8` (separate module and class fields rather than the compact colon-pair form, per the out-of-repo convention) → module `vllm_omni/model_executor/models/indextts2/indextts2_talker.py`, class `IndexTTS2TalkerForConditionalGeneration`. Deploy config `vllm_omni/deploy/indextts2_5.yaml` (selected by `model_type`); offline entry point `indextts2.end2end`; served as `vllm-omni serve IndexTeam/IndexTTS-2.5 --omni --trust-remote-code` over `/v1/audio/speech` + `/v1/audio/voices`. Official recipe: `IndexTeam/IndexTTS-2.5` (~0.8B talker, ~6 GB VRAM). Tracked by [#634](https://github.com/mudler/vllm.cpp/issues/634) | conditional generation / text + REFERENCE AUDIO in, mel codes out (stage 0 of a 2-stage TTS pipeline) | GPT-2 backbone (`opt.cpp` is the same family: learned absolute positions + LayerNorm); reference-audio conditioning via w2v-bert-2.0 + MaskGCT semantic codec + CAMPPlus speaker embedding — MANDATORY, since upstream states IndexTTS-2 does not support text-only synthesis | [indextts-2-5 spec](specs/indextts-2-5.md) | `INVENTORIED` | none. Blocked on the UNPINNED vllm-omni oracle ([`.agents/oracles/vllm-omni.md`](oracles/vllm-omni.md), `pin = UNPINNED` / `gateable = no` / `evidence = #633`), which vLLM-Omni compounds by requiring vLLM 0.27.0+ against our 0.26.0.dev0 parity pin | unassigned | +| `MODEL-MM-indextts2-index-tts2-s2-mel-decoder` | `IndexTTS2S2MelDecoder` | **OUT-OF-REPO** — not in `555967922` and not in the vLLM repository at all: registered by the separate `vllm-project/vllm-omni` at `vllm_omni/model_executor/models/registry.py` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8` (separate module and class fields rather than the compact colon-pair form, per the out-of-repo convention) → module `vllm_omni/model_executor/models/indextts2/indextts2_s2mel_decoder.py`, class `IndexTTS2S2MelDecoder`. The stage-1 half of the same recipe as the talker row; 2.5 replaces IndexTTS-2's RepCodec with EnhancedCodec and sets `use_gpt_latent=false`. Tracked by [#634](https://github.com/mudler/vllm.cpp/issues/634) | mel codes in, 22.05 kHz mono WAV out (stage 1) | EnhancedCodec; S2Mel CFM/DiT flow-matching decoder; BigVGAN vocoder. REUSE: the BigVGAN 1-D core (Conv1d/ConvTranspose1d, pads, Snake/SnakeBeta, alias-free `Activation1d`) is ALREADY shared by H3 and LTX-2.5, published from `minimax_h3.h` and gated by both suites (`ltx2_audio_vae.cpp:223-230` explains why it was not copied); this row would be the THIRD consumer, so the work is relocating that core to a neutral home, not generalizing it. WAV at `minimax_h3_wav.cpp`; denoise loop + AdaLN from the H3 lane | [indextts-2-5 spec](specs/indextts-2-5.md) | `INVENTORIED` | none. Blocked on the UNPINNED vllm-omni oracle ([`.agents/oracles/vllm-omni.md`](oracles/vllm-omni.md), `pin = UNPINNED` / `gateable = no` / `evidence = #633`), which vLLM-Omni compounds by requiring vLLM 0.27.0+ against our 0.26.0.dev0 parity pin. NOTE the shared vocoder core is gated at H3's and LTX-2.5's hyperparameters, not this model's, so it is re-gated here: a gate passing because both arms call the same helper proves consistency, not correctness | unassigned | | `MODEL-MM-qwen3-tts-qwen3-tts-talker-for-conditional-generation` | `Qwen3TTSForConditionalGeneration`, `Qwen3TTSTalkerForConditionalGeneration` | **OUT-OF-REPO** — not in `555967922`, and absent from `registry.py` on vLLM `main`; registered by `vllm-project/vllm-omni` at `vllm_omni/model_executor/models/registry.py:152-156` (and the explicit `Qwen3TTSTalkerForConditionalGeneration` spelling at `:157`) @ `bbe6ccc512a404a2df8c977ea29003002f2683e8` → module `vllm_omni/model_executor/models/qwen3_tts/qwen3_tts_talker.py`, class `Qwen3TTSTalkerForConditionalGeneration` (separate module and class fields rather than the compact colon-pair form, per the out-of-repo convention), with the code2wav stage at `registry.py:162-166` → module `qwen3_tts/qwen3_tts_code2wav.py`, class `Qwen3TTSCode2Wav` and 12 Hz / 25 Hz tokenizers under `qwen3_tts/tokenizer_12hz/` and `tokenizer_25hz/`. Documented at `vllm-omni` `docs/models/supported_models.md:72-74`. Official recipe: `Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice` (published at `recipes.vllm.ai`). The two architecture strings resolve to the identical `(module, class)` target so they are grouped on one row, per the alias rule | conditional generation / text in, AUDIO OUT (TTS, custom voice) | MM processor; audio detokenizer / code2wav; speaker-embedding conditioning; word timestamps; streaming audio output | ☐ required | `INVENTORIED` | none. Its text tower is Qwen3-derived, which is the one piece of this row we already ship; everything downstream of the talker (code2wav, audio tokenizer, speaker embeddings) is unported | unassigned | | `MODEL-MM-higgs-audio-v3-higgs-audio-v3-talker-for-conditional-generation` | `HiggsMultimodalQwen3ForConditionalGeneration`, `HiggsAudioV3TalkerForConditionalGeneration` | **OUT-OF-REPO** — not in `555967922`, and absent from `registry.py` on vLLM `main`; registered by `vllm-project/vllm-omni` at `vllm_omni/model_executor/models/registry.py:213-217` (and the explicit `HiggsAudioV3TalkerForConditionalGeneration` spelling at `:218`) @ `bbe6ccc512a404a2df8c977ea29003002f2683e8` → module `vllm_omni/model_executor/models/higgs_audio_v3/higgs_audio_v3_talker.py`, class `HiggsAudioV3TalkerForConditionalGeneration` (separate module and class fields rather than the compact colon-pair form, per the out-of-repo convention), with code2wav at `registry.py:223-227` → module `higgs_audio_v3/higgs_audio_v3_code2wav.py`, class `HiggsAudioV3Code2WavForConditionalGeneration`. Documented at `vllm-omni` `docs/models/supported_models.md:81`. Official recipe: `bosonai/higgs-audio-v3-tts-4b` (published at `recipes.vllm.ai`), whose `config.json` declares `architectures: ["HiggsMultimodalQwen3ForConditionalGeneration"]`. The distinct v2 lineage (`HiggsAudioV2ForConditionalGeneration`, `higgs_audio_v2/`) is a DIFFERENT target and has no recipe in the audited set, so it gets no row here | conditional generation / text+audio in, AUDIO OUT (TTS) | MM processor; audio detokenizer / code2wav; audio tokenizer; Qwen3-derived multimodal decoder; streaming audio output | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-voxtral-realtime-voxtral-realtime-for-conditional-generation` | `VoxtralRealtimeForConditionalGeneration` (target-pending) | **TARGET-PENDING — the architecture string is registered NOWHERE we can find.** Recipe `mistralai/Voxtral-Mini-4B-Realtime-2602` (one of the 157 recipes at `vllm-project/recipes` `86c7777a`) declares `architectures: ["VoxtralRealtimeForConditionalGeneration"]` with an inner `audio_config.model_type: "voxtral_realtime_encoder"`, but that exact string appears in none of: (a) the pinned registry `555967922`; (b) any of the ten dictionaries of `vllm/model_executor/models/registry.py` on vLLM `main`; (c) `_OMNI_MODELS` in `vllm_omni/model_executor/models/registry.py` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8`; (d) `vllm-omni` `docs/models/supported_models.md`. **Related but NOT equal, recorded so nobody re-derives it:** `VoxtralRealtimeGeneration` — a DIFFERENT string — is registered at the pin (`registry.py:585`, `main` `:603`) and already has its own row here, `MODEL-MM-voxtral-realtime-voxtral-realtime-generation`; `VoxtralForConditionalGeneration` (pin `registry.py:584`) is the audio→TEXT model; and `vllm-omni` `registry.py:305-309` registers `VoxtralTTSForConditionalGeneration` for the SEPARATE `mistralai/Voxtral-4B-TTS-2603` recipe. The most likely reading is that this checkpoint's `config.json` names a renamed or not-yet-landed spelling of the existing `VoxtralRealtimeGeneration` target, but that is a hypothesis: no anchor is asserted until one of those is shown to be what actually resolves. Tracked by [#610](https://github.com/mudler/vllm.cpp/issues/610) | conditional generation / audio in, AUDIO OUT (realtime speech-to-speech) — modality inferred from the recipe and config, NOT from an upstream implementation | unresolved until the target is located | ☐ required | `INVENTORIED` | none | unassigned | diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 91219dbf9..29ab578da 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -91,6 +91,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#406](https://github.com/mudler/vllm.cpp/issues/406) | `ENG-TRAILER-MERGE-ARTIFACTS` | The trailer gate fails on how commits LAND: GitHub's Co-authored-by displaces the trailer block | bug | | [#467](https://github.com/mudler/vllm.cpp/issues/467) | `ENG-TRAILER-MERGE-ARTIFACTS` | `agent-preflight.sh` prints "trailer suites ok / All gates green" over a range where `check-commit-trailers.py` reds on 3 rules: it runs the checker's own MUTATION SUITE, never the checker over the actual range, so git-generated merge messages are the one commit class nothing checks | bug | | [#633](https://github.com/mudler/vllm.cpp/issues/633) | `ENG-UPSTREAM-OMNI-PIN` | vLLM-Omni has no parity pin: H3 W3+, LTX-2.5 and ~40 omni-only architectures (the whole TTS family included) cannot be gated against any oracle | feature | +| [#634](https://github.com/mudler/vllm.cpp/issues/634) | `MODEL-MM-indextts2-index-tts2-talker-for-conditional-generation` | IndexTTS-2.5: port the vLLM-Omni TTS lane (GPT-2 AR talker + EnhancedCodec + S2Mel CFM/DiT + BigVGAN) — our first audio-generating model | feature | | [#466](https://github.com/mudler/vllm.cpp/issues/466) | `GATE-27B-FP8-TOWER-GOLDEN` | The SACRED 27B gate cannot execute the FP8 tower: it pins unsloth@890bdef7 (zero FP8 tensors) while every fp8 lever targets nvidia@0893e160 | bug | | [#470](https://github.com/mudler/vllm.cpp/issues/470) | `GATE-27B-FP8-TOWER-GOLDEN` | `PackedGdnDecodeEnvSelected` mirrors only the ENV; the real predicate also requires `in_proj_qkv_fp8.Empty()`, so the 27B gate throws for the wrong reason on any fp8 tower | bug | | [#476](https://github.com/mudler/vllm.cpp/issues/476) | `GATE-27B-FP8-TOWER-GOLDEN` | The 27n fp8-tower arm cannot see a dequant fallback on `out_proj_fp8` or the four `self_attn.*_proj_fp8`: `GdnFp8InProjDebugStats` counts GDN `in_proj` only, and `6603356a` proves that defect is token-invisible | bug | diff --git a/.agents/specs/indextts-2-5.md b/.agents/specs/indextts-2-5.md new file mode 100644 index 000000000..b8debd1ba --- /dev/null +++ b/.agents/specs/indextts-2-5.md @@ -0,0 +1,239 @@ +# SPEC — IndexTTS-2.5, the first audio-GENERATING lane + +**Rows:** `MODEL-MM-indextts2-index-tts2-talker-for-conditional-generation`, +`MODEL-MM-indextts2-index-tts2-s2-mel-decoder` +**Issue:** [#634](https://github.com/mudler/vllm.cpp/issues/634) +**State:** `INVENTORIED` — scoped in this spec, unclaimed, and blocked on +[#633](https://github.com/mudler/vllm.cpp/issues/633). `SPIKE` would owe a +`CLAIM-*` owner these rows do not have. + +## Scope + +Port IndexTTS-2.5 — upstream-supported at +`https://recipes.vllm.ai/IndexTeam/IndexTTS-2.5` and served by vLLM-Omni — so +that text plus a reference clip renders 22.05 kHz speech through our own engine +and the OpenAI-compatible speech surface. + +This is the project's **first audio-generating model**. Every audio path we ship +today consumes audio (Parakeet, Voxtral, `audio_processor.cpp`); nothing +synthesizes it, and `/v1/audio/speech` does not exist. + +In scope: both registered architectures, the mandatory reference-audio +conditioning path, bf16 inference, **the `SpeechEngine` seam, the ABI entry +points and the two OpenAI routes** (none of which exist today — see Shared seams +and ABI), and the gates below. Out of scope: quantized arms (see Risks/decisions), streaming +(`async_chunk=false` upstream, disabled for correctness), and any language whose +support cannot be confirmed against the shipped config. + +## Upstream chain + +`vllm-project/vllm-omni` registers **two** architectures for this model, at +`vllm_omni/model_executor/models/registry.py` @ +`bbe6ccc512a404a2df8c977ea29003002f2683e8` (the same commit the Moss-TTS rows +anchor to): + +| Registry key | Module | Class | +|---|---|---| +| `IndexTTS2TalkerForConditionalGeneration` | `indextts2/indextts2_talker.py` | `IndexTTS2TalkerForConditionalGeneration` | +| `IndexTTS2S2MelDecoder` | `indextts2/indextts2_s2mel_decoder.py` | `IndexTTS2S2MelDecoder` | + +Deploy config `vllm_omni/deploy/indextts2_5.yaml`, selected by `model_type`. +Offline entry point `vllm_omni.model_executor.models.indextts2.end2end`. Serving +is `vllm-omni serve IndexTeam/IndexTTS-2.5 --omni --trust-remote-code`, exposing +`/v1/audio/speech` and `/v1/audio/voices`. + +**Pipeline.** Stage 0 is a ~0.8B GPT-2 AR talker turning text + reference audio +into mel codes. Stage 1 is EnhancedCodec (2.5 replaces IndexTTS-2's RepCodec, and +sets `use_gpt_latent=false`), then an S2Mel CFM/DiT flow-matching decoder, then +BigVGAN, emitting 22.05 kHz mono. About 6 GB VRAM — it fits GB10 with enormous +headroom. + +**Reference audio is mandatory.** Upstream states IndexTTS-2 does not support +text-only synthesis, so the voice-cloning encoders (w2v-bert-2.0, MaskGCT +semantic codec, CAMPPlus speaker embedding) are required port surface, not an +optional extra. They fetch into `checkpoints/hf_cache/` on first run and need +revisions pinned under the NAS checkpoint policy. + +## Our baseline + +Nothing named IndexTTS exists in the tree. What exists and is reusable: + +| Piece | Where | Note | +|---|---|---| +| BigVGAN 1-D core (Conv1d, ConvTranspose1d, replicate/zero pad, Snake/SnakeBeta, alias-free `Activation1d`) | published from `include/vllm/model_executor/models/minimax_h3.h`, gated by BOTH the H3 and LTX-2.5 suites | **already shared across two consumers.** LTX-2.5 (#435, merged) deliberately did NOT copy it: `ltx2_audio_vae.cpp:223-230` records that a second copy of the alias-free trim geometry "goes wrong quietly, because each copy keeps its own green gate while the two audio VAEs drift apart". IndexTTS-2.5 would be the THIRD consumer | +| WAV serialization | `minimax_h3_wav.cpp` | channel-major → interleaved, clamped | +| Flow-matching denoise loop, AdaLN/timestep machinery | the H3 lane | S2Mel is the same shape of computation | +| Conformer encoder | `parakeet_encoder.cpp` | w2v-bert-2.0 is a Conformer | +| Mel front end | `whisper_audio.cpp`, plus LTX-2.5's own (`ltx2_audio_vae_encoder.h`, #641) | two exist; which one w2v-bert-2.0 needs is a W3 question, not an assumption | +| Generation serving seam | `vllm::multimodal::VideoEngine` (abstract, checkpoint-detected; H3 and LTX-2.5 both behind it since #641) | the template for a speech engine seam, and the precedent that a second generative lane extends the seam rather than forking it | +| GPT-2-family backbone | `opt.cpp` | learned absolute positions + LayerNorm; the talker is an additive delta, not a fresh transformer | + +New from scratch: EnhancedCodec, S2Mel, and the three reference encoders. + +## Port map + +| Stage | Upstream | Ours | Kind | +|---|---|---|---| +| AR talker | `indextts2_talker.py` | additive GPT-2 arch routed through `ModelRegistry::Forward` + `dense_attn::AttnBlock` + on-device sampling | new, small | +| Reference encoders | aux checkpoints under `hf_cache/` | partial reuse of `parakeet_encoder.cpp` / `whisper_audio.cpp` | new, largest | +| EnhancedCodec | `indextts2/` | — | new | +| S2Mel CFM/DiT | `indextts2_s2mel_decoder.py` | H3 denoise loop | reuse | +| BigVGAN | — | the shared 1-D core published from `minimax_h3.h`, relocated to a neutral home (W1) | reuse, third consumer | +| WAV 22.05 kHz | — | `minimax_h3_wav.cpp` | reuse | +| `/v1/audio/speech`, `/v1/audio/voices` | `vllm_omni/entrypoints/openai/` | a `SpeechEngine` seam + ABI v19 + two additive routes; see Shared seams and ABI | new | + +Examples and servers stay ABI clients; no internal headers. + +## Shared seams and ABI + +A capability that is not reachable through the shared surface is not done, so +this is scope, not follow-up. + +**Verified state today: nothing is wired.** `include/vllm.h` is at +`VLLM_ABI_VERSION 18` and contains no speech or TTS entry point of any kind. +`ApiServer` registers `/v1/chat/completions`, `/v1/completions`, +`/v1/embeddings`, `/v1/models`, `/v1/audio/transcriptions`, `/v1/videos` and +`/v1/videos/sync`. `audio/speech` and `audio/voices` have zero hits across +`src/`, `include/` and `examples/`. Every audio route we serve today consumes +audio; none produces it. + +**The precedent to mirror is the video lane**, which solved the same problem for +a generative modality: an abstract `vllm::multimodal::VideoEngine` +(`include/vllm/multimodal/video_engine.h:135`) with checkpoint detection, the +whole assembly library-owned behind ABI entry points (`vllm_video_engine_load`, +`vllm_video_generate`, `vllm_video_result_free`, `vllm_video_params_default`, +`vllm_video_engine_family`, and the mux argv pair), `/v1/videos` routing through +that same seam, and `minimax_h3_gen` reduced to a thin `vllm.h` client. H3 and +LTX-2.5 both sit behind it, which is the proof the shape holds for a second lane. + +**What this lane adds**, mirroring that structure rather than inventing one: + +| Piece | Shape | +|---|---| +| `vllm::multimodal::SpeechEngine` | abstract seam, checkpoint-detected, in `include/vllm/multimodal/speech_engine.h`; IndexTTS-2.5 is its first implementation and must not be its only possible one, since the omni TTS family is ~10 more architectures | +| ABI v19 | `vllm_speech_engine_load` / `_free` / `_family`, `vllm_synthesize` (naming symmetric with the existing `vllm_transcribe`), `vllm_speech_params` / `_default`, `vllm_speech_result` / `_free`, and a voice-enumeration pair for the voices route | +| `/v1/audio/speech` | additive route on `ApiServer`, routing through the SAME seam, in OpenAI's wire shape; the reference clip arrives per-request because upstream has no text-only synthesis | +| `/v1/audio/voices` | enumerates registered reference voices; upstream exposes it alongside speech | +| `examples/` | a thin `vllm.h` client only. No internal headers, per the ABI-clients rule | + +The ABI bump is a real version increment with the `test_capi` section that goes +with it, not a header edit: the video lane's v12 bump is the template. + +**Refusal is part of the surface.** Asking a non-TTS checkpoint to synthesize, or +asking for a quant arm that is not implemented, refuses at load naming the +missing piece. An arm that is silently absent is the failure this project has +already recorded; an arm that refuses by name is owed debt. + +## Tests to port + +Upstream's modules are Python and the checkpoint carries its own remote code +under `--trust-remote-code`, which a pure-C++ engine cannot execute. So the +oracle is upstream's modules executed offline and frozen, exactly the +`scripts/gen-minimax-h3-goldens.py` pattern — a `scripts/gen-indextts2-5-goldens.py` +that imports them by file path, with both sides rebuilding weights and inputs +from an identical deterministic stream so no checkpoint byte is committed. + +| Golden | Stage | +|---|---| +| talker logits + emitted mel codes at fixed seed | Stage 0 | +| reference-encoder embeddings (w2v-bert-2.0, MaskGCT, CAMPPlus) | conditioning | +| EnhancedCodec round trip | Stage 1 | +| S2Mel mel output at supplied noise | Stage 1 | +| BigVGAN waveform | vocoder | +| full render, fixed seed / c1 / fixed batch composition | e2e | + +Noise is **supplied**, never sampled, wherever a stage is stochastic — the +comparison must isolate the computation from the RNG, as the H3 +condition-noise gate does. + +## Gates + +**Binding: per-stage numerics vs the checkpoint's own remote code**, at the +tolerances the H3 lane already achieves (that port landed 1.6e-7 worst case on +the DiT forward and 4.2e-9 on BigVGAN), plus **token-exact mel codes** out of the +AR talker at fixed seed. + +Gate conditions are pinned by upstream's own statement that a seed controls both +AR sampling and per-request CFM noise, and that differing concurrent batch +composition does not guarantee a bit-identical waveform: **fixed seed, c1, fixed +batch composition**. A token-exact e2e waveform gate is therefore not available +and is not claimed. + +**Additional ratchet: e2e perceptual.** ASR round-trip through our own Parakeet +(WER against the input text) plus a speaker-similarity band against the reference +clip. This bounds **max relative error from both sides with a measured band**. It +is explicitly NOT a correlation gate — Pearson is scale-invariant and cannot see +a scale error — and NOT a count-based tolerance, which bounds nothing. + +**Memory format is checked against the oracle explicitly.** A token gate cannot +see a dtype that is too wide: it stays numerically correct while moving twice the +bytes. Every f32 on this path owes a one-line reason. + +**Speed axes:** RTF (audio seconds per wall second), TTFB, peak VRAM/RSS, +startup. Denominator is vLLM-Omni's production configuration, bf16 both sides, +never `--enforce-eager`. + +## Dependencies + +- **[#633](https://github.com/mudler/vllm.cpp/issues/633) — hard blocker.** The + oracle is registered at [`.agents/oracles/vllm-omni.md`](../oracles/vllm-omni.md) + (#650) and that file states the position exactly: `pin = UNPINNED`, + `gateable = no`, `evidence = #633`. vllm-omni additionally requires vLLM 0.27.0+ + against our 0.26.0.dev0 parity pin. Until it is pinned there is no oracle this + row can be gated against, and it does not advance past `INVENTORIED`. +- Checkpoint access for `IndexTeam/IndexTTS-2.5` plus the three auxiliary + encoders, at pinned revisions on the NAS. +- W1 touches a header two shipped lanes already depend on, so it coordinates with both H3 and LTX-2.5 (#435, merged). + +## Work breakdown + +| W | Work | Depends on | +|---|---|---| +| W1 | Relocate the ALREADY-SHARED vocoder core out of the `minimax_h3.h` header into a neutral home, plus WAV. Smaller than it first looked: the sharing exists and is gated by two suites, so this is a rename/relocate with a live precedent, not a generalization | — | +| W2 | GPT-2 talker backbone, additive, on the existing decode framework | — | +| W3 | Reference-encoder path (w2v-bert-2.0, MaskGCT, CAMPPlus) | — | +| W4 | EnhancedCodec + S2Mel CFM/DiT on the H3 denoise loop | W1 | +| W5 | Compose the render; goldens per stage | W2-W4, #633 | +| W6a | `SpeechEngine` seam + ABI v19 entry points + `test_capi` section | W5 | +| W6b | `/v1/audio/speech` + `/v1/audio/voices` on `ApiServer`, routed through the seam; example as a thin ABI client | W6a | +| W7 | Speed axes vs the omni oracle | W5, #633 | + +W1-W4 are gateable against frozen upstream goldens without a pin, since they +compare against upstream modules executed offline. W5's e2e claim, W7 entirely, +and any parity statement need #633. W6a/W6b need no oracle at all: a seam and an +ABI are gateable against their own contract, which is why they are not deferred +behind the pin. + +## Risks/decisions + +- **Quantization: bf16 for v1, arms refused and owed.** vLLM-Omni ships no + quantized IndexTTS arm, the same situation recorded for H3, and the model fits + GB10 unquantized. Every unimplemented quant arm refuses at load naming the + missing piece and is recorded as owed — never left to be discovered. +- **We mirror vLLM-Omni, which itself deviates from IndexTeam.** Upstream's + Stage 0 uses plain vLLM sampling and deliberately does not reproduce the + official `num_beams=3` beam search. Mirroring vLLM-Omni is the rule; the + divergence from the reference implementation is recorded, not silently + inherited. +- **Language claims disagree between upstream surfaces.** The recipe page says + zh/en/ja/es/ar; the vllm-omni docs say zh/en/zhen/ja/yue. Resolve against + `indextts2_5.yaml` before anything reaches `docs/FEATURES.md`. Shipping the + wrong list is a user-visible false claim. +- **Licensing.** The checkpoint is under a custom bilibili-model-license, not + Apache-2.0. Check it before any fixture or golden derived from those weights + lands in-tree. +- **The shared vocoder core is gated at H3's and LTX-2.5's hyperparameters, not + this model's.** Reuse is right, and the two existing consumers prove the seam + holds, but it must be re-gated at IndexTTS-2.5's own configuration against the + checkpoint's own remote code. A gate that passes because both arms call the + same helper proves consistency, not correctness. +- **Stop** if the reference-encoder path cannot be reproduced without executing + checkpoint Python: that would make the mandatory conditioning path + un-portable, which is a scope question, not an implementation detail. + +## Now + +`INVENTORIED`, unclaimed, blocked on +[#633](https://github.com/mudler/vllm.cpp/issues/633). Nothing implemented. W1-W4 +can start against frozen goldens once the row is claimed; no parity or speed +claim is possible until the omni pin exists. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 7326e7338..d835bbd30 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -193,7 +193,7 @@ on the committed fixture); reranking/classify models are not yet registered. | Video | ✅ correctness-gated | ✅ | ✅ | ☐ | | Audio | ✅ correctness-gated | ✅ | ◐ | ◐ | | Video+audio GENERATION (MiniMax-H3 DiT, LTX-2.5 DiT) | ◐ H3: all three modalities COHERENT on Q4_K_M (t2va, fl2va, ref2va; §8.20); the NVFP4 arm carries the patch grid; GGUF/NVFP4/bf16 loaders, pruned too (§8.21). LTX-2.5: a second lane, `SPIKE`, gated at reduced dims | ✅ H3 (vllm-omni, BF16-only, no quantized arm); LTX-2.5 only through the generic diffusers adapter, no native recipe ([vllm-omni#6066](https://github.com/vllm-project/vllm-omni/issues/6066)) | ☐ | ☐ | -| Speech / audio GENERATION (TTS, vLLM-Omni lane) | ☐ not started. The six architectures reached by the nine official `vllm-project/recipes` TTS models are inventoried in `.agents/model-matrix.md` (#610); an inventoried row is not a supported one | ✅ (vllm-omni: MOSS-TTS, Qwen3-TTS, Higgs Audio v3, Voxtral TTS) | not assessed | not assessed | +| Speech / audio GENERATION (TTS, vLLM-Omni lane) | ☐ not started. Eight architectures inventoried in `.agents/model-matrix.md`: six from the recipes sweep (#610) plus IndexTTS-2.5 (#634). Inventoried is not supported; all block on the absent `vllm-omni` pin (#633) | ✅ (vllm-omni: MOSS-TTS, Qwen3-TTS, Higgs Audio v3, Voxtral TTS, IndexTTS-2.5) | not assessed | not assessed | | Multimodal over the OpenAI server | ◐ image request path wired, forward pending | ✅ | ✅ | ◐ | Image, video and audio are correct through the CLI and library. Over the HTTP diff --git a/scripts/check-agent-record.py b/scripts/check-agent-record.py index 0e8aeb40a..f005a6952 100644 --- a/scripts/check-agent-record.py +++ b/scripts/check-agent-record.py @@ -65,7 +65,13 @@ # stops at 2.3 (`ltx2_recipes.py:162-166`), with 2.5 still OPEN upstream at # vllm-omni#6066. Same lane as the MiniMax-H3 diffusion row. Bumped because a new # row EXISTS, never to make a transition pass. - "MODEL": (AGENTS / "model-matrix.md", 370), + # 372 since 2026-08-13: +2 for IndexTTS-2.5, which vLLM-Omni registers as TWO + # architectures (`IndexTTS2TalkerForConditionalGeneration` stage 0 and + # `IndexTTS2S2MelDecoder` stage 1), so a port described in prose as "a model" + # moves this pin by two. Both land `INVENTORIED`, unclaimed and blocked on the + # absent vllm-omni pin (#633). Bumped because two rows EXIST, never to make a + # transition pass. #634. + "MODEL": (AGENTS / "model-matrix.md", 372), # 82 since 2026-07-21: +`QUANT-NVFP4-CT-W4A16` (compressed-tensors NVFP4A16 / # W4A16 — NVFP4 weights with BF16 activations, distinct from the existing # `QUANT-NVFP4-CT-W4A4` and `QUANT-NVFP4-MO-W4A16` rows in both scheme diff --git a/tests/scripts/test_agent_record.py b/tests/scripts/test_agent_record.py index 549781aae..988825888 100644 --- a/tests/scripts/test_agent_record.py +++ b/tests/scripts/test_agent_record.py @@ -323,6 +323,33 @@ def test_model_row_ratchet_is_load_bearing(self) -> None: agent_record.check_matrices(errors) require(errors, r"\d+ MODEL rows; expected \d+") + def test_indextts_rows_are_inside_the_model_ratchet(self) -> None: + """The #634 rows and the 370 -> 372 bump are one semantic change. + + IndexTTS-2.5 is registered by vLLM-Omni as TWO architectures, a talker + and an S2Mel decoder, so it moves the pin by two rather than one. That + is the hazard worth pinning: a port described in prose as "a model" is + the shape that lands one row and a bump of two, and the count alone + cannot tell that from two rows landing. Both are named here, and both + are asserted `INVENTORIED` rather than `SPIKE` — they are unclaimed and + blocked on #633, and `SPIKE` would owe a `CLAIM-*` owner they do not + have. + """ + errors: list[str] = [] + rows, _ = agent_record.check_matrices(errors) + self.assertEqual([error for error in errors if "MODEL rows" in error], []) + + for item_id in ( + "MODEL-MM-indextts2-index-tts2-talker-for-conditional-generation", + "MODEL-MM-indextts2-index-tts2-s2-mel-decoder", + ): + found = [row for row in rows if row.item_id == item_id] + self.assertEqual(len(found), 1, item_id) + self.assertEqual(found[0].path.name, "model-matrix.md", item_id) + self.assertEqual( + found[0].field("state").strip().strip("`"), "INVENTORIED", item_id + ) + def test_recipe_backfill_rows_are_inside_the_model_ratchet(self) -> None: """The #609/#610 rows and the 362 -> 369 bump are one semantic change. From 43a6c551890f2d820279b1832fd115345808401d Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:25:34 +0200 Subject: [PATCH 11/55] =?UTF-8?q?W2=20(KERNEL-SSM-MAMBA):=20the=20Mamba2?= =?UTF-8?q?=20SSD=20CUDA=20arm=20=E2=80=94=209/9=20mutations=20caught=20ag?= =?UTF-8?q?ainst=20the=20corrected=20bound=20(#496)=20(#675)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL W2 of KERNEL-SSM-MAMBA (#496): the CUDA arm of vt::Mamba2ChunkScan, vt::Mamba2StateUpdate and vt::RmsNormGatedGroup. Supersedes #566 and #592, which had diverged -- one carried the evidence resolving the merge precondition, the other the F1/F2 repair, and neither contained the other. F1: the declared equivalence contract named the elementary functions as the ONLY admitted source of device-vs-host divergence. Host C++ is pinned -ffp-contract=off and nothing passes --fmad=false to nvcc, so acc += xv * bv contracts on device and not on host. Stated model 3.5*K*u, contraction adds ~1.0*K*u, bound was 4*(K+2)*u -- provable only for K <= 18 while the driver shapes run at K = 200. Repaired by carrying the term to 5*(K+2)*u with the arithmetic shown in the header, all three test comments and 8.3. -fmad=false was rejected deliberately: it is a per-TU flag on a header included by the hot GDN TU. Repo-wide gap filed as #591. F2: the header claimed the device kernels stay memory-safe under a contract violation, but the validator checks metadata shape and dtype only. Two dropped checks were memory-unsafe -- an out-of-bounds WRITE past a cudaMallocAsync allocation and an out-of-bounds READ of initial_states -- and the stated reason for omitting them (a D2H plus a stream sync) did not apply, since both values are already in registers and the decode kernel does exactly that clamp for free. Repaired with both the clamps and a narrowed claim enumerating all six dropped checks. A third hole the review did not name was found and closed: seq_idx[0] < 0 indexes passed at chunk -1. All four owed gates discharged by measurement, operator-run on the gate host, each stamping its own lock-acquire time, load and disk. nvcc compile for sm_121a clean with CUTLASS, FA2 and Marlin confirmed ENABLED in the configure log rather than assumed. The three CUDA arms 12/2095, 10/5965, 12/3723 all SUCCESS. compute-sanitizer memcheck ERROR SUMMARY 0 errors. And the 9-mutation re-sweep against the MOVED bound: 9 of 9 CAUGHT. The re-sweep is the item a reader would most likely have waved through, and M6 is why it could not be: it aborts at exit 134 while printing "assertions: 2577 | 2577 passed | 0 failed" -- a clean assertions line on a FAILING run -- and is caught only because the harness reads the exit code. Two of the original eight mutations did not COMPILE under -Werror=all-warnings and were being scored as caught; a mutation that will not build is a suite that never ran. test_minimax_h3 is attributed rather than waived. Reproduced standalone on an idle box under the lock at TEST_EXIT=139, it is #486 with root cause #516, signature-for-signature. The independent baseline that PASSED was row/pool-device-key, the branch that FIXES #516, so the baseline carried a fix and this branch does not carry a defect. CI is REMOTE_UNVERIFIED, not green: every run on the predecessor branches ended cancelled, including a repo-wide mass cancellation of 20 runs across 7 branches, and cuda-fat-build never completed -- which is why the compile was run directly on the gate host. Windows reds are the main baseline (#514, #584). The row stays INVENTORIED. No lifecycle move, no measurement claimed, no performance result: a host reference plus its device arm is not a speed number. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/specs/mamba2-ssd.md | 637 +++++++++++++++++- src/vt/cuda/cuda_gdn.cu | 22 + src/vt/cuda/cuda_mamba2_ssd.cuh | 692 ++++++++++++++++++++ tests/vt/test_ops_mamba2_gated_norm.cpp | 349 ++++++++++ tests/vt/test_ops_mamba2_ssd.cpp | 747 ++++++++++++++++++++++ tests/vt/test_ops_mamba2_state_update.cpp | 544 ++++++++++++++++ 6 files changed, 2980 insertions(+), 11 deletions(-) create mode 100644 src/vt/cuda/cuda_mamba2_ssd.cuh diff --git a/.agents/specs/mamba2-ssd.md b/.agents/specs/mamba2-ssd.md index b33042335..e9c9114de 100644 --- a/.agents/specs/mamba2-ssd.md +++ b/.agents/specs/mamba2-ssd.md @@ -283,12 +283,23 @@ FAIL, round 2 PASS). `KERNEL-SSM-MAMBA` stays `INVENTORIED`: this is a host reference, not generic Mamba support, and no lifecycle state moved, so it owes no `STATUS.md` / `BENCHMARKS.md` projection. No performance claim is made. -**Owed before the row can move:** W2 (the CUDA arm, byte-compared to these host -references, `compute-sanitizer` clean on dgx), W3 (the `MambaSpec` producer for -Mamba2 shapes), and the one missing decode refusal `SUBCASE` recorded in §8.2. - -**Next action:** dispatch a fresh implementer for W2 (CUDA), and fold the §8.2 -`SUBCASE` into that task since it touches the same suites. +**W2 (2026-08-13):** the CUDA arm for all three ops is implemented and gated — +see §8.3 for the equivalence contract it was written against, which supersedes +§6's "byte-compared to W1" exit criterion with a named reason (the two arms call +different libms, so a byte compare is not reachable; the primary gate is the +device output against the same double-precision reference at the same +upstream-ported tolerances). The §8.2 decode `SUBCASE` is CLOSED and re-proved. +`KERNEL-SSM-MAMBA` still stays `INVENTORIED`: this lands `src/`, `include/`, +`tests/` and this spec only, no lifecycle state moved, and no performance claim +is made. + +**Owed before the row can move:** W3 (the `MambaSpec` producer for Mamba2 +shapes), a fresh scoped review of W2, and the two residuals named in §8.3 — +routing the device-side `A < 0` and `state_indices` precondition checks through +the deferred error ring, and #547 (`ReferenceTierEligible` gating on +`UnifiedMemory()` where it needs `DeviceMemoryIsHostAddressable()`). + +**Next action:** dispatch a fresh scoped review of W2, then W3. ### 8.1 W1 progress (host references landed, awaiting a fresh scoped review) @@ -399,8 +410,22 @@ half of F4. Dropping `CheckMamba2ANegative` at `cpu_ops.cpp:1877` leaves chunk-scan twin at `:1633` reds. The guard itself is present, correct and reachable (a direct probe refuses `A` = `+1.0`, `0.0`, `-0.0` naming `A_log`, and accepts `-1e-30` and `-9.8e-45`), so this is a missing mutation-proof, not -a defect — **owed:** an "A must be negative" `SUBCASE` on the state-update -refusal case mirroring `tests/vt/test_ops_mamba2_ssd.cpp:900`. +a defect — ~~**owed:** an "A must be negative" `SUBCASE` on the state-update +refusal case mirroring `tests/vt/test_ops_mamba2_ssd.cpp:900`.~~ **CLOSED in W2** +(`tests/vt/test_ops_mamba2_state_update.cpp:658`). Re-proved here rather than +taken on report, as mutation **M9** of the W2 sweep: deleting +`CheckMamba2ANegative(A, "mamba2_state_update");` at `cpu_ops.cpp:1877` — the +call site is unique, the other two hits of that symbol being its definition at +`:1582` and the chunk-scan call at `:1633` — takes +`test_ops_mamba2_state_update -tc=mamba2 state update refuses the arms it does +not implement` from `1 passed | 0 failed`, `assertions: 11 | 11 passed`, +`Status: SUCCESS!` to `0 passed | 1 failed`, `assertions: 11 | 8 passed | +3 failed`, `Status: FAILURE!` — the three reds being `CHECK(threw)`, +`CHECK(msg.find("A_log") != npos)` and `CHECK_THROWS(...) did NOT throw at all` +at `:672`, `:673`, `:680`. The pristine binary was run under the identical +filter FIRST, so the filter is proved to select a non-zero assertion count +rather than nothing; `cpu_ops.cpp` was restored byte-for-byte and its md5 +re-asserted at `9ed9eb980c239eca37ec7d92bfe0e766`. One repo-wide test trap found while capturing the RED output, and worth carrying to any doctest suite: **doctest 2.5.2 `INFO` prints a `const char*` VARIABLE as @@ -412,9 +437,599 @@ reported `1: worst element ...` instead of naming the tensor. The labels are Named residuals unchanged from §2: the CUDA arm (W2), `n_groups` TP sharding, spec-decode temporal state, ReplaySSM and Mamba v1. One more, from the port itself: the Triton dots downcast their tile inputs (`ssd_chunk_state.py:283-285`, -`ssd_chunk_scan.py:266-269`) where this host reference stays f32, so W2's -device-vs-host comparison is a tolerance comparison at the activation dtype, not -a byte compare. +`ssd_chunk_scan.py:266-269`) where this host reference stays f32. + +**Corrected by §8.3, which supersedes this paragraph.** As originally written it +predicted that W2 would mirror those downcasts, and therefore that W2's +device-vs-host comparison would be "a tolerance comparison at the activation +dtype, not a byte compare". W2 did NOT mirror them — §8.3 point 1 records the +decision and why — so **both arms stay f32** and the dtype is not the reason a +byte compare is out of reach. The reasons are **libm and FMA contraction** +(§8.3 points 4 and 4b). Only the conclusion survived; its stated cause did not, +and two different causes for one fact in one spec is the drift this note closes. + +### 8.3 W2 — the declared equivalence contract for the CUDA arm + +This section is the spec copy of the contract the W2 implementer decided +**before** writing the kernel and recorded in `src/vt/cuda/cuda_mamba2_ssd.cuh` +and in all three test headers. Its original copy was a staged blob lost with the +worktree; it is re-authored here from the recovery commit `fcdb7d824`, unchanged. +It is a contract, not a tolerance budget: nothing in it was renegotiated to make +a run pass. + +**1. f32 accumulation throughout; the upstream tile downcasts are deliberately +NOT mirrored.** Upstream downcasts its tiles before `tl.dot` — `b.to(x_ptr. +dtype.element_ty)` at `ssd_chunk_state.py:283-285`, `cb.to(...)` and +`prev_states.to(C_ptr.dtype.element_ty)` at `ssd_chunk_scan.py:266-269` and +`:359-363`. Those casts are the **input-precision requirement of `tl.dot`**, i.e. +of a tensor-core MMA, not a statement of the algorithm: every one of those tiles +is loaded with an explicit `.to(tl.float32)` and computed in f32 right up to the +instant it is fed to the MMA. These are scalar-FMA kernels with no MMA, so +mirroring the downcast would copy a constraint we do not have, and would be lossy +for nothing. + +**2. This is not the "too wide" deviation §7 warns about, and the distinction is +checkable.** A token gate cannot catch a dtype that is too wide +([[token-gates-cannot-see-dequant-fallbacks]]), so the claim is made about the +**memory format**, which is byte-for-byte the host arm's: every load and store +goes through the operand's own declared dtype (`M2Load` / `M2Store`); `states` +and `CB` are f32 because upstream pins them there (`states_in_fp32=True`, +`ssd_combined.py:100-102`; `output_dtype=torch.float32`, `:124`); and the +inter-chunk `passed` buffer is allocated at **`state_dtype`**, *not* at the host +reference's f32 working width, which §8.2 F9 explicitly flagged as a width W2 +must not inherit. No extra byte moves. Only the register precision of one product +differs, and it differs in the direction Triton itself takes wherever it is not +feeding an MMA. + +**3. Accumulation ORDER is part of the port.** Except in the gated norm's group +reduction — which is a block reduction, and says so at the kernel — every +accumulation runs in ONE thread, over the SAME index range in the SAME direction +as the host reference. That is deliberate: order is the *amplifying* source, and +pinning it holds the derived bound to the two terms named in points 4 and 4b. + +**4. A byte compare against the host arm is NOT reachable, and the downcasts are +not why.** The two arms call different libms — CUDA `expf` is documented at +≤ 2 ulp, glibc's at ≤ 0.5 — and the gated norm additionally reorders one +non-negative reduction. §9's third stop condition ("the device arm cannot reach +the host reference byte-for-byte") is therefore resolved as **not reachable for +a named, non-defect reason**, and the gap is kept open in the form below rather +than closed by widening anything. + +**4b. The second reason is FMA CONTRACTION, and the original derivation omitted +it.** Corrected here after a fresh review of PR #566 (finding F1, MEDIUM); +points 3 and 4 as first written claimed the elementary functions were the *only* +admitted source, and that was false as written. Host C++ is pinned +`-ffp-contract=off` (`CMakeLists.txt:41-56`) precisely so `a*b + c` keeps two +roundings. **Nothing passes `--fmad=false` to nvcc** — `grep -rn fmad +CMakeLists.txt cmake/` returns nothing — so `cuda_mamba2_ssd.cuh` compiles at +nvcc's **default `--fmad=true`**, and every `acc += a*b` in it is a +single-rounding `fma` whose host twin is not. This project has *measured* that +exact idiom: `.agents/benchmark-record.md:532` records a pre-rounded `v²` +differing by ≤ 1 ulp from the nvcc-`fmad` form and flipping a near-tie at token +108. `CMakeLists.txt:41-56` carves CUDA out of the contraction policy on the +grounds that "GPU parity tests compare GPU-vs-GPU"; **G2 is precisely the case +that carve-out does not cover**. + +Nothing was hidden empirically — the worst audited comparison used 7.66% of the +bound and the driver shapes 0.32% / 0.18% — so this is a **derivation-accuracy** +defect, not a numerical failure. It is repaired by carrying the term, not by +turning the flag off: + +- **`-fmad=false` on the TU: REJECTED.** nvcc takes the flag per *translation + unit*. `cuda_mamba2_ssd.cuh` is a **header**, included by `cuda_gdn.cu:48`, so + the only ways to apply it are (a) de-contract every GDN kernel in that TU — a + measured hot decode path ([[gdn-packed-bridge-closes-31pct-decode-gap]]) — or + (b) split a new `src/vt/` TU, which the *Placement* deviation below already + records as blocked on #515. Slowing a shipped kernel to make a bound's prose + true is the wrong trade. +- **Carrying the term: TAKEN.** The bound moves from `4·(K+2)·u` to + `5·(K+2)·u`; the arithmetic is in point 6. + +**5. The primary gate is therefore NOT device-vs-host.** It is the device output +against the **same double-precision sequential reference** the host arm is held +to, at the **same upstream-ported tolerances**, on the **same inputs** — e.g. atol +8e-3 / rtol 5e-3 from `test_mamba_ssm_ssd.py:210-213` on the driver shapes. Both +arms are asserted against it in the same test case, so a failure separates +cleanly: device-only means a device defect; both means the cited upstream +threshold does not cover this shape, which is a `NEEDS_DECISION`, not a wider +tolerance. + +**6. The derived device-vs-host bar is `rtol(K) = 5·(K+2)·2⁻²⁴`** +(`ExpectDeviceMatchesHost` / `DerivedRtol`, in all three suites). Three terms, +one per admitted source: + +| term | bound | source | +|---|---|---| +| libm | `2.5·K·u` | ≤ 2.5 ulp per decay factor (CUDA `expf` ≤ 2, glibc ≤ 0.5) through a product of at most `K` | +| summation | `(K-1)·u` | the standard forward error of a length-`K` f32 sum, which is what *amplifies* the libm difference | +| contraction | `K·u` | the `K` product roundings the host keeps under `-ffp-contract=off` and the device's `fma` does not (point 4b) | + +Total `≤ 2.5·K·u + (K-1)·u + K·u = 4.5·K·u − u`, and `5·(K+2)·u` covers it +for every `K ≥ 0` because `5K + 10 ≥ 4.5K − 1` reduces to `0.5K + 11 ≥ 0`. + +**The previous `4·(K+2)·u` did not, and that is why the constant moved.** +`4.5K − 1 ≤ 4K + 8` reduces to `K ≤ 18`, so the old bound was provable only up +to `K = 18` — while the driver-shapes case runs at `K = T = 200` +(`test_ops_mamba2_ssd.cpp`, §1.4 shapes). The constant changed because the +**derivation gained a term the build actually emits**, not because a run needed +slack: re-scaling §8.4's audit by `4/5`, the worst of the 55 comparisons goes +from 7.66% to **6.13%** of budget, the driver shapes from 0.32%/0.18% to +**0.26%/0.14%**, and mutant M3 from 962173% to **769738%** — still caught by +four orders of magnitude. **No number is tuned**; `K` is the sequence length the +comparison actually ran at. +Because a bar nobody audits is a false claim, every comparison logs the +**fraction of the budget actually used** through `MESSAGE` — not `INFO`, because +doctest prints `INFO` only on failure, so an `INFO` would have been invisible on +the green run that the claim rests on. + +**Two deviations recorded with the arm, carried forward deliberately:** + +- **Placement.** The kernels live in `src/vt/cuda/cuda_mamba2_ssd.cuh`, included + by `cuda_gdn.cu`, not in a new `.cu`. Same reason as W1's `cpu_ops.cpp` + placement (§8.1): a new library TU must be listed in the root `CMakeLists.txt`, + which `check-doc-checkpoint` classifies `user_usage` + `landing_page`, so any + new `src/vt/` file owes a `docs/USAGE.md` update that a kernel exposing no + command, config key or C-ABI entry point has nothing true to write (#515). +- **The device arm re-checks NONE of the host arm's metadata VALUE checks.** + Corrected here after the fresh review of PR #566 (finding F2, MEDIUM), which + established both that the list was longer than "`A < 0` and `state_indices` + distinctness" and that the memory-safety claim attached to it was broader than + the kernels guarantee. + + The **shared** validator (`Mamba2ChunkScan` / `Mamba2StateUpdate`, + `src/vt/ops.cpp`) checks metadata **shape, dtype and device only** + (`CheckI32Meta`, `ops.cpp:1717-1723`). Every **value** check lives in the host + kernel, where the data is host-readable (`cpu_ops.cpp:1622-1648`). The device + arm therefore does not check, in full: + + 1. `A < 0` (`CheckMamba2ANegative`) — value-only + 2. `state_indices` distinctness (§8.2 F8) — value-only + 3. the `cu_chunk_seqlens` tiling of `[0,T)` — **memory-unsafe** + 4. per-chunk `0 < len ≤ chunk_size` — **memory-unsafe** + 5. `seq_idx[c] ∈ [0,S)` — **memory-unsafe**, now CLAMPED + 6. `0 ≤ last_chunk_indices[b] < nchunks` (§8.2 **F7**) — **memory-unsafe**, now CLAMPED + + The reason for dropping them is unchanged and still holds for the *reads*: + the operands are on-device, so re-reading them costs a D2H plus a stream + synchronise per call — the host tax the GDN prefill path was rebuilt to remove + — and makes the op uncapturable in a CUDA graph, mirroring the policy + `cuda_gdn.cu:8-13` states for exactly this case. + + **But that reason does not reach 5 and 6, and the review was right that the + file was internally inconsistent.** Both values are already **in device + registers** at their use sites, and the decode kernel has always clamped its + `state_indices` slot for free on exactly that basis. Unclamped, `lci[b] ≥ + nchunks` makes the `passed` store an out-of-bounds **write** past the + `cudaMallocAsync` allocation, and a `seq_idx[c] ∉ [0,S)` an out-of-bounds + **read** of `initial_states` (with `seq_idx[0] < 0` additionally indexing + `passed` at chunk −1). Both are now **clamped in registers**, which is free, + matches the decode kernel, and matters because W4 is about to become the first + caller of these ops. The clamps do **not** restore the checks: out-of-contract + metadata still yields a **wrong answer**, now with a defined shape ("the chunk + loop stops at `nchunks`", "that chunk opens with a zero previous state"). They + bound only *where* it is read from. Pinned by a device-only case + (`test_ops_mamba2_ssd.cpp`, "clamps out-of-contract metadata in registers") + against in-contract reference runs, so the assertions are exact. + + **3 and 4 are NOT clamped and the arm is NOT memory-safe under them** — a + garbage `ccs` yields a `start`/`len` that index `x`/`B`/`C`/`z`/`out` out of + bounds in every stage, and bounding that needs `T` at each use site plus a + clamp inside the inner loops, which is not free. That is stated as unsafe + rather than folded into a blanket "memory safe" claim. **Owed, not implemented + here:** route 1–4 through the deferred device error ring at + `cuda_ops.cu:790-940`. + +**Not fixed here, filed as #547.** The W2 RED run SIGSEGV'd on all three +binaries. GB10 reports `Backend::UnifiedMemory() == true`, so +`ReferenceTierEligible(kCUDA)` is true and, with no native kernel registered, +`GetOp` installs the CPU host kernel as a `vt-cpu-ref` provider over `cudaMalloc` +pointers — which `include/vt/backend.h` already says are not host-dereferenceable +on GB10. `op_provider.cpp:515-526` gates on `UnifiedMemory()` where it needs +`DeviceMemoryIsHostAddressable()`. That is shared-seam semantics across three +backends, so it takes its own row. Every CUDA case here calls +`RequireNativeCudaProvider`, so a device arm can never be gated by running the +host arm twice. + +### 8.4 W2 evidence (gate host `promaxgb10-4ad8`, GB10 / sm_121a, 2026-08-13) + +Build recipe, both arms: `cmake -G Ninja -DVLLM_CPP_CUDA=ON +-DVLLM_CPP_CUDA_ARCHITECTURES=121a -DVLLM_CPP_CUTLASS_DIR=$HOME/cutlass-4.5.0 +-DVLLM_CPP_TRITON=ON -DVLLM_CPP_BUILD_TESTS=ON`. The configure log was READ, not +assumed: `cutlass-nvfp4: ENABLED`, `cutlass-fp8: ENABLED`, `marlin-nvfp4: +ENABLED`, `fa2: ENABLED for [121a]`, `CUTLASS found at ~/cutlass-4.5.0` — an +absent CUTLASS silently falls back and the arm would not be the shipped one. +**0 warnings** in both build logs. + +**Release, after the `origin/main` re-merge** — identical to the pre-merge +counts, so the merge moved nothing: + +| suite | test cases | assertions | status | +|---|---|---|---| +| `test_ops_mamba2_ssd` | 11 / 11 passed | 2069 / 2069 | `SUCCESS!` | +| `test_ops_mamba2_state_update` | 10 / 10 passed | 5965 / 5965 | `SUCCESS!` | +| `test_ops_mamba2_gated_norm` | 12 / 12 passed | 3723 / 3723 | `SUCCESS!` | + +**Debug arm** (`CMAKE_BUILD_TYPE=Debug`, so `NDEBUG` is OFF and every `assert` +in the tree is live; CXX `-g -O0`, CUDA `-g` and deliberately *not* `-G`, which +would disable device optimisation and change what was measured): the same +`11 / 2069`, `10 / 5965`, `12 / 3723`, all `SUCCESS!`, exit 0. This arm exists +because the gate build is `-O3 -DNDEBUG`, where an assert-abort defect stays +latent behind a green Release run. + +**`compute-sanitizer`, 8 runs, all `ERROR SUMMARY: 0 errors` / `EXIT=0`:** +`memcheck` on the ssd optional-arms + dtype-knobs case, on the continuous-batch +`initial_states` subcase, and on both decode suites' `*CUDA arm*` cases; +`initcheck` on three; `synccheck` on the gated norm, which is the one kernel +with a block reduction and `__syncthreads`. + +**Mutation sweep — 9 of 9 CAUGHT.** Each mutation patches one source file, is +rebuilt, and is run under a doctest `-tc` filter; the **pristine** binary is run +under the *identical* filter first, because a filter that selects no test case +makes doctest print `SUCCESS!` and an unverified filter would score a false +catch. Sources restored byte-for-byte after each, md5 re-asserted +(`cuda_mamba2_ssd.cuh` `cbb1f928f4b421bdea2e24476012eed2`, `cpu_ops.cpp` +`9ed9eb980c239eca37ec7d92bfe0e766`). + +| # | mutation | control assertions | mutant | +|---|---|---|---| +| M1 | drop the inter-chunk state term | 27 `SUCCESS!` | `FAILURE!` | +| M2 | ignore `initial_states` in state passing | 297 `SUCCESS!` | `FAILURE!` | +| M3 | read `states[c]` for `states[c-1]` | 297 `SUCCESS!` | `FAILURE!` | +| M4 | drop the `D` skip connection | 570 `SUCCESS!` | `FAILURE!` | +| M5 | ignore `state_indices` (slot = row) | 1318 `SUCCESS!` | `FAILURE!` | +| M6 | treat the NULL row as slot 0 | 1318 `SUCCESS!` | `FAILURE!` | +| M7 | `n_groups = 1` in the kernel, launcher grid unchanged (see below) | 9 `SUCCESS!` | `FAILURE!` | +| M8 | sigmoid instead of silu | 9 `SUCCESS!` | `FAILURE!` | +| M9 | drop `CheckMamba2ANegative` on decode (§8.2) | 11 `SUCCESS!` | `FAILURE!` | + +**Two mutations had to be REFORMULATED, and that is worth carrying.** The +obvious form of M1 (`if (!prev_zero)` → `if (false)`) and of M7 (passing +`1, hidden` for `n_groups, group_size`) do not COMPILE: the CUDA arm is built +`-Werror=all-warnings`, and nvcc raises `#550-D "prev_zero was set but never +used"` and `#177-D "group_size was declared but never referenced"` once the +mutation dead-codes the read. A mutation that will not build is not a caught +mutation and must not be scored as one. Both were rewritten to drop exactly the +same term while leaving every variable read — M1 multiplies the inter-chunk +product by `0.0f`, M7 passes `1, group_size * args.n_groups` (which *is* +`hidden`) — and both then failed as intended. + +**M7's label is narrower than "whole-row variance", and the fresh review of +#566 was right to say so (finding F3, LOW).** The reformulated M7 forces +`n_groups = 1` in the *kernel* while the launcher's `nblocks = rows * +args.n_groups` is left alone, so blocks `blk ≥ rows` compute `r = blk / 1 ≥ rows` +and read and write past the tensor. The device mutant is therefore +**memory-unsafe**, and fails partly for that rather than purely on the +whole-row-variance arithmetic. The guarantee IS pinned — the reviewer ran a +clean CPU twin of the same mutation, with no grid mismatch, and it reds on the +intended assertion — so only the label was wrong, and it is corrected in the +table above. A device mutant that is also memory-unsafe is a weaker instrument +than one that is not, and is recorded as such rather than re-scored. + +**Full `ctest` on the gate host**, all 392 test targets built (777 ninja edges, +0 warnings), `ctest -j 1` — serial is required, not cautious: GB10 memory is +UNIFIED, so a parallel CUDA suite reserves HOST RAM and has OOM-rebooted this +box, and several suites starve under `-j` and red spuriously. Result: +**`98% tests passed, 10 tests failed out of 431`**, `CTEST_EXIT=8`, 53 min. + +None of the three mamba2 suites is among the failures. Nine of the ten match, by +name, an **independent same-box baseline** — another agent's full `ctest` on +`row/pool-device-key` finishing 80 minutes earlier, `98% tests passed, 9 tests +failed out of 437`: `test_serve_low_tools`, `test_linear_method`, +`test_glm4_moe_lite_paged_engine`, `test_capi` (SEGFAULT), `test_ops_gdn`, +`test_qwen3_apc_e2e`, `test_minicpm3_paged_engine`, `test_internlm2_paged_engine`, +`test_llama_paged_engine`. Two branches, two builds, the same nine. + +The tenth, **`test_minimax_h3` (SEGFAULT at 11.81 s)**, passed on that baseline +and was the one difference, so it was NOT dismissed. It is now **fully +attributed, and it is not this brick**. + +It was RE-RUN STANDALONE, serially, under the lock, on a box that had just +rebooted and was idle (`up 5 min`, load 0.12, no CUDA process resident) — so the +contention hypothesis this section previously recorded as "plausible, not +proven" is **REFUTED**: it reproduces with nobody else on the GPU. It is instead +**#486**, already filed and open: *"test_minimax_h3 is RED on dgx (GB10): +cudaFree invalid argument + SIGSEGV when two CUDA cases run in one process"*. +The standalone re-run reproduces that issue's recorded signature exactly, number +for number: + +- `minimax_h3: the WHOLE t2va path composes end to end` throws + `vt cuda: cudaFree: invalid argument` (`test_minimax_h3.cpp:3537`); +- `minimax_h3: an NVFP4 checkpoint loads into a runnable DiT` then SIGSEGVs + (`:3977`); +- `test cases: 38 | 36 passed | 2 failed | 41 skipped`, `assertions: 42724 | + 42724 passed | 0 failed` — the identical counts #486 records. + +#486 further records that each case passes ALONE and only crashes when the two +run in one process, i.e. cross-test CUDA state, and that an A/B in another +agent's tree already proved it was not THEIR change either. Its root cause is +tracked as **#516** — *"vllm::Pool() free list is keyed by size class with no +DEVICE in the key: a cudaMalloc'd block can be handed to a CPU DBuf"* — which is +exactly what `row/pool-device-key` is repairing, and therefore exactly why the +baseline branch passed a test that main-based branches fail. The difference was +the baseline carrying a FIX, not this branch carrying a defect. + +Two independent lines already pointed the same way and are kept because they +remain true: **no model or layer code calls these ops at all** — `grep` for +`vt::Mamba2ChunkScan`, `vt::Mamba2StateUpdate` and `vt::RmsNormGatedGroup` +outside `src/vt/` returns `include/vt/ops.h` declarations and the three unit +tests, nothing else — so the H3 path cannot reach a kernel this brick added; and +the only W2 delta it could see is three extra registrations in the op table. + +So **all ten ctest failures are pre-existing and tracked** (#486 / #516 for +`test_minimax_h3`, #233 for `test_glm4_moe_lite_paged_engine` "plus 4 more +pre-existing ctest failures", and the same-name baseline for the rest). None is +attributable to W2. The standalone log is `~/w2ssd-evidence/refail.log`. + +**All ten reproduce STANDALONE** on the idle box, one `ctest -R` per binary under +the lock — `CTEST_EXIT=8` for every one of `test_minimax_h3`, `test_ops_gdn`, +`test_capi`, `test_serve_low_tools`, `test_linear_method`, +`test_glm4_moe_lite_paged_engine`, `test_qwen3_apc_e2e`, +`test_minicpm3_paged_engine`, `test_internlm2_paged_engine`, +`test_llama_paged_engine`, `W2_REFAIL_DONE`. That matters in both directions: it +rules out "the 431-test run starved them", so none of the ten is a contention +flake to be waved through, and it confirms they are deterministic reds that +exist independently of this branch — which is what makes the same-name baseline +comparison sound rather than a coincidence of two noisy runs. + +**A live protocol defect found while doing this, and fixed.** The re-run had +been relaunched (not by this session) as +`flock -w 3600 $HOME/gpu.lock ./w2refail.sh` — an OUTER `flock` wrapping a +script that takes the SAME lock itself on its own fd. `flock` locks an open file +DESCRIPTION, so the inner acquisition blocks against the outer one held by its +own parent: a self-deadlock that **held the shared GPU mutex while making no +progress**, with three other agents' jobs queued behind it. Killed the stack, the +lock passed straight to a waiter, and the re-run was relaunched with its own +single acquisition. Anything wrapping a script that already locks must not lock +again. + +**The gate host rebooted TWICE during this window** (08:57 and ~09:30 CEST, +`up 14 min` then `up 5 min`), which is the documented GB10 unified-memory +OOM-reboot under multi-agent load, and it killed two queued attempts before the +third landed. Recorded because it is the environment every measurement on this +box is taken in, not as an excuse for any result above. + +**CI: the two Windows jobs are the `main` BASELINE, not this branch.** +`windows-msvc-cpu` and `windows-msvc-vulkan` fail on PR #566, and the baseline +was subtracted rather than assumed: **#580** (`row/ENG-ISSUE-TABLE-INTAKE`, a +records-only change) fails both at 22m15s / 22m6s, and **#576** — already +*merged* to `main` — failed both at 22m6s / 21m33s. Two fixes are in flight for +exactly these: **#583** "Invoke-Checked rejected the empty argument list every +no-arg test uses" (#512), which is the failing step here — *Build and execute +the native Windows CPU focused gate* — and **#578** "the Windows arm cannot +compile `test_backend_cross_device`" (#514, #540). This branch's diff since the +merge is one markdown file and cannot reach an MSVC build; no Windows-arm claim +is made or repaired here. + +**The derived bar is audited, not asserted.** Across the 55 device-vs-host +comparisons in a green run, the worst one used **7.66%** of `rtol(K) = +4·(K+2)·2⁻²⁴`; the driver shapes used 0.32% and 0.18%. For contrast the same +`MESSAGE` line under mutant M3 reads `used 962173% of its derived budget`. So +the bound is neither tuned down to the observed error nor wide enough to hide a +defect. + +These percentages are **against the bound as it stood at this run**, +`4·(K+2)·2⁻²⁴`. §8.5 corrects the derivation and the bound is now +`5·(K+2)·2⁻²⁴`, against which the same measurements read 6.13%, 0.26%/0.14% and +769738%. The numbers above are left as captured rather than restated, because +they are what the run produced; §8.3 point 6 carries the conversion. + +### 8.5 W2 tightening pass (fresh review of PR #566 returned PASS + 5 findings) + +`row/KERNEL-SSM-MAMBA-SSD-W2-FIX`, branched from `1e819144e` with `origin/main` +merged. The review's **verdict was PASS**; it verified the equivalence contract's +central MMA reasoning, reconstructed the reformulated mutations on CPU twins, +and confirmed the grep, the recovered-byte md5s and the CI baseline subtraction. +None of that was re-done. What changed: + +- **F1 (MEDIUM, derivation accuracy)** — repaired by carrying the FMA-contraction + term, not by disabling contraction. `-fmad=false` was rejected on the grounds + in §8.3 point 4b: it is a per-TU flag on a header included by `cuda_gdn.cu`, so + it would de-contract a measured hot decode path. `DerivedRtol` is + `5·(K+2)·2⁻²⁴` in all three suites; §8.3 point 6 carries the arithmetic and the + re-scaled audit. +- **F2 (MEDIUM, over-broad memory-safety claim)** — repaired by taking BOTH + halves the finding offered: the two free register-local clamps AND the narrowed + claim. The header and §8.3's second deviation now enumerate all six dropped + value checks, mark which are memory-unsafe, and state plainly that the + `cu_chunk_seqlens` tiling and length checks are **NOT** clamped and **NOT** + safe. `M2ChunkScanKernel` gained an `S` parameter for the `seq_idx` clamp. +- **F3, F4 (LOW, record accuracy)** — M7's label corrected in §8.4 with the grid + mismatch stated; §8.2's superseded downcast-tolerance sentence reconciled + against §8.3. +- **F5 (LOW)** — taken. The five per-call `cudaMallocAsync` scratch buffers are + held by an `M2Scratch` scope guard, so a throw on the Nth no longer leaks the + N-1 before it. `Release()` also frees all five before reporting, where the + open-coded sequence it replaces leaked the remainder on a mid-sequence failure. + +**Evidence, CPU box `promaxgb10` worktree host, `df -h /` = 85% used (67G free) +before and after every result below.** + +| suite | test cases | assertions | status | +|---|---|---|---| +| `test_ops_mamba2_ssd` | 8 / 8 passed | 1175 / 1175 | `SUCCESS!` | +| `test_ops_mamba2_state_update` | 6 / 6 passed | 2469 / 2469 | `SUCCESS!` | +| `test_ops_mamba2_gated_norm` | 9 / 9 passed | 2107 / 2107 | `SUCCESS!` | + +Identical to the pre-change CPU counts, as expected: every code change is inside +`#ifdef VLLM_CPP_CUDA` or in the `.cuh`. `Status:` was read, not `assertions:` +alone ([[doctest-assertions-line-hides-thrown-cases]]). + +Clean Release rebuild of every target — **817 / 817 ninja edges, 0 warnings, 0 +errors** at `-Wall -Wextra -Werror`, exit 0 — then **full `ctest -j 4`: +`100% tests passed, 0 tests failed out of 403`**, `CTEST_EXIT=0`, 22.35 s, +2 skipped (`test_modelopt_mixed_precision_checkpoint`, `test_voxtral_e2e`). This +is the CPU-only lane, so it is a much smaller and much faster gate than §8.4's +431-test GPU-host run and is **not** a substitute for it; none of §8.4's ten +failures is reachable here. `scripts/agent-preflight.sh --staged` returned +`RC=0`. + +**The CUDA arm was COMPILED by the operator on `dgx.casa`; it was not RUN.** +`dgx.casa` returned at **06:57 UTC** (it had rebooted; uptime was 0), which +supersedes §8.4's `REMOTE_UNVERIFIED` note on the host being away. The +implementer's box has no `nvcc` and no GPU, so the compile was **operator-run, +not implementer-run**, and is recorded as the operator's result rather than +folded into the implementer's evidence. The branch was transferred by +`git archive` — never `rsync`, which has previously overwritten goldens into +false passes — and built with real nvcc: + +``` +NVCC: cuda_13.0.r13.0 +CUTLASS found at ~/cutlass-4.5.0; enabling sm120a NVFP4 cutlass GEMM +Marlin NVFP4 W4A16 MoE GEMM enabled (vendored) for [121a] +FlashAttention-2 prefill/decode: ENABLED for arch(es) [121a] +CONFIGURE_EXIT=0 BUILD_EXIT=0 WARNINGS=0 ENOSPC=0 +``` + +Zero nvcc errors and **zero warnings under the project's `-Werror` flags**, disk +unchanged at 64G either side (so not the stale-binary false-green shape), and +the three fast-path features read **out of the configure log** rather than +assumed — an absent CUTLASS also exits 0, so "the build succeeded" alone would +have proved nothing ([[dgx-build-fast-path-verification]]). This closes the axis +the shim check below could only approximate: `cuda_mamba2_ssd.cuh` now compiles +through `cuda_gdn.cu` with real nvcc at the arch it ships on, which is what +actually retires the risk that the new `S` kernel parameter or the `M2Scratch` +guard broke the device build. + +**Attribute it to the operator, not to CI.** `cuda-fat-build` has still never +completed on this branch (see the cancellation note below), so no CI job has +compiled this code. + +The two substitutes below were run BEFORE that compile existed. They are kept +because they are what the implementer could establish unaided, and because the +second one is not superseded by any compile — but neither is offered as the +device gate: + +1. **`.cuh` compile + arity check.** The header was compiled by the host compiler + at `-std=c++20 -Wall -Wextra -Werror` against minimal CUDA shims, with each + `Kernel<<>>(args)` rewritten to `M2Sink(cfg), Kernel(args)` — which drops + the launch configuration while PRESERVING the argument-count and + argument-type check on all 7 launches. `EXIT 0`. Proved ARMED rather than + vacuous by deleting the `S` argument the F2 clamp added to the chunk-scan + launch on a scratch copy: `too few arguments to function M2ChunkScanKernel`, + exit 1. This checks C++, not PTX or device semantics. +2. **F2 clamp CPU twin.** The two index computations were transcribed and walked + over the device case's own shape (`S=H=nchunks=4`, `row=128`, `passed` + allocation 2048 elements), clamped and unclamped. Unclamped and out of + contract, every claimed hole reproduced: `lci[3] = nchunks` forms index + **2432** (an out-of-bounds WRITE), `lci[1] = -9` forms **−4096**, + `seq_idx >= S` reaches **463232** into a 2048-element `initial_states`, and + `seq_idx = {-1,…}` forms **−512** into `passed` — the `c == 0`, + `si == si_prev` hole. Clamped, all of them land in `[0, 1920] ⊂ [0, 2048)`, + the `lci` clamp reproduces the in-contract index range exactly, and both + `seq_idx` violations read no previous state at all — which is what the new + device case compares against an in-contract zero-init run. + +**Still `omitted_gates` — and the blocker is now the GPU LOCK, not the host.** +`dgx.casa` is back, so these are no longer waiting on a machine; they are +waiting on `$HOME/gpu.lock`, currently held by other coordinators' jobs with +8-hour timeouts. A host being reachable is not the same as the GPU being +available, and running any of these against a contended GPU would reproduce +exactly the undetected-contention defect §8.4 already records. Owed: + +1. the three suites' CUDA arms (Release and Debug) — **execution**, which the + operator's compile does not supply; +2. `compute-sanitizer memcheck` on the new "clamps out-of-contract metadata in + registers" case. This is the one that actually proves the F2 clamps: a green + run without it is necessary and not sufficient, because an out-of-bounds + write into a `cudaMallocAsync` pool commonly does not fault; +3. a re-run of the 9-mutation sweep against the moved bound `5·(K+2)·u` — the + §8.4 sweep was scored against `4·(K+2)·u`, and a widened bound is precisely + the change that could stop a mutation reddening; +4. §8.4's `~/w2ssd/refail.log` attribution, still `REMOTE_UNVERIFIED`. + +Item 3 is the one a reader is most likely to assume is safe. It is not assumed +here: the re-scaled margins in §8.3 point 6 make it very likely to hold, and +"very likely" is not a gate result. + +### 8.6 All four discharged, operator-run on the gate host (2026-08-13) + +Items 1, 2 and 3 are CLOSED by measurement, not by argument. Item 4 is closed by +attribution. + +**Items 1 + 2 — the CUDA arms and `compute-sanitizer`.** One lock acquisition, +`LOCK_ACQUIRED_UTC=2026-08-13T08:07:31Z`, `BUILD_EXIT=0`, `ENOSPC=0`: + +| arm | result | +|---|---| +| `test_ops_mamba2_ssd` | 12 cases / **2095** assertions, `SUCCESS!`, exit 0 | +| `test_ops_mamba2_state_update` | 10 / **5965**, `SUCCESS!`, exit 0 | +| `test_ops_mamba2_gated_norm` | 12 / **3723**, `SUCCESS!`, exit 0 | +| `memcheck` (ssd, state_update) | `ERROR SUMMARY: 0 errors`, exit 0 both | + +The assertion counts are quoted because a suite that skipped every CUDA case +would also exit 0; a non-zero count is what distinguishes "the device arms ran" +from "the binary started". + +**Item 3 — the re-sweep against the MOVED bound.** `BOUND=5*(K+2)*u`, +`LOCK_ACQUIRED_UTC=2026-08-13T21:06:55Z`. Controls first, at the same counts as +the arms above (2095 / 5965 / 3723, all `SUCCESS!`), so an empty run is +excluded. **9 of 9 CAUGHT:** + +| | guarantee | evidence | +|---|---|---| +| M1 | inter-chunk state term | ssd 74 failed, state_update 2 | +| M2 | `initial_states` seeding | ssd 16 failed | +| M3 | decay reads the LAST tap | ssd 69, state_update 2 | +| M4 | `D` skip term | ssd 6, state_update 1 | +| M5 | `state_indices` honoured | state_update 4 | +| M6 | NULL row skipped | state_update **exit 134** (SIGABRT) | +| M7 | PER-GROUP variance | gated_norm 11 | +| M8 | silu gate, not sigmoid | ssd 2, state_update 7, gated_norm 17 | +| M9 | `eps` INSIDE the sqrt | gated_norm 1 | + +Every mutant was built in a phase that takes **no** lock, stashed, and run under +one acquisition; the source was restored and md5-verified after each +(`FINAL_MD5` matched `PRISTINE_MD5`). Every form keeps every variable read, +because a mutation that fails to compile under `-Werror=all-warnings` is not a +caught mutation — it is a suite that never ran. + +**M6 is the one worth reading.** It aborts, printing +`assertions: 2577 | 2577 passed | 0 failed` — a **clean** assertions line on a +FAILING run. It is caught only because the harness reads the **exit code**. This +is the repo's recurring trap in its sharpest form, and it is why item 3 could +not have been discharged by inspecting summaries. + +**Item 4 — `test_minimax_h3` ATTRIBUTED, not waived.** Reproduced standalone on +an idle box under the lock: `TEST_EXIT=139`, `ENOSPC=0`. It is **#486**, root +cause **#516** (`vllm::Pool()`'s free list keyed by size class with no device in +the key), signature-for-signature — `38 | 36 passed | 2 failed | 41 skipped`, +42724 assertions, `cudaFree: invalid argument` then SIGSEGV. The independent +baseline that PASSED was `row/pool-device-key`, **the branch that fixes #516** — +so the baseline carried a fix, this branch does not carry a defect. The +GPU-contention hypothesis §8.4 recorded as "plausible, not proven" is +**REFUTED**, not quietly retained. + +**CI on this branch is `REMOTE_UNVERIFIED`, and the distinction matters.** Every +GitHub Actions run for PR #592 -- all four SHAs, both the `ci` and `containers` +workflows -- ended `conclusion: cancelled`, never `failure` and never `success`. +`gh pr checks` renders a cancelled job as `fail`, so the PR reads as 16 reds +that are not reds; the run-level conclusion is what settles it. Three of the +four were cancelled by my own subsequent pushes, which is ordinary. The fourth +was not: run `31676434945` on the head SHA `f5c589f5e` sat queued from 07:06:49, +started at ~07:42, and was cancelled at **07:44:45-07:44:55** together with +**every other run in the repository** -- 20 of 20 across 7 branches, including +`row/LTX25-L9C-REGISTER-GATE`, `row/GATE-GPU-LOCK-WRAPPER` and +`row/FIX-HF-SNAPSHOT-ORDER-551`. `windows-msvc-cpu` had already passed two steps +when it was killed mid-build. A simultaneous repo-wide cancellation is an +Actions-side event, not a verdict on any diff, and it is recorded as unknown +rather than as either absence or success. A re-run was triggered at 07:47 and +was still `queued` at 07:50 when this was written; **whatever it reports, not +this paragraph, is the CI result.** + +**The rendering trap is worth carrying beyond this row, because two people hit +it independently from opposite ends.** `gh pr checks` prints a CANCELLED job as +`fail`. The implementer's watcher reported 16 failures on #592 and the +operator's reported 20, and the true count of failures in both was **zero**. A +per-check listing therefore cannot distinguish "this branch is red" from "the +whole runner pool was killed"; only the RUN-level `conclusion` field can +(`gh run view --json status,conclusion` → `cancelled`). Anyone subtracting +a CI baseline on this repo should read run conclusions, not check rows, or they +will attribute an infrastructure event to a diff. + +**No CI claim is made or repaired here.** In particular the two Windows jobs +remain the `main` baseline described in §8.4 (#512 now fixed by #583, #514, +#584) -- subtracted, not inherited -- and nothing above should be read as +evidence that this branch's CI passed. ## 9. Stop conditions diff --git a/src/vt/cuda/cuda_gdn.cu b/src/vt/cuda/cuda_gdn.cu index 0ad4eff66..5dcfb22e8 100644 --- a/src/vt/cuda/cuda_gdn.cu +++ b/src/vt/cuda/cuda_gdn.cu @@ -35,6 +35,17 @@ #include "vt/cuda/conv_update_fast.h" #include "vt/cuda/cuda_device_caps.h" #include "vt/cuda/cuda_gdn_internal.h" +// MAMBA2 / SSD IS NOT THE GATED DELTA RULE (.agents/specs/mamba2-ssd.md §0, §7): +// no delta-removal term, decay driven by A_log/dt, B/C shared across n_groups. +// The three kernels live in their own header and their own namespace and share +// nothing with the GDN/KDA code below; they are compiled into THIS translation +// unit because it is where the sibling SSM/linear-attention device arms already +// live, and because a new .cu has to be listed in the ROOT CMakeLists.txt, which +// check-doc-checkpoint classifies as `user_usage` and therefore charges a +// docs/USAGE.md update that a kernel exposing no command, config key or C-ABI +// entry point has nothing true to write (the same deviation W1 recorded for +// cpu_ops.cpp, mamba2-ssd.md §8.1). +#include "vt/cuda/cuda_mamba2_ssd.cuh" #include "vt/cuda/gdn_decode_fused.h" #include "vt/cuda/gdn_packed_decode_triton.h" #include "vt/cuda/gdn_prefill_conv.h" @@ -6641,6 +6652,17 @@ struct Registrar { RegisterOp(OpId::kIndexCopy, DeviceType::kCUDA, reinterpret_cast( static_cast(&IndexCopyKernelCuda))); + // Mamba2 / SSD device arm (mamba2-ssd.md W2, #496) — sibling ops, never a + // parameterisation of the GDN kernels above. + RegisterOp(OpId::kMamba2ChunkScan, DeviceType::kCUDA, + reinterpret_cast( + static_cast(&mamba2::Mamba2ChunkScanKernelCuda))); + RegisterOp(OpId::kMamba2StateUpdate, DeviceType::kCUDA, + reinterpret_cast( + static_cast(&mamba2::Mamba2StateUpdateKernelCuda))); + RegisterOp(OpId::kRmsNormGatedGroup, DeviceType::kCUDA, + reinterpret_cast( + static_cast(&mamba2::RmsNormGatedGroupKernelCuda))); } } registrar; diff --git a/src/vt/cuda/cuda_mamba2_ssd.cuh b/src/vt/cuda/cuda_mamba2_ssd.cuh new file mode 100644 index 000000000..f7df33ae7 --- /dev/null +++ b/src/vt/cuda/cuda_mamba2_ssd.cuh @@ -0,0 +1,692 @@ +// CUDA device arm of the Mamba2 / SSD selective-scan core. +// .agents/specs/mamba2-ssd.md W2, issue #496. +// +// Three kernels, each the DEVICE transcription of the CPU host reference that +// landed in W1 (src/vt/cpu/cpu_ops.cpp `Mamba2ChunkScanKernel`, +// `Mamba2StateUpdateKernel`, `RmsNormGatedGroupKernel`), which is itself the 1:1 +// port of the upstream path named on it at the pinned oracle `555967922` +// (vLLM 0.26.0.dev0): +// +// Mamba2ChunkScan <- ops/ssd_combined.py:27-235 (the 5-stage varlen pipeline) +// over ssd_chunk_state.py, ssd_state_passing.py, +// ssd_bmm.py and ssd_chunk_scan.py +// Mamba2StateUpdate <- ops/mamba_ssm.py:497+ `selective_state_update` +// RmsNormGatedGroup <- mamba_mixer2.py:100-149 `Mixer2RMSNormGated.forward_native` +// +// ─── THE DECLARED EQUIVALENCE CONTRACT (mamba2-ssd.md §8.3) ─────────────────── +// +// This arm keeps **f32 accumulation throughout** and does NOT mirror the tile +// downcasts in upstream's Triton kernels — `b = b.to(x_ptr.dtype.element_ty)` +// before `tl.dot` (ssd_chunk_state.py:283-285), and `cb.to(x_ptr.dtype.element_ty)` +// / `prev_states.to(C_ptr.dtype.element_ty)` (ssd_chunk_scan.py:266-269, +// :359-363). Those casts are the INPUT-PRECISION REQUIREMENT OF `tl.dot`, i.e. of +// a tensor-core MMA, not a statement of the algorithm: every one of those tiles is +// loaded with an explicit `.to(tl.float32)` and computed in f32 right up to the +// instant it is fed to the MMA. These are scalar-FMA kernels with no MMA, where +// the downcast would be lossy for nothing. +// +// That is NOT a "wider dtype" deviation, and the distinction matters because a +// token gate cannot catch a dtype that is too wide (.agents/porting.md): the +// MEMORY FORMAT here is byte-for-byte the host arm's. Every load and store goes +// through the operand's own declared dtype; `states` and `CB` are f32 because +// upstream pins them there (`states_in_fp32=True` ssd_combined.py:100-102, +// `output_dtype=torch.float32` :124); the inter-chunk `passed` buffer is allocated +// at `state_dtype` — NOT at the host reference's f32 working width, which W1 +// explicitly flagged as a width W2 must not inherit (cpu_ops.cpp, §8.2 F9). +// No extra byte moves; only the register precision of one product differs, and it +// differs in the direction Triton itself takes wherever it is not feeding an MMA. +// +// The consequence for the gate is stated in the test files: the two arms are NOT +// bit-identical. TWO effects are admitted, both structural and both carrying a +// DERIVED forward-error bound; neither is a tuned number. See +// tests/vt/test_ops_mamba2_ssd.cpp `DerivedRtol`. +// +// ─── ADMITTED SOURCE 1: THE ELEMENTARY FUNCTIONS ───────────────────────────── +// The two arms call different libms (`expf`/`log1pf`) — CUDA's `expf` is +// documented to <= 2 ulp and glibc's to <= 0.5. +// +// ─── ADMITTED SOURCE 2: FMA CONTRACTION ────────────────────────────────────── +// Host C++ is pinned `-ffp-contract=off` (CMakeLists.txt:41-56) precisely so +// `a*b + c` keeps two roundings. NOTHING passes `--fmad=false` to nvcc, so this +// header compiles at nvcc's DEFAULT `--fmad=true` and every `acc += a*b` below +// (:290, :335, :364, :434, :442, :501, and the gated norm's `part += v*v` at +// :554) is a SINGLE-rounding `fma` whose host twin is not. That is measured, not +// theoretical: .agents/benchmark-record.md:532 records a pre-rounded `v²` +// differing by <= 1 ulp from this exact nvcc-`fmad` idiom and flipping a +// near-tie. CMakeLists.txt:41-56 carves CUDA out of the contraction policy on +// the grounds that "GPU parity tests compare GPU-vs-GPU"; the device-vs-host +// comparison is exactly the case that carve-out does not cover, so the bound +// carries the term instead of the build removing it. +// +// `-fmad=false` was REJECTED, not overlooked. nvcc takes it per TRANSLATION +// UNIT and this is a header, included by cuda_gdn.cu:48 — so applying it means +// either de-contracting every GDN kernel in that TU (a measured hot decode +// path) or splitting a new `src/vt/` TU, which §8.3 already records as blocked +// on #515. Slowing an unrelated shipped kernel to make a bound's prose true is +// the wrong trade; widening the bound by the term the build actually emits is +// the right one. `DerivedRtol` is `5·(K+2)·u`, not `4·(K+2)·u`, and §8.3 shows +// the arithmetic. +// +// ─── ACCUMULATION ORDER IS PART OF THE PORT ────────────────────────────────── +// Except in the gated norm's group reduction (which is a block reduction, and +// says so), every accumulation below runs in ONE thread, over the SAME index +// range in the SAME direction as the host reference. That is deliberate — order +// is the AMPLIFYING source and pinning it keeps the bound to the two terms +// above. It does not, on its own, make the libm the only one. +// +// ─── WHAT THIS ARM DOES NOT CHECK (named residual, §8.3) ───────────────────── +// The SHARED validator checks metadata SHAPE, DTYPE and DEVICE only +// (`CheckI32Meta`, ops.cpp:1717-1723). Every VALUE check lives in the host +// kernel, which reads the tensors (cpu_ops.cpp:1622-1648): `A < 0` +// (`CheckMamba2ANegative`), `state_indices` distinctness, the `cu_chunk_seqlens` +// tiling, per-chunk length bounds, `seq_idx[c] ∈ [0,S)`, and both halves of +// `0 <= last_chunk_indices[b] < nchunks`. This arm re-checks NONE of them: the +// operands live on the DEVICE, so reading them costs a D2H copy plus a stream +// synchronise per call — the same host tax the GDN prefill path was rebuilt to +// remove (`GdnArgs::query_start_loc_host`, include/vt/ops.h) — and it would make +// the op uncapturable in a CUDA graph. This mirrors the policy cuda_gdn.cu +// already states for exactly this case ("here bad metadata is unchecked -- +// correctness-grade; the M0.9 builder owns metadata integrity", +// cuda_gdn.cu:8-13). Closing the gap needs the deferred device error ring +// cuda_ops.cu:790-940 already implements for embedding, and is owed. +// +// MEMORY SAFETY IS A NARROWER CLAIM AND IS MADE SEPARATELY, because dropping a +// check whose consequence is a WRONG NUMBER is correctness-grade while dropping +// one whose consequence is an out-of-bounds ACCESS is not: +// +// CLAMPED, therefore memory-safe under a violation — +// * `last_chunk_indices[b] >= nchunks` would make M2Store(passed, ...) at +// :336 write PAST the cudaMallocAsync allocation; `lci[b-1] < -1` would +// make `states[...]` at :335 read before it. Clamped at :319-331. +// * `seq_idx[c] ∉ [0,S)` would read `initial_states` out of bounds at :435, +// and a `seq_idx[0] < 0` would additionally make `si == si_prev` at c == 0 +// and index `passed` at chunk -1. Clamped at :404-419. +// * an out-of-range `state_indices` slot writes nothing at all. Clamped at +// :486, as it always has been. +// The clamps are the reason the D2H argument above does not apply to these: +// the values are ALREADY IN REGISTERS at their use sites, so bounding them +// costs nothing and needs no host round trip. They do NOT restore the checks — +// out-of-contract metadata still produces a WRONG ANSWER, now with a defined +// shape. They bound only WHERE it is read from. Both are pinned by device-only +// cases in tests/vt/test_ops_mamba2_ssd.cpp rather than asserted here. +// +// NOT CLAMPED, therefore NOT memory-safe under a violation — +// * the `cu_chunk_seqlens` tiling and per-chunk length checks. `start` and +// `len` derived from a garbage `ccs` index x/B/C/z/out out of bounds in +// every stage. Bounding these needs T at each use site and a clamp in the +// inner loops, which is not free; it is owed with the error ring above. +// VALUE-ONLY, in-bounds wrong number — +// * `A < 0` and `state_indices` distinctness. +#ifndef VT_CUDA_MAMBA2_SSD_CUH_ +#define VT_CUDA_MAMBA2_SSD_CUH_ + +#include +#include +#include + +#include +#include +#include + +#include "vt/ops.h" + +namespace vt::cuda::mamba2 { +namespace { + +constexpr int kM2Block = 256; + +void M2Check(cudaError_t err, const char* what) { + if (err != cudaSuccess) { + throw std::runtime_error(std::string("vt cuda mamba2: ") + what + ": " + + cudaGetErrorString(err)); + } +} + +cudaStream_t M2Stream(const Queue& q) { return static_cast(q.handle); } + +// Scope guard for the prefill path's per-call scratch. `M2Check` throws, so a +// failure on the Nth `cudaMallocAsync` would otherwise leak the N-1 before it. +// The happy path calls `Release()`, which frees on the stream and CHECKS each +// free exactly as the open-coded sequence it replaces did; the destructor is the +// unwinding path only, and cannot throw. +class M2Scratch { + public: + explicit M2Scratch(cudaStream_t s) : s_(s) {} + M2Scratch(const M2Scratch&) = delete; + M2Scratch& operator=(const M2Scratch&) = delete; + ~M2Scratch() { + for (int i = 0; i < n_; ++i) static_cast(cudaFreeAsync(p_[i], s_)); + } + void* Alloc(size_t bytes, const char* what) { + // Refuse rather than overrun if a sixth buffer is ever added: a silent + // overflow here would be the exact defect class the guard exists to remove. + if (n_ >= kMax) throw std::runtime_error("vt cuda mamba2: scratch slots exhausted"); + void* p = nullptr; + M2Check(cudaMallocAsync(&p, bytes, s_), what); + p_[n_++] = p; + return p; + } + void Release() { + const int n = n_; + n_ = 0; + // Free ALL of them before reporting, so a mid-sequence failure does not + // leak the remainder the way the open-coded `M2Check(cudaFreeAsync(...))` + // sequence this replaces did; then throw on the first error seen. + cudaError_t first = cudaSuccess; + for (int i = 0; i < n; ++i) { + const cudaError_t e = cudaFreeAsync(p_[i], s_); + if (first == cudaSuccess) first = e; + } + M2Check(first, "scratch free"); + } + + private: + static constexpr int kMax = 5; + cudaStream_t s_; + void* p_[kMax] = {}; + int n_ = 0; +}; + +// Grid for a grid-stride loop over `n` items at kM2Block threads, capped so a +// launch stays reasonable on any element count. +unsigned M2Grid(int64_t n) { + const int64_t blocks = (n + kM2Block - 1) / kM2Block; + if (blocks < 1) return 1u; + return static_cast(blocks < 65535 ? blocks : 65535); +} + +// f32 load/store through the operand's OWN dtype — the memory format is the host +// arm's, only the arithmetic is f32 (`LoadF32`/`StoreF32`, cpu_ops.cpp). +__device__ inline float M2Load(const void* p, DType dt, int64_t i) { + if (dt == DType::kF32) return static_cast(p)[i]; + if (dt == DType::kF16) return __half2float(static_cast(p)[i]); + return __bfloat162float(static_cast(p)[i]); +} + +__device__ inline void M2Store(void* p, DType dt, int64_t i, float v) { + if (dt == DType::kF32) { + static_cast(p)[i] = v; + } else if (dt == DType::kF16) { + static_cast<__half*>(p)[i] = __float2half_rn(v); + } else { + static_cast<__nv_bfloat16*>(p)[i] = __float2bfloat16(v); // RNE, as host F32ToBF16 + } +} + +// The value `v` as it reads back after a store/load round trip through `dt` — +// the `state_dtype` / `input_dtype` cast points (`RoundThrough`, cpu_ops.cpp). +__device__ inline float M2RoundThrough(DType dt, float v) { + if (dt == DType::kF32) return v; + if (dt == DType::kF16) return __half2float(__float2half_rn(v)); + return __bfloat162float(__float2bfloat16(v)); +} + +// softplus, guarded exactly as upstream: `tl.where(dt <= 20.0, softplus(dt), dt)` +// (ssd_chunk_state.py:94; the same guard at csrc/cpu/mamba_kernels.hpp:177). +__device__ inline float M2Softplus(float v) { return v <= 20.0f ? log1pf(expf(v)) : v; } + +__device__ inline float M2Silu(float z) { return z / (1.0f + expf(-z)); } + +// ───────────────────────────────────────────────────────────────────────────── +// stage 1 — `_chunk_cumsum_fwd` (ssd_chunk_state.py:300-346). +// One thread per (h, c): the prefix sum over the chunk is SEQUENTIAL, in the host +// reference's order. Positions past a partial chunk hold dt = 0 (:104-107), so +// dA_cumsum[..., cs-1] is the chunk's TOTAL decay whatever its length. +// ───────────────────────────────────────────────────────────────────────────── +__global__ void M2CumsumKernel(float* dtv, float* dac, const void* dt_in, DType dt_dtype, + const float* A, const float* dbp, const int32_t* ccs, int64_t H, + int64_t nchunks, int64_t cs, bool softplus, float dt_min, + float dt_max) { + const int64_t total = H * nchunks; + for (int64_t r = blockIdx.x * blockDim.x + threadIdx.x; r < total; + r += static_cast(gridDim.x) * blockDim.x) { + const int64_t h = r / nchunks, c = r % nchunks; + const float a = A[h]; + const int64_t start = ccs[c], len = ccs[c + 1] - start; + const int64_t base = r * cs; + float acc = 0.0f; + for (int64_t i = 0; i < cs; ++i) { + float d = 0.0f; + if (i < len) { + d = M2Load(dt_in, dt_dtype, (start + i) * H + h); + if (dbp != nullptr) d += dbp[h]; + if (softplus) d = M2Softplus(d); + d = fminf(fmaxf(d, dt_min), dt_max); + } + dtv[base + i] = d; + acc += d * a; + dac[base + i] = acc; + } + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// stage 2 — `_chunk_state_fwd` (ssd_chunk_state.py:349-407). +// states[c,h,p,n] = sum_i x[i,h,p] * (B[i,g,n] * exp(min(dA_last - dA_i, 0)) * dt_i) +// f32 by upstream's own `states_in_fp32=True` (ssd_combined.py:100-102). One +// thread per (c,h,p,n), accumulating over i in the host reference's order. +// ───────────────────────────────────────────────────────────────────────────── +__global__ void M2ChunkStateKernel(float* states, const void* x, DType xdt, const void* B, + DType Bdt, const float* dtv, const float* dac, + const int32_t* ccs, int64_t nchunks, int64_t H, int64_t P, + int64_t G, int64_t N, int64_t cs, int64_t hpg) { + const int64_t total = nchunks * H * P * N; + for (int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; + idx += static_cast(gridDim.x) * blockDim.x) { + const int64_t n = idx % N; + const int64_t p = (idx / N) % P; + const int64_t h = (idx / (N * P)) % H; + const int64_t c = idx / (N * P * H); + const int64_t g = h / hpg; + const int64_t start = ccs[c], len = ccs[c + 1] - start; + const int64_t dbase = (h * nchunks + c) * cs; + const float da_last = dac[dbase + cs - 1]; + float acc = 0.0f; + for (int64_t i = 0; i < len; ++i) { + // The `min(., 0)` is upstream's and is an algebraic no-op inside the + // enforced contract (`A < 0`, `dt >= 0` make dA_cumsum non-increasing over + // i); it is kept because upstream keeps it. + const float scale = expf(fminf(da_last - dac[dbase + i], 0.0f)) * dtv[dbase + i]; + if (scale == 0.0f) continue; + const float xv = M2Load(x, xdt, ((start + i) * H + h) * P + p); + const float bv = M2Load(B, Bdt, ((start + i) * G + g) * N + n) * scale; + acc += xv * bv; + } + states[((c * H + h) * P + p) * N + n] = acc; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// stage 3 — `_state_passing_fwd` (ssd_state_passing.py:99-146). +// S_c = exp(dA_last[c]) * S_{c-1} + states[c], S_{-1} = initial_states[b] +// out[c] is the state AFTER chunk c (:90-97). +// +// THE RUNNING STATE STAYS F32 AND ONLY THE STORE ROUNDS: upstream carries +// `states` in f32 registers across the chunk loop and stores a `state_dtype` copy +// per chunk (:88-97) — it never reads that store back into the recurrence. +// `passed` is allocated at `state_dtype` here, NOT at the host reference's f32 +// working width (cpu_ops.cpp records that width as one W2 must not inherit). +// One thread per (b,h,p,n), sequential over chunks. +// ───────────────────────────────────────────────────────────────────────────── +__global__ void M2StatePassKernel(void* passed, DType sdt, void* final_states, const float* states, + const float* dac, const int32_t* lci, const void* init, + DType initdt, int64_t S, int64_t H, int64_t P, int64_t N, + int64_t nchunks, int64_t cs) { + const int64_t row = P * N; + const int64_t total = S * H * row; + for (int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; + idx += static_cast(gridDim.x) * blockDim.x) { + const int64_t i = idx % row; + const int64_t h = (idx / row) % H; + const int64_t b = idx / (row * H); + // REGISTER-LOCAL MEMORY-SAFETY CLAMP (see the header's "what this arm does + // not check"). The host arm value-checks `0 <= last_chunk_indices[b] < + // nchunks` (cpu_ops.cpp); this arm cannot. Unclamped, `lci[b] >= nchunks` + // makes the `M2Store(passed, ...)` below write PAST the cudaMallocAsync + // allocation, and `lci[b-1] < -1` makes `states[...]` read before it. Both + // bounds are already in registers, so the D2H argument for dropping the + // check does not reach them and this costs nothing. It does NOT restore the + // check: out-of-contract metadata still yields a wrong number, now with a + // DEFINED shape — the chunk loop stops at nchunks and starts at 0. + int64_t chunk_end = lci[b] + 1; + int64_t chunk_start = b > 0 ? lci[b - 1] + 1 : 0; + if (chunk_end > nchunks) chunk_end = nchunks; + if (chunk_start < 0) chunk_start = 0; + float s = init != nullptr ? M2Load(init, initdt, (b * H + h) * row + i) : 0.0f; + for (int64_t c = chunk_start; c < chunk_end; ++c) { + const float decay = expf(dac[(h * nchunks + c) * cs + cs - 1]); + s = s * decay + states[(c * H + h) * row + i]; + M2Store(passed, sdt, (c * H + h) * row + i, s); + } + // `varlen_states = states[last_chunk_indices]` (ssd_combined.py:154). + M2Store(final_states, sdt, (b * H + h) * row + i, s); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// stage 4 — `_bmm_chunk_fwd` (ssd_bmm.py:148-209). +// CB[c,g,i,j] = sum_n C[i,g,n] * B[j,g,n], f32 REGARDLESS of the activation dtype +// (`output_dtype=torch.float32`, ssd_combined.py:124). Only j <= i is ever read +// (IS_CAUSAL), so only j <= i is written — as in the host reference. +// ───────────────────────────────────────────────────────────────────────────── +__global__ void M2BmmKernel(float* cb, const void* Bp, DType Bdt, const void* Cp, DType Cdt, + const int32_t* ccs, int64_t nchunks, int64_t G, int64_t N, + int64_t cs) { + const int64_t total = nchunks * G * cs * cs; + for (int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; + idx += static_cast(gridDim.x) * blockDim.x) { + const int64_t j = idx % cs; + const int64_t i = (idx / cs) % cs; + if (j > i) continue; + const int64_t g = (idx / (cs * cs)) % G; + const int64_t c = idx / (cs * cs * G); + const int64_t start = ccs[c], len = ccs[c + 1] - start; + if (i >= len) continue; + float acc = 0.0f; + for (int64_t n = 0; n < N; ++n) { + acc += M2Load(Cp, Cdt, ((start + i) * G + g) * N + n) * + M2Load(Bp, Bdt, ((start + j) * G + g) * N + n); + } + cb[((c * G + g) * cs + i) * cs + j] = acc; + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// stage 5 — `_chunk_scan_fwd` (ssd_chunk_scan.py:216-525). +// out_i = exp(dA_i) * (C_i . S_{c-1}) inter +// + sum_{j<=i} CB[i,j] * exp(min(dA_i - dA_j, 0)) * dt_j * x_j intra +// + D * x_i skip +// then `out *= z * sigmoid(z)` when z is given (:394-406). +// `S_{c-1}` is `initial_states[seq_idx[c]]` when this chunk opens a new sequence +// AND initial states were supplied, ZEROS when they were not (:236-250, :271-289) +// — that is what makes a sequence boundary INSIDE a physical chunk correct. +// One thread per (c,h,i,p); both accumulations run in the host arm's order. +// ───────────────────────────────────────────────────────────────────────────── +__global__ void M2ChunkScanKernel(void* out, DType odt, const void* x, DType xdt, const void* Cp, + DType Cdt, const float* cb, const float* dtv, const float* dac, + const void* passed, DType sdt, const void* init, DType initdt, + const float* D, bool d_has_hdim, const void* z, DType zdt, + const int32_t* ccs, const int32_t* sidx, int64_t nchunks, + int64_t H, int64_t P, int64_t G, int64_t N, int64_t S, + int64_t cs, int64_t hpg) { + const int64_t total = nchunks * H * cs * P; + for (int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; + idx += static_cast(gridDim.x) * blockDim.x) { + const int64_t p = idx % P; + const int64_t i = (idx / P) % cs; + const int64_t h = (idx / (P * cs)) % H; + const int64_t c = idx / (P * cs * H); + const int64_t start = ccs[c], len = ccs[c + 1] - start; + if (i >= len) continue; + const int64_t g = h / hpg; + const int64_t dbase = (h * nchunks + c) * cs; + const int64_t row = P * N; + + const int32_t si = sidx[c]; + const int32_t si_prev = c >= 1 ? sidx[c - 1] : -1; + // REGISTER-LOCAL MEMORY-SAFETY CLAMP, as in M2StatePassKernel above. The + // host arm value-checks `seq_idx[c] in [0,S)` (cpu_ops.cpp); unclamped, an + // out-of-range `si` reads `initial_states` out of bounds at `prevbase`, and + // a `sidx[0] < 0` makes `si == si_prev` at c == 0 and indexes `passed` at + // chunk -1. In-contract `si` is ALWAYS in range, so every in-contract path + // below is bit-identical to the unclamped form; out of contract the answer + // is still wrong, now with the defined shape "this chunk opens with a zero + // previous state". + const bool si_ok = si >= 0 && static_cast(si) < S; + bool prev_zero = false; + const void* prevp = passed; + DType prevdt = sdt; + int64_t prevbase = ((c - 1) * H + h) * row; + if (!si_ok) { + prev_zero = true; + } else if (si != si_prev) { + if (init != nullptr) { + prevp = init; + prevdt = initdt; + prevbase = (static_cast(si) * H + h) * row; + } else { + prev_zero = true; + } + } + + const float da_i = dac[dbase + i]; + const float scale_m = expf(da_i); + float acc = 0.0f; + if (!prev_zero) { + for (int64_t n = 0; n < N; ++n) { + acc += M2Load(Cp, Cdt, ((start + i) * G + g) * N + n) * + M2Load(prevp, prevdt, prevbase + p * N + n); + } + } + acc *= scale_m; + const float* cbc = cb + (c * G + g) * cs * cs; + for (int64_t j = 0; j <= i; ++j) { + const float w = cbc[i * cs + j] * expf(fminf(da_i - dac[dbase + j], 0.0f)) * dtv[dbase + j]; + acc += w * M2Load(x, xdt, ((start + j) * H + h) * P + p); + } + const float xi = M2Load(x, xdt, ((start + i) * H + h) * P + p); + if (D != nullptr) acc += (d_has_hdim ? D[h * P + p] : D[h]) * xi; + if (z != nullptr) acc *= M2Silu(M2Load(z, zdt, ((start + i) * H + h) * P + p)); + M2Store(out, odt, ((start + i) * H + h) * P + p, acc); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// vt::Mamba2StateUpdate — `selective_state_update` (ops/mamba_ssm.py:497+) at the +// scalar-per-head shape (csrc/cpu/mamba_kernels.hpp:104-250). +// One thread per (b,h,p), sequential over n exactly as the host arm. +// +// The readout uses the F32 value, not the value re-read from the cache: the +// Triton kernel holds `state` in registers and computes `out = sum(state * C)` +// from them, storing the cache-width copy separately (mamba_ssm.py:433,451). +// ───────────────────────────────────────────────────────────────────────────── +__global__ void M2StateUpdateKernel(void* out, DType odt, void* state, DType sdt, const void* x, + DType xdt, const void* dtp, DType dtdt, const float* A, + const void* Bp, DType Bdt, const void* Cp, DType Cdt, + const float* D, const void* z, DType zdt, const float* dbp, + const int32_t* sidx, int64_t Nb, int64_t H, int64_t P, + int64_t G, int64_t N, int64_t S, int64_t hpg, + bool softplus) { + const int64_t total = Nb * H * P; + for (int64_t idx = blockIdx.x * blockDim.x + threadIdx.x; idx < total; + idx += static_cast(gridDim.x) * blockDim.x) { + const int64_t p = idx % P; + const int64_t h = (idx / P) % H; + const int64_t b = idx / (P * H); + int64_t slot = b; + if (sidx != nullptr) { + // LOCAL ABI: index < 0 is the NULL row — its cache slot is untouched + // (`continue`, mamba_kernels.hpp:147) and its output row is zeroed, as + // GdnDecode already models it. + if (sidx[b] < 0) { + M2Store(out, odt, (b * H + h) * P + p, 0.0f); + continue; + } + slot = sidx[b]; + // The host arm REFUSES an out-of-range slot; a device kernel cannot throw, + // so it writes nothing at all rather than out of bounds (see the header + // note on unchecked device-side preconditions). + if (slot >= S) continue; + } + const int64_t gg = h / hpg; + float d = M2Load(dtp, dtdt, b * H + h); + if (dbp != nullptr) d += dbp[h]; + if (softplus) d = M2Softplus(d); + const float dA = expf(A[h] * d); + const float xv = M2Load(x, xdt, (b * H + h) * P + p); + const int64_t sbase = ((slot * H + h) * P + p) * N; + float y = 0.0f; + for (int64_t n = 0; n < N; ++n) { + const float bv = M2Load(Bp, Bdt, (b * G + gg) * N + n); + const float cv = M2Load(Cp, Cdt, (b * G + gg) * N + n); + const float sn = M2Load(state, sdt, sbase + n) * dA + bv * xv * d; + M2Store(state, sdt, sbase + n, sn); + y += sn * cv; + } + if (D != nullptr) y += D[h] * xv; + if (z != nullptr) y *= M2Silu(M2Load(z, zdt, (b * H + h) * P + p)); + M2Store(out, odt, (b * H + h) * P + p, y); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// vt::RmsNormGatedGroup — `Mixer2RMSNormGated.forward_native` (mamba_mixer2.py:100-149). +// v = x * silu(f32(gate)) (:114) +// out = weight * dtype(x)( v * rsqrt(mean(v^2 over its group) + eps) ) (:136-141, :149) +// +// One BLOCK per (row, group). This is the ONE accumulation in this file whose +// order differs from the host arm's sequential sum: a per-group reduction is the +// whole shape of the op, and a block reduction is how it is done on device. The +// summands are all NON-NEGATIVE (they are squares), so there is no cancellation +// and the reordering carries the plain forward-error bound the tests state. +// ───────────────────────────────────────────────────────────────────────────── +__device__ inline float M2BlockReduceSum(float v) { + __shared__ float smem[kM2Block / 32]; + const int lane = static_cast(threadIdx.x) & 31; + const int warp = static_cast(threadIdx.x) >> 5; + for (int off = 16; off > 0; off >>= 1) v += __shfl_down_sync(0xffffffffu, v, off); + if (lane == 0) smem[warp] = v; + __syncthreads(); + if (threadIdx.x == 0) { + float total = 0.0f; + const int nwarps = static_cast(blockDim.x) >> 5; + for (int i = 0; i < nwarps; ++i) total += smem[i]; + smem[0] = total; + } + __syncthreads(); + const float out = smem[0]; + __syncthreads(); // smem is reused by the next (row, group) this block takes + return out; +} + +__global__ void M2GatedNormKernel(void* out, DType odt, const void* x, DType xdt, DType input_dt, + const void* gate, DType gdt, const void* w, DType wdt, + bool has_w, int64_t nblocks, int64_t hidden, int64_t n_groups, + int64_t group_size, float eps) { + for (int64_t blk = blockIdx.x; blk < nblocks; blk += gridDim.x) { + const int64_t r = blk / n_groups, gsel = blk % n_groups; + const int64_t off = r * hidden + gsel * group_size; + float part = 0.0f; + for (int64_t j = threadIdx.x; j < group_size; j += blockDim.x) { + // The gate is promoted to f32 BEFORE the silu (:114). + const float v = M2Load(x, xdt, off + j) * M2Silu(M2Load(gate, gdt, off + j)); + if (!has_w) { + // use_rms_norm == False: no parameter, no norm (:94-96, :115-116). + M2Store(out, odt, off + j, M2RoundThrough(input_dt, v)); + } + part += v * v; + } + if (!has_w) continue; + // f32 accumulation, NOT double: upstream reduces `x.pow(2).mean(-1)` in f32. + const float ss = M2BlockReduceSum(part); + // `rsqrt(variance + eps)` (:130, :141) — eps is INSIDE the square root. + // Written as 1/sqrt to match the host reference's rounding, not `rsqrtf`. + const float inv = 1.0f / sqrtf(ss / static_cast(group_size) + eps); + for (int64_t j = threadIdx.x; j < group_size; j += blockDim.x) { + const float v = M2Load(x, xdt, off + j) * M2Silu(M2Load(gate, gdt, off + j)); + const float normed = M2RoundThrough(input_dt, v * inv); + M2Store(out, odt, off + j, M2Load(w, wdt, gsel * group_size + j) * normed); + } + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// host launchers +// ───────────────────────────────────────────────────────────────────────────── + +void Mamba2ChunkScanKernelCuda(Queue& q, Tensor& out, Tensor& final_states, const Tensor& x, + const Tensor& dt_in, const Tensor& A, const Tensor& B, + const Tensor& C, const Tensor* D, const Tensor* z, + const Tensor* dt_bias, const Tensor* initial_states, + const Tensor& /*cu_seqlens*/, const Tensor& cu_chunk_seqlens, + const Tensor& last_chunk_indices, const Tensor& seq_idx, + const Mamba2Args& args) { + const int64_t T = x.shape[0], H = x.shape[1], P = x.shape[2]; + const int64_t G = B.shape[1], N = B.shape[2]; + const int64_t S = final_states.shape[0]; + const int64_t cs = args.chunk_size; + const int64_t nchunks = cu_chunk_seqlens.shape[0] - 1; + const int64_t hpg = H / G; // nheads_ngroups_ratio (ssd_chunk_state.py:238) + if (T == 0 || nchunks == 0) return; + + cudaStream_t s = M2Stream(q); + const int32_t* ccs = cu_chunk_seqlens.Ptr(); + const int32_t* lci = last_chunk_indices.Ptr(); + const int32_t* sidx = seq_idx.Ptr(); + const float* Ap = A.Ptr(); + const float* dbp = dt_bias != nullptr ? dt_bias->Ptr() : nullptr; + const float* Dp = D != nullptr ? D->Ptr() : nullptr; + const bool d_has_hdim = D != nullptr && D->rank == 2; + const DType sdt = final_states.dtype; // `state_dtype` (ssd_combined.py:46,119,176) + + // Per-call scratch on the stream's memory pool, exactly as the sibling varlen + // prefill KdaChunkPrefill does (cuda_gdn.cu). This is the PREFILL path; the + // decode kernel below allocates nothing. + const size_t n_cumsum = static_cast(H * nchunks * cs); + const size_t n_states = static_cast(nchunks * H * P * N); + const size_t n_cb = static_cast(nchunks * G * cs * cs); + const size_t state_elem = sdt == DType::kF32 ? 4u : 2u; + // `M2Check` THROWS, so the five allocations below are held by a scope guard: + // without it a failure on the 3rd leaks the 1st and 2nd. The guard is released + // once the explicit frees at the end of the happy path have run. + M2Scratch scratch(s); + float* dtv = static_cast(scratch.Alloc(n_cumsum * sizeof(float), "dtv alloc")); + float* dac = static_cast(scratch.Alloc(n_cumsum * sizeof(float), "dac alloc")); + float* states = static_cast(scratch.Alloc(n_states * sizeof(float), "states alloc")); + float* cb = static_cast(scratch.Alloc(n_cb * sizeof(float), "cb alloc")); + void* passed = scratch.Alloc(n_states * state_elem, "passed alloc"); + // cudaMallocAsync hands back DIRTY pool memory. Every element of dtv/dac/states + // is written before it is read; `cb` and `passed` are written only where they + // are read (the causal triangle, and the chunks of a scheduled sequence), so + // they are zeroed rather than left to an initcheck report. + M2Check(cudaMemsetAsync(cb, 0, n_cb * sizeof(float), s), "cb zero"); + M2Check(cudaMemsetAsync(passed, 0, n_states * state_elem, s), "passed zero"); + + M2CumsumKernel<<>>( + dtv, dac, dt_in.data, dt_in.dtype, Ap, dbp, ccs, H, nchunks, cs, args.dt_softplus, + args.dt_min, args.dt_max); + M2ChunkStateKernel<<(n_states)), kM2Block, 0, s>>>( + states, x.data, x.dtype, B.data, B.dtype, dtv, dac, ccs, nchunks, H, P, G, N, cs, hpg); + M2StatePassKernel<<(S) * H * P * N), kM2Block, 0, s>>>( + passed, sdt, final_states.data, states, dac, lci, + initial_states != nullptr ? initial_states->data : nullptr, + initial_states != nullptr ? initial_states->dtype : DType::kF32, S, H, P, N, nchunks, cs); + M2BmmKernel<<(n_cb)), kM2Block, 0, s>>>( + cb, B.data, B.dtype, C.data, C.dtype, ccs, nchunks, G, N, cs); + M2ChunkScanKernel<<>>( + out.data, out.dtype, x.data, x.dtype, C.data, C.dtype, cb, dtv, dac, passed, sdt, + initial_states != nullptr ? initial_states->data : nullptr, + initial_states != nullptr ? initial_states->dtype : DType::kF32, Dp, d_has_hdim, + z != nullptr ? z->data : nullptr, z != nullptr ? z->dtype : DType::kF32, ccs, sidx, + nchunks, H, P, G, N, S, cs, hpg); + + const cudaError_t launched = cudaGetLastError(); + scratch.Release(); // frees all five on the stream, reporting the first error + M2Check(launched, "mamba2_chunk_scan launch"); +} + +void Mamba2StateUpdateKernelCuda(Queue& q, Tensor& out, Tensor& state, const Tensor& x, + const Tensor& dt_in, const Tensor& A, const Tensor& B, + const Tensor& C, const Tensor* D, const Tensor* z, + const Tensor* dt_bias, const Tensor* state_indices, + const Mamba2Args& args) { + const int64_t Nb = x.shape[0], H = x.shape[1], P = x.shape[2]; + const int64_t G = B.shape[1], N = B.shape[2]; + const int64_t S = state.shape[0]; + const int64_t hpg = H / G; + if (Nb == 0) return; + cudaStream_t s = M2Stream(q); + M2StateUpdateKernel<<>>( + out.data, out.dtype, state.data, state.dtype, x.data, x.dtype, dt_in.data, dt_in.dtype, + A.Ptr(), B.data, B.dtype, C.data, C.dtype, + D != nullptr ? D->Ptr() : nullptr, z != nullptr ? z->data : nullptr, + z != nullptr ? z->dtype : DType::kF32, dt_bias != nullptr ? dt_bias->Ptr() : nullptr, + state_indices != nullptr ? state_indices->Ptr() : nullptr, Nb, H, P, G, N, S, hpg, + args.dt_softplus); + M2Check(cudaGetLastError(), "mamba2_state_update launch"); +} + +void RmsNormGatedGroupKernelCuda(Queue& q, Tensor& out, const Tensor& x, const Tensor& gate, + const Tensor* weight, const RmsNormGatedGroupArgs& args) { + const int64_t hidden = x.shape[x.rank - 1]; + int64_t rows = 1; + for (int r = 0; r < x.rank - 1; ++r) rows *= x.shape[r]; + if (rows == 0 || hidden == 0) return; + const int64_t group_size = hidden / args.n_groups; + const int64_t nblocks = rows * args.n_groups; + // `input_dtype = x.dtype` (:113) is the width the normalized value is cast back + // to before the weight multiply (`self.weight * x.to(input_dtype)`, :149). + cudaStream_t s = M2Stream(q); + const unsigned grid = static_cast(nblocks < 65535 ? nblocks : 65535); + M2GatedNormKernel<<>>( + out.data, out.dtype, x.data, x.dtype, x.dtype, gate.data, gate.dtype, + weight != nullptr ? weight->data : nullptr, + weight != nullptr ? weight->dtype : DType::kF32, weight != nullptr, nblocks, hidden, + args.n_groups, group_size, args.eps); + M2Check(cudaGetLastError(), "rms_norm_gated_group launch"); +} + +} // namespace +} // namespace vt::cuda::mamba2 + +#endif // VT_CUDA_MAMBA2_SSD_CUH_ diff --git a/tests/vt/test_ops_mamba2_gated_norm.cpp b/tests/vt/test_ops_mamba2_gated_norm.cpp index 81e160911..a90ef15d2 100644 --- a/tests/vt/test_ops_mamba2_gated_norm.cpp +++ b/tests/vt/test_ops_mamba2_gated_norm.cpp @@ -543,3 +543,352 @@ TEST_CASE("mamba2 gated group norm refuses the arms it does not implement") { CHECK_THROWS(RunNorm(in, {rows, hidden}, 7, 1e-6f, DType::kF32, true)); } } + +// ═════════════════════════════════════════════════════════════════════════════ +// (10) THE CUDA ARM — .agents/specs/mamba2-ssd.md W2, issue #496. +// +// The declared equivalence contract is stated in full at the head of the CUDA +// section of tests/vt/test_ops_mamba2_ssd.cpp and in +// src/vt/cuda/cuda_mamba2_ssd.cuh, including the FMA-contraction term: `part += +// v * v` is one nvcc-`fmad` rounding on device and two under the host's +// `-ffp-contract=off`. ONE further thing is different for this op, and it is +// stated here rather than inherited: the group reduction is a BLOCK reduction on +// device and a sequential sum on host, so this arm admits a THIRD source of +// divergence — summation ORDER — on top of libm and contraction. Its summands +// are all squares, hence non-negative, so there is no cancellation and the +// reordering carries the plain forward-error bound: for a length-m sum, +// |fl_a - fl_b| <= 2(m-1)*u*sum|x| = 2(m-1)*u*sum(x) because sum|x| IS the sum. +// That is 2(K-1)*u for the reorder plus K*u for the contraction plus the silu +// `expf` difference, comfortably inside the `5*(K + 10)*u` that +// `DerivedRtol(group_size)` — the same expression the other two suites use — +// gives, K being the group's own length. Nothing is tuned. +// ═════════════════════════════════════════════════════════════════════════════ +#ifdef VLLM_CPP_CUDA + +#include +#include + +namespace { + +using vt::Backend; + +Backend* MaybeCuda() { + try { + return &vt::GetBackend(DeviceType::kCUDA); + } catch (const std::exception&) { + return nullptr; + } +} + +// A GREEN TEST DOES NOT PROVE THE DEVICE RAN IT. GB10 is +// `integrated && pageable_memory_access`, so `Backend::UnifiedMemory()` is TRUE +// and `ReferenceTierEligible(kCUDA)` with it: absent a native kernel, `GetOp` +// does not throw — it installs the CPU HOST kernel as a `kReferenceProviderName` +// provider and runs THAT over the device pointers (op_provider.h, "portable +// reference tier"), so every assertion below would pass while nothing ran on the +// GPU. Every CUDA case therefore asserts the SELECTED provider is native. These +// are EAGER dispatches, so the counters are populated +// ([[graph-replay-does-no-host-dispatch-counters-read-zero]]). +void RequireNativeCudaProvider(vt::OpId op, const std::string& what) { + const vt::OpProviderStats st = vt::GetOpProviderStats(op, DeviceType::kCUDA); + INFO(what << ": selected CUDA provider = " + << (st.last_selected != nullptr ? st.last_selected : "") + << "; process-wide reference-tier hits = " << vt::GetReferenceTierHits()); + REQUIRE(st.last_selected != nullptr); + CHECK(std::string(st.last_selected) != std::string(vt::kReferenceProviderName)); +} + +// `5*(K + 2)*u` — the bound derived at the head of the CUDA section of +// tests/vt/test_ops_mamba2_ssd.cpp, which for this op covers the reordering of a +// length-K non-negative reduction (2(K-1)*u, no cancellation because sum|x| IS +// the sum), plus K*u for the contraction of `part += v * v` on the device side +// only, plus the libm difference in silu's `expf`. +constexpr double kUnitRoundoff = 5.9604644775390625e-08; // 2^-24 +double DerivedRtol(int64_t K) { return 5.0 * static_cast(K + 2) * kUnitRoundoff; } + +void ExpectDeviceMatchesHost(const std::string& what, const std::vector& dev, + const std::vector& host, int64_t K) { + REQUIRE(dev.size() == host.size()); + REQUIRE(!dev.empty()); + double scale = 0.0; + for (float v : host) scale = std::max(scale, std::abs(static_cast(v))); + const double rtol = DerivedRtol(K); + const double atol = rtol * scale; + size_t bit_differing = 0, worst_i = 0; + double worst_ratio = -1.0, worst_diff = 0.0; + for (size_t i = 0; i < dev.size(); ++i) { + if (dev[i] != host[i]) ++bit_differing; + const double d = std::abs(static_cast(dev[i]) - static_cast(host[i])); + const double budget = atol + rtol * std::abs(static_cast(host[i])); + const double ratio = budget > 0.0 ? d / budget : (d > 0.0 ? 1e30 : 0.0); + if (!std::isfinite(static_cast(dev[i])) || ratio > worst_ratio) { + worst_ratio = ratio; + worst_i = i; + worst_diff = d; + if (!std::isfinite(static_cast(dev[i]))) break; + } + } + // MESSAGE, not INFO: doctest prints an INFO context only when an assertion in + // its scope FAILS, so the used slack has to be logged unconditionally for the + // derived bar to be auditable on the green run that matters. + MESSAGE(what << ": K=" << K << " rtol=" << rtol << " scale=" << scale << "; " + << bit_differing << " of " << dev.size() + << " elements differ in any bit; worst element [" << worst_i + << "] dev=" << dev[worst_i] << " host=" << host[worst_i] + << " |diff|=" << worst_diff << " used " << (worst_ratio * 100.0) + << "% of its derived budget"); + CHECK(std::isfinite(static_cast(dev[worst_i]))); + CHECK(worst_ratio <= 1.0); +} + +Tensor MakeTDev(void* data, DType dt, Device dev, const std::vector& shape) { + Tensor t; + t.data = data; + t.dtype = dt; + t.device = dev; + t.rank = static_cast(shape.size()); + int64_t stride = 1; + for (int i = t.rank - 1; i >= 0; --i) { + t.shape[i] = shape[static_cast(i)]; + t.stride[i] = stride; + stride *= shape[static_cast(i)]; + } + return t; +} + +class DBuf { + public: + DBuf(Backend& b, Queue& q, const void* host, size_t bytes) : b_(&b), bytes_(bytes) { + p_ = b.Alloc(bytes == 0 ? 1 : bytes); + if (host != nullptr && bytes > 0) b.Copy(q, p_, host, bytes); + } + ~DBuf() { + if (p_ != nullptr) b_->Free(p_); + } + DBuf(const DBuf&) = delete; + DBuf& operator=(const DBuf&) = delete; + void* get() const { return p_; } + void Download(Queue& q, void* dst) const { + if (bytes_ > 0) b_->Copy(q, dst, p_, bytes_); + b_->Synchronize(q); + } + + private: + Backend* b_; + void* p_ = nullptr; + size_t bytes_ = 0; +}; + +// The CUDA twin of RunNorm, argument for argument. +std::vector RunNormCuda(Backend& gpu, const NormInputs& in, + const std::vector& shape, int64_t n_groups, float eps, + DType dt, bool use_rms_norm, int64_t tp_world_size = 1, + DType weight_dt = DType::kF32, DType out_dt = kSameAsAct) { + Queue q = gpu.CreateQueue(); + const Device dev{DeviceType::kCUDA, 0}; + if (out_dt == kSameAsAct) out_dt = dt; + size_t n = 1; + for (int64_t d : shape) n *= static_cast(d); + const std::vector xb = Pack(in.x, dt); + const std::vector gb = Pack(in.gate, dt); + const std::vector wb = Pack(in.weight, weight_dt); + const size_t out_bytes = n * vt::SizeOf(out_dt); + + DBuf dx(gpu, q, xb.data(), xb.size()); + DBuf dg(gpu, q, gb.data(), gb.size()); + DBuf dw(gpu, q, wb.data(), wb.size()); + DBuf dout(gpu, q, nullptr, out_bytes); + + Tensor xt = MakeTDev(dx.get(), dt, dev, shape); + Tensor gt = MakeTDev(dg.get(), dt, dev, shape); + Tensor ot = MakeTDev(dout.get(), out_dt, dev, shape); + Tensor wt = MakeTDev(dw.get(), weight_dt, dev, {shape.back()}); + + RmsNormGatedGroupArgs args; + args.eps = eps; + args.n_groups = n_groups; + args.tp_world_size = tp_world_size; + vt::RmsNormGatedGroup(q, ot, xt, gt, use_rms_norm ? &wt : nullptr, args); + + std::vector ob(out_bytes); + dout.Download(q, ob.data()); + gpu.Synchronize(q); + gpu.DestroyQueue(q); + return Unpack(ob, n, out_dt); +} + +} // namespace + +TEST_CASE("mamba2 gated group norm CUDA arm matches forward_native") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + // Upstream's shapes (batch 8, seq 128, hidden 64, n_groups in {1,2,4}, + // test_mamba_mixer2.py:21-33) plus one at the driver's own width: the mamba + // layer's gated norm runs over `intermediate_size` with n_groups = 8 + // (mamba2-ssd.md §1.4), which is the case whose group_size is large enough for + // the block reduction to differ from the host's sequential sum at all. + struct Case { + int64_t rows, hidden, n_groups; + }; + const std::vector cases{{8 * 128, 64, 1}, {8 * 128, 64, 2}, + {8 * 128, 64, 4}, {64, 4096, 8}}; + const float eps = 1e-6f; + for (const Case& c : cases) { + const NormInputs in = GenerateNorm(c.rows, c.hidden, 0x9A17Eu); + const int64_t group_size = c.hidden / c.n_groups; + INFO("rows=" << c.rows << " hidden=" << c.hidden << " n_groups=" << c.n_groups); + + const std::vector ref = + GatedGroupNormRef(in.x, in.gate, &in.weight, c.rows, c.hidden, c.n_groups, eps); + const std::vector host = + RunNorm(in, {c.rows, c.hidden}, c.n_groups, eps, DType::kF32, true); + const std::vector dev = + RunNormCuda(*gpu, in, {c.rows, c.hidden}, c.n_groups, eps, DType::kF32, true); + RequireNativeCudaProvider(vt::OpId::kRmsNormGatedGroup, "forward_native shapes"); + // G1 — device vs the INDEPENDENT double reference at upstream's tolerance. + ExpectClose("device out f32", dev, ref, 5e-3, 1e-3); + // G2 — device vs host, at the derived bound for a length-group_size reduction. + ExpectDeviceMatchesHost("out f32 device vs host", dev, host, group_size); + + // bf16 activation arm (upstream runs float16; the vt `out` contract is + // f32/bf16). `input_dtype` is x's dtype (:113), so the reference casts + // through bf16 at :149 too. + const std::vector bref = + GatedGroupNormRef(in.x, in.gate, &in.weight, c.rows, c.hidden, c.n_groups, eps, + DType::kBF16); + ExpectClose("device out bf16", + RunNormCuda(*gpu, in, {c.rows, c.hidden}, c.n_groups, eps, DType::kBF16, true), + bref, 5e-2, 1e-2); + } +} + +// The two differences that make this a SIBLING of vt::RmsNormGated rather than a +// mode of it, pinned on device: the reduction extent (per GROUP, not per row) and +// the activation (silu, not sigmoid). +TEST_CASE("mamba2 gated group norm CUDA arm keeps both sibling differences") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + const int64_t rows = 32, hidden = 64; + const float eps = 1e-6f; + const NormInputs in = GenerateNorm(rows, hidden, 0x6A7Eu); + + SUBCASE("the reduction is per group, not per row") { + const std::vector g1 = + RunNormCuda(*gpu, in, {rows, hidden}, 1, eps, DType::kF32, true); + const std::vector g4 = + RunNormCuda(*gpu, in, {rows, hidden}, 4, eps, DType::kF32, true); + RequireNativeCudaProvider(vt::OpId::kRmsNormGatedGroup, "per-group reduction"); + REQUIRE(g1.size() == g4.size()); + double max_diff = 0.0; + for (size_t i = 0; i < g1.size(); ++i) + max_diff = std::max(max_diff, std::abs(static_cast(g1[i]) - g4[i])); + INFO("max|n_groups=1 - n_groups=4| on device = " << max_diff); + // A whole-row variance would make these IDENTICAL. They must not be. + CHECK(max_diff > 1e-3); + // ... and n_groups=4 is the one that matches a per-group double reference. + ExpectClose("device n_groups=4", g4, + GatedGroupNormRef(in.x, in.gate, &in.weight, rows, hidden, 4, eps), 5e-3, 1e-3); + } + + SUBCASE("the gate is silu, not sigmoid") { + // A sigmoid-gated reference must NOT match what the device produced. + const NormInputs& sig = in; + const std::vector dev = + RunNormCuda(*gpu, in, {rows, hidden}, 2, eps, DType::kF32, true); + // The reference a SIGMOID gate would give, written out here rather than + // parameterised, so it shares nothing with the op under test. + std::vector sigmoid_ref(static_cast(rows * hidden), 0.0); + { + const int64_t group_size = hidden / 2; + for (int64_t r = 0; r < rows; ++r) { + std::vector v(static_cast(hidden)); + for (int64_t j = 0; j < hidden; ++j) { + const double zv = sig.gate[static_cast(r * hidden + j)]; + v[static_cast(j)] = + static_cast(sig.x[static_cast(r * hidden + j)]) / + (1.0 + std::exp(-zv)); // SIGMOID, not silu + } + for (int64_t g = 0; g < 2; ++g) { + double ss = 0.0; + for (int64_t j = 0; j < group_size; ++j) { + const double t = v[static_cast(g * group_size + j)]; + ss += t * t; + } + const double inv = 1.0 / std::sqrt(ss / static_cast(group_size) + eps); + for (int64_t j = 0; j < group_size; ++j) { + const int64_t idx = g * group_size + j; + sigmoid_ref[static_cast(r * hidden + idx)] = + static_cast(sig.weight[static_cast(idx)]) * + v[static_cast(idx)] * inv; + } + } + } + } + double max_diff = 0.0; + for (size_t i = 0; i < dev.size(); ++i) + max_diff = std::max(max_diff, std::abs(static_cast(dev[i]) - sigmoid_ref[i])); + INFO("max|device - sigmoid-gated reference| = " << max_diff); + CHECK(max_diff > 1e-2); + } +} + +// `use_rms_norm == False`: no parameter, no norm, just the gated value cast back +// to the input dtype (mamba_mixer2.py:94-96, :115-116); and every leading dim is +// a row (`*prefix_dims, hidden_dim`, :136), so a rank-3 [T,H,D] input is the same +// computation as its flattened rank-2 view. +TEST_CASE("mamba2 gated group norm CUDA arm covers the no-weight and rank-3 arms") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + const float eps = 1e-6f; + + SUBCASE("no weight: the gated value, unnormalised") { + const int64_t rows = 40, hidden = 64; + const NormInputs in = GenerateNorm(rows, hidden, 0x1234u); + const std::vector ref = + GatedGroupNormRef(in.x, in.gate, nullptr, rows, hidden, 4, eps); + const std::vector dev = + RunNormCuda(*gpu, in, {rows, hidden}, 4, eps, DType::kF32, false); + RequireNativeCudaProvider(vt::OpId::kRmsNormGatedGroup, "no-weight arm"); + ExpectClose("device out (no weight)", dev, ref, 5e-3, 1e-3); + ExpectDeviceMatchesHost("no-weight device vs host", dev, + RunNorm(in, {rows, hidden}, 4, eps, DType::kF32, false), 1); + } + + SUBCASE("rank 3 [T,H,D] equals its flattened rank-2 view") { + const int64_t T = 12, Hh = 4, Dd = 32; + const int64_t rows = T * Hh; + const NormInputs in = GenerateNorm(rows, Dd, 0x5678u); + const std::vector flat = + RunNormCuda(*gpu, in, {rows, Dd}, 2, eps, DType::kF32, true); + const std::vector cube = + RunNormCuda(*gpu, in, {T, Hh, Dd}, 2, eps, DType::kF32, true); + REQUIRE(flat.size() == cube.size()); + for (size_t i = 0; i < flat.size(); ++i) CHECK(flat[i] == cube[i]); + } + + SUBCASE("the weight is read at the WEIGHT's dtype") { + // `Mixer2RMSNormGated.weight` is created at the MODEL dtype (:91), bf16 for + // every checkpoint that ships this layer. A kernel that read it as f32 would + // over-read a real allocation and shift every output — the W1 F1 finding + // (mamba2-ssd.md §8.2), re-pinned here for the device arm. + const int64_t rows = 32, hidden = 64; + NormInputs in = GenerateNorm(rows, hidden, 0x9999u); + for (auto& w : in.weight) w = vt::BF16ToF32(vt::F32ToBF16(w)); + const std::vector ref = + GatedGroupNormRef(in.x, in.gate, &in.weight, rows, hidden, 2, eps); + const std::vector dev = RunNormCuda(*gpu, in, {rows, hidden}, 2, eps, DType::kF32, + true, 1, DType::kBF16); + ExpectClose("device out, bf16 weight", dev, ref, 5e-3, 1e-3); + } +} + +#endif // VLLM_CPP_CUDA diff --git a/tests/vt/test_ops_mamba2_ssd.cpp b/tests/vt/test_ops_mamba2_ssd.cpp index e4a5eee7c..37add73fd 100644 --- a/tests/vt/test_ops_mamba2_ssd.cpp +++ b/tests/vt/test_ops_mamba2_ssd.cpp @@ -999,3 +999,750 @@ TEST_CASE("mamba2 chunk scan refuses the arms it does not implement") { nullptr, nullptr, cust, ccst, lcit, sit, args)); } } + +// ═════════════════════════════════════════════════════════════════════════════ +// (8) THE CUDA ARM — .agents/specs/mamba2-ssd.md W2, issue #496. +// +// ─── THE DECLARED EQUIVALENCE CONTRACT ────────────────────────────────────── +// The CUDA kernels keep f32 accumulation THROUGHOUT and do NOT mirror the tile +// downcasts inside upstream's Triton dots (`b.to(x_ptr.dtype.element_ty)` +// ssd_chunk_state.py:283-285; `cb.to(...)` / `prev_states.to(C_ptr.dtype...)` +// ssd_chunk_scan.py:266-269, :359-363). Those casts are the input-precision +// requirement of `tl.dot`, i.e. of a tensor-core MMA — every one of those tiles is +// loaded `.to(tl.float32)` and computed in f32 right up to the MMA. Our kernels +// are scalar-FMA kernels with no MMA, so the bar here is NOT a downcast-derived +// tolerance. The memory format is unchanged: every load and store goes through the +// operand's own dtype, and the inter-chunk `passed` buffer is allocated at +// `state_dtype`, not at the host reference's f32 working width (§8.2 F9). +// +// Two gates, in order of authority: +// +// G1 (PRIMARY, INDEPENDENT). The device outputs are held to the SAME +// double-precision sequential reference (`SequentialSsdRef`) at the SAME +// upstream-ported tolerances as the host arm. Every structural defect — a +// dropped inter-chunk term, `states[c]` for `states[c-1]`, ignored +// `initial_states`, a missing `D` skip — is an O(1) error and fails here, +// against a reference the kernel was not written from. +// +// G2 (DEVICE vs HOST, DERIVED). A BYTE COMPARE IS NOT REACHABLE, for TWO +// named reasons and no others: +// +// (a) LIBM. The two arms call different ones. CUDA's `expf` is documented to +// <= 2 ulp and glibc's to <= 0.5, and they disagree in the last ulp on +// some inputs, so `exp`/`log1p` alone put the two arms off each other by +// ulps that the recurrence then amplifies. +// (b) FMA CONTRACTION. Host C++ is pinned `-ffp-contract=off` +// (CMakeLists.txt:41-56), so `a*b + c` keeps two roundings; nothing +// passes `--fmad=false` to nvcc, so the device arm compiles at the +// DEFAULT `--fmad=true` and every `acc += a*b` is a single-rounding +// `fma` whose host twin is not. `-fmad=false` was weighed and rejected +// at src/vt/cuda/cuda_mamba2_ssd.cuh — it is a per-TU flag on a header +// included by a hot GDN TU — so the bound carries the term. +// +// Summation ORDER is not a third source: the device kernels accumulate every +// output element in ONE thread, over the same index range in the same +// direction as the host arm. `DerivedRtol` below propagates (a) and (b), and +// only those. Nothing in it was tuned, and the slack actually USED is reported +// on every comparison — if it ever approached the bar, the bar would have +// stopped being a statement about libm and contraction and the finding would +// be a NEEDS_DECISION, not a wider tolerance. +// ═════════════════════════════════════════════════════════════════════════════ +#ifdef VLLM_CPP_CUDA + +#include +#include + +namespace { + +using vt::Backend; + +Backend* MaybeCuda() { + try { + return &vt::GetBackend(DeviceType::kCUDA); + } catch (const std::exception&) { + return nullptr; + } +} + +// A GREEN TEST DOES NOT PROVE THE DEVICE RAN IT — and on THIS box it very nearly +// proves the opposite. GB10 is `integrated && pageable_memory_access`, so +// `Backend::UnifiedMemory()` is TRUE (cuda_backend.cu Registrar) and therefore +// `ReferenceTierEligible(kCUDA)` is TRUE. Absent a native kernel, `GetOp` does +// not throw: it installs the CPU HOST kernel as a `kReferenceProviderName` +// provider and runs THAT over the device pointers (op_provider.h, "portable +// reference tier"). Every numeric assertion below would then pass — the device +// arm would be gated by running the host arm twice, the exact false-green shape +// of [[absent-hook-looks-like-armed-instrument]] and +// [[gate-comparing-shared-helper-proves-consistency-not-correctness]]. +// +// So every CUDA case asserts the SELECTED provider is native. These are EAGER +// dispatches rather than a captured graph, so the counters are genuinely +// populated ([[graph-replay-does-no-host-dispatch-counters-read-zero]]). +void RequireNativeCudaProvider(vt::OpId op, const std::string& what) { + const vt::OpProviderStats st = vt::GetOpProviderStats(op, DeviceType::kCUDA); + INFO(what << ": selected CUDA provider = " + << (st.last_selected != nullptr ? st.last_selected : "") + << "; process-wide reference-tier hits = " << vt::GetReferenceTierHits()); + REQUIRE(st.last_selected != nullptr); + CHECK(std::string(st.last_selected) != std::string(vt::kReferenceProviderName)); +} + +// f32 unit roundoff, and the bound the two arms are held to. +// +// A value that has run through a product of at most K decay factors and a +// length-K f32 sum of products carries, between the two arms, THREE terms: +// +// libm <= 2.5 ulp PER FACTOR — CUDA `expf` <= 2 ulp, glibc <= 0.5 — +// through a product of at most K, i.e. <= 2.5*K*u; +// summation the standard (K-1)*u forward error of a length-K f32 sum, +// which is what AMPLIFIES the libm difference in the inputs; +// contraction the device arm's `acc += a*b` is ONE nvcc-`fmad` rounding and +// the host arm's is TWO (`-ffp-contract=off`, CMakeLists.txt:55), +// so the host carries K product roundings the device does not: +// <= K*u. See the FMA-contraction note in +// src/vt/cuda/cuda_mamba2_ssd.cuh for why the flag is not simply +// turned off instead. +// +// Total <= 2.5*K*u + (K-1)*u + K*u = 4.5*K*u - u. `5*(K + 2)*u` is that, +// rounded up to integers, and it covers the model for every K >= 0 because +// 5K + 10 >= 4.5K - 1 reduces to 0.5K + 11 >= 0. +// +// THE OLD `4*(K + 2)*u` DID NOT. It omitted the contraction term, and +// 4.5K - 1 <= 4K + 8 holds only for K <= 18 — while the driver-shapes case runs +// at K = T = 200. The constant moved because the DERIVATION gained a term the +// build actually emits, not because a run needed slack: at 4*(K+2) the worst of +// the 55 audited comparisons used 7.66% of budget, so at 5*(K+2) it uses 6.13%, +// and mutant M3's 962173% becomes 769738% — still caught by four orders of +// magnitude (§8.4). +// +// K is the case's own recurrence length; nothing here is fitted, and the slack +// actually USED is reported on every comparison so a bar that had stopped doing +// work would be visible rather than silently absorbing a defect. +constexpr double kUnitRoundoff = 5.9604644775390625e-08; // 2^-24 +double DerivedRtol(int64_t K) { return 5.0 * static_cast(K + 2) * kUnitRoundoff; } + +// atol is `rtol * max|host|` rather than 0: a bound proportional to |want| alone +// is vacuous for an element that is near zero through cancellation of O(max) +// terms, which this recurrence produces routinely. +void ExpectDeviceMatchesHost(const std::string& what, const std::vector& dev, + const std::vector& host, int64_t K) { + REQUIRE(dev.size() == host.size()); + REQUIRE(!dev.empty()); + double scale = 0.0; + for (float v : host) scale = std::max(scale, std::abs(static_cast(v))); + const double rtol = DerivedRtol(K); + const double atol = rtol * scale; + size_t bit_differing = 0, worst_i = 0; + double worst_ratio = -1.0, worst_diff = 0.0; + for (size_t i = 0; i < dev.size(); ++i) { + if (dev[i] != host[i]) ++bit_differing; + const double d = std::abs(static_cast(dev[i]) - static_cast(host[i])); + const double budget = atol + rtol * std::abs(static_cast(host[i])); + const double ratio = budget > 0.0 ? d / budget : (d > 0.0 ? 1e30 : 0.0); + if (!std::isfinite(static_cast(dev[i])) || ratio > worst_ratio) { + worst_ratio = ratio; + worst_i = i; + worst_diff = d; + if (!std::isfinite(static_cast(dev[i]))) break; + } + } + // MESSAGE, not INFO: doctest prints an INFO context only when an assertion in + // its scope FAILS, so a claim that the used slack "is reported" would be false + // on the green run that matters. This line is emitted unconditionally, which is + // what makes the derived bar auditable rather than merely asserted. + MESSAGE(what << ": K=" << K << " rtol=" << rtol << " scale=" << scale << "; " + << bit_differing << " of " << dev.size() + << " elements differ in any bit; worst element [" << worst_i + << "] dev=" << dev[worst_i] << " host=" << host[worst_i] + << " |diff|=" << worst_diff << " used " << (worst_ratio * 100.0) + << "% of its derived budget"); + CHECK(std::isfinite(static_cast(dev[worst_i]))); + CHECK(worst_ratio <= 1.0); +} + +Tensor MakeTDev(void* data, DType dt, Device dev, const std::vector& shape) { + Tensor t; + t.data = data; + t.dtype = dt; + t.device = dev; + t.rank = static_cast(shape.size()); + int64_t stride = 1; + for (int i = t.rank - 1; i >= 0; --i) { + t.shape[i] = shape[static_cast(i)]; + t.stride[i] = stride; + stride *= shape[static_cast(i)]; + } + return t; +} + +// Owning device allocation, uploaded from host bytes (or left zero-sized). +class DBuf { + public: + DBuf(Backend& b, Queue& q, const void* host, size_t bytes) : b_(&b), bytes_(bytes) { + p_ = b.Alloc(bytes == 0 ? 1 : bytes); + if (host != nullptr && bytes > 0) b.Copy(q, p_, host, bytes); + } + ~DBuf() { + if (p_ != nullptr) b_->Free(p_); + } + DBuf(const DBuf&) = delete; + DBuf& operator=(const DBuf&) = delete; + void* get() const { return p_; } + void Download(Queue& q, void* dst) const { + if (bytes_ > 0) b_->Copy(q, dst, p_, bytes_); + b_->Synchronize(q); + } + + private: + Backend* b_; + void* p_ = nullptr; + size_t bytes_ = 0; +}; + +// The CUDA twin of RunChunkScan, argument for argument. +// +// `lci_override` / `sidx_override` replace the metadata `ComputeVarlenChunkMetadata` +// derived, and exist for ONE caller: the clamp case below, which must reach the +// device kernels with metadata the host arm refuses. Every other caller passes +// nullptr and gets the derived metadata unchanged. +RunOut RunChunkScanCuda(Backend& gpu, const Inputs& in, int64_t T, int64_t H, int64_t P, + int64_t G, int64_t N, const std::vector& cu_seqlens, + const std::vector* D, const std::vector* z, + const std::vector* dt_bias, + const std::vector* initial_states, const RunCfg& cfg, + const std::vector* lci_override = nullptr, + const std::vector* sidx_override = nullptr) { + Queue q = gpu.CreateQueue(); + const Device dev{DeviceType::kCUDA, 0}; + const int64_t S = static_cast(cu_seqlens.size()) - 1; + ChunkMeta meta = ComputeVarlenChunkMetadata(cu_seqlens, cfg.chunk_size); + const int64_t nchunks = static_cast(meta.seq_idx.size()); + if (lci_override != nullptr) meta.last_chunk_indices = *lci_override; + if (sidx_override != nullptr) meta.seq_idx = *sidx_override; + + const std::vector xb = Pack(in.x, cfg.act_dtype); + const std::vector dtb = Pack(in.dt, cfg.act_dtype); + const std::vector Bb = Pack(in.B, cfg.act_dtype); + const std::vector Cb = Pack(in.C, cfg.act_dtype); + const size_t out_bytes = static_cast(T * H * P) * vt::SizeOf(cfg.act_dtype); + const size_t fs_bytes = static_cast(S * H * P * N) * vt::SizeOf(cfg.state_dtype); + std::vector cus = cu_seqlens; + + DBuf dx(gpu, q, xb.data(), xb.size()); + DBuf ddt(gpu, q, dtb.data(), dtb.size()); + DBuf dA(gpu, q, in.A.data(), in.A.size() * sizeof(float)); + DBuf dB(gpu, q, Bb.data(), Bb.size()); + DBuf dC(gpu, q, Cb.data(), Cb.size()); + DBuf dout(gpu, q, nullptr, out_bytes); + DBuf dfs(gpu, q, nullptr, fs_bytes); + DBuf dcus(gpu, q, cus.data(), cus.size() * sizeof(int32_t)); + DBuf dccs(gpu, q, meta.cu_chunk_seqlens.data(), meta.cu_chunk_seqlens.size() * sizeof(int32_t)); + DBuf dlci(gpu, q, meta.last_chunk_indices.data(), + meta.last_chunk_indices.size() * sizeof(int32_t)); + DBuf dsi(gpu, q, meta.seq_idx.data(), meta.seq_idx.size() * sizeof(int32_t)); + + Tensor xt = MakeTDev(dx.get(), cfg.act_dtype, dev, {T, H, P}); + Tensor dtt = MakeTDev(ddt.get(), cfg.act_dtype, dev, {T, H}); + Tensor At = MakeTDev(dA.get(), DType::kF32, dev, {H}); + Tensor Bt = MakeTDev(dB.get(), cfg.act_dtype, dev, {T, G, N}); + Tensor Ct = MakeTDev(dC.get(), cfg.act_dtype, dev, {T, G, N}); + Tensor outt = MakeTDev(dout.get(), cfg.act_dtype, dev, {T, H, P}); + Tensor fst = MakeTDev(dfs.get(), cfg.state_dtype, dev, {S, H, P, N}); + Tensor cust = MakeTDev(dcus.get(), DType::kI32, dev, {S + 1}); + Tensor ccst = MakeTDev(dccs.get(), DType::kI32, dev, {nchunks + 1}); + Tensor lcit = MakeTDev(dlci.get(), DType::kI32, dev, {S}); + Tensor sit = MakeTDev(dsi.get(), DType::kI32, dev, {nchunks}); + + std::vector Dc; + std::unique_ptr dD; + Tensor Dt; + if (D != nullptr) { + Dc = *D; + dD = std::make_unique(gpu, q, Dc.data(), Dc.size() * sizeof(float)); + Dt = cfg.d_has_hdim ? MakeTDev(dD->get(), DType::kF32, dev, {H, P}) + : MakeTDev(dD->get(), DType::kF32, dev, {H}); + } + std::vector zb; + std::unique_ptr dz; + Tensor zt; + if (z != nullptr) { + zb = Pack(*z, cfg.act_dtype); + dz = std::make_unique(gpu, q, zb.data(), zb.size()); + zt = MakeTDev(dz->get(), cfg.act_dtype, dev, {T, H, P}); + } + std::vector dbc; + std::unique_ptr ddb; + Tensor dbt; + if (dt_bias != nullptr) { + dbc = *dt_bias; + ddb = std::make_unique(gpu, q, dbc.data(), dbc.size() * sizeof(float)); + dbt = MakeTDev(ddb->get(), DType::kF32, dev, {H}); + } + std::vector isb; + std::unique_ptr dis; + Tensor ist; + if (initial_states != nullptr) { + isb = Pack(*initial_states, cfg.state_dtype); + dis = std::make_unique(gpu, q, isb.data(), isb.size()); + ist = MakeTDev(dis->get(), cfg.state_dtype, dev, {S, H, P, N}); + } + + Mamba2Args args; + args.chunk_size = cfg.chunk_size; + args.dt_softplus = cfg.dt_softplus; + args.dt_min = cfg.dt_min; + args.dt_max = cfg.dt_max; + + vt::Mamba2ChunkScan(q, outt, fst, xt, dtt, At, Bt, Ct, D != nullptr ? &Dt : nullptr, + z != nullptr ? &zt : nullptr, dt_bias != nullptr ? &dbt : nullptr, + initial_states != nullptr ? &ist : nullptr, cust, ccst, lcit, sit, args); + + std::vector outb(out_bytes), fsb(fs_bytes); + dout.Download(q, outb.data()); + dfs.Download(q, fsb.data()); + gpu.Synchronize(q); + + RunOut r; + r.y = Unpack(outb, static_cast(T * H * P), cfg.act_dtype); + r.final_states = Unpack(fsb, static_cast(S * H * P * N), cfg.state_dtype); + gpu.DestroyQueue(q); + return r; +} + +} // namespace + +// G1 + G2 on the shapes the row exists for: Nemotron-3.5-Lightning-30B-A3B's +// mamba layer — nheads 64, headdim 64, dstate 128, ngroups 8, chunk_size 128, +// mamba_ssm_cache_dtype float32 (mamba2-ssd.md §1.4). T is 200 so `nchunks == 2` +// and the arm actually exercises inter-chunk state passing — the failure shape of +// [[h3-video-decode-temporal-and-tiling-compose]], and of §8.2 F6. +TEST_CASE("mamba2 chunk scan CUDA arm on the driver shapes") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + const int64_t T = 200, H = 64, P = 64, G = 8, N = 128, chunk = 128; + const Inputs in = GenerateInputs(T, H, P, G, N, 0x4E33Au); + const std::vector cu{0, static_cast(T)}; + REQUIRE(ComputeVarlenChunkMetadata(cu, chunk).seq_idx.size() > 1); + + std::mt19937 rng(0x77u); + std::normal_distribution nd(0.0f, 1.0f); + std::vector D(static_cast(H)); + for (auto& v : D) v = nd(rng); + + RunCfg cfg; + cfg.chunk_size = chunk; + const SeqRefOut ref = + SequentialSsdRef(in, T, H, P, G, N, cu, &D, false, nullptr, nullptr, nullptr, {}); + const RunOut host = RunChunkScan(in, T, H, P, G, N, cu, &D, nullptr, nullptr, nullptr, cfg); + const RunOut dev = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, &D, nullptr, nullptr, nullptr, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "driver shapes"); + + // G1 — the device arm against the INDEPENDENT double reference, at upstream's + // own f32 threshold for the chunked single-example test (atol 8e-3 / rtol 5e-3, + // test_mamba_ssm_ssd.py:210-213). THE HOST ARM IS HELD TO THE SAME NUMBERS on + // the same inputs, so a failure separates cleanly: device-only means a device + // defect, both means the cited threshold does not cover this shape and the + // finding is a NEEDS_DECISION rather than a wider tolerance. + ExpectClose("host y vs sequential double", host.y, ref.y, 8e-3, 5e-3); + ExpectClose("host final_states vs sequential double", host.final_states, ref.final_states, + 8e-3, 5e-3); + ExpectClose("device y vs sequential double", dev.y, ref.y, 8e-3, 5e-3); + ExpectClose("device final_states vs sequential double", dev.final_states, ref.final_states, + 8e-3, 5e-3); + // G2 — device vs host, at the derived libm bound. + ExpectDeviceMatchesHost("y device vs host", dev.y, host.y, T); + ExpectDeviceMatchesHost("final_states device vs host", dev.final_states, host.final_states, T); +} + +// The structural properties the chunked factorisation lives on, on device: +// chunk-size invariance, sequence boundaries INSIDE a physical chunk, and +// `initial_states`. Small shapes so the sweep stays cheap; the driver shapes are +// covered above. +TEST_CASE("mamba2 chunk scan CUDA arm holds the chunked-factorisation properties") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + + SUBCASE("invariant to chunk_size") { + const int64_t T = 300, H = 8, P = 16, G = 2, N = 32; + const Inputs in = GenerateInputs(T, H, P, G, N, 0xC0FFEEu); + const std::vector cu{0, static_cast(T)}; + const SeqRefOut ref = + SequentialSsdRef(in, T, H, P, G, N, cu, nullptr, false, nullptr, nullptr, nullptr, {}); + std::vector first_y, first_state; + for (int64_t chunk : {8, 16, 32, 64, 128}) { + RunCfg cfg; + cfg.chunk_size = chunk; + const int64_t nchunks = + static_cast(ComputeVarlenChunkMetadata(cu, chunk).seq_idx.size()); + INFO("chunk_size=" << chunk << " nchunks=" << nchunks); + REQUIRE(nchunks > 1); + const RunOut dev = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, nullptr, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "chunk_size invariance"); + const RunOut host = + RunChunkScan(in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, nullptr, cfg); + ExpectClose("device y vs sequential", dev.y, ref.y, 1e-2, 5e-3); + ExpectClose("device final_states vs sequential", dev.final_states, ref.final_states, 1e-2, + 5e-3); + ExpectDeviceMatchesHost("y device vs host", dev.y, host.y, T); + ExpectDeviceMatchesHost("final_states device vs host", dev.final_states, host.final_states, + T); + if (first_y.empty()) { + first_y = dev.y; + first_state = dev.final_states; + } else { + ExpectCloseF("device y vs chunk_size=8", dev.y, first_y, 1e-2, 5e-3); + ExpectCloseF("device final_states vs chunk_size=8", dev.final_states, first_state, 1e-2, + 5e-3); + } + } + } + + SUBCASE("continuous batches, with and without initial_states") { + struct Case { + std::vector lens; + int64_t chunk; + }; + const std::vector cases{ + {{64, 32}, 8}, {{4, 4, 4, 4}, 8}, {{5, 30, 1, 2}, 256}, + {{138, 225}, 128}, {{270, 88}, 8}, + }; + const int64_t H = 8, P = 16, G = 2, N = 16; + for (const Case& c : cases) { + std::vector cu{0}; + for (int32_t l : c.lens) cu.push_back(cu.back() + l); + const int64_t T = cu.back(); + const int64_t S = static_cast(c.lens.size()); + const int64_t maxlen = *std::max_element(c.lens.begin(), c.lens.end()); + const double atol = maxlen > 256 ? 1e-2 : 5e-3; + const Inputs in = GenerateInputs(T, H, P, G, N, 0xBEEF01u + static_cast(c.chunk)); + INFO("chunk=" << c.chunk << " nseq=" << S << " T=" << T); + RunCfg cfg; + cfg.chunk_size = c.chunk; + + // (a) fresh sequences — the `seq_idx[c] != seq_idx[c-1]` branch must take + // ZEROS as the previous state (ssd_chunk_scan.py:271-274). + { + const SeqRefOut ref = SequentialSsdRef(in, T, H, P, G, N, cu, nullptr, false, nullptr, + nullptr, nullptr, {}); + const RunOut dev = RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, + nullptr, nullptr, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "continuous batch (fresh)"); + const RunOut host = + RunChunkScan(in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, nullptr, cfg); + ExpectClose("device y (fresh)", dev.y, ref.y, atol, 5e-3); + ExpectClose("device final_states (fresh)", dev.final_states, ref.final_states, atol, + 5e-3); + ExpectDeviceMatchesHost("y (fresh) device vs host", dev.y, host.y, maxlen); + ExpectDeviceMatchesHost("final_states (fresh) device vs host", dev.final_states, + host.final_states, maxlen); + } + // (b) with initial_states — the same branch must instead take + // initial_states[seq_idx[c]] (ssd_chunk_scan.py:236-243). + { + std::mt19937 rng(1234u); + std::normal_distribution nd(0.0f, 0.5f); + std::vector init(static_cast(S * H * P * N)); + for (auto& v : init) v = nd(rng); + const std::vector initd(init.begin(), init.end()); + const SeqRefOut ref = SequentialSsdRef(in, T, H, P, G, N, cu, nullptr, false, nullptr, + nullptr, &initd, {}); + const RunOut dev = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, &init, cfg); + const RunOut host = + RunChunkScan(in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, &init, cfg); + ExpectClose("device y (init states)", dev.y, ref.y, atol, 5e-3); + ExpectClose("device final_states (init states)", dev.final_states, ref.final_states, atol, + 5e-3); + ExpectDeviceMatchesHost("y (init) device vs host", dev.y, host.y, maxlen); + ExpectDeviceMatchesHost("final_states (init) device vs host", dev.final_states, + host.final_states, maxlen); + } + } + } +} + +// The optional arms and the dtype knobs, on device: D as [H] and [H,P], the z +// silu gate, dt_bias + dt_softplus, the dt_limit clamp, a bf16 activation stream +// and a bf16 SSM state. +TEST_CASE("mamba2 chunk scan CUDA arm covers the optional arms and the dtype knobs") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + const int64_t T = 100, H = 8, P = 12, G = 2, N = 16, chunk = 32; + const Inputs in = GenerateInputs(T, H, P, G, N, 0xD00D42u); + const std::vector cu{0, 40, static_cast(T)}; + const int64_t maxlen = 60; // the longer of the two sequences + + std::mt19937 rng(7u); + std::normal_distribution nd(0.0f, 1.0f); + std::uniform_real_distribution ud(0.0f, 1.0f); + std::vector d_head_scalar(static_cast(H)); + for (auto& v : d_head_scalar) v = nd(rng); + std::vector d_hdim(static_cast(H * P)); + for (auto& v : d_hdim) v = nd(rng); + std::vector z(static_cast(T * H * P)); + for (auto& v : z) v = nd(rng); + std::vector dt_bias(static_cast(H)); + for (auto& v : dt_bias) v = ud(rng) - 4.0f; + + SUBCASE("D as [H]") { + RunCfg cfg; + cfg.chunk_size = chunk; + const SeqRefOut ref = SequentialSsdRef(in, T, H, P, G, N, cu, &d_head_scalar, false, nullptr, + nullptr, nullptr, {}); + const RunOut dev = RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, &d_head_scalar, nullptr, + nullptr, nullptr, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "D as [H]"); + const RunOut host = + RunChunkScan(in, T, H, P, G, N, cu, &d_head_scalar, nullptr, nullptr, nullptr, cfg); + ExpectClose("device y", dev.y, ref.y, 5e-3, 5e-3); + ExpectDeviceMatchesHost("y device vs host", dev.y, host.y, maxlen); + } + SUBCASE("D as [H,P]") { + RunCfg cfg; + cfg.chunk_size = chunk; + cfg.d_has_hdim = true; + const SeqRefOut ref = + SequentialSsdRef(in, T, H, P, G, N, cu, &d_hdim, true, nullptr, nullptr, nullptr, {}); + const RunOut dev = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, &d_hdim, nullptr, nullptr, nullptr, cfg); + const RunOut host = + RunChunkScan(in, T, H, P, G, N, cu, &d_hdim, nullptr, nullptr, nullptr, cfg); + ExpectClose("device y", dev.y, ref.y, 5e-3, 5e-3); + ExpectDeviceMatchesHost("y device vs host", dev.y, host.y, maxlen); + } + SUBCASE("z silu gate") { + RunCfg cfg; + cfg.chunk_size = chunk; + const SeqRefOut ref = + SequentialSsdRef(in, T, H, P, G, N, cu, nullptr, false, &z, nullptr, nullptr, {}); + const RunOut dev = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, &z, nullptr, nullptr, cfg); + const RunOut host = RunChunkScan(in, T, H, P, G, N, cu, nullptr, &z, nullptr, nullptr, cfg); + ExpectClose("device y", dev.y, ref.y, 5e-3, 5e-3); + ExpectDeviceMatchesHost("y device vs host", dev.y, host.y, maxlen); + } + SUBCASE("dt_bias + dt_softplus, then the dt_limit clamp") { + RefCfg rc; + rc.dt_softplus = true; + RunCfg cfg; + cfg.chunk_size = chunk; + cfg.dt_softplus = true; + { + const SeqRefOut ref = + SequentialSsdRef(in, T, H, P, G, N, cu, nullptr, false, nullptr, &dt_bias, nullptr, rc); + const RunOut dev = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, &dt_bias, nullptr, cfg); + const RunOut host = + RunChunkScan(in, T, H, P, G, N, cu, nullptr, nullptr, &dt_bias, nullptr, cfg); + ExpectClose("device y", dev.y, ref.y, 5e-3, 5e-3); + ExpectClose("device final_states", dev.final_states, ref.final_states, 5e-3, 5e-3); + ExpectDeviceMatchesHost("y device vs host", dev.y, host.y, maxlen); + } + RefCfg clamped = rc; + clamped.dt_min = 0.05; + clamped.dt_max = 0.10; + RunCfg ccfg = cfg; + ccfg.dt_min = 0.05f; + ccfg.dt_max = 0.10f; + const SeqRefOut cref = SequentialSsdRef(in, T, H, P, G, N, cu, nullptr, false, nullptr, + &dt_bias, nullptr, clamped); + const RunOut cdev = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, &dt_bias, nullptr, ccfg); + ExpectClose("device y (clamped)", cdev.y, cref.y, 5e-3, 5e-3); + // The clamp must actually BITE, or the comparison is trivial. + const SeqRefOut loose = + SequentialSsdRef(in, T, H, P, G, N, cu, nullptr, false, nullptr, &dt_bias, nullptr, rc); + double max_diff = 0.0; + for (size_t i = 0; i < loose.y.size(); ++i) + max_diff = std::max(max_diff, std::abs(loose.y[i] - cref.y[i])); + CHECK(max_diff > 1e-2); + } + SUBCASE("bf16 activations") { + Inputs bin = in; + RoundInputsTo(bin, DType::kBF16); + RunCfg cfg; + cfg.chunk_size = chunk; + cfg.act_dtype = DType::kBF16; + const SeqRefOut bref = + SequentialSsdRef(bin, T, H, P, G, N, cu, nullptr, false, nullptr, nullptr, nullptr, {}); + const RunOut dev = + RunChunkScanCuda(*gpu, bin, T, H, P, G, N, cu, nullptr, nullptr, nullptr, nullptr, cfg); + ExpectClose("device y bf16", dev.y, bref.y, 5e-2, 5e-2); + } + // `state_dtype` is a SEPARATE knob (ssd_combined.py:46,119,176), and it moves + // `out` as well as `final_states` because `_chunk_scan_fwd` reads the stored + // copy back (:249-250, :266-269). The device arm allocates its inter-chunk + // buffer at that width — NOT at the host reference's f32 working width (§8.2 F9) + // — so the two arms must agree on BOTH outputs. + SUBCASE("bf16 SSM state with f32 activations") { + const int64_t T2 = 96, H2 = 4, P2 = 8, G2 = 2, N2 = 16, chunk2 = 16; + const Inputs in2 = GenerateInputs(T2, H2, P2, G2, N2, 0x51A7Eu); + const std::vector cu2{0, static_cast(T2)}; + RunCfg cfg; + cfg.chunk_size = chunk2; + cfg.state_dtype = DType::kBF16; + const RunOut dev = RunChunkScanCuda(*gpu, in2, T2, H2, P2, G2, N2, cu2, nullptr, nullptr, + nullptr, nullptr, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "bf16 SSM state"); + const RunOut host = + RunChunkScan(in2, T2, H2, P2, G2, N2, cu2, nullptr, nullptr, nullptr, nullptr, cfg); + for (float v : dev.final_states) CHECK(vt::BF16ToF32(vt::F32ToBF16(v)) == v); + ExpectDeviceMatchesHost("y (bf16 state) device vs host", dev.y, host.y, T2); + ExpectDeviceMatchesHost("final_states (bf16 state) device vs host", dev.final_states, + host.final_states, T2); + // ... and the bf16 state really does move `out` on the device arm too. + RunCfg f32cfg = cfg; + f32cfg.state_dtype = DType::kF32; + const RunOut f32dev = RunChunkScanCuda(*gpu, in2, T2, H2, P2, G2, N2, cu2, nullptr, nullptr, + nullptr, nullptr, f32cfg); + double max_out_diff = 0.0; + for (size_t i = 0; i < dev.y.size(); ++i) + max_out_diff = + std::max(max_out_diff, std::abs(static_cast(dev.y[i]) - f32dev.y[i])); + INFO("device max|out(bf16 state) - out(f32 state)| = " << max_out_diff); + CHECK(max_out_diff > 1e-6); + } +} + +// ───────────────────────────────────────────────────────────────────────────── +// OUT-OF-CONTRACT METADATA, DEVICE ONLY. +// +// The shared validator checks metadata SHAPE / DTYPE / DEVICE only +// (`CheckI32Meta`, ops.cpp); every VALUE check lives in the HOST kernel, which +// reads the tensors (cpu_ops.cpp). The device arm therefore runs with NONE of +// them. src/vt/cuda/cuda_mamba2_ssd.cuh enumerates all six and says which are +// memory-unsafe; this case covers the TWO that are both memory-unsafe and +// bounded by a register-local clamp: +// * `last_chunk_indices[b] >= nchunks` makes the `passed` store run past its +// `cudaMallocAsync` allocation, and +// * a `seq_idx[c]` outside `[0,S)` reads `initial_states` out of bounds, with +// `seq_idx[0] < 0` additionally indexing `passed` at chunk -1. +// It PINS the clamped behaviour so it is a test, not a sentence in a header. +// The `cu_chunk_seqlens` checks are NOT clamped and are NOT covered here; the +// header states that as an open memory-safety gap rather than hiding it. +// +// THIS CASE HAS NO HOST TWIN, deliberately: the host kernel REFUSES both inputs, +// which is exactly why the device arm needed the clamp. Each clamp is pinned +// against an IN-CONTRACT reference run whose result the clamp is defined to +// reproduce, so the assertions are EXACT — a tolerance here would be wide enough +// to hide the defect it is meant to catch. +// +// What this case does NOT establish is memory safety itself. An out-of-bounds +// write into a `cudaMallocAsync` pool very often does not fault, so a green run +// is necessary and not sufficient; `compute-sanitizer memcheck` on this case is +// what proves it, and is recorded as owed in mamba2-ssd.md §8.5. +TEST_CASE("mamba2 chunk scan CUDA arm clamps out-of-contract metadata in registers") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + // Four one-chunk sequences, so `nchunks == S == 4` and every chunk opens a new + // sequence. That makes the in-contract `seq_idx` {0,1,2,3} and the in-contract + // `last_chunk_indices` {0,1,2,3}. + const int64_t H = 4, P = 8, G = 2, N = 16, chunk = 16; + const std::vector cu{0, 16, 32, 48, 64}; + const int64_t T = cu.back(), S = static_cast(cu.size()) - 1; + const Inputs in = GenerateInputs(T, H, P, G, N, 0xC1A47u); + const ChunkMeta meta = ComputeVarlenChunkMetadata(cu, chunk); + const int64_t nchunks = static_cast(meta.seq_idx.size()); + REQUIRE(nchunks == S); + // A braced init-list cannot appear inside a doctest macro — the preprocessor + // splits it on the commas — so the expectation is named first. + const std::vector in_contract{0, 1, 2, 3}; + REQUIRE(meta.last_chunk_indices == in_contract); + REQUIRE(meta.seq_idx == in_contract); + + std::mt19937 rng(0xB0B0u); + std::normal_distribution nd(0.0f, 0.5f); + std::vector init(static_cast(S * H * P * N)); + for (auto& v : init) v = nd(rng); + + RunCfg cfg; + cfg.chunk_size = chunk; + + auto max_abs_diff = [](const std::vector& a, const std::vector& b) { + REQUIRE(a.size() == b.size()); + double worst = 0.0; + for (size_t i = 0; i < a.size(); ++i) + worst = std::max(worst, std::abs(static_cast(a[i]) - static_cast(b[i]))); + return worst; + }; + auto count_differing = [](const std::vector& a, const std::vector& b) { + REQUIRE(a.size() == b.size()); + size_t n = 0; + for (size_t i = 0; i < a.size(); ++i) + if (a[i] != b[i]) ++n; + return n; + }; + + SUBCASE("last_chunk_indices past the end stops the chunk loop at nchunks") { + // In contract, the last sequence's `lci` is `nchunks - 1`. The clamp is + // defined to make anything >= nchunks behave as nchunks - 1, so these two + // runs must agree BIT FOR BIT — in `final_states` as much as in `y`, since + // the clamp is inside the state-passing kernel that produces both. + const RunOut ref = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, &init, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "lci clamp reference"); + for (int32_t past : {static_cast(nchunks), static_cast(nchunks + 41)}) { + std::vector lci = meta.last_chunk_indices; + lci.back() = past; + const RunOut got = RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, + &init, cfg, &lci, nullptr); + INFO("last_chunk_indices.back() = " << past << " (nchunks = " << nchunks << ")"); + CHECK(count_differing(got.y, ref.y) == 0); + CHECK(count_differing(got.final_states, ref.final_states) == 0); + } + } + + SUBCASE("a seq_idx outside [0,S) opens the chunk with a ZERO previous state") { + // The reference is an IN-CONTRACT run with NO initial states: `seq_idx` + // {0,1,2,3} makes every chunk open a new sequence, and with `initial_states + // == nullptr` every one of them therefore opens from zero. That is exactly + // what the clamp is defined to do for an out-of-range index, so `y` must + // match bit for bit. `final_states` is NOT compared: the state-passing + // kernel reads `initial_states` by `b`, not by `seq_idx`, so it legitimately + // differs between a run that was given initial states and one that was not. + const RunOut zero_ref = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, nullptr, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "seq_idx clamp reference"); + // ... and the pin is not vacuous: with the SAME metadata in contract, the + // initial states genuinely move `y`, so "matches the zero-init run" is a + // real statement rather than two identical computations + // ([[gate-comparing-shared-helper-proves-consistency-not-correctness]]). + const RunOut init_ref = + RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, &init, cfg); + INFO("max|y(initial_states) - y(none)| = " << max_abs_diff(init_ref.y, zero_ref.y)); + REQUIRE(max_abs_diff(init_ref.y, zero_ref.y) > 1e-6); + + // Over the top: every index >= S, which unclamped indexes `initial_states` + // out of bounds. Under the bottom: every index negative, which unclamped + // ALSO makes `si == si_prev` at c == 0 and indexes `passed` at chunk -1. + const std::vector> bad{ + {static_cast(S), static_cast(S + 1), static_cast(S + 2), + static_cast(S + 900)}, + {-1, -1, -1, -1}, + }; + for (const std::vector& sidx : bad) { + REQUIRE(static_cast(sidx.size()) == nchunks); + const RunOut got = RunChunkScanCuda(*gpu, in, T, H, P, G, N, cu, nullptr, nullptr, nullptr, + &init, cfg, nullptr, &sidx); + INFO("seq_idx = {" << sidx[0] << ", " << sidx[1] << ", " << sidx[2] << ", " << sidx[3] + << "}, S = " << S); + CHECK(count_differing(got.y, zero_ref.y) == 0); + } + } +} + +#endif // VLLM_CPP_CUDA diff --git a/tests/vt/test_ops_mamba2_state_update.cpp b/tests/vt/test_ops_mamba2_state_update.cpp index 81a2e9595..d4e6f9bf1 100644 --- a/tests/vt/test_ops_mamba2_state_update.cpp +++ b/tests/vt/test_ops_mamba2_state_update.cpp @@ -643,6 +643,50 @@ TEST_CASE("mamba2 state update refuses the arms it does not implement") { CHECK(msg.find("extra_groups_for_head_shards") != std::string::npos); } + // ── the precondition the decay rests on ─────────────────────────────────── + // `A = -exp(A_log)` (mamba_mixer2.py:456) is strictly negative for every finite + // `A_log`, and the decode step's `exp(A[h]*dt)` is a DECAY only while it is. + // Fed `A > 0` the step returns a silently GROWING recurrence where the caller + // asked for a decaying one, so the op refuses — `CheckMamba2ANegative`, + // src/vt/cpu/cpu_ops.cpp, the decode twin of the chunk-scan guard this case + // mirrors (test_ops_mamba2_ssd.cpp:900). + // + // WHY THIS SUBCASE EXISTS (mamba2-ssd.md §8.2, round-2 review correction). The + // guard was present, correct and reachable, and pinned by NOTHING: deleting the + // call left this whole suite green, while the same deletion on the chunk-scan + // twin reds. A guard no test can see is a guard the next refactor removes. + SUBCASE("A must be negative (A = -exp(A_log))") { + StepCfg cfg; + StepInputs bad = in; + bad.A[static_cast(H - 1)] = 1.0f; + std::vector raw(static_cast(Nb * H * P * N) * 4, 0); + bool threw = false; + std::string msg; + try { + RunStateUpdate(raw, Nb, bad, nullptr, Nb, H, P, G, N, cfg); + } catch (const std::exception& e) { + threw = true; + msg = e.what(); + } + INFO(msg); + CHECK(threw); + CHECK(msg.find("A_log") != std::string::npos); + + // Zero is refused too: `-exp(A_log)` is strictly negative for every finite + // A_log, and a flat (non-decaying) state is not what the layer asked for. + StepInputs zero = in; + zero.A[0] = 0.0f; + std::vector raw_zero(static_cast(Nb * H * P * N) * 4, 0); + CHECK_THROWS(RunStateUpdate(raw_zero, Nb, zero, nullptr, Nb, H, P, G, N, cfg)); + + // ... and a legitimately tiny negative A is ACCEPTED, so the guard is a + // sign test and not an accidental magnitude floor. + StepInputs tiny = in; + for (auto& v : tiny.A) v = -1e-30f; + std::vector raw_tiny(static_cast(Nb * H * P * N) * 4, 0); + CHECK_NOTHROW(RunStateUpdate(raw_tiny, Nb, tiny, nullptr, Nb, H, P, G, N, cfg)); + } + SUBCASE("a compact state must have one row per token") { StepCfg cfg; std::vector raw(static_cast((Nb + 1) * H * P * N) * 4, 0); @@ -682,3 +726,503 @@ TEST_CASE("mamba2 state update refuses the arms it does not implement") { CHECK_NOTHROW(RunStateUpdate(raw_null, S, in, &nulls, Nb, H, P, G, N, cfg)); } } + +// ═════════════════════════════════════════════════════════════════════════════ +// (6) THE CUDA ARM — .agents/specs/mamba2-ssd.md W2, issue #496. +// The declared equivalence contract is stated in full at the head of the CUDA +// section of tests/vt/test_ops_mamba2_ssd.cpp and in +// src/vt/cuda/cuda_mamba2_ssd.cuh: f32 accumulation throughout, no tile +// downcasts, identical memory format, and the same per-element accumulation +// order — leaving TWO admitted sources of divergence, the libm difference and +// nvcc's default `--fmad=true` contraction of `y += sn * cv`, both of which +// `DerivedRtol` bounds without a tuned number. +// +// The decode step's evidence is mostly EXACT rather than approximate, and +// deliberately so: which cache slot was written, which was left alone, and what a +// NULL row produced are byte facts, not tolerances. +// ═════════════════════════════════════════════════════════════════════════════ +#ifdef VLLM_CPP_CUDA + +#include +#include + +namespace { + +using vt::Backend; + +Backend* MaybeCuda() { + try { + return &vt::GetBackend(DeviceType::kCUDA); + } catch (const std::exception&) { + return nullptr; + } +} + +// A GREEN TEST DOES NOT PROVE THE DEVICE RAN IT. GB10 is +// `integrated && pageable_memory_access`, so `Backend::UnifiedMemory()` is TRUE +// and `ReferenceTierEligible(kCUDA)` with it: absent a native kernel, `GetOp` +// does not throw — it installs the CPU HOST kernel as a `kReferenceProviderName` +// provider and runs THAT over the device pointers (op_provider.h, "portable +// reference tier"), so every assertion below would pass while nothing ran on the +// GPU. Every CUDA case therefore asserts the SELECTED provider is native. These +// are EAGER dispatches, so the counters are populated +// ([[graph-replay-does-no-host-dispatch-counters-read-zero]]). +void RequireNativeCudaProvider(vt::OpId op, const std::string& what) { + const vt::OpProviderStats st = vt::GetOpProviderStats(op, DeviceType::kCUDA); + INFO(what << ": selected CUDA provider = " + << (st.last_selected != nullptr ? st.last_selected : "") + << "; process-wide reference-tier hits = " << vt::GetReferenceTierHits()); + REQUIRE(st.last_selected != nullptr); + CHECK(std::string(st.last_selected) != std::string(vt::kReferenceProviderName)); +} + +// `5*(K + 2)*u` — the bound derived at the head of the CUDA section of +// tests/vt/test_ops_mamba2_ssd.cpp: 2.5 ulp of glibc-vs-CUDA libm disagreement +// per decay factor through a product of at most K, plus the standard (K-1)*u +// forward error of a length-K f32 summation, plus K*u for the K product +// roundings the host arm's `-ffp-contract=off` keeps and the device arm's +// nvcc-`fmad` `fma` does not (`y += sn * cv`, cuda_mamba2_ssd.cuh). +constexpr double kUnitRoundoff = 5.9604644775390625e-08; // 2^-24 +double DerivedRtol(int64_t K) { return 5.0 * static_cast(K + 2) * kUnitRoundoff; } + +void ExpectDeviceMatchesHost(const std::string& what, const std::vector& dev, + const std::vector& host, int64_t K) { + REQUIRE(dev.size() == host.size()); + REQUIRE(!dev.empty()); + double scale = 0.0; + for (float v : host) scale = std::max(scale, std::abs(static_cast(v))); + const double rtol = DerivedRtol(K); + const double atol = rtol * scale; + size_t bit_differing = 0, worst_i = 0; + double worst_ratio = -1.0, worst_diff = 0.0; + for (size_t i = 0; i < dev.size(); ++i) { + if (dev[i] != host[i]) ++bit_differing; + const double d = std::abs(static_cast(dev[i]) - static_cast(host[i])); + const double budget = atol + rtol * std::abs(static_cast(host[i])); + const double ratio = budget > 0.0 ? d / budget : (d > 0.0 ? 1e30 : 0.0); + if (!std::isfinite(static_cast(dev[i])) || ratio > worst_ratio) { + worst_ratio = ratio; + worst_i = i; + worst_diff = d; + if (!std::isfinite(static_cast(dev[i]))) break; + } + } + // MESSAGE, not INFO: doctest prints an INFO context only when an assertion in + // its scope FAILS, so the used slack has to be logged unconditionally for the + // derived bar to be auditable on the green run that matters. + MESSAGE(what << ": K=" << K << " rtol=" << rtol << " scale=" << scale << "; " + << bit_differing << " of " << dev.size() + << " elements differ in any bit; worst element [" << worst_i + << "] dev=" << dev[worst_i] << " host=" << host[worst_i] + << " |diff|=" << worst_diff << " used " << (worst_ratio * 100.0) + << "% of its derived budget"); + CHECK(std::isfinite(static_cast(dev[worst_i]))); + CHECK(worst_ratio <= 1.0); +} + +Tensor MakeTDev(void* data, DType dt, Device dev, const std::vector& shape) { + Tensor t; + t.data = data; + t.dtype = dt; + t.device = dev; + t.rank = static_cast(shape.size()); + int64_t stride = 1; + for (int i = t.rank - 1; i >= 0; --i) { + t.shape[i] = shape[static_cast(i)]; + t.stride[i] = stride; + stride *= shape[static_cast(i)]; + } + return t; +} + +class DBuf { + public: + DBuf(Backend& b, Queue& q, const void* host, size_t bytes) : b_(&b), bytes_(bytes) { + p_ = b.Alloc(bytes == 0 ? 1 : bytes); + if (host != nullptr && bytes > 0) b.Copy(q, p_, host, bytes); + } + ~DBuf() { + if (p_ != nullptr) b_->Free(p_); + } + DBuf(const DBuf&) = delete; + DBuf& operator=(const DBuf&) = delete; + void* get() const { return p_; } + void Download(Queue& q, void* dst) const { + if (bytes_ > 0) b_->Copy(q, dst, p_, bytes_); + b_->Synchronize(q); + } + + private: + Backend* b_; + void* p_ = nullptr; + size_t bytes_ = 0; +}; + +// The CUDA twin of RunStateUpdate. `state_raw` is updated IN PLACE, as on host. +std::vector RunStateUpdateCuda(Backend& gpu, std::vector& state_raw, int64_t S, + const StepInputs& in, const std::vector* slots, + int64_t Nb, int64_t H, int64_t P, int64_t G, int64_t N, + const StepCfg& cfg) { + Queue q = gpu.CreateQueue(); + const Device dev{DeviceType::kCUDA, 0}; + const std::vector xb = Pack(in.x, cfg.act_dtype); + const std::vector dtb = Pack(in.dt, cfg.act_dtype); + const std::vector Bb = Pack(in.B, cfg.act_dtype); + const std::vector Cb = Pack(in.C, cfg.act_dtype); + const std::vector zb = Pack(in.z, cfg.act_dtype); + const size_t out_bytes = static_cast(Nb * H * P) * vt::SizeOf(cfg.act_dtype); + + DBuf dstate(gpu, q, state_raw.data(), state_raw.size()); + DBuf dx(gpu, q, xb.data(), xb.size()); + DBuf ddt(gpu, q, dtb.data(), dtb.size()); + DBuf dA(gpu, q, in.A.data(), in.A.size() * sizeof(float)); + DBuf dB(gpu, q, Bb.data(), Bb.size()); + DBuf dC(gpu, q, Cb.data(), Cb.size()); + DBuf dD(gpu, q, in.D.data(), in.D.size() * sizeof(float)); + DBuf dz(gpu, q, zb.data(), zb.size()); + DBuf ddb(gpu, q, in.dt_bias.data(), in.dt_bias.size() * sizeof(float)); + DBuf dout(gpu, q, nullptr, out_bytes); + + Tensor st = MakeTDev(dstate.get(), cfg.state_dtype, dev, {S, H, P, N}); + Tensor xt = MakeTDev(dx.get(), cfg.act_dtype, dev, {Nb, H, P}); + Tensor dtt = MakeTDev(ddt.get(), cfg.act_dtype, dev, {Nb, H}); + Tensor At = MakeTDev(dA.get(), DType::kF32, dev, {H}); + Tensor Bt = MakeTDev(dB.get(), cfg.act_dtype, dev, {Nb, G, N}); + Tensor Ct = MakeTDev(dC.get(), cfg.act_dtype, dev, {Nb, G, N}); + Tensor Dt = MakeTDev(dD.get(), DType::kF32, dev, {H}); + Tensor zt = MakeTDev(dz.get(), cfg.act_dtype, dev, {Nb, H, P}); + Tensor dbt = MakeTDev(ddb.get(), DType::kF32, dev, {H}); + Tensor outt = MakeTDev(dout.get(), cfg.act_dtype, dev, {Nb, H, P}); + + std::vector idx; + std::unique_ptr didx; + Tensor idxt; + if (slots != nullptr) { + idx = *slots; + didx = std::make_unique(gpu, q, idx.data(), idx.size() * sizeof(int32_t)); + idxt = MakeTDev(didx->get(), DType::kI32, dev, {Nb}); + } + + Mamba2Args args; + args.dt_softplus = cfg.dt_softplus; + args.tp_world_size = cfg.tp_world_size; + vt::Mamba2StateUpdate(q, outt, st, xt, dtt, At, Bt, Ct, cfg.use_D ? &Dt : nullptr, + cfg.use_z ? &zt : nullptr, cfg.use_dt_bias ? &dbt : nullptr, + slots != nullptr ? &idxt : nullptr, args); + + std::vector outb(out_bytes); + dout.Download(q, outb.data()); + dstate.Download(q, state_raw.data()); + gpu.Synchronize(q); + gpu.DestroyQueue(q); + return Unpack(outb, static_cast(Nb * H * P), cfg.act_dtype); +} + +} // namespace + +TEST_CASE("mamba2 state update CUDA arm matches the reference recurrence") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + // The driver shapes: nheads 64, headdim 64, dstate 128, ngroups 8 + // (mamba2-ssd.md §1.4), plus the small sweep the host arm runs. + struct Shape { + int64_t Nb, H, P, G, N; + }; + const std::vector shapes{{4, 64, 64, 8, 128}, {3, 16, 64, 1, 16}, {3, 16, 64, 4, 64}}; + for (const Shape& sh : shapes) { + for (bool has_z : {false, true}) { + const StepInputs in = + GenerateStep(sh.Nb, sh.H, sh.P, sh.G, sh.N, 0x51A7Eu + static_cast(sh.N)); + std::mt19937 rng(9u); + std::normal_distribution nd(0.0f, 1.0f); + std::vector state0(static_cast(sh.Nb * sh.H * sh.P * sh.N)); + for (auto& v : state0) v = nd(rng); + const std::vector state0d(state0.begin(), state0.end()); + + StepCfg cfg; + cfg.use_z = has_z; + const RefStep ref = + SelectiveStateUpdateRef(state0d, in.x, in.dt, in.A, in.B, in.C, &in.D, + has_z ? &in.z : nullptr, &in.dt_bias, true, sh.Nb, sh.H, sh.P, + sh.G, sh.N); + INFO("Nb=" << sh.Nb << " H=" << sh.H << " P=" << sh.P << " G=" << sh.G << " N=" << sh.N + << " has_z=" << has_z); + + std::vector raw_dev = Pack(state0, DType::kF32); + const std::vector out_dev = + RunStateUpdateCuda(*gpu, raw_dev, sh.Nb, in, nullptr, sh.Nb, sh.H, sh.P, sh.G, sh.N, + cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2StateUpdate, "plain decode step"); + std::vector raw_host = Pack(state0, DType::kF32); + const std::vector out_host = + RunStateUpdate(raw_host, sh.Nb, in, nullptr, sh.Nb, sh.H, sh.P, sh.G, sh.N, cfg); + + // G1 — the device arm against the INDEPENDENT double reference, at + // upstream's own f32 tolerance (rtol 3e-4 / atol 1e-3, test_mamba_ssm.py:828). + ExpectClose("device out", out_dev, ref.out, 1e-3, 3e-4); + ExpectClose("device state", Unpack(raw_dev, state0.size(), DType::kF32), ref.state, 1e-3, + 3e-4); + // G2 — device vs host. A decode step runs exactly ONE decay factor and one + // length-N summation over dstate, so K is N and the bound is at its + // tightest anywhere in this row. + ExpectDeviceMatchesHost("out device vs host", out_dev, out_host, sh.N); + ExpectDeviceMatchesHost("state device vs host", + Unpack(raw_dev, state0.size(), DType::kF32), + Unpack(raw_host, state0.size(), DType::kF32), sh.N); + } + } +} + +// SCATTERED CACHE SLOTS and the NULL row, on device. This is the case that pins +// `state_indices` being honoured at all: a kernel that wrote through the ROW +// index instead of the SLOT index, or that treated the NULL row as slot 0, passes +// nothing here. The evidence is BYTE-EXACT — no tolerance can hide a slot that +// was written when it should not have been. +TEST_CASE("mamba2 state update CUDA arm honours scattered slots and the NULL row") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + const int64_t H = 8, P = 32, N = 16, G = 2; + const int64_t real = 3, padding = 5, Nb = real + padding; + const int64_t S = 30; + + const StepInputs in = GenerateStep(Nb, H, P, G, N, 0xFACEu); + std::mt19937 rng(11u); + std::normal_distribution nd(0.0f, 1.0f); + std::vector cache(static_cast(S * H * P * N)); + for (auto& v : cache) v = nd(rng); + + const std::vector chosen{17, 2, 25}; + std::vector slots = chosen; + for (int64_t i = 0; i < padding; ++i) slots.push_back(-1); + + std::vector state0d(static_cast(real * H * P * N)); + for (int64_t b = 0; b < real; ++b) { + const size_t src = + static_cast(chosen[static_cast(b)]) * static_cast(H * P * N); + for (int64_t i = 0; i < H * P * N; ++i) + state0d[static_cast(b * H * P * N + i)] = cache[src + static_cast(i)]; + } + StepInputs real_in = in; + real_in.x.resize(static_cast(real * H * P)); + real_in.dt.resize(static_cast(real * H)); + real_in.B.resize(static_cast(real * G * N)); + real_in.C.resize(static_cast(real * G * N)); + real_in.z.resize(static_cast(real * H * P)); + const RefStep ref = + SelectiveStateUpdateRef(state0d, real_in.x, real_in.dt, real_in.A, real_in.B, real_in.C, + &real_in.D, &real_in.z, &real_in.dt_bias, true, real, H, P, G, N); + + StepCfg cfg; + cfg.use_z = true; + std::vector raw = Pack(cache, DType::kF32); + const std::vector before = raw; + const std::vector out = RunStateUpdateCuda(*gpu, raw, S, in, &slots, Nb, H, P, G, N, cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2StateUpdate, "scattered slots"); + const std::vector after = Unpack(raw, cache.size(), DType::kF32); + + // (a) the three selected slots hold the advanced state. + std::vector got_state(static_cast(real * H * P * N)); + for (int64_t b = 0; b < real; ++b) { + const size_t src = + static_cast(chosen[static_cast(b)]) * static_cast(H * P * N); + for (int64_t i = 0; i < H * P * N; ++i) + got_state[static_cast(b * H * P * N + i)] = after[src + static_cast(i)]; + } + ExpectClose("device scattered state", got_state, ref.state, 1e-3, 3e-4); + + // (b) the three real output rows match. + std::vector got_out(out.begin(), out.begin() + static_cast(real * H * P)); + ExpectClose("device scattered out", got_out, ref.out, 1e-3, 3e-4); + + // (c) EVERY slot that was not selected is BYTE-identical — upstream's + // `torch.equal(state_before[unused], state[unused])` (test_mamba_ssm.py:800). + // Slot 0 is among them, so a kernel that mapped the NULL row onto slot 0 + // fails here and nowhere else. + size_t untouched_slots = 0; + for (int64_t s = 0; s < S; ++s) { + if (std::find(chosen.begin(), chosen.end(), static_cast(s)) != chosen.end()) continue; + ++untouched_slots; + const size_t off = static_cast(s) * static_cast(H * P * N) * 4; + CHECK(std::memcmp(before.data() + off, raw.data() + off, + static_cast(H * P * N) * 4) == 0); + } + CHECK(untouched_slots == static_cast(S - real)); + + // (d) the NULL rows write an EXACTLY zeroed output row. + for (int64_t b = real; b < Nb; ++b) + for (int64_t i = 0; i < H * P; ++i) + CHECK(out[static_cast(b * H * P + i)] == 0.0f); +} + +// DECODE == PREFILL, both arms on device. The two go through entirely different +// device code (a single-token step vs the 5-stage chunked pipeline), which is +// what makes the agreement evidence rather than a shared-helper tautology +// ([[gate-comparing-shared-helper-proves-consistency-not-correctness]]). +TEST_CASE("mamba2 state update CUDA arm reproduces the CUDA chunked prefill") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + const int64_t T = 40, H = 4, P = 8, G = 2, N = 16, chunk = 8; + std::mt19937 rng(0x1234u); + std::normal_distribution nd(0.0f, 1.0f); + std::uniform_real_distribution ud(0.0f, 1.0f); + + std::vector A(static_cast(H)); + for (auto& v : A) v = -ud(rng) - 1.0f; + std::vector D(static_cast(H)); + for (auto& v : D) v = nd(rng); + std::vector dt_bias(static_cast(H)); + for (auto& v : dt_bias) v = ud(rng) - 4.0f; + std::vector x(static_cast(T * H * P)); + for (auto& v : x) v = nd(rng); + std::vector dt(static_cast(T * H)); + for (auto& v : dt) v = nd(rng); + std::vector B(static_cast(T * G * N)); + for (auto& v : B) v = nd(rng); + std::vector C(static_cast(T * G * N)); + for (auto& v : C) v = nd(rng); + + // ── prefill arm, on device ── + std::vector y_prefill(static_cast(T * H * P), 0.0f); + std::vector fs(static_cast(H * P * N), 0.0f); + { + Queue q = gpu->CreateQueue(); + const Device dev{DeviceType::kCUDA, 0}; + std::vector cu{0, static_cast(T)}; + std::vector ccs{0}; + std::vector sidx; + for (int64_t pos = 0; pos < T; pos += chunk) { + ccs.push_back(static_cast(std::min(pos + chunk, T))); + sidx.push_back(0); + } + std::vector lci{static_cast(sidx.size()) - 1}; + + DBuf dx(*gpu, q, x.data(), x.size() * sizeof(float)); + DBuf ddt(*gpu, q, dt.data(), dt.size() * sizeof(float)); + DBuf dA(*gpu, q, A.data(), A.size() * sizeof(float)); + DBuf dB(*gpu, q, B.data(), B.size() * sizeof(float)); + DBuf dC(*gpu, q, C.data(), C.size() * sizeof(float)); + DBuf dD(*gpu, q, D.data(), D.size() * sizeof(float)); + DBuf ddb(*gpu, q, dt_bias.data(), dt_bias.size() * sizeof(float)); + DBuf dout(*gpu, q, nullptr, y_prefill.size() * sizeof(float)); + DBuf dfs(*gpu, q, nullptr, fs.size() * sizeof(float)); + DBuf dcu(*gpu, q, cu.data(), cu.size() * sizeof(int32_t)); + DBuf dccs(*gpu, q, ccs.data(), ccs.size() * sizeof(int32_t)); + DBuf dlci(*gpu, q, lci.data(), lci.size() * sizeof(int32_t)); + DBuf dsi(*gpu, q, sidx.data(), sidx.size() * sizeof(int32_t)); + + Tensor xt = MakeTDev(dx.get(), DType::kF32, dev, {T, H, P}); + Tensor dtt = MakeTDev(ddt.get(), DType::kF32, dev, {T, H}); + Tensor At = MakeTDev(dA.get(), DType::kF32, dev, {H}); + Tensor Bt = MakeTDev(dB.get(), DType::kF32, dev, {T, G, N}); + Tensor Ct = MakeTDev(dC.get(), DType::kF32, dev, {T, G, N}); + Tensor Dt = MakeTDev(dD.get(), DType::kF32, dev, {H}); + Tensor dbt = MakeTDev(ddb.get(), DType::kF32, dev, {H}); + Tensor outt = MakeTDev(dout.get(), DType::kF32, dev, {T, H, P}); + Tensor fst = MakeTDev(dfs.get(), DType::kF32, dev, {1, H, P, N}); + Tensor cust = MakeTDev(dcu.get(), DType::kI32, dev, {2}); + Tensor ccst = MakeTDev(dccs.get(), DType::kI32, dev, {static_cast(ccs.size())}); + Tensor lcit = MakeTDev(dlci.get(), DType::kI32, dev, {1}); + Tensor sit = MakeTDev(dsi.get(), DType::kI32, dev, {static_cast(sidx.size())}); + Mamba2Args args; + args.chunk_size = chunk; + args.dt_softplus = true; + vt::Mamba2ChunkScan(q, outt, fst, xt, dtt, At, Bt, Ct, &Dt, nullptr, &dbt, nullptr, cust, + ccst, lcit, sit, args); + dout.Download(q, y_prefill.data()); + dfs.Download(q, fs.data()); + gpu->Synchronize(q); + gpu->DestroyQueue(q); + } + RequireNativeCudaProvider(vt::OpId::kMamba2ChunkScan, "decode-vs-prefill: prefill arm"); + + // ── decode arm: T single-token device steps on one cache slot ── + std::vector raw(static_cast(H * P * N) * 4, 0); + std::vector y_decode(static_cast(T * H * P), 0.0f); + for (int64_t t = 0; t < T; ++t) { + StepInputs step; + step.A = A; + step.D = D; + step.dt_bias = dt_bias; + step.x.assign(x.begin() + static_cast(t * H * P), + x.begin() + static_cast((t + 1) * H * P)); + step.dt.assign(dt.begin() + static_cast(t * H), + dt.begin() + static_cast((t + 1) * H)); + step.B.assign(B.begin() + static_cast(t * G * N), + B.begin() + static_cast((t + 1) * G * N)); + step.C.assign(C.begin() + static_cast(t * G * N), + C.begin() + static_cast((t + 1) * G * N)); + step.z.assign(static_cast(H * P), 0.0f); + StepCfg cfg; + const std::vector o = + RunStateUpdateCuda(*gpu, raw, 1, step, nullptr, 1, H, P, G, N, cfg); + std::copy(o.begin(), o.end(), y_decode.begin() + static_cast(t * H * P)); + } + + RequireNativeCudaProvider(vt::OpId::kMamba2StateUpdate, "decode-vs-prefill: decode arm"); + ExpectCloseF("device decode vs device chunked prefill", y_decode, y_prefill, 5e-3, 5e-3); + ExpectCloseF("device final state", Unpack(raw, static_cast(H * P * N), DType::kF32), fs, + 5e-3, 5e-3); +} + +// The SSM cache dtype is its own knob (mamba_utils.py:73-81), and `out` does not +// depend on it at all: the readout uses the f32 state held in registers, not the +// value re-read from the cache (mamba_ssm.py:433,451). On device that is the +// EXACT, tolerance-free statement it is on host. +TEST_CASE("mamba2 state update CUDA arm reads out the f32 state, not the rounded cache") { + Backend* gpu = MaybeCuda(); + if (gpu == nullptr) { + MESSAGE("SKIP: no CUDA backend registered (CPU-only build/box)"); + return; + } + const int64_t Nb = 2, H = 4, P = 8, G = 2, N = 16; + const StepInputs in = GenerateStep(Nb, H, P, G, N, 0xDEC0DEu); + std::mt19937 rng(0x99u); + std::normal_distribution nd(0.0f, 1.0f); + std::vector state0(static_cast(Nb * H * P * N)); + for (auto& v : state0) v = vt::BF16ToF32(vt::F32ToBF16(nd(rng))); + + for (bool has_z : {false, true}) { + StepCfg f32cfg; + f32cfg.use_z = has_z; + StepCfg bf16cfg = f32cfg; + bf16cfg.state_dtype = DType::kBF16; + + std::vector raw_f32 = Pack(state0, DType::kF32); + const std::vector out_f32 = + RunStateUpdateCuda(*gpu, raw_f32, Nb, in, nullptr, Nb, H, P, G, N, f32cfg); + std::vector raw_bf16 = Pack(state0, DType::kBF16); + const std::vector out_bf16 = + RunStateUpdateCuda(*gpu, raw_bf16, Nb, in, nullptr, Nb, H, P, G, N, bf16cfg); + RequireNativeCudaProvider(vt::OpId::kMamba2StateUpdate, "cache-dtype independence"); + + REQUIRE(out_f32.size() == out_bf16.size()); + size_t differing = 0; + for (size_t i = 0; i < out_f32.size(); ++i) + if (out_f32[i] != out_bf16[i]) ++differing; + INFO("has_z=" << has_z << ": " << differing << " of " << out_f32.size() + << " device outputs move with the CACHE dtype"); + CHECK(differing == 0); + + // The cache rounding really is live, so the check above is not comparing two + // identical computations. + const std::vector after_f32 = Unpack(raw_f32, state0.size(), DType::kF32); + const std::vector after_bf16 = Unpack(raw_bf16, state0.size(), DType::kBF16); + for (float v : after_bf16) CHECK(vt::BF16ToF32(vt::F32ToBF16(v)) == v); + size_t rounded = 0; + for (size_t i = 0; i < after_f32.size(); ++i) + if (after_f32[i] != after_bf16[i]) ++rounded; + INFO("stored states the bf16 cache rounded: " << rounded); + REQUIRE(rounded > 0); + } +} + +#endif // VLLM_CPP_CUDA From 4d0c399e10f46c6feb02264c2fa3b06140210c00 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:55:04 +0200 Subject: [PATCH 12/55] =?UTF-8?q?W2=20(MODEL-NEMOTRON-H):=20the=20non-gate?= =?UTF-8?q?d=20relu=C2=B2=20MoE=20expert=20=E2=80=94=20one=20GEMM,=20not?= =?UTF-8?q?=20a=20merged=20pair=20(#517)=20(#684)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL W2 of MODEL-TEXT-nemotron-h (#517): the non-gated relu^2 MoE expert. NemotronH's expert has no gate half -- ckpt_names=("up_proj","down_proj",""), where the empty third entry IS the absent gate. So this is not a new merged pair. MergedGemmGroup describes N GEMMs sharing an operand with a fused epilogue; with N = 1 there is nothing to merge and an arity-1 descriptor would name a fusion that does not exist. It lands as the existing grouped projection plus the activation we lacked, with the reasoning recorded next to the seam it excludes, and a fresh review confirmed that is a correct reading rather than a parallel path with a good story. The finding that justifies the row: folding the routed scale into the per-expert weight SURVIVED green in the first review, yet differs from upstream bitwise on 13276 of 20000 rows -- and it is the most likely W4 mistake, because Laguna legitimately does exactly that fold, legal there only because Laguna passes no shared expert. Now pinned bitwise at 10 and 4 ULP separation. Two conflicts on the re-merge. include/vt/ops.h had BOTH sides appending an OpId before kCount -- kLtx2 from the LTX-2.5 campaign and kMoeRelu2 here -- resolved by union with main's entry first so no existing id shifts. And op_provider.cpp auto-merged CLEANLY yet did not compile: main added an exhaustive OpName switch with no default, documented as failing the build when an OpId is appended without being named. That is the gate working exactly as designed; the alternative is a new op with no canonical spelling that nothing would have caught. cuda_moe.cu compiled against the merged ops.h with real nvcc on the gate host, BUILD_EXIT=0 WARNINGS=0 ENOSPC=0, closing the review's F1 -- whose original justification, that neither file had changed since the last GPU compile, was factually false: ops.h moved +180/-1 across six merges. Post-merge: 0 diagnostics under -Werror, relu2 12/81 and router 14/941 both SUCCESS, ten mutations caught. Squash-merged deliberately: the branch carries an inherited trailer-less merge commit that a local --no-ff would put on main. Row stays INVENTORIED. No lifecycle move, no measurement claimed. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/specs/nemotron-h-model.md | 217 +++++++- include/vt/merged_gemm.h | 15 + include/vt/ops.h | 51 +- src/vt/cpu/cpu_ops.cpp | 37 +- src/vt/cuda/cuda_moe.cu | 101 +++- src/vt/op_provider.cpp | 2 + src/vt/ops.cpp | 15 +- tests/CMakeLists.txt | 1 + tests/vt/test_ops_moe_nongated_relu2.cpp | 606 +++++++++++++++++++++++ 9 files changed, 1017 insertions(+), 28 deletions(-) create mode 100644 tests/vt/test_ops_moe_nongated_relu2.cpp diff --git a/.agents/specs/nemotron-h-model.md b/.agents/specs/nemotron-h-model.md index 515abdf7f..9c1a85f67 100644 --- a/.agents/specs/nemotron-h-model.md +++ b/.agents/specs/nemotron-h-model.md @@ -95,7 +95,7 @@ memory format against the oracle explicitly. | MoE | `nemotron_h.py:126-256` (`NemotronHMoE`), decoder layer `:317` | | non-gated activation | `activation_without_mul(config.mlp_hidden_act)` -> `ReLUSquaredActivation` (`layers/activation.py`) | | expert ckpt naming | `ckpt_names=("up_proj", "down_proj", "")` (`nemotron_h.py:220`) | -| routed scale applied to OUTPUT | `apply_routed_scale_to_output=True` (`nemotron_h.py:246`) | +| routed scale applied to OUTPUT | `apply_routed_scale_to_output=True` (`nemotron_h.py:234`), factor `:233` | | router dtype | `GateLinear(..., out_dtype=torch.float32, force_fp32_compute=True)` (`nemotron_h.py:150-156`) | | state shape / dtype | `mamba_utils.py:174-199`, `:73-81` | | MTP | `models/nemotron_h_mtp.py::NemotronHMTP` (`registry.py:638`) | @@ -179,7 +179,7 @@ cannot. | W | Content | Gate | Depends on | |---|---|---|---| | **W1** | ModelOpt `MIXED_PRECISION` resolver: parse `quantization_config`, resolve per-module `quant_algo` (direct then shard-prefix), expose it to weight loading. Refuse an unknown algo by name | unit tests on the REAL `config.json` (committed as a fixture, weights not needed): every one of the 5981 entries resolves, the `ignore` list resolves to unquantized, an unknown algo refuses | — | -| **W2** | Non-gated `relu²` grouped MoE through `MlpGateUpMethodBase` / `vt::MergedGemmGroup`; bf16 arm then NVFP4 W4A16 g16 | byte/tolerance tests vs a host reference; `relu²` mutation caught; routed scale applied to the OUTPUT, not the logits | — | +| **W2** | Non-gated `relu²` grouped MoE; bf16 arm then NVFP4 W4A16 g16. **As built it is NOT a merged pair** — the planned `MlpGateUpMethodBase` / `vt::MergedGemmGroup` routing was refuted during implementation and the arm is the EXISTING grouped GEMM plus a new `vt::MoeRelu2`; §6a is the authority on the seam | byte/tolerance tests vs a host reference; `relu²` mutation caught; routed scale applied to the OUTPUT, not the logits | — | | **W3** | `nemotron_h_weights.cpp` + `_registry.cpp`: `layers_block_type` dispatch, `backbone.` prefix, het-KV group construction (1 Mamba group + 1 full-attn group over 6 layers), enumeration gate vs the released index | enumeration: every tensor in `model.safetensors.index.json` is claimed or explicitly refused; KV spec shapes match `mamba2_state_shape` | #496 W1 | | **W4** | `nemotron_h.cpp` forward: hybrid layer loop, Mamba2 mixer wiring, 6 attention layers, MoE layers | CPU forward runs; per-layer activations vs a dumped oracle reference | #496 W1, W2, W3 | | **W5** | MTP head (`mtp.layers.0`, `eh_proj`/`enorm`/`hnorm`) on the existing spec-decode seam | draft acceptance non-zero; spec-off and spec-on token-identical | W4 | @@ -879,15 +879,214 @@ Read `Status:`. non-attention layers and may not move tokens on short prompts. Gate with a long-prompt arm, not only a 6-token one. -## 7. Now +## 6a. W2 note — the non-gated `relu²` expert, as built + +**Seam verdict: the non-gated expert is NOT a merged pair, and does not get a +`MergedGemmGroup` descriptor.** `MergedGemmGroup` describes N GEMMs *sharing +operand A* collapsed into one launch (`merged_gemm.h:1-22`). NemotronH's expert +has exactly one projection — `ckpt_names=("up_proj", "down_proj", "")` +(`nemotron_h.py:220`, the empty third entry being the absent gate) — so with +N == 1 there is nothing to merge and no launch to save; an arity-1 descriptor +would name a fusion that does not exist. `MlpGateUpMethodBase` +(`linear.h:82-86`) is likewise a *merged `[2I,H]` gate_up* seam and has no pair +to hold either. + +The arm is therefore the **existing** grouped projection plus the activation we +did not have — exactly the shape the gated bf16 archs had before their pair was +folded (`kMoeGroupedGemmBf16` + `kMoeSiluMul`): + +``` +up : kMoeGroupedGemmBf16 (bf16) | kMoeGroupedGemmNvfp4Marlin (W4A16 g16) +act : kMoeRelu2 <- NEW, the only new kernel +down : kMoeGroupedGemmBf16 (bf16) | kMoeGroupedGemmNvfp4Marlin (W4A16 g16) +comb : kMoeCombine(..., routed_scale) <- routed scale on the OUTPUT +``` -**State at this commit:** spec committed, implementation **not started**. The -row stays `INVENTORIED`; this commit changes no lifecycle state. The checkpoint -is staged on the NAS and the oracle smoke run is queued behind the GPU lock. +No parallel MoE path was added. The reasoning is recorded next to the seam it +excludes (`merged_gemm.h`, the note after the bf16-sibling block). + +**`vt::MoeRelu2` (`OpId::kMoeRelu2`, CPU + CUDA).** Mirrors +`ReLUSquaredActivation` (`layers/activation.py:609-628`) as the fused-MoE path +reaches it: `activation_without_mul("relu2")` → `MoEActivation.RELU2_NO_MUL` +(`layers/fused_moe/activation.py:34`; `activation_without_mul` is `:98`, the +enumerator at `:33` is `GELU_TANH_NO_MUL`) → `apply_moe_activation`'s (`:184`) +`F.relu(input, inplace=True); torch.square(input, out=output)`. The **dtype +order is the mirrored part**: upstream's kernel +(`csrc/libtorch_stable/activation_kernels.cu:673-678`) widens to f32, clamps at +zero in f32, squares in f32 and rounds ONCE on the store. No new f32 buffer is +introduced — the op reads and writes the caller's dtype and only its arithmetic +is f32, which is what `LoadF32`/`StoreF32` already are elsewhere in `vt`. + +**`routed_scaling_factor` is applied to the OUTPUT** +(`apply_routed_scale_to_output=True`, `nemotron_h.py:234`). `vt::MoeCombine` +gained a trailing `routed_scale` (default `1.0f`, so every landed caller is +byte-identical) which multiplies the routed sum *before* the shared term is +added — literally `moe_runner.py:390-407` (`:402-406` `fused_output *= routed_scaling_factor`, +`shared_output` untouched) followed by `:722-725` (`shared_output + fused_output`). +Upstream forces the ROUTER's factor to `1.0` in exactly this case +(`layer.py:291-300`), so `MoeRouterTopKArgs::routed_scaling_factor` stays 1.0 on +this path. Note this is the *opposite* polarity from Laguna, which folds the same +factor into the router weights by linearity (`laguna_ops.h:48`); NemotronH takes +the literal upstream form. + +**`group_size=16` NVFP4 — SUPPORTED, risk closed by source.** `MoeMarlinArgs` +already defaults to `group_size = 16` with `mxfp4 = false` (`ops.h`), and +`cuda_moe_marlin.cu:7,115-129` documents and consumes exactly that +(`group_blocks=1`, `s_type = kFE4M3fn`, `num_groups = size_k / group_size`); 32 +is reachable only via the MXFP4 branch. It is the configuration the landed +NVFP4 MoE archs (Laguna, Qwen3.5) already run. A unit test pins the default so a +later widening cannot silently re-point these experts. + +**CUDA arms — what was actually run, and by whom.** The implementer did NOT +compile them: their worktree had no `nvcc`, so at `e2d68404` the CUDA arms were +*written and reviewed*, never built, and the earlier wording here +("compiled-and-reviewed") overstated it. They have since been compiled and +GPU-verified **by the fresh reviewer**, on `dgx.casa` (GB10, nvcc 13.0.88), from +a `git archive` of `e2d68404`: + +- Release `-DVLLM_CPP_CUDA=ON -DVLLM_CPP_CUDA_ARCHITECTURES=121a + -DVLLM_CPP_CUTLASS_DIR=$HOME/cutlass-4.5.0 -DVLLM_CPP_TRITON=ON` exited 0 with + **671/671 targets and zero warnings**; `cuda_moe.cu.o` compiled under + `-Werror=all-warnings`. +- A reviewer-authored GPU parity test proved `MoeRelu2` CUDA == CPU + **bit-for-bit** over 4097 elements in all four dtype arms; that CUDA + `routed_scale` scales the routed sum only; and that the `1.0f` default is + byte-identical to the landed 4-arg call across all 8 dtype combinations. +- Branch tests on the GPU box: `test_ops_moe_nongated_relu2` 10/10, + `test_ops_moe` 9/9 with 33451 assertions. + +**That GPU build is NOT a proof about this head, and the merge messages that +implied otherwise were wrong (corrected 2026-08-13).** Four of this branch's +land-prep merge commits carry a sentence of the form "`src/vt/cuda/cuda_moe.cu` +and `vt/ops.h` are untouched by this merge, so the CUDA arm the fresh reviewer +compiled and GPU-verified on GB10 at `e2d68404` is unchanged." Each half is +individually true — `git diff --shortstat ^1 -- include/vt/ops.h` +is empty for `57e4301489`, `40908db153`, `6ac8eed85e`, `721f44d38d` and +`f6a7f87090` — but the CONCLUSION does not follow, because the compile inputs +moved in commits those merges do not cover. Measured on this branch: + +| Since `e2d68404` (the GPU-verified tree) | Delta | +|---|---| +| `include/vt/ops.h` | **+180 / −1** (`git diff --numstat e2d68404 HEAD`) | +| `src/vt/cuda/cuda_moe.cu` | **+7 / −1** | + +The header movement is `d3cd442e6`, the merge that brought Mamba2 SSD W1 +(`fe81bd3b5`, #496) — a merge that DID touch `include/vt/ops.h`, by +179 lines +against its first parent (`git show --stat d3cd442e6`) — and the `.cu` movement +is this row's own `dd7a6477d` repair. So `cuda_moe.cu` has been compiled at +`e2d68404` and nowhere since, and no local GPU host is reachable to redo it +(`dgx.casa` is down). The standing evidence for the CUDA arm at THIS head is +therefore the PR's `cuda-fat-build` CI job, which compiles `cuda_moe.cu` +against the merged `include/vt/ops.h` for 10 architectures under +`-Werror=all-warnings`; the GB10 run remains the evidence for the arm's +RUNTIME behaviour at `e2d68404`, which the eight-line `.cu` delta since is a +comment change plus the `routed_scale` parameter the reviewer's own GPU parity +test exercised. + +**Scope of the "bit-for-bit" claim.** The reviewer's GPU parity result is a +MEASUREMENT on GB10, not a guarantee the build flags provide: `-ffp-contract=off` +is pinned for CXX, HIP and OBJCXX (`CMakeLists.txt:55`, `:393`, `:467`) but +nothing passes nvcc `--fmad=false`, and `CMakeLists.txt:41-56` carves CUDA out +deliberately ("GPU parity tests compare GPU-vs-GPU"). `MoeRelu2` is safe by +construction — a compare-select and one multiply, with no multiply-add to +contract — and the `routed_scale` step is one standalone multiply on the +finished accumulator, which is why §6a's evidence is stated for exactly those +two. `MoeCombine`'s `acc += w * Load(...)` reduction is the contractable one +and is NOT covered; the comment in `cuda_moe.cu` claiming it was has been +narrowed to what holds. The flag gap itself is repo-wide and tracked as **#591** +(found independently in `cuda_mamba2_ssd.cuh`); it is not repaired here. + +**Still OWED** (no GPU in the implementer/repair worktrees, and not covered by +the above): `kMoeGroupedGemmNvfp4Marlin` exercised on the real NemotronH g16 +tensors, and the end-to-end NemotronH MoE block on GB10. Both remain owed to W6 +or an earlier GPU-host spot check. The `group_size` unit test pins the default +only — it is not a run of the Marlin arm. + +**Evidence.** `tests/vt/test_ops_moe_nongated_relu2.cpp` (**12 cases / 81 +assertions**): the activation against hand-computed exact values, the +`relu`/`silu` mis-ports, a bf16-in/f32-out arm that catches narrowing the square, +a bf16-out raw-bit arm, the shape/dtype/**device** contract refusals, the routed +scale on the routed sum only, the routed scale on the **assembled sum rather than +each router weight** (bitwise), the f16-out refusal that makes upstream's fp16 +arm unreachable, the 1.0 default being byte-identical to the landed call, and the +whole expert `up → relu² → down → scaled combine` against an +independently-written scalar reference. + +Mutations executed and caught (Release, `-ffp-contract=off`; every one restored +and md5-verified afterwards): + +| # | Mutation | Target | Result | +|---|---|---|---| +| M1 | `relu` (square dropped) | `test_ops_moe_nongated_relu2` | RED 5 cases / 27 assertions | +| M2 | `silu` (the gated family's activation) | same | RED 5 / 35 | +| M3 | square narrowed through bf16 | same | RED 2 / 20 | +| M4 | `routed_scale` dropped | same | RED 3 / 32 | +| M5 | `routed_scale` applied to routed **+ shared** | same | RED 2 / 29 | +| M6 | `routed_scale` **folded into each router weight** | same | RED 1 / 4 | +| M7 | routed scale folded into the router **logits** | `test_ops_moe_router_grouped` | RED 3 / **525** (was recorded 498 — see below) | +| M8 | NVFP4 `group_size` default 16 → 32 | `test_ops_moe_nongated_relu2` | RED 1 / 1 | +| M9 | `MoeRelu2` device `VT_CHECK` dropped | same | RED 1 / 2 | +| M10 | `IsOutFloat` widened to admit `kF16` | same | RED 1 / 2 | + +M6 is the one this repair added. At `e2d68404` it **survived green** (10/10 +cases, 71/71 assertions): the landed cases all compared with a tolerance, and the +fold is exact-arithmetic-equal, so nothing could see it. It is also the most +likely W4 mistake, because Laguna performs exactly that fold +(`laguna_ops.h:48`) — legally, since Laguna passes no `shared`. The new case +pins it bitwise on decimal-grid data whose f32 products carry full mantissas +(rows separate by 10 and 4 ULP), with a `REQUIRE` that the data separates the two +forms so the green cannot be vacuous. M7 does NOT red the NemotronH file by +design — this path forces the router factor to 1.0 (`layer.py:291-300`), so the +router's own suite is where that defect is visible. + +**M7's assertion count was recorded wrong (repaired 2026-08-13).** The table and +the `dd7a6477d` commit message both said `RED 3 / 498`. Three independent +re-measurements — the fresh review's, the operator's, and this repair's — all +read **3 cases / 525 assertions** failing, out of the unchanged 14 / 941 +baseline. The CASE count was right; only the assertion count was wrong, and 498 +is reproducible by nobody. `tests/vt/test_ops_moe_router_grouped.cpp` and the +grouped-router kernel it exercises have not moved since `e2d68404` +(`git log --oneline e2d68404..HEAD -- tests/vt/test_ops_moe_router_grouped.cpp` +is empty), so the count is stable and the original entry was a transcription +slip, not drift. Re-measured here by folding +`args.routed_scaling_factor` into the logits fed to sigmoid/softmax in +`MoeRouterGroupedTopKKernel` (`cpu_ops.cpp:2325-2339`) and disabling the +post-renormalize weight scale (`:2430-2434`); Release, `-ffp-contract=off`; +tree restored and md5-verified (`cd409b9465c00834be373cf3ecfb4c1d`), rebuilt, +back to 14/941 SUCCESS. + +## 7. Now -**Next action:** dispatch fresh implementers for **W1** and **W2** (both -independent of #496) as soon as `row/KERNEL-SSM-MAMBA-SSD-W1` clears review, -so the ops-header churn does not collide. +**State at this commit:** **W1 and W3 have LANDED on `main`; W2 is in +re-review.** The `MIXED_PRECISION` resolver landed at `1bc5ef82c` (#561) and the +W3 scaffold at `c6b240edd` (#576); both are merged into this branch, and their +spec sections (§4's three W1 subsections, §5c/§5d/§5e) are main's, carried here +byte for byte. The Mamba2 SSD kernel work W1 landed earlier at `47960a009` +(#496), so `include/vt/ops.h` carries main's +`kMamba2ChunkScan`/`kMamba2StateUpdate`/`kRmsNormGatedGroup` first and appends +`kMoeRelu2` after them; no existing op id shifted. W2 (the non-gated `relu²` +expert, §6a) was reviewed PASS at `e2d68404`, repaired for that review's six +findings at `dd7a6477d`, and this branch is its land-prep: re-merged onto +`origin/main` and fully re-gated. A second fresh review (PR #586 @ `f6a7f8709`) +returned **PASS** — it established that the repair delta changes zero executable +lines — with four RECORD findings, all repaired on +`row/MODEL-NEMOTRON-H-W2-RECORDS`: two stale `activation.py:33` anchors +(`include/vt/ops.h`, this spec), M7's assertion count (§6a), §4's W2 seam text +contradicting §6a, and an overstated bit-identity comment in `cuda_moe.cu` +(#591). That repair touches comments and this spec only; no executable line +moved. `tests/vt/test_ops_moe_nongated_relu2.cpp:12` already carried the +corrected anchors and needed no change. + +The row stays `INVENTORIED`; this commit changes no lifecycle state, so it owes +no `STATUS`/`BENCHMARKS` write. **Oracle gateability is CLOSED** — §5a records +the pinned oracle loading and running the checkpoint on GB10 with three greedy +goldens committed, so W6 has a denominator whenever it is reached. + +**Next action:** land W2 after a fresh scoped re-review, then dispatch **W4** +(W3 having landed). Three things are carried forward, not resolved here: the two +OWED GPU items in §6a (`kMoeGroupedGemmNvfp4Marlin` on the real g16 tensors, and +the end-to-end NemotronH MoE block on GB10), and the OWED GGUF k-quant arm +tracked as W7 (§5b). ## 8. Stop conditions diff --git a/include/vt/merged_gemm.h b/include/vt/merged_gemm.h index 7e1d64d6c..313310c64 100644 --- a/include/vt/merged_gemm.h +++ b/include/vt/merged_gemm.h @@ -76,6 +76,21 @@ inline constexpr MergedGemmGroup kKeepQuantGateUpSwiGLU = { // op vt::MoeGroupedGemmBf16GateUpSilu / OpId::kMoeGroupedGemmBf16GateUpSilu — the // bf16 twin of kMoeGateUpSwiGLUGrouped, BIT-IDENTICAL to {2x MoeGroupedGemmBf16 + // MoeSiluMul}. Same family, distinct weight-marshaling seam. +// +// NON-GATED experts are NOT in this family at all, and deliberately get no +// descriptor. NemotronH's expert (models/nemotron_h.py:126-256 @ 555967922) has +// NO gate half — `ckpt_names=("up_proj", "down_proj", "")` (:220), the empty +// third entry being the absent gate — so the expert is +// h = up_proj(x); h = relu(h)^2; y = down_proj(h) +// with `activation_without_mul(config.mlp_hidden_act)` (:227). A MergedGemmGroup +// describes N GEMMs SHARING operand A collapsed into one launch; with N == 1 +// there is nothing to merge and no launch to save, so an arity-1 descriptor would +// name a fusion that does not exist. The non-gated arm is therefore realized as +// the EXISTING single grouped GEMM plus the activation — kMoeGroupedGemmBf16 (or +// kMoeGroupedGemmNvfp4Marlin for the W4A16 group-16 arm) followed by +// OpId::kMoeRelu2 — which is exactly the shape the gated bf16 archs had before +// their pair was folded. See vt::MoeRelu2 (ops.h) and +// .agents/specs/nemotron-h-model.md §4 W2. // ── Dispatch ───────────────────────────────────────────────────────────────── // Run a merged-GEMM group. For an arity-2 kSiluMulClamp group over keep-quant diff --git a/include/vt/ops.h b/include/vt/ops.h index 4d3f533ba..bf5ef66e4 100644 --- a/include/vt/ops.h +++ b/include/vt/ops.h @@ -397,6 +397,13 @@ enum class OpId : uint8_t { // Additive: only Ltx2DitForwardDevice dispatches it. Appended before kCount // so no existing op's id shifts. kLtx2, + // The NON-GATED MoE activation: out = relu(x)^2, the whole epilogue of a + // NemotronH expert (models/nemotron_h.py:227 activation_without_mul("relu2") + // -> MoEActivation.RELU2_NO_MUL). Sibling of kMoeSiluMul with ONE input + // instead of two, because a non-gated expert has no gate half to multiply by + // (nemotron_h.py:220 ckpt_names=("up_proj","down_proj","")). See vt::MoeRelu2. + // Appended before kCount so no existing op's id shifts. + kMoeRelu2, kCount }; @@ -941,6 +948,8 @@ using MarlinDenseGemmFn = const Tensor& /*b_scales*/, const Tensor& /*global_scale*/, Tensor& /*workspace*/, const MarlinDenseArgs&); using MoeSiluMulFn = void (*)(Queue&, Tensor&, const Tensor&, const Tensor&); +// kMoeRelu2: out[i] = relu(x[i])^2 — the NON-GATED MoE activation (one input). +using MoeRelu2Fn = void (*)(Queue&, Tensor&, const Tensor&); // --- Qwen3.6 elementwise "glue" ops (M0.9 forward). These replace host-side // loops so the decode step can run entirely on-device (CUDA-graph capture). // All math in f32; dims are inferred from the tensor shapes (no args structs). @@ -1064,8 +1073,10 @@ using IndexSelectFn = void (*)(Queue&, Tensor&, const Tensor&, const Tensor&); using IndexCopyFn = void (*)(Queue&, Tensor&, const Tensor&, const Tensor&); using MoeRouterTopKFn = void (*)(Queue&, Tensor&, Tensor&, const Tensor&, const MoeRouterTopKArgs&, const Tensor*); +// The trailing float is `routed_scale` — the routed_scaling_factor applied to +// the ROUTED sum before the shared term is added (see vt::MoeCombine). using MoeCombineFn = - void (*)(Queue&, Tensor&, const Tensor&, const Tensor&, const Tensor*); + void (*)(Queue&, Tensor&, const Tensor&, const Tensor&, const Tensor*, float); using MoeCombineGateFn = void (*)(Queue&, Tensor&, const Tensor&, const Tensor&, const Tensor&, const Tensor&); using AttentionFn = void (*)(Queue&, Tensor&, const Tensor&, const Tensor&, const Tensor&, @@ -1684,6 +1695,30 @@ void MarlinDenseGemm(Queue& q, Tensor& c, const Tensor& a, const Tensor& b_q_wei // projections so no concat/copy is needed. CPU + CUDA. void MoeSiluMul(Queue& q, Tensor& out, const Tensor& gate, const Tensor& up); +// out[R,I] = relu(x[R,I])^2 — the NON-GATED MoE activation, and the whole +// epilogue of a NemotronH expert. Mirror of vLLM's `ReLUSquaredActivation` +// (layers/activation.py:609-628, forward_native = torch.square(F.relu(x))) as +// reached through the fused-MoE path: `activation_without_mul("relu2")` -> +// `MoEActivation.RELU2_NO_MUL` -> `apply_moe_activation`'s +// `F.relu(input, inplace=True); torch.square(input, out=output)` +// (layers/fused_moe/activation.py:34 `RELU2_NO_MUL`, :98 +// `activation_without_mul`, and the :184 RELU2_NO_MUL branch; `:33` is +// GELU_TANH_NO_MUL, the neighbouring enumerator). +// +// Why this is NOT a MergedGemmGroup epilogue: a NON-gated expert has no gate +// half to merge with (nemotron_h.py:220 `ckpt_names=("up_proj","down_proj","")` +// — the empty third entry IS the absent gate). There is exactly ONE projection, +// so the expert is the EXISTING grouped GEMM plus this activation, exactly as +// the gated bf16 archs are kMoeGroupedGemmBf16 + kMoeSiluMul. See +// merged_gemm.h's note on the non-gated family. +// +// DTYPE/ROUNDING ORDER is the mirrored part, not an implementation detail: +// upstream's kernel (csrc/libtorch_stable/activation_kernels.cu:673-678) +// widens to f32, clamps at zero in f32, squares in f32 and rounds ONCE on the +// store — so a bf16 input with an f32 output keeps the FULL f32 square. x f32 +// or bf16, out f32/bf16. CPU + CUDA. +void MoeRelu2(Queue& q, Tensor& out, const Tensor& x); + // out[T,H] = x[T,H] / sqrt(mean(x^2) + eps) * w (or *(1+w) when gemma); // out f32 or bf16 (computed in f32, rounded on store). // With residual != nullptr (f32 OR bf16 [T,H]): residual += x first (new residual @@ -2372,7 +2407,7 @@ void MoeRouterTopK(Queue& q, Tensor& weights, Tensor& indices, const Tensor& log const Tensor* e_score_correction_bias = nullptr); // Weighted scatter-combine of the per-expert outputs (moe-semantics.md §4/§6). -// out[t,:] = sum_j weights[t,j] * expert_out[t,j,:] (f32 accumulation) +// out[t,:] = routed_scale * sum_j weights[t,j] * expert_out[t,j,:] (f32 accum) // + shared[t,:] (when shared != nullptr) // expert_out [T,K,H] any float dtype (the K per-slot expert MLP outputs for // token t), weights [T,K] f32 (router weights, §3), optional shared [T,H] any @@ -2381,8 +2416,18 @@ void MoeRouterTopK(Queue& q, Tensor& weights, Tensor& indices, const Tensor& log // (§6 combine order: shared_output + routed_output). The activation-dtype // rounding of the routed sum before the shared add is carried by the caller // materializing expert_out/shared in the activation dtype. +// +// `routed_scale` is upstream's `apply_routed_scale_to_output=True` arm +// (layers/fused_moe/runner/moe_runner.py:390-407, :402-406 `fused_output *= +// routed_scaling_factor`, then :722-725 `result = shared_output + fused_output`). +// It multiplies the ROUTED sum ONLY — the shared-expert term is added unscaled, +// which is the whole point of the flag and the error a token gate catches late. +// The DEFAULT 1.0f is the `apply_routed_scale_to_output=False` polarity every +// landed caller uses, where the factor is instead folded into the router weights +// by MoeRouterTopKArgs::routed_scaling_factor (layer.py:291-300 forces the +// router's factor to 1.0 exactly when this one is not). void MoeCombine(Queue& q, Tensor& out, const Tensor& expert_out, const Tensor& weights, - const Tensor* shared = nullptr); + const Tensor* shared = nullptr, float routed_scale = 1.0f); // --- Fused MoE combine + shared-expert gate (MoE glue fusion). Equivalent to // SharedExpertGate(shared=bf16(sigmoid(gl)*sd)) followed by MoeCombine(...,shared), diff --git a/src/vt/cpu/cpu_ops.cpp b/src/vt/cpu/cpu_ops.cpp index 7e86a8e7c..5d342c66a 100644 --- a/src/vt/cpu/cpu_ops.cpp +++ b/src/vt/cpu/cpu_ops.cpp @@ -461,6 +461,23 @@ void MoeSiluMulKernel(Queue&, Tensor& out, const Tensor& gate, const Tensor& up) }); } +// The NON-GATED MoE activation: out[i] = relu(x[i])^2, the whole epilogue of a +// NemotronH expert (nemotron_h.py:227 -> MoEActivation.RELU2_NO_MUL). Mirrors +// vLLM's relu_squared_kernel (csrc/libtorch_stable/activation_kernels.cu:673-678) +// EXACTLY in dtype order: widen to f32, clamp at zero in f32, square in f32, and +// round ONCE on the store. LoadF32/StoreF32 are that widen/round pair, so a bf16 +// input with an f32 output keeps the full f32 square (no intermediate narrowing). +void MoeRelu2Kernel(Queue&, Tensor& out, const Tensor& x) { + const int64_t n = out.Numel(); + ForRows(n, [&](int64_t r0, int64_t r1) { + for (int64_t i = r0; i < r1; ++i) { + const float f = LoadF32(x, i); + const float v = f > 0.0f ? f : 0.0f; + StoreF32(out, i, v * v); + } + }); +} + // --- TRUE W4A4 (fp4xfp4) helpers + kernels (notes §7). Self-contained fp8/fp4 // codec (vt does not depend on vllm), bit-matching vllm::F8E4M3ToF32 / // F32ToF8E4M3 / CastToFp4 / kE2M1Lut so the op equals vllm::RunNvfp4Emulation. @@ -2475,8 +2492,23 @@ void MoeRouterTopKKernel(Queue&, Tensor& weights, Tensor& indices, const Tensor& // §4/§6 weighted scatter-combine: out[t,:] = sum_j w[t,j]*expert_out[t,j,:] // (f32 accumulation) + shared[t,:] (optional). Stored at out's dtype. +// `routed_scale` multiplies the ROUTED sum only, BEFORE the shared term is added +// — upstream's apply_routed_scale_to_output arm (moe_runner.py:390-407, :402-406 scales +// `fused_output`, leaves `shared_output` alone, then :722-725 adds them). The +// default 1.0f is the fold-into-router-weights polarity every landed caller uses. +// It scales the ASSEMBLED sum, not each router weight: upstream's +// `fused_output *= factor` (:404) is one multiply on the finished tensor, so the +// scale rounds ONCE after the K-term reduction. Folding it into `weights[j]` is +// equal in exact arithmetic and a different f32 value (it rounds K times inside +// the sum); Laguna is entitled to that fold (`laguna_ops.h:48`, no `shared`), +// this path is not. Pinned bitwise in test_ops_moe_nongated_relu2.cpp. +// NOT MIRRORED, UNREACHABLE: upstream's fp16 arm (:403-406) instead divides +// `shared_output` by the factor to dodge an fp16 overflow. That branch is keyed +// on `fused_output.dtype == torch.float16`; the analogue here is `out`, whose +// dtype `MoeCombine` gates through `IsOutFloat` (ops.cpp:22 — f32/bf16 only, no +// kF16), so no caller can reach it. Pinned by the f16-out refusal test. void MoeCombineKernel(Queue&, Tensor& out, const Tensor& expert_out, const Tensor& weights, - const Tensor* shared) { + const Tensor* shared, float routed_scale) { const int64_t t = out.shape[0], h = out.shape[1], k = weights.shape[1]; ForRows(t, [&](int64_t r0, int64_t r1) { for (int64_t row = r0; row < r1; ++row) { @@ -2485,6 +2517,7 @@ void MoeCombineKernel(Queue&, Tensor& out, const Tensor& expert_out, const Tenso for (int64_t j = 0; j < k; ++j) acc += weights.Ptr()[row * k + j] * LoadF32(expert_out, (row * k + j) * h + col); + if (routed_scale != 1.0f) acc *= routed_scale; if (shared != nullptr) acc += LoadF32(*shared, row * h + col); StoreF32(out, row * h + col, acc); } @@ -3097,6 +3130,8 @@ struct Registrar { reinterpret_cast(static_cast(&SoftCapKernel))); RegisterOp(OpId::kMoeSiluMul, DeviceType::kCPU, reinterpret_cast(static_cast(&MoeSiluMulKernel))); + RegisterOp(OpId::kMoeRelu2, DeviceType::kCPU, + reinterpret_cast(static_cast(&MoeRelu2Kernel))); RegisterOp(OpId::kScaledFp4Quant, DeviceType::kCPU, reinterpret_cast(static_cast(&ScaledFp4QuantKernel))); RegisterOp(OpId::kSiluMulFp4Quant, DeviceType::kCPU, diff --git a/src/vt/cuda/cuda_moe.cu b/src/vt/cuda/cuda_moe.cu index 1e6537418..3d2991356 100644 --- a/src/vt/cuda/cuda_moe.cu +++ b/src/vt/cuda/cuda_moe.cu @@ -469,9 +469,28 @@ void MoeRouterTopKKernelCuda(Queue& q, Tensor& weights, Tensor& indices, const T // Upstream counterpart: layers/fused_moe/ (moe_sum reduction over the topk // weighted w2 outputs) — M2.2 replaces this correctness-grade path. +// `routed_scale` multiplies the ROUTED sum only, BEFORE the shared term is added +// — upstream's apply_routed_scale_to_output arm (layers/fused_moe/runner/ +// moe_runner.py:390-407 (:402-406) scales `fused_output`, leaves `shared_output` alone, +// then :722-725 adds them). Applied in the same f32 accumulator the CPU +// reference (cpu_ops.cpp MoeCombineKernel) uses, in the same order. The scale +// itself is ONE standalone f32 multiply on the finished accumulator, with +// nothing adjacent to contract into, so THIS step is bit-identical to the CPU +// reference. That does not extend to the `acc += w * Load(...)` reduction above +// it: only CXX/HIP/OBJCXX carry -ffp-contract=off (CMakeLists.txt:55, :393, +// :467) and nothing passes nvcc --fmad=false, so device code may contract that +// multiply-add into an FMA where the host may not. See #591, which tracks that +// repo-wide flag gap. Default 1.0f == the landed fold-into-weights arm. +// Like the CPU reference it scales the ASSEMBLED sum, not each router weight +// (:404 `fused_output *= factor` is one multiply on the finished tensor); the +// fold is equal in exact arithmetic and a different f32 value. Upstream's fp16 +// arm (:403-406, divide `shared_output` instead) is unreachable here — `out` is +// gated to f32/bf16 by `IsOutFloat` (ops.cpp:22). See cpu_ops.cpp for the full +// note. template __global__ void MoeCombineKernel(Tout* out, const Teo* expert_out, const float* weights, - const Tsh* shared, int64_t t, int64_t h, int k) { + const Tsh* shared, int64_t t, int64_t h, int k, + float routed_scale) { const int64_t n = t * h; const int64_t step = static_cast(gridDim.x) * blockDim.x; for (int64_t idx = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; idx < n; @@ -481,6 +500,7 @@ __global__ void MoeCombineKernel(Tout* out, const Teo* expert_out, const float* float acc = 0.0f; for (int j = 0; j < k; ++j) acc += weights[row * k + j] * Load(expert_out, (row * k + j) * h + col); + if (routed_scale != 1.0f) acc *= routed_scale; if (shared != nullptr) acc += Load(shared, idx); Store(out, idx, acc); } @@ -488,36 +508,37 @@ __global__ void MoeCombineKernel(Tout* out, const Teo* expert_out, const float* template void LaunchCombine(cudaStream_t s, Tensor& out, const Tensor& expert_out, const Tensor& weights, - const Tensor* shared, int64_t t, int64_t h, int k) { + const Tensor* shared, int64_t t, int64_t h, int k, float routed_scale) { MoeCombineKernel<<>>( out.Ptr(), expert_out.Ptr(), weights.Ptr(), - shared != nullptr ? shared->Ptr() : nullptr, t, h, k); + shared != nullptr ? shared->Ptr() : nullptr, t, h, k, routed_scale); Check(cudaGetLastError(), "moe_combine launch"); } // Dispatch shared dtype (or the no-shared path, where Tsh is unused). template void DispatchShared(cudaStream_t s, Tensor& out, const Tensor& expert_out, const Tensor& weights, - const Tensor* shared, int64_t t, int64_t h, int k) { + const Tensor* shared, int64_t t, int64_t h, int k, float routed_scale) { if (shared == nullptr || shared->dtype == DType::kF32) { - LaunchCombine(s, out, expert_out, weights, shared, t, h, k); + LaunchCombine(s, out, expert_out, weights, shared, t, h, k, routed_scale); } else { - LaunchCombine(s, out, expert_out, weights, shared, t, h, k); + LaunchCombine(s, out, expert_out, weights, shared, t, h, k, + routed_scale); } } template void DispatchOut(cudaStream_t s, Tensor& out, const Tensor& expert_out, const Tensor& weights, - const Tensor* shared, int64_t t, int64_t h, int k) { + const Tensor* shared, int64_t t, int64_t h, int k, float routed_scale) { if (out.dtype == DType::kF32) { - DispatchShared(s, out, expert_out, weights, shared, t, h, k); + DispatchShared(s, out, expert_out, weights, shared, t, h, k, routed_scale); } else { - DispatchShared(s, out, expert_out, weights, shared, t, h, k); + DispatchShared(s, out, expert_out, weights, shared, t, h, k, routed_scale); } } void MoeCombineKernelCuda(Queue& q, Tensor& out, const Tensor& expert_out, const Tensor& weights, - const Tensor* shared) { + const Tensor* shared, float routed_scale) { VT_CHECK(expert_out.dtype == DType::kF32 || expert_out.dtype == DType::kBF16, "cuda moe_combine: unsupported expert_out dtype (f32/bf16 only)"); VT_CHECK(out.dtype == DType::kF32 || out.dtype == DType::kBF16, @@ -528,9 +549,11 @@ void MoeCombineKernelCuda(Queue& q, Tensor& out, const Tensor& expert_out, const if (t == 0 || h == 0) return; cudaStream_t s = AsStream(q); if (expert_out.dtype == DType::kF32) { - DispatchOut(s, out, expert_out, weights, shared, t, h, static_cast(k)); + DispatchOut(s, out, expert_out, weights, shared, t, h, static_cast(k), + routed_scale); } else { - DispatchOut<__nv_bfloat16>(s, out, expert_out, weights, shared, t, h, static_cast(k)); + DispatchOut<__nv_bfloat16>(s, out, expert_out, weights, shared, t, h, static_cast(k), + routed_scale); } } @@ -672,6 +695,58 @@ void MoeSiluMulKernelCuda(Queue& q, Tensor& out, const Tensor& gate, const Tenso } } +// --------------------------------------------------------------------------- +// moe_relu2: out[i] = relu(x[i])^2, the NON-GATED MoE activation (NemotronH's +// expert epilogue — nemotron_h.py:227 activation_without_mul("relu2") -> +// MoEActivation.RELU2_NO_MUL). Sibling of moe_silu_mul with ONE input, because a +// non-gated expert has no gate half. Dtype order is upstream's relu_squared_kernel +// (csrc/libtorch_stable/activation_kernels.cu:673-678) verbatim: widen to f32, +// clamp at zero in f32, square in f32, ONE round on the store — so a bf16 input +// with an f32 output keeps the full f32 square. Byte-identical to the CPU +// reference (cpu_ops.cpp MoeRelu2Kernel): both are exact f32 ops, no expf — +// and unlike the combine reduction above there is no multiply-add here for +// nvcc to contract into an FMA (a compare-select and ONE multiply), so this +// one holds without --fmad=false. See #591 for the flag gap itself. +template +__global__ void MoeRelu2Kernel(Tout* out, const Tx* x, int64_t n) { + const int64_t step = static_cast(gridDim.x) * blockDim.x; + for (int64_t i = static_cast(blockIdx.x) * blockDim.x + threadIdx.x; i < n; i += step) { + const float f = Load(x, i); + const float v = f > 0.0f ? f : 0.0f; + Store(out, i, v * v); + } +} + +template +void LaunchRelu2(cudaStream_t s, Tensor& out, const Tensor& x, int64_t n) { + MoeRelu2Kernel<<>>(out.Ptr(), x.Ptr(), n); + Check(cudaGetLastError(), "moe_relu2 launch"); +} + +template +void Relu2ByOut(cudaStream_t s, Tensor& out, const Tensor& x, int64_t n) { + if (out.dtype == DType::kF32) { + LaunchRelu2(s, out, x, n); + } else { + LaunchRelu2(s, out, x, n); + } +} + +void MoeRelu2KernelCuda(Queue& q, Tensor& out, const Tensor& x) { + VT_CHECK(x.dtype == DType::kF32 || x.dtype == DType::kBF16, + "cuda moe_relu2: unsupported x dtype (f32/bf16 only)"); + VT_CHECK(out.dtype == DType::kF32 || out.dtype == DType::kBF16, + "cuda moe_relu2: unsupported out dtype (f32/bf16 only)"); + const int64_t n = out.Numel(); + if (n == 0) return; + cudaStream_t s = AsStream(q); + if (x.dtype == DType::kF32) { + Relu2ByOut(s, out, x, n); + } else { + Relu2ByOut<__nv_bfloat16>(s, out, x, n); + } +} + // Registers the CUDA MoE kernels during static init (pre-main, like the M0.6 // ops in cuda_ops.cu). Filling the op table is harmless on machines without a // GPU: the kCUDA backend never registers there, so no CUDA queue can dispatch. @@ -685,6 +760,8 @@ struct Registrar { reinterpret_cast(static_cast(&MoeCombineGateKernelCuda))); RegisterOp(OpId::kMoeSiluMul, DeviceType::kCUDA, reinterpret_cast(static_cast(&MoeSiluMulKernelCuda))); + RegisterOp(OpId::kMoeRelu2, DeviceType::kCUDA, + reinterpret_cast(static_cast(&MoeRelu2KernelCuda))); } } registrar; diff --git a/src/vt/op_provider.cpp b/src/vt/op_provider.cpp index 1f59f1be3..9848a4584 100644 --- a/src/vt/op_provider.cpp +++ b/src/vt/op_provider.cpp @@ -354,6 +354,8 @@ const char* OpNameImpl(OpId op) { return "MoeGroupedGemmNvfp4"; case OpId::kMoeSiluMul: return "MoeSiluMul"; + case OpId::kMoeRelu2: + return "MoeRelu2"; case OpId::kCastBf16: return "CastBf16"; case OpId::kCastF32: diff --git a/src/vt/ops.cpp b/src/vt/ops.cpp index 37c02a19c..8a3a9f1ad 100644 --- a/src/vt/ops.cpp +++ b/src/vt/ops.cpp @@ -923,6 +923,15 @@ void MoeSiluMul(Queue& q, Tensor& out, const Tensor& gate, const Tensor& up) { reinterpret_cast(GetOp(OpId::kMoeSiluMul, q.device.type))(q, out, gate, up); } +void MoeRelu2(Queue& q, Tensor& out, const Tensor& x) { + VT_CHECK(x.Numel() == out.Numel(), "moe_relu2: out/x must have the same element count"); + VT_CHECK(IsFloat(x.dtype) && IsOutFloat(out.dtype), "moe_relu2: float x, f32/bf16 out"); + VT_CHECK(out.IsContiguous() && x.IsContiguous(), "moe_relu2: contiguous tensors required"); + VT_CHECK(out.device == q.device && x.device == q.device, + "moe_relu2: device mismatch (out/x/queue)"); + reinterpret_cast(GetOp(OpId::kMoeRelu2, q.device.type))(q, out, x); +} + void RmsNorm(Queue& q, Tensor& out, const Tensor& x, const Tensor& weight, const RmsNormArgs& args, Tensor* residual) { VT_CHECK(x.rank == 2 && out.rank == 2 && weight.rank == 1, "rmsnorm: x/out rank-2, w rank-1"); @@ -2510,7 +2519,7 @@ void MoeRouterTopK(Queue& q, Tensor& weights, Tensor& indices, const Tensor& log } void MoeCombine(Queue& q, Tensor& out, const Tensor& expert_out, const Tensor& weights, - const Tensor* shared) { + const Tensor* shared, float routed_scale) { VT_CHECK(expert_out.rank == 3 && weights.rank == 2 && out.rank == 2, "moe_combine: expert_out [T,K,H], weights [T,K], out [T,H]"); const int64_t t = out.shape[0], h = out.shape[1], k = weights.shape[1]; @@ -2531,8 +2540,8 @@ void MoeCombine(Queue& q, Tensor& out, const Tensor& expert_out, const Tensor& w shared->device == q.device, "moe_combine: shared must be float [T,H] contiguous on the queue device"); } - reinterpret_cast(GetOp(OpId::kMoeCombine, q.device.type))(q, out, expert_out, - weights, shared); + reinterpret_cast(GetOp(OpId::kMoeCombine, q.device.type))( + q, out, expert_out, weights, shared, routed_scale); } void MoeCombineGate(Queue& q, Tensor& out, const Tensor& expert_out, const Tensor& weights, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ddeaa7484..8619242f3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1282,6 +1282,7 @@ vllm_cpp_add_test(test_ops_moe_grouped vt/test_ops_moe_grouped.cpp) vllm_cpp_add_test(test_ops_moe_grouped_bf16 vt/test_ops_moe_grouped_bf16.cpp) vllm_cpp_add_test(test_ops_moe_grouped_bf16_gate_up_silu vt/test_ops_moe_grouped_bf16_gate_up_silu.cpp) +vllm_cpp_add_test(test_ops_moe_nongated_relu2 vt/test_ops_moe_nongated_relu2.cpp) vllm_cpp_add_test(test_ops_rmsnorm vt/test_ops_rmsnorm.cpp) vllm_cpp_add_test(test_ops_fused_chain vt/test_ops_fused_chain.cpp) vllm_cpp_add_test(test_ops_layernorm vt/test_ops_layernorm.cpp) diff --git a/tests/vt/test_ops_moe_nongated_relu2.cpp b/tests/vt/test_ops_moe_nongated_relu2.cpp new file mode 100644 index 000000000..562675cfa --- /dev/null +++ b/tests/vt/test_ops_moe_nongated_relu2.cpp @@ -0,0 +1,606 @@ +// NemotronH's NON-GATED relu^2 MoE expert (row MODEL-TEXT-nemotron-h, W2). +// Spec: .agents/specs/nemotron-h-model.md §4 W2. Issue #517. +// +// Upstream mirror, all @ 5559679229bc961848b121ccdeaa8fa5d79bec98 (vLLM 0.26.0.dev0): +// models/nemotron_h.py:126-256 `NemotronHMoE` +// :220 ckpt_names=("up_proj", "down_proj", "") — the EMPTY third entry is the +// absent gate half. The expert is up_proj -> relu^2 -> down_proj; there +// is no gate_proj tensor anywhere in the checkpoint. +// :227 activation=activation_without_mul(config.mlp_hidden_act) ("relu2" -> +// "relu2_no_mul") +// :234 apply_routed_scale_to_output=True, :233 routed_scaling_factor +// layers/fused_moe/activation.py:98 `activation_without_mul`, :34 +// `MoEActivation.RELU2_NO_MUL`, and :184 `apply_moe_activation`'s +// RELU2_NO_MUL branch: F.relu(input, inplace=True); +// torch.square(input, out=output). +// layers/activation.py:609-628 `ReLUSquaredActivation` +// (forward_native = torch.square(F.relu(x))). +// csrc/libtorch_stable/activation_kernels.cu:672-678 `relu_squared_kernel` — +// the DTYPE/ROUNDING ORDER this file pins: widen to f32, clamp at 0 in f32, +// square in f32, then ONE round back to the store dtype. +// layers/fused_moe/runner/moe_runner.py:390-407 +// `_maybe_apply_routed_scale_to_output` — :402-406, `fused_output *= +// routed_scaling_factor` on the ASSEMBLED tensor with the SHARED output left +// UNSCALED, then :722-725 `result = shared_output + fused_output`. The +// :403-406 fp16 arm (divide `shared_output` instead) is unreachable here; +// see the f16-out refusal case below. +// layers/fused_moe/layer.py:291-300 — with apply_routed_scale_to_output the +// ROUTER's routed_scaling_factor is forced to 1.0 ("so it ends up being a nop"), +// which is why the scale is NOT visible in the router weights here. +// +// CPU-only by construction: the two things W2 adds (vt::MoeRelu2 and the +// routed-output scale on vt::MoeCombine) are both registered on kCPU, and the +// non-gated expert composite below runs through vt::MatmulBT — the same +// per-expert reference loop the CPU/GGUF MoE path already uses. The CUDA arms +// (kMoeGroupedGemmBf16 / kMoeGroupedGemmNvfp4Marlin) are registered for kCUDA +// only and are exercised where a GPU exists. +#include + +#include +#include +#include +#include +#include + +#include "vt/dtype.h" +#include "vt/ops.h" + +namespace { + +using vt::Device; +using vt::DeviceType; +using vt::DType; +using vt::Queue; +using vt::Tensor; + +Device Cpu() { return Device{DeviceType::kCPU, 0}; } +Queue Q() { return Queue{Cpu(), nullptr}; } + +Tensor F32_1(std::vector& v) { + return Tensor::Contiguous(v.data(), DType::kF32, Cpu(), {static_cast(v.size())}); +} +Tensor F32_2(std::vector& v, int64_t a, int64_t b) { + return Tensor::Contiguous(v.data(), DType::kF32, Cpu(), {a, b}); +} +Tensor F32_3(std::vector& v, int64_t a, int64_t b, int64_t c) { + return Tensor::Contiguous(v.data(), DType::kF32, Cpu(), {a, b, c}); +} +Tensor Bf16_1(std::vector& v) { + return Tensor::Contiguous(v.data(), DType::kBF16, Cpu(), {static_cast(v.size())}); +} + +// doctest::Approx carries a 1.19e-5 ABSOLUTE floor (its `scale` defaults to 1.0), +// which silently accepts a dropped term on small values. Every non-exact +// comparison here goes through this explicit relative+absolute comparator. +bool Close(float a, float b, float rel = 1e-6f, float abs_tol = 1e-30f) { + const float d = std::fabs(a - b); + return d <= abs_tol || d <= rel * std::fmax(std::fabs(a), std::fabs(b)); +} + +float SiluRef(float x) { return x / (1.0f + std::exp(-x)); } + +// Raw f32 bit pattern. The routed-scale PLACEMENT test below distinguishes two +// expressions that agree in exact arithmetic and differ only in where they +// round, so it can only be gated bitwise — any tolerance loose enough to be a +// tolerance accepts both. +uint32_t Bits(float v) { + uint32_t u = 0; + std::memcpy(&u, &v, sizeof(u)); + return u; +} + +} // namespace + +// --------------------------------------------------------------------------- +// 1. The activation itself — ReLUSquaredActivation (activation.py:609-628). +// --------------------------------------------------------------------------- + +// torch.square(F.relu(x)) on exactly-representable inputs, so the expected +// values are exact and the check needs no tolerance at all. +TEST_CASE("moe relu2: mirrors torch.square(F.relu(x)) on the f32 arm") { + std::vector x = {-3.0f, -0.5f, -0.0f, 0.0f, 0.5f, 1.0f, 2.0f, 3.0f}; + std::vector out(x.size(), -1.0f); + Tensor xt = F32_1(x); + Tensor ot = F32_1(out); + Queue q = Q(); + vt::MoeRelu2(q, ot, xt); + + const std::vector want = {0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f, 4.0f, 9.0f}; + for (size_t i = 0; i < want.size(); ++i) { + CHECK(out[i] == want[i]); + } + // The negative half is exactly ZERO, not a small negative (a `x*|x|` or + // `x*x*sign(x)` mis-port would keep the sign). + CHECK(std::signbit(out[0]) == false); +} + +// The two mis-ports a token gate would catch late: relu (forgot the square) and +// silu (took the GATED family's activation). Pinned at a single input where all +// three differ by a wide margin. +TEST_CASE("moe relu2: is neither relu nor silu") { + std::vector x = {2.0f, 3.0f, -1.5f}; + std::vector out(x.size(), -1.0f); + Tensor xt = F32_1(x); + Tensor ot = F32_1(out); + Queue q = Q(); + vt::MoeRelu2(q, ot, xt); + + CHECK(out[0] == 4.0f); // relu(2)^2 + CHECK(out[0] != 2.0f); // NOT relu(2) + CHECK(!Close(out[0], SiluRef(2.0f))); // NOT silu(2) == 1.7615942 + CHECK(out[1] == 9.0f); // relu(3)^2 + CHECK(out[1] != 3.0f); // NOT relu(3) + CHECK(!Close(out[1], SiluRef(3.0f))); // NOT silu(3) == 2.8577223 + CHECK(out[2] == 0.0f); // relu(-1.5)^2 + CHECK(!Close(out[2], SiluRef(-1.5f))); // silu(-1.5) == -0.27440965, NOT zero +} + +// The dtype/rounding order of relu_squared_kernel (activation_kernels.cu:673-678): +// the square is computed in FP32 and stored ONCE. bf16 in, f32 out is the arm +// that catches a kernel narrowing the product back through bf16 before the store +// — the exact defect a bf16-out-only test absorbs (bf16 rounds it away). +// x = 1 + 1/128 = 1.0078125 (exactly representable in bf16) +// x^2 = 16641/16384 = 1.01568603515625 (exact in f32) +// bf16(x^2) = 1 + 2/128 = 1.015625 (what a narrowed square would store) +TEST_CASE("moe relu2: bf16 in / f32 out keeps the square in f32 (no narrowing)") { + const float x0 = 1.0078125f; + std::vector x = {vt::F32ToBF16(x0)}; + REQUIRE(vt::BF16ToF32(x[0]) == x0); // the input itself is exact in bf16 + std::vector out(1, -1.0f); + Tensor xt = Bf16_1(x); + Tensor ot = F32_1(out); + Queue q = Q(); + vt::MoeRelu2(q, ot, xt); + + CHECK(out[0] == x0 * x0); // 1.01568603515625, the f32 square + CHECK(out[0] != vt::BF16ToF32(vt::F32ToBF16(x0 * x0))); // NOT the narrowed 1.015625 +} + +// The bf16 STORE arm: exactly one round-to-nearest-even of the f32 square. Raw +// bit comparison, not a tolerance — a second rounding step is invisible to any +// bf16 tolerance wide enough to be meaningful. +TEST_CASE("moe relu2: bf16 out rounds the f32 square exactly once") { + const float x0 = 1.0078125f; + std::vector x = {vt::F32ToBF16(x0), vt::F32ToBF16(-2.5f)}; + std::vector out(2, 0xFFFF); + Tensor xt = Bf16_1(x); + Tensor ot = Bf16_1(out); + Queue q = Q(); + vt::MoeRelu2(q, ot, xt); + + CHECK(out[0] == vt::F32ToBF16(x0 * x0)); + CHECK(out[1] == vt::F32ToBF16(0.0f)); +} + +TEST_CASE("moe relu2: rejects a shape/dtype/device contract violation") { + std::vector x(8, 1.0f); + std::vector small(4, 0.0f); + Tensor xt = F32_1(x); + Tensor st = F32_1(small); + Queue q = Q(); + CHECK_THROWS_AS(vt::MoeRelu2(q, st, xt), std::runtime_error); + + std::vector ints(8, 0); + Tensor it = Tensor::Contiguous(ints.data(), DType::kI32, Cpu(), {8}); + CHECK_THROWS_AS(vt::MoeRelu2(q, it, xt), std::runtime_error); + + // The DEVICE half of the contract, which the name promises. Runnable without + // a GPU: `vt::MoeRelu2` validates `out.device == q.device && x.device == + // q.device` BEFORE `GetOp` dispatches, so a tensor merely LABELLED kCUDA is + // rejected on the host and its data pointer is never dereferenced. Both + // operand positions are checked — a wrapper that validated only `out` would + // pass the first and fail the second. + std::vector host(8, 1.0f); + Tensor cuda_out = Tensor::Contiguous(host.data(), DType::kF32, Device{DeviceType::kCUDA, 0}, {8}); + Tensor cuda_x = Tensor::Contiguous(host.data(), DType::kF32, Device{DeviceType::kCUDA, 0}, {8}); + std::vector out8(8, 0.0f); + Tensor ot8 = F32_1(out8); + CHECK_THROWS_AS(vt::MoeRelu2(q, cuda_out, xt), std::runtime_error); // out on the wrong device + CHECK_THROWS_AS(vt::MoeRelu2(q, ot8, cuda_x), std::runtime_error); // x on the wrong device +} + +// --------------------------------------------------------------------------- +// 2. routed_scaling_factor on the OUTPUT (apply_routed_scale_to_output=True). +// --------------------------------------------------------------------------- + +// moe_runner.py:402-406 scales `fused_output` and leaves `shared_output` alone; +// :722-725 then adds them. So the combine is +// out = routed_scale * sum_j w[t,j]*expert_out[t,j] + shared[t] +// and NOT routed_scale * (routed + shared), and NOT (routed + shared). +TEST_CASE("moe combine: routed_scale multiplies the ROUTED sum, not the shared term") { + const int64_t T = 2, K = 2, H = 3; + std::vector expert_out = { + // t=0 + 1.0f, 2.0f, 3.0f, // slot 0 + -1.0f, 0.5f, 4.0f, // slot 1 + // t=1 + 2.0f, -2.0f, 1.0f, // slot 0 + 0.25f, 1.5f, -3.0f, // slot 1 + }; + std::vector weights = {0.75f, 0.25f, 0.5f, 0.5f}; + std::vector shared = {10.0f, 20.0f, 30.0f, -1.0f, -2.0f, -3.0f}; + std::vector out(static_cast(T * H), 0.0f); + + Tensor eo = F32_3(expert_out, T, K, H); + Tensor wt = F32_2(weights, T, K); + Tensor sh = F32_2(shared, T, H); + Tensor ot = F32_2(out, T, H); + Queue q = Q(); + const float scale = 2.5f; + vt::MoeCombine(q, ot, eo, wt, &sh, scale); + + for (int64_t t = 0; t < T; ++t) { + for (int64_t h = 0; h < H; ++h) { + float routed = 0.0f; + for (int64_t j = 0; j < K; ++j) { + routed += weights[static_cast(t * K + j)] * + expert_out[static_cast((t * K + j) * H + h)]; + } + const float sv = shared[static_cast(t * H + h)]; + const size_t i = static_cast(t * H + h); + CHECK(Close(out[i], scale * routed + sv)); + // The two misplacements: scaling the shared term too, and dropping the + // scale entirely. Both are wrong by a wide margin on every element here. + CHECK(!Close(out[i], scale * (routed + sv))); + CHECK(!Close(out[i], routed + sv)); + } + } +} + +// The mis-port the case above CANNOT see, and the one most likely to be made: +// folding the factor into each router weight — `acc += (scale*w[j])*eo[j]` — +// instead of scaling the assembled sum — `acc = scale * sum_j w[j]*eo[j]`. +// The two agree in exact arithmetic, so every wide-margin check above passes +// under the fold; they differ in f32 because the fold rounds K times INSIDE the +// reduction instead of once at the end. +// +// Upstream scales the ASSEMBLED tensor: `_maybe_apply_routed_scale_to_output` +// (moe_runner.py:402-404) runs `fused_output *= self.routed_scaling_factor` on +// the finished `fused_output` — one multiply per output element, AFTER the +// K-term reduction that produced it. Laguna folds the same factor into the +// router weights by linearity (`laguna_ops.h:48`) and is entitled to; NemotronH +// takes the literal upstream form, so the fold is a defect here. +// +// The comparison is bitwise and portable: the project pins -ffp-contract=off +// for every C++ TU (CMakeLists.txt:42-56), so both expressions below are IEEE +// as-written, with no FMA contraction to make the reference disagree with the +// kernel. The REQUIRE is what stops this being a vacuous green — it proves the +// chosen data actually SEPARATES the two forms before the CHECKs assert which +// one we implement. +TEST_CASE("moe combine: routed_scale scales the ASSEMBLED sum, not each router weight") { + const int64_t T = 2, K = 4, H = 1; + const float scale = 2.5f; + // Decimal-grid values: their f32 products carry full mantissas, so the K-term + // reduction genuinely rounds and the two orderings separate (row 0 by 10 ULP, + // row 1 by 4 ULP). A dyadic grid like 1/8 makes every product exact and both + // forms bit-identical — which is exactly why the wide-margin case above, on + // such data, cannot catch this. + std::vector expert_out = {2.2f, -1.15f, -1.18f, -1.52f, // t=0, slots 0..3 + 2.72f, -2.55f, 2.06f, -2.21f}; // t=1, slots 0..3 + std::vector weights = {0.95f, 0.32f, 0.37f, 0.72f, + 0.80f, 0.45f, 0.86f, 0.59f}; + std::vector out(static_cast(T * H), 0.0f); + + Tensor eo = F32_3(expert_out, T, K, H); + Tensor wt = F32_2(weights, T, K); + Tensor ot = F32_2(out, T, H); + Queue q = Q(); + // No shared term: this case isolates the placement of the scale WITHIN the + // routed reduction. The shared-term placement is the case above. + vt::MoeCombine(q, ot, eo, wt, nullptr, scale); + + for (int64_t t = 0; t < T; ++t) { + CAPTURE(t); + // Upstream form: reduce the K terms, THEN scale once. + float acc = 0.0f; + for (int64_t j = 0; j < K; ++j) { + acc += weights[static_cast(t * K + j)] * + expert_out[static_cast(t * K + j)]; + } + const float want = acc * scale; + // Folded form: scale each router weight, then reduce. + float folded = 0.0f; + for (int64_t j = 0; j < K; ++j) { + folded += (scale * weights[static_cast(t * K + j)]) * + expert_out[static_cast(t * K + j)]; + } + // The data separates the two forms — without this the CHECKs below prove + // nothing. + REQUIRE(Bits(want) != Bits(folded)); + CHECK(Bits(out[static_cast(t)]) == Bits(want)); + CHECK(Bits(out[static_cast(t)]) != Bits(folded)); + } +} + +// The default keeps every landed caller byte-identical: the 5-argument call with +// routed_scale == 1.0f must produce the same bits as the 4-argument call. +TEST_CASE("moe combine: routed_scale defaults to 1.0 (landed callers unchanged)") { + const int64_t T = 2, K = 3, H = 4; + std::vector expert_out(static_cast(T * K * H)); + for (size_t i = 0; i < expert_out.size(); ++i) { + expert_out[i] = 0.125f * static_cast(i) - 1.5f; + } + std::vector weights = {0.5f, 0.25f, 0.25f, 0.125f, 0.375f, 0.5f}; + std::vector shared(static_cast(T * H)); + for (size_t i = 0; i < shared.size(); ++i) shared[i] = 0.5f - 0.25f * static_cast(i); + + std::vector a(static_cast(T * H), 0.0f); + std::vector b(static_cast(T * H), 0.0f); + Tensor eo = F32_3(expert_out, T, K, H); + Tensor wt = F32_2(weights, T, K); + Tensor sh = F32_2(shared, T, H); + Tensor at = F32_2(a, T, H); + Tensor bt = F32_2(b, T, H); + Queue q = Q(); + vt::MoeCombine(q, at, eo, wt, &sh); // landed 4-arg form + vt::MoeCombine(q, bt, eo, wt, &sh, 1.0f); // explicit no-op scale + CHECK(std::memcmp(a.data(), b.data(), a.size() * sizeof(float)) == 0); +} + +// The ONE arm of `_maybe_apply_routed_scale_to_output` this port does not +// mirror: moe_runner.py:403-406 keys on `fused_output.dtype == torch.float16` +// and, when a `shared_output` exists, divides the SHARED term by the factor +// instead of multiplying the fused one (fp16 overflow protection; the decoder +// layer compensates). It is not mirrored because it is UNREACHABLE, not because +// it was missed: the analogue of `fused_output` here is `out`, and MoeCombine +// gates `out.dtype` through `IsOutFloat` (src/vt/ops.cpp:22), which admits f32 +// and bf16 ONLY. This case pins that gate, so the "unreachable" claim next to +// the kernel cannot quietly become false if `IsOutFloat` is ever widened to +// kF16 — that widening makes an unmirrored upstream branch reachable and must +// fail here rather than in a token gate. +TEST_CASE("moe combine: f16 out is refused, which is why upstream's fp16 arm is unreachable") { + const int64_t T = 1, K = 2, H = 2; + std::vector expert_out = {1.0f, 2.0f, 3.0f, 4.0f}; + std::vector weights = {0.5f, 0.5f}; + std::vector out_f16(static_cast(T * H), 0); + std::vector shared(static_cast(T * H), 1.0f); + + Tensor eo = F32_3(expert_out, T, K, H); + Tensor wt = F32_2(weights, T, K); + Tensor sh = F32_2(shared, T, H); + Tensor ot = Tensor::Contiguous(out_f16.data(), DType::kF16, Cpu(), {T, H}); + Queue q = Q(); + CHECK_THROWS_AS(vt::MoeCombine(q, ot, eo, wt, &sh, 2.5f), std::runtime_error); + // ... and with no shared term either: the refusal is on the OUT dtype, which + // is what makes the fp16 branch unreachable regardless of `shared`. + CHECK_THROWS_AS(vt::MoeCombine(q, ot, eo, wt, nullptr, 2.5f), std::runtime_error); +} + +// --------------------------------------------------------------------------- +// 3. The whole non-gated expert, end to end on the shared ops. +// --------------------------------------------------------------------------- + +namespace { + +// An independent scalar reference for ONE NemotronH MoE block. Deliberately +// written from the upstream formula rather than from any vt op, so it cannot +// agree with the implementation by sharing a helper. +// h = x @ W_up[e]^T ; h = relu(h)^2 ; y = h @ W_down[e]^T +// out = routed_scale * sum_j w[t,j] * y[t,j] + shared[t] +std::vector NonGatedExpertRef(const std::vector& x, int64_t T, int64_t H, + int64_t I, int64_t E, const std::vector& w_up, + const std::vector& w_down, + const std::vector& ids, + const std::vector& weights, int64_t K, + const std::vector& shared, float routed_scale) { + std::vector out(static_cast(T * H), 0.0f); + (void)E; + for (int64_t t = 0; t < T; ++t) { + std::vector acc(static_cast(H), 0.0f); + for (int64_t j = 0; j < K; ++j) { + const int64_t e = ids[static_cast(t * K + j)]; + std::vector hbuf(static_cast(I), 0.0f); + for (int64_t i = 0; i < I; ++i) { + float s = 0.0f; + for (int64_t k = 0; k < H; ++k) { + s += x[static_cast(t * H + k)] * + w_up[static_cast((e * I + i) * H + k)]; + } + const float r = s > 0.0f ? s : 0.0f; + hbuf[static_cast(i)] = r * r; // relu^2, NOT relu, NOT silu + } + for (int64_t h = 0; h < H; ++h) { + float s = 0.0f; + for (int64_t i = 0; i < I; ++i) { + s += hbuf[static_cast(i)] * + w_down[static_cast((e * H + h) * I + i)]; + } + acc[static_cast(h)] += weights[static_cast(t * K + j)] * s; + } + } + for (int64_t h = 0; h < H; ++h) { + out[static_cast(t * H + h)] = + routed_scale * acc[static_cast(h)] + shared[static_cast(t * H + h)]; + } + } + return out; +} + +float Synth(int64_t a, int64_t b, float k) { + return std::sin(static_cast(a) * 0.7f + static_cast(b) * 0.13f) * k; +} + +} // namespace + +// The W2 deliverable: the expert has NO gate half, so it is the existing SINGLE +// grouped projection plus the relu^2 activation — up_proj -> relu^2 -> down_proj +// -> weighted combine with the routed scale on the OUTPUT. Every step is a +// shared vt:: op; nothing here is a NemotronH-specific MoE path. +TEST_CASE("nemotron-h non-gated expert: up -> relu^2 -> down -> scaled combine") { + const int64_t T = 3, H = 6, I = 4, E = 4, K = 2; + const float routed_scale = 2.5f; // config.routed_scaling_factor + + std::vector x(static_cast(T * H)); + for (int64_t t = 0; t < T; ++t) { + for (int64_t h = 0; h < H; ++h) x[static_cast(t * H + h)] = Synth(t, h, 0.9f); + } + // up_proj weight [E, I, H] and down_proj weight [E, H, I] — torch Linear + // (out, in) orientation, which is exactly vt::MatmulBT's `b [N, K]`. + std::vector w_up(static_cast(E * I * H)); + for (int64_t e = 0; e < E; ++e) { + for (int64_t i = 0; i < I; ++i) { + for (int64_t h = 0; h < H; ++h) { + w_up[static_cast((e * I + i) * H + h)] = Synth(e * I + i, h, 0.4f); + } + } + } + std::vector w_down(static_cast(E * H * I)); + for (int64_t e = 0; e < E; ++e) { + for (int64_t h = 0; h < H; ++h) { + for (int64_t i = 0; i < I; ++i) { + w_down[static_cast((e * H + h) * I + i)] = Synth(e * H + h + 3, i, 0.3f); + } + } + } + const std::vector ids = {0, 1, 2, 3, 1, 0}; + const std::vector weights = {0.6f, 0.4f, 0.7f, 0.3f, 0.5f, 0.5f}; + std::vector shared(static_cast(T * H)); + for (size_t i = 0; i < shared.size(); ++i) shared[i] = 0.05f * static_cast(i) - 0.2f; + + Queue q = Q(); + // Per (token, slot) expert projection through the shared GEMM op, exactly as + // the CPU/GGUF MoE reference loop does for the gated archs. The ONLY structural + // difference from a SwiGLU expert is that there is one projection, not a merged + // pair, and the epilogue is relu^2 instead of silu*up. + std::vector expert_out(static_cast(T * K * H), 0.0f); + for (int64_t t = 0; t < T; ++t) { + for (int64_t j = 0; j < K; ++j) { + const int64_t e = ids[static_cast(t * K + j)]; + Tensor xt = Tensor::Contiguous(&x[static_cast(t * H)], DType::kF32, Cpu(), {1, H}); + std::vector hbuf(static_cast(I), 0.0f); + Tensor ht = F32_2(hbuf, 1, I); + Tensor wu = Tensor::Contiguous(&w_up[static_cast(e * I * H)], DType::kF32, Cpu(), + {I, H}); + vt::MatmulBT(q, ht, xt, wu); + std::vector act(static_cast(I), 0.0f); + Tensor at = F32_2(act, 1, I); + vt::MoeRelu2(q, at, ht); + Tensor yt = Tensor::Contiguous(&expert_out[static_cast((t * K + j) * H)], + DType::kF32, Cpu(), {1, H}); + Tensor wd = Tensor::Contiguous(&w_down[static_cast(e * H * I)], DType::kF32, Cpu(), + {H, I}); + vt::MatmulBT(q, yt, at, wd); + } + } + + std::vector out(static_cast(T * H), 0.0f); + Tensor eo = F32_3(expert_out, T, K, H); + std::vector wcopy = weights; + Tensor wt = F32_2(wcopy, T, K); + Tensor sh = F32_2(shared, T, H); + Tensor ot = F32_2(out, T, H); + vt::MoeCombine(q, ot, eo, wt, &sh, routed_scale); + + const std::vector want = + NonGatedExpertRef(x, T, H, I, E, w_up, w_down, ids, weights, K, shared, routed_scale); + for (size_t i = 0; i < want.size(); ++i) { + CHECK(Close(out[i], want[i], 1e-5f, 1e-6f)); + } + + // The same composite with the GATED family's activation is a DIFFERENT answer: + // proves the block is genuinely sensitive to the activation choice and is not + // dominated by the combine. + const std::vector silu_ref = [&] { + std::vector o(static_cast(T * H), 0.0f); + for (int64_t t = 0; t < T; ++t) { + for (int64_t j = 0; j < K; ++j) { + const int64_t e = ids[static_cast(t * K + j)]; + std::vector hb(static_cast(I), 0.0f); + for (int64_t i = 0; i < I; ++i) { + float s = 0.0f; + for (int64_t k = 0; k < H; ++k) { + s += x[static_cast(t * H + k)] * + w_up[static_cast((e * I + i) * H + k)]; + } + hb[static_cast(i)] = SiluRef(s); + } + for (int64_t h = 0; h < H; ++h) { + float s = 0.0f; + for (int64_t i = 0; i < I; ++i) { + s += hb[static_cast(i)] * + w_down[static_cast((e * H + h) * I + i)]; + } + o[static_cast(t * H + h)] += weights[static_cast(t * K + j)] * s; + } + } + } + for (size_t i = 0; i < o.size(); ++i) o[i] = routed_scale * o[i] + shared[i]; + return o; + }(); + bool any_differs = false; + for (size_t i = 0; i < want.size(); ++i) { + if (!Close(out[i], silu_ref[i], 1e-3f, 1e-6f)) any_differs = true; + } + CHECK(any_differs); +} + +// The scale lives on the OUTPUT, so the ROUTER runs with routed_scaling_factor +// 1.0 (layer.py:291-300) and its weights are the plain renormalized sigmoid +// scores. Scaling the LOGITS instead is a different answer entirely — sigmoid is +// non-linear, so it moves the weights (and can move the SELECTION). +TEST_CASE("nemotron-h routed scale: on the output, not on the router logits") { + const int64_t T = 1, E = 6, K = 3; + std::vector logits = {0.2f, -0.4f, 1.1f, 0.05f, -1.3f, 0.7f}; + std::vector scaled_logits(logits.size()); + const float routed_scale = 2.5f; + for (size_t i = 0; i < logits.size(); ++i) scaled_logits[i] = routed_scale * logits[i]; + + vt::MoeRouterTopKArgs args; + args.top_k = static_cast(K); + args.renormalize = true; // config.norm_topk_prob + args.scoring_func = vt::MoeScoringFunc::kSigmoid; + args.num_expert_group = 1; // config.n_group + args.topk_group = 1; // config.topk_group + // apply_routed_scale_to_output=True => the ROUTER's factor is a nop (1.0). + args.routed_scaling_factor = 1.0f; + std::vector bias(static_cast(E), 0.0f); + bias[2] = -5.0f; // e_score_correction_bias: biases SELECTION only + + std::vector w(static_cast(T * K), 0.0f); + std::vector ids(static_cast(T * K), -1); + Tensor lt = F32_2(logits, T, E); + Tensor wt = F32_2(w, T, K); + Tensor it = Tensor::Contiguous(ids.data(), DType::kI32, Cpu(), {T, K}); + Tensor bt = F32_1(bias); + Queue q = Q(); + vt::MoeRouterTopK(q, wt, it, lt, args, &bt); + + // Renormalized => the weights sum to 1: the routed scale is NOT in them. + float sum = 0.0f; + for (float v : w) sum += v; + CHECK(Close(sum, 1.0f, 1e-6f)); + for (float v : w) CHECK(v <= 1.0f); + + // Scaling the logits produces DIFFERENT weights, so a mis-port that folds the + // scale into the router input cannot pass the block-level gate above. + std::vector w2(static_cast(T * K), 0.0f); + std::vector ids2(static_cast(T * K), -1); + Tensor lt2 = F32_2(scaled_logits, T, E); + Tensor wt2 = F32_2(w2, T, K); + Tensor it2 = Tensor::Contiguous(ids2.data(), DType::kI32, Cpu(), {T, K}); + vt::MoeRouterTopK(q, wt2, it2, lt2, args, &bt); + bool weights_differ = false; + for (size_t i = 0; i < w.size(); ++i) { + if (!Close(w[i], w2[i], 1e-4f, 1e-6f)) weights_differ = true; + } + CHECK(weights_differ); +} + +// --------------------------------------------------------------------------- +// 4. The NVFP4 W4A16 arm's group size (spec §6 named risk). +// --------------------------------------------------------------------------- + +// NemotronH's routed experts are W4A16_NVFP4 with group_size=16. The grouped +// Marlin path takes the group size as an explicit argument whose DEFAULT is the +// NVFP4 one, so 16 is the configuration it already runs (the alternative, 32, +// only comes with mxfp4=true). Pinned here so a later widening of the default +// cannot silently re-point NemotronH's experts at a group size the checkpoint +// does not carry. +TEST_CASE("nvfp4 grouped moe: group_size 16 is the NVFP4 default NemotronH needs") { + vt::MoeMarlinArgs args; + CHECK(args.group_size == 16); + CHECK(args.mxfp4 == false); + vt::MarlinDenseArgs dense; + CHECK(dense.group_size == 16); + CHECK(dense.mxfp4 == false); +} From 73d217db64270f0e12a53a54ff1f15eacef5d181 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Thu, 13 Aug 2026 23:56:13 +0200 Subject: [PATCH 13/55] feat(MODEL-MM-indextts2): W1 vocoder core, W2 GPT-2 backbone, W6a speech seam, family refusal (#634) (#681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL FOUR of the seven work items in .agents/specs/indextts-2-5.md. This does NOT make IndexTTS-2.5 runnable: the lane detects its checkpoint and REFUSES, naming the stages that are missing. No audio is synthesized by this change and none is claimed. W1 — the shared BigVGAN 1-D core moves out of `minimax_h3.h` into `vllm::vocoder1d`. It lived there because H3 ported it first; LTX-2.5 reused rather than copied it, and `ltx2_audio_vae.cpp:223-230` records why ("each copy keeps its own green gate while the two audio VAEs drift apart"). IndexTTS-2.5 is the THIRD consumer. 28 call sites retargeted; `MaterializeWeightNorm` stays with H3, which is its only caller. Gated twice, because the failures differ: a structural guard (one declaration site, one definition site, no retired spellings) is the ONLY thing that can see a fork, since a fresh copy agrees on the day it is made; and 10 cases of HAND-COMPUTED arithmetic, not values captured from the implementation, which would pass just as happily if the helper were uniformly wrong. `test_minimax_h3` 79/79 and `test_ltx2_vae` 36/36 prove the move is behaviour-preserving. W2 — the GPT-2 talker backbone, ported from `gpt2.py` @ 555967922 with the reference checkout verified at that SHA before any line was cited. The oracle is a TP=1 restatement (upstream's module needs a KV cache, a device and an initialized distributed environment), the technique gen-minimax-h3-goldens.py already uses. Hidden states within 2e-5, logits within 5e-5, argmax TOKEN-EXACT. The Conv1D transpose and causality each get their own case because both fail silently: goldens are emitted in upstream [in, out] orientation so the loader must transpose, and a non-causal model stays fluent. W6a — the `SpeechEngine` seam, mirroring `VideoEngine`, shaped for a FAMILY because vLLM-Omni carries ~10 further TTS architectures behind this one. Family registration — an IndexTTS-2.5 checkpoint now resolves to a named family and refuses with the missing pieces spelled out, which is the AGENTS.md contract for an unimplemented arm. Also records the MEASURED component inventory (gpt 17,171 LOC, s2mel 15,011, utils 18,265, codec 1,930) and one correction that changes the plan: the talker is built with `spk_cond_mode="campplus"`, so CAMPPlus is UPSTREAM of it and W3 cannot be deferred. STILL OPEN: W3 (w2v-bert-2.0, MaskGCT, CAMPPlus), W4 (EnhancedCodec, S2Mel), W5 (compose), W6b (routes + ABI). No e2e render, and any parity claim additionally needs the vLLM-Omni oracle, still UNPINNED (#633). Verified locally: six suites green (10/4/7/4/79/36 cases) plus the structural guard; check-test-registration, check-agent-record, check-doc-checkpoint and check-pr-size green. Every gate was mutation-proven, and TWICE a mutation's build failed under -Werror so the old binary re-ran and printed SUCCESS; compile status is now checked alongside every mutation result. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/specs/indextts-2-5.md | 59 +++- CMakeLists.txt | 4 + include/vllm/model_executor/models/gpt2.h | 90 ++++++ .../vllm/model_executor/models/indextts2.h | 29 ++ .../model_executor/models/ltx2_video_vae.h | 2 +- .../vllm/model_executor/models/minimax_h3.h | 87 +----- .../vllm/model_executor/models/vocoder1d.h | 93 ++++++ include/vllm/multimodal/speech_engine.h | 124 ++++++++ scripts/gen-gpt2-goldens.py | 293 ++++++++++++++++++ src/vllm/model_executor/models/gpt2.cpp | 257 +++++++++++++++ src/vllm/model_executor/models/indextts2.cpp | 61 ++++ .../model_executor/models/ltx2_audio_vae.cpp | 33 +- .../models/minimax_h3_audio_vae.cpp | 282 +---------------- .../models/minimax_h3_vae_loader.cpp | 3 +- src/vllm/model_executor/models/vocoder1d.cpp | 279 +++++++++++++++++ src/vllm/multimodal/speech_engine.cpp | 88 ++++++ tests/CMakeLists.txt | 5 + tests/scripts/test_vocoder1d_single_home.py | 108 +++++++ tests/vllm/models/gpt2_goldens.inc | 91 ++++++ tests/vllm/models/test_gpt2.cpp | 206 ++++++++++++ tests/vllm/models/test_indextts2_family.cpp | 89 ++++++ tests/vllm/models/test_ltx2_vae.cpp | 5 +- tests/vllm/models/test_minimax_h3.cpp | 3 +- tests/vllm/models/test_vocoder1d.cpp | 186 +++++++++++ tests/vllm/multimodal/test_speech_engine.cpp | 165 ++++++++++ 25 files changed, 2262 insertions(+), 380 deletions(-) create mode 100644 include/vllm/model_executor/models/gpt2.h create mode 100644 include/vllm/model_executor/models/indextts2.h create mode 100644 include/vllm/model_executor/models/vocoder1d.h create mode 100644 include/vllm/multimodal/speech_engine.h create mode 100644 scripts/gen-gpt2-goldens.py create mode 100644 src/vllm/model_executor/models/gpt2.cpp create mode 100644 src/vllm/model_executor/models/indextts2.cpp create mode 100644 src/vllm/model_executor/models/vocoder1d.cpp create mode 100644 src/vllm/multimodal/speech_engine.cpp create mode 100644 tests/scripts/test_vocoder1d_single_home.py create mode 100644 tests/vllm/models/gpt2_goldens.inc create mode 100644 tests/vllm/models/test_gpt2.cpp create mode 100644 tests/vllm/models/test_indextts2_family.cpp create mode 100644 tests/vllm/models/test_vocoder1d.cpp create mode 100644 tests/vllm/multimodal/test_speech_engine.cpp diff --git a/.agents/specs/indextts-2-5.md b/.agents/specs/indextts-2-5.md index b8debd1ba..53c4cad74 100644 --- a/.agents/specs/indextts-2-5.md +++ b/.agents/specs/indextts-2-5.md @@ -185,6 +185,44 @@ never `--enforce-eager`. encoders, at pinned revisions on the NAS. - W1 touches a header two shipped lanes already depend on, so it coordinates with both H3 and LTX-2.5 (#435, merged). +## Measured component inventory + +Taken from the reference implementation itself (`github.com/index-tts/index-tts`, +cloned 2026-08-13), not estimated. This is what W3-W5 actually contain, and it is +the reason this lane is a campaign rather than a change. + +| Reference component | Python LOC | Ours | +|---|---:|---| +| `indextts/gpt` (UnifiedVoice talker) | 17,171 | backbone DONE (W2); the talker head, conditioning and generate loop remain | +| `indextts/s2mel` (CFM/DiT + length regulator + CAMPPlus) | 15,011 | not started | +| `indextts/utils` | 18,265 | not started; much is training-only and will not be ported | +| `indextts/codec` (EnhancedCodec) | 1,930 | not started | +| `indextts/BigVGAN` | 3,740 | 1-D core DONE (W1), shared with H3 and LTX-2.5 | +| `indextts/vqvae` | 395 | not started | +| `Wav2Vec2BertModel` (HF transformers) | external | not started; a Conformer, so `parakeet_encoder.cpp` is partial reuse | + +**The pipeline order, read from `infer_v2_5.py`** (`infer_generator`, lines +569-660), which supersedes the recipe page's prose: + +1. `SeamlessM4TFeatureExtractor` -> features from the 16 kHz reference clip +2. `Wav2Vec2BertModel` (`semantic_model`) -> `vq_emb` +3. `EnhancedCodec.quantize` -> `semantic_code`, `feat` +4. `CAMPPlus` -> a 192-d global style vector from `feat` +5. `s2mel.models['length_regulator']` -> `prompt_condition` +6. `UnifiedVoice` (GPT-2 talker, `spk_cond_mode="campplus"`) -> mel codes +7. `s2mel` CFM/DiT -> mel, then BigVGAN -> 22.05 kHz waveform + +Note step 6: the talker is conditioned on the CAMPPlus style vector, so **CAMPPlus +is upstream of the talker, not a post-hoc speaker check**. Sequencing W3 after W2 +but before W5 is therefore forced, not a preference. + +**CAMPPlus is not the small brick its file size suggests.** 436 lines define a +2-D conv front end (`FCM`: Conv2d + BatchNorm2d + `BasicResBlock` x4) and an +`xvector` stack of `TDNNLayer` + three `CAMDenseTDNNBlock`s of 12, 24 and 16 +layers, each carrying a `CAMLayer` attention, separated by `TransitLayer`s, then +`StatsPool` and a `DenseLayer`. That is 52 dense layers plus batch norms; the +line count is small because the blocks are looped. + ## Work breakdown | W | Work | Depends on | @@ -233,7 +271,20 @@ behind the pin. ## Now -`INVENTORIED`, unclaimed, blocked on -[#633](https://github.com/mudler/vllm.cpp/issues/633). Nothing implemented. W1-W4 -can start against frozen goldens once the row is claimed; no parity or speed -claim is possible until the omni pin exists. +`INVENTORIED`, blocked on [#633](https://github.com/mudler/vllm.cpp/issues/633) +for any parity or e2e claim. + +**Landed** (PR #681): W1, the shared 1-D vocoder core in `vllm::vocoder1d` with a +structural anti-fork guard and hand-computed numerics; W2, the GPT-2 backbone +host reference, token-exact against upstream at the parity pin and proven +load-bearing by three mutations. + +**Next, in forced order:** W3 CAMPPlus (upstream of the talker, see the inventory +above), then the w2v-bert-2.0 Conformer and EnhancedCodec, then W4 S2Mel, then W5 +compose. W6a/W6b (the `SpeechEngine` seam and ABI v19) need no oracle and can be +taken in parallel by a second claim. + +**Groundwork done for whoever picks it up:** the reference implementation is +cloned and its component sizes measured, the NAS is mounted, and +`huggingface.co/IndexTeam/IndexTTS-2.5` resolves. What is NOT done: the ~6 GB +checkpoint is not downloaded, and no golden generator exists past W2. diff --git a/CMakeLists.txt b/CMakeLists.txt index e166d11b2..a22423ec6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -766,6 +766,10 @@ add_library(vllm STATIC src/vllm/model_executor/models/minimax_h3_nvfp4.cpp src/vllm/model_executor/models/minimax_h3_sharded.cpp src/vllm/model_executor/models/minimax_h3_audio_vae.cpp + src/vllm/model_executor/models/vocoder1d.cpp + src/vllm/model_executor/models/gpt2.cpp + src/vllm/multimodal/speech_engine.cpp + src/vllm/model_executor/models/indextts2.cpp src/vllm/model_executor/models/minimax_h3_video_vae.cpp src/vllm/model_executor/models/minimax_h3_video_vae_device.cpp src/vllm/model_executor/models/minimax_h3_vae_cnn.cpp diff --git a/include/vllm/model_executor/models/gpt2.h b/include/vllm/model_executor/models/gpt2.h new file mode 100644 index 000000000..9f8ed837f --- /dev/null +++ b/include/vllm/model_executor/models/gpt2.h @@ -0,0 +1,90 @@ +// GPT-2 backbone — IndexTTS-2.5's stage-0 talker (W2, #634). +// +// Ported from vllm/model_executor/models/gpt2.py @ 555967922 (the parity pin): +// GPT2Attention.__init__/forward gpt2.py:61-110 +// GPT2MLP.__init__/forward gpt2.py:113-143 +// GPT2Block.forward gpt2.py:165-180 +// GPT2Model.forward gpt2.py:217-240 +// _transpose_conv1d gpt2.py:242-254 +// +// This is the HOST REFERENCE forward, the same shape the MiniMax-H3 and LTX-2.5 +// lanes started from: a portable f32 implementation gated against upstream +// before any device path exists. It is not wired to the runner, the ABI or the +// server; that is W6a/W6b in .agents/specs/indextts-2-5.md. +// +// TWO THINGS THIS ARCHITECTURE GETS WRONG QUIETLY, both gated in test_gpt2.cpp: +// +// 1. CONV1D ORIENTATION. HF's GPT-2 uses Conv1D, not Linear, so c_attn/c_proj/ +// c_fc store their 2D weight as [in, out]. `Load` transposes to [out, in]. +// Skipping that produces a model that runs, emits plausible tokens, and is +// wrong. +// 2. CAUSALITY. Without the upper-triangular mask every position attends to the +// future. The output stays fluent, so only a perturbation test sees it. +#pragma once + +#include +#include +#include +#include + +namespace vllm { +namespace gpt2 { + +// The subset of GPT2Config the backbone reads (gpt2.py:184-210). +struct Params { + int64_t vocab_size = 0; + int64_t max_position_embeddings = 0; + int64_t hidden_size = 0; + int64_t num_hidden_layers = 0; + int64_t num_attention_heads = 0; + // `n_inner` when the config sets it, else 4 * hidden_size (gpt2.py:157). + int64_t inner_size = 0; + double layer_norm_eps = 1e-5; + + int64_t head_dim() const { return hidden_size / num_attention_heads; } +}; + +// Raw checkpoint tensors, in the orientation the file carries them. +struct CheckpointTensors { + std::map> values; + std::map> shapes; + + void Set(const std::string& name, std::vector shape, std::vector data); + const std::vector& Get(const std::string& name) const; + const std::vector& Shape(const std::string& name) const; + bool Has(const std::string& name) const { return values.count(name) != 0; } +}; + +// One transformer block's weights, already transposed for the matmul. +struct LayerWeights { + std::vector ln_1_weight, ln_1_bias; + std::vector ln_2_weight, ln_2_bias; + std::vector c_attn_weight, c_attn_bias; // [3H, H], [3H] + std::vector c_proj_weight, c_proj_bias; // [H, H], [H] + std::vector c_fc_weight, c_fc_bias; // [I, H], [I] + std::vector mlp_c_proj_weight, mlp_c_proj_bias; // [H, I], [H] +}; + +struct Weights { + std::vector wte; // [vocab, H] + std::vector wpe; // [positions, H] + std::vector ln_f_weight, ln_f_bias; + std::vector layers; +}; + +// Materialize `Weights` from a checkpoint, applying the Conv1D transpose +// (gpt2.py:242-254). Throws BY NAME on a missing tensor rather than reading +// zeros. +Weights Load(const Params& params, const CheckpointTensors& tensors); + +// gpt2.py:217-240. Returns the post-`ln_f` hidden states, [seq, hidden]. +std::vector ForwardHost(const Params& params, const Weights& weights, + const std::vector& input_ids, + const std::vector& positions); + +// Tied lm_head: GPT-2 ties the output projection to `wte`. Returns [seq, vocab]. +std::vector LogitsHost(const Params& params, const Weights& weights, + const std::vector& hidden); + +} // namespace gpt2 +} // namespace vllm diff --git a/include/vllm/model_executor/models/indextts2.h b/include/vllm/model_executor/models/indextts2.h new file mode 100644 index 000000000..2977a27a6 --- /dev/null +++ b/include/vllm/model_executor/models/indextts2.h @@ -0,0 +1,29 @@ +// IndexTTS-2.5 — the speech family registration (#634). +// +// Upstream: vllm-project/vllm-omni registers TWO architectures for this model, +// `IndexTTS2TalkerForConditionalGeneration` (stage 0) and `IndexTTS2S2MelDecoder` +// (stage 1), at `vllm_omni/model_executor/models/registry.py`. +// +// WHAT EXISTS TODAY. The GPT-2 backbone the talker is built on (gpt2.h, W2) and +// the BigVGAN 1-D core the vocoder needs (vocoder1d.h, W1). What does NOT exist +// is the reference-audio conditioning path (w2v-bert-2.0, the MaskGCT semantic +// codec, CAMPPlus), the EnhancedCodec, and the S2Mel CFM/DiT decoder — W3-W5 in +// .agents/specs/indextts-2-5.md. +// +// So this registration DETECTS the checkpoint and REFUSES the load, naming the +// missing stages. That is deliberate: an unimplemented arm that is silently +// absent is a failure this project has already recorded, while one that refuses +// by name is visible debt a reader can act on. +#pragma once + +#include "vllm/multimodal/speech_engine.h" + +namespace vllm { +namespace models { + +// Register the IndexTTS-2.5 family into `registry`. Explicit rather than +// self-registering at static-init time so a test can drive a local registry. +void RegisterIndexTts2SpeechFamily(multimodal::SpeechRegistry& registry); + +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/ltx2_video_vae.h b/include/vllm/model_executor/models/ltx2_video_vae.h index bfb7022a5..6613fa184 100644 --- a/include/vllm/model_executor/models/ltx2_video_vae.h +++ b/include/vllm/model_executor/models/ltx2_video_vae.h @@ -114,7 +114,7 @@ struct Ltx2VideoDecoderBlock { // the direction and MAGNITUDE of the mutation are therefore part of the verdict. // MEASURED, by mutating each in turn, with the bound each number actually holds: // -// kMiniMaxH3SnakeEps 1e-9 -> 0.0 green +// vocoder1d::kSnakeEps 1e-9 -> 0.0 green // kLtx2RmsNorm2dEps 1e-12 -> 0.0 green ...but NOT green upward: // escalating it to 1.0 REDS "the video ENCODER (*_res family)" and "the video // encoder CROPS a frame count that is not 1 + k*factor", both at 0.000525832 diff --git a/include/vllm/model_executor/models/minimax_h3.h b/include/vllm/model_executor/models/minimax_h3.h index bce4e8934..896916f90 100644 --- a/include/vllm/model_executor/models/minimax_h3.h +++ b/include/vllm/model_executor/models/minimax_h3.h @@ -487,91 +487,6 @@ struct MiniMaxH3AudioVaeWeights { bool Has(const std::string& name) const { return tensors.count(name) != 0; } }; -// --------------------------------------------------------------------------- -// THE SHARED 1-D BigVGAN PRIMITIVES. -// -// These are published here, next to MiniMaxH3KaiserSincFilter1d and -// MiniMaxH3GroupNorm3d, because MiniMax-H3 is not their only caller: LTX-2.5's -// audio VAE (ltx2_audio_vae.cpp) descends from the same BigVGAN lineage and needs -// exactly this arithmetic. They used to be TU-private to -// minimax_h3_audio_vae.cpp, which forced LTX to stand up a second copy — and a -// second copy of the alias-free trim geometry in particular is the kind of -// duplicate that goes wrong quietly: a fix to the pad/trim arithmetic lands in -// one file, the other keeps its own green gate, and the two audio VAEs disagree -// with nothing to say so. AGENTS.md §"Shared seams" says to extend the seam -// rather than hand-roll a parallel path, so the seam is extended here and there -// is now ONE implementation, gated by BOTH suites. -// -// The `MiniMaxH3` prefix records where they were ported first, not who may call -// them. Signals are CHANNEL-MAJOR [C, T] throughout. -// --------------------------------------------------------------------------- - -// One 1-D convolution over [C_in, T] with stride/dilation/groups. Weight is -// [C_out, C_in/groups, K]; the input must ALREADY be padded. Accumulates in -// double, and reports the produced length through `out_len`. -std::vector MiniMaxH3Conv1d(const std::vector& in, int64_t in_channels, - int64_t in_len, const std::vector& weight, - const std::vector* bias, int64_t out_channels, - int64_t kernel, int64_t stride, int64_t dilation, - int64_t groups, int64_t* out_len); - -// torch.nn.functional.conv_transpose1d over [C_in, T]. Weight is -// [C_in, C_out/groups, K]; output length is (T-1)*stride - 2*padding + K. -std::vector MiniMaxH3ConvTranspose1d(const std::vector& in, int64_t in_channels, - int64_t in_len, const std::vector& weight, - const std::vector* bias, int64_t out_channels, - int64_t kernel, int64_t stride, int64_t padding, - int64_t groups, int64_t* out_len); - -// F.pad along the time axis. `replicate` selects mode="replicate"; false is the -// zero pad an ordinary nn.Conv1d `padding=` argument performs. -std::vector MiniMaxH3Pad1d(const std::vector& in, int64_t channels, int64_t in_len, - int64_t left, int64_t right, bool replicate, int64_t* out_len); - -// The stabilizing epsilon in Snake/SnakeBeta's reciprocal, named so it can be -// pinned: upstream writes `1.0 / (beta + 1e-9)` on both sides of this port's -// lineage — LTX-2.5 at audio_vae/vocoder.py:198 (Snake) and :221 (SnakeBeta), and -// MiniMax-H3 in its BigVGAN activation. Mutation proves no reduced-dimension -// golden can tell 1e-9 from 0.0, because beta is O(1) there and never small -// enough for the term to matter; the value still decides whether a real -// checkpoint whose learned beta approaches zero divides or explodes. It is -// therefore held by a source-anchored constant assertion, not by a tensor -// comparison. -inline constexpr double kMiniMaxH3SnakeEps = 1e-9; - -// Snake / SnakeBeta: x + (b + kMiniMaxH3SnakeEps)^-1 * sin^2(a * x), in place. -// A null `beta` selects plain Snake, which reuses ALPHA as the reciprocal scale -// (LTX-2.5 vocoder.py:198); a non-null one selects SnakeBeta (vocoder.py:221), -// which is what every MiniMax-H3 checkpoint carries. `logscale` exponentiates -// both, which is how the parameters are stored. -void MiniMaxH3SnakeActivation(std::vector& x, int64_t channels, int64_t length, - const std::vector& alpha, const std::vector* beta, - bool logscale); - -// The anti-aliased activation, `Activation1d`: upsample by `ratio` -> Snake(Beta) -// -> downsample by `ratio`, both through the kaiser-sinc window with REPLICATE -// padding. MiniMax-H3 reaches it through dac_alias_free_act.py + -// dac_alias_free_resample.py; LTX-2.5 through vocoder.py:104-184. The trim -// geometry is the fragile part and the reason this is shared rather than copied. -// -// Build() computes the window once; Apply() is const and may be reused. -struct MiniMaxH3AliasFreeActivation1d { - int64_t ratio = 2; - int64_t kernel_size = 12; - std::vector filter; - - void Build(); - - std::vector Apply(const std::vector& in, int64_t channels, int64_t in_len, - const std::vector& alpha, const std::vector* beta, - bool logscale, int64_t* out_len) const; -}; - -// kaiser_sinc_filter1d (dac_alias_free_filter.py:26-60) — built at load time, never -// read from the checkpoint. -std::vector MiniMaxH3KaiserSincFilter1d(double cutoff, double half_width, - int64_t kernel_size); - // torch weight_norm: w = g * v / ||v||, norm over every dim except dim 0. Every // conv in this decoder is weight-normalized, so the checkpoint stores (g, v). std::vector MiniMaxH3MaterializeWeightNorm(const std::vector& g, @@ -601,7 +516,7 @@ std::vector MiniMaxH3ReadSafetensorF32(const StTensor& tensor); // Conv1d that runs BEFORE BigVGAN — is at the top level. // // The anti-aliasing `.filter` tensors are SKIPPED: those kaiser-sinc filters are -// COMPUTED at load (MiniMaxH3KaiserSincFilter1d), never read from the checkpoint. +// COMPUTED at load (vocoder1d::KaiserSincFilter1d), never read from the checkpoint. // The file also carries the audio ENCODER (`encoder.*`), which generation does not // need; it is ignored rather than loaded. MiniMaxH3AudioVaeWeights LoadMiniMaxH3AudioVaeWeights(const SafetensorsFile& file); diff --git a/include/vllm/model_executor/models/vocoder1d.h b/include/vllm/model_executor/models/vocoder1d.h new file mode 100644 index 000000000..0297bbf76 --- /dev/null +++ b/include/vllm/model_executor/models/vocoder1d.h @@ -0,0 +1,93 @@ +// The shared 1-D BigVGAN vocoder core. +// +// MiniMax-H3 ported these first; LTX-2.5's audio VAE descends from the same +// BigVGAN lineage and reused them rather than copying, because a second copy of +// the alias-free trim geometry is the duplicate that goes wrong quietly -- a fix +// to the pad/trim arithmetic lands in one file, the other keeps its own green +// gate, and the two audio VAEs drift apart with nothing to say so. +// +// IndexTTS-2.5 (#634) is the third consumer, which is what moved this out of +// `minimax_h3.h`: a model's header is not a home for something three lanes +// share, and the `MiniMaxH3` prefix said who ported it rather than who may call +// it. Signals are CHANNEL-MAJOR [C, T] throughout. +// +// `tests/scripts/test_vocoder1d_single_home.py` asserts there is exactly one +// home and one definition of each symbol; no numeric test can see a fork, +// because a fresh copy agrees on the day it is made and only drifts later. +#pragma once + +#include +#include + +namespace vllm { +namespace vocoder1d { + + +// One 1-D convolution over [C_in, T] with stride/dilation/groups. Weight is +// [C_out, C_in/groups, K]; the input must ALREADY be padded. Accumulates in +// double, and reports the produced length through `out_len`. +std::vector Conv1d(const std::vector& in, int64_t in_channels, + int64_t in_len, const std::vector& weight, + const std::vector* bias, int64_t out_channels, + int64_t kernel, int64_t stride, int64_t dilation, + int64_t groups, int64_t* out_len); + +// torch.nn.functional.conv_transpose1d over [C_in, T]. Weight is +// [C_in, C_out/groups, K]; output length is (T-1)*stride - 2*padding + K. +std::vector ConvTranspose1d(const std::vector& in, int64_t in_channels, + int64_t in_len, const std::vector& weight, + const std::vector* bias, int64_t out_channels, + int64_t kernel, int64_t stride, int64_t padding, + int64_t groups, int64_t* out_len); + +// F.pad along the time axis. `replicate` selects mode="replicate"; false is the +// zero pad an ordinary nn.Conv1d `padding=` argument performs. +std::vector Pad1d(const std::vector& in, int64_t channels, int64_t in_len, + int64_t left, int64_t right, bool replicate, int64_t* out_len); + +// The stabilizing epsilon in Snake/SnakeBeta's reciprocal, named so it can be +// pinned: upstream writes `1.0 / (beta + 1e-9)` on both sides of this port's +// lineage — LTX-2.5 at audio_vae/vocoder.py:198 (Snake) and :221 (SnakeBeta), and +// MiniMax-H3 in its BigVGAN activation. Mutation proves no reduced-dimension +// golden can tell 1e-9 from 0.0, because beta is O(1) there and never small +// enough for the term to matter; the value still decides whether a real +// checkpoint whose learned beta approaches zero divides or explodes. It is +// therefore held by a source-anchored constant assertion, not by a tensor +// comparison. +inline constexpr double kSnakeEps = 1e-9; + +// Snake / SnakeBeta: x + (b + kSnakeEps)^-1 * sin^2(a * x), in place. +// A null `beta` selects plain Snake, which reuses ALPHA as the reciprocal scale +// (LTX-2.5 vocoder.py:198); a non-null one selects SnakeBeta (vocoder.py:221), +// which is what every MiniMax-H3 checkpoint carries. `logscale` exponentiates +// both, which is how the parameters are stored. +void SnakeActivation(std::vector& x, int64_t channels, int64_t length, + const std::vector& alpha, const std::vector* beta, + bool logscale); + +// The anti-aliased activation, `Activation1d`: upsample by `ratio` -> Snake(Beta) +// -> downsample by `ratio`, both through the kaiser-sinc window with REPLICATE +// padding. MiniMax-H3 reaches it through dac_alias_free_act.py + +// dac_alias_free_resample.py; LTX-2.5 through vocoder.py:104-184. The trim +// geometry is the fragile part and the reason this is shared rather than copied. +// +// Build() computes the window once; Apply() is const and may be reused. +struct AliasFreeActivation1d { + int64_t ratio = 2; + int64_t kernel_size = 12; + std::vector filter; + + void Build(); + + std::vector Apply(const std::vector& in, int64_t channels, int64_t in_len, + const std::vector& alpha, const std::vector* beta, + bool logscale, int64_t* out_len) const; +}; + +// kaiser_sinc_filter1d (dac_alias_free_filter.py:26-60) — built at load time, never +// read from the checkpoint. +std::vector KaiserSincFilter1d(double cutoff, double half_width, + int64_t kernel_size); + +} // namespace vocoder1d +} // namespace vllm diff --git a/include/vllm/multimodal/speech_engine.h b/include/vllm/multimodal/speech_engine.h new file mode 100644 index 000000000..33f1da942 --- /dev/null +++ b/include/vllm/multimodal/speech_engine.h @@ -0,0 +1,124 @@ +// The speech (text-to-speech) engine seam — W6a of #634. +// +// This project's audio surface CONSUMES audio today (Parakeet, Voxtral, +// audio_processor.cpp); nothing synthesizes it. IndexTTS-2.5 is the first +// generating lane, and vLLM-Omni carries roughly ten more TTS architectures +// behind it, so the seam is shaped for a family rather than for one model. +// +// It mirrors `vllm::multimodal::VideoEngine`, which solved the same problem for +// the other generative modality: one abstract engine, per-family +// self-registration so adding a family edits no shared array, and detection that +// INSPECTS the artifact rather than trusting a file extension or a path spelling +// (both chosen by whoever repackaged the checkpoint). +// +// WHAT THIS IS NOT. No family is registered yet: the IndexTTS-2.5 stages land +// with W3-W5 (.agents/specs/indextts-2-5.md). Until one does, `Load` REFUSES and +// names what it tried, because a silently absent arm is a failure this project +// has already recorded, while a refusal that names the missing piece is owed +// debt. +#pragma once + +#include +#include +#include +#include +#include + +namespace vllm { +namespace multimodal { + +// Where the checkpoint set lives, plus the optional family override a caller +// uses to SKIP detection (never to override a detector that disagrees). +struct SpeechModelParams { + std::string path; + std::string family; // empty => detect +}; + +// One synthesis request. +struct SpeechGenParams { + std::string text; + std::string language; // upstream's `lang`; empty => the family's default + + // The reference clip. IndexTTS-2 has NO text-only synthesis, so for that + // family an empty clip is a refusal rather than a default voice. + std::vector reference_audio; + int64_t reference_sample_rate = 0; + + // Upstream states a seed controls both AR sampling and per-request CFM noise. + int64_t seed = 0; +}; + +// A rendered waveform. Mono unless a family says otherwise; `sample_rate` is the +// family's native rate (22050 for IndexTTS-2.5) rather than a resampled one, so +// the caller decides whether to resample. +struct SpeechResult { + std::vector samples; + int64_t sample_rate = 0; + int64_t channels = 1; +}; + +// A loaded speech checkpoint set, weights staged once, ready to synthesize. +class SpeechEngine { + public: + virtual ~SpeechEngine(); + + // The stable registry name of the family this engine implements. + virtual std::string family() const = 0; + + // The native output rate, so a caller never has to infer it from the family. + virtual int64_t sample_rate() const = 0; + + // True when the family cannot synthesize without a reference clip. Exposed + // rather than implied, so a server can reject a request before staging. + virtual bool requires_reference_audio() const = 0; + + // Run one blocking synthesis. Implementations serialize internally (staged + // weights are shared state); throws std::runtime_error to fail the request. + virtual SpeechResult Synthesize(const SpeechGenParams& params) = 0; + + protected: + SpeechEngine() = default; + SpeechEngine(const SpeechEngine&) = default; + SpeechEngine& operator=(const SpeechEngine&) = default; + SpeechEngine(SpeechEngine&&) = default; + SpeechEngine& operator=(SpeechEngine&&) = default; +}; + +// Does this checkpoint set belong to the family? A detector must not throw: an +// unreadable or unrecognizable artifact is `false`, and one family's bad day +// must not deny every other family a chance to claim the checkpoint. +using SpeechFamilyDetector = std::function; + +// Load the checkpoint set as this family. Throws std::runtime_error naming the +// problem on any mismatch. +using SpeechFamilyLoader = std::function(const SpeechModelParams&)>; + +struct SpeechFamilyRegistration { + std::string name; // stable family name, e.g. "indextts2" + SpeechFamilyDetector detect; + SpeechFamilyLoader load; +}; + +class SpeechRegistry { + public: + // Throws on an empty name, a missing detector or loader, or A NAME ALREADY + // REGISTERED — the last because two families sharing one name is the + // never-guess guarantee defeated from the inside. + void Register(SpeechFamilyRegistration registration); + + // Resolve and load. Returns nullptr and fills `why` when nothing claims the + // checkpoint; `why` names every family that was tried and the path, so the + // refusal is evidence rather than a verdict. + std::unique_ptr Load(const SpeechModelParams& params, std::string* why) const; + + std::vector families() const; + + private: + std::vector families_; +}; + +// The process-global registry, for families that self-register. +SpeechRegistry& GlobalSpeechRegistry(); + +} // namespace multimodal +} // namespace vllm diff --git a/scripts/gen-gpt2-goldens.py b/scripts/gen-gpt2-goldens.py new file mode 100644 index 000000000..f2df70205 --- /dev/null +++ b/scripts/gen-gpt2-goldens.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/gpt2_goldens.inc — the GPT-2 backbone parity oracle. + +IndexTTS-2.5's stage-0 talker is a GPT-2 autoregressive backbone (see +.agents/specs/indextts-2-5.md, W2). This generator produces the goldens the C++ +port is gated against. + +WHY A RESTATEMENT RATHER THAN AN IMPORT. Upstream's module +(vllm/model_executor/models/gpt2.py @ the parity pin 555967922) cannot be +executed standalone: `GPT2Attention` builds a paged `Attention` layer that wants +a KV cache and a device, and every projection is a {QKV,Column,Row}ParallelLinear +that wants an initialized distributed environment. At tensor_parallel_size=1 each +of those degenerates to a plain nn.Linear, so the restatement below is +line-for-line faithful and every class cites the upstream lines it mirrors. This +is the same technique `gen-minimax-h3-goldens.py` uses for the H3 DiT, and for +the same reason. + +Upstream anchors (vllm/model_executor/models/gpt2.py @ 555967922): + GPT2Attention.__init__/forward -> gpt2.py:61-110 (fused c_attn, scale, c_proj) + GPT2MLP.__init__/forward -> gpt2.py:113-143 (c_fc -> act -> c_proj) + GPT2Block.forward -> gpt2.py:165-180 (pre-LN, two residuals) + GPT2Model.forward -> gpt2.py:217-240 (wte + wpe, blocks, ln_f) + GPT2LMHeadModel -> gpt2.py:250-... (lm_head, tied embeddings) + _transpose_conv1d -> gpt2.py:242-254 (HF Conv1D stores W^T) + +THE CONV1D TRANSPOSE is the trap this port has to get right and is therefore +gated explicitly: HF's GPT-2 stores c_attn/c_proj/c_fc as Conv1D, whose 2D weight +is [in, out] rather than the [out, in] a Linear expects. Loading it untransposed +produces a model that runs and is wrong. The goldens below are emitted in the +UPSTREAM (Conv1D, [in, out]) orientation, exactly as a checkpoint carries them, +so the C++ loader has to perform the transpose to reproduce the output. + +Usage: + python3 scripts/gen-gpt2-goldens.py --out tests/vllm/models/gpt2_goldens.inc + +Needs torch + numpy (CPU only). +""" + +from __future__ import annotations + +import argparse +import math +from pathlib import Path + +import numpy as np +import torch +import torch.nn.functional as F + +_MASK64 = (1 << 64) - 1 + + +def fnv1a64(name: str) -> int: + h = 0xCBF29CE484222325 + for byte in name.encode("utf-8"): + h ^= byte + h = (h * 0x100000001B3) & _MASK64 + return h + + +def splitmix64(x: int) -> int: + x = (x + 0x9E3779B97F4A7C15) & _MASK64 + z = x + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & _MASK64 + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & _MASK64 + return z ^ (z >> 31) + + +def rand(name: str, count: int) -> np.ndarray: + """`count` values uniform in [-1, 1), reproducible from `name` alone. + + The C++ side rebuilds this byte-for-byte; both use the same 53-bit mantissa + construction so the doubles are bit-identical. + """ + seed = fnv1a64(name) + out = np.empty(count, dtype=np.float64) + for i in range(count): + u = splitmix64((seed + i) & _MASK64) + out[i] = ((u >> 11) * (2.0**-53)) * 2.0 - 1.0 + return out + + +def param(name: str, shape, scale: float) -> torch.Tensor: + count = 1 + for dim in shape: + count *= dim + values = rand(name, count) * scale + return torch.tensor(values, dtype=torch.float64).reshape(shape).to(torch.float32) + + +# ── the model, restated at TP=1 ────────────────────────────────────────────── + + +class Config: + """The subset of GPT2Config the backbone reads.""" + + def __init__(self) -> None: + self.vocab_size = 33 + self.n_positions = 16 + self.hidden_size = 8 + self.num_hidden_layers = 2 + self.num_attention_heads = 2 + self.n_inner = 16 + self.layer_norm_epsilon = 1e-5 + + +class Weights: + """Checkpoint tensors in UPSTREAM orientation (Conv1D weights are [in, out]).""" + + def __init__(self, cfg: Config) -> None: + h, inner = cfg.hidden_size, cfg.n_inner + self.t: dict[str, torch.Tensor] = { + "wte.weight": param("wte.weight", (cfg.vocab_size, h), 0.5), + "wpe.weight": param("wpe.weight", (cfg.n_positions, h), 0.25), + "ln_f.weight": param("ln_f.weight", (h,), 0.1) + 1.0, + "ln_f.bias": param("ln_f.bias", (h,), 0.1), + } + for i in range(cfg.num_hidden_layers): + p = f"h.{i}." + self.t.update( + { + p + "ln_1.weight": param(p + "ln_1.weight", (h,), 0.1) + 1.0, + p + "ln_1.bias": param(p + "ln_1.bias", (h,), 0.1), + p + "ln_2.weight": param(p + "ln_2.weight", (h,), 0.1) + 1.0, + p + "ln_2.bias": param(p + "ln_2.bias", (h,), 0.1), + # Conv1D: [in, out]. c_attn fuses q|k|v so out = 3*h. + p + "attn.c_attn.weight": param(p + "attn.c_attn.weight", (h, 3 * h), 0.3), + p + "attn.c_attn.bias": param(p + "attn.c_attn.bias", (3 * h,), 0.2), + p + "attn.c_proj.weight": param(p + "attn.c_proj.weight", (h, h), 0.3), + p + "attn.c_proj.bias": param(p + "attn.c_proj.bias", (h,), 0.2), + p + "mlp.c_fc.weight": param(p + "mlp.c_fc.weight", (h, inner), 0.3), + p + "mlp.c_fc.bias": param(p + "mlp.c_fc.bias", (inner,), 0.2), + p + "mlp.c_proj.weight": param(p + "mlp.c_proj.weight", (inner, h), 0.3), + p + "mlp.c_proj.bias": param(p + "mlp.c_proj.bias", (h,), 0.2), + } + ) + + def linear(self, name: str) -> tuple[torch.Tensor, torch.Tensor]: + """Return (W^T, b) — the transpose upstream applies at load time. + + gpt2.py:242-254 `_transpose_conv1d`: HF stores these as Conv1D, so the + 2D weight is transposed relative to what a Linear expects. + """ + return self.t[name + ".weight"].t(), self.t[name + ".bias"] + + +def layer_norm(x: torch.Tensor, w: torch.Tensor, b: torch.Tensor, eps: float) -> torch.Tensor: + return F.layer_norm(x, (x.shape[-1],), weight=w, bias=b, eps=eps) + + +def gelu_new(x: torch.Tensor) -> torch.Tensor: + """GPT-2's `activation_function` is gelu_new (the tanh approximation).""" + return ( + 0.5 + * x + * (1.0 + torch.tanh(math.sqrt(2.0 / math.pi) * (x + 0.044715 * torch.pow(x, 3.0)))) + ) + + +def attention(cfg: Config, w: Weights, layer: int, x: torch.Tensor) -> torch.Tensor: + """gpt2.py:102-110. Fused c_attn -> chunk(3) -> causal attention -> c_proj.""" + p = f"h.{layer}.attn." + weight, bias = w.linear(p + "c_attn") + qkv = F.linear(x, weight, bias) + q, k, v = qkv.chunk(3, dim=-1) + + heads = cfg.num_attention_heads + head_dim = cfg.hidden_size // heads + scale = head_dim**-0.5 # gpt2.py:76 + + seq = x.shape[0] + + def split(t: torch.Tensor) -> torch.Tensor: + return t.reshape(seq, heads, head_dim).transpose(0, 1) # [H, T, D] + + qh, kh, vh = split(q), split(k), split(v) + scores = torch.matmul(qh, kh.transpose(-1, -2)) * scale + mask = torch.full((seq, seq), float("-inf")).triu(1) + scores = scores + mask + probs = torch.softmax(scores, dim=-1) + out = torch.matmul(probs, vh) # [H, T, D] + out = out.transpose(0, 1).reshape(seq, cfg.hidden_size) + + weight, bias = w.linear(p + "c_proj") + return F.linear(out, weight, bias) + + +def mlp(cfg: Config, w: Weights, layer: int, x: torch.Tensor) -> torch.Tensor: + """gpt2.py:139-143. c_fc -> act -> c_proj.""" + p = f"h.{layer}.mlp." + weight, bias = w.linear(p + "c_fc") + h = F.linear(x, weight, bias) + h = gelu_new(h) + weight, bias = w.linear(p + "c_proj") + return F.linear(h, weight, bias) + + +def block(cfg: Config, w: Weights, layer: int, x: torch.Tensor) -> torch.Tensor: + """gpt2.py:165-180. Pre-LN with two residual adds.""" + p = f"h.{layer}." + residual = x + h = layer_norm(x, w.t[p + "ln_1.weight"], w.t[p + "ln_1.bias"], cfg.layer_norm_epsilon) + h = attention(cfg, w, layer, h) + x = h + residual + + residual = x + h = layer_norm(x, w.t[p + "ln_2.weight"], w.t[p + "ln_2.bias"], cfg.layer_norm_epsilon) + h = mlp(cfg, w, layer, h) + return residual + h + + +def forward(cfg: Config, w: Weights, ids: list[int], positions: list[int]) -> torch.Tensor: + """gpt2.py:217-240. wte + wpe, the block stack, then ln_f.""" + inputs_embeds = w.t["wte.weight"][torch.tensor(ids)] + position_embeds = w.t["wpe.weight"][torch.tensor(positions)] + x = inputs_embeds + position_embeds + for layer in range(cfg.num_hidden_layers): + x = block(cfg, w, layer, x) + return layer_norm(x, w.t["ln_f.weight"], w.t["ln_f.bias"], cfg.layer_norm_epsilon) + + +def logits(cfg: Config, w: Weights, hidden: torch.Tensor) -> torch.Tensor: + """Tied lm_head: GPT-2 ties lm_head to wte (gpt2.py, tie_word_embeddings).""" + return F.linear(hidden, w.t["wte.weight"]) + + +# ── emit ───────────────────────────────────────────────────────────────────── + + +def emit_f32(out, name: str, values) -> None: + flat = np.asarray(values, dtype=np.float32).reshape(-1) + out.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(flat), 6): + chunk = ", ".join(f"{v:.9e}F" for v in flat[i : i + 6]) + out.write(f" {chunk},\n") + out.write("};\n\n") + + +def emit_i64(out, name: str, values) -> None: + out.write(f"inline constexpr int64_t {name}[] = {{\n") + joined = ", ".join(str(int(v)) for v in values) + out.write(f" {joined},\n") + out.write("};\n\n") + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--out", required=True) + args = ap.parse_args() + + torch.manual_seed(0) + cfg = Config() + w = Weights(cfg) + + ids = [3, 17, 0, 31, 8, 8, 12] + positions = list(range(len(ids))) + + hidden = forward(cfg, w, ids, positions) + lg = logits(cfg, w, hidden) + argmax = torch.argmax(lg, dim=-1) + + path = Path(args.out) + with path.open("w", encoding="utf-8") as out: + out.write("// GENERATED by scripts/gen-gpt2-goldens.py -- do not edit.\n") + out.write("//\n") + out.write("// GPT-2 backbone goldens: a TP=1 restatement of vLLM's\n") + out.write("// vllm/model_executor/models/gpt2.py @ 555967922 (the parity pin),\n") + out.write("// executed at reduced dimensions on CPU. Weights are rebuilt on both\n") + out.write("// sides from the same FNV-1a -> splitmix64 stream, so no weight byte is\n") + out.write("// checked in. Conv1D weights are emitted in UPSTREAM [in, out]\n") + out.write("// orientation: the loader must transpose them, and a port that does not\n") + out.write("// still runs while producing the wrong tokens.\n") + out.write("#pragma once\n\n#include \n\n") + out.write("namespace gpt2_goldens {\n\n") + out.write(f"inline constexpr int64_t kVocab = {cfg.vocab_size};\n") + out.write(f"inline constexpr int64_t kPositions = {cfg.n_positions};\n") + out.write(f"inline constexpr int64_t kHidden = {cfg.hidden_size};\n") + out.write(f"inline constexpr int64_t kLayers = {cfg.num_hidden_layers};\n") + out.write(f"inline constexpr int64_t kHeads = {cfg.num_attention_heads};\n") + out.write(f"inline constexpr int64_t kInner = {cfg.n_inner};\n") + out.write(f"inline constexpr double kLayerNormEps = {cfg.layer_norm_epsilon!r};\n\n") + emit_i64(out, "kInputIds", ids) + emit_i64(out, "kPositions0", positions) + emit_f32(out, "kHiddenStates", hidden.detach().numpy()) + emit_f32(out, "kLogits", lg.detach().numpy()) + emit_i64(out, "kArgmax", argmax.detach().numpy()) + out.write("} // namespace gpt2_goldens\n") + + print(f"wrote {path} (seq={len(ids)}, hidden={cfg.hidden_size}, layers={cfg.num_hidden_layers})") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/vllm/model_executor/models/gpt2.cpp b/src/vllm/model_executor/models/gpt2.cpp new file mode 100644 index 000000000..7e0b65e39 --- /dev/null +++ b/src/vllm/model_executor/models/gpt2.cpp @@ -0,0 +1,257 @@ +// GPT-2 backbone host reference. See gpt2.h for the upstream anchors. +#include "vllm/model_executor/models/gpt2.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace gpt2 { + +void CheckpointTensors::Set(const std::string& name, std::vector shape, + std::vector data) { + int64_t count = 1; + for (const int64_t dim : shape) count *= dim; + VT_CHECK(static_cast(count) == data.size(), + "gpt2 checkpoint: element count does not match shape"); + shapes[name] = std::move(shape); + values[name] = std::move(data); +} + +const std::vector& CheckpointTensors::Get(const std::string& name) const { + const auto it = values.find(name); + VT_CHECK(it != values.end(), "gpt2 checkpoint: missing tensor '" + name + "'"); + return it->second; +} + +const std::vector& CheckpointTensors::Shape(const std::string& name) const { + const auto it = shapes.find(name); + VT_CHECK(it != shapes.end(), "gpt2 checkpoint: missing shape for '" + name + "'"); + return it->second; +} + +namespace { + +// gpt2.py:242-254 `_transpose_conv1d`. HF's GPT-2 stores c_attn/c_proj/c_fc as +// Conv1D, whose 2D weight is [in, out]; the matmul below wants [out, in]. The +// transpose happens once, at load. +std::vector TransposeConv1d(const std::vector& w, int64_t in_dim, int64_t out_dim) { + VT_CHECK(w.size() == static_cast(in_dim * out_dim), + "gpt2: Conv1D weight does not match [in, out]"); + std::vector out(w.size()); + for (int64_t i = 0; i < in_dim; ++i) { + for (int64_t o = 0; o < out_dim; ++o) { + out[static_cast(o * in_dim + i)] = w[static_cast(i * out_dim + o)]; + } + } + return out; +} + +// y = W x + b, with W stored [out, in] and x a single row of `in` values. +void LinearRow(const float* x, const std::vector& w, const std::vector& b, + int64_t in_dim, int64_t out_dim, float* out) { + for (int64_t o = 0; o < out_dim; ++o) { + double acc = b.empty() ? 0.0 : static_cast(b[static_cast(o)]); + const float* row = w.data() + static_cast(o * in_dim); + for (int64_t i = 0; i < in_dim; ++i) { + acc += static_cast(row[i]) * static_cast(x[i]); + } + out[o] = static_cast(acc); + } +} + +// torch.nn.LayerNorm over the last dimension, with the biased (1/N) variance +// torch uses. +void LayerNormRow(const float* x, const std::vector& w, const std::vector& b, + int64_t n, double eps, float* out) { + double mean = 0.0; + for (int64_t i = 0; i < n; ++i) mean += static_cast(x[i]); + mean /= static_cast(n); + double var = 0.0; + for (int64_t i = 0; i < n; ++i) { + const double d = static_cast(x[i]) - mean; + var += d * d; + } + var /= static_cast(n); + const double inv = 1.0 / std::sqrt(var + eps); + for (int64_t i = 0; i < n; ++i) { + const double normed = (static_cast(x[i]) - mean) * inv; + out[i] = static_cast(normed * static_cast(w[static_cast(i)]) + + static_cast(b[static_cast(i)])); + } +} + +// GPT-2's `activation_function` is gelu_new: the tanh approximation, NOT the +// exact-erf GELU. They differ by ~1e-3, which is far above this gate's +// tolerance, so picking the wrong one is caught rather than absorbed. +double GeluNew(double x) { + constexpr double kSqrt2OverPi = 0.7978845608028654; // sqrt(2/pi) + const double inner = kSqrt2OverPi * (x + 0.044715 * x * x * x); + return 0.5 * x * (1.0 + std::tanh(inner)); +} + +} // namespace + +Weights Load(const Params& params, const CheckpointTensors& tensors) { + VT_CHECK(params.hidden_size > 0 && params.num_attention_heads > 0, + "gpt2: hidden_size and num_attention_heads must be positive"); + VT_CHECK(params.hidden_size % params.num_attention_heads == 0, + "gpt2: hidden_size must divide by num_attention_heads"); + + Weights w; + w.wte = tensors.Get("wte.weight"); + w.wpe = tensors.Get("wpe.weight"); + w.ln_f_weight = tensors.Get("ln_f.weight"); + w.ln_f_bias = tensors.Get("ln_f.bias"); + + const int64_t h = params.hidden_size; + const int64_t inner = params.inner_size; + w.layers.resize(static_cast(params.num_hidden_layers)); + for (int64_t i = 0; i < params.num_hidden_layers; ++i) { + const std::string b = "h." + std::to_string(i) + "."; + LayerWeights& l = w.layers[static_cast(i)]; + l.ln_1_weight = tensors.Get(b + "ln_1.weight"); + l.ln_1_bias = tensors.Get(b + "ln_1.bias"); + l.ln_2_weight = tensors.Get(b + "ln_2.weight"); + l.ln_2_bias = tensors.Get(b + "ln_2.bias"); + l.c_attn_weight = TransposeConv1d(tensors.Get(b + "attn.c_attn.weight"), h, 3 * h); + l.c_attn_bias = tensors.Get(b + "attn.c_attn.bias"); + l.c_proj_weight = TransposeConv1d(tensors.Get(b + "attn.c_proj.weight"), h, h); + l.c_proj_bias = tensors.Get(b + "attn.c_proj.bias"); + l.c_fc_weight = TransposeConv1d(tensors.Get(b + "mlp.c_fc.weight"), h, inner); + l.c_fc_bias = tensors.Get(b + "mlp.c_fc.bias"); + l.mlp_c_proj_weight = TransposeConv1d(tensors.Get(b + "mlp.c_proj.weight"), inner, h); + l.mlp_c_proj_bias = tensors.Get(b + "mlp.c_proj.bias"); + } + return w; +} + +std::vector ForwardHost(const Params& params, const Weights& weights, + const std::vector& input_ids, + const std::vector& positions) { + VT_CHECK(input_ids.size() == positions.size(), "gpt2: ids and positions must be the same length"); + const int64_t seq = static_cast(input_ids.size()); + const int64_t h = params.hidden_size; + const int64_t heads = params.num_attention_heads; + const int64_t head_dim = params.head_dim(); + const double scale = 1.0 / std::sqrt(static_cast(head_dim)); // gpt2.py:76 + + // gpt2.py:225-229 — inputs_embeds + position_embeds. + std::vector x(static_cast(seq * h)); + for (int64_t t = 0; t < seq; ++t) { + const int64_t id = input_ids[static_cast(t)]; + const int64_t pos = positions[static_cast(t)]; + VT_CHECK(id >= 0 && id < params.vocab_size, "gpt2: input id out of range"); + VT_CHECK(pos >= 0 && pos < params.max_position_embeddings, "gpt2: position out of range"); + for (int64_t j = 0; j < h; ++j) { + x[static_cast(t * h + j)] = + weights.wte[static_cast(id * h + j)] + weights.wpe[static_cast(pos * h + j)]; + } + } + + std::vector normed(static_cast(seq * h)); + std::vector qkv(static_cast(seq * 3 * h)); + std::vector attn_out(static_cast(seq * h)); + std::vector proj(static_cast(seq * h)); + std::vector inner_buf(static_cast(seq * params.inner_size)); + + for (const LayerWeights& l : weights.layers) { + // gpt2.py:170-174 — ln_1 -> attn -> residual. + for (int64_t t = 0; t < seq; ++t) { + LayerNormRow(&x[static_cast(t * h)], l.ln_1_weight, l.ln_1_bias, h, + params.layer_norm_eps, &normed[static_cast(t * h)]); + } + for (int64_t t = 0; t < seq; ++t) { + LinearRow(&normed[static_cast(t * h)], l.c_attn_weight, l.c_attn_bias, h, 3 * h, + &qkv[static_cast(t * 3 * h)]); + } + // gpt2.py:107 — qkv.chunk(3, dim=-1): q | k | v, contiguous thirds. + for (int64_t head = 0; head < heads; ++head) { + for (int64_t t = 0; t < seq; ++t) { + const float* q = &qkv[static_cast(t * 3 * h + head * head_dim)]; + // Causal: key positions beyond t are masked out entirely (gpt2 is a + // decoder), so the softmax runs over [0, t]. + std::vector scores(static_cast(t + 1)); + double max_score = -std::numeric_limits::infinity(); + for (int64_t s = 0; s <= t; ++s) { + const float* k = &qkv[static_cast(s * 3 * h + h + head * head_dim)]; + double dot = 0.0; + for (int64_t d = 0; d < head_dim; ++d) { + dot += static_cast(q[d]) * static_cast(k[d]); + } + dot *= scale; + scores[static_cast(s)] = dot; + max_score = std::max(max_score, dot); + } + double denom = 0.0; + for (double& s : scores) { + s = std::exp(s - max_score); + denom += s; + } + for (int64_t d = 0; d < head_dim; ++d) { + double acc = 0.0; + for (int64_t s = 0; s <= t; ++s) { + const float* v = &qkv[static_cast(s * 3 * h + 2 * h + head * head_dim)]; + acc += scores[static_cast(s)] * static_cast(v[d]); + } + attn_out[static_cast(t * h + head * head_dim + d)] = + static_cast(acc / denom); + } + } + } + for (int64_t t = 0; t < seq; ++t) { + LinearRow(&attn_out[static_cast(t * h)], l.c_proj_weight, l.c_proj_bias, h, h, + &proj[static_cast(t * h)]); + } + for (size_t i = 0; i < x.size(); ++i) x[i] += proj[i]; + + // gpt2.py:176-180 — ln_2 -> mlp -> residual. + for (int64_t t = 0; t < seq; ++t) { + LayerNormRow(&x[static_cast(t * h)], l.ln_2_weight, l.ln_2_bias, h, + params.layer_norm_eps, &normed[static_cast(t * h)]); + } + for (int64_t t = 0; t < seq; ++t) { + LinearRow(&normed[static_cast(t * h)], l.c_fc_weight, l.c_fc_bias, h, + params.inner_size, &inner_buf[static_cast(t * params.inner_size)]); + } + for (float& v : inner_buf) v = static_cast(GeluNew(static_cast(v))); + for (int64_t t = 0; t < seq; ++t) { + LinearRow(&inner_buf[static_cast(t * params.inner_size)], l.mlp_c_proj_weight, + l.mlp_c_proj_bias, params.inner_size, h, &proj[static_cast(t * h)]); + } + for (size_t i = 0; i < x.size(); ++i) x[i] += proj[i]; + } + + // gpt2.py:239 — ln_f. + std::vector out(static_cast(seq * h)); + for (int64_t t = 0; t < seq; ++t) { + LayerNormRow(&x[static_cast(t * h)], weights.ln_f_weight, weights.ln_f_bias, h, + params.layer_norm_eps, &out[static_cast(t * h)]); + } + return out; +} + +std::vector LogitsHost(const Params& params, const Weights& weights, + const std::vector& hidden) { + const int64_t h = params.hidden_size; + VT_CHECK(hidden.size() % static_cast(h) == 0, "gpt2: hidden is not a multiple of H"); + const int64_t seq = static_cast(hidden.size()) / h; + std::vector out(static_cast(seq * params.vocab_size)); + for (int64_t t = 0; t < seq; ++t) { + // Tied lm_head: the projection IS wte, [vocab, H], so this is already + // [out, in] and needs no transpose. + LinearRow(&hidden[static_cast(t * h)], weights.wte, {}, h, params.vocab_size, + &out[static_cast(t * params.vocab_size)]); + } + return out; +} + +} // namespace gpt2 +} // namespace vllm diff --git a/src/vllm/model_executor/models/indextts2.cpp b/src/vllm/model_executor/models/indextts2.cpp new file mode 100644 index 000000000..e754e8d28 --- /dev/null +++ b/src/vllm/model_executor/models/indextts2.cpp @@ -0,0 +1,61 @@ +// IndexTTS-2.5 speech-family registration. See indextts2.h for why it refuses. +#include "vllm/model_executor/models/indextts2.h" + +#include +#include +#include +#include +#include + +namespace vllm { +namespace models { +namespace { + +// Detection INSPECTS the artifact rather than trusting the path spelling, which +// is chosen by whoever repackaged the checkpoint. IndexTTS-2.5 ships a +// `config.yaml` carrying its stage keys (infer_v2_5.py reads `cfg.gpt`, +// `cfg.s2mel_checkpoint` and `cfg.semantic_codec`), so the presence of all three +// is what identifies the family. +bool LooksLikeIndexTts2(const multimodal::SpeechModelParams& params) { + std::error_code ec; + const std::filesystem::path config = std::filesystem::path(params.path) / "config.yaml"; + if (!std::filesystem::is_regular_file(config, ec) || ec) return false; + + std::ifstream in(config); + if (!in) return false; + std::ostringstream buffer; + buffer << in.rdbuf(); + const std::string text = buffer.str(); + + // All three, not any: a config carrying only one of them is a different model + // in the same lineage, and claiming it would be a guess. + return text.find("gpt:") != std::string::npos && + text.find("s2mel_checkpoint") != std::string::npos && + text.find("semantic_codec") != std::string::npos; +} + +} // namespace + +void RegisterIndexTts2SpeechFamily(multimodal::SpeechRegistry& registry) { + multimodal::SpeechFamilyRegistration reg; + reg.name = "indextts2"; + reg.detect = LooksLikeIndexTts2; + reg.load = [](const multimodal::SpeechModelParams& params) + -> std::unique_ptr { + // Name every missing piece. "unsupported" with no evidence is what sends the + // next person reading loader source to work out what was meant. + throw std::runtime_error( + "indextts2: recognized an IndexTTS-2.5 checkpoint at '" + params.path + + "' but the lane is not implemented yet. Ported so far: the GPT-2 talker " + "backbone (W2) and the shared BigVGAN 1-D core (W1). Still missing: the " + "mandatory reference-audio conditioning path (w2v-bert-2.0, the MaskGCT " + "semantic codec, CAMPPlus), the EnhancedCodec, and the S2Mel CFM/DiT " + "decoder — W3-W5 of .agents/specs/indextts-2-5.md, issue #634. Note " + "IndexTTS-2 has no text-only synthesis, so the conditioning path is " + "required rather than optional."); + }; + registry.Register(std::move(reg)); +} + +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/ltx2_audio_vae.cpp b/src/vllm/model_executor/models/ltx2_audio_vae.cpp index 6c9251319..cff349f48 100644 --- a/src/vllm/model_executor/models/ltx2_audio_vae.cpp +++ b/src/vllm/model_executor/models/ltx2_audio_vae.cpp @@ -28,6 +28,7 @@ #include #include "vllm/model_executor/models/minimax_h3.h" +#include "vllm/model_executor/models/vocoder1d.h" #include "vt/dtype.h" #ifndef M_PI @@ -405,7 +406,7 @@ AudioMap Upsample(const AudioMap& x, const AudioConvSpec& config, // MiniMax-H3 audio VAE already ports, so this DELEGATES rather than standing up a // second copy; the golden proves the shared code matches LTX's upstream too. std::vector Ltx2KaiserSincFilter1d(double cutoff, double half_width, int64_t kernel_size) { - return MiniMaxH3KaiserSincFilter1d(cutoff, half_width, kernel_size); + return vocoder1d::KaiserSincFilter1d(cutoff, half_width, kernel_size); } // UpSample1d's HANN-windowed sinc (vocoder.py:116-128) — the BWE resampler's @@ -545,7 +546,7 @@ namespace { // AMPBlock1 (vocoder.py:283-290) and ResBlock1 (resnet.py:73-80): the two residual // stacks a Vocoder can carry. std::vector VocoderResBlock(const Ltx2VocoderConfig& config, const Ltx2VaeWeights& weights, - const std::string& prefix, const MiniMaxH3AliasFreeActivation1d& act, + const std::string& prefix, const vocoder1d::AliasFreeActivation1d& act, const std::vector& x, int64_t channels, int64_t length, int64_t kernel, const std::vector& dilations) { std::vector current = x; @@ -573,10 +574,10 @@ std::vector VocoderResBlock(const Ltx2VocoderConfig& config, const Ltx2Va } int64_t padded_len = 0; const std::vector padded = - MiniMaxH3Pad1d(input, channels, in_len, left, right, /*replicate=*/false, &padded_len); + vocoder1d::Pad1d(input, channels, in_len, left, right, /*replicate=*/false, &padded_len); int64_t produced = 0; std::vector result = - MiniMaxH3Conv1d(padded, channels, padded_len, weights.Get(name + ".weight"), + vocoder1d::Conv1d(padded, channels, padded_len, weights.Get(name + ".weight"), &weights.Get(name + ".bias"), channels, kernel, 1, dil, 1, &produced); VT_CHECK(produced == in_len, "ltx2 vocoder: resblock conv changed the length"); return result; @@ -626,18 +627,18 @@ std::vector VocoderForwardFromRows(const Ltx2VocoderConfig& config, "ltx2 vocoder: input size does not match [rows, frames]"); const std::string p = config.prefix; - MiniMaxH3AliasFreeActivation1d act; + vocoder1d::AliasFreeActivation1d act; if (config.amp) act.Build(); - // conv_pre: MiniMaxH3Conv1d(128 -> upsample_initial_channel, k=7, padding=3). + // conv_pre: vocoder1d::Conv1d(128 -> upsample_initial_channel, k=7, padding=3). int64_t channels = config.upsample_initial_channel; int64_t length = 0; std::vector x; { int64_t padded_len = 0; const std::vector padded = - MiniMaxH3Pad1d(rows, row_count, frames, 3, 3, /*replicate=*/false, &padded_len); - x = MiniMaxH3Conv1d(padded, row_count, padded_len, weights.Get(p + "conv_pre.weight"), + vocoder1d::Pad1d(rows, row_count, frames, 3, 3, /*replicate=*/false, &padded_len); + x = vocoder1d::Conv1d(padded, row_count, padded_len, weights.Get(p + "conv_pre.weight"), &weights.Get(p + "conv_pre.bias"), channels, 7, 1, 1, 1, &length); } @@ -648,7 +649,7 @@ std::vector VocoderForwardFromRows(const Ltx2VocoderConfig& config, const int64_t out_channels = config.upsample_initial_channel / (int64_t{1} << (i + 1)); const std::string up = p + "ups." + std::to_string(i); int64_t up_len = 0; - x = MiniMaxH3ConvTranspose1d(x, channels, length, weights.Get(up + ".weight"), + x = vocoder1d::ConvTranspose1d(x, channels, length, weights.Get(up + ".weight"), &weights.Get(up + ".bias"), out_channels, kernel, stride, /*padding=*/(kernel - stride) / 2, /*groups=*/1, &up_len); channels = out_channels; @@ -692,11 +693,11 @@ std::vector VocoderForwardFromRows(const Ltx2VocoderConfig& config, int64_t padded_len = 0; const std::vector padded = - MiniMaxH3Pad1d(x, channels, length, 3, 3, /*replicate=*/false, &padded_len); + vocoder1d::Pad1d(x, channels, length, 3, 3, /*replicate=*/false, &padded_len); const std::vector* post_bias = config.use_bias_at_final ? &weights.Get(p + "conv_post.bias") : nullptr; int64_t final_len = 0; - std::vector out = MiniMaxH3Conv1d(padded, channels, padded_len, weights.Get(p + "conv_post.weight"), + std::vector out = vocoder1d::Conv1d(padded, channels, padded_len, weights.Get(p + "conv_post.weight"), post_bias, 2, 7, 1, 1, 1, &final_len); if (config.apply_final_activation) { @@ -760,7 +761,7 @@ std::vector Ltx2VocoderWithBweForward(const Ltx2VocoderBweConfig& config, const int64_t remainder = low_len % config.hop_length; if (remainder != 0) { std::vector grown; - grown = MiniMaxH3Pad1d(x, out_channels, low_len, 0, config.hop_length - remainder, /*replicate=*/false, + grown = vocoder1d::Pad1d(x, out_channels, low_len, 0, config.hop_length - remainder, /*replicate=*/false, &padded_len); x.swap(grown); } @@ -786,9 +787,9 @@ std::vector Ltx2VocoderWithBweForward(const Ltx2VocoderBweConfig& config, // CAUSAL: left-only padding, so a frame never depends on future samples // (vocoder.py:469-470). const std::vector padded = - MiniMaxH3Pad1d(channel, 1, padded_len, left_pad, 0, /*replicate=*/false, &padded_wave); + vocoder1d::Pad1d(channel, 1, padded_len, left_pad, 0, /*replicate=*/false, &padded_wave); const std::vector spec = - MiniMaxH3Conv1d(padded, 1, padded_wave, forward_basis, nullptr, n_freqs * 2, config.filter_length, + vocoder1d::Conv1d(padded, 1, padded_wave, forward_basis, nullptr, n_freqs * 2, config.filter_length, config.hop_length, 1, 1, &stft_len); if (c == 0) { mel_frames = stft_len; @@ -829,10 +830,10 @@ std::vector Ltx2VocoderWithBweForward(const Ltx2VocoderBweConfig& config, } int64_t skip_padded = 0; const std::vector skip_in = - MiniMaxH3Pad1d(x, out_channels, padded_len, pad, pad, /*replicate=*/true, &skip_padded); + vocoder1d::Pad1d(x, out_channels, padded_len, pad, pad, /*replicate=*/true, &skip_padded); int64_t skip_full = 0; std::vector skip = - MiniMaxH3ConvTranspose1d(skip_in, out_channels, skip_padded, depthwise, nullptr, out_channels, + vocoder1d::ConvTranspose1d(skip_in, out_channels, skip_padded, depthwise, nullptr, out_channels, kernel_size, ratio, /*padding=*/0, /*groups=*/out_channels, &skip_full); for (float& value : skip) value *= static_cast(ratio); const int64_t skip_len = skip_full - pad_left - pad_right; diff --git a/src/vllm/model_executor/models/minimax_h3_audio_vae.cpp b/src/vllm/model_executor/models/minimax_h3_audio_vae.cpp index cb28d0437..0335b11cf 100644 --- a/src/vllm/model_executor/models/minimax_h3_audio_vae.cpp +++ b/src/vllm/model_executor/models/minimax_h3_audio_vae.cpp @@ -33,6 +33,7 @@ // downsamples 2x, both through a KAISER-SINC filter built at load time (never // loaded from the checkpoint) with REPLICATE padding. #include "vllm/model_executor/models/minimax_h3.h" +#include "vllm/model_executor/models/vocoder1d.h" #include #include @@ -53,209 +54,7 @@ const std::vector& MiniMaxH3AudioVaeWeights::Get(const std::string& name) return it->second; } -namespace { - -// Zeroth-order modified Bessel function of the first kind, matching the series -// torch.kaiser_window uses. -double BesselI0(double x) { - double sum = 1.0, term = 1.0; - const double half_x_sq = (x / 2.0) * (x / 2.0); - for (int k = 1; k < 64; ++k) { - term *= half_x_sq / (static_cast(k) * static_cast(k)); - sum += term; - if (term < sum * 1e-18) break; - } - return sum; -} - -double Sinc(double x) { - if (x == 0.0) return 1.0; - const double pix = std::numbers::pi_v * x; - return std::sin(pix) / pix; -} - -// torch.kaiser_window(n, periodic=false, beta). -std::vector KaiserWindow(int64_t length, double beta) { - std::vector window(static_cast(length)); - const double denom = BesselI0(beta); - // periodic=false => the window spans [0, length-1] inclusive. - const double n_minus_1 = static_cast(length - 1); - for (int64_t i = 0; i < length; ++i) { - const double ratio = (2.0 * static_cast(i) - n_minus_1) / n_minus_1; - window[static_cast(i)] = BesselI0(beta * std::sqrt(std::max(0.0, 1.0 - ratio * ratio))) / denom; - } - return window; -} - -} // namespace - -// --------------------------------------------------------------------------- -// The shared 1-D BigVGAN primitives (declared in minimax_h3.h). They were private -// to this translation unit until LTX-2.5's audio VAE — the same BigVGAN lineage — -// needed them and copied them instead; see the header for why one implementation -// gated by two suites beats two implementations each with its own green gate. -// --------------------------------------------------------------------------- - -// One 1-D convolution over [C_in, T] with dilation/stride/groups. -// Weight is [C_out, C_in/groups, K]; input is assumed ALREADY padded. -std::vector MiniMaxH3Conv1d(const std::vector& in, int64_t in_channels, - int64_t in_len, const std::vector& weight, - const std::vector* bias, int64_t out_channels, - int64_t kernel, int64_t stride, int64_t dilation, - int64_t groups, int64_t* out_len) { - const int64_t effective = dilation * (kernel - 1) + 1; - const int64_t length = (in_len - effective) / stride + 1; - VT_CHECK(length > 0, "minimax_h3 audio vae: conv1d output length is empty"); - const int64_t in_per_group = in_channels / groups; - const int64_t out_per_group = out_channels / groups; - std::vector out(static_cast(out_channels * length), 0.0f); - for (int64_t oc = 0; oc < out_channels; ++oc) { - const int64_t g = oc / out_per_group; - for (int64_t t = 0; t < length; ++t) { - double acc = bias != nullptr ? (*bias)[static_cast(oc)] : 0.0; - for (int64_t ic = 0; ic < in_per_group; ++ic) { - const int64_t src_c = g * in_per_group + ic; - for (int64_t k = 0; k < kernel; ++k) { - const int64_t pos = t * stride + k * dilation; - acc += static_cast(in[static_cast(src_c * in_len + pos)]) * - static_cast(weight[static_cast((oc * in_per_group + ic) * kernel + k)]); - } - } - out[static_cast(oc * length + t)] = static_cast(acc); - } - } - *out_len = length; - return out; -} - -// torch.nn.functional.conv_transpose1d over [C_in, T]. -// Weight is [C_in, C_out/groups, K]; output length = (T-1)*stride - 2*padding + K. -std::vector MiniMaxH3ConvTranspose1d(const std::vector& in, int64_t in_channels, - int64_t in_len, const std::vector& weight, - const std::vector* bias, int64_t out_channels, - int64_t kernel, int64_t stride, int64_t padding, - int64_t groups, int64_t* out_len) { - const int64_t full = (in_len - 1) * stride + kernel; - const int64_t length = full - 2 * padding; - VT_CHECK(length > 0, "minimax_h3 audio vae: conv_transpose1d output length is empty"); - const int64_t in_per_group = in_channels / groups; - const int64_t out_per_group = out_channels / groups; - std::vector acc(static_cast(out_channels * full), 0.0); - for (int64_t ic = 0; ic < in_channels; ++ic) { - const int64_t g = ic / in_per_group; - for (int64_t t = 0; t < in_len; ++t) { - const double value = in[static_cast(ic * in_len + t)]; - if (value == 0.0) continue; - for (int64_t oc = 0; oc < out_per_group; ++oc) { - const int64_t dst_c = g * out_per_group + oc; - for (int64_t k = 0; k < kernel; ++k) { - acc[static_cast(dst_c * full + t * stride + k)] += - value * static_cast(weight[static_cast((ic * out_per_group + oc) * kernel + k)]); - } - } - } - } - std::vector out(static_cast(out_channels * length)); - for (int64_t c = 0; c < out_channels; ++c) { - for (int64_t t = 0; t < length; ++t) { - double value = acc[static_cast(c * full + t + padding)]; - if (bias != nullptr) value += (*bias)[static_cast(c)]; - out[static_cast(c * length + t)] = static_cast(value); - } - } - *out_len = length; - return out; -} - -// F.pad along the time axis: mode="replicate", or the zero pad an ordinary -// nn.Conv1d `padding=` argument performs. -std::vector MiniMaxH3Pad1d(const std::vector& in, int64_t channels, int64_t in_len, - int64_t left, int64_t right, bool replicate, int64_t* out_len) { - const int64_t length = in_len + left + right; - std::vector out(static_cast(channels * length), 0.0f); - for (int64_t c = 0; c < channels; ++c) { - for (int64_t t = 0; t < length; ++t) { - int64_t src = t - left; - if (src < 0 || src >= in_len) { - if (!replicate) continue; // already zero - src = std::max(0, std::min(in_len - 1, src)); - } - out[static_cast(c * length + t)] = in[static_cast(c * in_len + src)]; - } - } - *out_len = length; - return out; -} -// Snake / SnakeBeta: x + (b + kMiniMaxH3SnakeEps)^-1 * sin^2(a * x). A null `beta` -// is plain Snake, which reuses ALPHA as the reciprocal scale (LTX-2.5 -// vocoder.py:198); a non-null one is SnakeBeta (vocoder.py:221), which is what -// every MiniMax-H3 checkpoint carries. Both are exponentiated when the checkpoint -// stores them in log scale. -void MiniMaxH3SnakeActivation(std::vector& x, int64_t channels, int64_t length, - const std::vector& alpha, const std::vector* beta, - bool logscale) { - for (int64_t c = 0; c < channels; ++c) { - double a = alpha[static_cast(c)]; - double b = beta != nullptr ? (*beta)[static_cast(c)] : a; - if (logscale) { - a = std::exp(a); - b = std::exp(b); - } - const double inv_beta = 1.0 / (b + kMiniMaxH3SnakeEps); - for (int64_t t = 0; t < length; ++t) { - const double v = x[static_cast(c * length + t)]; - const double s = std::sin(a * v); - x[static_cast(c * length + t)] = static_cast(v + inv_beta * s * s); - } - } -} - -// kaiser_sinc_filter1d (dac_alias_free_filter.py:26-60). Returns [kernel_size]. -std::vector MiniMaxH3KaiserSincFilter1d(double cutoff, double half_width, - int64_t kernel_size) { - VT_CHECK(kernel_size > 0, "minimax_h3 audio vae: kernel_size must be positive"); - VT_CHECK(cutoff >= 0.0 && cutoff <= 0.5, "minimax_h3 audio vae: cutoff must be in [0, 0.5]"); - const bool even = (kernel_size % 2) == 0; - const int64_t half_size = kernel_size / 2; - - const double delta_f = 4.0 * half_width; - const double a = 2.285 * (static_cast(half_size) - 1.0) * - std::numbers::pi_v * delta_f + - 7.95; - double beta = 0.0; - if (a > 50.0) { - beta = 0.1102 * (a - 8.7); - } else if (a >= 21.0) { - beta = 0.5842 * std::pow(a - 21.0, 0.4) + 0.07886 * (a - 21.0); - } - const std::vector window = KaiserWindow(kernel_size, beta); - - std::vector time(static_cast(kernel_size)); - for (int64_t i = 0; i < kernel_size; ++i) { - time[static_cast(i)] = even ? (static_cast(-half_size + i) + 0.5) - : static_cast(i - half_size); - } - - std::vector filter(static_cast(kernel_size), 0.0); - if (cutoff == 0.0) { - return std::vector(static_cast(kernel_size), 0.0f); - } - double sum = 0.0; - for (int64_t i = 0; i < kernel_size; ++i) { - filter[static_cast(i)] = - 2.0 * cutoff * window[static_cast(i)] * Sinc(2.0 * cutoff * time[static_cast(i)]); - sum += filter[static_cast(i)]; - } - // Normalized to sum 1 so a constant input does not leak. - std::vector out(static_cast(kernel_size)); - for (int64_t i = 0; i < kernel_size; ++i) { - out[static_cast(i)] = static_cast(filter[static_cast(i)] / sum); - } - return out; -} - -// torch weight_norm: w = g * v / ||v||, norm over every dim except dim 0. std::vector MiniMaxH3MaterializeWeightNorm(const std::vector& g, const std::vector& v, int64_t out_channels) { @@ -286,61 +85,6 @@ std::vector MiniMaxH3MaterializeWeightNorm(const std::vector& g, // dac_alias_free_resample.py; LTX-2.5: audio_vae/vocoder.py:104-184). Declared in // minimax_h3.h and shared with ltx2_audio_vae.cpp — the pad/trim geometry below is // exactly the arithmetic that must not exist twice. -void MiniMaxH3AliasFreeActivation1d::Build() { - // Up and down use the same cutoff/half_width/kernel, so one window serves both. - filter = MiniMaxH3KaiserSincFilter1d(0.5 / static_cast(ratio), - 0.6 / static_cast(ratio), kernel_size); -} - -std::vector MiniMaxH3AliasFreeActivation1d::Apply(const std::vector& in, - int64_t channels, int64_t in_len, - const std::vector& alpha, - const std::vector* beta, - bool logscale, int64_t* out_len) const { - // --- UpSample1d --- - const int64_t pad = kernel_size / ratio - 1; - const int64_t pad_left = pad * ratio + (kernel_size - ratio) / 2; - const int64_t pad_right = pad * ratio + (kernel_size - ratio + 1) / 2; - int64_t padded_len = 0; - const std::vector padded = - MiniMaxH3Pad1d(in, channels, in_len, pad, pad, /*replicate=*/true, &padded_len); - // Depthwise transposed conv: filter.expand(C, -1, -1) => weight [C, 1, K]. - std::vector depthwise(static_cast(channels * kernel_size)); - for (int64_t c = 0; c < channels; ++c) { - for (int64_t k = 0; k < kernel_size; ++k) { - depthwise[static_cast(c * kernel_size + k)] = filter[static_cast(k)]; - } - } - int64_t up_len = 0; - std::vector up = - MiniMaxH3ConvTranspose1d(padded, channels, padded_len, depthwise, nullptr, channels, - kernel_size, ratio, /*padding=*/0, /*groups=*/channels, &up_len); - for (float& value : up) value *= static_cast(ratio); - // x[..., pad_left : -pad_right] - const int64_t trimmed_len = up_len - pad_left - pad_right; - VT_CHECK(trimmed_len > 0, "minimax_h3 audio vae: upsample trim emptied the signal"); - std::vector trimmed(static_cast(channels * trimmed_len)); - for (int64_t c = 0; c < channels; ++c) { - for (int64_t t = 0; t < trimmed_len; ++t) { - trimmed[static_cast(c * trimmed_len + t)] = - up[static_cast(c * up_len + pad_left + t)]; - } - } - - // --- Snake / SnakeBeta --- - MiniMaxH3SnakeActivation(trimmed, channels, trimmed_len, alpha, beta, logscale); - - // --- DownSample1d (LowPassFilter1d, stride = ratio, replicate padding) --- - const bool even = (kernel_size % 2) == 0; - const int64_t lp_left = kernel_size / 2 - (even ? 1 : 0); - const int64_t lp_right = kernel_size / 2; - int64_t lp_padded_len = 0; - const std::vector lp_padded = MiniMaxH3Pad1d(trimmed, channels, trimmed_len, lp_left, - lp_right, /*replicate=*/true, &lp_padded_len); - return MiniMaxH3Conv1d(lp_padded, channels, lp_padded_len, depthwise, nullptr, channels, - kernel_size, /*stride=*/ratio, /*dilation=*/1, /*groups=*/channels, - out_len); -} namespace { @@ -362,7 +106,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config "minimax_h3 audio vae: upsample rates/kernels length mismatch"); VT_CHECK(static_cast(config.resblock_dilation_sizes.size()) == num_kernels, "minimax_h3 audio vae: resblock kernels/dilations length mismatch"); - MiniMaxH3AliasFreeActivation1d act; + vocoder1d::AliasFreeActivation1d act; act.Build(); auto conv_weight = [&](const std::string& prefix, int64_t out_channels) { @@ -371,7 +115,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config out_channels); }; - // --- dec_in_proj: MiniMaxH3Conv1d(vae_latent_channels -> num_mels, k=1) --- + // --- dec_in_proj: vocoder1d::Conv1d(vae_latent_channels -> num_mels, k=1) --- // DacAudioVAE.decode applies this BEFORE BigVGAN (dac_audio_vae.py:218-231). It // is absent when the caller already supplies a num_mels-wide tensor, which is // what the standalone BigVGAN gate does. @@ -387,7 +131,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config int64_t projected_len = 0; const std::vector* bias = weights.Has("dec_in_proj.bias") ? &weights.Get("dec_in_proj.bias") : nullptr; - mels = MiniMaxH3Conv1d(latent, in_channels, frames, w, bias, config.num_mels, 1, 1, 1, 1, &projected_len); + mels = vocoder1d::Conv1d(latent, in_channels, frames, w, bias, config.num_mels, 1, 1, 1, 1, &projected_len); VT_CHECK(projected_len == frames, "minimax_h3 audio vae: dec_in_proj changed the length"); mel_source = mels.data(); } else { @@ -395,7 +139,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config "minimax_h3 audio vae: latent size does not match [num_mels, frames]"); } - // --- conv_pre: MiniMaxH3Conv1d(num_mels -> upsample_initial_channel, k=7, padding=3) --- + // --- conv_pre: vocoder1d::Conv1d(num_mels -> upsample_initial_channel, k=7, padding=3) --- int64_t channels = config.upsample_initial_channel; int64_t length = 0; std::vector x; @@ -409,7 +153,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config const int64_t padded_len = frames + 6; // zero padding 3 on each side const std::vector w = conv_weight("conv_pre", channels); const std::vector& b = weights.Get("conv_pre.bias"); - x = MiniMaxH3Conv1d(padded, config.num_mels, padded_len, w, &b, channels, 7, 1, 1, 1, &length); + x = vocoder1d::Conv1d(padded, config.num_mels, padded_len, w, &b, channels, 7, 1, 1, 1, &length); } // --- upsample stages --- @@ -422,7 +166,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config const std::vector w = conv_weight(prefix, channels); const std::vector& b = weights.Get(prefix + ".bias"); int64_t up_len = 0; - x = MiniMaxH3ConvTranspose1d(x, channels, length, w, &b, out_channels, kernel, rate, + x = vocoder1d::ConvTranspose1d(x, channels, length, w, &b, out_channels, kernel, rate, /*padding=*/(kernel - rate) / 2, /*groups=*/1, &up_len); channels = out_channels; length = up_len; @@ -457,7 +201,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config } padded_len = length + 2 * pad1; int64_t conv_len = 0; - xt = MiniMaxH3Conv1d(padded, channels, padded_len, conv_weight(c1, channels), + xt = vocoder1d::Conv1d(padded, channels, padded_len, conv_weight(c1, channels), &weights.Get(c1 + ".bias"), channels, kernel_size, 1, dilation, 1, &conv_len); @@ -472,7 +216,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config } } int64_t conv2_len = 0; - xt = MiniMaxH3Conv1d(padded2, channels, act_len + 2 * pad2, conv_weight(c2, channels), + xt = vocoder1d::Conv1d(padded2, channels, act_len + 2 * pad2, conv_weight(c2, channels), &weights.Get(c2 + ".bias"), channels, kernel_size, 1, 1, 1, &conv2_len); VT_CHECK(conv2_len == length, "minimax_h3 audio vae: resblock changed the sequence length"); @@ -508,7 +252,7 @@ std::vector MiniMaxH3AudioVaeDecode(const MiniMaxH3AudioVaeConfig& config b = &bias_storage; } int64_t final_len = 0; - x = MiniMaxH3Conv1d(padded, channels, length + 2 * pad, w, b, 1, 7, 1, 1, 1, &final_len); + x = vocoder1d::Conv1d(padded, channels, length + 2 * pad, w, b, 1, 7, 1, 1, 1, &final_len); length = final_len; } // H3 sets use_tanh_at_final=false, so the output is CLAMPED, not squashed. @@ -581,7 +325,7 @@ std::vector WnConv1d(const MiniMaxH3AudioVaeWeights& weights, const std:: const std::vector& bias = weights.Get(prefix + ".bias"); int64_t padded_len = 0; const std::vector padded = PadZero(in, in_channels, in_len, padding, padding, &padded_len); - return MiniMaxH3Conv1d(padded, in_channels, padded_len, w, &bias, out_channels, kernel, stride, dilation, + return vocoder1d::Conv1d(padded, in_channels, padded_len, w, &bias, out_channels, kernel, stride, dilation, /*groups=*/1, out_len); } @@ -939,7 +683,7 @@ std::vector MiniMaxH3AudioVaeEncodeToLatent(const MiniMaxH3AudioVaeEncode width = projected; } - // mean_proj: a PLAIN MiniMaxH3Conv1d(attn_proj_dim -> vae_latent_channels, k=1); the VAE's + // mean_proj: a PLAIN vocoder1d::Conv1d(attn_proj_dim -> vae_latent_channels, k=1); the VAE's // distribution MEAN. `logs_proj` is never evaluated — a reference must be // deterministic, so nothing is sampled. const std::vector& w = weights.Get("mean_proj.weight"); @@ -947,7 +691,7 @@ std::vector MiniMaxH3AudioVaeEncodeToLatent(const MiniMaxH3AudioVaeEncode VT_CHECK(static_cast(w.size()) == config.vae_latent_channels * width, "minimax_h3 audio encoder: mean_proj is not [vae_latent_channels, attn_proj_dim, 1]"); int64_t out_len = 0; - std::vector mean = MiniMaxH3Conv1d(latent, width, frames, w, &b, config.vae_latent_channels, + std::vector mean = vocoder1d::Conv1d(latent, width, frames, w, &b, config.vae_latent_channels, /*kernel=*/1, /*stride=*/1, /*dilation=*/1, /*groups=*/1, &out_len); VT_CHECK(out_len == frames, "minimax_h3 audio encoder: mean_proj changed the frame count"); diff --git a/src/vllm/model_executor/models/minimax_h3_vae_loader.cpp b/src/vllm/model_executor/models/minimax_h3_vae_loader.cpp index 42e3e7a16..65eb78571 100644 --- a/src/vllm/model_executor/models/minimax_h3_vae_loader.cpp +++ b/src/vllm/model_executor/models/minimax_h3_vae_loader.cpp @@ -20,6 +20,7 @@ // future refactor made lookups lenient, a decoder reading zeros. The mapping is // therefore asserted against the real manifest in the test, not just exercised. #include "vllm/model_executor/models/minimax_h3.h" +#include "vllm/model_executor/models/vocoder1d.h" #include #include @@ -118,7 +119,7 @@ MiniMaxH3AudioVaeWeights LoadMiniMaxH3AudioVaeWeights(const SafetensorsFile& fil if (key.rfind("decoder.", 0) == 0) key = key.substr(std::strlen("decoder.")); // The kaiser-sinc anti-aliasing filters are COMPUTED at load - // (MiniMaxH3KaiserSincFilter1d), never read. + // (vocoder1d::KaiserSincFilter1d), never read. if (key.size() >= 7 && key.compare(key.size() - 7, 7, ".filter") == 0) continue; const StTensor& tensor = file.Get(name); diff --git a/src/vllm/model_executor/models/vocoder1d.cpp b/src/vllm/model_executor/models/vocoder1d.cpp new file mode 100644 index 000000000..445b839f8 --- /dev/null +++ b/src/vllm/model_executor/models/vocoder1d.cpp @@ -0,0 +1,279 @@ +// Definitions for the shared 1-D BigVGAN vocoder core. See vocoder1d.h. +#include "vllm/model_executor/models/vocoder1d.h" + +#include "vt/dtype.h" + +#include +#include +#include +#include +#include +#include + +namespace vllm { +namespace vocoder1d { + +namespace { + +// TU-private helpers for KaiserSincFilter1d, moved with their only caller. + +// Zeroth-order modified Bessel function of the first kind, matching the series +// torch.kaiser_window uses. +double BesselI0(double x) { + double sum = 1.0, term = 1.0; + const double half_x_sq = (x / 2.0) * (x / 2.0); + for (int k = 1; k < 64; ++k) { + term *= half_x_sq / (static_cast(k) * static_cast(k)); + sum += term; + if (term < sum * 1e-18) break; + } + return sum; +} + +double Sinc(double x) { + if (x == 0.0) return 1.0; + const double pix = std::numbers::pi_v * x; + return std::sin(pix) / pix; +} + +// torch.kaiser_window(n, periodic=false, beta). +std::vector KaiserWindow(int64_t length, double beta) { + std::vector window(static_cast(length)); + const double denom = BesselI0(beta); + // periodic=false => the window spans [0, length-1] inclusive. + const double n_minus_1 = static_cast(length - 1); + for (int64_t i = 0; i < length; ++i) { + const double ratio = (2.0 * static_cast(i) - n_minus_1) / n_minus_1; + window[static_cast(i)] = BesselI0(beta * std::sqrt(std::max(0.0, 1.0 - ratio * ratio))) / denom; + } + return window; +} + +} // namespace + +// --------------------------------------------------------------------------- +// The shared 1-D BigVGAN primitives (declared in vocoder1d.h). They were private +// to this translation unit until LTX-2.5's audio VAE — the same BigVGAN lineage — +// needed them and copied them instead; see the header for why one implementation +// gated by two suites beats two implementations each with its own green gate. +// --------------------------------------------------------------------------- + +// One 1-D convolution over [C_in, T] with dilation/stride/groups. +// Weight is [C_out, C_in/groups, K]; input is assumed ALREADY padded. +std::vector Conv1d(const std::vector& in, int64_t in_channels, + int64_t in_len, const std::vector& weight, + const std::vector* bias, int64_t out_channels, + int64_t kernel, int64_t stride, int64_t dilation, + int64_t groups, int64_t* out_len) { + const int64_t effective = dilation * (kernel - 1) + 1; + const int64_t length = (in_len - effective) / stride + 1; + VT_CHECK(length > 0, "minimax_h3 audio vae: conv1d output length is empty"); + const int64_t in_per_group = in_channels / groups; + const int64_t out_per_group = out_channels / groups; + std::vector out(static_cast(out_channels * length), 0.0f); + for (int64_t oc = 0; oc < out_channels; ++oc) { + const int64_t g = oc / out_per_group; + for (int64_t t = 0; t < length; ++t) { + double acc = bias != nullptr ? (*bias)[static_cast(oc)] : 0.0; + for (int64_t ic = 0; ic < in_per_group; ++ic) { + const int64_t src_c = g * in_per_group + ic; + for (int64_t k = 0; k < kernel; ++k) { + const int64_t pos = t * stride + k * dilation; + acc += static_cast(in[static_cast(src_c * in_len + pos)]) * + static_cast(weight[static_cast((oc * in_per_group + ic) * kernel + k)]); + } + } + out[static_cast(oc * length + t)] = static_cast(acc); + } + } + *out_len = length; + return out; +} + +// torch.nn.functional.conv_transpose1d over [C_in, T]. +// Weight is [C_in, C_out/groups, K]; output length = (T-1)*stride - 2*padding + K. +std::vector ConvTranspose1d(const std::vector& in, int64_t in_channels, + int64_t in_len, const std::vector& weight, + const std::vector* bias, int64_t out_channels, + int64_t kernel, int64_t stride, int64_t padding, + int64_t groups, int64_t* out_len) { + const int64_t full = (in_len - 1) * stride + kernel; + const int64_t length = full - 2 * padding; + VT_CHECK(length > 0, "minimax_h3 audio vae: conv_transpose1d output length is empty"); + const int64_t in_per_group = in_channels / groups; + const int64_t out_per_group = out_channels / groups; + std::vector acc(static_cast(out_channels * full), 0.0); + for (int64_t ic = 0; ic < in_channels; ++ic) { + const int64_t g = ic / in_per_group; + for (int64_t t = 0; t < in_len; ++t) { + const double value = in[static_cast(ic * in_len + t)]; + if (value == 0.0) continue; + for (int64_t oc = 0; oc < out_per_group; ++oc) { + const int64_t dst_c = g * out_per_group + oc; + for (int64_t k = 0; k < kernel; ++k) { + acc[static_cast(dst_c * full + t * stride + k)] += + value * static_cast(weight[static_cast((ic * out_per_group + oc) * kernel + k)]); + } + } + } + } + std::vector out(static_cast(out_channels * length)); + for (int64_t c = 0; c < out_channels; ++c) { + for (int64_t t = 0; t < length; ++t) { + double value = acc[static_cast(c * full + t + padding)]; + if (bias != nullptr) value += (*bias)[static_cast(c)]; + out[static_cast(c * length + t)] = static_cast(value); + } + } + *out_len = length; + return out; +} + +// F.pad along the time axis: mode="replicate", or the zero pad an ordinary +// nn.Conv1d `padding=` argument performs. +std::vector Pad1d(const std::vector& in, int64_t channels, int64_t in_len, + int64_t left, int64_t right, bool replicate, int64_t* out_len) { + const int64_t length = in_len + left + right; + std::vector out(static_cast(channels * length), 0.0f); + for (int64_t c = 0; c < channels; ++c) { + for (int64_t t = 0; t < length; ++t) { + int64_t src = t - left; + if (src < 0 || src >= in_len) { + if (!replicate) continue; // already zero + src = std::max(0, std::min(in_len - 1, src)); + } + out[static_cast(c * length + t)] = in[static_cast(c * in_len + src)]; + } + } + *out_len = length; + return out; +} + +// Snake / SnakeBeta: x + (b + kSnakeEps)^-1 * sin^2(a * x). A null `beta` +// is plain Snake, which reuses ALPHA as the reciprocal scale (LTX-2.5 +// vocoder.py:198); a non-null one is SnakeBeta (vocoder.py:221), which is what +// every MiniMax-H3 checkpoint carries. Both are exponentiated when the checkpoint +// stores them in log scale. +void SnakeActivation(std::vector& x, int64_t channels, int64_t length, + const std::vector& alpha, const std::vector* beta, + bool logscale) { + for (int64_t c = 0; c < channels; ++c) { + double a = alpha[static_cast(c)]; + double b = beta != nullptr ? (*beta)[static_cast(c)] : a; + if (logscale) { + a = std::exp(a); + b = std::exp(b); + } + const double inv_beta = 1.0 / (b + kSnakeEps); + for (int64_t t = 0; t < length; ++t) { + const double v = x[static_cast(c * length + t)]; + const double s = std::sin(a * v); + x[static_cast(c * length + t)] = static_cast(v + inv_beta * s * s); + } + } +} + +// kaiser_sinc_filter1d (dac_alias_free_filter.py:26-60). Returns [kernel_size]. +std::vector KaiserSincFilter1d(double cutoff, double half_width, + int64_t kernel_size) { + VT_CHECK(kernel_size > 0, "minimax_h3 audio vae: kernel_size must be positive"); + VT_CHECK(cutoff >= 0.0 && cutoff <= 0.5, "minimax_h3 audio vae: cutoff must be in [0, 0.5]"); + const bool even = (kernel_size % 2) == 0; + const int64_t half_size = kernel_size / 2; + + const double delta_f = 4.0 * half_width; + const double a = 2.285 * (static_cast(half_size) - 1.0) * + std::numbers::pi_v * delta_f + + 7.95; + double beta = 0.0; + if (a > 50.0) { + beta = 0.1102 * (a - 8.7); + } else if (a >= 21.0) { + beta = 0.5842 * std::pow(a - 21.0, 0.4) + 0.07886 * (a - 21.0); + } + const std::vector window = KaiserWindow(kernel_size, beta); + + std::vector time(static_cast(kernel_size)); + for (int64_t i = 0; i < kernel_size; ++i) { + time[static_cast(i)] = even ? (static_cast(-half_size + i) + 0.5) + : static_cast(i - half_size); + } + + std::vector filter(static_cast(kernel_size), 0.0); + if (cutoff == 0.0) { + return std::vector(static_cast(kernel_size), 0.0f); + } + double sum = 0.0; + for (int64_t i = 0; i < kernel_size; ++i) { + filter[static_cast(i)] = + 2.0 * cutoff * window[static_cast(i)] * Sinc(2.0 * cutoff * time[static_cast(i)]); + sum += filter[static_cast(i)]; + } + // Normalized to sum 1 so a constant input does not leak. + std::vector out(static_cast(kernel_size)); + for (int64_t i = 0; i < kernel_size; ++i) { + out[static_cast(i)] = static_cast(filter[static_cast(i)] / sum); + } + return out; +} + +// torch weight_norm: w = g * v / ||v||, norm over every dim except dim 0. + +void AliasFreeActivation1d::Build() { + // Up and down use the same cutoff/half_width/kernel, so one window serves both. + filter = KaiserSincFilter1d(0.5 / static_cast(ratio), + 0.6 / static_cast(ratio), kernel_size); +} + +std::vector AliasFreeActivation1d::Apply(const std::vector& in, + int64_t channels, int64_t in_len, + const std::vector& alpha, + const std::vector* beta, + bool logscale, int64_t* out_len) const { + // --- UpSample1d --- + const int64_t pad = kernel_size / ratio - 1; + const int64_t pad_left = pad * ratio + (kernel_size - ratio) / 2; + const int64_t pad_right = pad * ratio + (kernel_size - ratio + 1) / 2; + int64_t padded_len = 0; + const std::vector padded = + Pad1d(in, channels, in_len, pad, pad, /*replicate=*/true, &padded_len); + // Depthwise transposed conv: filter.expand(C, -1, -1) => weight [C, 1, K]. + std::vector depthwise(static_cast(channels * kernel_size)); + for (int64_t c = 0; c < channels; ++c) { + for (int64_t k = 0; k < kernel_size; ++k) { + depthwise[static_cast(c * kernel_size + k)] = filter[static_cast(k)]; + } + } + int64_t up_len = 0; + std::vector up = + ConvTranspose1d(padded, channels, padded_len, depthwise, nullptr, channels, + kernel_size, ratio, /*padding=*/0, /*groups=*/channels, &up_len); + for (float& value : up) value *= static_cast(ratio); + // x[..., pad_left : -pad_right] + const int64_t trimmed_len = up_len - pad_left - pad_right; + VT_CHECK(trimmed_len > 0, "minimax_h3 audio vae: upsample trim emptied the signal"); + std::vector trimmed(static_cast(channels * trimmed_len)); + for (int64_t c = 0; c < channels; ++c) { + for (int64_t t = 0; t < trimmed_len; ++t) { + trimmed[static_cast(c * trimmed_len + t)] = + up[static_cast(c * up_len + pad_left + t)]; + } + } + + // --- Snake / SnakeBeta --- + SnakeActivation(trimmed, channels, trimmed_len, alpha, beta, logscale); + + // --- DownSample1d (LowPassFilter1d, stride = ratio, replicate padding) --- + const bool even = (kernel_size % 2) == 0; + const int64_t lp_left = kernel_size / 2 - (even ? 1 : 0); + const int64_t lp_right = kernel_size / 2; + int64_t lp_padded_len = 0; + const std::vector lp_padded = Pad1d(trimmed, channels, trimmed_len, lp_left, + lp_right, /*replicate=*/true, &lp_padded_len); + return Conv1d(lp_padded, channels, lp_padded_len, depthwise, nullptr, channels, + kernel_size, /*stride=*/ratio, /*dilation=*/1, /*groups=*/channels, + out_len); +} + +} // namespace vocoder1d +} // namespace vllm diff --git a/src/vllm/multimodal/speech_engine.cpp b/src/vllm/multimodal/speech_engine.cpp new file mode 100644 index 000000000..3739a5b5d --- /dev/null +++ b/src/vllm/multimodal/speech_engine.cpp @@ -0,0 +1,88 @@ +// The speech engine seam. See speech_engine.h for why it mirrors VideoEngine. +#include "vllm/multimodal/speech_engine.h" + +#include +#include +#include + +namespace vllm { +namespace multimodal { + +SpeechEngine::~SpeechEngine() = default; + +void SpeechRegistry::Register(SpeechFamilyRegistration registration) { + if (registration.name.empty()) { + throw std::runtime_error("speech registry: a family must have a name"); + } + if (!registration.detect) { + throw std::runtime_error("speech registry: family '" + registration.name + + "' has no detector"); + } + if (!registration.load) { + throw std::runtime_error("speech registry: family '" + registration.name + "' has no loader"); + } + for (const SpeechFamilyRegistration& existing : families_) { + if (existing.name == registration.name) { + // Two claimants collapsing into one listed name is the never-guess + // guarantee defeated from the inside. + throw std::runtime_error("speech registry: family '" + registration.name + + "' is already registered"); + } + } + families_.push_back(std::move(registration)); +} + +std::vector SpeechRegistry::families() const { + std::vector out; + out.reserve(families_.size()); + for (const SpeechFamilyRegistration& f : families_) out.push_back(f.name); + return out; +} + +std::unique_ptr SpeechRegistry::Load(const SpeechModelParams& params, + std::string* why) const { + const auto say = [why](const std::string& message) { + if (why != nullptr) *why = message; + }; + + if (families_.empty()) { + // Distinguish "this lane does not exist yet" from "your checkpoint is bad". + // Reading the second when the first is true sends people to debug a file + // that was never the problem. + say("no speech (TTS) family is registered: the IndexTTS-2.5 stages are not " + "implemented yet (see .agents/specs/indextts-2-5.md, W3-W5)"); + return nullptr; + } + + std::string tried; + for (const SpeechFamilyRegistration& family : families_) { + if (!params.family.empty() && params.family != family.name) continue; + if (!tried.empty()) tried += ", "; + tried += family.name; + + bool claimed = false; + try { + claimed = family.detect(params); + } catch (const std::exception&) { + // A detector that throws is a no-match, never a crash: it runs over + // artifacts chosen by whoever repackaged the checkpoint. + claimed = false; + } + if (claimed) return family.load(params); + } + + if (!params.family.empty() && tried.empty()) { + say("speech: no family named '" + params.family + "' is registered"); + return nullptr; + } + say("speech: no registered family claims '" + params.path + "'; tried " + tried); + return nullptr; +} + +SpeechRegistry& GlobalSpeechRegistry() { + static SpeechRegistry registry; + return registry; +} + +} // namespace multimodal +} // namespace vllm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8619242f3..77400c6ab 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -109,6 +109,11 @@ vllm_cpp_add_test(test_gguf_qwen36_loader vllm/test_gguf_qwen36_loader.cpp) vllm_cpp_add_test(test_gguf_keep_quant vllm/test_gguf_keep_quant.cpp) vllm_cpp_add_test(test_model_loader_gguf vllm/test_model_loader_gguf.cpp) vllm_cpp_add_test(test_model_registry vllm/models/test_model_registry.cpp) +vllm_cpp_add_test(test_vocoder1d vllm/models/test_vocoder1d.cpp) +vllm_cpp_add_test(test_speech_engine vllm/multimodal/test_speech_engine.cpp) +vllm_cpp_add_test(test_indextts2_family vllm/models/test_indextts2_family.cpp) +vllm_cpp_add_test(test_gpt2 vllm/models/test_gpt2.cpp) +target_include_directories(test_gpt2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) vllm_cpp_add_test(test_minimax_h3 vllm/models/test_minimax_h3.cpp) # ARCH-ONE-SURFACE ROW 2 fold gate: the video seam == the replicated pre-fold # pipeline == the goldens the PRE-fold minimax-h3-gen binary rendered at the diff --git a/tests/scripts/test_vocoder1d_single_home.py b/tests/scripts/test_vocoder1d_single_home.py new file mode 100644 index 000000000..6d5a1e566 --- /dev/null +++ b/tests/scripts/test_vocoder1d_single_home.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +"""The 1-D vocoder core has exactly ONE home, and it is not a model's header. + +`ltx2_audio_vae.cpp` records why this matters: LTX-2.5 deliberately did not copy +MiniMax-H3's alias-free primitives, because "a second copy of the alias-free trim +geometry is the duplicate that goes wrong quietly -- each copy keeps its own green +gate while the two audio VAEs drift apart." IndexTTS-2.5 is the third consumer. + +No numeric test can see that failure: a fork passes every tensor comparison on +both sides on the day it is made, and only drifts later. So the invariant is +asserted structurally here -- one declaration site, one definition site, and no +consumer reaching for a model-specific spelling. +""" + +import re +import subprocess +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] + +HEADER = ROOT / "include/vllm/model_executor/models/vocoder1d.h" +SOURCE = ROOT / "src/vllm/model_executor/models/vocoder1d.cpp" +H3_HEADER = ROOT / "include/vllm/model_executor/models/minimax_h3.h" + +# The core: every symbol both audio VAEs share. +SYMBOLS = ( + "Conv1d", + "ConvTranspose1d", + "Pad1d", + "SnakeActivation", + "AliasFreeActivation1d", + "KaiserSincFilter1d", +) + +# The spellings the move retires. A reappearance means someone re-forked, or +# re-anchored the shared core back onto one model's name. +RETIRED = tuple("MiniMaxH3" + name for name in SYMBOLS) + ("kMiniMaxH3SnakeEps",) + + +def tracked_sources() -> list[Path]: + out = subprocess.run( + ["git", "ls-files", "src", "include", "tests", "examples"], + cwd=ROOT, + capture_output=True, + text=True, + check=True, + ).stdout.split() + return [ROOT / p for p in out if p.endswith((".h", ".cpp", ".cu", ".hip"))] + + +class Vocoder1dSingleHomeTests(unittest.TestCase): + def test_the_neutral_header_declares_every_shared_symbol(self) -> None: + self.assertTrue(HEADER.is_file(), f"{HEADER} must exist") + text = HEADER.read_text(encoding="utf-8") + self.assertIn("namespace vocoder1d", text) + for name in SYMBOLS: + self.assertIn(name, text, f"{name} must be declared in the neutral header") + self.assertIn("kSnakeEps", text) + + def test_the_definitions_live_beside_the_declarations(self) -> None: + self.assertTrue(SOURCE.is_file(), f"{SOURCE} must exist") + text = SOURCE.read_text(encoding="utf-8") + self.assertIn("namespace vocoder1d", text) + + def test_the_model_header_no_longer_owns_the_core(self) -> None: + """A model's header is not a home for something three lanes share.""" + text = H3_HEADER.read_text(encoding="utf-8") + for name in RETIRED: + self.assertNotIn( + name, + text, + f"{name} still lives in minimax_h3.h; the core moved to vocoder1d.h", + ) + + def test_no_tracked_source_uses_a_retired_model_specific_spelling(self) -> None: + offenders: list[str] = [] + for path in tracked_sources(): + if path.name.startswith("vocoder1d"): + continue + text = path.read_text(encoding="utf-8", errors="replace") + for name in RETIRED: + if re.search(rf"\b{re.escape(name)}\b", text): + offenders.append(f"{path.relative_to(ROOT)}: {name}") + self.assertEqual(offenders, [], "retired spellings survive: " + "; ".join(offenders)) + + def test_exactly_one_definition_of_each_shared_symbol(self) -> None: + """Two definitions is the fork this guard exists to catch.""" + pattern = re.compile( + r"^(?:std::vector|void|double)\s+(?:vocoder1d::)?" + r"(Conv1d|ConvTranspose1d|Pad1d|SnakeActivation|KaiserSincFilter1d)\s*\(", + re.M, + ) + counts: dict[str, int] = {name: 0 for name in pattern.pattern and SYMBOLS} + for path in tracked_sources(): + if path.suffix != ".cpp": + continue + for match in pattern.finditer(path.read_text(encoding="utf-8", errors="replace")): + counts[match.group(1)] += 1 + for name, count in counts.items(): + if name == "AliasFreeActivation1d": + continue + self.assertEqual(count, 1, f"{name} has {count} definitions; exactly one is allowed") + + +if __name__ == "__main__": + unittest.main(verbosity=2 if "-v" in sys.argv else 1) diff --git a/tests/vllm/models/gpt2_goldens.inc b/tests/vllm/models/gpt2_goldens.inc new file mode 100644 index 000000000..30bb6f26a --- /dev/null +++ b/tests/vllm/models/gpt2_goldens.inc @@ -0,0 +1,91 @@ +// GENERATED by scripts/gen-gpt2-goldens.py -- do not edit. +// +// GPT-2 backbone goldens: a TP=1 restatement of vLLM's +// vllm/model_executor/models/gpt2.py @ 555967922 (the parity pin), +// executed at reduced dimensions on CPU. Weights are rebuilt on both +// sides from the same FNV-1a -> splitmix64 stream, so no weight byte is +// checked in. Conv1D weights are emitted in UPSTREAM [in, out] +// orientation: the loader must transpose them, and a port that does not +// still runs while producing the wrong tokens. +#pragma once + +#include + +namespace gpt2_goldens { + +inline constexpr int64_t kVocab = 33; +inline constexpr int64_t kPositions = 16; +inline constexpr int64_t kHidden = 8; +inline constexpr int64_t kLayers = 2; +inline constexpr int64_t kHeads = 2; +inline constexpr int64_t kInner = 16; +inline constexpr double kLayerNormEps = 1e-05; + +inline constexpr int64_t kInputIds[] = { + 3, 17, 0, 31, 8, 8, 12, +}; + +inline constexpr int64_t kPositions0[] = { + 0, 1, 2, 3, 4, 5, 6, +}; + +inline constexpr float kHiddenStates[] = { + -6.758919954e-01F, -1.563158780e-01F, -6.724408865e-01F, 5.617963076e-01F, 6.348996162e-01F, -1.034704328e+00F, + 2.132793665e+00F, -9.813426733e-01F, -9.695178866e-01F, -2.413581461e-01F, 2.211368382e-01F, 1.802160859e+00F, + -2.662685513e-01F, -8.393397927e-01F, 1.127580881e+00F, -1.150931478e+00F, -8.719039559e-01F, -6.009030938e-01F, + 2.439662367e-01F, 1.130790830e+00F, 1.882359982e-01F, -8.141697049e-01F, 1.775224447e+00F, -1.366588116e+00F, + -4.486901164e-01F, -1.123752952e+00F, -2.738695443e-01F, 9.832941294e-01F, -3.175678253e-01F, -1.047375083e+00F, + 2.117383242e+00F, -1.114867777e-01F, -8.835280538e-01F, 3.065048158e-01F, -1.633155704e+00F, 1.135896087e+00F, + 3.427859843e-01F, 1.570451260e-01F, 1.362044215e+00F, -8.771175146e-01F, -1.058530331e+00F, 1.035283208e+00F, + -1.017389774e+00F, 1.935140133e+00F, -2.707275003e-02F, -1.332954317e-01F, -4.182530642e-01F, -4.608068466e-01F, + 7.295622826e-01F, -2.380384922e+00F, -6.079788506e-02F, 5.133523345e-01F, 7.862060666e-01F, -8.430351615e-01F, + 1.772120446e-01F, 8.087040186e-01F, +}; + +inline constexpr float kLogits[] = { + 6.327223182e-01F, -1.076550126e+00F, 7.914394736e-01F, 1.839220643e+00F, -1.636679173e+00F, 4.613402188e-01F, + -1.152832031e+00F, -2.643049359e-01F, -5.349091887e-01F, -3.746800423e-01F, -1.864831150e-02F, 2.765813656e-02F, + -5.321614742e-01F, 1.182386398e+00F, -1.421773195e+00F, 7.513378263e-01F, 5.502961278e-01F, 3.250021338e-01F, + 7.291159630e-01F, -9.061471224e-01F, 1.517630816e+00F, 3.498831689e-01F, 1.973308623e-01F, -2.792831883e-02F, + -7.242191434e-01F, 1.677690744e-01F, 2.033040822e-01F, 3.402658403e-01F, -6.670000404e-02F, -2.820742130e-01F, + -5.457843542e-01F, 1.274694681e+00F, 7.620344758e-01F, 7.984229922e-01F, -1.402860761e+00F, 1.279740810e+00F, + 1.222942948e+00F, -1.668448925e+00F, -3.781856298e-01F, -1.136785299e-01F, -2.809134126e-01F, 3.013675287e-02F, + -6.562127471e-01F, -1.669737101e-01F, 2.141165137e-01F, -7.849505544e-01F, 8.159685135e-01F, -3.838966191e-01F, + 3.903498054e-01F, 6.000024676e-01F, 6.459107399e-01F, 1.249702454e+00F, -5.600686073e-01F, 9.716996551e-01F, + 3.872746825e-01F, 9.208218753e-02F, -1.927974820e-01F, -3.743252754e-01F, -1.727477014e-01F, 8.185905814e-01F, + -2.799971104e-01F, 1.009188056e+00F, 3.488124907e-01F, 2.768089771e-01F, 1.051468968e+00F, 1.096209168e+00F, + 6.587898731e-01F, -1.188513160e+00F, 8.122190833e-01F, 1.329302430e+00F, -1.566394091e+00F, -6.485868990e-02F, + -6.723068953e-01F, 7.551644742e-02F, -3.227161467e-01F, -5.252106190e-01F, -2.810249627e-01F, 2.393045872e-01F, + -8.549718261e-01F, 1.172486424e+00F, -6.393569112e-01F, 6.154721975e-01F, 4.442424178e-01F, 7.254419327e-01F, + 1.262800217e+00F, -5.040304065e-01F, 1.128740191e+00F, 3.764043748e-01F, 5.362797976e-01F, -2.368482500e-01F, + -2.518990040e-01F, -9.356956184e-02F, 3.374186754e-01F, 2.318107933e-01F, 5.804494023e-01F, 2.189453691e-01F, + -3.558536768e-01F, 1.222767591e+00F, 1.208353281e+00F, 7.201600075e-02F, -6.462588310e-01F, 5.316134095e-01F, + 8.877359629e-01F, -1.556266308e+00F, -7.816831023e-02F, -1.180930495e+00F, 5.334194899e-01F, -4.552132487e-01F, + -2.674963474e-01F, 4.823009074e-01F, 3.405889496e-02F, 9.007601440e-02F, 1.502164006e+00F, -9.794121385e-01F, + 5.607809424e-01F, 4.261497557e-01F, 2.864490962e-03F, 5.328261256e-01F, -9.738094807e-01F, 1.229519844e+00F, + 1.209003210e+00F, 5.173375830e-02F, 3.624272943e-01F, -2.780126035e-01F, -1.338052899e-01F, 8.330433071e-02F, + 5.209188908e-02F, -4.234863222e-01F, 4.906530976e-01F, -1.077257991e+00F, 1.834574699e+00F, 1.489409924e+00F, + 8.854604959e-01F, -2.101440430e+00F, 1.377321601e+00F, 1.652816653e+00F, -1.648378611e+00F, 7.427574396e-01F, + 5.771225691e-02F, -4.983791113e-01F, -1.590702534e-01F, -9.747824073e-02F, 3.038123250e-01F, -6.982609630e-02F, + -1.025648832e+00F, 6.036202908e-01F, -1.125904679e+00F, 2.767229080e-01F, 1.160629988e+00F, 2.478124648e-01F, + 6.738340259e-01F, -7.440980673e-01F, 1.401547313e+00F, -4.551619887e-01F, -3.224387765e-02F, 6.938266754e-02F, + -9.547463059e-01F, 1.573506743e-01F, -1.952986121e-01F, -7.093986273e-01F, 3.983899355e-01F, -3.377259672e-01F, + 7.821530700e-01F, 5.183002949e-01F, 8.617430925e-03F, 1.121782303e+00F, -1.798915505e+00F, 1.786674976e+00F, + 1.107441902e+00F, -1.160478354e+00F, -3.262609243e-02F, 9.505915046e-01F, -1.248005152e+00F, 5.271589756e-01F, + -4.298262000e-01F, 1.981309354e-01F, 1.280394197e-01F, -6.989376545e-01F, -3.189443946e-01F, -1.434016824e-01F, + -1.291949749e-01F, 9.188229442e-01F, 2.094301283e-01F, 4.791071415e-01F, -6.614661217e-01F, 5.551105142e-01F, + -5.299359560e-01F, -7.801373005e-01F, -1.854672730e-01F, -1.150613308e+00F, -2.100667804e-01F, 1.159075975e+00F, + -1.437965870e+00F, 1.275697351e+00F, -2.298414558e-01F, 1.657693982e+00F, -3.228655457e-01F, -4.052702785e-01F, + -5.900129080e-01F, 1.356296778e+00F, -9.971211553e-01F, -1.227285385e+00F, 6.910169125e-01F, 1.235000193e-01F, + -1.074662447e+00F, 1.398223639e+00F, -6.294883490e-01F, -2.945438027e-01F, 1.376653671e+00F, 6.915906072e-01F, + 1.471363783e+00F, 2.661502957e-01F, 7.486875653e-01F, 1.348164678e-02F, -3.476230204e-01F, -6.799117923e-01F, + -1.173442125e+00F, -9.591936469e-01F, 6.586188078e-02F, 1.197048426e+00F, -1.209189594e-01F, -3.468989134e-01F, + 4.431295991e-01F, -8.946682215e-01F, -3.895069659e-01F, -3.359264731e-01F, -1.349938631e+00F, -3.279751241e-01F, + -2.236937284e+00F, 9.770478010e-01F, 1.244192600e+00F, +}; + +inline constexpr int64_t kArgmax[] = { + 3, 2, 3, 31, 3, 2, 12, +}; + +} // namespace gpt2_goldens diff --git a/tests/vllm/models/test_gpt2.cpp b/tests/vllm/models/test_gpt2.cpp new file mode 100644 index 000000000..051bf5e96 --- /dev/null +++ b/tests/vllm/models/test_gpt2.cpp @@ -0,0 +1,206 @@ +// GPT-2 backbone parity gate — IndexTTS-2.5's stage-0 talker (W2, #634). +// +// Compared against a TP=1 restatement of vLLM's +// vllm/model_executor/models/gpt2.py @ 555967922 (the parity pin), executed at +// reduced dimensions by scripts/gen-gpt2-goldens.py. Both sides rebuild every +// weight from ONE deterministic FNV-1a -> splitmix64 stream, so no weight byte +// is checked in. +// +// The goldens carry Conv1D weights in UPSTREAM [in, out] orientation, which is +// how a real checkpoint stores them. A port that skips the transpose +// (gpt2.py:242-254 `_transpose_conv1d`) still runs and still emits plausible +// tokens, so the transpose is gated by a case of its own rather than left to be +// implied by the forward. +#include +#include +#include +#include + +#include "doctest/doctest.h" +#include "gpt2_goldens.inc" +#include "vllm/model_executor/models/gpt2.h" + +namespace { + +// The generator's stream, byte-for-byte: values uniform in [-1, 1) derived from +// the tensor NAME alone. Deliberately a local copy — if this drifts from the +// generator the goldens stop matching, which is the failure we want, not a +// shared helper that could drift on both sides at once. +std::vector Rand(const std::string& name, int64_t count, double scale) { + uint64_t seed = 0xCBF29CE484222325ULL; + for (const char c : name) { + seed ^= static_cast(c); + seed *= 0x100000001B3ULL; + } + std::vector out(static_cast(count)); + for (int64_t i = 0; i < count; ++i) { + uint64_t x = seed + static_cast(i); + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + z ^= z >> 31; + const double u = static_cast(z >> 11) * 0x1.0p-53; + out[static_cast(i)] = static_cast((u * 2.0 - 1.0) * scale); + } + return out; +} + +std::vector RandPlusOne(const std::string& name, int64_t count, double scale) { + std::vector v = Rand(name, count, scale); + for (float& x : v) x += 1.0F; + return v; +} + +vllm::gpt2::Params GoldenParams() { + vllm::gpt2::Params p; + p.vocab_size = gpt2_goldens::kVocab; + p.max_position_embeddings = gpt2_goldens::kPositions; + p.hidden_size = gpt2_goldens::kHidden; + p.num_hidden_layers = gpt2_goldens::kLayers; + p.num_attention_heads = gpt2_goldens::kHeads; + p.inner_size = gpt2_goldens::kInner; + p.layer_norm_eps = gpt2_goldens::kLayerNormEps; + return p; +} + +// Rebuild the checkpoint in UPSTREAM orientation: Conv1D 2D weights are +// [in, out]. `Load` is what must transpose them. +vllm::gpt2::CheckpointTensors GoldenCheckpoint(const vllm::gpt2::Params& p) { + vllm::gpt2::CheckpointTensors t; + const int64_t h = p.hidden_size; + t.Set("wte.weight", {p.vocab_size, h}, Rand("wte.weight", p.vocab_size * h, 0.5)); + t.Set("wpe.weight", {p.max_position_embeddings, h}, + Rand("wpe.weight", p.max_position_embeddings * h, 0.25)); + t.Set("ln_f.weight", {h}, RandPlusOne("ln_f.weight", h, 0.1)); + t.Set("ln_f.bias", {h}, Rand("ln_f.bias", h, 0.1)); + for (int64_t i = 0; i < p.num_hidden_layers; ++i) { + const std::string b = "h." + std::to_string(i) + "."; + t.Set(b + "ln_1.weight", {h}, RandPlusOne(b + "ln_1.weight", h, 0.1)); + t.Set(b + "ln_1.bias", {h}, Rand(b + "ln_1.bias", h, 0.1)); + t.Set(b + "ln_2.weight", {h}, RandPlusOne(b + "ln_2.weight", h, 0.1)); + t.Set(b + "ln_2.bias", {h}, Rand(b + "ln_2.bias", h, 0.1)); + t.Set(b + "attn.c_attn.weight", {h, 3 * h}, Rand(b + "attn.c_attn.weight", h * 3 * h, 0.3)); + t.Set(b + "attn.c_attn.bias", {3 * h}, Rand(b + "attn.c_attn.bias", 3 * h, 0.2)); + t.Set(b + "attn.c_proj.weight", {h, h}, Rand(b + "attn.c_proj.weight", h * h, 0.3)); + t.Set(b + "attn.c_proj.bias", {h}, Rand(b + "attn.c_proj.bias", h, 0.2)); + t.Set(b + "mlp.c_fc.weight", {h, p.inner_size}, Rand(b + "mlp.c_fc.weight", h * p.inner_size, 0.3)); + t.Set(b + "mlp.c_fc.bias", {p.inner_size}, Rand(b + "mlp.c_fc.bias", p.inner_size, 0.2)); + t.Set(b + "mlp.c_proj.weight", {p.inner_size, h}, + Rand(b + "mlp.c_proj.weight", p.inner_size * h, 0.3)); + t.Set(b + "mlp.c_proj.bias", {h}, Rand(b + "mlp.c_proj.bias", h, 0.2)); + } + return t; +} + +std::vector GoldenIds() { + return std::vector(std::begin(gpt2_goldens::kInputIds), + std::end(gpt2_goldens::kInputIds)); +} + +std::vector GoldenPositions() { + return std::vector(std::begin(gpt2_goldens::kPositions0), + std::end(gpt2_goldens::kPositions0)); +} + +double MaxAbsDiff(const std::vector& got, const float* want, size_t n) { + double worst = 0.0; + for (size_t i = 0; i < n; ++i) { + worst = std::max(worst, std::fabs(static_cast(got[i]) - static_cast(want[i]))); + } + return worst; +} + +} // namespace + +TEST_CASE("gpt2 forward reproduces the upstream hidden states") { + const vllm::gpt2::Params p = GoldenParams(); + const vllm::gpt2::Weights w = vllm::gpt2::Load(p, GoldenCheckpoint(p)); + const std::vector hidden = vllm::gpt2::ForwardHost(p, w, GoldenIds(), GoldenPositions()); + + const size_t n = GoldenIds().size() * static_cast(p.hidden_size); + REQUIRE(hidden.size() == n); + const double worst = MaxAbsDiff(hidden, gpt2_goldens::kHiddenStates, n); + INFO("max abs diff vs upstream hidden states: ", worst); + CHECK(worst < 2e-5); +} + +TEST_CASE("gpt2 logits reproduce upstream, and the argmax is token-exact") { + const vllm::gpt2::Params p = GoldenParams(); + const vllm::gpt2::Weights w = vllm::gpt2::Load(p, GoldenCheckpoint(p)); + const std::vector hidden = vllm::gpt2::ForwardHost(p, w, GoldenIds(), GoldenPositions()); + const std::vector logits = vllm::gpt2::LogitsHost(p, w, hidden); + + const size_t seq = GoldenIds().size(); + const size_t n = seq * static_cast(p.vocab_size); + REQUIRE(logits.size() == n); + const double worst = MaxAbsDiff(logits, gpt2_goldens::kLogits, n); + INFO("max abs diff vs upstream logits: ", worst); + CHECK(worst < 5e-5); + + // Token-exactness is the property the talker actually needs: a small logit + // drift that never changes an argmax is harmless, one that does is a + // different utterance. + for (size_t t = 0; t < seq; ++t) { + int64_t best = 0; + float best_v = logits[t * static_cast(p.vocab_size)]; + for (int64_t v = 1; v < p.vocab_size; ++v) { + const float cur = logits[t * static_cast(p.vocab_size) + static_cast(v)]; + if (cur > best_v) { + best_v = cur; + best = v; + } + } + CHECK(best == gpt2_goldens::kArgmax[t]); + } +} + +TEST_CASE("gpt2 Load transposes the Conv1D weights") { + // gpt2.py:242-254. HF stores c_attn/c_proj/c_fc as Conv1D: the 2D weight is + // [in, out], while the matmul wants [out, in]. Skipping the transpose yields a + // model that runs and is wrong, so the orientation is asserted directly rather + // than inferred from the forward. + const vllm::gpt2::Params p = GoldenParams(); + const vllm::gpt2::CheckpointTensors ckpt = GoldenCheckpoint(p); + const vllm::gpt2::Weights w = vllm::gpt2::Load(p, ckpt); + + const std::vector& raw = ckpt.Get("h.0.attn.c_attn.weight"); + const std::vector& loaded = w.layers[0].c_attn_weight; + REQUIRE(loaded.size() == raw.size()); + + const int64_t in_dim = p.hidden_size; + const int64_t out_dim = 3 * p.hidden_size; + for (int64_t i = 0; i < in_dim; ++i) { + for (int64_t o = 0; o < out_dim; ++o) { + // raw is [in, out]; loaded must be [out, in]. + CHECK(loaded[static_cast(o * in_dim + i)] == + raw[static_cast(i * out_dim + o)]); + } + } +} + +TEST_CASE("gpt2 attention is causal") { + // A change to the LAST token must not move any earlier position. Without the + // causal mask every position sees the future, which still produces fluent + // output and is the wrong model. + const vllm::gpt2::Params p = GoldenParams(); + const vllm::gpt2::Weights w = vllm::gpt2::Load(p, GoldenCheckpoint(p)); + + std::vector ids = GoldenIds(); + const std::vector positions = GoldenPositions(); + const std::vector base = vllm::gpt2::ForwardHost(p, w, ids, positions); + + ids.back() = (ids.back() + 5) % p.vocab_size; + const std::vector perturbed = vllm::gpt2::ForwardHost(p, w, ids, positions); + + const size_t hidden = static_cast(p.hidden_size); + const size_t prefix = (ids.size() - 1) * hidden; + for (size_t i = 0; i < prefix; ++i) { + CHECK(base[i] == perturbed[i]); + } + double moved = 0.0; + for (size_t i = prefix; i < base.size(); ++i) { + moved = std::max(moved, std::fabs(static_cast(base[i] - perturbed[i]))); + } + CHECK(moved > 1e-6); // the last position MUST move, or the test proves nothing +} diff --git a/tests/vllm/models/test_indextts2_family.cpp b/tests/vllm/models/test_indextts2_family.cpp new file mode 100644 index 000000000..725140cb0 --- /dev/null +++ b/tests/vllm/models/test_indextts2_family.cpp @@ -0,0 +1,89 @@ +// IndexTTS-2.5 speech-family registration (#634). +// +// The stages are not implemented (W3-W5), so this gate covers what IS true +// today: the family DETECTS its own checkpoint, and loading refuses while naming +// the exact pieces that are missing. An arm that is silently absent is the +// failure this project has already recorded; an arm that refuses by name is +// visible debt. +#include +#include +#include +#include +#include + +#include "doctest/doctest.h" +#include "vllm/model_executor/models/indextts2.h" +#include "vllm/multimodal/speech_engine.h" + +namespace { + +std::string MakeCheckpoint(const std::string& leaf, const std::string& config_body) { + const std::filesystem::path dir = + std::filesystem::temp_directory_path() / ("indextts2_gate_" + leaf); + std::filesystem::create_directories(dir); + std::ofstream(dir / "config.yaml") << config_body; + return dir.string(); +} + +} // namespace + +TEST_CASE("indextts2 detects its own checkpoint by INSPECTING config.yaml") { + vllm::multimodal::SpeechRegistry registry; + vllm::models::RegisterIndexTts2SpeechFamily(registry); + + vllm::multimodal::SpeechModelParams params; + params.path = MakeCheckpoint("real", + "gpt:\n model_dim: 1280\n" + "s2mel_checkpoint: s2mel.pth\n" + "semantic_codec:\n codebook_size: 8192\n"); + std::string why; + // Detection must SUCCEED and the load must then refuse for a reason about the + // PORT, not about the file. + CHECK_THROWS_WITH_AS(registry.Load(params, &why), + doctest::Contains("not implemented"), std::runtime_error); +} + +TEST_CASE("indextts2 refusal names every missing stage and its issue") { + vllm::multimodal::SpeechRegistry registry; + vllm::models::RegisterIndexTts2SpeechFamily(registry); + vllm::multimodal::SpeechModelParams params; + params.path = MakeCheckpoint("named", + "gpt:\n model_dim: 1280\n" + "s2mel_checkpoint: s2mel.pth\n" + "semantic_codec:\n codebook_size: 8192\n"); + std::string why; + try { + registry.Load(params, &why); + FAIL("load must refuse while the stages are unimplemented"); + } catch (const std::runtime_error& e) { + const std::string msg = e.what(); + // Naming the pieces is the whole point: "unsupported" sends the next person + // reading loader source. + CHECK(msg.find("w2v-bert") != std::string::npos); + CHECK(msg.find("EnhancedCodec") != std::string::npos); + CHECK(msg.find("S2Mel") != std::string::npos); + CHECK(msg.find("#634") != std::string::npos); + } +} + +TEST_CASE("indextts2 does not claim a checkpoint that is not its own") { + vllm::multimodal::SpeechRegistry registry; + vllm::models::RegisterIndexTts2SpeechFamily(registry); + + vllm::multimodal::SpeechModelParams params; + params.path = MakeCheckpoint("other", "architectures:\n - SomethingElse\n"); + std::string why; + CHECK(registry.Load(params, &why) == nullptr); + CHECK(why.find("indextts2") != std::string::npos); +} + +TEST_CASE("indextts2 detector does not throw on a missing directory") { + // Detectors run over artifacts chosen by whoever repackaged a checkpoint; an + // unreadable one is a no-match, never a crash. + vllm::multimodal::SpeechRegistry registry; + vllm::models::RegisterIndexTts2SpeechFamily(registry); + vllm::multimodal::SpeechModelParams params; + params.path = "/definitely/not/here"; + std::string why; + CHECK_NOTHROW(registry.Load(params, &why)); +} diff --git a/tests/vllm/models/test_ltx2_vae.cpp b/tests/vllm/models/test_ltx2_vae.cpp index 1667f58d3..4bc035b25 100644 --- a/tests/vllm/models/test_ltx2_vae.cpp +++ b/tests/vllm/models/test_ltx2_vae.cpp @@ -28,9 +28,10 @@ #include "vllm/model_executor/models/ltx2_conditioning.h" #include "vllm/model_executor/models/ltx2_video_vae.h" #include "vllm/model_executor/models/ltx2_video_vae_encoder.h" -// kMiniMaxH3SnakeEps: the Snake/SnakeBeta stabilizer is SHARED with MiniMax-H3's +// vocoder1d::kSnakeEps: the Snake/SnakeBeta stabilizer is SHARED with MiniMax-H3's // BigVGAN, so the constant this suite pins lives in that header. #include "vllm/model_executor/models/minimax_h3.h" +#include "vllm/model_executor/models/vocoder1d.h" #include "ltx2_vae_goldens.inc" @@ -1282,7 +1283,7 @@ TEST_CASE("ltx2 vae: every stabilizing epsilon is pinned to its upstream line") // stabilizer in `1 / (beta + eps)`. Shared with MiniMax-H3's BigVGAN, which is // why it lives in minimax_h3.h. Mutation: 1e-9 -> 0.0 left every golden green, // because beta is O(1) here and never approaches zero. - CHECK(vllm::kMiniMaxH3SnakeEps == doctest::Approx(1e-9).epsilon(1e-12).scale(0.0)); + CHECK(vllm::vocoder1d::kSnakeEps == doctest::Approx(1e-9).epsilon(1e-12).scale(0.0)); // `_RMSNorm2D` is `F.normalize(x, dim=1) * (sqrt(C) * gamma)` // (video_vae/attention.py:11-30), so the floor is torch's `F.normalize` DEFAULT diff --git a/tests/vllm/models/test_minimax_h3.cpp b/tests/vllm/models/test_minimax_h3.cpp index 949a7c177..0e54a5b07 100644 --- a/tests/vllm/models/test_minimax_h3.cpp +++ b/tests/vllm/models/test_minimax_h3.cpp @@ -12,6 +12,7 @@ // .agents/specs/minimax-h3.md sections 0 and 4. #include "vllm/model_executor/models/dense_nvfp4_gemm.h" // W-FP4a: W4A16 exec stats #include "vllm/model_executor/models/minimax_h3.h" +#include "vllm/model_executor/models/vocoder1d.h" #include @@ -1851,7 +1852,7 @@ TEST_CASE("minimax_h3: the audio VAE decoder matches the checkpoint's own remote // The kaiser-sinc filter is COMPUTED, not loaded. Prove it first: if the filter // is wrong, every anti-aliased activation is wrong and the decoder mismatch // would be impossible to localize. - const std::vector filter = vllm::MiniMaxH3KaiserSincFilter1d(0.5 / 2, 0.6 / 2, 12); + const std::vector filter = vllm::vocoder1d::KaiserSincFilter1d(0.5 / 2, 0.6 / 2, 12); REQUIRE(filter.size() == std::size(vllm_test::kH3AudioVaeUpFilterGolden)); double filter_err = 0.0; double filter_sum = 0.0; diff --git a/tests/vllm/models/test_vocoder1d.cpp b/tests/vllm/models/test_vocoder1d.cpp new file mode 100644 index 000000000..01075f92c --- /dev/null +++ b/tests/vllm/models/test_vocoder1d.cpp @@ -0,0 +1,186 @@ +// The shared 1-D vocoder core, gated on HAND-COMPUTED values. +// +// These primitives are the BigVGAN lineage that MiniMax-H3 ported first and +// LTX-2.5 then reused rather than copied (`ltx2_audio_vae.cpp` records why: a +// second copy of the alias-free trim geometry goes wrong quietly, because each +// copy keeps its own green gate while the two audio VAEs drift apart). +// IndexTTS-2.5 is the third consumer, so the core moves out of `minimax_h3.h` +// and into a neutral home here. +// +// Every expectation below is computed BY HAND from the operator's definition, +// never captured from the implementation. Capturing would make this a +// consistency check: it would pass just as happily if the shared helper were +// uniformly wrong, which is the exact failure this project has already recorded +// for a gate whose two arms called the same dequant. +// +// Tolerances use `.scale(0.0)`: doctest's default scale of 1.0 puts a 1.19e-5 +// ABSOLUTE floor under any epsilon, which would accept almost anything. +#include +#include +#include + +#include "doctest/doctest.h" +#include "vllm/model_executor/models/vocoder1d.h" + +namespace { + +} // namespace + +TEST_CASE("vocoder1d Conv1d matches a hand-computed convolution") { + // in [1, 4] = [1, 2, 3, 4]; weight [1, 1, 2] = [1, -1]; stride 1, no bias. + // out[t] = in[t]*1 + in[t+1]*(-1) => [-1, -1, -1], length 4 - 2 + 1 = 3. + const std::vector in{1.0F, 2.0F, 3.0F, 4.0F}; + const std::vector weight{1.0F, -1.0F}; + int64_t out_len = 0; + const std::vector out = + vllm::vocoder1d::Conv1d(in, /*in_channels=*/1, /*in_len=*/4, weight, /*bias=*/nullptr, + /*out_channels=*/1, /*kernel=*/2, /*stride=*/1, /*dilation=*/1, + /*groups=*/1, &out_len); + REQUIRE(out_len == 3); + REQUIRE(out.size() == 3U); + for (const float v : out) { + CHECK(v == -1.0F); // exact: integer taps, no rounding to absorb + } +} + +TEST_CASE("vocoder1d Conv1d honours stride and bias") { + // in [1, 5] = [1, 2, 3, 4, 5]; weight k=2 = [1, 1]; stride 2; bias 10. + // taps at t=0,2 => (1+2)+10 = 13, (3+4)+10 = 17. Length = (5-2)/2 + 1 = 2. + const std::vector in{1.0F, 2.0F, 3.0F, 4.0F, 5.0F}; + const std::vector weight{1.0F, 1.0F}; + const std::vector bias{10.0F}; + int64_t out_len = 0; + const std::vector out = + vllm::vocoder1d::Conv1d(in, 1, 5, weight, &bias, 1, /*kernel=*/2, /*stride=*/2, + /*dilation=*/1, /*groups=*/1, &out_len); + REQUIRE(out_len == 2); + CHECK(out[0] == 13.0F); + CHECK(out[1] == 17.0F); +} + +TEST_CASE("vocoder1d ConvTranspose1d scatters each input across the stride") { + // in [1, 2] = [1, 2]; weight [1, 1, 2] = [1, 10]; stride 2, padding 0. + // out[i*stride + k] += in[i] * w[k] => [1, 10, 2, 20]. + // Length = (2-1)*2 - 2*0 + 2 = 4. + const std::vector in{1.0F, 2.0F}; + const std::vector weight{1.0F, 10.0F}; + int64_t out_len = 0; + const std::vector out = vllm::vocoder1d::ConvTranspose1d( + in, /*in_channels=*/1, /*in_len=*/2, weight, /*bias=*/nullptr, /*out_channels=*/1, + /*kernel=*/2, /*stride=*/2, /*padding=*/0, /*groups=*/1, &out_len); + REQUIRE(out_len == 4); + const std::vector want{1.0F, 10.0F, 2.0F, 20.0F}; + for (size_t i = 0; i < want.size(); ++i) { + CHECK(out[i] == want[i]); // exact: hand-computed, integer-valued + } +} + +TEST_CASE("vocoder1d Pad1d replicate repeats the edge sample") { + const std::vector in{1.0F, 2.0F, 3.0F}; + int64_t out_len = 0; + const std::vector out = vllm::vocoder1d::Pad1d(in, /*channels=*/1, /*in_len=*/3, + /*left=*/2, /*right=*/1, + /*replicate=*/true, &out_len); + REQUIRE(out_len == 6); + const std::vector want{1.0F, 1.0F, 1.0F, 2.0F, 3.0F, 3.0F}; + for (size_t i = 0; i < want.size(); ++i) { + CHECK(out[i] == want[i]); // exact: hand-computed, integer-valued + } +} + +TEST_CASE("vocoder1d Pad1d zero mode does not replicate") { + const std::vector in{1.0F, 2.0F, 3.0F}; + int64_t out_len = 0; + const std::vector out = vllm::vocoder1d::Pad1d(in, 1, 3, /*left=*/1, /*right=*/2, + /*replicate=*/false, &out_len); + REQUIRE(out_len == 6); + const std::vector want{0.0F, 1.0F, 2.0F, 3.0F, 0.0F, 0.0F}; + for (size_t i = 0; i < want.size(); ++i) { + CHECK(out[i] == want[i]); // exact: hand-computed, integer-valued + } +} + +TEST_CASE("vocoder1d SnakeActivation adds sin^2 scaled by 1/beta") { + // x + (beta + eps)^-1 * sin^2(alpha * x), alpha = 1, beta = 1, x = pi/2. + // sin(pi/2) = 1, so the result is pi/2 + 1/(1 + 1e-9). + const double x0 = M_PI / 2.0; + std::vector x{static_cast(x0)}; + const std::vector alpha{1.0F}; + const std::vector beta{1.0F}; + vllm::vocoder1d::SnakeActivation(x, /*channels=*/1, /*length=*/1, alpha, &beta, + /*logscale=*/false); + const double want = x0 + 1.0 / (1.0 + vllm::vocoder1d::kSnakeEps); + CHECK(static_cast(x[0]) == doctest::Approx(want).epsilon(1e-6).scale(0.0)); +} + +TEST_CASE("vocoder1d SnakeActivation with a null beta reuses alpha as the scale") { + // Plain Snake (LTX-2.5 vocoder.py:198): the reciprocal scale is ALPHA, not a + // separate beta. alpha = 2, x = pi/4 => sin(2 * pi/4) = sin(pi/2) = 1, so the + // result is pi/4 + 1/(2 + eps). Passing beta here instead would give a + // different number, which is what makes the null-beta arm distinguishable. + const double x0 = M_PI / 4.0; + std::vector x{static_cast(x0)}; + const std::vector alpha{2.0F}; + vllm::vocoder1d::SnakeActivation(x, 1, 1, alpha, /*beta=*/nullptr, /*logscale=*/false); + const double want = x0 + 1.0 / (2.0 + vllm::vocoder1d::kSnakeEps); + CHECK(static_cast(x[0]) == doctest::Approx(want).epsilon(1e-6).scale(0.0)); +} + +TEST_CASE("vocoder1d snake epsilon is pinned to the upstream constant") { + // Source-anchored: upstream writes `1.0 / (beta + 1e-9)` on both sides of this + // lineage. No reduced-dimension golden can tell 1e-9 from 0.0 because beta is + // O(1) there, so the value is held by an assertion rather than a tensor. + CHECK(vllm::vocoder1d::kSnakeEps == 1e-9); +} + +TEST_CASE("vocoder1d kaiser-sinc filter is symmetric and unit-gain") { + // The window is built, never read from a checkpoint. Two properties hold for + // any cutoff: it is symmetric, and a low-pass at DC sums to its ratio-scaled + // gain. Asserting the SHAPE rather than sampled taps keeps this independent of + // the filter's internal normalisation convention. + const std::vector f = + vllm::vocoder1d::KaiserSincFilter1d(/*cutoff=*/0.5, /*half_width=*/0.6, /*kernel_size=*/12); + REQUIRE(f.size() == 12U); + for (size_t i = 0; i < f.size() / 2; ++i) { + CHECK(static_cast(f[i]) == + doctest::Approx(static_cast(f[f.size() - 1 - i])).epsilon(1e-6).scale(0.0)); + } + double sum = 0.0; + for (const float v : f) { + sum += static_cast(v); + } + CHECK(sum > 0.0); +} + +TEST_CASE("vocoder1d AliasFreeActivation1d preserves length and is deterministic") { + // The trim geometry is the fragile part and the reason this is shared rather + // than copied: upsample by ratio -> Snake -> downsample by ratio must return + // the ORIGINAL length. A trim that is off by one produces a signal that still + // plays and is wrong, which no length-agnostic check would catch. + vllm::vocoder1d::AliasFreeActivation1d act; + act.ratio = 2; + act.kernel_size = 12; + act.Build(); + REQUIRE(!act.filter.empty()); + + const int64_t channels = 2; + const int64_t len = 8; + std::vector in(static_cast(channels * len)); + for (size_t i = 0; i < in.size(); ++i) { + in[i] = static_cast(0.1 * static_cast(i) - 0.3); + } + const std::vector alpha{0.5F, 0.25F}; + const std::vector beta{1.0F, 2.0F}; + + int64_t out_len = 0; + const std::vector a = act.Apply(in, channels, len, alpha, &beta, false, &out_len); + CHECK(out_len == len); + REQUIRE(a.size() == in.size()); + + int64_t out_len2 = 0; + const std::vector b = act.Apply(in, channels, len, alpha, &beta, false, &out_len2); + REQUIRE(out_len2 == len); + for (size_t i = 0; i < a.size(); ++i) { + CHECK(a[i] == b[i]); // determinism is bit-exact or it is not determinism + } +} diff --git a/tests/vllm/multimodal/test_speech_engine.cpp b/tests/vllm/multimodal/test_speech_engine.cpp new file mode 100644 index 000000000..3ffe2488e --- /dev/null +++ b/tests/vllm/multimodal/test_speech_engine.cpp @@ -0,0 +1,165 @@ +// The speech (TTS) engine seam — W6a of #634. +// +// Mirrors `vllm::multimodal::VideoEngine`, which solved the same problem for the +// other generative modality: one abstract seam, per-family self-registration, and +// detection that INSPECTS the artifact rather than trusting a path spelling. +// +// This gate covers the seam's contract, which is testable with no model and no +// oracle: registration, refusal-by-name, and the never-guess guarantees. The +// first real family (IndexTTS-2.5) arrives with W3-W5; until then a load must +// REFUSE and say what is missing, because an arm that is silently absent is the +// failure this project has already recorded. +#include +#include +#include +#include + +#include "doctest/doctest.h" +#include "vllm/multimodal/speech_engine.h" + +namespace { + +using vllm::multimodal::SpeechEngine; +using vllm::multimodal::SpeechFamilyRegistration; +using vllm::multimodal::SpeechGenParams; +using vllm::multimodal::SpeechModelParams; +using vllm::multimodal::SpeechRegistry; +using vllm::multimodal::SpeechResult; + +class FakeEngine final : public SpeechEngine { + public: + explicit FakeEngine(std::string family) : family_(std::move(family)) {} + std::string family() const override { return family_; } + int64_t sample_rate() const override { return 22050; } + bool requires_reference_audio() const override { return true; } + SpeechResult Synthesize(const SpeechGenParams& params) override { + if (params.reference_audio.empty()) { + throw std::runtime_error("fake-tts: reference audio is required"); + } + SpeechResult r; + r.sample_rate = 22050; + r.samples.assign(4, 0.25F); + return r; + } + + private: + std::string family_; +}; + +SpeechFamilyRegistration MakeFamily(const std::string& name, bool detects) { + SpeechFamilyRegistration reg; + reg.name = name; + reg.detect = [detects](const SpeechModelParams&) { return detects; }; + reg.load = [name](const SpeechModelParams&) -> std::unique_ptr { + return std::make_unique(name); + }; + return reg; +} + +} // namespace + +TEST_CASE("speech registry resolves a family by inspecting the checkpoint") { + SpeechRegistry registry; + registry.Register(MakeFamily("no-match", false)); + registry.Register(MakeFamily("match", true)); + + SpeechModelParams params; + params.path = "/some/checkpoint"; + std::string why; + std::unique_ptr engine = registry.Load(params, &why); + REQUIRE(engine != nullptr); + CHECK(engine->family() == "match"); + CHECK(engine->sample_rate() == 22050); + CHECK(engine->requires_reference_audio()); +} + +TEST_CASE("speech registry refuses a duplicate family name") { + // Two families sharing one name is the never-guess guarantee defeated from the + // inside: the listing shows one family while two claimants collapse into it. + SpeechRegistry registry; + registry.Register(MakeFamily("dup", true)); + CHECK_THROWS_AS(registry.Register(MakeFamily("dup", true)), std::runtime_error); +} + +TEST_CASE("speech registry refuses an incomplete registration") { + SpeechRegistry registry; + SpeechFamilyRegistration empty_name = MakeFamily("", true); + CHECK_THROWS_AS(registry.Register(empty_name), std::runtime_error); + + SpeechFamilyRegistration no_detect = MakeFamily("x", true); + no_detect.detect = nullptr; + CHECK_THROWS_AS(registry.Register(no_detect), std::runtime_error); + + SpeechFamilyRegistration no_load = MakeFamily("y", true); + no_load.load = nullptr; + CHECK_THROWS_AS(registry.Register(no_load), std::runtime_error); +} + +TEST_CASE("an unresolved checkpoint refuses BY NAME rather than guessing") { + // The refusal has to name what was tried. "unsupported" with no evidence is + // what sends the next person reading loader source. + SpeechRegistry registry; + registry.Register(MakeFamily("alpha", false)); + registry.Register(MakeFamily("beta", false)); + + SpeechModelParams params; + params.path = "/unknown/checkpoint"; + std::string why; + CHECK(registry.Load(params, &why) == nullptr); + CHECK(why.find("alpha") != std::string::npos); + CHECK(why.find("beta") != std::string::npos); + CHECK(why.find("/unknown/checkpoint") != std::string::npos); +} + +TEST_CASE("an EMPTY registry says the lane is unimplemented, not that the file is bad") { + // Today this is the real state: no family is registered, because IndexTTS-2.5 + // lands with W3-W5. A caller must be told the LANE is missing rather than be + // left to conclude their checkpoint is corrupt. + SpeechRegistry registry; + SpeechModelParams params; + params.path = "/anything"; + std::string why; + CHECK(registry.Load(params, &why) == nullptr); + CHECK(why.find("no speech") != std::string::npos); +} + +TEST_CASE("a detector that throws is treated as no-match, not as a crash") { + // Detection runs over artifacts chosen by whoever repackaged a checkpoint. One + // family's bad day must not deny every other family a chance to claim it. + SpeechRegistry registry; + SpeechFamilyRegistration thrower = MakeFamily("thrower", true); + thrower.detect = [](const SpeechModelParams&) -> bool { + throw std::runtime_error("detector exploded"); + }; + registry.Register(thrower); + registry.Register(MakeFamily("survivor", true)); + + SpeechModelParams params; + params.path = "/x"; + std::string why; + std::unique_ptr engine = registry.Load(params, &why); + REQUIRE(engine != nullptr); + CHECK(engine->family() == "survivor"); +} + +TEST_CASE("synthesis refuses when the mandatory reference clip is absent") { + // IndexTTS-2 has NO text-only synthesis, so a missing reference clip is a + // refusal rather than a default voice. + SpeechRegistry registry; + registry.Register(MakeFamily("match", true)); + SpeechModelParams params; + params.path = "/ckpt"; + std::string why; + std::unique_ptr engine = registry.Load(params, &why); + REQUIRE(engine != nullptr); + + SpeechGenParams gen; + gen.text = "hello"; + CHECK_THROWS_AS(engine->Synthesize(gen), std::runtime_error); + + gen.reference_audio.assign(16, 0.0F); + gen.reference_sample_rate = 16000; + const SpeechResult out = engine->Synthesize(gen); + CHECK(out.sample_rate == 22050); + CHECK(out.samples.size() == 4U); +} From f91d543351b348b14e465b57938fbf1ff32ca293 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:09:07 +0200 Subject: [PATCH 14/55] =?UTF-8?q?feat(MODEL-MM-indextts2):=20W3=20first=20?= =?UTF-8?q?=E2=80=94=20CAMPPlus=20primitives=20gated=20on=20upstream=20its?= =?UTF-8?q?elf=20(#634)=20(#689)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL First increment of W3. Does NOT make IndexTTS-2.5 runnable; the lane still refuses by name. CAMPPlus is the speaker-style encoder on the mandatory reference-audio path and sits UPSTREAM of stage 0 — the talker is built with `spk_cond_mode="campplus"` (infer_v2_5.py:138) and consumes its style vector. A STRONGER ORACLE THAN W2's: `campplus/layers.py` has no vllm dependency, so the generator loads it BY FILE PATH and executes the REAL classes. W2 needed a TP=1 restatement because upstream wanted a KV cache and a distributed environment; here there is none, so there is no risk of being faithful to the wrong thing. Four primitives, each a silent-failure mode: StatsPool (UNBIASED N-1 std; the biased form differs ~0.2% at T=250, reads as noise, moves a style vector), BatchNorm1dEval (RUNNING statistics; batch statistics still normalize and are a different model), SegPooling (ceil_mode expand-then-truncate, goldens at T=250 so the trailing PARTIAL segment is exercised) and CamLayer. 4 cases / 8 assertions, all within 1e-5 of upstream. MUTATION EVIDENCE with compile status alongside every result: biased std, floor instead of ceil_mode, and dropping the global-mean term each go RED at compile_err=0; restored tree returns to SUCCESS. The compile check is there because this campaign already produced TWO false readings where a mutation failed to build under -Werror and the old binary re-ran printing SUCCESS. A killed mutation run also left the tree dirty mid-sequence — the file is untracked so `git diff` could not see it, and it was caught by grepping for the mutated line. Restoration is verified by content now, not assumed. STILL OPEN: CAMPPlus's FCM 2-D front end and 52-layer CAMDenseTDNN stack; then w2v-bert-2.0 and EnhancedCodec; then W4 S2Mel, W5 compose, W6b routes/ABI. No e2e render, and any parity claim needs the still-UNPINNED oracle (#633). Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- CMakeLists.txt | 1 + include/vllm/model_executor/models/campplus.h | 61 ++ scripts/gen-campplus-goldens.py | 75 ++ src/vllm/model_executor/models/campplus.cpp | 143 ++++ tests/CMakeLists.txt | 2 + tests/vllm/models/campplus_goldens.inc | 660 ++++++++++++++++++ tests/vllm/models/test_campplus.cpp | 106 +++ 7 files changed, 1048 insertions(+) create mode 100644 include/vllm/model_executor/models/campplus.h create mode 100644 scripts/gen-campplus-goldens.py create mode 100644 src/vllm/model_executor/models/campplus.cpp create mode 100644 tests/vllm/models/campplus_goldens.inc create mode 100644 tests/vllm/models/test_campplus.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a22423ec6..a31be7aae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -770,6 +770,7 @@ add_library(vllm STATIC src/vllm/model_executor/models/gpt2.cpp src/vllm/multimodal/speech_engine.cpp src/vllm/model_executor/models/indextts2.cpp + src/vllm/model_executor/models/campplus.cpp src/vllm/model_executor/models/minimax_h3_video_vae.cpp src/vllm/model_executor/models/minimax_h3_video_vae_device.cpp src/vllm/model_executor/models/minimax_h3_vae_cnn.cpp diff --git a/include/vllm/model_executor/models/campplus.h b/include/vllm/model_executor/models/campplus.h new file mode 100644 index 000000000..c36df40fd --- /dev/null +++ b/include/vllm/model_executor/models/campplus.h @@ -0,0 +1,61 @@ +// CAMPPlus primitives — the speaker-style encoder (W3 of #634). +// +// Ported from `indextts/s2mel/modules/campplus/layers.py` (index-tts), which the +// goldens execute DIRECTLY: it carries no vllm dependency, so the oracle is the +// real class rather than a restatement. +// +// WHY THIS IS ON THE CRITICAL PATH. The talker is constructed with +// `spk_cond_mode="campplus"` (infer_v2_5.py:138) and consumes the 192-d style +// vector this encoder produces, so CAMPPlus sits UPSTREAM of stage 0 rather than +// beside it. Signals are CHANNEL-MAJOR [C, T], matching vocoder1d.h. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace campplus { + +// statistics_pooling (layers.py:26-32): mean concatenated with the UNBIASED +// (N-1) standard deviation. The biased form differs by ~0.2% at T=250 -- small +// enough to read as noise, large enough to move a style vector. +std::vector StatsPool(const std::vector& x, int64_t channels, int64_t frames); + +// torch.nn.BatchNorm1d in EVAL mode: RUNNING statistics, never batch statistics. +// Using batch statistics still normalizes and is a different model. +std::vector BatchNorm1dEval(const std::vector& x, int64_t channels, int64_t frames, + const std::vector& gamma, const std::vector& beta, + const std::vector& running_mean, + const std::vector& running_var, double eps); + +// CAMLayer::seg_pooling (layers.py:100-111): avg_pool1d with kernel == stride == +// seg_len and ceil_mode=true, each segment then EXPANDED back over seg_len +// frames and the result TRUNCATED to the input length. The final partial segment +// is where an off-by-one lives. +std::vector SegPooling(const std::vector& x, int64_t channels, int64_t frames, + int64_t seg_len); + +struct CamLayerWeights { + std::vector linear_local; // [out, bn, k] + std::vector linear1_weight; // [bn/2, bn, 1] + std::vector linear1_bias; // [bn/2] + std::vector linear2_weight; // [out, bn/2, 1] + std::vector linear2_bias; // [out] +}; + +// CAMLayer::forward (layers.py:93-98): +// y = linear_local(x) +// context = mean(x, -1, keepdim) + seg_pooling(x) +// m = sigmoid(linear2(relu(linear1(context)))) +// return y * m +// The context is a per-channel scalar broadcast over time, so `m` gates each +// output channel uniformly -- getting the broadcast axis wrong still produces a +// plausible signal. +std::vector CamLayer(const std::vector& x, int64_t bn_channels, int64_t frames, + int64_t out_channels, int64_t kernel, int64_t dilation, + int64_t seg_len, const CamLayerWeights& weights); + +} // namespace campplus +} // namespace models +} // namespace vllm diff --git a/scripts/gen-campplus-goldens.py b/scripts/gen-campplus-goldens.py new file mode 100644 index 000000000..4be0d5b01 --- /dev/null +++ b/scripts/gen-campplus-goldens.py @@ -0,0 +1,75 @@ +import importlib.util, sys, types, numpy as np, torch +from pathlib import Path +SRC = Path("/tmp/claude-1000/-home-mudler--git-vllm-cpp/38b1c7ca-4dbc-4077-a36b-a9495dd81893/scratchpad/it2/indextts/s2mel/modules/campplus/layers.py") +# load the UPSTREAM module by file path (no package __init__, no vllm deps) +spec = importlib.util.spec_from_file_location("cp_layers", SRC) +m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) + +MASK = (1 << 64) - 1 +def fnv(n): + h = 0xCBF29CE484222325 + for b in n.encode(): h ^= b; h = (h * 0x100000001B3) & MASK + return h +def sm(x): + x = (x + 0x9E3779B97F4A7C15) & MASK + z = x + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & MASK + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & MASK + return z ^ (z >> 31) +def rnd(name, n, scale=1.0): + s = fnv(name); o = np.empty(n) + for i in range(n): o[i] = (((sm((s + i) & MASK) >> 11) * 2.0**-53) * 2 - 1) * scale + return o +def P(name, shape, scale=1.0): + n = int(np.prod(shape)) + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + +torch.manual_seed(0) +C, T, BN, OUT, K, D = 6, 250, 4, 5, 3, 2 # T>100 so seg_pooling's ceil_mode bites +x = P("x", (1, C, T), 1.0) + +# ---- StatsPool: mean + UNBIASED std ---- +stats = m.StatsPool()(x) + +# ---- BatchNorm1d in EVAL mode (running stats, not batch stats) ---- +bn = torch.nn.BatchNorm1d(C).eval() +with torch.no_grad(): + bn.weight.copy_(P("bn.w", (C,), 0.5) + 1.0); bn.bias.copy_(P("bn.b", (C,), 0.3)) + bn.running_mean.copy_(P("bn.rm", (C,), 0.4)); bn.running_var.copy_(P("bn.rv", (C,), 0.2).abs() + 0.5) +bn_out = bn(x) + +# ---- CAMLayer (incl. seg_pooling with ceil_mode) ---- +cam = m.CAMLayer(BN, OUT, K, stride=1, padding=(K-1)//2*D, dilation=D, bias=False).eval() +xb = P("xb", (1, BN, T), 1.0) +with torch.no_grad(): + cam.linear_local.weight.copy_(P("cam.ll.w", tuple(cam.linear_local.weight.shape), 0.3)) + cam.linear1.weight.copy_(P("cam.l1.w", tuple(cam.linear1.weight.shape), 0.3)) + cam.linear1.bias.copy_(P("cam.l1.b", tuple(cam.linear1.bias.shape), 0.2)) + cam.linear2.weight.copy_(P("cam.l2.w", tuple(cam.linear2.weight.shape), 0.3)) + cam.linear2.bias.copy_(P("cam.l2.b", tuple(cam.linear2.bias.shape), 0.2)) +cam_out = cam(xb) +seg = cam.seg_pooling(xb) + +def emit(f, name, t): + a = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) + f.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(a), 6): + f.write(" " + ", ".join(f"{v:.9e}F" for v in a[i:i+6]) + ",\n") + f.write("};\n\n") + +out = Path(sys.argv[1]) +with out.open("w") as f: + f.write("// GENERATED by scripts/gen-campplus-goldens.py -- do not edit.\n") + f.write("// Upstream executed DIRECTLY (indextts/s2mel/modules/campplus/layers.py,\n") + f.write("// index-tts @ main): it has no vllm dependency, so these are the real\n") + f.write("// classes rather than a restatement. Weights rebuilt both sides from one\n") + f.write("// FNV-1a -> splitmix64 stream; no weight byte is checked in.\n") + f.write("#pragma once\n\n#include \n\nnamespace campplus_goldens {\n\n") + for n, v in (("kChannels", C), ("kFrames", T), ("kBnChannels", BN), ("kOutChannels", OUT), + ("kKernel", K), ("kDilation", D), ("kSegLen", 100)): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write("\n") + emit(f, "kStats", stats); emit(f, "kBatchNormEval", bn_out) + emit(f, "kSegPooling", seg); emit(f, "kCamOut", cam_out) + f.write("} // namespace campplus_goldens\n") +print("wrote", out, "T=", T, "seg segments=", -(-T//100)) diff --git a/src/vllm/model_executor/models/campplus.cpp b/src/vllm/model_executor/models/campplus.cpp new file mode 100644 index 000000000..a109ccd6b --- /dev/null +++ b/src/vllm/model_executor/models/campplus.cpp @@ -0,0 +1,143 @@ +// CAMPPlus primitives. See campplus.h for the upstream anchors. +#include "vllm/model_executor/models/campplus.h" + +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace campplus { + +std::vector StatsPool(const std::vector& x, int64_t channels, int64_t frames) { + VT_CHECK(x.size() == static_cast(channels * frames), "campplus: StatsPool shape"); + VT_CHECK(frames > 1, "campplus: unbiased std needs at least two frames"); + std::vector out(static_cast(2 * channels)); + for (int64_t c = 0; c < channels; ++c) { + const float* row = x.data() + static_cast(c * frames); + double mean = 0.0; + for (int64_t t = 0; t < frames; ++t) mean += static_cast(row[t]); + mean /= static_cast(frames); + double sq = 0.0; + for (int64_t t = 0; t < frames; ++t) { + const double d = static_cast(row[t]) - mean; + sq += d * d; + } + // UNBIASED: torch's std(unbiased=True) divides by N-1. + const double std_dev = std::sqrt(sq / static_cast(frames - 1)); + out[static_cast(c)] = static_cast(mean); + out[static_cast(channels + c)] = static_cast(std_dev); + } + return out; +} + +std::vector BatchNorm1dEval(const std::vector& x, int64_t channels, int64_t frames, + const std::vector& gamma, const std::vector& beta, + const std::vector& running_mean, + const std::vector& running_var, double eps) { + VT_CHECK(x.size() == static_cast(channels * frames), "campplus: BatchNorm shape"); + std::vector out(x.size()); + for (int64_t c = 0; c < channels; ++c) { + const double m = static_cast(running_mean[static_cast(c)]); + const double inv = 1.0 / std::sqrt(static_cast(running_var[static_cast(c)]) + eps); + const double g = gamma.empty() ? 1.0 : static_cast(gamma[static_cast(c)]); + const double b = beta.empty() ? 0.0 : static_cast(beta[static_cast(c)]); + for (int64_t t = 0; t < frames; ++t) { + const size_t i = static_cast(c * frames + t); + out[i] = static_cast((static_cast(x[i]) - m) * inv * g + b); + } + } + return out; +} + +std::vector SegPooling(const std::vector& x, int64_t channels, int64_t frames, + int64_t seg_len) { + VT_CHECK(x.size() == static_cast(channels * frames), "campplus: SegPooling shape"); + VT_CHECK(seg_len > 0, "campplus: seg_len must be positive"); + // ceil_mode=true: a partial trailing window still produces a segment, averaged + // over the frames it actually covers. + const int64_t segments = (frames + seg_len - 1) / seg_len; + std::vector out(x.size()); + for (int64_t c = 0; c < channels; ++c) { + const float* row = x.data() + static_cast(c * frames); + float* dst = out.data() + static_cast(c * frames); + for (int64_t s = 0; s < segments; ++s) { + const int64_t begin = s * seg_len; + const int64_t end = std::min(begin + seg_len, frames); + double acc = 0.0; + for (int64_t t = begin; t < end; ++t) acc += static_cast(row[t]); + const float mean = static_cast(acc / static_cast(end - begin)); + // expand back over seg_len frames, then TRUNCATE to the input length. + for (int64_t t = begin; t < end; ++t) dst[t] = mean; + } + } + return out; +} + +namespace { + +// 1-D convolution over [C, T] with `same` padding, as every CAMPPlus conv uses. +std::vector Conv1dSame(const std::vector& in, int64_t in_ch, int64_t frames, + const std::vector& w, const std::vector& bias, + int64_t out_ch, int64_t kernel, int64_t dilation) { + const int64_t pad = (kernel - 1) / 2 * dilation; + std::vector out(static_cast(out_ch * frames), 0.0F); + for (int64_t o = 0; o < out_ch; ++o) { + for (int64_t t = 0; t < frames; ++t) { + double acc = bias.empty() ? 0.0 : static_cast(bias[static_cast(o)]); + for (int64_t i = 0; i < in_ch; ++i) { + for (int64_t k = 0; k < kernel; ++k) { + const int64_t src = t + k * dilation - pad; + if (src < 0 || src >= frames) continue; // zero padding + acc += static_cast(w[static_cast((o * in_ch + i) * kernel + k)]) * + static_cast(in[static_cast(i * frames + src)]); + } + } + out[static_cast(o * frames + t)] = static_cast(acc); + } + } + return out; +} + +} // namespace + +std::vector CamLayer(const std::vector& x, int64_t bn_channels, int64_t frames, + int64_t out_channels, int64_t kernel, int64_t dilation, + int64_t seg_len, const CamLayerWeights& weights) { + const std::vector y = + Conv1dSame(x, bn_channels, frames, weights.linear_local, {}, out_channels, kernel, dilation); + + // context = x.mean(-1, keepdim=True) + seg_pooling(x): a [C, T] signal, since + // the per-channel mean broadcasts across the segment-pooled one. + const std::vector seg = SegPooling(x, bn_channels, frames, seg_len); + std::vector context(x.size()); + for (int64_t c = 0; c < bn_channels; ++c) { + double mean = 0.0; + for (int64_t t = 0; t < frames; ++t) { + mean += static_cast(x[static_cast(c * frames + t)]); + } + mean /= static_cast(frames); + for (int64_t t = 0; t < frames; ++t) { + const size_t i = static_cast(c * frames + t); + context[i] = static_cast(mean + static_cast(seg[i])); + } + } + + const int64_t reduced = bn_channels / 2; + std::vector h = Conv1dSame(context, bn_channels, frames, weights.linear1_weight, + weights.linear1_bias, reduced, 1, 1); + for (float& v : h) v = v > 0.0F ? v : 0.0F; // ReLU + std::vector m = Conv1dSame(h, reduced, frames, weights.linear2_weight, + weights.linear2_bias, out_channels, 1, 1); + for (float& v : m) v = static_cast(1.0 / (1.0 + std::exp(-static_cast(v)))); + + std::vector out(y.size()); + for (size_t i = 0; i < y.size(); ++i) out[i] = y[i] * m[i]; + return out; +} + +} // namespace campplus +} // namespace models +} // namespace vllm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 77400c6ab..291f8cfdb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -112,6 +112,8 @@ vllm_cpp_add_test(test_model_registry vllm/models/test_model_registry.cpp) vllm_cpp_add_test(test_vocoder1d vllm/models/test_vocoder1d.cpp) vllm_cpp_add_test(test_speech_engine vllm/multimodal/test_speech_engine.cpp) vllm_cpp_add_test(test_indextts2_family vllm/models/test_indextts2_family.cpp) +vllm_cpp_add_test(test_campplus vllm/models/test_campplus.cpp) +target_include_directories(test_campplus PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) vllm_cpp_add_test(test_gpt2 vllm/models/test_gpt2.cpp) target_include_directories(test_gpt2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) vllm_cpp_add_test(test_minimax_h3 vllm/models/test_minimax_h3.cpp) diff --git a/tests/vllm/models/campplus_goldens.inc b/tests/vllm/models/campplus_goldens.inc new file mode 100644 index 000000000..f5b947e05 --- /dev/null +++ b/tests/vllm/models/campplus_goldens.inc @@ -0,0 +1,660 @@ +// GENERATED by scripts/gen-campplus-goldens.py -- do not edit. +// Upstream executed DIRECTLY (indextts/s2mel/modules/campplus/layers.py, +// index-tts @ main): it has no vllm dependency, so these are the real +// classes rather than a restatement. Weights rebuilt both sides from one +// FNV-1a -> splitmix64 stream; no weight byte is checked in. +#pragma once + +#include + +namespace campplus_goldens { + +inline constexpr int64_t kChannels = 6; +inline constexpr int64_t kFrames = 250; +inline constexpr int64_t kBnChannels = 4; +inline constexpr int64_t kOutChannels = 5; +inline constexpr int64_t kKernel = 3; +inline constexpr int64_t kDilation = 2; +inline constexpr int64_t kSegLen = 100; + +inline constexpr float kStats[] = { + 3.743343800e-02F, 2.342207916e-02F, 5.418617278e-02F, -1.500653452e-03F, -6.817653030e-02F, -5.413483083e-02F, + 5.534929037e-01F, 5.814045668e-01F, 6.068772674e-01F, 5.889041424e-01F, 6.169255376e-01F, 5.937492251e-01F, +}; + +inline constexpr float kBatchNormEval[] = { + -9.851844311e-01F, 4.708131030e-02F, 1.479494810e+00F, -9.785402417e-01F, 7.400213480e-01F, 1.298890859e-01F, + -1.446273457e-02F, -2.103383690e-01F, -1.903531402e-01F, 4.823647141e-01F, -2.567018867e-01F, 1.767546982e-01F, + 4.520796984e-02F, 2.632010579e-01F, -1.270225525e+00F, -1.324376613e-01F, 7.398702204e-02F, 1.623408437e+00F, + 4.376341999e-01F, -1.520394683e+00F, 2.898574471e-01F, 1.040739417e+00F, -1.050378799e+00F, 2.939671278e-01F, + -1.241439104e+00F, -5.295318365e-01F, 2.862103879e-01F, -3.990913928e-01F, -1.617472053e+00F, 7.832028866e-01F, + 1.037789941e+00F, 7.292543054e-01F, 1.218947053e+00F, -1.385738015e+00F, -3.507440686e-01F, 1.489947289e-01F, + -8.105055094e-01F, 6.464648843e-01F, -1.629588455e-01F, 5.620851517e-01F, -1.613007903e+00F, 6.851549745e-01F, + 5.664080977e-01F, -1.396780729e+00F, -1.593172193e+00F, 4.191704094e-01F, 1.005881786e+00F, 1.306931615e+00F, + -1.023267269e+00F, 1.469440699e+00F, -6.954281926e-01F, -1.603088498e+00F, -6.698713899e-01F, -1.126382947e+00F, + 1.882735193e-01F, -8.691844940e-01F, 1.661928147e-01F, -5.145962834e-01F, 1.430106163e-02F, -1.541421533e+00F, + -8.119313121e-01F, 1.223647714e+00F, 4.554299712e-01F, 9.016062617e-01F, 6.550078094e-02F, 1.214495420e+00F, + -1.708904654e-01F, 4.991465807e-01F, -1.006204963e+00F, 1.401716828e+00F, 6.909337640e-01F, -7.441946268e-01F, + 1.425817132e+00F, 5.459764004e-01F, 1.025859714e+00F, -1.338053226e+00F, -1.428563118e+00F, 4.283864200e-01F, + 1.581783175e+00F, 8.917160630e-01F, 7.867314816e-01F, 5.943295360e-01F, 1.577602625e+00F, -1.424702108e-01F, + 2.389374375e-01F, -6.290615797e-01F, -4.772656262e-01F, 6.451147050e-02F, 1.274459600e+00F, 1.474961996e+00F, + -5.804778934e-01F, 1.198838055e-01F, -1.573433995e+00F, -1.446498990e+00F, 7.791250944e-01F, -7.464619875e-01F, + -4.362091050e-02F, -3.193825185e-01F, -1.963071823e-01F, 1.276958138e-01F, -8.808127046e-02F, 1.182130933e+00F, + -4.299479723e-01F, 7.256302834e-01F, 1.272356153e+00F, 8.654559255e-01F, 1.136456966e+00F, -6.287534833e-01F, + 1.088686109e+00F, -1.339879394e+00F, -1.113537550e+00F, 2.391038239e-01F, 1.636703610e+00F, 1.078911543e+00F, + 1.228632569e+00F, 9.000107646e-01F, -1.509702444e+00F, -3.802744448e-01F, 1.445003152e+00F, -7.211277485e-01F, + 1.136734962e+00F, -1.338047028e+00F, -7.815974951e-01F, -9.842619896e-01F, 1.483963490e+00F, 1.511906028e+00F, + 1.006315708e+00F, -6.258260608e-01F, -1.647545815e+00F, 2.587329745e-01F, 1.309426904e+00F, -1.070028782e+00F, + -4.081555642e-03F, 1.122329593e+00F, 1.622579575e+00F, 1.313752532e+00F, 3.169698715e-01F, -2.036248744e-01F, + -3.885628581e-01F, -2.055345178e-01F, -5.136427656e-02F, -2.354292274e-01F, -8.536843657e-01F, 1.035663724e+00F, + -1.584179521e+00F, -1.370129347e+00F, -6.768531799e-01F, -1.077800989e+00F, 7.851408720e-01F, 1.356790185e+00F, + 1.507851839e+00F, 5.392167345e-02F, 1.470110714e-01F, -1.525479704e-01F, 1.269427896e+00F, -1.085533381e+00F, + 4.052372649e-02F, -1.589846015e+00F, -6.286586523e-01F, 1.004913688e+00F, 8.171494007e-01F, 4.437318444e-02F, + 1.336106777e+00F, 1.016400382e-01F, -3.788954020e-01F, -1.419602990e+00F, -5.964035392e-01F, 8.935017586e-01F, + -7.803112864e-01F, 7.617778778e-01F, 5.203037858e-01F, -1.375825047e+00F, -1.223783851e+00F, -3.021776378e-01F, + 6.075348854e-01F, 1.028803468e+00F, -4.363378510e-02F, 3.124350607e-01F, 1.593945265e+00F, -1.364206672e-01F, + 4.549133480e-01F, -1.181332543e-01F, -3.622325361e-01F, -9.313156456e-02F, 1.631559804e-02F, -9.576695561e-01F, + 1.122400016e-01F, -2.591373920e-01F, 5.170621276e-01F, 1.387488127e+00F, -1.245504856e+00F, 4.373455942e-01F, + 1.559751749e+00F, 9.939870834e-01F, -4.182111919e-01F, -4.601785541e-01F, -6.946704388e-01F, 1.346964240e+00F, + -4.269869924e-01F, 3.849843144e-01F, 1.627654552e+00F, -8.739209175e-01F, -1.312645316e+00F, 6.695709229e-01F, + 7.423822284e-01F, -1.189600587e+00F, -1.233634949e+00F, 6.203428507e-01F, -9.040484577e-02F, -9.042347074e-01F, + -9.633267671e-02F, -6.510872245e-01F, -4.240335524e-01F, 5.186629295e-01F, -3.673729300e-01F, -9.972196221e-01F, + -7.247005105e-01F, 1.367386818e+00F, 1.152543187e+00F, -2.358424962e-01F, 5.034411699e-02F, 1.467386961e+00F, + 3.534102142e-01F, 1.525073647e+00F, 7.138103843e-01F, 1.075628042e+00F, 3.030712306e-01F, 1.499844074e+00F, + 6.037003994e-01F, 8.448970914e-01F, 1.377328038e+00F, 1.630164742e+00F, 2.822307050e-01F, -2.432436449e-03F, + -9.755749106e-01F, -6.741740704e-01F, -6.737446785e-01F, -9.428101182e-01F, 8.761678636e-02F, -8.168898225e-01F, + 3.592736125e-01F, 8.682206869e-01F, -1.490811110e+00F, -9.391112924e-01F, -3.376932442e-01F, 1.507676482e+00F, + -3.078364022e-02F, -6.682397127e-01F, 5.755625963e-01F, 4.614345431e-01F, 6.603372693e-01F, 1.394471154e-02F, + 1.141538501e+00F, 2.027914375e-01F, -5.131741762e-01F, -3.120976985e-01F, 1.092440724e+00F, -1.244231939e+00F, + -1.631223559e+00F, -1.005976796e-01F, -1.637898445e+00F, -1.915042281e+00F, 1.186899781e+00F, 9.555853158e-02F, + 1.163715482e+00F, -5.676311255e-01F, -8.448094130e-01F, 9.896939397e-01F, -1.856276274e+00F, -1.427483320e+00F, + -2.187507749e-01F, -1.710271478e+00F, -9.434084892e-01F, -1.031232357e+00F, -1.168681502e+00F, -9.176382422e-01F, + 4.183145463e-01F, -1.979965210e+00F, 5.886018872e-01F, 5.579829216e-01F, -5.745728612e-01F, -4.102223814e-01F, + -2.385603487e-01F, -4.185673594e-01F, -1.722649574e+00F, -7.638186812e-01F, -8.920246363e-01F, -1.238374352e+00F, + 7.209951431e-02F, -7.281816006e-01F, -1.884089470e+00F, 5.648776293e-01F, 1.159622431e+00F, 5.191366673e-01F, + -4.818421006e-01F, 1.074089646e+00F, -1.382494867e-01F, 8.508859277e-01F, -6.859126091e-01F, 3.786036074e-01F, + 6.892070770e-01F, 9.401881099e-01F, -7.801883817e-01F, -1.047487617e+00F, -1.054782629e+00F, -2.055379868e+00F, + -5.965834856e-01F, 9.019463658e-01F, -4.560639858e-01F, -1.213045139e-02F, 2.741163373e-01F, -1.914574027e+00F, + -2.245941199e-02F, -1.480603337e+00F, 1.160907745e+00F, -1.858352900e+00F, 1.013852715e+00F, -1.967383265e+00F, + -6.718977094e-01F, -8.012340069e-01F, -4.555884600e-01F, 8.093960881e-01F, 1.172175527e+00F, 1.014812350e+00F, + 6.449561119e-01F, 1.284930587e+00F, -7.449821234e-01F, -1.260424018e+00F, -1.056493044e+00F, -9.620307684e-01F, + 1.914995164e-01F, -6.237496734e-01F, 5.225593448e-01F, -1.173716426e+00F, -4.010230005e-01F, 1.587048173e-01F, + -1.680434108e+00F, 9.271780252e-01F, 7.744646668e-01F, 4.975069165e-01F, -1.792317748e+00F, 5.795154572e-01F, + 7.701674700e-01F, 7.465395927e-01F, -1.835419059e+00F, -1.103186727e+00F, -1.146358490e+00F, 3.885702491e-01F, + 1.296184182e+00F, -7.906506062e-01F, -1.938024759e+00F, -1.458616257e+00F, 1.023744106e+00F, -1.739545107e+00F, + -2.186111659e-01F, -3.882863820e-01F, 3.589727283e-01F, -1.956312895e+00F, 1.106856346e+00F, -7.874664664e-01F, + -5.675547719e-01F, -1.821585178e+00F, -2.027816057e+00F, 2.967954874e-01F, -9.414934367e-02F, -1.748723626e+00F, + -1.434377432e+00F, -6.866786480e-01F, -1.910361767e+00F, 8.075827956e-01F, 5.168972015e-01F, 6.268123388e-01F, + -1.115185738e+00F, -3.023109436e-01F, 6.099495292e-01F, 7.704541832e-02F, -1.270914197e+00F, -3.843191564e-01F, + -1.372246265e+00F, -4.451633394e-01F, -2.995690107e-01F, 3.153431714e-01F, 5.849123597e-01F, -5.621476844e-02F, + -1.667268991e+00F, -1.201984659e-01F, -1.904569268e+00F, -3.536513308e-03F, 1.064579248e+00F, 1.012826800e+00F, + -1.731337309e+00F, -1.870841682e-01F, 7.110562325e-01F, -1.353641033e+00F, -5.297145844e-01F, -1.190269142e-01F, + -4.616137147e-01F, -3.142619133e-01F, -1.471573710e+00F, -1.504894853e+00F, -7.177373767e-02F, -7.082943916e-01F, + -8.864228129e-01F, 1.461629868e-01F, -1.986914515e+00F, -7.542912960e-01F, -1.231854796e+00F, 6.980066895e-01F, + -1.297334433e-01F, -2.107349157e+00F, -9.449254274e-01F, -1.843168497e+00F, -4.232656360e-01F, -1.286149740e+00F, + -5.814156532e-01F, -1.920703769e+00F, 1.849697530e-01F, -8.003592491e-01F, 6.956666112e-01F, 1.052672505e+00F, + 4.306144416e-01F, 1.009097882e-02F, -1.570408583e+00F, 3.980930150e-01F, 6.348213553e-01F, -2.133559465e+00F, + 1.282423258e+00F, -7.543866038e-01F, 5.159935951e-01F, -1.904266000e+00F, -8.614909649e-01F, 5.777724385e-01F, + -1.241328359e+00F, 1.188379526e+00F, -1.465727568e+00F, 8.414832354e-01F, -1.707554221e+00F, -8.865512609e-01F, + 5.048994422e-01F, 4.503593445e-01F, -1.153510332e+00F, -1.579958200e+00F, -9.621900916e-01F, 1.072488308e+00F, + -2.007869720e+00F, 1.097748518e+00F, -1.709068060e+00F, -3.497632742e-01F, -3.046539426e-01F, -1.454211712e+00F, + 1.252294779e+00F, 1.095438123e+00F, 7.639694810e-01F, -2.178407013e-01F, 2.509177923e-01F, 2.546589673e-01F, + 1.268450499e+00F, 9.377083927e-02F, 9.987730980e-01F, 3.270196617e-01F, -3.765673935e-02F, -1.596748829e+00F, + -1.753045321e-01F, -1.962946892e+00F, 6.033068895e-01F, -1.342140913e+00F, -3.561224639e-01F, -1.680611372e+00F, + 3.191154301e-01F, -1.211127877e+00F, -3.430563807e-01F, -1.525801420e+00F, 8.593079448e-01F, 2.815059721e-01F, + 6.267039776e-01F, 1.578778774e-01F, -1.139243960e+00F, -5.040315986e-01F, -6.180057675e-02F, 1.013234973e+00F, + -3.365150392e-01F, 1.289181232e+00F, -1.048938274e+00F, 2.984550083e-03F, 1.180573106e-01F, -5.930172205e-01F, + -1.316336036e+00F, 7.961645722e-01F, -2.086997986e+00F, 1.706954837e-01F, -1.350266337e+00F, -4.537180662e-01F, + 5.517622232e-01F, -4.390690848e-02F, 1.232097149e+00F, -9.168065786e-01F, -2.132752895e+00F, -1.505666852e+00F, + -2.087406158e+00F, -1.193572655e-01F, -1.274549007e+00F, 7.347614765e-01F, -6.304273605e-01F, 5.259246826e-01F, + -4.636161029e-01F, -1.503843546e+00F, 2.726554498e-02F, 2.179120183e-01F, -3.615053892e-01F, 1.755729318e+00F, + 1.543197036e-01F, 1.262200952e+00F, -1.365228057e+00F, 1.565008759e+00F, 1.260242581e+00F, 4.389002025e-01F, + 1.727794886e+00F, -6.494255066e-01F, 4.203817844e-01F, 7.340391874e-01F, 1.645721078e+00F, 1.362533689e+00F, + 1.766195774e+00F, 8.649594188e-01F, -1.094982386e+00F, 2.155917585e-01F, 1.176912665e+00F, 1.019424438e+00F, + 2.067956775e-01F, -9.851595759e-02F, 1.413226962e+00F, 1.304954171e+00F, -1.308207512e+00F, 1.432374120e+00F, + 3.374350443e-02F, -5.557708740e-01F, -9.652633667e-01F, 9.790800810e-01F, 6.702603102e-01F, -1.115660667e+00F, + -1.965205520e-01F, 1.115441203e+00F, 1.039943457e+00F, -9.488908052e-01F, -4.368895590e-01F, -6.355435252e-01F, + 6.808435917e-02F, 2.558697462e-01F, -1.429064631e+00F, 7.985455543e-02F, 1.526963592e+00F, -1.507924050e-01F, + 1.533571720e+00F, -7.389622927e-01F, 1.776236296e+00F, 9.773771465e-02F, -9.871808887e-01F, -7.452960014e-01F, + 9.931406975e-01F, -1.226303339e+00F, -3.781354129e-01F, 1.724330544e+00F, 1.662175179e+00F, 1.513062596e+00F, + 2.790541351e-01F, -1.345475197e+00F, -2.802453339e-01F, -1.018484116e+00F, -1.295277476e+00F, -1.242780805e+00F, + -1.015929580e+00F, 8.292371035e-01F, -7.303634286e-01F, 1.237574443e-01F, 2.871560454e-01F, 2.968273759e-01F, + -1.197407246e+00F, 1.670789242e+00F, -1.514970660e+00F, 7.615802288e-01F, 1.252806902e+00F, 2.472041696e-01F, + 8.829095960e-01F, 1.586033106e+00F, 9.183962941e-01F, -7.084733248e-01F, -1.024989963e+00F, 5.505309105e-01F, + 1.207763076e+00F, -1.753618419e-01F, 1.480154514e+00F, 1.247403383e+00F, 1.993306130e-01F, 5.990163684e-01F, + 1.678255439e+00F, -2.937705219e-01F, 9.243205190e-01F, 3.183309138e-01F, -7.992348671e-01F, 8.999804258e-01F, + 1.718962073e+00F, 8.506133556e-01F, -1.082709670e+00F, 9.247531891e-01F, 5.538039207e-01F, 2.329361886e-01F, + 1.668431044e+00F, -8.427484632e-01F, -1.359380007e+00F, -8.786647767e-02F, 2.549920678e-01F, 9.849885106e-01F, + 3.528008163e-01F, 4.461549819e-01F, 1.843117476e+00F, -3.851249814e-01F, 1.257940292e+00F, 9.626734853e-01F, + 7.633949518e-01F, 6.365198493e-01F, -2.788638175e-01F, 1.368077993e+00F, 5.300392509e-01F, -1.247951984e+00F, + -7.226623297e-01F, 9.926107526e-01F, 1.431289673e+00F, -4.111065865e-01F, 9.439466596e-01F, 8.220732808e-01F, + -5.507427454e-01F, 9.790428281e-01F, -3.904130757e-01F, 1.313472152e+00F, 1.840809464e+00F, -1.244588733e+00F, + -4.409357011e-01F, -1.477318048e+00F, -5.777629018e-01F, 1.477047682e+00F, -8.913729787e-01F, 5.704383254e-01F, + -1.243898392e+00F, 9.866008162e-01F, -1.489933491e+00F, -9.895941019e-01F, 2.932395935e-01F, -1.118351102e+00F, + 8.497806787e-01F, 1.219536424e+00F, -4.249502420e-01F, 1.398995280e+00F, -3.025332391e-01F, 1.779585600e+00F, + -1.055062175e+00F, 1.371548891e+00F, 2.065507770e-01F, -1.138114095e+00F, -1.649325341e-01F, 4.541133344e-01F, + -5.222420692e-01F, 8.272252679e-01F, 1.533818603e+00F, 2.878676951e-01F, 8.884479403e-01F, 6.333130002e-01F, + 9.959849119e-01F, 1.285938978e+00F, -1.076741695e+00F, 1.261734843e+00F, 1.679780245e+00F, 1.162769079e+00F, + -7.274963856e-01F, -5.877293348e-01F, -1.437457561e+00F, 1.052547455e+00F, 3.139331937e-01F, -8.548865914e-01F, + -3.029838502e-01F, 1.443263412e+00F, 8.654170632e-01F, 1.842344165e+00F, -8.577220142e-02F, -9.999188185e-01F, + 1.657427788e+00F, 3.682779893e-02F, 4.151986539e-01F, -2.024272978e-01F, -1.208292723e+00F, 1.798243165e+00F, + 9.669562578e-01F, 1.337713122e+00F, 1.660036325e+00F, 1.699753404e+00F, -5.211309716e-02F, 1.467346668e+00F, + 1.324973106e+00F, 1.552841067e+00F, -1.657153666e-01F, 1.691722512e+00F, 8.727994561e-02F, -1.355046272e+00F, + 1.765496880e-01F, -2.102374882e-01F, 1.993684322e-01F, -1.510450006e+00F, -9.078705311e-01F, -4.572091103e-01F, + 1.106264114e+00F, 4.875743687e-01F, -2.805148363e-01F, 3.145065531e-02F, -1.338246584e+00F, -1.192354560e+00F, + 1.543287754e+00F, 1.171245456e+00F, -1.471855044e+00F, -5.355342627e-01F, -4.928006530e-01F, 8.937995434e-01F, + -7.504868507e-01F, -3.612716794e-01F, -1.425437212e+00F, 1.460452080e+00F, 9.387583137e-01F, -2.106482983e-01F, + 1.722978711e+00F, -1.513512254e+00F, 1.254725695e+00F, -1.425256729e+00F, -8.215326071e-01F, -4.047824442e-01F, + 2.179626226e-01F, -1.271153808e+00F, -7.084526420e-01F, 5.053833723e-01F, 8.342918754e-02F, 1.823507071e+00F, + 1.278515816e+00F, 1.387727141e+00F, 1.443236947e+00F, -2.065815926e-01F, 1.109594822e+00F, -5.738745332e-01F, + 1.434150338e+00F, -9.552696347e-01F, -1.401020527e+00F, 1.784177303e+00F, -1.338683724e+00F, 1.439137220e+00F, + 2.489201166e-02F, 1.104080081e-01F, 6.355588436e-01F, 8.622679710e-01F, 8.666797280e-01F, -1.164026558e-02F, + 8.711560965e-01F, 3.730432391e-01F, 6.713986993e-01F, -2.436145842e-01F, -1.726668328e-02F, 4.446004927e-01F, + 5.278616548e-01F, -1.329286844e-01F, 9.274973869e-01F, 1.159515157e-01F, 1.694408506e-01F, 1.702062599e-02F, + 6.362063885e-01F, -3.295033574e-01F, 2.313336432e-01F, 4.708173871e-01F, 8.523576856e-01F, 5.696780086e-01F, + 5.566172004e-01F, 7.203695178e-01F, -2.925447822e-01F, 1.644268334e-01F, 9.061912894e-01F, -3.254545107e-02F, + -1.068570688e-01F, 6.028177738e-01F, 8.625314832e-01F, 7.947853804e-01F, 2.196586877e-01F, 1.931338608e-01F, + 9.612634778e-02F, 4.276007116e-01F, 1.147362054e-03F, 4.890331030e-01F, 5.757212043e-01F, -2.721282244e-01F, + 4.503596127e-01F, 7.538001537e-01F, 5.508890748e-01F, 2.478654534e-01F, 3.387480676e-01F, 7.724870443e-01F, + -2.466423064e-02F, 2.794424593e-01F, 2.634246349e-01F, -1.499266326e-01F, 6.558894515e-01F, 4.912320077e-01F, + 3.415653706e-01F, -3.266833127e-01F, 6.672360301e-01F, 4.473367035e-01F, -2.741800249e-01F, -7.108918577e-02F, + -2.895817161e-01F, 6.965593100e-01F, -3.563807309e-01F, -1.053854674e-01F, 2.526929416e-02F, 7.058338523e-01F, + 7.973586023e-02F, 2.046252489e-01F, 5.433157682e-01F, -2.827918902e-02F, -3.668708503e-01F, -2.035128772e-01F, + -2.150800675e-01F, 7.235044837e-01F, 5.632303357e-01F, 9.518454671e-01F, 1.139020920e-01F, -1.420277953e-01F, + 6.389464736e-01F, -1.048690919e-02F, 8.617702127e-01F, 3.158093393e-01F, 4.176135957e-01F, 8.668476939e-01F, + 5.989944339e-01F, -3.720080256e-01F, 6.951826811e-01F, -1.295873970e-01F, 3.431979418e-01F, 7.801601887e-01F, + 4.083960354e-01F, -3.309850693e-01F, -3.574382365e-01F, 8.224182576e-02F, 3.471310139e-01F, 8.313520551e-01F, + -3.176645339e-01F, 6.780417562e-01F, 3.085079491e-01F, -2.819872200e-01F, -1.338352263e-01F, 7.502267361e-01F, + 8.499725908e-02F, -2.496922612e-01F, 3.093895316e-01F, -8.900294453e-02F, -3.110986352e-01F, 5.982449055e-01F, + -1.348660141e-01F, 7.940340638e-01F, 4.047382474e-01F, -2.460781336e-01F, 1.489045024e-01F, 7.723398805e-01F, + -2.971286774e-01F, 7.411394119e-01F, -2.828772962e-01F, 2.399756014e-01F, 3.217331469e-01F, 5.040751696e-01F, + 3.162961602e-01F, 7.451490760e-01F, -3.772551417e-01F, 7.399884462e-01F, 8.783076406e-01F, 3.710671663e-01F, + 4.649992883e-01F, -2.944571078e-01F, 7.882573605e-01F, 6.513062119e-01F, -3.573435843e-01F, -2.390206233e-02F, + 9.436530620e-02F, 7.669261098e-01F, 1.023569480e-01F, 7.648462057e-01F, -3.452057838e-01F, -2.265439183e-01F, + 3.829133511e-02F, 4.097581804e-01F, 2.380345762e-01F, 7.252900600e-01F, 1.094363108e-01F, 3.830469251e-01F, + -1.693266034e-01F, 7.478742003e-01F, 5.584431291e-01F, 1.275866628e-01F, 3.902418613e-01F, 1.041108668e-01F, + -1.264260430e-02F, 7.125738859e-01F, 6.757175922e-01F, -1.886495948e-01F, 3.203504160e-02F, 2.339432091e-01F, + -2.073579282e-01F, 3.973917961e-01F, 8.819465041e-01F, 8.318279982e-01F, 7.243245244e-01F, 4.268783927e-01F, + 6.464518905e-01F, 4.175573289e-01F, 4.694832563e-01F, 2.820574641e-01F, -2.209891975e-01F, 5.282112360e-01F, + 5.278688669e-01F, 3.669373393e-01F, 5.951901898e-02F, -3.520807028e-01F, 6.141425371e-01F, -2.397258580e-01F, + 9.328851104e-01F, -2.296753824e-01F, 6.544040442e-01F, -1.873095036e-01F, -3.129191995e-01F, 1.048400477e-01F, + -3.365894556e-01F, 4.741140008e-01F, 8.730383515e-01F, 6.993994713e-01F, -1.662293226e-01F, 7.381193638e-01F, + 6.886125803e-01F, 1.592616141e-01F, 4.671995044e-01F, 1.911435723e-01F, 6.299538016e-01F, 4.952042699e-01F, + -1.691266149e-01F, 2.549563646e-01F, -1.504212469e-01F, -1.343561411e-01F, 3.344577551e-01F, 6.700736880e-01F, + 4.167332649e-01F, 6.404754519e-02F, -3.631082177e-02F, 4.458155036e-01F, 5.044886470e-01F, -2.053819895e-01F, + 6.486723423e-01F, -3.498142660e-01F, 5.702585578e-01F, 2.902197242e-01F, 9.210583568e-01F, -3.551872075e-01F, + -1.499426812e-01F, -1.486780867e-02F, 9.360548854e-01F, 2.073585689e-01F, -2.558003962e-01F, 8.490709662e-01F, + 5.867659450e-01F, 3.943789005e-01F, -1.176918522e-01F, 6.729320884e-01F, -9.860274196e-02F, 9.041756988e-01F, + 3.472373262e-02F, -2.120127678e-01F, -7.213338464e-02F, 3.801572919e-01F, -2.014946342e-01F, 2.774246335e-01F, + 8.530613780e-01F, 5.677468181e-01F, 1.142003536e-01F, 8.441463858e-02F, 2.968618572e-01F, 7.715069056e-01F, + 8.589490652e-01F, 4.919083714e-01F, -1.366823614e-01F, 4.873481691e-01F, -2.812913060e-01F, 5.487207174e-01F, + -1.470548064e-01F, 6.393932104e-01F, -3.017309308e-01F, 6.095537543e-01F, 2.058896422e-01F, 9.182940125e-01F, + 4.621618986e-01F, 8.987808228e-01F, 8.955482841e-01F, 7.891153693e-01F, 8.226342797e-01F, -9.310794473e-01F, + -3.933910728e-01F, -1.500548244e+00F, 7.458130121e-01F, -8.145378828e-01F, -1.607799292e+00F, -1.858587146e+00F, + -8.881159425e-01F, -1.062910914e+00F, 6.603999138e-01F, 1.455446780e-01F, 1.064992785e+00F, -1.196731806e+00F, + 1.153526664e+00F, -1.228061676e+00F, -4.048203230e-01F, -1.586266875e+00F, -1.696280479e+00F, -1.729722977e+00F, + -1.801980615e+00F, 1.601609588e+00F, -6.656720638e-01F, 1.603987366e-01F, 4.979689121e-01F, -9.795895219e-01F, + -1.063023925e+00F, -9.417611361e-02F, -1.475850940e+00F, -1.890762091e+00F, -8.645039201e-01F, 1.562502265e+00F, + 4.140533507e-02F, -1.756199002e+00F, -1.416658163e+00F, -1.454322338e+00F, -1.778343439e+00F, -1.174208164e+00F, + 4.801199436e-01F, -6.259321570e-01F, 3.018714786e-01F, 1.474965096e+00F, -1.808427811e+00F, -1.419024825e+00F, + -1.661043048e+00F, -1.662049770e+00F, 1.194706082e+00F, -1.801941276e+00F, -1.643826365e+00F, 6.636302471e-01F, + 1.245188475e+00F, -1.071975827e+00F, 1.308131069e-01F, -1.819498181e+00F, 1.209584475e+00F, -9.241363406e-01F, + 1.298449755e+00F, 1.399134398e+00F, -5.967003107e-01F, 8.232907653e-01F, -1.613480091e+00F, 4.167488217e-02F, + 6.211488843e-01F, -1.412692785e+00F, 1.150581241e+00F, 1.348729253e+00F, -5.390939116e-01F, -1.808134556e+00F, + -1.325357437e+00F, -6.312574744e-01F, -1.664349437e+00F, 1.650784969e+00F, 1.246633291e+00F, -1.719520330e+00F, + 9.355897903e-01F, -9.253705144e-01F, 1.134702325e+00F, -8.583057523e-01F, -4.518041313e-01F, -1.121666878e-01F, + 9.783449173e-01F, -1.839433193e+00F, 8.927806020e-01F, -3.203070462e-01F, -1.782903075e-01F, -3.519935310e-01F, + 7.124045491e-01F, -1.191476658e-01F, -1.097720638e-01F, -5.143767595e-01F, 1.023415446e+00F, -2.072156966e-01F, + -2.587817013e-01F, -1.826456666e+00F, -3.236234784e-01F, 5.678506494e-01F, -1.389783740e+00F, -1.154879093e+00F, + 1.239763737e+00F, 4.966047406e-01F, -8.470458388e-01F, -8.432782888e-01F, 1.219093680e+00F, -1.394188881e+00F, + -1.108892560e+00F, 1.122801661e+00F, 3.725157082e-01F, -1.043035984e+00F, 1.438107491e+00F, -6.427200437e-01F, + 7.755503058e-01F, -1.121384978e+00F, -1.146009564e+00F, 5.807831287e-01F, -1.003001690e+00F, 5.096754432e-01F, + 4.730013907e-01F, 1.119991422e+00F, 1.452155352e+00F, -9.305627942e-01F, -6.515232921e-01F, 1.017319918e+00F, + -1.739890575e+00F, 1.354849219e+00F, 1.480331182e+00F, -1.451882839e+00F, 1.296657801e+00F, 1.286923289e+00F, + 1.500821471e+00F, 3.561431170e-01F, -4.233269393e-01F, -1.003331780e+00F, -1.366426706e+00F, -4.585962594e-01F, + 1.208893135e-01F, -1.239938378e+00F, -8.936134577e-01F, 7.982954979e-01F, -1.485641837e+00F, 4.306541681e-01F, + 1.700495958e+00F, 1.435361505e+00F, -7.431954741e-01F, 1.188158274e+00F, 9.042938948e-01F, -1.616062880e+00F, + -1.358540535e+00F, -1.127118587e+00F, -2.640525401e-01F, -1.856886744e+00F, 1.379204631e+00F, 6.279976964e-01F, + 1.181945443e+00F, -1.459485173e+00F, -9.457242489e-01F, 7.286190987e-01F, -1.848719239e+00F, 1.486706257e+00F, + -8.071177602e-01F, 1.137414575e+00F, 1.554892182e+00F, -1.628962994e+00F, 1.674357057e+00F, -1.882977962e+00F, + 7.105877399e-01F, -4.059712589e-01F, -1.422652602e+00F, -1.840878129e+00F, 1.770366549e+00F, 1.480974674e+00F, + -1.883104801e+00F, -1.818309069e+00F, -1.185963511e+00F, -2.798290551e-02F, 5.438866019e-01F, 2.929928601e-01F, + -1.337405682e+00F, 1.727144837e+00F, -1.833508253e+00F, 1.493191242e+00F, 1.298743606e+00F, -8.175874949e-01F, + -4.950586557e-01F, -1.230900407e+00F, 1.492833853e+00F, -1.422952652e+00F, 1.752569675e+00F, 2.272272557e-01F, + 1.538178563e+00F, 6.308912635e-01F, -1.652695417e+00F, 4.116832092e-02F, 1.692096114e+00F, 5.577697754e-01F, + -1.605745256e-01F, -6.893481016e-01F, 1.631205678e+00F, -1.017177224e+00F, -3.377099037e-01F, -4.566328824e-01F, + 1.673553467e+00F, -3.360145688e-01F, 7.974320650e-01F, 1.026028872e+00F, -5.590292811e-01F, -8.281306624e-01F, + 9.768518806e-01F, -6.221979856e-01F, -1.139748454e+00F, 1.032037377e+00F, -1.697630405e+00F, -1.311363578e+00F, + 8.471591473e-01F, -1.891155362e+00F, -1.607266307e+00F, 6.327275038e-01F, 2.443218827e-01F, -3.032463193e-01F, + 1.331165314e+00F, 1.108452201e+00F, 1.162839174e+00F, 1.135922074e+00F, -1.730082482e-01F, 4.467081130e-01F, + -1.758722901e+00F, -1.876158595e+00F, 1.349527597e+00F, 1.777557135e+00F, 1.511219144e+00F, -1.301660538e+00F, + -9.380344748e-01F, 2.128135413e-01F, 1.349947333e+00F, -2.980944216e-01F, -1.410171509e+00F, -1.055992961e+00F, + 5.979869887e-02F, -7.786969543e-01F, 1.591328382e+00F, -5.630584955e-01F, 1.894161701e-01F, -9.796315432e-01F, + 5.046239495e-01F, 1.770424247e-01F, -1.224571705e+00F, -6.789293289e-01F, -8.358801603e-01F, -4.766264558e-01F, + 2.394245416e-01F, -8.143131137e-01F, 5.804842710e-01F, 1.185652256e+00F, 3.846624494e-01F, 6.533800364e-01F, + -7.128174901e-01F, 4.608623087e-01F, 1.674124956e+00F, -3.458429277e-01F, 4.168368578e-01F, -7.075027227e-01F, + 7.738572359e-01F, 7.761216164e-01F, 2.040232182e+00F, -7.145895362e-01F, -6.606688499e-01F, -4.799076319e-01F, + -6.172255278e-01F, 1.699550033e+00F, -4.700908065e-01F, 3.113278071e-04F, -3.228164613e-01F, -1.536759641e-02F, + 1.721532941e+00F, -3.165886700e-01F, -1.686776131e-01F, 3.447774053e-01F, 1.023449451e-01F, 1.092543527e-01F, + 1.458123684e+00F, 1.473904490e+00F, 1.142795444e+00F, -2.615384459e-01F, 1.378392577e+00F, 8.867146075e-02F, + 1.060641527e+00F, 7.927377224e-01F, -3.506691158e-01F, -4.762987196e-01F, 1.648496747e+00F, 2.075530767e+00F, + 1.338338614e+00F, 9.515404701e-01F, 3.379337788e-01F, 5.238951445e-01F, 1.323279977e+00F, 1.640440464e+00F, + -3.251858950e-01F, 1.371461153e+00F, 7.996396422e-01F, 1.401394606e+00F, -2.545595169e-01F, 1.470392104e-02F, + 1.881048083e+00F, 7.868690491e-01F, 1.028112769e+00F, -7.336190343e-01F, 1.074898958e+00F, -2.654800415e-01F, + 1.475285649e+00F, 1.116396189e+00F, -2.373265624e-01F, 4.824057221e-01F, 2.964201756e-02F, 1.360930949e-01F, + 1.824715137e-01F, 1.701882184e-01F, -4.316045344e-01F, 9.331944585e-01F, 1.838805676e+00F, 1.495110989e-01F, + 2.953099310e-01F, -5.390303135e-01F, 2.987341583e-01F, -4.084130824e-01F, 2.004989147e+00F, -3.855883777e-01F, + 9.841612726e-02F, 1.220504761e+00F, 9.297986031e-01F, 8.466026783e-01F, 1.239450872e-01F, 2.018570423e+00F, + 1.482603908e+00F, 1.970268846e+00F, 5.087848902e-01F, 1.137083292e+00F, 1.504699349e+00F, 1.139631748e+00F, + -2.905278653e-02F, 4.391726553e-01F, 2.066413164e+00F, 4.642412961e-01F, 1.393335104e+00F, -9.280166775e-02F, + -3.680856824e-01F, 7.981830239e-01F, 6.133755445e-01F, 7.937285304e-01F, 4.335603416e-01F, 9.986534119e-01F, + 2.987370491e-01F, 1.903530359e-01F, 9.935685396e-01F, 1.622917295e+00F, 1.401247501e+00F, 1.525818706e+00F, + 1.917773724e+00F, 1.676602721e+00F, 1.933823377e-01F, -6.491324902e-01F, -8.071522117e-01F, 3.297113180e-01F, + 1.415086865e+00F, 1.326936841e+00F, 1.887058496e+00F, 1.705103993e+00F, -9.136317298e-03F, 1.667027548e-02F, + -5.829775333e-01F, 1.420943975e+00F, -4.293112755e-01F, 2.932177782e-01F, 1.290065408e+00F, -2.992335856e-01F, + 1.058434248e+00F, 1.055640101e+00F, 5.912424326e-01F, 3.923750818e-01F, 4.850946665e-01F, -6.859942675e-01F, + -3.245630860e-01F, -5.415931940e-01F, 4.750089347e-01F, 2.774907835e-02F, 4.133041501e-01F, 8.708555996e-02F, + 1.386759043e+00F, 2.159569710e-01F, 2.083554029e+00F, 1.428168893e+00F, -3.836219311e-01F, 1.400144339e+00F, + 1.552331209e+00F, -6.723220348e-01F, 6.035015583e-01F, 1.771974802e+00F, 4.620397389e-01F, 7.041393518e-01F, + -2.236479372e-01F, 1.372190356e+00F, 1.480355144e+00F, 4.328353405e-01F, 1.534908652e+00F, -5.346488953e-01F, + 1.666253328e+00F, 1.588213921e+00F, 1.406947374e+00F, 6.712723970e-01F, -6.933228970e-01F, -4.915204048e-01F, + -6.885030866e-01F, -2.481472641e-01F, 1.531762481e+00F, 4.914970696e-02F, -4.114178419e-01F, -3.299647570e-01F, + -7.860244513e-01F, -6.013127789e-02F, 1.964339137e+00F, 2.007743567e-01F, 1.781391382e+00F, 7.904550433e-01F, + -5.357052088e-01F, -5.424223542e-01F, -6.738526225e-01F, 1.979719639e+00F, 1.816095352e+00F, 1.694902420e+00F, + 4.441099763e-01F, 5.424606800e-01F, -2.459565997e-01F, 1.789957523e+00F, 5.974689722e-01F, -7.043283582e-01F, + 1.658022285e+00F, -7.153555155e-01F, 2.709684670e-01F, 1.216010571e+00F, -6.474583149e-01F, -5.288992524e-01F, + 1.464298964e+00F, -6.898429990e-01F, -4.798209369e-01F, -6.637329459e-01F, 2.726794481e-01F, 1.314396739e+00F, + 2.677512169e-01F, -1.968669295e-01F, 1.000071406e+00F, 7.574311644e-02F, 1.119585156e+00F, 1.009004235e+00F, + -5.734546185e-01F, -5.821155310e-01F, 4.910971597e-02F, 1.940495253e+00F, -6.919752061e-02F, -8.312872797e-02F, + 2.695063651e-01F, 2.002412558e+00F, 1.725616455e+00F, 1.034172416e+00F, 2.023341179e+00F, -2.033348233e-01F, + 1.085088730e+00F, -7.085729837e-01F, -5.396309495e-01F, -7.101830840e-01F, 2.079326391e+00F, 1.619812250e+00F, + 2.050996542e+00F, 1.290184140e+00F, 1.768423915e+00F, 5.334734321e-01F, -6.505182981e-01F, 4.786348939e-01F, + -4.143879712e-01F, 8.889412880e-01F, 8.953039050e-01F, 1.301094770e+00F, -2.816871107e-01F, 2.051441669e+00F, + -3.217720985e-01F, -2.272484750e-01F, 5.745131969e-01F, 1.344103456e+00F, -3.152041733e-01F, 1.097996950e+00F, + 1.406530261e+00F, -3.643126786e-01F, -8.045493364e-01F, 1.091153741e+00F, -3.808220327e-01F, 1.554867625e+00F, + 1.316006064e+00F, 2.342783809e-01F, 6.209475994e-01F, 6.490681767e-01F, -2.745364904e-01F, -4.448469728e-02F, +}; + +inline constexpr float kSegPooling[] = { + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, + -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -4.303080495e-03F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, -3.793735430e-02F, + -3.793735430e-02F, -3.793735430e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, + 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 1.240370423e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, 6.709390134e-02F, + 6.709390134e-02F, 6.709390134e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, 3.307950869e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, -1.889597438e-02F, + -1.889597438e-02F, -1.889597438e-02F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, -1.191449192e-04F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, + 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 5.618155003e-03F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, 4.500975087e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, + -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -1.778247207e-02F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, -6.650165189e-03F, + -6.650165189e-03F, -6.650165189e-03F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, + 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, 1.016459148e-02F, +}; + +inline constexpr float kCamOut[] = { + 2.141402364e-01F, 1.629903018e-01F, -3.135347739e-02F, -1.089277938e-01F, 6.614306569e-02F, 3.881603479e-03F, + -3.436809778e-01F, -3.981447220e-02F, -1.414220333e-01F, 1.471896172e-01F, -1.176117361e-01F, 2.422043495e-02F, + -3.362451196e-01F, 4.152579010e-01F, -3.517737091e-01F, -3.044604063e-01F, -1.800719351e-01F, 1.948019266e-01F, + -1.522460952e-02F, 9.841810912e-03F, -7.345686853e-02F, 3.736168146e-02F, -3.473892510e-01F, -5.279959366e-02F, + -6.809151173e-02F, 2.990382016e-01F, -5.264108256e-02F, 6.067845225e-02F, -3.442028463e-01F, 1.103208661e-01F, + -4.846980795e-02F, 2.983191907e-01F, -7.418157160e-02F, 1.006188169e-01F, -1.660054922e-01F, 1.408527195e-01F, + -1.575157046e-01F, -4.892523587e-02F, 2.034029216e-01F, 1.295891851e-01F, -4.196640104e-02F, 6.885848939e-02F, + 1.426176280e-01F, 4.167740792e-02F, -1.076271012e-02F, 1.729386151e-01F, 1.773745269e-01F, -9.594069421e-02F, + -3.151847422e-02F, 6.409297884e-02F, -2.470554560e-01F, 1.616607159e-01F, 9.065511823e-02F, -2.827847898e-01F, + -1.459558755e-01F, 1.175332144e-01F, -2.300819755e-01F, 1.236230209e-01F, 4.618959501e-02F, -1.232862845e-01F, + -1.506796628e-01F, 9.770649672e-02F, 9.092372656e-02F, 5.105291028e-03F, -9.328560531e-02F, 2.307729563e-03F, + 5.678829551e-02F, 3.937169611e-01F, 2.351298034e-01F, -4.959933460e-02F, -4.341674596e-02F, -1.596652269e-01F, + 1.731791496e-01F, 2.678788602e-01F, 2.406468540e-01F, 1.214314718e-02F, -3.640739620e-01F, -1.738548577e-01F, + -8.842732757e-02F, -9.491326660e-02F, -3.097639084e-01F, -1.879651845e-01F, 1.211403161e-01F, -3.850037158e-01F, + -1.653465927e-01F, 1.111064553e-01F, 1.300769150e-01F, 1.517647356e-01F, 2.430541366e-01F, -9.968010336e-02F, + -1.239885986e-01F, 6.822680682e-02F, -6.241570041e-02F, -8.337501436e-02F, -2.672529221e-02F, 1.299791783e-01F, + -1.085198671e-01F, -2.328413725e-01F, -1.568683237e-01F, -1.095176712e-01F, -1.537617594e-01F, 1.832641065e-01F, + 2.418304235e-02F, -1.271283627e-01F, 2.540079318e-02F, 1.361719072e-01F, -7.768063247e-02F, 1.798701733e-01F, + 1.701876968e-01F, -4.597243294e-02F, -3.689146787e-02F, -1.256364137e-01F, 6.220883504e-02F, -2.311330438e-01F, + -4.306050017e-02F, -8.259904385e-02F, -2.506719232e-01F, -3.251737952e-01F, -4.652327672e-02F, 1.386815459e-01F, + -9.007783979e-02F, -1.027687490e-01F, 5.508138984e-02F, 2.184426337e-01F, 1.055453941e-01F, 2.506908029e-03F, + -1.296692044e-01F, 1.977900565e-01F, -8.879961073e-02F, 1.534050144e-02F, -3.846796229e-02F, 5.734517425e-02F, + -4.613142833e-02F, -9.643769264e-02F, 6.062148884e-02F, -2.887004614e-01F, 8.702143282e-02F, 1.986400187e-01F, + -1.858224124e-01F, -9.644728154e-03F, 2.185085267e-01F, -1.530835927e-01F, -2.015287429e-01F, -9.871330857e-02F, + -1.069440991e-01F, 4.116947576e-02F, 1.268741935e-01F, -1.571932286e-01F, 1.585000008e-01F, -2.951902151e-02F, + -6.654544920e-02F, 3.829584271e-02F, 1.515505910e-01F, 4.285616800e-02F, -8.225187659e-03F, 7.074952871e-02F, + -1.884022802e-01F, -3.773140488e-03F, -9.077847749e-02F, 2.924268842e-01F, -6.365364790e-02F, -1.266926974e-01F, + -1.196799055e-01F, 1.687890142e-01F, -9.652125649e-03F, 1.918958575e-01F, -1.897625774e-01F, -2.517760694e-01F, + -1.343761943e-02F, 2.336612791e-01F, -2.182663530e-01F, -1.718774810e-02F, 4.157760367e-02F, 7.418771740e-03F, + -1.610690057e-01F, -2.509329654e-02F, -2.566138282e-02F, 1.203730851e-01F, 8.963651210e-02F, -1.061279774e-01F, + 2.686383128e-01F, 1.437454950e-02F, 2.725990713e-01F, -1.280735433e-01F, -1.271179020e-01F, -2.935676873e-01F, + 3.576220870e-01F, 1.457037032e-02F, 1.493640989e-01F, 7.269680034e-03F, 1.713864803e-01F, -2.613587081e-01F, + -1.258099079e-01F, 1.389646828e-01F, 1.116226315e-01F, -2.620130479e-01F, -2.455088049e-01F, -6.295262370e-03F, + 1.083317176e-01F, 2.411414236e-01F, -6.710893009e-03F, -2.101025283e-01F, 1.444005519e-01F, -1.668644249e-01F, + 2.888410091e-01F, 1.282257736e-01F, -1.269434243e-01F, 1.766383201e-01F, -4.613642395e-02F, -1.174832657e-01F, + 9.762755781e-02F, 7.845254987e-02F, 6.350764632e-02F, 2.032231688e-01F, -2.446594387e-01F, 1.954187267e-02F, + 1.660126001e-01F, 6.552489102e-02F, -1.776993722e-01F, 2.052565664e-01F, -1.915314198e-01F, -2.773903906e-01F, + 3.805746883e-02F, 1.559026819e-02F, -1.399479955e-01F, 1.063200757e-01F, 9.417101741e-02F, 3.358703107e-02F, + 1.156829968e-01F, -1.745771803e-02F, -8.136089891e-02F, 1.166243106e-01F, -1.105817482e-01F, -7.248546183e-02F, + -3.076086342e-01F, 3.328728676e-01F, -7.483632863e-02F, 1.654508859e-01F, -2.829714715e-01F, 2.601861656e-01F, + -4.424654879e-03F, -1.049451344e-02F, -1.692514718e-01F, 1.601648629e-01F, -3.034690917e-01F, -5.634678528e-02F, + -1.191773638e-01F, -2.727690153e-02F, 2.408462577e-02F, -1.356648058e-01F, -2.985223234e-01F, -2.372950315e-01F, + -3.545616567e-01F, -3.885919154e-01F, -5.104715750e-02F, -1.123873293e-01F, -5.489460751e-02F, 4.157244414e-02F, + -1.273930222e-01F, 2.282143682e-01F, -1.533623040e-01F, 1.263554990e-01F, -4.846444353e-02F, -1.856883615e-01F, + 1.612564363e-02F, -1.247244626e-01F, -4.845613241e-02F, -6.183398142e-02F, -1.716306433e-02F, -2.449672855e-02F, + -1.246189475e-01F, 5.139274895e-02F, 2.503855526e-01F, 9.853186458e-02F, 1.459560394e-01F, -6.988989562e-02F, + -1.175293848e-01F, -3.308180571e-01F, -1.724123210e-01F, 5.809630826e-02F, -1.368281394e-01F, 2.006013878e-02F, + -9.445729107e-02F, -4.586207680e-03F, 1.918444037e-01F, 7.926347107e-02F, 2.923615873e-01F, -1.166774705e-01F, + 8.463422209e-02F, -4.981132597e-02F, -1.452348232e-01F, 6.229596958e-02F, 1.447377652e-01F, -3.574577868e-01F, + 2.004494816e-01F, -2.516633570e-01F, -3.783014417e-02F, -2.079602629e-01F, -9.253471345e-02F, -1.473150253e-01F, + 6.348735839e-02F, -2.951620519e-01F, 1.409954429e-01F, -7.891192287e-02F, 5.687647313e-02F, 1.775422841e-01F, + 3.485793471e-01F, -1.313757747e-01F, 2.561978102e-01F, 1.245851740e-01F, 1.877997816e-01F, 3.798812628e-01F, + -6.125742197e-02F, 9.130702168e-02F, 1.033084542e-01F, 4.184054434e-01F, 3.690876067e-01F, -1.306609064e-01F, + 9.348855168e-02F, -2.841673195e-01F, 1.656858325e-01F, -1.323807836e-01F, 2.660595179e-01F, 6.765573751e-03F, + 8.780156821e-02F, -7.566502690e-02F, 9.304832667e-02F, 2.252344340e-01F, 4.198883101e-02F, 2.611718476e-01F, + 1.546245068e-01F, 1.460592896e-01F, -1.201419905e-01F, 4.730503261e-01F, -9.093035012e-02F, 1.723528057e-01F, + 5.793103948e-02F, -5.890880898e-02F, -1.854786873e-01F, 1.596828103e-01F, -3.641863540e-02F, 1.901560873e-01F, + 1.822460443e-01F, 4.691369832e-01F, -1.281490433e-03F, 2.249198109e-01F, 6.611284614e-02F, -9.574525058e-02F, + -1.408735942e-02F, -2.796210051e-01F, -1.019135350e-03F, -5.352811813e-01F, 3.572192788e-02F, -3.844687939e-01F, + -2.685866691e-02F, 7.588090003e-02F, 3.367809113e-04F, 1.148769408e-01F, -3.272685781e-02F, -9.899853170e-02F, + -1.593749076e-01F, 8.859094232e-02F, 1.868129373e-01F, 9.303091466e-02F, 1.545992121e-02F, 2.588278949e-01F, + 2.452173680e-01F, 3.250672519e-01F, 2.758764923e-01F, 7.229230553e-02F, 4.127926230e-01F, 3.693301603e-02F, + -1.878283545e-02F, 2.059506252e-02F, 9.113039821e-02F, -1.802317947e-01F, -3.332493901e-01F, 5.422794446e-02F, + 2.763094902e-01F, 1.025382988e-02F, -1.441834420e-01F, 1.892621815e-01F, 1.442491561e-01F, 3.199083358e-02F, + -1.620543003e-01F, 1.815260202e-01F, -8.254701272e-03F, -1.167475134e-01F, 1.037108451e-01F, -2.283436991e-02F, + 1.134807095e-01F, -5.025903508e-02F, 8.442137390e-02F, 2.905143499e-01F, 1.957493722e-01F, -9.959761798e-02F, + -4.367309064e-02F, -5.848019198e-02F, -4.534605742e-01F, 5.581416190e-02F, -2.315297127e-01F, -3.433375433e-02F, + -1.078166813e-01F, 3.026598990e-01F, -1.628918946e-02F, 1.417988539e-01F, 8.082633466e-02F, -1.439440548e-01F, + 2.648880333e-02F, -1.460361630e-01F, -3.538554162e-02F, -1.141405702e-01F, -1.586441100e-01F, -8.012406528e-02F, + 1.637024134e-01F, -1.192043722e-01F, -4.155514762e-02F, 1.465270109e-02F, 1.798952818e-01F, 2.693433762e-01F, + -1.742510796e-01F, -1.425910592e-01F, -2.005113959e-01F, 1.393773705e-01F, -2.237370163e-01F, 1.824112236e-01F, + -3.856549272e-03F, 1.993503273e-01F, 5.411503837e-02F, 1.693536490e-01F, 2.353303283e-01F, -2.790507376e-01F, + -9.181449562e-02F, -2.654973567e-01F, 8.717564493e-02F, -5.643673241e-02F, 5.198718235e-02F, 1.152660772e-01F, + -9.772383422e-02F, -1.473841965e-01F, 1.930573285e-01F, -2.508386374e-01F, 4.780839756e-02F, -1.705464721e-01F, + 7.994092256e-02F, -2.913819551e-01F, 3.687345237e-02F, 3.806764185e-01F, -1.555708200e-01F, 1.036879346e-01F, + -1.685733348e-01F, 4.089440107e-01F, 2.269313037e-01F, 3.730624616e-01F, -1.843331009e-02F, 1.136174574e-01F, + 4.962772429e-01F, -3.390379250e-01F, 1.853100359e-01F, -1.529406905e-01F, -2.860285044e-01F, 2.684485018e-01F, + -4.362545311e-01F, 2.287850678e-01F, -5.261182785e-01F, 2.087592483e-01F, -2.944425642e-01F, 3.550033867e-01F, + -2.224727124e-01F, -2.089425623e-01F, 1.807127893e-01F, 8.145938814e-02F, 2.977216840e-01F, 3.236897588e-01F, + 9.207761846e-03F, -1.181376725e-02F, 1.126640961e-01F, -1.948837042e-01F, -4.001078308e-01F, -3.035211861e-01F, + -1.646989286e-01F, -2.352250665e-01F, -1.573813111e-01F, -2.059379406e-02F, -1.233690977e-01F, -1.629344374e-01F, + 5.759380013e-02F, -1.359344125e-01F, -2.409064472e-01F, -1.913328189e-03F, -1.599913239e-01F, -6.810136884e-02F, + -1.573188305e-01F, -2.800395014e-03F, 3.185673058e-01F, -2.703174353e-01F, 6.149197817e-01F, -9.790419042e-02F, + 2.257921845e-01F, -2.448787689e-01F, 2.000714093e-01F, 3.337047994e-01F, -2.909873612e-02F, 1.938844770e-01F, + 1.729310900e-01F, 1.033385769e-01F, 7.634737529e-03F, -9.710461646e-02F, -1.203365102e-01F, 6.555165350e-02F, + -1.630906612e-01F, -2.721762657e-02F, 1.470848266e-02F, 3.292041421e-01F, -2.563211918e-01F, -4.423286766e-02F, + -2.689380050e-01F, -1.685909927e-01F, 3.958673216e-03F, -4.428101704e-02F, 5.313190445e-02F, 9.750763327e-02F, + -3.925315142e-01F, -2.839142382e-01F, -3.032095730e-01F, 9.146885574e-02F, 2.233606130e-01F, 1.362460256e-01F, + 6.372315343e-03F, -1.293552816e-01F, -2.352147251e-01F, -5.257336795e-02F, 4.260914400e-02F, -8.334818482e-02F, + -2.176743187e-02F, 2.788843215e-01F, -3.254318237e-01F, -1.303827912e-01F, 1.974986866e-02F, -2.601256967e-02F, + -8.076798986e-04F, 5.632023141e-02F, 5.083699524e-02F, 2.398221642e-01F, -8.357670158e-02F, 3.271901608e-01F, + -1.219003275e-02F, 2.982496619e-01F, 2.578371167e-01F, 7.061185688e-02F, -2.506397665e-01F, 3.015561998e-01F, + 1.342002749e-01F, -2.455090135e-01F, -1.204677150e-01F, 1.053658426e-01F, -2.774514556e-01F, 1.546749920e-01F, + -1.543184817e-01F, -4.682416469e-02F, -1.135576591e-01F, 8.709397167e-02F, 2.964294851e-01F, -4.855551943e-02F, + 1.609153696e-03F, -2.786875665e-01F, 1.256558150e-01F, 1.904167086e-01F, 1.432597521e-03F, 2.493754625e-01F, + -1.293171942e-01F, -7.727087289e-02F, -3.491207361e-01F, 7.300223410e-02F, 7.621576637e-02F, 2.825644612e-01F, + -8.048793674e-02F, -2.263881266e-01F, -2.211253494e-01F, -5.955261365e-02F, -9.821254760e-02F, -3.355771676e-02F, + 7.292540371e-02F, -3.224142492e-01F, -2.413895130e-01F, -1.884828880e-02F, -1.981263906e-01F, 8.168395609e-02F, + 3.660948277e-01F, 1.973964423e-01F, 2.412462085e-01F, -7.477702945e-02F, 5.038551055e-03F, -2.434360981e-01F, + -2.839529812e-01F, 1.134456396e-01F, 2.550916560e-02F, -5.466492847e-02F, 4.130262136e-02F, -3.942107409e-02F, + -3.933456838e-01F, 3.512420654e-01F, -1.732960641e-01F, 1.008285359e-01F, -8.265728503e-02F, -1.552637108e-02F, + 7.420425862e-02F, 1.450845450e-01F, 2.486256808e-01F, 2.095334046e-02F, 1.820367277e-01F, -7.830302417e-02F, + -5.969356745e-02F, -1.370586604e-01F, 2.236990929e-01F, -7.290255278e-02F, -1.957755536e-01F, -3.269714117e-01F, + -1.193588376e-01F, -4.241994396e-02F, -2.025650740e-01F, -3.323481083e-01F, -9.965986758e-02F, 2.568146288e-01F, + -8.842758834e-02F, -1.072742790e-01F, 8.512944728e-02F, 4.376471415e-02F, -1.662442982e-01F, -1.413645297e-01F, + -8.628250659e-02F, 6.972683966e-02F, -2.399795800e-01F, 1.404106468e-01F, 1.029302180e-01F, -4.170496464e-01F, + -2.771163546e-02F, -6.889635324e-02F, -2.266590148e-01F, 3.186344206e-01F, -7.012388855e-02F, 1.402222663e-01F, + -1.083473116e-01F, -1.019391194e-01F, -2.942257822e-01F, -8.096040785e-02F, -1.106711291e-02F, 8.312079310e-02F, + 3.471544981e-01F, 1.654312387e-02F, 1.967008710e-01F, -5.384420976e-02F, 3.338587284e-01F, -1.591147035e-01F, + 2.029963881e-01F, 1.520123631e-01F, -1.606250368e-02F, -6.702471524e-02F, -2.491230965e-01F, -5.931594875e-03F, + 1.289071590e-01F, 1.000744551e-01F, -1.796738952e-01F, 2.489733510e-02F, -7.821685821e-02F, 1.601745486e-01F, + -2.664788961e-01F, 9.045707411e-04F, -1.134891212e-01F, -1.677996516e-01F, 1.851300150e-02F, 2.081087977e-01F, + 9.212723933e-03F, -3.829684341e-03F, -3.797593713e-02F, -2.700093091e-01F, -3.330314159e-02F, -1.021770611e-01F, + -1.130477786e-01F, -1.898062229e-01F, 1.816057265e-01F, 2.355312407e-01F, 4.071789086e-01F, 1.612854749e-01F, + 4.418157414e-02F, -1.672173738e-01F, 9.045495838e-02F, -1.648408771e-01F, 3.502321243e-02F, 8.332471550e-02F, + 2.270808518e-01F, -1.340648979e-01F, -1.360472199e-02F, 3.552403450e-01F, -5.223969370e-02F, 1.327477396e-01F, + -6.940771639e-02F, -2.252144217e-01F, 4.890347645e-02F, 1.214067489e-01F, 5.580199510e-02F, -1.017105356e-01F, + 3.933233768e-02F, -5.057944059e-01F, 1.787621826e-01F, -1.284837276e-01F, -1.374425441e-01F, 3.913329244e-01F, + -1.364161968e-01F, 1.740917265e-01F, -7.288320363e-02F, 2.058092318e-02F, 2.769298255e-01F, -1.136884615e-01F, + 1.621472090e-01F, -2.744716108e-01F, 1.777161062e-01F, -1.880510449e-01F, 1.186054200e-01F, 2.166649550e-01F, + -2.419505119e-01F, 1.514018476e-01F, -5.821174383e-02F, -1.522868276e-01F, -2.723606303e-02F, -7.157117128e-02F, + -1.932421923e-01F, 1.403774172e-01F, 3.859850764e-02F, -4.173072800e-02F, 1.997431275e-03F, 1.265222859e-02F, + 3.120918870e-01F, 2.187387645e-02F, -4.567826539e-02F, 1.953824610e-01F, -1.862350255e-01F, -2.865367197e-02F, + 7.672464103e-02F, 2.260081470e-01F, 4.853106663e-02F, -6.356312335e-02F, 1.848215014e-01F, 1.629672796e-01F, + -4.143000245e-01F, -7.228872180e-02F, -3.333045542e-01F, 1.413324475e-01F, -2.226627022e-01F, 1.073361784e-01F, + 1.048622578e-01F, 4.466899484e-02F, 3.241675496e-01F, 1.826128066e-01F, -1.605753750e-01F, -4.745441303e-02F, + 1.014942527e-01F, 4.856013879e-02F, 2.279228121e-01F, -2.731133699e-01F, 2.044633329e-01F, 1.419631392e-01F, + -1.101802215e-01F, -1.953048818e-02F, 1.692490876e-01F, 1.514306515e-01F, 5.714444071e-02F, 1.176400334e-01F, + 1.833199561e-01F, -3.566860408e-02F, 3.814553842e-02F, -1.878646612e-01F, -2.397772856e-02F, 5.251331095e-05F, + -1.302066147e-01F, -2.857860178e-02F, 1.491483599e-01F, 2.151815034e-02F, 2.625767887e-01F, 8.348638192e-03F, + 1.388736665e-01F, -1.157609001e-01F, 2.346283756e-02F, -2.896639407e-01F, -1.411754191e-01F, -1.992161572e-01F, + 1.186986789e-01F, 1.728571206e-01F, -3.229103088e-01F, -1.862050742e-01F, 1.406949759e-02F, 1.695173830e-01F, + -2.288717479e-01F, 8.464720100e-02F, -2.827802114e-02F, 7.478839159e-02F, -7.095924020e-02F, 2.173121572e-01F, + -1.296070069e-01F, 1.346046329e-01F, 1.850022376e-01F, -6.732805818e-02F, -1.316139475e-03F, 4.065303504e-02F, + -1.314048748e-02F, 3.424953436e-03F, -1.233556792e-01F, 1.578065604e-01F, -2.105633467e-01F, -1.848731041e-01F, + -1.192155257e-01F, -8.490137011e-02F, -2.114699036e-01F, -2.946865261e-01F, 7.149727643e-02F, 5.388428643e-02F, + 5.589700490e-02F, 3.483892605e-02F, -1.177445985e-02F, -1.666403860e-01F, -2.308498174e-01F, 2.037932128e-01F, + 4.032107070e-02F, 2.834599651e-02F, -1.393628716e-01F, 4.879494756e-02F, 3.635932133e-02F, -1.221544854e-02F, + -6.875495613e-02F, 2.272231504e-02F, 2.018633932e-01F, -1.010152921e-01F, 2.702250779e-01F, -6.187666208e-02F, + -1.060121357e-01F, 4.467458278e-02F, 8.162009530e-03F, -4.708737135e-02F, -2.532742918e-01F, 3.571304306e-02F, + 8.021222800e-02F, -7.213497907e-02F, 1.656069010e-01F, -1.876264364e-01F, -2.635847218e-02F, 1.318911761e-01F, + -9.932493791e-03F, 8.762092143e-02F, 1.760291308e-01F, 2.853555679e-01F, 2.716597915e-01F, 1.031324267e-01F, + -1.382920444e-01F, 1.190007553e-01F, -7.713814825e-02F, -1.728102863e-01F, -1.039289683e-02F, 1.169349998e-01F, + 1.400438696e-02F, -6.337586790e-02F, 8.773098141e-02F, 8.156987280e-02F, 4.666690901e-02F, 3.953804821e-02F, + -1.135882214e-01F, -6.804607809e-02F, 2.056628838e-02F, 3.278331831e-02F, -1.514716893e-01F, -7.831074297e-02F, + 1.515166909e-01F, 3.852671012e-02F, -6.470779330e-02F, -4.355356842e-02F, 2.217990160e-01F, 3.023844399e-02F, + -1.345071048e-01F, -1.003757715e-01F, 4.533469677e-02F, -1.113744900e-01F, 8.376756310e-02F, 8.111052215e-02F, + -9.263113141e-02F, -1.081585698e-02F, 3.857607022e-02F, 3.327940404e-01F, -6.546857208e-02F, -2.546835542e-01F, + -1.530913264e-01F, -4.934815317e-02F, -2.237937152e-01F, 6.808197498e-02F, -1.548797935e-01F, 2.027617693e-01F, + -9.841343760e-02F, 1.054512709e-01F, 4.537334666e-02F, -6.301181763e-02F, -3.828914464e-02F, 1.920094192e-01F, + 1.988874674e-01F, 1.411058754e-01F, 7.646321505e-02F, -1.885022670e-01F, 1.937018186e-01F, -6.543530524e-02F, + 1.634106636e-01F, 1.386418194e-01F, 1.112095919e-02F, -7.129587978e-02F, 5.120681226e-02F, -1.547387391e-01F, + 2.864317000e-01F, 1.911786348e-01F, -2.128180265e-01F, -9.601763636e-02F, 3.070390085e-04F, -3.697939590e-02F, + -1.766437292e-01F, 1.718061790e-02F, -1.114650667e-01F, -1.266114414e-01F, 2.767363787e-01F, -6.761566550e-02F, + 2.003650963e-01F, 1.939103603e-01F, -7.738803327e-02F, -3.282482624e-01F, -1.115769967e-01F, 1.655596197e-01F, + -1.857896000e-01F, -2.163237892e-02F, -1.712120026e-01F, 1.567033678e-01F, -5.180859007e-03F, 2.596228421e-01F, + 1.392268669e-02F, 2.159842849e-01F, -1.255977899e-01F, 7.707631588e-02F, -1.722585559e-01F, 5.009933561e-02F, + 2.289877981e-01F, 5.391423777e-02F, -1.354487091e-01F, 3.278645128e-02F, 8.026067168e-02F, 1.356572509e-01F, + -4.652971029e-02F, -1.978766024e-01F, -1.362774670e-01F, -1.488918215e-01F, 1.870353892e-02F, -9.841666371e-02F, + -3.074529767e-01F, -1.213746145e-01F, -2.322306186e-01F, 2.254468761e-02F, 1.762013696e-02F, -2.462560311e-02F, + 8.288809657e-02F, 1.050796658e-01F, 2.148795575e-01F, -4.637644067e-02F, 1.813910156e-01F, -2.137775421e-01F, + 2.132926285e-01F, -2.752059512e-02F, 1.524363458e-01F, -7.627484947e-02F, -4.756737500e-02F, -1.278198510e-01F, + -1.407018453e-01F, 1.199056953e-01F, -8.012977988e-02F, -2.088816278e-02F, 2.222599238e-01F, -1.087842211e-01F, + 7.093944401e-02F, 2.382875085e-01F, -3.606960922e-02F, -2.463877201e-01F, 1.586781740e-01F, 3.037844971e-02F, + 2.108561546e-01F, -1.033986285e-01F, 1.412003338e-01F, 4.155199602e-02F, 6.394240260e-02F, -3.298176527e-01F, + 2.395564467e-01F, 1.597493514e-02F, 1.229501963e-01F, 1.711018980e-01F, -6.979029626e-02F, 1.209737882e-01F, + -1.625985354e-01F, 4.035837576e-02F, -1.088611707e-01F, 1.085111052e-01F, -1.223221421e-02F, -2.663618699e-02F, + -2.087176032e-02F, -1.927122623e-01F, -2.459328920e-01F, 2.321845740e-01F, -5.903954804e-02F, 2.150892466e-01F, + -1.333296485e-02F, 4.760921374e-02F, -1.750536263e-01F, 3.505396424e-03F, -8.190236986e-02F, -1.960998923e-01F, + 9.634251893e-02F, 1.167872623e-01F, -6.000956520e-02F, -9.569927305e-02F, 5.563295260e-02F, 4.057709593e-03F, + -1.164135113e-01F, 1.098104790e-01F, 9.951805323e-02F, 4.801275954e-02F, -2.365446538e-01F, -2.156470120e-01F, + -2.549593896e-02F, 6.611454487e-02F, -1.128360536e-02F, 2.534074783e-01F, -1.350063086e-01F, -1.497804075e-01F, + 8.445001394e-02F, -2.625392973e-01F, 1.821655333e-01F, 2.825025022e-01F, 3.506082669e-02F, -1.209245846e-01F, + -1.535844356e-01F, -8.625866473e-02F, 1.509633660e-01F, 9.691457450e-02F, 1.876671165e-01F, -4.011184871e-01F, + 3.924952447e-02F, 1.086077914e-01F, -4.018698633e-01F, 9.317259490e-02F, 1.997814924e-01F, -2.379930764e-01F, + -7.880704477e-03F, 1.536282003e-01F, -2.486622147e-02F, 4.486738145e-02F, 5.077668931e-03F, 4.380859435e-02F, + 1.141948104e-01F, -1.236538123e-02F, 5.295853317e-02F, 1.982518472e-02F, -7.220803946e-02F, 2.268621027e-01F, + 9.320334345e-02F, 4.548493773e-02F, 3.364000320e-01F, 3.083848357e-01F, 1.540845782e-01F, -4.743848741e-01F, + -1.545555890e-01F, -5.946942791e-02F, 2.942073047e-01F, 2.696125507e-01F, 7.670826465e-02F, -1.064434648e-01F, + -3.588648438e-01F, 1.643124782e-02F, -5.442558974e-02F, -3.779457137e-02F, -9.276970476e-02F, -1.095783561e-01F, + 1.999351829e-01F, 1.223445684e-01F, -2.016326338e-01F, 2.255173922e-01F, 1.226234734e-01F, 1.634714007e-02F, + 1.646967977e-02F, -2.847835422e-02F, -1.789938956e-01F, 2.098367065e-01F, 1.694821268e-01F, 1.024333015e-01F, + 1.380023807e-01F, 1.460551322e-01F, -3.317235410e-01F, -1.767082661e-01F, 6.347235292e-02F, -2.937624454e-01F, + 1.551583707e-01F, -5.675145239e-02F, -1.428561509e-01F, 4.794099554e-02F, -4.780802876e-02F, 1.084899753e-01F, + 9.085772559e-03F, 1.540809572e-01F, -1.036494388e-03F, 4.893045872e-02F, -8.351852000e-02F, -4.044584930e-01F, + 1.683979630e-01F, 1.039175391e-01F, -4.615206271e-02F, 2.040673234e-02F, -1.095368266e-01F, 1.760297865e-01F, + 2.716188133e-01F, -5.973888561e-02F, 1.503935158e-01F, 8.775928617e-02F, 1.155470535e-01F, 5.972424522e-02F, + -2.567255497e-01F, -5.652304273e-03F, 9.589818865e-02F, -8.921126276e-02F, -3.392399251e-01F, 2.062889785e-01F, + 4.077020586e-01F, -1.134710386e-01F, -2.288632840e-01F, -1.112778708e-01F, -3.368454427e-02F, -2.557650581e-02F, + -1.881617866e-02F, 7.960526645e-02F, 1.180145666e-01F, -3.640719950e-01F, -1.079093292e-01F, 3.775866032e-01F, + 2.124478668e-02F, -6.932077557e-02F, 1.124042422e-01F, 1.647764742e-01F, -6.605489552e-02F, -3.542600274e-01F, + -1.212620884e-01F, 1.829260737e-01F, -1.363480538e-01F, 1.853770465e-01F, 1.314600259e-01F, -2.819254622e-02F, + -2.660948969e-02F, 7.726924121e-02F, 1.822431805e-03F, -3.935068101e-02F, -2.082460374e-02F, -2.380785346e-02F, + -2.103998512e-01F, -6.211024150e-02F, 2.299217135e-01F, 5.459949747e-02F, -1.092392281e-01F, 5.424054340e-02F, + -4.887797404e-03F, -2.286173552e-01F, -4.446997494e-02F, 2.377912402e-01F, -2.281474136e-02F, 1.732720248e-02F, + -1.506749094e-01F, -2.589065135e-01F, 1.560672075e-01F, 4.277846217e-01F, -2.366209179e-01F, -1.757626608e-02F, + 1.398306042e-01F, -1.381504387e-01F, 2.760923207e-01F, -6.828580052e-02F, 2.574165817e-03F, -4.047624469e-01F, + -1.985766143e-01F, 2.510111034e-01F, 3.103970587e-01F, 7.979132235e-02F, 9.866134077e-02F, -1.805309355e-01F, + -9.099559486e-02F, -1.446219385e-01F, 6.199353188e-02F, -1.837347448e-01F, -4.682877287e-02F, 6.049750373e-02F, + -1.427479833e-01F, 1.618067622e-01F, 5.013866350e-02F, 3.576031327e-01F, -1.461488605e-01F, -2.728185952e-01F, + 3.037589192e-01F, 3.219360709e-01F, 2.624734640e-01F, -1.147825494e-01F, -2.329646200e-01F, -9.621760249e-02F, + 3.259269595e-01F, -6.477586459e-03F, -2.853234410e-01F, 1.472934484e-01F, -2.059278786e-01F, 3.382449150e-01F, + -8.181252331e-02F, -3.583823889e-02F, -1.871012300e-01F, -7.842759788e-02F, 1.019839793e-01F, 6.678096950e-02F, + -1.730515435e-02F, -4.530839622e-01F, 2.517964505e-02F, 4.219501615e-01F, 1.127565876e-01F, 1.826989353e-01F, + 3.972071782e-02F, -3.910230100e-01F, -2.375494223e-03F, 5.690404214e-03F, -3.934600353e-01F, 1.355238110e-01F, + 1.596109197e-02F, -1.438011378e-01F, -1.173453927e-01F, 1.527261138e-01F, 1.788592897e-02F, 5.713234469e-02F, + -1.319680661e-01F, 3.147881711e-03F, -1.108170673e-01F, -5.006913841e-02F, 3.937126696e-02F, -7.200197875e-02F, + 1.229225472e-01F, 4.598938301e-02F, 1.351474971e-01F, -3.982362747e-01F, 1.962782592e-01F, -2.779457718e-03F, + -1.453491598e-01F, -2.713320311e-03F, +}; + +} // namespace campplus_goldens diff --git a/tests/vllm/models/test_campplus.cpp b/tests/vllm/models/test_campplus.cpp new file mode 100644 index 000000000..d79ebf1d8 --- /dev/null +++ b/tests/vllm/models/test_campplus.cpp @@ -0,0 +1,106 @@ +// CAMPPlus primitives — W3 of #634, the speaker-style encoder on the MANDATORY +// reference-audio path (the talker is built with spk_cond_mode="campplus", so +// this feeds it). +// +// Gated against `indextts/s2mel/modules/campplus/layers.py` executed DIRECTLY: +// it has no vllm dependency, so the goldens come from the real classes rather +// than a restatement. Weights are rebuilt both sides from one FNV-1a -> +// splitmix64 stream. +// +// THREE THINGS HERE FAIL SILENTLY, so each has its own case: +// 1. std is UNBIASED (N-1). The biased form differs by ~0.2% at T=250 — small +// enough to look like noise, large enough to move a style vector. +// 2. BatchNorm1d in EVAL uses RUNNING statistics. Using batch statistics still +// normalizes, and is a different model. +// 3. seg_pooling uses ceil_mode, expands each segment back over seg_len frames +// and TRUNCATES to the input length. T=250 gives 3 segments, the last one +// partial, which is where an off-by-one lives. +#include +#include +#include +#include +#include + +#include "campplus_goldens.inc" +#include "doctest/doctest.h" +#include "vllm/model_executor/models/campplus.h" + +namespace { +using namespace campplus_goldens; + +std::vector Rand(const std::string& name, int64_t n, double scale) { + uint64_t seed = 0xCBF29CE484222325ULL; + for (char c : name) { seed ^= static_cast(c); seed *= 0x100000001B3ULL; } + std::vector o(static_cast(n)); + for (int64_t i = 0; i < n; ++i) { + uint64_t x = seed + static_cast(i); + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + z ^= z >> 31; + o[static_cast(i)] = + static_cast(((static_cast(z >> 11) * 0x1.0p-53) * 2.0 - 1.0) * scale); + } + return o; +} +double Worst(const std::vector& got, const float* want, size_t n) { + double w = 0.0; + for (size_t i = 0; i < n; ++i) w = std::max(w, std::fabs(static_cast(got[i]) - want[i])); + return w; +} +} // namespace + +TEST_CASE("campplus StatsPool concatenates mean and UNBIASED std") { + const std::vector x = Rand("x", kChannels * kFrames, 1.0); + const std::vector got = vllm::models::campplus::StatsPool(x, kChannels, kFrames); + REQUIRE(got.size() == static_cast(2 * kChannels)); + const double w = Worst(got, kStats, got.size()); + INFO("max abs diff vs upstream StatsPool: ", w); + CHECK(w < 1e-5); +} + +TEST_CASE("campplus BatchNorm1d in eval uses RUNNING statistics") { + const std::vector x = Rand("x", kChannels * kFrames, 1.0); + std::vector gamma = Rand("bn.w", kChannels, 0.5); + for (float& g : gamma) g += 1.0F; + const std::vector beta = Rand("bn.b", kChannels, 0.3); + const std::vector mean = Rand("bn.rm", kChannels, 0.4); + std::vector var = Rand("bn.rv", kChannels, 0.2); + for (float& v : var) v = std::fabs(v) + 0.5F; + + const std::vector got = + vllm::models::campplus::BatchNorm1dEval(x, kChannels, kFrames, gamma, beta, mean, var, 1e-5); + REQUIRE(got.size() == x.size()); + const double w = Worst(got, kBatchNormEval, got.size()); + INFO("max abs diff vs upstream BatchNorm1d(eval): ", w); + CHECK(w < 1e-5); +} + +TEST_CASE("campplus seg_pooling expands ceil-mode segments and truncates") { + const std::vector x = Rand("xb", kBnChannels * kFrames, 1.0); + const std::vector got = + vllm::models::campplus::SegPooling(x, kBnChannels, kFrames, kSegLen); + // The expansion must return the INPUT length, not a multiple of seg_len. + REQUIRE(got.size() == x.size()); + const double w = Worst(got, kSegPooling, got.size()); + INFO("max abs diff vs upstream seg_pooling: ", w); + CHECK(w < 1e-5); +} + +TEST_CASE("campplus CAMLayer gates the local branch by the pooled context") { + const std::vector x = Rand("xb", kBnChannels * kFrames, 1.0); + vllm::models::campplus::CamLayerWeights w; + w.linear_local = Rand("cam.ll.w", kOutChannels * kBnChannels * kKernel, 0.3); + w.linear1_weight = Rand("cam.l1.w", (kBnChannels / 2) * kBnChannels, 0.3); + w.linear1_bias = Rand("cam.l1.b", kBnChannels / 2, 0.2); + w.linear2_weight = Rand("cam.l2.w", kOutChannels * (kBnChannels / 2), 0.3); + w.linear2_bias = Rand("cam.l2.b", kOutChannels, 0.2); + + const std::vector got = vllm::models::campplus::CamLayer( + x, kBnChannels, kFrames, kOutChannels, kKernel, kDilation, kSegLen, w); + REQUIRE(got.size() == static_cast(kOutChannels * kFrames)); + const double d = Worst(got, kCamOut, got.size()); + INFO("max abs diff vs upstream CAMLayer: ", d); + CHECK(d < 1e-5); +} From e6ede74d7719771095f75f7a46f7cd52484ab795 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:15:51 +0200 Subject: [PATCH 15/55] =?UTF-8?q?feat(MODEL-MM-indextts2):=20W3=20?= =?UTF-8?q?=E2=80=94=20CAMPPlus=20layer=20stack,=20and=20a=20defect=20the?= =?UTF-8?q?=20gate=20caught=20(#634)=20(#690)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Adds TransitLayer, DenseLayer, CAMDenseTDNNLayer and CAMDenseTDNNBlock on top of the primitives already on main, gated against `campplus/layers.py` executed DIRECTLY (no restatement). 8 cases / 16 assertions, within 1e-5. A REAL DEFECT, found by the gate rather than review: upstream's final dense uses `config_str='batchnorm_'`, which get_nonlinear expands to a SINGLE batchnorm with affine=false and NO relu. The first implementation applied one unconditionally, clamping every negative component of the style vector to zero — a plausible embedding from the wrong model, invisible to any shape check, and the talker consumes exactly that vector. Two ordering traps also gated, each producing a correctly-shaped wrong tensor: TransitLayer applies its nonlinear BEFORE the projection while DenseLayer applies it AFTER, and the block's cat([x, layer(x)], dim=1) growth means appending in the wrong order still yields the right shape. MUTATION EVIDENCE with compile status per result: forced relu, prepend-instead-of- append, and swapped transit order each RED at compile_err=0. The relu mutation's first attempt reported compile_err=1 and a meaningless SUCCESS — the third time this campaign that a mutation failed to build under -Werror and the old binary re-ran, which is why compile status is now reported beside every result. REMAINING: CAMPPlus's FCM 2-D front end and forward assembly; then w2v-bert-2.0 and EnhancedCodec; then W4 S2Mel, W5 compose, W6b. No e2e render. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- include/vllm/model_executor/models/campplus.h | 57 ++ scripts/gen-campplus-goldens.py | 29 + src/vllm/model_executor/models/campplus.cpp | 78 +- tests/vllm/models/campplus_goldens.inc | 894 ++++++++++++++++++ tests/vllm/models/test_campplus.cpp | 82 ++ 5 files changed, 1135 insertions(+), 5 deletions(-) diff --git a/include/vllm/model_executor/models/campplus.h b/include/vllm/model_executor/models/campplus.h index c36df40fd..447980119 100644 --- a/include/vllm/model_executor/models/campplus.h +++ b/include/vllm/model_executor/models/campplus.h @@ -56,6 +56,63 @@ std::vector CamLayer(const std::vector& x, int64_t bn_channels, in int64_t out_channels, int64_t kernel, int64_t dilation, int64_t seg_len, const CamLayerWeights& weights); + +// BatchNorm + ReLU, the `config_str='batchnorm-relu'` nonlinearity every layer +// wraps itself in (layers.py:10-24). `batchnorm_` sets affine=false, which is +// what the FINAL DenseLayer uses -- passing a gamma/beta there is a different +// model. +std::vector BatchNormRelu(const std::vector& x, int64_t channels, int64_t frames, + const std::vector& gamma, const std::vector& beta, + const std::vector& running_mean, + const std::vector& running_var, double eps); + +// TransitLayer (layers.py:183-197): nonlinear THEN 1x1 conv, in that order. The +// reverse still runs and is a different model. +std::vector TransitLayer(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t out_channels, const std::vector& bn_gamma, + const std::vector& bn_beta, + const std::vector& bn_mean, + const std::vector& bn_var, + const std::vector& weight, const std::vector& bias, + double eps); + +// DenseLayer (layers.py:199-215): 1x1 conv THEN nonlinear -- the opposite order +// to TransitLayer. A 2-D input (the pooled stats vector) is treated as T=1. +// +// `apply_relu` mirrors `config_str`, and is NOT cosmetic: the final dense uses +// `batchnorm_`, which get_nonlinear (layers.py:10-24) expands to a SINGLE +// batchnorm with affine=false and NO relu. Applying one anyway clamps every +// negative component of the style vector to zero -- a plausible-looking +// embedding that is the wrong model. +std::vector DenseLayer(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t out_channels, const std::vector& weight, + const std::vector& bias, const std::vector& bn_gamma, + const std::vector& bn_beta, const std::vector& bn_mean, + const std::vector& bn_var, double eps, bool apply_relu); + +struct DenseTdnnLayerWeights { + std::vector bn1_gamma, bn1_beta, bn1_mean, bn1_var; // nonlinear1 + std::vector linear1; // 1x1, no bias + std::vector bn2_gamma, bn2_beta, bn2_mean, bn2_var; // nonlinear2 + CamLayerWeights cam; +}; + +// CAMDenseTDNNLayer (layers.py:143-150): nonlinear1 -> linear1 -> nonlinear2 -> +// cam_layer. +std::vector DenseTdnnLayer(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t bn_channels, int64_t out_channels, int64_t kernel, + int64_t dilation, int64_t seg_len, + const DenseTdnnLayerWeights& weights, double eps); + +// CAMDenseTDNNBlock (layers.py:177-181): x = cat([x, layer(x)], dim=1) — the +// channel count GROWS by out_channels per layer, and each layer sees every +// earlier output. Appending in the wrong order still produces a tensor of the +// right shape. +std::vector DenseTdnnBlock(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t bn_channels, int64_t growth, int64_t kernel, + int64_t dilation, int64_t seg_len, + const std::vector& layers, double eps); + } // namespace campplus } // namespace models } // namespace vllm diff --git a/scripts/gen-campplus-goldens.py b/scripts/gen-campplus-goldens.py index 4be0d5b01..3b75f02dc 100644 --- a/scripts/gen-campplus-goldens.py +++ b/scripts/gen-campplus-goldens.py @@ -50,6 +50,30 @@ def P(name, shape, scale=1.0): cam_out = cam(xb) seg = cam.seg_pooling(xb) + +# ---- TransitLayer / DenseLayer / CAMDenseTDNNLayer / Block ---- +IN, GROWTH, NL = 8, 3, 2 +def fill(mod, prefix): + with torch.no_grad(): + for n, q in mod.named_parameters(): + q.copy_(P(prefix + "." + n, tuple(q.shape), 0.3)) + for n, b in mod.named_buffers(): + if n.endswith("running_var"): b.copy_(P(prefix + "." + n, tuple(b.shape), 0.2).abs() + 0.5) + elif n.endswith("running_mean"): b.copy_(P(prefix + "." + n, tuple(b.shape), 0.4)) + +xt = P("xt", (1, IN, T), 1.0) +transit = m.TransitLayer(IN, IN // 2, bias=True).eval(); fill(transit, "transit") +transit_out = transit(xt) + +dense2d = m.DenseLayer(2 * C, 12, config_str="batchnorm_").eval(); fill(dense2d, "dense2d") +dense_out = dense2d(stats) # 2-D input path: unsqueeze -> conv -> squeeze + +dl = m.CAMDenseTDNNLayer(IN, GROWTH, BN, K, dilation=D).eval(); fill(dl, "dl") +dl_out = dl(xt) + +blk = m.CAMDenseTDNNBlock(NL, IN, GROWTH, BN, K, dilation=D).eval(); fill(blk, "blk") +blk_out = blk(xt) + def emit(f, name, t): a = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) f.write(f"inline constexpr float {name}[] = {{\n") @@ -71,5 +95,10 @@ def emit(f, name, t): f.write("\n") emit(f, "kStats", stats); emit(f, "kBatchNormEval", bn_out) emit(f, "kSegPooling", seg); emit(f, "kCamOut", cam_out) + for n, v in (("kIn", IN), ("kGrowth", GROWTH), ("kNumLayers", NL)): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write("\n") + emit(f, "kTransit", transit_out); emit(f, "kDense2d", dense_out) + emit(f, "kDenseTdnnLayer", dl_out); emit(f, "kDenseTdnnBlock", blk_out) f.write("} // namespace campplus_goldens\n") print("wrote", out, "T=", T, "seg segments=", -(-T//100)) diff --git a/src/vllm/model_executor/models/campplus.cpp b/src/vllm/model_executor/models/campplus.cpp index a109ccd6b..c41297d2a 100644 --- a/src/vllm/model_executor/models/campplus.cpp +++ b/src/vllm/model_executor/models/campplus.cpp @@ -76,10 +76,8 @@ std::vector SegPooling(const std::vector& x, int64_t channels, int return out; } -namespace { - // 1-D convolution over [C, T] with `same` padding, as every CAMPPlus conv uses. -std::vector Conv1dSame(const std::vector& in, int64_t in_ch, int64_t frames, +static std::vector Conv1dSame(const std::vector& in, int64_t in_ch, int64_t frames, const std::vector& w, const std::vector& bias, int64_t out_ch, int64_t kernel, int64_t dilation) { const int64_t pad = (kernel - 1) / 2 * dilation; @@ -101,8 +99,6 @@ std::vector Conv1dSame(const std::vector& in, int64_t in_ch, int64 return out; } -} // namespace - std::vector CamLayer(const std::vector& x, int64_t bn_channels, int64_t frames, int64_t out_channels, int64_t kernel, int64_t dilation, int64_t seg_len, const CamLayerWeights& weights) { @@ -138,6 +134,78 @@ std::vector CamLayer(const std::vector& x, int64_t bn_channels, in return out; } + +std::vector BatchNormRelu(const std::vector& x, int64_t channels, int64_t frames, + const std::vector& gamma, const std::vector& beta, + const std::vector& running_mean, + const std::vector& running_var, double eps) { + std::vector out = + BatchNorm1dEval(x, channels, frames, gamma, beta, running_mean, running_var, eps); + for (float& v : out) v = v > 0.0F ? v : 0.0F; + return out; +} + +std::vector TransitLayer(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t out_channels, const std::vector& bn_gamma, + const std::vector& bn_beta, + const std::vector& bn_mean, + const std::vector& bn_var, + const std::vector& weight, const std::vector& bias, + double eps) { + // nonlinear FIRST, then the 1x1 projection. + const std::vector h = + BatchNormRelu(x, in_channels, frames, bn_gamma, bn_beta, bn_mean, bn_var, eps); + return Conv1dSame(h, in_channels, frames, weight, bias, out_channels, 1, 1); +} + +std::vector DenseLayer(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t out_channels, const std::vector& weight, + const std::vector& bias, const std::vector& bn_gamma, + const std::vector& bn_beta, const std::vector& bn_mean, + const std::vector& bn_var, double eps, bool apply_relu) { + // 1x1 projection FIRST, then the nonlinear -- the opposite order to + // TransitLayer. A pooled stats vector arrives as frames == 1. + const std::vector h = + Conv1dSame(x, in_channels, frames, weight, bias, out_channels, 1, 1); + std::vector out = + BatchNorm1dEval(h, out_channels, frames, bn_gamma, bn_beta, bn_mean, bn_var, eps); + // `batchnorm_` is batchnorm ALONE (affine=false, no relu). + if (apply_relu) { + for (float& v : out) v = v > 0.0F ? v : 0.0F; + } + return out; +} + +std::vector DenseTdnnLayer(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t bn_channels, int64_t out_channels, int64_t kernel, + int64_t dilation, int64_t seg_len, + const DenseTdnnLayerWeights& w, double eps) { + const std::vector a = BatchNormRelu(x, in_channels, frames, w.bn1_gamma, w.bn1_beta, + w.bn1_mean, w.bn1_var, eps); + const std::vector b = + Conv1dSame(a, in_channels, frames, w.linear1, {}, bn_channels, 1, 1); + const std::vector c = BatchNormRelu(b, bn_channels, frames, w.bn2_gamma, w.bn2_beta, + w.bn2_mean, w.bn2_var, eps); + return CamLayer(c, bn_channels, frames, out_channels, kernel, dilation, seg_len, w.cam); +} + +std::vector DenseTdnnBlock(const std::vector& x, int64_t in_channels, int64_t frames, + int64_t bn_channels, int64_t growth, int64_t kernel, + int64_t dilation, int64_t seg_len, + const std::vector& layers, double eps) { + std::vector acc = x; + int64_t channels = in_channels; + for (const DenseTdnnLayerWeights& w : layers) { + const std::vector y = DenseTdnnLayer(acc, channels, frames, bn_channels, growth, kernel, + dilation, seg_len, w, eps); + // cat([x, layer(x)], dim=1): the new channels are APPENDED, so every later + // layer sees all earlier outputs. + acc.insert(acc.end(), y.begin(), y.end()); + channels += growth; + } + return acc; +} + } // namespace campplus } // namespace models } // namespace vllm diff --git a/tests/vllm/models/campplus_goldens.inc b/tests/vllm/models/campplus_goldens.inc index f5b947e05..3ee0b4fd0 100644 --- a/tests/vllm/models/campplus_goldens.inc +++ b/tests/vllm/models/campplus_goldens.inc @@ -657,4 +657,898 @@ inline constexpr float kCamOut[] = { -1.453491598e-01F, -2.713320311e-03F, }; +inline constexpr int64_t kIn = 8; +inline constexpr int64_t kGrowth = 3; +inline constexpr int64_t kNumLayers = 2; + +inline constexpr float kTransit[] = { + 2.240921259e-01F, 2.254148573e-01F, 2.255452424e-01F, 2.504585683e-01F, 1.912565231e-01F, 2.742036283e-01F, + 2.511843443e-01F, 2.175291628e-01F, 2.215423435e-01F, 2.169079930e-01F, 1.950578839e-01F, 2.630996704e-01F, + 2.089037150e-01F, 2.666632533e-01F, 2.847739756e-01F, 2.374662310e-01F, 2.828461528e-01F, 2.285848409e-01F, + 2.218874097e-01F, 2.364695817e-01F, 2.206629664e-01F, 2.532271147e-01F, 2.743554711e-01F, 2.433511615e-01F, + 2.622085810e-01F, 2.152915448e-01F, 2.532919943e-01F, 2.582544088e-01F, 2.709517479e-01F, 2.811224759e-01F, + 2.118384838e-01F, 2.512213588e-01F, 2.419623584e-01F, 2.546386421e-01F, 3.015563488e-01F, 2.907534242e-01F, + 2.526232004e-01F, 2.198827267e-01F, 2.526699007e-01F, 2.565664053e-01F, 2.386525571e-01F, 2.627362013e-01F, + 1.918078363e-01F, 2.346314341e-01F, 2.611431181e-01F, 2.766290903e-01F, 1.986639798e-01F, 2.078969181e-01F, + 2.797226608e-01F, 2.132159919e-01F, 2.523279786e-01F, 2.674677074e-01F, 2.476970106e-01F, 2.483438104e-01F, + 2.366559356e-01F, 2.755389214e-01F, 1.857912093e-01F, 2.845574915e-01F, 2.803699970e-01F, 2.313859463e-01F, + 2.704184055e-01F, 2.565677166e-01F, 2.270609736e-01F, 2.409108430e-01F, 2.837070227e-01F, 2.767791450e-01F, + 2.189274728e-01F, 2.732663751e-01F, 2.189449817e-01F, 2.436566502e-01F, 2.308578938e-01F, 2.791860998e-01F, + 2.331376225e-01F, 2.568229437e-01F, 2.299235761e-01F, 2.779795825e-01F, 2.483414263e-01F, 2.684818804e-01F, + 2.547707260e-01F, 2.538687885e-01F, 2.418347150e-01F, 1.957484037e-01F, 2.508561611e-01F, 2.784458399e-01F, + 2.529785931e-01F, 3.005597889e-01F, 2.612517774e-01F, 1.920154542e-01F, 2.952501476e-01F, 2.660868466e-01F, + 2.729607224e-01F, 2.728524506e-01F, 2.589747012e-01F, 2.641308606e-01F, 2.542238533e-01F, 2.272263169e-01F, + 2.643278539e-01F, 2.023562640e-01F, 2.794044316e-01F, 2.079038471e-01F, 2.509563863e-01F, 2.671239078e-01F, + 2.375261486e-01F, 2.765995562e-01F, 2.585590184e-01F, 2.708402276e-01F, 2.676184177e-01F, 2.333882451e-01F, + 2.789086103e-01F, 2.525849342e-01F, 2.732797861e-01F, 2.303589880e-01F, 2.896885872e-01F, 2.033266574e-01F, + 2.549493909e-01F, 2.703709304e-01F, 2.475554198e-01F, 2.789032161e-01F, 2.706549466e-01F, 2.313418984e-01F, + 2.580159605e-01F, 2.004748583e-01F, 2.330642492e-01F, 2.893070579e-01F, 2.424845248e-01F, 2.196847200e-01F, + 2.439858168e-01F, 2.218419909e-01F, 2.796883285e-01F, 2.663047612e-01F, 2.719293535e-01F, 2.204645425e-01F, + 2.523795962e-01F, 2.668873370e-01F, 2.389072925e-01F, 2.059471309e-01F, 2.405264527e-01F, 2.501369715e-01F, + 2.575434148e-01F, 2.591688633e-01F, 2.602013648e-01F, 2.489713281e-01F, 2.490302175e-01F, 2.649352551e-01F, + 2.549716532e-01F, 2.389121950e-01F, 2.238328159e-01F, 1.818905622e-01F, 2.690646946e-01F, 2.752290368e-01F, + 2.067652345e-01F, 2.689183056e-01F, 2.218358666e-01F, 2.699654102e-01F, 2.207703590e-01F, 2.469881475e-01F, + 2.426096052e-01F, 2.675678134e-01F, 2.555851638e-01F, 2.484482527e-01F, 2.124758959e-01F, 2.800687253e-01F, + 2.720732689e-01F, 2.772063017e-01F, 2.929624915e-01F, 2.366131395e-01F, 2.580512166e-01F, 2.193642110e-01F, + 2.366144061e-01F, 1.740395129e-01F, 2.591984868e-01F, 1.897568703e-01F, 2.050187886e-01F, 3.032407761e-01F, + 2.531512678e-01F, 2.766172588e-01F, 2.688231170e-01F, 2.609506249e-01F, 2.700800598e-01F, 2.722812295e-01F, + 2.690882385e-01F, 2.575482428e-01F, 2.171662748e-01F, 2.551550269e-01F, 2.530945539e-01F, 2.719417512e-01F, + 2.758464217e-01F, 1.822618693e-01F, 2.599267364e-01F, 2.937820554e-01F, 2.030957341e-01F, 2.530142665e-01F, + 2.369902283e-01F, 2.153631747e-01F, 2.560510635e-01F, 2.071922570e-01F, 2.877348065e-01F, 2.579424381e-01F, + 2.559593320e-01F, 2.757709622e-01F, 2.020339668e-01F, 2.180683464e-01F, 2.207067758e-01F, 2.736280262e-01F, + 2.903173566e-01F, 2.085822970e-01F, 2.439090759e-01F, 2.149615884e-01F, 2.801110446e-01F, 2.104003578e-01F, + 2.266443372e-01F, 2.506824136e-01F, 2.531082928e-01F, 2.932851017e-01F, 2.994239926e-01F, 2.339401990e-01F, + 2.470908016e-01F, 2.456625998e-01F, 2.648464441e-01F, 2.358472943e-01F, 2.275167555e-01F, 2.619313598e-01F, + 2.012538612e-01F, 2.332645059e-01F, 2.393338978e-01F, 2.785956860e-01F, 2.619722784e-01F, 2.719748020e-01F, + 2.184891999e-01F, 2.482686043e-01F, 2.887288332e-01F, 2.112939209e-01F, 2.790930569e-01F, 2.283590883e-01F, + 2.689432502e-01F, 2.625746429e-01F, 2.520240247e-01F, 2.071056366e-01F, 2.575912774e-01F, 2.205965966e-01F, + 2.576123774e-01F, 1.958818138e-01F, 2.830229998e-01F, 2.758864164e-01F, 2.294269353e-01F, 2.544637918e-01F, + 2.491785288e-01F, 2.856200337e-01F, 2.218257487e-01F, 2.672992945e-01F, 3.252539039e-01F, 2.686072290e-01F, + 3.244839311e-01F, 2.814680040e-01F, 3.159575760e-01F, 2.353199124e-01F, 2.028073668e-01F, 2.791208625e-01F, + 2.835713923e-01F, 2.821083665e-01F, 3.296481967e-01F, 1.908140630e-01F, 2.547859848e-01F, 2.180361897e-01F, + 1.988607943e-01F, 2.609801292e-01F, 1.707001179e-01F, 2.908600867e-01F, 4.034772515e-01F, 2.092962861e-01F, + 2.698487341e-01F, 3.379967511e-01F, 2.262318134e-01F, 3.234517574e-01F, 2.301883996e-01F, 2.317389846e-01F, + 2.612713873e-01F, 2.439724952e-01F, 1.756618023e-01F, 1.642550528e-01F, 2.749582529e-01F, 2.594783604e-01F, + 2.602840662e-01F, 2.506695986e-01F, 2.228508294e-01F, 2.531952560e-01F, 2.850213051e-01F, 2.169503719e-01F, + 2.520529926e-01F, 2.043362409e-01F, 2.174358219e-01F, 2.565566003e-01F, 3.759308457e-01F, 2.436128855e-01F, + 2.011715472e-01F, 2.236655056e-01F, 3.331499994e-01F, 3.462163806e-01F, 2.764914930e-01F, 3.189127445e-01F, + 1.804571748e-01F, 2.102819681e-01F, 2.628020346e-01F, 2.412368357e-01F, 2.529340982e-01F, 2.459507734e-01F, + 2.884695530e-01F, 2.081550658e-01F, 2.367844880e-01F, 1.974072754e-01F, 2.733270824e-01F, 2.747295201e-01F, + 3.269558251e-01F, 3.126354218e-01F, 2.415348589e-01F, 1.640100330e-01F, 3.329271674e-01F, 2.206738889e-01F, + 2.570381761e-01F, 3.160298169e-01F, 3.463267088e-01F, 2.084063590e-01F, 3.297396302e-01F, 2.256901562e-01F, + 3.304330409e-01F, 1.933974326e-01F, 2.829513550e-01F, 2.886025310e-01F, 2.033279985e-01F, 2.083568275e-01F, + 3.165687323e-01F, 3.888521791e-01F, 2.525401711e-01F, 2.009275705e-01F, 2.280785590e-01F, 2.181122452e-01F, + 1.951283962e-01F, 3.469654024e-01F, 2.501845658e-01F, 2.357766926e-01F, 2.800428867e-01F, 1.697361171e-01F, + 3.106998801e-01F, 2.234873474e-01F, 3.120122850e-01F, 3.115594983e-01F, 1.879169494e-01F, 3.395100236e-01F, + 2.512837648e-01F, 3.526116312e-01F, 2.379118055e-01F, 1.836053431e-01F, 2.157115936e-01F, 2.141630799e-01F, + 2.825846374e-01F, 2.170974910e-01F, 2.471244633e-01F, 2.933551669e-01F, 2.897155881e-01F, 1.945561767e-01F, + 1.830820441e-01F, 3.229011893e-01F, 2.189594209e-01F, 3.407281041e-01F, 2.546637356e-01F, 3.208163381e-01F, + 3.390239477e-01F, 2.849963009e-01F, 3.029479980e-01F, 3.242682815e-01F, 2.608024180e-01F, 3.129833043e-01F, + 2.849713266e-01F, 2.290203869e-01F, 2.938433588e-01F, 3.295237422e-01F, 2.448684871e-01F, 2.682718635e-01F, + 2.591395080e-01F, 3.226684928e-01F, 2.001654059e-01F, 3.342446089e-01F, 2.920465469e-01F, 3.033321500e-01F, + 2.315310538e-01F, 2.972545028e-01F, 2.362485528e-01F, 2.262145281e-01F, 2.023581266e-01F, 1.679706275e-01F, + 2.057034671e-01F, 2.323778421e-01F, 2.729227543e-01F, 3.004922867e-01F, 2.906951308e-01F, 3.256913424e-01F, + 2.993475795e-01F, 3.531863987e-01F, 2.722885609e-01F, 1.754881889e-01F, 2.832761109e-01F, 1.794569194e-01F, + 2.750133872e-01F, 2.319118232e-01F, 2.949480116e-01F, 2.592049241e-01F, 2.643012702e-01F, 2.112009525e-01F, + 2.551598847e-01F, 3.612378538e-01F, 3.131943047e-01F, 1.818426549e-01F, 2.744390368e-01F, 2.022419721e-01F, + 2.553022802e-01F, 2.728666067e-01F, 2.172105908e-01F, 2.890600562e-01F, 2.855481505e-01F, 3.404996395e-01F, + 2.098549306e-01F, 3.857764304e-01F, 3.697060943e-01F, 2.273404002e-01F, 2.364708483e-01F, 3.036482632e-01F, + 2.917348146e-01F, 2.032487690e-01F, 1.768781394e-01F, 2.579437792e-01F, 2.216856778e-01F, 2.260691822e-01F, + 3.844693899e-01F, 2.299776375e-01F, 2.030442059e-01F, 1.825068295e-01F, 2.382898182e-01F, 3.216758370e-01F, + 2.743023932e-01F, 1.826694608e-01F, 2.901377380e-01F, 3.320131302e-01F, 2.168819904e-01F, 2.859112024e-01F, + 2.516533136e-01F, 3.622957170e-01F, 1.779715121e-01F, 2.106725872e-01F, 1.555658728e-01F, 2.670546174e-01F, + 3.033561707e-01F, 3.206121325e-01F, 1.945859343e-01F, 1.752386689e-01F, 1.936014742e-01F, 3.217945993e-01F, + 3.782478571e-01F, 2.565030456e-01F, 3.100525141e-01F, 2.777393460e-01F, 2.385008037e-01F, 2.096298039e-01F, + 2.619116902e-01F, 2.135982066e-01F, 2.532861829e-01F, 2.439753860e-01F, 2.470427305e-01F, 2.840754688e-01F, + 2.184601128e-01F, 2.497819513e-01F, 2.687129974e-01F, 2.081200182e-01F, 3.704710603e-01F, 2.632463276e-01F, + 3.571094871e-01F, 2.748554349e-01F, 1.871449053e-01F, 2.652237117e-01F, 3.719056249e-01F, 2.256034613e-01F, + 2.279733717e-01F, 3.051649332e-01F, 3.437986374e-01F, 3.585415184e-01F, 2.371487617e-01F, 2.117537558e-01F, + 2.986766100e-01F, 3.203232288e-01F, 2.668891251e-01F, 2.945639491e-01F, 1.716816872e-01F, 2.233105600e-01F, + 2.247304469e-01F, 2.668175697e-01F, 2.539560795e-01F, 3.105300963e-01F, 3.162601888e-01F, 2.210928202e-01F, + 2.921504080e-01F, 2.651685476e-01F, -2.067929357e-01F, -2.933028936e-01F, -2.114748359e-01F, -2.214000225e-01F, + -2.146919966e-01F, -3.108518422e-01F, -3.399316072e-01F, -2.385206521e-01F, -2.302241474e-01F, -2.753525674e-01F, + -1.792438775e-01F, -3.591853976e-01F, -3.080863655e-01F, -3.131518066e-01F, -3.672483563e-01F, -2.579446137e-01F, + -4.088425338e-01F, -2.541074753e-01F, -1.285627484e-01F, -3.742679954e-01F, -2.835524976e-01F, -1.672129035e-01F, + -3.411217034e-01F, -2.043869793e-01F, -3.130440116e-01F, -2.691036761e-01F, -2.648962140e-01F, -2.916923165e-01F, + -4.078186154e-01F, -4.151752591e-01F, -2.944072187e-01F, -2.715095580e-01F, -3.049971461e-01F, -2.780242860e-01F, + -3.531998396e-01F, -3.124402761e-01F, -2.343860269e-01F, -3.594259620e-01F, -2.735991180e-01F, -3.724667430e-01F, + -3.442081213e-01F, -2.804681957e-01F, -1.276250184e-01F, -3.274574578e-01F, -3.547536731e-01F, -3.359719217e-01F, + -1.984339952e-01F, -1.552536786e-01F, -2.869762182e-01F, -2.225454301e-01F, -3.690805435e-01F, -3.278303146e-01F, + -2.537232935e-01F, -2.776711881e-01F, -3.060466945e-01F, -3.207950592e-01F, -2.573865652e-01F, -3.628989458e-01F, + -3.292261660e-01F, -3.262458742e-01F, -2.709670067e-01F, -2.663658857e-01F, -2.131576091e-01F, -2.368411273e-01F, + -3.129220605e-01F, -4.080200791e-01F, -2.228368968e-01F, -3.300400972e-01F, -2.334655970e-01F, -1.876244396e-01F, + -2.165266573e-01F, -3.525494933e-01F, -2.214346975e-01F, -3.299186230e-01F, -1.871047914e-01F, -3.826070428e-01F, + -2.677766383e-01F, -2.397716343e-01F, -3.484093845e-01F, -3.356942832e-01F, -2.075763643e-01F, -1.365504265e-01F, + -2.699866891e-01F, -3.526884913e-01F, -3.309069872e-01F, -3.643279672e-01F, -3.836966753e-01F, -1.576444805e-01F, + -3.224011958e-01F, -2.953506112e-01F, -2.614207566e-01F, -4.166240096e-01F, -2.055561393e-01F, -3.234239817e-01F, + -2.643115222e-01F, -2.324227393e-01F, -3.569922447e-01F, -1.899981350e-01F, -3.128970563e-01F, -1.776106656e-01F, + -2.834404707e-01F, -3.791270256e-01F, -3.782584965e-01F, -3.487590551e-01F, -2.435118556e-01F, -3.359527886e-01F, + -3.205663264e-01F, -2.475823313e-01F, -2.447229922e-01F, -3.527318835e-01F, -3.781312108e-01F, -2.093872875e-01F, + -3.479684293e-01F, -1.844429523e-01F, -2.800097764e-01F, -2.120535225e-01F, -1.715796888e-01F, -2.676777244e-01F, + -2.321072519e-01F, -2.156345993e-01F, -2.658370435e-01F, -2.324922383e-01F, -2.151698917e-01F, -3.342225850e-01F, + -2.109951228e-01F, -1.886141896e-01F, -2.802290320e-01F, -2.856343389e-01F, -3.021613657e-01F, -1.981521994e-01F, + -3.519974053e-01F, -2.058068663e-01F, -2.334933281e-01F, -2.113613784e-01F, -3.540447950e-01F, -1.909976453e-01F, + -3.469313383e-01F, -2.966453433e-01F, -3.345600665e-01F, -3.921561837e-01F, -3.643728495e-01F, -2.881266773e-01F, + -2.835893333e-01F, -2.275379747e-01F, -2.285820991e-01F, -2.121564895e-01F, -2.194077969e-01F, -1.527564973e-01F, + -2.577193975e-01F, -3.932151794e-01F, -2.373949885e-01F, -3.815089464e-01F, -2.157481313e-01F, -3.073549271e-01F, + -2.466551661e-01F, -3.038115501e-01F, -2.531464398e-01F, -3.425856233e-01F, -2.743881941e-01F, -1.550412029e-01F, + -2.224155515e-01F, -3.942662477e-01F, -2.885577083e-01F, -3.634668589e-01F, -2.997261584e-01F, -2.404030114e-01F, + -3.196939528e-01F, -2.251721770e-01F, -2.127770334e-01F, -1.720976084e-01F, -3.576632142e-01F, -1.233039051e-01F, + -1.564680934e-01F, -3.425522149e-01F, -2.909268439e-01F, -2.164758593e-01F, -2.391832024e-01F, -3.469342887e-01F, + -3.887316585e-01F, -2.905175090e-01F, -3.307524323e-01F, -3.094514906e-01F, -1.454611719e-01F, -3.430326581e-01F, + -3.750064969e-01F, -3.840664625e-01F, -3.100766838e-01F, -1.944115460e-01F, -2.470815778e-01F, -3.909230828e-01F, + -2.336805165e-01F, -1.881994009e-01F, -3.767426014e-01F, -2.539741397e-01F, -2.797946334e-01F, -1.494918913e-01F, + -3.905127645e-01F, -3.264338672e-01F, -4.128597975e-01F, -3.111956120e-01F, -2.451862395e-01F, -1.914280504e-01F, + -3.571161032e-01F, -3.864517808e-01F, -3.737944365e-01F, -1.717900336e-01F, -1.333535016e-01F, -2.814314663e-01F, + -2.176624388e-01F, -2.587395310e-01F, -3.170817494e-01F, -3.429056406e-01F, -2.639043629e-01F, -3.677715361e-01F, + -3.104826808e-01F, -3.390182853e-01F, -2.757717073e-01F, -2.277155817e-01F, -3.299708664e-01F, -2.540467680e-01F, + -2.881717086e-01F, -3.359475136e-01F, -1.303231269e-01F, -2.983874083e-01F, -1.529589146e-01F, -2.928653955e-01F, + -4.011001587e-01F, -2.751162648e-01F, -1.655090898e-01F, -3.446825743e-01F, -3.429262936e-01F, -2.311107069e-01F, + -1.734487116e-01F, -1.753421277e-01F, -2.963370383e-01F, -3.350167871e-01F, -2.020407617e-01F, -1.695564687e-01F, + -2.564704418e-01F, -2.476713955e-01F, -3.961885273e-01F, -3.407978415e-01F, -3.495104909e-01F, -2.811801732e-01F, + -2.544206381e-01F, -2.007061392e-01F, -1.907359958e-01F, -3.407052755e-01F, -2.506134808e-01F, -2.983376980e-01F, + -2.872240543e-01F, -2.532160580e-01F, -2.531298995e-01F, -1.830744296e-01F, -2.394393235e-01F, -1.745410115e-01F, + -1.382058859e-01F, -1.914092004e-01F, -1.823258102e-01F, -2.610296011e-01F, -2.358584255e-01F, -1.552970856e-01F, + -2.333192825e-01F, -1.586245000e-01F, -2.007046491e-01F, -1.937642694e-01F, -1.457598209e-01F, -2.599574924e-01F, + -3.352895379e-01F, -1.955014318e-01F, -2.241655886e-01F, -2.457580417e-01F, -1.932532042e-01F, -2.844761610e-01F, + -1.656384915e-01F, -1.561521888e-01F, -1.786263287e-01F, -1.713981032e-01F, -1.528950334e-01F, -1.380886585e-01F, + -2.745123208e-01F, -1.816073060e-01F, -2.317629457e-01F, -1.770581752e-01F, -1.905307174e-01F, -2.279770672e-01F, + -1.971116513e-01F, -1.882437617e-01F, -1.782054156e-01F, -1.736794859e-01F, -1.970182210e-01F, -1.879315674e-01F, + -2.833523452e-01F, -2.054849267e-01F, -1.660746932e-01F, -1.803996563e-01F, -2.828605175e-01F, -2.519294322e-01F, + -2.447596192e-01F, -2.391729504e-01F, -1.684967875e-01F, -1.571055353e-01F, -1.846813262e-01F, -1.814887673e-01F, + -2.025998831e-01F, -2.192120105e-01F, -2.306164354e-01F, -1.893922091e-01F, -2.089356780e-01F, -1.427461952e-01F, + -2.088326067e-01F, -2.072027177e-01F, -2.962739468e-01F, -2.597488761e-01F, -2.328340262e-01F, -1.426220536e-01F, + -2.833302021e-01F, -1.573600769e-01F, -1.771880537e-01F, -2.175079733e-01F, -3.090150356e-01F, -1.582980007e-01F, + -2.584064603e-01F, -1.733925194e-01F, -2.725503445e-01F, -1.469549984e-01F, -2.656700015e-01F, -2.058075964e-01F, + -1.633521020e-01F, -1.504496038e-01F, -2.348499447e-01F, -3.064507246e-01F, -1.801672429e-01F, -1.443135738e-01F, + -1.789492369e-01F, -2.030631304e-01F, -1.668825746e-01F, -2.638189793e-01F, -2.264736444e-01F, -1.613864899e-01F, + -2.213804722e-01F, -1.500437558e-01F, -2.379093170e-01F, -1.628385037e-01F, -2.743165195e-01F, -2.653254867e-01F, + -1.582856178e-01F, -2.918904722e-01F, -2.225754112e-01F, -2.863723934e-01F, -1.862488389e-01F, -1.412376016e-01F, + -2.011991441e-01F, -1.898566484e-01F, -2.033906877e-01F, -1.753515005e-01F, -2.400905788e-01F, -2.108993977e-01F, + -2.101257890e-01F, -1.580987275e-01F, -1.643859744e-01F, -2.568604350e-01F, -2.206358910e-01F, -2.952049375e-01F, + -1.892181337e-01F, -2.561840117e-01F, -2.808959484e-01F, -2.409611642e-01F, -2.401978970e-01F, -2.736221552e-01F, + -1.732661426e-01F, -2.539771795e-01F, -1.721659899e-01F, -2.308999002e-01F, -1.937309951e-01F, -2.790903449e-01F, + -1.635847241e-01F, -2.400830388e-01F, -2.078018039e-01F, -2.393750101e-01F, -1.856094152e-01F, -2.647247612e-01F, + -2.390887439e-01F, -1.942361593e-01F, -2.090113908e-01F, -1.879422963e-01F, -1.998173296e-01F, -1.778546423e-01F, + -1.672951430e-01F, -1.311473846e-01F, -1.684395224e-01F, -1.797972322e-01F, -2.262904346e-01F, -2.269163281e-01F, + -2.071449161e-01F, -2.440302074e-01F, -2.285064161e-01F, -2.775149047e-01F, -2.402952313e-01F, -1.455878466e-01F, + -2.046652436e-01F, -1.244375929e-01F, -1.816529185e-01F, -1.484786719e-01F, -2.256866395e-01F, -2.209196240e-01F, + -1.741579175e-01F, -1.578310728e-01F, -1.754650474e-01F, -2.769691944e-01F, -2.392600179e-01F, -1.570259333e-01F, + -2.393400967e-01F, -1.636886895e-01F, -1.992145032e-01F, -1.722907573e-01F, -1.684926748e-01F, -1.953024119e-01F, + -2.117141038e-01F, -2.436461896e-01F, -1.699028611e-01F, -3.061885238e-01F, -3.052055240e-01F, -1.947253346e-01F, + -1.758838743e-01F, -2.353442907e-01F, -2.275453210e-01F, -1.875991672e-01F, -1.350754797e-01F, -2.078103125e-01F, + -1.682732999e-01F, -1.700533777e-01F, -3.159718812e-01F, -1.797824502e-01F, -1.780489832e-01F, -1.434097886e-01F, + -2.144322097e-01F, -2.307749540e-01F, -2.164442688e-01F, -1.436703354e-01F, -2.063539773e-01F, -2.822465599e-01F, + -2.019770443e-01F, -2.311714292e-01F, -1.746074855e-01F, -2.938064933e-01F, -1.317756921e-01F, -1.678449959e-01F, + -1.220807508e-01F, -2.493055016e-01F, -2.719700038e-01F, -2.218100578e-01F, -1.731052697e-01F, -1.381508261e-01F, + -1.389364302e-01F, -2.288239151e-01F, -2.848387361e-01F, -1.917543858e-01F, -2.397790104e-01F, -2.359954715e-01F, + -1.924405992e-01F, -1.565709412e-01F, -1.788938791e-01F, -1.965313703e-01F, -2.064622492e-01F, -2.524268031e-01F, + -1.990199685e-01F, -1.892973483e-01F, -1.615010202e-01F, -1.878769696e-01F, -2.158150226e-01F, -1.633578092e-01F, + -2.890891433e-01F, -2.097816020e-01F, -2.613084018e-01F, -2.467600703e-01F, -1.693632305e-01F, -2.218271345e-01F, + -3.180042803e-01F, -2.176704556e-01F, -2.007818818e-01F, -2.820099592e-01F, -2.455378771e-01F, -3.237287402e-01F, + -1.584362984e-01F, -1.473214030e-01F, -2.095193416e-01F, -2.326937020e-01F, -1.743225902e-01F, -2.259358764e-01F, + -1.660591960e-01F, -1.969413161e-01F, -2.179403454e-01F, -2.150734067e-01F, -1.524205208e-01F, -2.331025600e-01F, + -2.155320495e-01F, -2.182310224e-01F, -2.241528034e-01F, -2.285224646e-01F, +}; + +inline constexpr float kDense2d[] = { + 5.388486385e-02F, 1.409728974e-01F, 3.964864016e-01F, 7.272020578e-01F, -9.924189448e-01F, -7.454258949e-02F, + -3.816688359e-01F, -3.635310382e-02F, -4.364022017e-01F, 5.170200467e-01F, 2.808949649e-01F, 3.513668478e-01F, +}; + +inline constexpr float kDenseTdnnLayer[] = { + -2.135734819e-02F, -2.215569094e-02F, 3.030702472e-02F, 3.119064495e-02F, 2.937515639e-02F, 3.124698065e-02F, + 3.012361936e-02F, 3.053360432e-02F, 3.078426607e-02F, 3.330079839e-02F, 3.157724068e-02F, 3.124734201e-02F, + 3.250208497e-02F, 3.340777382e-02F, 3.319552913e-02F, 3.114873357e-02F, 3.045299277e-02F, 3.388970718e-02F, + 3.113533370e-02F, 2.957421355e-02F, 3.176096454e-02F, 3.267431259e-02F, 3.213656694e-02F, 3.333175182e-02F, + 2.960886806e-02F, 2.902898006e-02F, 3.099618293e-02F, 3.227976337e-02F, 3.141971305e-02F, 2.978916839e-02F, + 3.183283284e-02F, 3.107172810e-02F, 3.096941113e-02F, 3.115653247e-02F, 3.217925876e-02F, 3.210436553e-02F, + 3.073002025e-02F, 2.905735001e-02F, 3.071582876e-02F, 3.147748485e-02F, 3.335144743e-02F, 3.093161993e-02F, + 3.102941625e-02F, 3.081963770e-02F, 3.227895498e-02F, 3.144280240e-02F, 3.206644207e-02F, 3.019852191e-02F, + 3.204834089e-02F, 3.179382905e-02F, 3.064307570e-02F, 3.118042834e-02F, 3.254130855e-02F, 3.288665041e-02F, + 3.020791337e-02F, 3.162804618e-02F, 3.058441728e-02F, 3.165411949e-02F, 3.302545846e-02F, 3.020004369e-02F, + 3.109385632e-02F, 3.277598694e-02F, 3.304352239e-02F, 3.063808195e-02F, 3.201283514e-02F, 3.193473071e-02F, + 3.158459067e-02F, 3.002701513e-02F, 3.050425090e-02F, 3.075115196e-02F, 3.298021480e-02F, 3.048385121e-02F, + 3.079753928e-02F, 3.045333736e-02F, 3.201423213e-02F, 3.069662489e-02F, 3.371569887e-02F, 3.173671663e-02F, + 2.982602641e-02F, 2.965639718e-02F, 3.060591593e-02F, 3.168859705e-02F, 3.173467144e-02F, 3.050508723e-02F, + 3.063158877e-02F, 3.249736503e-02F, 3.030805662e-02F, 3.177135810e-02F, 3.189682588e-02F, 3.039474785e-02F, + 3.098862618e-02F, 3.129107133e-02F, 3.133546188e-02F, 3.030381538e-02F, 3.202793747e-02F, 3.277751803e-02F, + 3.182782605e-02F, 3.276503459e-02F, 3.067059256e-02F, 3.003919311e-02F, 3.314559162e-02F, 2.974822186e-02F, + 3.080243059e-02F, 3.158874810e-02F, 3.100574203e-02F, 3.203260526e-02F, 3.283066675e-02F, 3.116034903e-02F, + 3.059585765e-02F, 3.043268435e-02F, 3.282723948e-02F, 3.330112994e-02F, 3.322656080e-02F, 3.249901161e-02F, + 2.942900173e-02F, 3.155976534e-02F, 3.306096420e-02F, 3.104242682e-02F, 3.114547580e-02F, 3.160287440e-02F, + 3.073060513e-02F, 3.095744364e-02F, 2.995137870e-02F, 3.364082798e-02F, 3.108185716e-02F, 3.311194852e-02F, + 2.984568477e-02F, 3.147174045e-02F, 3.208220378e-02F, 3.236699477e-02F, 3.270565346e-02F, 2.977907658e-02F, + 3.266822174e-02F, 3.126590326e-02F, 2.988023125e-02F, 3.031304106e-02F, 3.115369566e-02F, 3.405774385e-02F, + 3.260692582e-02F, 2.986288443e-02F, 3.090950474e-02F, 3.308004513e-02F, 3.080018423e-02F, 3.125633672e-02F, + 3.190350905e-02F, 3.164438158e-02F, 3.151721507e-02F, 3.113033623e-02F, 3.331421688e-02F, 3.157866746e-02F, + 2.899906784e-02F, 2.987778559e-02F, 3.155810013e-02F, 3.091441840e-02F, 3.194085881e-02F, 3.222093731e-02F, + 2.963511832e-02F, 2.980876528e-02F, 3.176620975e-02F, 3.122618608e-02F, 3.120645136e-02F, 3.002206609e-02F, + 3.220519051e-02F, 3.137646616e-02F, 3.118307516e-02F, 2.952116728e-02F, 3.140494972e-02F, 3.089134581e-02F, + 3.184593469e-02F, 3.042803146e-02F, 3.086931817e-02F, 3.257353231e-02F, 3.270502388e-02F, 3.314881399e-02F, + 3.096278384e-02F, 3.292129189e-02F, 3.219497204e-02F, 3.106773458e-02F, 2.923697606e-02F, 3.149087727e-02F, + 3.122483753e-02F, 3.144946322e-02F, 3.086858243e-02F, 3.061441518e-02F, 3.131135181e-02F, 2.998080850e-02F, + 3.280178457e-02F, 2.975469269e-02F, 3.215976804e-02F, 3.239337727e-02F, 2.886532433e-02F, 3.318620473e-02F, + 3.086472675e-02F, 3.072051704e-02F, 3.050708212e-02F, 3.332404792e-02F, 3.103146516e-02F, 3.107436001e-02F, + 2.980083227e-02F, 3.269896284e-02F, 3.212493286e-02F, 2.892625704e-02F, 3.112833388e-02F, 3.151416034e-02F, + 3.109791316e-02F, 3.184053302e-02F, 3.266566247e-02F, 2.970876731e-02F, 3.179035708e-02F, 3.316275775e-02F, + 2.903091908e-02F, 2.992418036e-02F, 3.156990930e-02F, 3.120159730e-02F, 3.193632141e-02F, 3.323367611e-02F, + 3.073423356e-02F, 3.006758541e-02F, 3.184701875e-02F, 3.200211376e-02F, 3.158373013e-02F, 3.195442632e-02F, + 3.139112890e-02F, 3.190138191e-02F, 3.204762936e-02F, 3.144560009e-02F, 3.033157624e-02F, 3.228969872e-02F, + 3.271705657e-02F, 3.226950765e-02F, 3.090384416e-02F, 3.259346634e-02F, 3.069512732e-02F, 3.378597274e-02F, + 3.153815120e-02F, 2.940639108e-02F, 3.205475211e-02F, 3.149463236e-02F, 3.085270338e-02F, 3.178056702e-02F, + 3.193745762e-02F, 2.905533090e-02F, 3.365898132e-02F, 3.217775375e-02F, 2.828266285e-02F, 3.195327148e-02F, + 3.253003955e-02F, 3.348523378e-02F, 1.980334893e-02F, 2.044087090e-02F, 2.468566969e-02F, 2.472857758e-02F, + -1.366579160e-02F, -1.565374434e-02F, -1.253520418e-02F, -1.450450066e-02F, -1.396322437e-02F, -1.687053218e-02F, + -1.545474119e-02F, -1.835692115e-02F, -1.722197607e-02F, -1.763927564e-02F, -1.990259252e-02F, -1.874677837e-02F, + -1.721519791e-02F, -1.843292452e-02F, -1.514639985e-02F, -1.745073870e-02F, -1.651701331e-02F, -1.529523544e-02F, + -1.822676510e-02F, -2.078376897e-02F, -1.603681594e-02F, -1.715647615e-02F, -1.387557480e-02F, -1.319651585e-02F, + -1.577658206e-02F, -1.618312299e-02F, -1.775181666e-02F, -1.418891735e-02F, -1.705786772e-02F, -1.639417373e-02F, + -1.608266123e-02F, -1.733831875e-02F, -1.694514789e-02F, -1.544559747e-02F, -1.528459601e-02F, -1.340550836e-02F, + -1.780006848e-02F, -1.640420407e-02F, -1.887375861e-02F, -1.584206522e-02F, -1.671241783e-02F, -1.585535333e-02F, + -1.802927442e-02F, -1.660154946e-02F, -1.795308478e-02F, -1.495364867e-02F, -1.854048856e-02F, -1.726512611e-02F, + -1.657817140e-02F, -1.807582751e-02F, -1.597952843e-02F, -1.872259565e-02F, -1.405100804e-02F, -1.691153832e-02F, + -1.712622121e-02F, -1.608452015e-02F, -1.826181449e-02F, -1.521297451e-02F, -1.882604696e-02F, -1.693150029e-02F, + -2.047944628e-02F, -1.619467512e-02F, -1.725537330e-02F, -1.577211916e-02F, -1.684523374e-02F, -1.446406916e-02F, + -1.633350924e-02F, -1.479111705e-02F, -1.733489148e-02F, -1.382589340e-02F, -1.733809523e-02F, -1.436637156e-02F, + -2.006575465e-02F, -1.572810113e-02F, -1.758141629e-02F, -1.580555178e-02F, -1.423309371e-02F, -1.472839527e-02F, + -1.635343768e-02F, -1.630100794e-02F, -1.638040133e-02F, -1.636515558e-02F, -1.472858153e-02F, -1.875249855e-02F, + -1.651523076e-02F, -1.660590060e-02F, -1.762439311e-02F, -1.453800965e-02F, -1.692334563e-02F, -1.494881697e-02F, + -1.700009778e-02F, -1.682631858e-02F, -1.854206808e-02F, -2.037337236e-02F, -1.652514935e-02F, -1.702897064e-02F, + -1.786264032e-02F, -1.298044343e-02F, -1.760107838e-02F, -1.488823723e-02F, -1.546164509e-02F, -1.724389941e-02F, + -1.848676801e-02F, -1.664286479e-02F, -1.777089946e-02F, -1.589244232e-02F, -1.721596904e-02F, -1.645515673e-02F, + -2.072309889e-02F, -2.102434821e-02F, -1.710214466e-02F, -1.833976805e-02F, -1.619830355e-02F, -1.634832658e-02F, + -1.780238561e-02F, -1.725396514e-02F, -1.496800967e-02F, -1.639688760e-02F, -1.410238352e-02F, -1.836758479e-02F, + -1.436184626e-02F, -2.156131342e-02F, -1.403680816e-02F, -1.913324930e-02F, -1.470882166e-02F, -1.680634730e-02F, + -1.925382391e-02F, -1.624546014e-02F, -1.998927444e-02F, -1.439202856e-02F, -1.642886922e-02F, -1.601894386e-02F, + -1.401293464e-02F, -1.755525544e-02F, -1.789694279e-02F, -1.765442081e-02F, -1.698904485e-02F, -1.605867594e-02F, + -1.568715088e-02F, -1.773730479e-02F, -1.608213969e-02F, -1.612025127e-02F, -1.749920100e-02F, -1.771002822e-02F, + -1.881318353e-02F, -1.605083048e-02F, -1.703575812e-02F, -1.475752611e-02F, -1.382765360e-02F, -1.351401396e-02F, + -1.597240567e-02F, -1.643608138e-02F, -1.521260478e-02F, -1.636705734e-02F, -1.413986273e-02F, -1.462038606e-02F, + -1.706144400e-02F, -1.523335651e-02F, -1.675903797e-02F, -1.477137860e-02F, -1.793176867e-02F, -1.506106276e-02F, + -1.741511747e-02F, -1.320323721e-02F, -1.776446775e-02F, -1.438348088e-02F, -1.594496332e-02F, -1.618566364e-02F, + -1.709374599e-02F, -1.899338700e-02F, -1.844166033e-02F, -2.044104412e-02F, -1.710738055e-02F, -1.901315525e-02F, + -1.533228718e-02F, -1.544823870e-02F, -1.344540529e-02F, -1.601224020e-02F, -1.587529108e-02F, -1.557468809e-02F, + -1.558559015e-02F, -1.393850427e-02F, -1.783924736e-02F, -1.328153815e-02F, -1.901743375e-02F, -1.489194762e-02F, + -1.524145436e-02F, -2.018409967e-02F, -1.250856090e-02F, -1.844968461e-02F, -1.471228525e-02F, -1.779046468e-02F, + -1.531479973e-02F, -1.930659823e-02F, -1.395661663e-02F, -1.677069627e-02F, -1.506829262e-02F, -1.582816243e-02F, + -1.693017781e-02F, -1.303866319e-02F, -1.493947767e-02F, -1.725277305e-02F, -1.670560986e-02F, -1.534454431e-02F, + -1.903999783e-02F, -1.624216698e-02F, -1.546811592e-02F, -1.678537391e-02F, -1.348439325e-02F, -1.407733094e-02F, + -1.668516360e-02F, -1.864681020e-02F, -1.795149036e-02F, -1.810031012e-02F, -1.575955190e-02F, -1.584085263e-02F, + -1.651870646e-02F, -1.757291891e-02F, -1.748098060e-02F, -1.707961597e-02F, -1.692571118e-02F, -1.723570190e-02F, + -1.579646952e-02F, -1.777746901e-02F, -1.654147916e-02F, -1.872964948e-02F, -1.763025671e-02F, -1.896446384e-02F, + -1.548436750e-02F, -2.039224282e-02F, -1.594517380e-02F, -1.693643071e-02F, -1.767455600e-02F, -1.454450283e-02F, + -1.597323641e-02F, -1.616268605e-02F, -1.673690975e-02F, -1.448566746e-02F, -1.891930774e-02F, -1.358130015e-02F, + -1.650513522e-02F, -1.822195016e-02F, -1.314919628e-02F, -1.999846101e-02F, -1.630993187e-02F, -1.821136475e-02F, + -1.092234347e-02F, -1.125198230e-02F, -4.308688641e-02F, -4.388641194e-02F, -6.272853911e-02F, -6.296809763e-02F, + -6.233945116e-02F, -6.291300803e-02F, -6.221941486e-02F, -6.267620623e-02F, -6.173710525e-02F, -6.058553606e-02F, + -6.174205244e-02F, -6.293290854e-02F, -6.107114255e-02F, -6.097346172e-02F, -6.116766110e-02F, -6.250943989e-02F, + -6.325445324e-02F, -6.090231240e-02F, -6.095353514e-02F, -6.341008842e-02F, -6.088601425e-02F, -5.967522413e-02F, + -6.064912304e-02F, -6.051246449e-02F, -6.298313290e-02F, -6.472432613e-02F, -6.118823215e-02F, -6.097814441e-02F, + -6.065593660e-02F, -6.155529991e-02F, -6.072166562e-02F, -6.094143540e-02F, -6.271942705e-02F, -6.056483462e-02F, + -6.118374318e-02F, -6.100130826e-02F, -6.146778166e-02F, -6.306051463e-02F, -6.156660616e-02F, -6.066081673e-02F, + -6.018782035e-02F, -6.085776538e-02F, -6.276049465e-02F, -6.122953072e-02F, -6.150276586e-02F, -6.058344617e-02F, + -6.089447066e-02F, -6.171931326e-02F, -6.005484983e-02F, -6.136141345e-02F, -6.175683439e-02F, -6.073828787e-02F, + -6.261935085e-02F, -6.096205488e-02F, -6.271199882e-02F, -6.197273731e-02F, -6.143082306e-02F, -6.192856282e-02F, + -5.990980938e-02F, -6.318042427e-02F, -6.104033440e-02F, -6.173120439e-02F, -5.923967436e-02F, -6.176758930e-02F, + -6.177273765e-02F, -6.089923531e-02F, -6.202821434e-02F, -6.221828610e-02F, -6.215150282e-02F, -6.142666191e-02F, + -6.156094745e-02F, -6.245388836e-02F, -6.129548326e-02F, -6.211298332e-02F, -6.035294011e-02F, -6.123251840e-02F, + -6.094503030e-02F, -6.051610410e-02F, -6.301961839e-02F, -6.188076362e-02F, -6.080577150e-02F, -6.088244170e-02F, + -6.081123278e-02F, -6.169947237e-02F, -6.155228987e-02F, -6.037475541e-02F, -6.076494977e-02F, -6.108270958e-02F, + -5.954282731e-02F, -6.292489171e-02F, -6.112079695e-02F, -6.197201833e-02F, -6.119021401e-02F, -6.135031208e-02F, + -6.093444303e-02F, -5.957950652e-02F, -6.084490567e-02F, -6.095529348e-02F, -6.242161617e-02F, -6.324908882e-02F, + -6.047995016e-02F, -6.201467663e-02F, -6.270429492e-02F, -6.079376861e-02F, -6.031708792e-02F, -6.153381616e-02F, + -6.010919064e-02F, -6.168899313e-02F, -6.251255423e-02F, -6.188639253e-02F, -5.989122391e-02F, -6.012306735e-02F, + -6.075556949e-02F, -6.035025045e-02F, -6.342341751e-02F, -6.240819767e-02F, -6.065954268e-02F, -6.098023802e-02F, + -6.318808347e-02F, -6.091312692e-02F, -6.192918494e-02F, -6.208700314e-02F, -6.212073937e-02F, -5.964047462e-02F, + -6.163584813e-02F, -6.148139015e-02F, -6.257081777e-02F, -6.302660704e-02F, -5.993490666e-02F, -6.160471216e-02F, + -6.013336033e-02F, -6.194412336e-02F, -6.175310165e-02F, -6.087852642e-02F, -6.343518198e-02F, -6.192785129e-02F, + -6.108869612e-02F, -6.104305387e-02F, -6.071396172e-02F, -6.377323717e-02F, -6.255777925e-02F, -6.087547541e-02F, + -6.128129363e-02F, -6.246776879e-02F, -6.127976626e-02F, -6.051092967e-02F, -6.084154174e-02F, -6.157414243e-02F, + -6.037747860e-02F, -6.280551106e-02F, -6.370057166e-02F, -6.284622848e-02F, -6.208477914e-02F, -6.152054667e-02F, + -6.211140007e-02F, -6.018529460e-02F, -6.244827434e-02F, -6.202542782e-02F, -6.100377440e-02F, -6.091464683e-02F, + -6.114548817e-02F, -6.181818619e-02F, -6.055636704e-02F, -6.091960892e-02F, -6.025214493e-02F, -6.261368096e-02F, + -6.107286736e-02F, -6.212968379e-02F, -6.199954078e-02F, -6.210837513e-02F, -6.244162098e-02F, -6.098543480e-02F, + -5.972565338e-02F, -6.079054996e-02F, -6.175794825e-02F, -6.062002480e-02F, -6.133954972e-02F, -6.320069730e-02F, + -6.316801161e-02F, -6.298344582e-02F, -6.092131138e-02F, -6.155319139e-02F, -6.165055558e-02F, -6.196917593e-02F, + -6.156405807e-02F, -6.196830049e-02F, -6.051244214e-02F, -6.190812215e-02F, -6.161567569e-02F, -6.000612304e-02F, + -6.337466836e-02F, -5.983389169e-02F, -6.139362603e-02F, -6.264059991e-02F, -6.134989858e-02F, -6.015243381e-02F, + -6.156706065e-02F, -6.274387240e-02F, -6.278894097e-02F, -6.161846593e-02F, -6.081916392e-02F, -6.317219883e-02F, + -6.298267096e-02F, -6.099067256e-02F, -6.230038032e-02F, -6.081109494e-02F, -5.988157541e-02F, -6.293413788e-02F, + -6.075955927e-02F, -6.037368253e-02F, -6.331131607e-02F, -6.332188845e-02F, -6.107876077e-02F, -6.039290503e-02F, + -6.024610996e-02F, -5.945201218e-02F, -6.170260906e-02F, -6.279771775e-02F, -6.253028661e-02F, -6.142561510e-02F, + -6.097405031e-02F, -6.221337616e-02F, -6.146703288e-02F, -6.152469292e-02F, -6.223694608e-02F, -6.115197763e-02F, + -6.228219345e-02F, -6.095391139e-02F, -6.028022990e-02F, -6.152317300e-02F, -6.197866797e-02F, -6.155221537e-02F, + -6.146821752e-02F, -6.135353073e-02F, -6.127114967e-02F, -6.385772675e-02F, -6.169093773e-02F, -6.123477221e-02F, + -6.262195855e-02F, -6.238905713e-02F, -6.081726775e-02F, -6.289032102e-02F, -6.124032289e-02F, -6.047854573e-02F, + -6.393662840e-02F, -6.022705883e-02F, -6.039805710e-02F, -6.068486348e-02F, -6.536971033e-02F, -6.701525301e-02F, +}; + +inline constexpr float kDenseTdnnBlock[] = { + -6.664474010e-01F, -8.050004244e-01F, -6.033317447e-01F, 2.855022252e-01F, -8.309143186e-01F, 4.631809425e-03F, + 2.511028945e-01F, -2.914390564e-01F, -3.932193816e-01F, -8.877019882e-01F, -6.909688115e-01F, 9.739603400e-01F, + -9.226090312e-01F, 6.249937415e-01F, 1.842848361e-01F, -1.670117527e-01F, 6.727538258e-02F, -6.502074003e-01F, + -8.190270662e-01F, -5.844432116e-01F, -7.240037322e-01F, 1.835842580e-01F, -3.028750420e-01F, -4.029914737e-01F, + 8.554711342e-01F, 6.262818724e-02F, 1.856071353e-01F, 5.347795039e-02F, -2.053743452e-01F, 7.359949499e-02F, + -9.904302359e-01F, -8.816932887e-02F, -5.898572206e-01F, -1.377408355e-01F, 2.715365887e-01F, -1.983237825e-02F, + 3.804090992e-02F, -7.441635132e-01F, 2.352955937e-01F, -3.707670569e-01F, -6.165668368e-01F, 2.254556566e-01F, + -8.447247744e-01F, -6.586186290e-01F, -2.485919893e-01F, 3.622625470e-01F, -8.985425234e-01F, -5.122727156e-01F, + -1.730104089e-01F, -8.652110100e-01F, -7.908225805e-02F, 8.358133435e-01F, 8.895398378e-01F, 4.895323515e-01F, + -5.318419933e-01F, -2.767958939e-01F, -9.839498997e-01F, -3.742840141e-02F, -1.880448014e-01F, 8.184153438e-01F, + -1.156209037e-01F, -3.071114719e-01F, -7.192144394e-01F, -5.415835977e-01F, 9.705035947e-03F, -1.097384933e-02F, + -9.126801491e-01F, 7.484034896e-01F, 9.729349986e-03F, 8.077436090e-01F, -8.946579695e-01F, 9.029577971e-01F, + -8.433012962e-01F, -1.985573769e-01F, -4.493118525e-01F, -2.357722223e-01F, -4.805492461e-01F, 9.343555570e-01F, + -1.216202751e-01F, 2.866094410e-01F, -2.750687599e-01F, -9.973037839e-01F, 8.200377226e-01F, 4.602562487e-01F, + -2.838012874e-01F, 3.439925611e-01F, -3.156151175e-01F, -7.636304498e-01F, -7.287041843e-02F, 4.998852015e-01F, + 9.888783842e-02F, -1.991423815e-01F, 8.270555139e-01F, 2.920468152e-01F, -8.178199530e-01F, -6.746445298e-01F, + 5.028232932e-01F, -9.104613662e-01F, -7.236293703e-02F, -7.786595225e-01F, 5.007073283e-01F, 7.074143887e-01F, + -7.964788079e-01F, 8.217760324e-01F, 6.427350640e-01F, 8.645296842e-02F, -3.301993906e-01F, -5.981086493e-01F, + 9.337267876e-01F, -9.881489724e-02F, -2.444174327e-02F, -5.259574056e-01F, 4.317086041e-01F, -8.716689348e-01F, + 8.730545640e-01F, -6.671641767e-02F, -1.934030056e-01F, -8.739140630e-02F, -2.910531685e-02F, -7.316378355e-01F, + 8.771622777e-01F, -9.700292945e-01F, 6.365862489e-02F, 1.887623817e-01F, -9.499511681e-03F, -5.859360099e-01F, + 8.354740739e-01F, -7.443530560e-01F, -2.078851759e-01F, 4.308537245e-01F, 3.720819056e-01F, -7.525839210e-01F, + -1.690974236e-01F, 1.209637597e-01F, -7.428814769e-01F, -3.304158449e-01F, -8.007102609e-01F, 5.659233332e-01F, + 6.734610200e-01F, 1.451503932e-01F, -2.621025443e-01F, 2.761949003e-01F, -4.911435246e-01F, 1.132388860e-01F, + 3.871789575e-02F, -5.967257619e-01F, -4.205853641e-01F, -9.241753221e-01F, 3.267784119e-01F, 2.716883719e-01F, + -4.897484779e-01F, -8.072669804e-02F, 7.676272392e-01F, 5.228054523e-01F, -6.352753639e-01F, -4.888826907e-01F, + 6.138608456e-01F, -2.078170888e-02F, 1.031947210e-01F, -2.406657189e-01F, -8.359152079e-01F, 7.027919888e-01F, + -2.446147650e-01F, 3.142356873e-01F, 4.046423137e-01F, -1.114351824e-01F, 9.996609688e-01F, -2.876989245e-01F, + 7.783250809e-01F, -8.827987909e-01F, -2.570291162e-01F, -9.071839452e-01F, -8.895539641e-01F, 3.128595352e-01F, + 8.322224021e-02F, 9.339975715e-01F, 2.982799411e-01F, 7.739809155e-01F, 9.595592618e-01F, 1.735282242e-01F, + 8.693872690e-01F, 3.533692360e-01F, -7.592989206e-01F, -5.471715927e-01F, -4.307863414e-01F, 4.947758615e-01F, + 8.318794966e-01F, -7.635436058e-01F, 9.442775249e-01F, 3.951825500e-01F, -5.529165268e-01F, -2.284403592e-01F, + -8.018803000e-01F, -6.942731738e-01F, 9.859101176e-01F, -7.191291451e-01F, -1.054335269e-03F, 9.370195866e-01F, + -6.877692789e-02F, -3.478315771e-01F, -9.347282648e-01F, -4.047136605e-01F, -4.522712827e-01F, 8.628836870e-01F, + 6.391599178e-01F, -4.623557031e-01F, -3.343902826e-01F, -4.547423720e-01F, 7.765261829e-02F, -7.330120206e-01F, + -4.814750850e-01F, -1.475774348e-01F, 5.905737877e-01F, -5.312379077e-02F, 9.933858514e-01F, -8.717277646e-01F, + 9.260625839e-01F, 2.326330403e-03F, 7.803340554e-01F, 3.322061151e-02F, -6.539536119e-01F, 1.356292069e-01F, + -7.397992611e-01F, -7.498250008e-01F, -3.166977167e-01F, -2.690460682e-01F, -3.837248385e-01F, 3.762080669e-01F, + -8.557103276e-01F, -5.187624693e-01F, 8.269799352e-01F, -7.119953036e-01F, 4.759521186e-01F, -7.215685248e-01F, + 2.405771613e-01F, 5.943665504e-01F, 5.488312840e-01F, -2.604136467e-01F, 5.821213126e-01F, -6.429365277e-01F, + -2.582524717e-01F, -8.744671345e-01F, -1.502330899e-01F, 6.327723265e-01F, 4.696826637e-01F, 9.459577203e-01F, + 3.250854611e-01F, 1.286806762e-01F, -6.284526587e-01F, -2.770907283e-01F, 8.289164305e-01F, -2.445905209e-01F, + 8.672105074e-01F, -3.139275908e-01F, 5.248607397e-01F, -3.726392686e-01F, 5.377714038e-01F, -8.834403753e-01F, + 3.533720672e-01F, -9.786468148e-01F, 2.475789376e-02F, -4.588267803e-01F, 5.214825273e-01F, -7.372943684e-03F, + -7.027382255e-01F, -7.787525058e-01F, 5.374923348e-01F, -6.087277457e-02F, -5.568491220e-01F, 7.314171195e-01F, + 8.940972090e-01F, -4.409804642e-01F, -4.315956309e-02F, -4.504736364e-01F, -1.674794406e-01F, 3.711091876e-01F, + -2.680053115e-01F, 7.981366515e-01F, -2.334988415e-01F, 7.814981788e-02F, 9.765727520e-01F, 6.156728268e-01F, + 2.451543361e-01F, 4.749840498e-01F, -2.594967484e-01F, -3.390634805e-02F, -7.046862841e-01F, 4.863301218e-01F, + -2.245335281e-02F, 2.953205444e-02F, 9.195434302e-02F, -6.654899716e-01F, 3.790899515e-01F, -1.560244262e-01F, + 9.690296054e-01F, 4.132890105e-01F, 6.264411807e-01F, 2.290912867e-01F, 9.179500937e-01F, 7.162752151e-01F, + -9.449150562e-01F, 5.144696236e-01F, -6.699326634e-01F, 2.126323581e-01F, -9.771750867e-02F, -7.535083592e-02F, + 9.048096538e-01F, -3.942195326e-02F, 1.548203230e-01F, -4.917097986e-01F, 3.004800379e-01F, 8.668797612e-01F, + 4.783124328e-01F, -1.467805803e-01F, 7.539263368e-01F, 4.086527824e-01F, -7.846199274e-01F, 9.859311581e-01F, + 2.552604303e-02F, 1.391711831e-01F, -4.813123345e-01F, -9.032167196e-01F, 3.621837795e-01F, 1.885038763e-01F, + 3.005030155e-01F, -8.391922712e-01F, 4.165257514e-01F, 5.144611001e-01F, 8.563503623e-01F, 8.095439672e-01F, + -3.175094426e-01F, 5.317869186e-01F, 5.684756637e-01F, 2.073357403e-01F, 5.060603023e-01F, -3.055848181e-01F, + 8.518061042e-01F, 2.949560583e-01F, 1.791647971e-01F, -2.877335846e-01F, -7.114386559e-01F, -1.094859559e-02F, + -4.415449500e-02F, 2.628201842e-01F, -2.796897590e-01F, -3.477995992e-01F, 7.572899461e-01F, 2.655950785e-01F, + -3.536702991e-01F, 9.818397760e-01F, -6.905337572e-01F, 5.788822174e-01F, 7.097736597e-01F, -2.924439311e-01F, + -9.720520973e-01F, -1.781101227e-01F, -9.012425542e-01F, -9.130036831e-01F, -4.956623912e-02F, 9.819947928e-02F, + -2.278159186e-02F, 8.035452962e-01F, 3.595397174e-01F, -8.414040804e-01F, -2.365501523e-01F, -3.675630093e-01F, + -9.636821151e-01F, 4.226312339e-01F, -2.153841704e-01F, 9.369049072e-01F, -1.362927724e-02F, 4.413759708e-01F, + 6.083116531e-01F, 1.144513711e-01F, -1.911156476e-01F, -5.126938224e-01F, 7.987384200e-01F, -3.095664680e-01F, + -5.073916912e-01F, 3.050409257e-01F, 5.291264653e-01F, 9.909027219e-01F, -5.694952011e-01F, 2.657601237e-01F, + 4.639302790e-01F, 3.388649225e-02F, -3.427698016e-01F, -2.113866359e-01F, -2.168427259e-01F, -3.157155216e-01F, + -9.487647563e-02F, -1.413163077e-02F, -8.867837191e-01F, 2.635756731e-01F, 3.426804841e-01F, -1.325456649e-01F, + 8.897725344e-01F, -2.011542618e-01F, 6.418443322e-01F, 8.123425394e-02F, 1.223976165e-01F, 2.258708477e-01F, + -4.318428412e-02F, 5.249078274e-01F, -3.457672596e-01F, 3.288011849e-01F, -8.443086147e-01F, -3.080004267e-02F, + -1.703541130e-01F, 6.418990493e-01F, -8.499538526e-03F, 5.101053119e-01F, 9.047672153e-01F, -1.671858877e-01F, + 1.324759424e-01F, -4.741707072e-02F, -3.389914036e-01F, 4.480639994e-01F, 9.127367735e-01F, -2.983556688e-01F, + -7.545189261e-01F, -3.626836240e-01F, 5.472349524e-01F, -8.728799224e-01F, 1.799205244e-01F, -7.045103312e-01F, + -8.921402693e-01F, -6.028670073e-01F, 5.946424007e-01F, -7.756362557e-01F, -3.591274619e-01F, 8.803128600e-01F, + -8.652650118e-01F, -8.115112782e-01F, -6.563092470e-01F, 8.587846160e-01F, 7.414793372e-01F, 3.965703249e-01F, + 2.277363837e-01F, -8.065672517e-01F, 6.338966489e-01F, -2.389589399e-01F, 4.825890958e-01F, 8.555942178e-01F, + 4.290379882e-01F, -1.803684384e-01F, -3.149066865e-01F, -8.782876730e-01F, 6.531119347e-01F, -7.115103602e-01F, + -3.921875656e-01F, -3.974310756e-01F, -9.508286119e-01F, -5.055146813e-01F, -4.608461857e-01F, -1.911870241e-01F, + 4.988023043e-01F, 2.529076040e-01F, -3.362723067e-02F, -1.729534417e-01F, -6.207580119e-02F, 9.435750842e-01F, + -5.707145929e-01F, 9.245682955e-01F, 5.771223307e-01F, 7.695973516e-01F, -4.816107154e-01F, -7.981315255e-01F, + -3.351185620e-01F, -2.063232064e-01F, -3.397321105e-01F, -6.152236462e-01F, 2.667672932e-01F, 3.305573773e-04F, + -5.809317827e-01F, -2.162463069e-01F, -4.274219275e-01F, -5.426065922e-01F, -2.178867310e-01F, -2.647005320e-01F, + 6.589328498e-02F, 7.944468260e-01F, -1.757274866e-01F, -2.375844717e-01F, -7.359752059e-01F, 5.808811784e-01F, + 2.570985556e-01F, 2.541255355e-01F, -1.692933589e-01F, -4.583499432e-01F, -9.577560425e-01F, 4.942984134e-02F, + 3.795484304e-01F, 8.399891853e-01F, -6.508278102e-02F, -8.741164207e-01F, 5.405238867e-01F, -4.956785142e-01F, + 7.576718926e-01F, -7.999979854e-01F, -5.542503595e-01F, -2.162376791e-01F, -1.737199724e-01F, 3.788846135e-01F, + 3.609797731e-02F, -5.698869228e-01F, 5.262937546e-01F, 8.279055357e-02F, 6.384890079e-01F, -1.692315042e-01F, + 3.110579252e-01F, 7.678477764e-01F, 8.698941767e-02F, 6.515657306e-01F, -5.708586574e-01F, 4.858506322e-01F, + 7.648192346e-02F, -3.335604668e-01F, -9.691867232e-01F, -3.502238095e-01F, 5.001254678e-01F, -5.190556645e-01F, + -2.715439498e-01F, -8.137830496e-01F, 9.430927634e-01F, 9.438490272e-01F, 3.144261241e-01F, 5.448603034e-01F, + 1.808621883e-01F, 3.459543884e-01F, -6.350125074e-01F, 4.887378514e-01F, -8.210591674e-01F, 9.157370329e-01F, + -6.355987191e-01F, -8.988244534e-01F, 7.061642827e-04F, 3.749132752e-01F, 9.638181329e-01F, 5.816945434e-01F, + 6.051118970e-01F, -5.667842180e-02F, -4.269821644e-01F, 4.816441238e-01F, 5.735348463e-01F, -1.880260557e-01F, + -2.505932450e-01F, -2.069253176e-01F, -9.018681049e-01F, 7.941548228e-01F, -2.452407777e-01F, 6.138986945e-01F, + 5.724484324e-01F, 5.307249427e-01F, 6.174401641e-01F, -5.717163086e-01F, 1.627678126e-01F, -4.153217673e-01F, + -5.373532176e-01F, 8.368613124e-01F, -3.702859879e-01F, -1.614650488e-01F, -6.893174648e-01F, -5.093734264e-01F, + -8.847814202e-01F, 2.484584749e-01F, -3.879042268e-01F, -5.655033886e-02F, 6.974133849e-01F, -2.167632282e-01F, + -8.330242634e-01F, -1.569324136e-01F, -2.964902520e-01F, 9.633292556e-01F, -6.570701599e-01F, 7.696442604e-01F, + -6.935280561e-01F, -3.058093786e-01F, 7.901423573e-01F, 1.510845721e-01F, -2.283288240e-01F, -2.796963453e-01F, + 5.629456043e-01F, 6.272542477e-01F, 2.510562837e-01F, -7.747378349e-01F, 9.128817916e-01F, 7.498852015e-01F, + -9.360026717e-01F, 9.698017836e-01F, -5.564735532e-01F, 3.322657347e-01F, -5.355663896e-01F, 9.938412309e-01F, + -7.834252715e-01F, -3.477059007e-01F, 6.860411167e-01F, -2.723939419e-01F, -9.284613729e-01F, -5.550507903e-01F, + 8.659651875e-02F, 7.912544012e-01F, 3.686855733e-01F, -5.280861855e-01F, -1.536009312e-01F, -6.307438761e-02F, + -7.059617043e-01F, 2.002917528e-01F, -4.923479259e-01F, 9.720005393e-01F, -8.494264446e-03F, -2.954652607e-01F, + -8.451780677e-01F, -3.297430277e-01F, -3.096103668e-01F, -8.775500655e-01F, -9.086449742e-01F, -8.487629890e-01F, + -7.732216716e-01F, -7.301587462e-01F, 2.499547303e-01F, 3.651431501e-01F, 1.639930755e-01F, -9.499669671e-01F, + 7.545730472e-01F, -4.084882736e-01F, 8.872366548e-01F, -5.245567858e-02F, -4.833217561e-01F, -6.319701672e-01F, + -2.243450582e-01F, -4.880632162e-01F, -4.660360515e-01F, -1.330654472e-01F, -3.655579686e-01F, 6.718791723e-01F, + 4.120429754e-01F, 5.994347334e-01F, 9.229288101e-01F, 7.791830897e-01F, 7.852119803e-01F, 7.046191692e-01F, + -2.705869973e-01F, -4.928490818e-01F, -1.422984153e-01F, -2.427942753e-01F, 7.747685909e-02F, 6.855545044e-01F, + -8.716999292e-01F, 2.895072699e-01F, 9.422993064e-01F, 8.526402712e-01F, 5.600883365e-01F, 1.347106099e-01F, + 4.504576027e-01F, -1.687182635e-01F, 3.017595112e-01F, 5.611304045e-01F, 2.941960990e-01F, -7.632064819e-01F, + 1.761739999e-01F, -1.553411633e-01F, -7.299664021e-01F, -2.394737490e-02F, -5.888661146e-01F, 5.009382367e-01F, + 6.972378492e-01F, 2.979265451e-01F, -2.544652224e-01F, 2.713663876e-01F, 9.736680984e-01F, -3.328641355e-01F, + -4.278967977e-01F, -7.050884366e-01F, 8.508763909e-01F, -7.497760653e-01F, -5.634060502e-01F, -6.541235447e-01F, + 7.174715996e-01F, -5.479285121e-01F, 5.090632662e-02F, 9.984844923e-01F, -9.348631501e-01F, 9.410431981e-01F, + 2.135388106e-01F, 6.388362646e-01F, -5.680363774e-01F, 9.780054539e-02F, -4.667055905e-01F, -7.967133075e-02F, + 4.234617651e-01F, -9.280466437e-01F, 5.214306116e-01F, 5.684632659e-01F, 2.717444003e-01F, -3.582961857e-01F, + 1.937444806e-01F, 4.589067400e-01F, 8.049890995e-01F, -8.892186880e-01F, -4.063736200e-01F, 7.977052927e-01F, + 2.540565431e-01F, 8.930848241e-01F, 8.342376351e-02F, 6.713445783e-01F, -7.640583515e-01F, -1.983345486e-02F, + -8.783587813e-01F, 8.543910384e-01F, -1.068965495e-01F, 2.609632313e-01F, 3.200134039e-01F, -8.341576457e-01F, + -7.600649595e-01F, -3.375936449e-01F, -4.055143297e-01F, 2.563565969e-01F, 2.837358117e-01F, 7.481672168e-01F, + 2.679900229e-01F, 6.118478179e-01F, -8.151832819e-01F, 2.418651581e-01F, -4.552429020e-01F, -9.000567794e-01F, + 1.228863150e-01F, -5.701969862e-01F, -7.556608915e-01F, -1.877306253e-01F, -5.809675455e-01F, -8.979833126e-01F, + -9.125647545e-01F, -9.958071709e-01F, 3.867124915e-01F, 6.185683608e-01F, -2.027076930e-01F, -3.582324386e-01F, + 5.574515462e-01F, 5.239468217e-01F, 4.837752283e-01F, 9.629920125e-01F, -6.970217824e-01F, -5.465078354e-01F, + 2.736031115e-01F, -4.207196832e-01F, -1.347083449e-01F, -7.616575956e-01F, 6.050362587e-01F, -5.464013815e-01F, + 8.742330074e-01F, 7.239961624e-01F, -9.551705718e-01F, 4.810151458e-01F, -8.158591986e-01F, -1.232985873e-02F, + -5.492005348e-01F, -4.472810626e-01F, 9.428728372e-02F, 7.216366529e-01F, 2.099930644e-01F, 4.850011468e-01F, + 5.780225396e-01F, 8.287416697e-01F, 8.881650567e-01F, 5.507509708e-01F, -6.578555703e-01F, 7.387467027e-01F, + -3.048114479e-01F, -5.871059895e-01F, 1.773558110e-01F, -1.773403957e-02F, 3.406339884e-01F, 8.589937687e-01F, + -2.830123007e-01F, 2.286705375e-01F, -3.177341521e-01F, -3.297594488e-01F, -3.663060367e-01F, -2.519116402e-01F, + 7.283867002e-01F, -6.155751944e-01F, 1.497628540e-01F, 3.557390869e-01F, -6.155465245e-01F, -5.028037354e-02F, + -2.449517846e-01F, -6.209838390e-01F, 1.100116223e-01F, -3.539802134e-01F, 9.628172517e-01F, -8.227713108e-01F, + 4.073410034e-01F, -2.103788406e-01F, 6.721790433e-01F, -6.329340935e-01F, 3.722984344e-02F, 2.658647299e-01F, + -1.599479169e-01F, -1.926832795e-01F, 9.927109480e-01F, 6.814389229e-01F, 2.790684402e-01F, 7.747268081e-01F, + -6.079727411e-01F, 2.628262937e-01F, -4.848546535e-02F, 1.336201280e-01F, 2.465824783e-01F, 6.011031866e-01F, + -5.440565348e-01F, 9.546066076e-02F, 8.000757694e-01F, -7.375778556e-01F, 8.415561318e-01F, 2.126513422e-01F, + -9.416143298e-01F, -5.858997703e-01F, 8.906577229e-01F, -9.961817414e-02F, -2.768361866e-01F, -5.877027512e-01F, + -5.745936632e-01F, -4.071307480e-01F, 7.238097787e-01F, -6.211617589e-01F, 8.296748996e-01F, -4.815910459e-01F, + -9.699926376e-01F, -9.335634112e-02F, -6.494459510e-01F, -1.529469192e-01F, 2.597963512e-01F, 1.436052918e-01F, + -5.516976118e-01F, -1.441869438e-01F, -5.118815899e-01F, 6.298379898e-01F, -9.815997630e-02F, 3.185220957e-01F, + -2.697710693e-01F, -7.490365505e-01F, 2.187784463e-01F, -2.187455893e-01F, -8.759133220e-01F, 2.909142673e-01F, + 9.527752399e-01F, 7.881079912e-01F, -7.371293902e-01F, -8.021841645e-01F, 9.152559042e-01F, -3.633273542e-01F, + -7.512621284e-01F, 1.297433823e-01F, -5.031378865e-01F, 1.248575523e-01F, 5.607483387e-01F, -5.167914629e-01F, + -2.453697175e-01F, 5.241100788e-01F, 5.939633846e-01F, 5.391392112e-02F, 7.655432820e-01F, 9.728021026e-01F, + -5.713831782e-01F, -5.601046085e-01F, 7.488980293e-01F, -9.997664690e-01F, -9.793427587e-01F, 5.566176176e-01F, + -3.796575367e-01F, 6.072380766e-02F, -4.873434603e-01F, 8.596466184e-01F, -2.117136901e-04F, 9.833519459e-01F, + -3.996217847e-01F, 6.257488132e-01F, -9.962878227e-01F, -2.563327551e-01F, 9.517392516e-01F, -8.544182777e-01F, + 5.768797994e-01F, -7.713162303e-01F, -7.100995183e-01F, 5.885950327e-01F, -1.730704755e-01F, 9.998702407e-01F, + 9.495025873e-01F, 2.092004940e-02F, -9.990655780e-01F, 9.459697008e-01F, 3.883789107e-02F, -7.433494329e-01F, + -7.807148993e-02F, -3.275252581e-01F, 4.593544602e-01F, 8.437276483e-01F, 4.785989225e-01F, 5.673045665e-02F, + 8.760782331e-02F, -9.118507802e-02F, 5.491551757e-01F, -7.569414377e-01F, -4.958319664e-01F, -6.101943925e-02F, + -7.144238353e-01F, -7.445554733e-01F, -2.894842625e-01F, -6.027989388e-01F, 2.164624333e-01F, -6.660767794e-01F, + -3.828259706e-01F, -8.178694844e-01F, -9.103706479e-02F, -1.576974839e-01F, 5.228335261e-01F, -2.394469827e-01F, + 8.358183503e-01F, -1.950931996e-01F, -5.703524947e-01F, 4.136354923e-01F, 3.163433969e-01F, 1.481660008e-01F, + 4.668997526e-01F, 6.403044462e-01F, 5.050100759e-02F, 6.993579865e-01F, -5.775395036e-01F, -2.849848270e-01F, + -8.407718539e-01F, 3.404595256e-01F, -4.950592220e-01F, -3.370911777e-01F, -3.747889772e-02F, -4.517472982e-01F, + 6.787430644e-01F, -9.795359969e-01F, 1.406152397e-01F, -2.794972062e-01F, -3.919120133e-01F, 7.173724771e-01F, + -3.508950770e-01F, 3.378678560e-01F, -3.095641732e-01F, 6.658640504e-01F, 2.834622189e-02F, 7.003830671e-01F, + -3.074581325e-01F, -2.741955221e-01F, 3.111521304e-01F, -3.294198513e-01F, 2.482297570e-01F, -7.099733353e-01F, + -6.267314553e-01F, 3.711379170e-01F, -1.445515454e-01F, -8.362260461e-01F, 3.835465759e-02F, 8.632213473e-01F, + -5.515952706e-01F, -5.788835883e-01F, -5.041927099e-01F, -4.165343046e-01F, -3.337328434e-01F, 7.406594753e-01F, + 7.057017684e-01F, -8.494126797e-02F, -1.172579899e-01F, 7.489250898e-01F, -1.744603068e-01F, 4.089842439e-01F, + 5.475627780e-01F, 3.494669497e-02F, -6.817838550e-02F, -5.441092700e-02F, -4.932297468e-01F, 3.279979825e-01F, + 1.621214449e-01F, 6.455371380e-01F, -7.420368791e-01F, 9.380366206e-01F, -9.740925431e-01F, 7.583027482e-01F, + 1.590173990e-01F, -4.194413424e-01F, 6.596305966e-01F, 5.552528501e-01F, -4.573510885e-01F, -4.828150868e-01F, + 8.763775826e-01F, -2.030911446e-01F, 3.268274069e-01F, -2.909453809e-01F, -6.616037488e-01F, 2.780088186e-01F, + -4.034560323e-01F, 8.212044239e-01F, -8.203758597e-01F, -6.568111777e-01F, -1.797347516e-01F, 7.502774596e-01F, + -5.354247689e-01F, -8.348873258e-01F, -9.687082767e-01F, -4.744609892e-01F, 3.338521719e-01F, 5.061730742e-02F, + -9.075610042e-01F, -9.876844287e-01F, -4.435960948e-01F, 1.705223322e-01F, 3.877367973e-01F, -5.859714150e-01F, + -4.104413986e-01F, -8.565473557e-01F, -5.582328439e-01F, -2.155221701e-01F, -4.232038278e-03F, 1.664590687e-01F, + 7.382896543e-01F, 5.653705597e-01F, -4.912669063e-01F, 1.386070549e-01F, -9.391647577e-01F, 9.409441352e-01F, + -6.159409285e-01F, -7.633982301e-01F, -9.056894183e-01F, 9.027801454e-02F, -3.360528350e-01F, 9.496202320e-02F, + -5.859377980e-01F, -2.913129330e-01F, -2.815384865e-01F, -5.861501098e-01F, -2.692910610e-03F, 8.042827845e-01F, + 3.515793085e-01F, 4.982362390e-01F, -9.719747305e-01F, -1.012065355e-02F, -1.603981256e-01F, 7.729856372e-01F, + 6.401243806e-01F, -1.291669309e-01F, 9.798441529e-01F, -8.108589649e-01F, 1.440368965e-02F, 2.618679106e-01F, + -7.031292319e-01F, 7.262783647e-01F, -8.843982816e-01F, 1.857513189e-01F, -5.485326052e-01F, -5.610884428e-01F, + 1.113229617e-01F, 5.161650181e-01F, -3.467023671e-01F, -3.111231625e-01F, 2.516450584e-01F, -8.870056272e-02F, + 2.886556685e-01F, 6.415097713e-01F, -8.926509619e-01F, -8.970090747e-01F, -6.983519197e-01F, 2.184269875e-01F, + 8.187710643e-01F, -5.956221819e-01F, 1.489785612e-01F, -9.061498642e-01F, -8.586310744e-01F, -1.828278601e-01F, + -6.167352796e-01F, -8.464559913e-01F, -1.988723874e-01F, -9.394451044e-03F, 9.488401413e-01F, 5.569176078e-01F, + -2.648041546e-01F, 2.204560339e-01F, -2.206998169e-01F, 2.093153149e-01F, 7.903396487e-01F, -7.703230381e-01F, + -6.282854676e-01F, 3.494130373e-01F, 5.083720013e-02F, -9.822932482e-01F, 4.779053628e-01F, 4.361160994e-01F, + -5.761404634e-01F, -3.485599160e-01F, -8.314868212e-01F, -4.220315814e-01F, -8.966500759e-01F, 5.062461495e-01F, + 7.295488119e-01F, 8.551369309e-01F, -9.337556362e-01F, -5.389101505e-01F, -7.566164434e-02F, -7.814050317e-01F, + 3.764754534e-02F, 1.688886732e-01F, -7.641018629e-01F, 2.832213342e-01F, -4.922154248e-01F, 7.575767487e-02F, + -9.063798189e-01F, 7.046857476e-01F, -4.681731164e-01F, -9.292065501e-01F, -4.770525396e-01F, 5.506880879e-01F, + 7.831702232e-01F, 6.608070135e-01F, -3.872582614e-01F, -8.967636228e-01F, 4.101604521e-01F, -9.470261335e-01F, + -8.100932240e-01F, -1.501079835e-02F, -1.674158722e-01F, -1.406863630e-01F, 3.128518760e-01F, -7.429209948e-01F, + -1.022632350e-03F, 5.496581197e-01F, 9.328229725e-02F, 4.537518322e-01F, 4.353609681e-01F, 1.105957031e-01F, + 5.026456118e-01F, 2.035257220e-01F, -4.214448035e-01F, 2.578625679e-01F, -8.433896899e-01F, 1.603650153e-01F, + -2.842188776e-01F, 7.936761975e-01F, -8.388788700e-01F, 4.173091948e-01F, -6.523848772e-01F, 6.575955451e-02F, + 3.468900546e-02F, 6.839113832e-01F, 9.785127044e-01F, 1.700064912e-02F, 6.667817943e-03F, 6.633818895e-02F, + 5.508370399e-01F, 6.323173046e-01F, -8.908600211e-01F, 4.296295643e-01F, -1.113316715e-01F, -7.009980083e-01F, + 2.770018578e-01F, 3.096559644e-01F, -7.458857298e-01F, -1.318078935e-01F, -8.993296623e-01F, 2.235269696e-01F, + -7.327435017e-01F, -6.720897555e-01F, 7.439908981e-01F, -2.554718256e-01F, 4.065260589e-01F, -7.146618366e-01F, + 2.720729709e-01F, -3.582591936e-02F, -8.072523475e-01F, -7.854484320e-01F, -1.241227090e-01F, -7.249443531e-01F, + 3.093121052e-01F, -7.570610046e-01F, -3.921199441e-01F, -5.202752352e-02F, -1.772270650e-01F, -6.874938607e-01F, + -1.667894274e-01F, 1.754380763e-01F, -2.416051179e-01F, 4.997328222e-01F, 4.911294878e-01F, 1.553249545e-02F, + 9.783420563e-01F, 8.658854961e-01F, -9.225851297e-02F, -9.375664592e-01F, 4.199918211e-01F, -3.079456985e-01F, + -7.883331776e-01F, 1.401168406e-01F, -5.047245622e-01F, -6.357402802e-01F, -6.333546042e-01F, -3.992050886e-01F, + 8.666485548e-01F, 4.101351500e-01F, 9.804090261e-01F, 9.504331946e-01F, 5.682228599e-03F, 9.703882337e-01F, + 2.519467175e-01F, 2.821687460e-01F, 8.571469784e-01F, -2.400688976e-01F, -6.991323829e-01F, 7.909191251e-01F, + -8.255741596e-01F, -3.774816692e-01F, -4.263537526e-01F, -9.200872183e-01F, -4.669767618e-01F, -4.603993297e-01F, + -2.948701978e-01F, 9.355180264e-01F, -4.139795005e-01F, 7.914066911e-01F, 8.857867122e-01F, 2.104181498e-01F, + 7.614148259e-01F, 2.890559733e-01F, -4.225981832e-01F, -7.955306172e-01F, 6.612067223e-01F, 4.233608544e-01F, + 2.291164175e-02F, -4.701060057e-01F, 5.398563147e-01F, 8.014456630e-01F, -7.325916886e-01F, 7.135860324e-01F, + 1.668676734e-01F, -9.810146093e-01F, 5.296228528e-01F, 3.109211102e-02F, -7.212812304e-01F, 9.448216558e-01F, + 2.548027337e-01F, -7.545669675e-01F, 3.538919389e-01F, -1.786124706e-01F, 4.925426841e-01F, -8.307010531e-01F, + -7.341310382e-02F, -6.541063190e-01F, -1.542009264e-01F, 4.808280170e-01F, -4.736224711e-01F, -7.340570092e-01F, + -5.302916169e-01F, 3.413369358e-01F, 1.164134890e-01F, -6.204538941e-01F, -5.829457045e-01F, 3.360862732e-01F, + -8.961731791e-01F, 4.220864773e-01F, 4.584498107e-01F, 1.318652630e-01F, 9.631621242e-01F, -3.939869404e-01F, + 8.554499149e-01F, 1.220320910e-01F, -6.693923473e-01F, 9.478235841e-01F, 7.362860441e-02F, -9.474924207e-01F, + 7.106677294e-01F, -1.653128415e-01F, -5.348798260e-02F, -5.235803723e-01F, -4.813957512e-01F, -2.432485819e-01F, + 8.814278841e-01F, 8.541914225e-01F, 7.370417714e-01F, -3.882470131e-01F, 6.772795916e-01F, -1.955831647e-01F, + 8.026202917e-01F, 5.477222800e-01F, -6.535919905e-01F, 6.434293985e-01F, -3.703184128e-01F, 6.482634544e-01F, + 6.931855083e-01F, -2.126755267e-01F, -9.252626896e-01F, 3.901032507e-01F, 5.772119761e-01F, 3.524024785e-01F, + -3.834737837e-01F, -9.853516221e-01F, 3.859075904e-01F, -3.971937597e-01F, 7.525758743e-01F, 2.283936888e-01F, + -5.274521112e-01F, -2.574152052e-01F, 1.373416930e-01F, 6.436502934e-01F, -1.365938187e-01F, 7.617777586e-01F, + 6.536182761e-01F, 3.132122010e-02F, -4.769881070e-02F, -5.368543789e-02F, -6.361887325e-03F, -1.018337831e-01F, + 6.465561986e-01F, 8.361769915e-01F, -1.031211615e-01F, 2.435941845e-01F, -7.231708765e-01F, -9.662704468e-01F, + 7.522051781e-02F, 4.927849472e-01F, -1.234164685e-01F, 1.852562726e-01F, -4.157868922e-01F, 7.451202273e-01F, + 3.671605289e-01F, -4.023962319e-01F, 3.060337603e-01F, 1.176153719e-01F, -6.401897669e-01F, -8.840418458e-01F, + -3.851371408e-01F, 3.998543918e-01F, 2.360488325e-01F, 9.452480078e-01F, 7.208888531e-01F, -9.736157656e-01F, + 6.981680393e-01F, -9.178992361e-02F, -1.087671295e-01F, 9.561284184e-01F, -5.704014301e-01F, 3.876271099e-02F, + 5.031412244e-01F, -1.025970727e-01F, 4.828736782e-01F, -3.472149074e-01F, 5.300790668e-01F, -5.269153714e-01F, + -1.379120052e-01F, 5.008202195e-01F, 9.095295668e-01F, 6.457120180e-01F, -4.881422222e-01F, 3.193778098e-01F, + 7.379894853e-01F, -2.900722027e-01F, -2.320562899e-01F, 3.853325546e-01F, 7.215062380e-01F, -4.365021288e-01F, + 9.276065230e-01F, -3.630405962e-01F, -1.445610970e-01F, -3.482094705e-01F, -5.348742604e-01F, 1.405976713e-01F, + -2.560722828e-01F, 3.546965122e-02F, -4.477004409e-01F, -2.723810077e-01F, -9.599346519e-01F, 8.735804558e-01F, + -9.362730980e-01F, -4.225197732e-01F, -3.998563886e-01F, 2.762362659e-01F, 3.631944954e-01F, -6.131790280e-01F, + 6.455520391e-01F, -2.972138524e-01F, -2.445359528e-01F, -7.024283409e-01F, 9.224071503e-01F, -9.408378601e-01F, + 6.847745180e-01F, -6.280166656e-02F, 2.683482468e-01F, 6.003437042e-01F, 5.227794647e-01F, -1.447305977e-01F, + 2.131558210e-01F, -4.446737468e-01F, -6.133956313e-01F, -3.716578782e-01F, -5.564347506e-01F, -9.132553935e-01F, + 1.226928830e-01F, 6.866057515e-01F, -5.394582152e-01F, 5.991103649e-01F, -9.093226194e-01F, -2.969234623e-02F, + -3.395211101e-01F, 3.018670380e-01F, -6.241508722e-01F, 2.102241516e-01F, -3.940561712e-01F, -8.316771984e-01F, + -3.593006730e-01F, 8.235262632e-01F, 3.387117088e-01F, 5.070742369e-01F, 3.306948841e-01F, -6.778090596e-01F, + 8.698657155e-02F, -7.641403675e-01F, -6.381416321e-01F, -9.758214355e-01F, 4.606479108e-01F, -9.739402533e-01F, + -6.249591112e-01F, 8.078826666e-01F, -1.751327962e-01F, -2.355228513e-01F, -2.670124769e-01F, 6.994859576e-01F, + 5.939193964e-01F, 1.188727096e-01F, 1.754134595e-01F, -2.508932538e-02F, -6.112408042e-01F, 5.425038338e-01F, + 7.171223760e-01F, 5.623063445e-01F, 4.301084578e-01F, -8.077583313e-01F, -2.245240211e-01F, 8.576545715e-01F, + -5.817677975e-01F, -3.480388820e-01F, 8.987190127e-01F, -1.853726655e-01F, -3.489876390e-01F, -7.686520219e-01F, + 7.305254936e-01F, 1.673773676e-01F, 7.576957345e-01F, 6.778170466e-01F, 1.144372746e-01F, -9.591407180e-01F, + 5.278024077e-01F, 6.323644519e-01F, 6.189747453e-01F, -9.939014912e-01F, -9.501982331e-01F, -1.278541833e-01F, + -2.331618220e-01F, -6.330018491e-02F, 2.675364912e-01F, 2.261934876e-01F, -4.902106225e-01F, 9.860546589e-01F, + 5.041165352e-01F, 9.882580638e-01F, -4.051167518e-02F, -8.006018400e-01F, 1.044500098e-01F, -4.755032361e-01F, + 3.955779970e-02F, 2.182990015e-01F, -7.242943645e-01F, 2.573639154e-01F, -9.444453120e-01F, 5.144479275e-01F, + 9.372256398e-01F, 7.933980227e-02F, -3.999277055e-01F, 7.717624307e-01F, 6.289920211e-01F, 1.709295660e-01F, + -6.227331161e-01F, -1.787850112e-01F, -9.333119541e-02F, 1.298717111e-01F, -6.946639419e-01F, -9.477793574e-01F, + -5.236511230e-01F, -3.267139494e-01F, 9.232382774e-01F, 5.097319484e-01F, 8.746562600e-01F, 5.932508782e-02F, + -6.683790684e-01F, -6.694452763e-01F, -9.624243975e-01F, 8.238273859e-01F, -3.077043593e-01F, 3.412970603e-01F, + 8.242762089e-01F, 1.651873440e-02F, 4.409243464e-01F, -7.689071298e-01F, 8.937194347e-01F, -9.473516345e-01F, + -4.413461089e-01F, 9.116571546e-01F, -9.260423779e-01F, -2.616499960e-01F, -4.797591269e-01F, -2.020529807e-01F, + 5.722568557e-02F, -8.486232758e-01F, 4.880639315e-01F, 8.922492266e-01F, 9.765795469e-01F, 6.494811773e-01F, + 4.307107255e-02F, -2.869206965e-01F, 1.066272631e-01F, -2.576802671e-01F, -8.376007080e-01F, 7.829882205e-02F, + 2.718816884e-02F, 1.828891784e-01F, 7.198506594e-01F, 7.935379744e-01F, 5.592875481e-01F, -1.632353812e-01F, + -2.864281535e-01F, 5.880760550e-01F, -7.272679210e-01F, -6.952453852e-01F, -7.284526825e-01F, 8.054094911e-01F, + 5.625089258e-02F, 4.406548738e-01F, 2.395883501e-01F, 5.994645506e-02F, -6.522243619e-01F, 6.684316993e-01F, + -7.233359814e-01F, 8.156073093e-02F, -6.930723786e-01F, 6.295647025e-01F, 4.749468341e-02F, -4.644614458e-01F, + 5.697522759e-01F, -6.561774611e-01F, -1.275090873e-01F, -7.152999640e-01F, -2.653823197e-01F, 6.592721939e-01F, + 1.462266892e-01F, -6.122295260e-01F, 9.289586544e-01F, 2.991696596e-01F, -6.077567339e-01F, 2.171438932e-01F, + -5.522794724e-01F, -7.579119802e-01F, 5.324259400e-01F, 9.225456715e-01F, 4.295551777e-01F, 9.308723211e-01F, + 1.470011920e-01F, -7.148450017e-01F, 4.338623285e-01F, 8.049651980e-01F, 8.366172314e-01F, -6.664015055e-01F, + -9.164208174e-01F, -1.640520990e-01F, -4.391734004e-01F, -8.923021555e-01F, 4.976378381e-01F, -5.971206427e-01F, + 1.908174753e-01F, 4.144880772e-01F, -9.298356622e-02F, 7.048145533e-01F, 5.145759583e-01F, -8.146061301e-01F, + -1.125259846e-01F, -4.391968250e-01F, 3.910413384e-01F, 4.858902991e-01F, -4.805140197e-01F, -7.969020009e-01F, + 3.424195945e-01F, 8.151190877e-01F, 3.222916126e-01F, 1.108408421e-01F, -8.525507450e-01F, 7.554149628e-01F, + -4.316636622e-01F, 1.251887716e-02F, -1.823472828e-01F, -3.267242312e-01F, 4.135667682e-01F, 5.958404541e-01F, + 2.195578367e-01F, -2.479904145e-01F, 1.022312418e-01F, 1.031916738e-01F, 9.274073839e-01F, -7.840898633e-01F, + -7.891068459e-01F, 1.291323304e-01F, 6.636643410e-01F, 1.860225946e-01F, -1.233503744e-01F, 7.854089737e-01F, + -1.360570192e-01F, -3.354815394e-02F, -2.252988666e-01F, -1.094722748e-01F, 7.346257567e-01F, -8.540698886e-01F, + -5.147614479e-01F, 5.866805464e-02F, -5.441957712e-01F, 7.735843062e-01F, -3.400287926e-01F, 5.628150702e-01F, + 1.468484849e-01F, -1.261093616e-01F, -7.602617741e-01F, -4.460678697e-01F, 7.192789912e-01F, -5.948815346e-01F, + 1.014408693e-01F, -9.664263129e-01F, -6.524010301e-01F, -8.648323417e-01F, -7.131146789e-01F, 6.407492161e-01F, + 5.660653710e-01F, 1.353777498e-01F, 1.454768423e-02F, 8.327323794e-01F, -5.350864530e-01F, 5.463505983e-01F, + -3.276975751e-01F, -7.989058495e-01F, 3.994535208e-01F, 9.485973716e-01F, -4.925802946e-01F, 2.023456618e-02F, + 2.907878458e-01F, -8.687805533e-01F, 4.332005680e-01F, -9.254451990e-01F, 4.134224355e-01F, 5.689552426e-02F, + 9.683101177e-01F, 2.425266355e-01F, -3.386843204e-01F, 8.864454031e-01F, -6.909952760e-01F, 3.582284749e-01F, + 1.982057393e-01F, -3.081912696e-01F, -8.728502989e-01F, -5.902858377e-01F, 7.596420646e-01F, 7.231259942e-01F, + -3.649899662e-01F, 4.545170665e-01F, 3.602166846e-02F, 5.374180079e-01F, 2.732529640e-01F, -9.157242775e-01F, + 4.406130314e-01F, -8.831139207e-01F, -3.840163052e-01F, -3.302130103e-01F, -4.156922996e-01F, 1.157017574e-01F, + 1.064227596e-01F, 5.899152532e-02F, 4.948867857e-02F, -8.579345942e-01F, 2.513473928e-01F, 8.164334893e-01F, + -3.212259710e-01F, -1.472947747e-01F, -5.532090664e-01F, -9.350771904e-01F, 7.737103701e-01F, -3.234435916e-01F, + -1.333168596e-01F, 9.110297561e-01F, 9.661647677e-01F, 2.994652987e-01F, -8.761575222e-01F, 6.873393655e-01F, + 6.562761664e-01F, 7.148913741e-01F, -8.177455515e-02F, 5.311955214e-01F, 9.587502480e-01F, -6.235996485e-01F, + -9.359635115e-01F, -6.717967987e-01F, -4.981084466e-01F, -2.973443456e-02F, -8.300547600e-01F, 9.911032319e-01F, + 5.836960077e-01F, 7.691587210e-01F, 3.753771484e-01F, -5.201098323e-01F, -8.316935301e-01F, 7.885102034e-01F, + 6.626006216e-02F, 1.057962030e-01F, 4.404658079e-01F, -2.541512251e-01F, 3.988902271e-01F, 4.925686121e-01F, + -9.223435521e-01F, -7.531912327e-01F, -4.998178482e-01F, -4.545090199e-01F, 4.598482549e-01F, 9.288663864e-01F, + 6.312217712e-01F, 1.560077071e-01F, 2.884795368e-01F, 4.289398789e-01F, -9.899928570e-01F, 4.129851162e-01F, + -8.607054949e-01F, -5.120114684e-01F, -7.273181081e-01F, -4.578070249e-03F, -5.722162724e-01F, 5.447641015e-01F, + 7.777114511e-01F, 1.512663215e-01F, 1.446926892e-01F, 7.889790535e-01F, -5.180943012e-01F, 7.770303488e-01F, + -3.552502990e-01F, 1.006535366e-01F, 6.901952028e-01F, 2.237192392e-01F, 9.130736589e-01F, 8.307381719e-02F, + 7.194549590e-02F, -3.249313235e-01F, -7.724239230e-01F, -8.466135859e-01F, -7.851450443e-01F, -8.297018409e-01F, + -7.987021208e-01F, -2.295123041e-01F, -6.011090279e-01F, -3.860711455e-01F, -4.816648364e-01F, 3.394713402e-01F, + 7.849691808e-02F, -3.704662323e-01F, -2.863151431e-01F, 5.426014662e-01F, -2.607808113e-01F, -9.340898991e-01F, + 1.746138334e-01F, 7.261546254e-01F, 7.530252934e-01F, 9.672193527e-01F, 5.399300531e-02F, -9.835867286e-01F, + 1.713334620e-01F, 5.585514307e-01F, 4.229465723e-01F, -1.844718158e-01F, -7.272046804e-01F, 2.345861942e-01F, + -9.642864466e-01F, 2.350912690e-01F, 3.018774688e-01F, 2.452661842e-01F, 2.677859962e-01F, -7.176558375e-01F, + -3.700140491e-02F, 4.082526565e-01F, 5.311907530e-01F, 8.633541465e-01F, -7.776238322e-01F, 7.896010876e-01F, + -1.654361933e-01F, 6.855440140e-01F, 7.084344029e-01F, -6.274949908e-01F, 8.500307202e-01F, 9.919223189e-01F, + -8.164654374e-01F, -2.324767858e-01F, 4.356827438e-01F, 8.612964749e-01F, -1.428242773e-01F, -2.126990110e-01F, + -8.123673797e-01F, -3.175305426e-01F, 7.496899366e-01F, -9.905060530e-01F, 7.549669743e-01F, -3.424934745e-01F, + 6.578056216e-01F, -3.599320054e-01F, -3.109783530e-01F, 2.221128345e-01F, 9.978434443e-01F, -5.132814050e-01F, + -7.869974971e-01F, -4.619612098e-01F, 3.523567617e-01F, -5.109924078e-01F, -2.572123706e-02F, -1.930657178e-01F, + -5.090795159e-01F, 1.792800874e-01F, 9.927337170e-01F, 3.684777021e-01F, 1.906812042e-01F, -6.340540051e-01F, + 3.050946444e-02F, -7.284081727e-02F, 8.909425139e-01F, -2.287642807e-01F, 3.547719419e-01F, 3.432514966e-01F, + 8.692468405e-01F, 6.008607745e-01F, 9.505707026e-01F, -1.860329658e-01F, 7.030812502e-01F, 6.279779226e-02F, + -5.946478248e-02F, 8.791387081e-03F, 8.982216716e-01F, 6.035797596e-01F, 1.583456546e-01F, 5.362397432e-01F, + -5.870531201e-01F, -5.755771995e-01F, 8.868579268e-01F, -5.026375055e-01F, 1.781354845e-01F, -5.526794195e-01F, + 7.392305732e-01F, 9.686752409e-02F, 9.882246852e-01F, 3.754557371e-01F, -1.760065705e-01F, -5.137321353e-01F, + 5.854247212e-01F, 6.673564315e-01F, 5.249041319e-01F, 6.184439063e-01F, -6.820315123e-01F, 4.072463214e-01F, + -3.987706304e-01F, -2.754939720e-02F, -3.807398975e-01F, -5.891314745e-01F, -4.751186352e-03F, -3.102737963e-01F, + -7.325565219e-01F, -4.917984828e-02F, -6.893200427e-02F, 2.772592604e-01F, -6.205356717e-01F, -3.544924259e-01F, + 1.587335616e-01F, 4.429485500e-01F, -9.053909034e-02F, -6.171016693e-01F, 3.947969079e-01F, 6.516628712e-02F, + -7.982098460e-01F, -9.484176040e-01F, -9.912240505e-02F, 6.634531617e-01F, 8.741019964e-01F, -7.181222439e-01F, + 6.944166422e-01F, 1.127546094e-02F, -3.519659340e-01F, -1.332900822e-01F, 7.640550286e-02F, 9.184844494e-01F, + -2.297020406e-01F, -2.632746696e-01F, 4.549829066e-01F, -2.196090072e-01F, -7.417198420e-01F, -6.891037822e-01F, + -7.334368825e-01F, -4.800065458e-01F, 3.255885541e-01F, 8.650339842e-01F, -5.315734744e-01F, 1.019218713e-01F, + 1.842586547e-01F, -1.951403916e-01F, -7.280259579e-02F, -4.814673364e-01F, 6.643168330e-01F, 2.728619576e-01F, + 7.033090591e-01F, -6.617624760e-01F, 5.276672840e-01F, -6.621732712e-01F, -6.263178587e-01F, -8.425797820e-01F, + 6.546846628e-01F, -4.441189170e-01F, 3.357607424e-01F, 3.982117176e-01F, 2.104454041e-01F, 5.176950693e-01F, + 8.337970376e-01F, -9.136780500e-01F, -5.298601985e-01F, -4.662178457e-01F, 1.351615041e-02F, -7.117313147e-02F, + -5.614359975e-01F, 7.841591239e-01F, -1.973339915e-01F, -4.072257280e-01F, -2.651928663e-01F, -9.367942214e-01F, + 7.090807557e-01F, 9.377478957e-01F, -7.920714617e-01F, 9.234046340e-01F, -2.682652473e-01F, -2.611247897e-01F, + -2.768379748e-01F, 6.349185705e-01F, -2.312759012e-01F, -2.014108896e-01F, -8.701069951e-01F, 8.323693275e-01F, + -6.293854713e-01F, -4.059478939e-01F, -6.875187755e-01F, 8.669758439e-01F, -3.101876974e-01F, 6.503431201e-01F, + 3.609050214e-01F, -9.671498537e-01F, 7.539758682e-01F, 6.032301188e-01F, -8.706395626e-01F, -5.914085507e-01F, + 3.399629295e-01F, -5.691475272e-01F, 1.775392145e-01F, 4.995456338e-01F, -7.535597682e-01F, -6.310535222e-02F, + 1.495456398e-01F, -1.493636072e-01F, 3.811804056e-01F, 7.669955492e-02F, -8.949294090e-01F, 2.516035736e-01F, + 3.741195798e-02F, -2.492928952e-01F, -3.457382917e-01F, 5.990988761e-02F, 1.947297156e-01F, -1.764506400e-01F, + -4.005028168e-04F, -9.132480621e-01F, -3.910650909e-01F, 3.389288187e-01F, 6.357645243e-02F, -5.238424540e-01F, + -4.010646641e-01F, -9.669828415e-01F, -1.174249407e-02F, 9.407782555e-01F, -3.376164138e-01F, -8.650923967e-01F, + 4.778270721e-01F, -3.348628879e-01F, -9.726695418e-01F, 1.293090433e-01F, 5.199207067e-01F, 9.398753047e-01F, + -5.942393467e-02F, -1.668225080e-01F, 3.662385419e-02F, 3.662403300e-02F, 4.687784985e-02F, 4.885003716e-02F, + 4.453744367e-02F, 4.681917652e-02F, 4.518040270e-02F, 4.853175208e-02F, 4.602690041e-02F, 4.633489996e-02F, + 4.778334871e-02F, 4.718134180e-02F, 4.995728657e-02F, 4.689235613e-02F, 4.756152630e-02F, 4.902380332e-02F, + 4.861936346e-02F, 4.795955122e-02F, 4.707497731e-02F, 4.849456996e-02F, 4.850632325e-02F, 5.001683533e-02F, + 4.514738545e-02F, 4.879918694e-02F, 4.508081824e-02F, 4.732166603e-02F, 4.413456842e-02F, 4.546541721e-02F, + 4.875557870e-02F, 4.431953654e-02F, 4.971252382e-02F, 4.564618319e-02F, 4.900963977e-02F, 4.750919715e-02F, + 4.723661393e-02F, 4.610923305e-02F, 4.612586275e-02F, 4.547592625e-02F, 4.741988331e-02F, 4.429096356e-02F, + 4.881316796e-02F, 4.462187365e-02F, 4.936761409e-02F, 4.457025602e-02F, 4.880937561e-02F, 4.670264944e-02F, + 4.922888801e-02F, 4.599030688e-02F, 5.026754364e-02F, 4.646550864e-02F, 4.747374356e-02F, 4.642704502e-02F, + 4.566831514e-02F, 4.823003337e-02F, 4.558603838e-02F, 4.875458032e-02F, 4.748097807e-02F, 4.769024253e-02F, + 4.727480561e-02F, 4.803414643e-02F, 4.978906736e-02F, 4.697655514e-02F, 5.116062611e-02F, 4.713366926e-02F, + 4.923530668e-02F, 4.542827979e-02F, 4.930356517e-02F, 4.537707567e-02F, 4.811764136e-02F, 4.380609095e-02F, + 4.739776254e-02F, 4.418956861e-02F, 4.951910302e-02F, 4.347110167e-02F, 5.048029870e-02F, 4.369254410e-02F, + 4.810491204e-02F, 4.496378079e-02F, 4.672422260e-02F, 4.628492519e-02F, 4.457291216e-02F, 4.619534314e-02F, + 4.450487345e-02F, 4.725884274e-02F, 4.425881803e-02F, 4.740569368e-02F, 4.677727818e-02F, 4.684635624e-02F, + 4.732878879e-02F, 4.518505931e-02F, 4.792106152e-02F, 4.455674440e-02F, 4.719789699e-02F, 4.655162245e-02F, + 4.765786603e-02F, 4.876262695e-02F, 4.773863405e-02F, 4.884118959e-02F, 4.824094847e-02F, 4.596939683e-02F, + 4.599944130e-02F, 4.603312537e-02F, 4.495792463e-02F, 4.627957195e-02F, 4.735408351e-02F, 4.591032863e-02F, + 4.639482126e-02F, 4.688164219e-02F, 4.834965616e-02F, 4.738714546e-02F, 4.919432104e-02F, 4.971674085e-02F, + 4.698410630e-02F, 4.889558628e-02F, 5.006815121e-02F, 4.848133400e-02F, 4.795642570e-02F, 4.991727322e-02F, + 4.626080766e-02F, 4.800194502e-02F, 4.489579052e-02F, 5.098567158e-02F, 4.482951015e-02F, 5.076332763e-02F, + 4.414754733e-02F, 5.100757256e-02F, 4.524902999e-02F, 4.892905802e-02F, 4.748999327e-02F, 4.792959988e-02F, + 4.948700964e-02F, 4.632003233e-02F, 4.832968116e-02F, 4.729781300e-02F, 4.670663923e-02F, 4.733430967e-02F, + 4.633055627e-02F, 4.706028104e-02F, 4.475167021e-02F, 4.786143079e-02F, 4.653517157e-02F, 4.698107019e-02F, + 4.650199413e-02F, 4.701145366e-02F, 4.845888913e-02F, 4.770044610e-02F, 4.966596514e-02F, 4.687580839e-02F, + 4.832599312e-02F, 4.652721807e-02F, 4.730466381e-02F, 4.452078044e-02F, 4.499016330e-02F, 4.571783543e-02F, + 4.364651814e-02F, 4.438735545e-02F, 4.403913766e-02F, 4.638767242e-02F, 4.551691189e-02F, 4.457111657e-02F, + 4.729895666e-02F, 4.482982680e-02F, 4.771841317e-02F, 4.465943947e-02F, 4.632830620e-02F, 4.516838863e-02F, + 4.789005965e-02F, 4.562589526e-02F, 4.546770826e-02F, 4.769626260e-02F, 4.795629159e-02F, 4.849002138e-02F, + 4.703563824e-02F, 4.921549559e-02F, 4.838616028e-02F, 4.937878624e-02F, 4.455911741e-02F, 4.863564670e-02F, + 4.336370155e-02F, 4.796361923e-02F, 4.501534626e-02F, 4.593539611e-02F, 4.583633691e-02F, 4.474273324e-02F, + 4.835430905e-02F, 4.440907389e-02F, 4.705331102e-02F, 4.472945258e-02F, 4.627822340e-02F, 4.852508754e-02F, + 4.500316083e-02F, 4.832304642e-02F, 4.346463084e-02F, 5.018093064e-02F, 4.490870610e-02F, 4.748028517e-02F, + 4.476359487e-02F, 4.830657691e-02F, 4.767323285e-02F, 4.604608938e-02F, 4.736597836e-02F, 4.376714677e-02F, + 4.568918422e-02F, 4.485751316e-02F, 4.694772512e-02F, 4.459319636e-02F, 4.793142527e-02F, 4.738263041e-02F, + 4.754722118e-02F, 4.610542953e-02F, 4.512079433e-02F, 4.800456017e-02F, 4.465983063e-02F, 4.902250692e-02F, + 4.620829970e-02F, 4.804614559e-02F, 4.527126253e-02F, 4.844563827e-02F, 4.566142336e-02F, 4.730535299e-02F, + 4.819447920e-02F, 4.710619152e-02F, 4.860326275e-02F, 4.824682698e-02F, 4.751109704e-02F, 4.867331311e-02F, + 4.826589674e-02F, 4.972883686e-02F, 4.596699774e-02F, 5.182658508e-02F, 4.691664875e-02F, 5.252035707e-02F, + 4.585247859e-02F, 4.828320816e-02F, 4.765882343e-02F, 4.896199703e-02F, 4.520273954e-02F, 4.532984644e-02F, + 4.731884971e-02F, 4.609883949e-02F, 4.812052473e-02F, 4.569499567e-02F, 4.645305499e-02F, 4.639897123e-02F, + 4.597930238e-02F, 4.935996607e-02F, 4.417283460e-02F, 4.804528132e-02F, 2.264313586e-02F, 2.491544373e-02F, + 4.545349628e-03F, 5.170093384e-03F, -1.645828225e-02F, -1.456867252e-02F, -1.388705987e-02F, -1.455928851e-02F, + -1.248346176e-02F, -1.354450081e-02F, -1.227064710e-02F, -1.415740326e-02F, -1.289715432e-02F, -1.459554769e-02F, + -1.271217782e-02F, -1.264899597e-02F, -1.598917879e-02F, -1.291445829e-02F, -1.479174942e-02F, -1.474231761e-02F, + -1.302235387e-02F, -1.477838028e-02F, -1.416503731e-02F, -1.229562610e-02F, -1.544151921e-02F, -1.521816012e-02F, + -1.437568758e-02F, -1.657102630e-02F, -1.202288643e-02F, -1.396455802e-02F, -1.088759676e-02F, -1.352775656e-02F, + -1.277489495e-02F, -1.238798816e-02F, -1.686835475e-02F, -1.163661294e-02F, -1.530631352e-02F, -1.507147960e-02F, + -1.362074632e-02F, -1.505945157e-02F, -1.274185814e-02F, -1.267650910e-02F, -1.274119876e-02F, -1.288117189e-02F, + -1.438162196e-02F, -1.307788212e-02F, -1.514758822e-02F, -1.194546092e-02F, -1.398772281e-02F, -1.348132733e-02F, + -1.332476456e-02F, -1.450276840e-02F, -1.648062095e-02F, -1.277603023e-02F, -1.610177010e-02F, -1.258872636e-02F, + -1.190621592e-02F, -1.377526019e-02F, -1.176208537e-02F, -1.549196802e-02F, -1.453159936e-02F, -1.390018500e-02F, + -1.323022414e-02F, -1.372484863e-02F, -1.242956240e-02F, -1.446630433e-02F, -1.710139960e-02F, -1.426663902e-02F, + -1.548081450e-02F, -1.363682561e-02F, -1.314089727e-02F, -1.350616198e-02F, -1.528020110e-02F, -1.313577313e-02F, + -1.287372969e-02F, -1.260575000e-02F, -1.265239809e-02F, -1.304463483e-02F, -1.734578237e-02F, -1.182846073e-02F, + -1.575249061e-02F, -1.211755723e-02F, -1.309403684e-02F, -1.408358477e-02F, -1.351458021e-02F, -1.336752903e-02F, + -1.290480513e-02F, -1.296869293e-02F, -1.149195060e-02F, -1.478040591e-02F, -1.282835193e-02F, -1.487130579e-02F, + -1.474832091e-02F, -1.329748053e-02F, -1.410955936e-02F, -1.140462887e-02F, -1.352731418e-02F, -1.206369232e-02F, + -1.364799868e-02F, -1.495178044e-02F, -1.399807259e-02F, -1.654506102e-02F, -1.522255037e-02F, -1.340358146e-02F, + -1.468827482e-02F, -1.179849263e-02F, -1.084278710e-02F, -1.453553140e-02F, -1.359043270e-02F, -1.339535881e-02F, + -1.460529491e-02F, -1.238564681e-02F, -1.205541566e-02F, -1.316138078e-02F, -1.606257074e-02F, -1.484723762e-02F, + -1.344720367e-02F, -1.548047457e-02F, -1.319094840e-02F, -1.269913279e-02F, -1.746843569e-02F, -1.505797356e-02F, + -1.375126559e-02F, -1.408665814e-02F, -1.185351331e-02F, -1.291416585e-02F, -1.391135529e-02F, -1.631016284e-02F, + -1.284830738e-02F, -1.610115170e-02F, -1.094262302e-02F, -1.466184203e-02F, -1.300146710e-02F, -1.438414771e-02F, + -1.596803777e-02F, -1.335313544e-02F, -1.557297166e-02F, -1.298488583e-02F, -1.306784432e-02F, -1.466904487e-02F, + -1.401464920e-02F, -1.344035193e-02F, -1.292747352e-02F, -1.379082631e-02F, -1.230769511e-02F, -1.457981858e-02F, + -1.356506534e-02F, -1.287322491e-02F, -1.310459897e-02F, -1.410733815e-02F, -1.549829170e-02F, -1.478851680e-02F, + -1.559129450e-02F, -1.413584501e-02F, -1.421811990e-02F, -1.256764680e-02F, -1.408076845e-02F, -1.316994708e-02F, + -1.225127093e-02F, -1.308279857e-02F, -1.148137171e-02F, -1.336088590e-02F, -1.259069238e-02F, -1.421272289e-02F, + -1.393034216e-02F, -1.227475144e-02F, -1.506123133e-02F, -1.278206799e-02F, -1.284241397e-02F, -1.321890578e-02F, + -1.416044775e-02F, -1.215212047e-02F, -1.369668543e-02F, -1.310317777e-02F, -1.256916858e-02F, -1.468863059e-02F, + -1.444405224e-02F, -1.416948810e-02F, -1.576879807e-02F, -1.474528108e-02F, -1.473280787e-02F, -1.496647298e-02F, + -1.014917064e-02F, -1.486247592e-02F, -1.218699571e-02F, -1.427634340e-02F, -1.356751006e-02F, -1.275892090e-02F, + -1.402703952e-02F, -1.258304249e-02F, -1.523321308e-02F, -1.112882514e-02F, -1.373737678e-02F, -1.356875058e-02F, + -1.376197860e-02F, -1.504153572e-02F, -1.212801971e-02F, -1.497134566e-02F, -1.224349160e-02F, -1.453581359e-02F, + -1.252291072e-02F, -1.406383794e-02F, -1.327814069e-02F, -1.578217931e-02F, -1.600461453e-02F, -1.187574398e-02F, + -1.268317271e-02F, -1.172389090e-02F, -1.158452965e-02F, -1.299243234e-02F, -1.537544839e-02F, -1.374755427e-02F, + -1.592261344e-02F, -1.359530445e-02F, -1.294843201e-02F, -1.226894837e-02F, -1.124817412e-02F, -1.550867222e-02F, + -1.424040459e-02F, -1.473434363e-02F, -1.424341183e-02F, -1.351073384e-02F, -1.070742402e-02F, -1.463612355e-02F, + -1.350418199e-02F, -1.297733840e-02F, -1.658052765e-02F, -1.327451039e-02F, -1.313442923e-02F, -1.442558784e-02F, + -1.408980694e-02F, -1.317999605e-02F, -1.464211103e-02F, -1.444240566e-02F, -1.284504309e-02F, -1.870121993e-02F, + -1.299833786e-02F, -1.466289721e-02F, -1.483808085e-02F, -1.344759483e-02F, -1.333437860e-02F, -1.507815812e-02F, + -1.140846964e-02F, -1.222263742e-02F, -1.613045484e-02F, -1.308847591e-02F, -1.493913401e-02F, -1.225338317e-02F, + -1.273445040e-02F, -1.452865265e-02F, -2.552551217e-02F, -2.864836343e-02F, -1.575095020e-02F, -1.472835056e-02F, + -3.389466088e-03F, -3.644593526e-03F, -4.108585883e-03F, -5.522220861e-03F, -3.656683024e-03F, -3.032432869e-03F, + -4.759924486e-03F, -6.308203563e-03F, -4.375160206e-03F, -2.991776681e-03F, -4.615116864e-03F, -4.822616931e-03F, + -6.523894146e-03F, -4.294200335e-03F, -2.203526208e-03F, -5.911352113e-03F, -5.849590991e-03F, -3.423183458e-03F, + -3.874839516e-03F, -4.415208939e-03F, -5.904385354e-03F, -6.531448103e-03F, -2.376184333e-03F, -3.692538943e-03F, + -4.778622650e-03F, -3.907505888e-03F, -3.142375965e-03F, -4.478810821e-03F, -6.894482300e-03F, -3.429716453e-03F, + -4.914268386e-03F, -4.770792089e-03F, -3.671604209e-03F, -6.033501588e-03F, -4.372762516e-03F, -2.962797415e-03F, + -3.973723855e-03F, -4.324779846e-03F, -4.990200978e-03F, -4.230919760e-03F, -5.177948624e-03F, -4.366819281e-03F, + -4.674412776e-03F, -3.575953189e-03F, -4.047780298e-03F, -5.975428503e-03F, -4.915534053e-03F, -3.511017654e-03F, + -6.257419474e-03F, -4.355332348e-03F, -2.826310927e-03F, -4.389493726e-03F, -3.570189001e-03F, -5.367741454e-03F, + -4.942586645e-03F, -4.876655992e-03F, -5.547282286e-03F, -3.456768347e-03F, -2.948952140e-03F, -5.457814783e-03F, + -5.899556447e-03F, -3.994774539e-03F, -6.323379930e-03F, -4.818151239e-03F, -2.462135861e-03F, -3.586775158e-03F, + -5.461481865e-03F, -4.894247744e-03F, -4.894162528e-03F, -3.362642368e-03F, -2.983117476e-03F, -4.678139929e-03F, + -6.111789495e-03F, -3.895855742e-03F, -6.028619129e-03F, -3.785449546e-03F, -2.528901910e-03F, -4.861702211e-03F, + -4.784125835e-03F, -5.002152640e-03F, -3.990449477e-03F, -3.521559993e-03F, -4.150236957e-03F, -5.152928177e-03F, + -3.568262327e-03F, -4.871133249e-03F, -5.896707997e-03F, -4.152604844e-03F, -4.267777316e-03F, -3.647111822e-03F, + -4.502404947e-03F, -3.816569224e-03F, -4.199786112e-03F, -5.303472746e-03F, -4.895940423e-03F, -5.521978252e-03F, + -4.421893973e-03F, -4.694600124e-03F, -5.357935093e-03F, -2.531860722e-03F, -3.266972024e-03F, -5.188789684e-03F, + -3.239432117e-03F, -5.103500094e-03F, -6.990081165e-03F, -3.170220414e-03F, -2.701056655e-03F, -5.084685516e-03F, + -4.981288686e-03F, -4.410722759e-03F, -6.436577532e-03F, -5.862545222e-03F, -1.467933296e-03F, -3.930202685e-03F, + -7.502869703e-03F, -3.823259147e-03F, -4.165336955e-03F, -6.743870210e-03F, -2.752503380e-03F, -2.206232632e-03F, + -4.648123402e-03F, -6.568523590e-03F, -4.973805044e-03F, -5.035278387e-03F, -3.007908352e-03F, -4.599649459e-03F, + -4.487817176e-03F, -3.992311656e-03F, -5.589344073e-03F, -4.922758788e-03F, -5.440881476e-03F, -3.454571124e-03F, + -3.678722307e-03F, -5.233061500e-03F, -3.862243146e-03F, -4.725208972e-03F, -5.364765879e-03F, -3.658352885e-03F, + -2.764889970e-03F, -5.515811965e-03F, -5.569458473e-03F, -3.968864679e-03F, -4.073787015e-03F, -4.066064022e-03F, + -4.857526161e-03F, -5.576419178e-03F, -5.820173770e-03F, -3.843161976e-03F, -3.594290931e-03F, -4.614342935e-03F, + -4.607120994e-03F, -3.133029677e-03F, -3.937227186e-03F, -5.715849809e-03F, -3.433025675e-03F, -2.896928694e-03F, + -4.472261760e-03F, -5.876819603e-03F, -4.775807727e-03F, -3.258774057e-03F, -4.943574313e-03F, -4.177030642e-03F, + -4.766229540e-03F, -4.576359876e-03F, -2.997172298e-03F, -4.285180476e-03F, -6.577651948e-03F, -3.867263440e-03F, + -2.153424546e-03F, -5.607902538e-03F, -6.097256672e-03F, -4.557244480e-03F, -4.019774031e-03F, -4.542340059e-03F, + -5.780910142e-03F, -5.132083781e-03F, -2.392323455e-03F, -4.310763907e-03F, -3.343783319e-03F, -4.739228170e-03F, + -6.129645742e-03F, -3.766057780e-03F, -3.531788941e-03F, -3.932133783e-03F, -5.767482333e-03F, -4.401675891e-03F, + -3.971673083e-03F, -3.414362203e-03F, -4.036809318e-03F, -6.893236656e-03F, -4.515142180e-03F, -3.357436508e-03F, + -2.998490119e-03F, -5.976763554e-03F, -5.445851944e-03F, -3.045056015e-03F, -3.382652299e-03F, -5.713378545e-03F, + -5.924761295e-03F, -4.094388336e-03F, -4.651891068e-03F, -2.278218744e-03F, -2.429914894e-03F, -5.927466787e-03F, + -5.694553256e-03F, -3.430251265e-03F, -5.486862734e-03F, -5.830941722e-03F, -3.971094731e-03F, -3.070107661e-03F, + -3.091536462e-03F, -5.213227123e-03F, -4.412814975e-03F, -5.821610801e-03F, -5.891218316e-03F, -3.110183170e-03F, + -2.945700428e-03F, -5.306225270e-03F, -3.798580030e-03F, -4.376763478e-03F, -6.680703256e-03F, -3.753664903e-03F, + -4.392265342e-03F, -5.476422142e-03F, -2.916216617e-03F, -4.486646969e-03F, -6.533211097e-03F, -4.332934972e-03F, + -2.788477344e-03F, -6.094780285e-03F, -5.025964696e-03F, -6.186612416e-03F, -3.876298666e-03F, -1.200445695e-03F, + -6.035519764e-03F, -7.056063041e-03F, -2.055085497e-03F, -2.761794720e-03F, -5.670935381e-03F, -4.373896401e-03F, + -6.006246898e-03F, -4.769777879e-03F, -2.456796356e-03F, -3.701595822e-03F, -4.941877909e-03F, -6.709074136e-03F, + -9.959583171e-03F, -9.779898450e-03F, -3.552291170e-02F, -3.448108211e-02F, -4.179338738e-02F, -4.075188935e-02F, + -4.311992228e-02F, -4.053553566e-02F, -4.189125448e-02F, -4.198080301e-02F, -4.227727279e-02F, -4.170370102e-02F, + -4.275410622e-02F, -3.850719333e-02F, -4.049266502e-02F, -4.114362597e-02F, -4.081190005e-02F, -4.012641311e-02F, + -3.958214447e-02F, -4.230379686e-02F, -4.157813638e-02F, -3.822329268e-02F, -3.758348525e-02F, -3.848795593e-02F, + -3.926521912e-02F, -4.083346948e-02F, -3.893906623e-02F, -3.947950900e-02F, -4.081470519e-02F, -4.286433011e-02F, + -4.113139212e-02F, -4.015873745e-02F, -3.890113905e-02F, -4.018111527e-02F, -3.720874339e-02F, -4.017849639e-02F, + -3.848121688e-02F, -4.050373286e-02F, -3.971926123e-02F, -3.904236108e-02F, -3.927826881e-02F, -4.018417746e-02F, + -4.081739113e-02F, -3.965885192e-02F, -3.884578869e-02F, -3.844229877e-02F, -3.939670324e-02F, -4.008243978e-02F, + -3.824447840e-02F, -3.869640455e-02F, -3.991778195e-02F, -4.078571126e-02F, -3.779228404e-02F, -3.766103461e-02F, + -4.149567708e-02F, -3.996520117e-02F, -4.192768410e-02F, -3.920492530e-02F, -4.003635421e-02F, -3.989358619e-02F, + -3.919297457e-02F, -4.097535089e-02F, -3.671735525e-02F, -4.154243693e-02F, -3.843203560e-02F, -4.066652060e-02F, + -3.748481348e-02F, -4.103803635e-02F, -4.196408391e-02F, -4.015349597e-02F, -3.916982189e-02F, -4.088643938e-02F, + -4.139136896e-02F, -4.028487578e-02F, -3.927546367e-02F, -3.859247267e-02F, -3.836596012e-02F, -3.861621767e-02F, + -3.998697177e-02F, -3.975496069e-02F, -3.827590868e-02F, -3.981646895e-02F, -4.026697949e-02F, -4.215669259e-02F, + -4.075463116e-02F, -3.836719319e-02F, -3.789057583e-02F, -3.830642253e-02F, -3.786989674e-02F, -3.901384771e-02F, + -3.650483862e-02F, -4.097945243e-02F, -3.740754351e-02F, -3.959660977e-02F, -3.757359087e-02F, -4.060263559e-02F, + -3.784622997e-02F, -4.068132117e-02F, -3.738501668e-02F, -4.009905830e-02F, -3.682525828e-02F, -4.065426067e-02F, + -3.993209824e-02F, -4.067024961e-02F, -3.855597973e-02F, -3.977454081e-02F, -3.886914253e-02F, -3.901295736e-02F, + -3.847024962e-02F, -4.079516605e-02F, -3.778419271e-02F, -3.945322707e-02F, -3.801064566e-02F, -4.044427350e-02F, + -3.824182600e-02F, -3.998639435e-02F, -3.860353306e-02F, -3.860633075e-02F, -3.940231353e-02F, -3.797705472e-02F, + -4.092688113e-02F, -3.945761546e-02F, -4.060783237e-02F, -4.051684588e-02F, -4.095126688e-02F, -4.003898054e-02F, + -4.270270839e-02F, -4.098515958e-02F, -3.978418186e-02F, -3.947121277e-02F, -4.048271105e-02F, -3.969999403e-02F, + -3.845836222e-02F, -3.853620589e-02F, -4.039310291e-02F, -4.107656330e-02F, -3.776306286e-02F, -3.963959590e-02F, + -3.882326558e-02F, -4.274735600e-02F, -3.864677623e-02F, -4.105998576e-02F, -3.935338557e-02F, -4.134800658e-02F, + -3.848912567e-02F, -4.009377584e-02F, -4.127013683e-02F, -4.133586586e-02F, -4.042897001e-02F, -4.019699618e-02F, + -4.046975449e-02F, -4.154950380e-02F, -4.215107858e-02F, -3.959897533e-02F, -4.246725142e-02F, -3.827446699e-02F, + -4.389594495e-02F, -3.950812668e-02F, -4.213954881e-02F, -3.964637220e-02F, -4.146650806e-02F, -4.129045457e-02F, + -3.928827122e-02F, -3.891038150e-02F, -3.742212057e-02F, -4.042889178e-02F, -3.796172887e-02F, -4.120811820e-02F, + -3.881507739e-02F, -4.420715943e-02F, -3.924944252e-02F, -4.402482510e-02F, -4.021786526e-02F, -4.087376967e-02F, + -4.037363082e-02F, -3.745280579e-02F, -3.955773637e-02F, -3.730351105e-02F, -3.949811310e-02F, -3.745111823e-02F, + -3.832962364e-02F, -4.017693177e-02F, -3.974436969e-02F, -3.891200572e-02F, -4.076643288e-02F, -4.076350480e-02F, + -4.012162983e-02F, -3.922604769e-02F, -3.784095496e-02F, -4.018947482e-02F, -3.986278921e-02F, -3.733494878e-02F, + -4.128092527e-02F, -3.835776076e-02F, -4.207045212e-02F, -3.982285038e-02F, -4.093607515e-02F, -4.253342003e-02F, + -4.193416238e-02F, -3.928987309e-02F, -4.055868089e-02F, -4.138091952e-02F, -4.276366532e-02F, -3.846817836e-02F, + -4.013895243e-02F, -3.947782889e-02F, -3.993869945e-02F, -3.893783689e-02F, -3.742515296e-02F, -4.040902853e-02F, + -3.838992864e-02F, -4.375016689e-02F, -3.955740854e-02F, -4.042628035e-02F, -4.046940431e-02F, -3.936982155e-02F, + -3.923146054e-02F, -3.984534740e-02F, -3.852868453e-02F, -4.162752628e-02F, -4.116830230e-02F, -4.135052115e-02F, + -4.138202220e-02F, -4.008117691e-02F, -3.927227482e-02F, -3.989907354e-02F, -4.018769413e-02F, -3.783304617e-02F, + -4.003755376e-02F, -4.029548168e-02F, -4.012335837e-02F, -3.980512172e-02F, -3.839790076e-02F, -4.004418105e-02F, + -3.772625700e-02F, -4.055468366e-02F, -3.951645270e-02F, -3.982249647e-02F, -3.915338591e-02F, -4.091424868e-02F, + -4.099863395e-02F, -4.371206835e-02F, -3.961148486e-02F, -3.975300491e-02F, -4.051499814e-02F, -4.198045284e-02F, + -3.896185756e-02F, -3.931810334e-02F, -4.135295749e-02F, -3.872678056e-02F, -4.138771072e-02F, -3.844034299e-02F, + 4.856378958e-02F, 4.737574607e-02F, 3.468940780e-02F, 2.808593586e-02F, 2.901319973e-02F, 3.411881998e-02F, + 3.001306206e-02F, 2.961646579e-02F, 3.122843616e-02F, 2.926683612e-02F, 2.934626117e-02F, 2.938678488e-02F, + 2.802130580e-02F, 3.188003972e-02F, 3.158008680e-02F, 2.940390073e-02F, 3.109638952e-02F, 2.823013999e-02F, + 3.096846677e-02F, 2.956516482e-02F, 2.843317762e-02F, 3.038843721e-02F, 3.103631176e-02F, 3.421949223e-02F, + 3.257223219e-02F, 3.230939433e-02F, 3.002645262e-02F, 2.885326184e-02F, 2.787918970e-02F, 3.086604923e-02F, + 2.843526751e-02F, 3.049121797e-02F, 2.869406715e-02F, 2.821602859e-02F, 3.323158622e-02F, 3.121302277e-02F, + 3.287275881e-02F, 3.011440299e-02F, 2.960567176e-02F, 3.012439050e-02F, 2.929631993e-02F, 3.049979918e-02F, + 2.914603986e-02F, 3.004456684e-02F, 2.866076864e-02F, 3.188733757e-02F, 2.907715552e-02F, 3.091243096e-02F, + 3.160311282e-02F, 2.880312502e-02F, 3.267761320e-02F, 3.074774332e-02F, 3.078128956e-02F, 2.748559974e-02F, + 3.285524249e-02F, 2.904981747e-02F, 2.656108700e-02F, 3.300594538e-02F, 2.796862833e-02F, 2.945005707e-02F, + 3.165448457e-02F, 3.066334501e-02F, 3.098600358e-02F, 3.161582351e-02F, 3.152500466e-02F, 2.797432989e-02F, + 3.422458842e-02F, 3.212508559e-02F, 3.005020693e-02F, 2.880875953e-02F, 2.916131169e-02F, 3.059596382e-02F, + 3.168509156e-02F, 2.947976254e-02F, 3.028983250e-02F, 2.840048634e-02F, 3.119828552e-02F, 3.178198263e-02F, + 3.250951320e-02F, 3.247933090e-02F, 3.271807730e-02F, 2.891194262e-02F, 2.914796397e-02F, 2.574688382e-02F, + 3.020615876e-02F, 3.273564950e-02F, 2.635416761e-02F, 3.171055019e-02F, 3.063972853e-02F, 2.899733558e-02F, + 3.362172469e-02F, 3.085256368e-02F, 2.826328576e-02F, 3.053106368e-02F, 3.259597346e-02F, 3.002608009e-02F, + 2.827759273e-02F, 3.078966588e-02F, 3.213193640e-02F, 3.211984783e-02F, 3.100362606e-02F, 2.815835178e-02F, + 2.987645753e-02F, 3.005585819e-02F, 2.971008606e-02F, 3.063306771e-02F, 3.057307005e-02F, 3.100365587e-02F, + 3.248314932e-02F, 2.924780548e-02F, 2.771409228e-02F, 3.193531558e-02F, 3.147969022e-02F, 2.944116853e-02F, + 3.064201586e-02F, 2.950247191e-02F, 3.221300617e-02F, 3.130193055e-02F, 3.131326661e-02F, 3.414944559e-02F, + 3.132642433e-02F, 2.879047021e-02F, 3.130480275e-02F, 3.196615353e-02F, 3.063135780e-02F, 3.163086623e-02F, + 2.690293826e-02F, 2.840257436e-02F, 3.102381341e-02F, 3.097023815e-02F, 3.091897070e-02F, 3.101292998e-02F, + 2.868523449e-02F, 3.231158853e-02F, 3.152766451e-02F, 3.148029745e-02F, 3.131942451e-02F, 2.961269207e-02F, + 2.924078144e-02F, 3.005586565e-02F, 2.916705795e-02F, 2.992991358e-02F, 3.121173382e-02F, 2.916053124e-02F, + 3.302006796e-02F, 3.400335833e-02F, 2.605606988e-02F, 3.007491678e-02F, 3.407820687e-02F, 2.770828269e-02F, + 3.260871023e-02F, 2.763471752e-02F, 3.024221025e-02F, 2.911406010e-02F, 3.263415769e-02F, 3.173096478e-02F, + 2.955593541e-02F, 3.192495555e-02F, 2.992708981e-02F, 2.964195609e-02F, 2.814349160e-02F, 2.928506210e-02F, + 2.907111682e-02F, 3.221130371e-02F, 3.260825947e-02F, 3.379071131e-02F, 3.056136705e-02F, 3.216563165e-02F, + 2.985471301e-02F, 3.016042337e-02F, 3.117274493e-02F, 2.546014823e-02F, 3.232799843e-02F, 2.826363035e-02F, + 2.793205716e-02F, 3.066625074e-02F, 3.077839687e-02F, 3.175228089e-02F, 3.075139038e-02F, 3.259496763e-02F, + 3.104275838e-02F, 2.889619395e-02F, 3.168019652e-02F, 2.952685207e-02F, 2.702717483e-02F, 3.219411522e-02F, + 2.801044844e-02F, 2.628052607e-02F, 3.577482700e-02F, 2.768270113e-02F, 2.948856167e-02F, 3.570593148e-02F, + 3.119128942e-02F, 3.131803870e-02F, 2.889190428e-02F, 3.145707399e-02F, 2.951404639e-02F, 2.804912254e-02F, + 2.855466306e-02F, 3.360943124e-02F, 3.066686168e-02F, 2.849228494e-02F, 2.815064415e-02F, 2.875338867e-02F, + 2.791195735e-02F, 3.147446737e-02F, 2.803936414e-02F, 3.219273314e-02F, 3.477162123e-02F, 3.060479276e-02F, + 3.311095014e-02F, 2.611426637e-02F, 2.656182647e-02F, 3.064493090e-02F, 3.143463284e-02F, 3.377560899e-02F, + 3.289235756e-02F, 3.030913137e-02F, 3.103494830e-02F, 2.967805229e-02F, 2.790440805e-02F, 2.930012904e-02F, + 2.931595035e-02F, 2.638421766e-02F, 3.183818609e-02F, 3.194634989e-02F, 3.071940690e-02F, 3.065467440e-02F, + 2.881465107e-02F, 3.098880127e-02F, 2.940281294e-02F, 2.941502072e-02F, 3.298681602e-02F, 3.056411259e-02F, + 3.004425205e-02F, 3.229041770e-02F, 3.026798181e-02F, 3.129019216e-02F, 3.149134293e-02F, 2.985967882e-02F, + 2.673751116e-02F, 3.017138876e-02F, 3.168155253e-02F, 3.144061565e-02F, 3.025867604e-02F, 2.802851237e-02F, + 3.310769051e-02F, 3.083388135e-02F, -2.904861979e-02F, -2.724610269e-02F, 9.904558538e-04F, 9.337565280e-04F, + -1.614063978e-02F, -1.816093177e-02F, -1.953050494e-02F, -1.600682735e-02F, -1.943475567e-02F, -1.914030313e-02F, + -1.821169443e-02F, -1.984073035e-02F, -1.934201643e-02F, -1.823459007e-02F, -2.022176981e-02F, -1.475140173e-02F, + -1.733135432e-02F, -1.849748008e-02F, -1.778609678e-02F, -1.994918846e-02F, -1.812171750e-02F, -1.828673854e-02F, + -1.940162107e-02F, -1.799432188e-02F, -1.547645964e-02F, -1.430281159e-02F, -1.454824489e-02F, -1.722850464e-02F, + -1.925539225e-02F, -2.059458941e-02F, -1.850187592e-02F, -1.686822623e-02F, -1.799306646e-02F, -1.865793951e-02F, + -1.878385060e-02F, -1.835456304e-02F, -1.464798953e-02F, -1.621715166e-02F, -1.521440037e-02F, -1.831036992e-02F, + -1.974536851e-02F, -1.815999486e-02F, -1.647618040e-02F, -1.778819039e-02F, -1.755231246e-02F, -1.692831144e-02F, + -1.747004502e-02F, -1.614603773e-02F, -1.834193058e-02F, -1.836834662e-02F, -1.627993770e-02F, -1.772217080e-02F, + -1.639573835e-02F, -1.582238078e-02F, -1.702358760e-02F, -1.833777502e-02F, -1.648641936e-02F, -1.708367094e-02F, + -2.203190140e-02F, -1.537572220e-02F, -1.891145855e-02F, -1.910554245e-02F, -1.465820335e-02F, -1.858130284e-02F, + -1.618971117e-02F, -1.788988151e-02F, -1.740559191e-02F, -1.874000765e-02F, -1.500722021e-02F, -1.662155427e-02F, + -1.842871867e-02F, -1.920724101e-02F, -2.012148127e-02F, -1.800700091e-02F, -1.682516374e-02F, -1.822435670e-02F, + -1.797068119e-02F, -1.706128940e-02F, -1.681246981e-02F, -1.477418281e-02F, -1.625937223e-02F, -1.685329340e-02F, + -1.784100384e-02F, -2.053979412e-02F, -1.780523360e-02F, -1.936119795e-02F, -1.781547628e-02F, -1.449703518e-02F, + -1.852312125e-02F, -1.657091267e-02F, -1.510906126e-02F, -1.768072322e-02F, -1.419567037e-02F, -1.748019271e-02F, + -1.921444945e-02F, -1.800181903e-02F, -1.613678411e-02F, -1.851422712e-02F, -1.454663649e-02F, -1.765320078e-02F, + -1.652682945e-02F, -1.694910415e-02F, -1.631308720e-02F, -1.904780604e-02F, -1.733825356e-02F, -1.828677021e-02F, + -1.802074164e-02F, -1.727094315e-02F, -1.693795063e-02F, -1.638187096e-02F, -1.609986089e-02F, -1.796347834e-02F, + -1.791273244e-02F, -1.818771474e-02F, -1.517619286e-02F, -1.880451664e-02F, -1.621619798e-02F, -1.810957678e-02F, + -1.756452210e-02F, -1.604245789e-02F, -1.770794764e-02F, -1.502489951e-02F, -1.687237807e-02F, -1.893765293e-02F, + -1.817265525e-02F, -1.806844398e-02F, -1.813492179e-02F, -1.709862985e-02F, -2.071431279e-02F, -1.923400350e-02F, + -1.766669378e-02F, -1.746870391e-02F, -1.662058197e-02F, -1.705675572e-02F, -1.831123792e-02F, -1.627067477e-02F, + -1.723590679e-02F, -1.787560433e-02F, -1.640596241e-02F, -1.826582849e-02F, -1.705919206e-02F, -1.753624715e-02F, + -1.652088203e-02F, -1.950914226e-02F, -1.652600989e-02F, -2.037007920e-02F, -1.662223600e-02F, -1.492409129e-02F, + -2.054633759e-02F, -1.803889312e-02F, -1.608191803e-02F, -1.954500005e-02F, -1.626426727e-02F, -1.759027876e-02F, + -2.051851898e-02F, -1.816434041e-02F, -1.882074587e-02F, -1.619617268e-02F, -2.001894638e-02F, -1.650470681e-02F, + -1.924482919e-02F, -1.850609295e-02F, -1.895289868e-02F, -1.898872480e-02F, -1.889550872e-02F, -1.579819620e-02F, + -1.368748490e-02F, -1.591288671e-02F, -1.511171833e-02F, -1.888858713e-02F, -1.907504164e-02F, -2.072884515e-02F, + -1.703042164e-02F, -2.308790013e-02F, -1.677983627e-02F, -1.784849539e-02F, -1.952510141e-02F, -1.401827671e-02F, + -1.729401760e-02F, -1.549754664e-02F, -1.577190123e-02F, -1.700879633e-02F, -1.744829677e-02F, -1.915517636e-02F, + -1.748255640e-02F, -1.534430310e-02F, -1.854540035e-02F, -1.595428400e-02F, -1.877844892e-02F, -2.219825424e-02F, + -1.357357111e-02F, -1.714848541e-02F, -1.827344112e-02F, -1.249231398e-02F, -1.836279407e-02F, -1.749210991e-02F, + -1.995585486e-02F, -1.882841066e-02F, -1.686660759e-02F, -1.942721009e-02F, -1.812679693e-02F, -1.638859138e-02F, + -1.830222085e-02F, -1.932206936e-02F, -2.043106966e-02F, -1.726492308e-02F, -1.889580116e-02F, -1.440963056e-02F, + -1.778995059e-02F, -1.748846285e-02F, -1.395079773e-02F, -1.856644824e-02F, -1.619845815e-02F, -2.095426247e-02F, + -2.117985487e-02F, -1.852445863e-02F, -1.590139046e-02F, -1.514953841e-02F, -1.574104838e-02F, -1.771193370e-02F, + -1.786860637e-02F, -1.889200509e-02F, -1.871067844e-02F, -1.932670735e-02F, -1.841845363e-02F, -1.836613938e-02F, + -1.705580018e-02F, -1.606026292e-02F, -1.758630201e-02F, -1.696120203e-02F, -1.910111308e-02F, -1.736309752e-02F, + -1.812685467e-02F, -1.873402111e-02F, -1.611991227e-02F, -1.762653142e-02F, -1.537533756e-02F, -1.594643481e-02F, + -1.744898409e-02F, -1.880832016e-02F, -1.706471480e-02F, -1.829566248e-02F, -1.959557459e-02F, -1.860144734e-02F, + -1.652530208e-02F, -1.899594255e-02F, -1.765397005e-02F, -2.029876411e-02F, -1.738560386e-02F, -1.642342284e-02F, + -3.652353212e-02F, -3.359469771e-02F, +}; + } // namespace campplus_goldens diff --git a/tests/vllm/models/test_campplus.cpp b/tests/vllm/models/test_campplus.cpp index d79ebf1d8..6bc890d50 100644 --- a/tests/vllm/models/test_campplus.cpp +++ b/tests/vllm/models/test_campplus.cpp @@ -104,3 +104,85 @@ TEST_CASE("campplus CAMLayer gates the local branch by the pooled context") { INFO("max abs diff vs upstream CAMLayer: ", d); CHECK(d < 1e-5); } + +namespace { +// Fill a layer's parameters from the generator's stream. Names mirror torch's +// `named_parameters()` / `named_buffers()` exactly, so a mismatch here shows up +// as a tensor difference rather than as silently different weights. +vllm::models::campplus::DenseTdnnLayerWeights LayerW(const std::string& p, int64_t in_ch, + int64_t bn, int64_t growth, int64_t k) { + vllm::models::campplus::DenseTdnnLayerWeights w; + w.bn1_gamma = Rand(p + ".nonlinear1.batchnorm.weight", in_ch, 0.3); + w.bn1_beta = Rand(p + ".nonlinear1.batchnorm.bias", in_ch, 0.3); + w.bn1_mean = Rand(p + ".nonlinear1.batchnorm.running_mean", in_ch, 0.4); + w.bn1_var = Rand(p + ".nonlinear1.batchnorm.running_var", in_ch, 0.2); + for (float& v : w.bn1_var) v = std::fabs(v) + 0.5F; + w.linear1 = Rand(p + ".linear1.weight", bn * in_ch, 0.3); + w.bn2_gamma = Rand(p + ".nonlinear2.batchnorm.weight", bn, 0.3); + w.bn2_beta = Rand(p + ".nonlinear2.batchnorm.bias", bn, 0.3); + w.bn2_mean = Rand(p + ".nonlinear2.batchnorm.running_mean", bn, 0.4); + w.bn2_var = Rand(p + ".nonlinear2.batchnorm.running_var", bn, 0.2); + for (float& v : w.bn2_var) v = std::fabs(v) + 0.5F; + w.cam.linear_local = Rand(p + ".cam_layer.linear_local.weight", growth * bn * k, 0.3); + w.cam.linear1_weight = Rand(p + ".cam_layer.linear1.weight", (bn / 2) * bn, 0.3); + w.cam.linear1_bias = Rand(p + ".cam_layer.linear1.bias", bn / 2, 0.3); + w.cam.linear2_weight = Rand(p + ".cam_layer.linear2.weight", growth * (bn / 2), 0.3); + w.cam.linear2_bias = Rand(p + ".cam_layer.linear2.bias", growth, 0.3); + return w; +} +} // namespace + +TEST_CASE("campplus TransitLayer applies the nonlinear BEFORE the projection") { + const std::vector x = Rand("xt", kIn * kFrames, 1.0); + std::vector var = Rand("transit.nonlinear.batchnorm.running_var", kIn, 0.2); + for (float& v : var) v = std::fabs(v) + 0.5F; + const std::vector got = vllm::models::campplus::TransitLayer( + x, kIn, kFrames, kIn / 2, Rand("transit.nonlinear.batchnorm.weight", kIn, 0.3), + Rand("transit.nonlinear.batchnorm.bias", kIn, 0.3), + Rand("transit.nonlinear.batchnorm.running_mean", kIn, 0.4), var, + Rand("transit.linear.weight", (kIn / 2) * kIn, 0.3), + Rand("transit.linear.bias", kIn / 2, 0.3), 1e-5); + REQUIRE(got.size() == static_cast((kIn / 2) * kFrames)); + CHECK(Worst(got, kTransit, got.size()) < 1e-5); +} + +TEST_CASE("campplus DenseLayer projects a POOLED stats vector as T=1") { + // The final dense consumes StatsPool's [2C] output, which upstream reaches by + // unsqueeze -> conv -> squeeze. Treating it as anything other than T=1 changes + // the shape rather than the numbers, so the length is asserted too. + const std::vector stats = vllm::models::campplus::StatsPool( + Rand("x", kChannels * kFrames, 1.0), kChannels, kFrames); + std::vector var = Rand("dense2d.nonlinear.batchnorm.running_var", 12, 0.2); + for (float& v : var) v = std::fabs(v) + 0.5F; + const std::vector got = vllm::models::campplus::DenseLayer( + stats, 2 * kChannels, /*frames=*/1, 12, Rand("dense2d.linear.weight", 12 * 2 * kChannels, 0.3), + {}, {}, {}, Rand("dense2d.nonlinear.batchnorm.running_mean", 12, 0.4), var, 1e-5, + /*apply_relu=*/false); // config_str="batchnorm_" => batchnorm only + REQUIRE(got.size() == 12U); + CHECK(Worst(got, kDense2d, got.size()) < 1e-5); +} + +TEST_CASE("campplus CAMDenseTDNNLayer chains the two nonlinears around linear1") { + const std::vector x = Rand("xt", kIn * kFrames, 1.0); + const std::vector got = vllm::models::campplus::DenseTdnnLayer( + x, kIn, kFrames, kBnChannels, kGrowth, kKernel, kDilation, kSegLen, + LayerW("dl", kIn, kBnChannels, kGrowth, kKernel), 1e-5); + REQUIRE(got.size() == static_cast(kGrowth * kFrames)); + CHECK(Worst(got, kDenseTdnnLayer, got.size()) < 1e-5); +} + +TEST_CASE("campplus CAMDenseTDNNBlock GROWS its channel count by concatenation") { + // x = cat([x, layer(x)], dim=1): each layer sees every earlier output, and the + // width grows by `growth` per layer. Appending in the wrong order still yields + // a correctly shaped tensor, so the values are what catch it. + const std::vector x = Rand("xt", kIn * kFrames, 1.0); + std::vector layers; + for (int64_t i = 0; i < kNumLayers; ++i) { + layers.push_back(LayerW("blk.tdnnd" + std::to_string(i + 1), kIn + i * kGrowth, kBnChannels, + kGrowth, kKernel)); + } + const std::vector got = vllm::models::campplus::DenseTdnnBlock( + x, kIn, kFrames, kBnChannels, kGrowth, kKernel, kDilation, kSegLen, layers, 1e-5); + REQUIRE(got.size() == static_cast((kIn + kNumLayers * kGrowth) * kFrames)); + CHECK(Worst(got, kDenseTdnnBlock, got.size()) < 1e-5); +} From a3aa02e197ec200e92058ac686f1874afb381225 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 00:18:32 +0200 Subject: [PATCH 16/55] spec(MODEL-MUSIC-MUSIC3): scope MiniMax-Music3, the first row whose oracle is not vLLM (#672) (#679) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * spec(MODEL-MUSIC-MUSIC3): scope MiniMax-Music3, the first row whose oracle is not vLLM (#672) MiniMaxAI/MiniMax-Music3 is text-to-music: lyrics plus a structured description in, a multi-minute stereo song out. It is absent from the pinned vLLM (no source files; the registry carries only the MiniMax M2/M3 TEXT architectures), absent from vLLM `main`, and absent from vllm-omni. So it is the first row to exercise the fallback-oracle rule added in #647, and its primary oracle is an OPEN diffusers PR. W0 ONLY. Spec committed before implementation, per AGENTS.md. No src/, include/ or tests/ change is in scope here; W1 onward are dispatched to fresh implementers from this spec rather than written in the coordinating session. GEOMETRY MEASURED, NOT INFERRED. Every component config.json read, plus the safetensors headers by HTTP range request -- the same no-payload technique LTX-2.5 L0 used. What that corrected, twice: * transformer is 231 tensors / 1.240B params per shard, ALL F32. The card's "2.4B" is right and the 9.73 GB on disk is fp32 storage, NOT a 4.9B bf16 model. Reading the byte count alone would have sized it at double. * condition_encoder has FOUR tensors -- layer_scale, layer_weight_logits and a proj. It is a learned weighted mix over 8 LLM hidden layers, not the encoder tower its name suggests. Also measured: rvq_depth_decoder 47 tensors / 0.646B BF16, vocoder 121 / 0.054B F32 carrying weight_g/weight_v, so weight-norm must be folded at load. THE LARGEST BRICK IS ALREADY BUILT. language_model is `Qwen3ForCausalLM` at 36L / 4096 / 32 heads / 8 KV / head_dim 128 -- our LANDED architecture exactly -- retrained on a 200 000-entry music vocabulary. Vocabulary size is a config value, not an architecture change. TWO PACKAGINGS, ONE SET OF WEIGHTS, and that had to be established rather than assumed, because the two oracles disagree about which one they serve. SGLang-Omni loads ONLY the native arm: checkpoint.py:35-56 resolves qwen_7B/qwen_7B, flowmatching_vae.pth and dav.pth, and pulls the RVQ decoder out of the Qwen shards by the model.audio_decoder. prefix. diffusers loads the six modular components. convert_minimax_music3_to_diffusers.py@c6da9936 :29-38,47-189 settles it -- load the three native artefacts, rename tensors, save. A pure re-layout, no numerical step. Therefore the diffusers arm is the port target (~28.5 GB resident, no pickle, no trust_remote_code, per-component gateable) AND SGLang-Omni stays a valid e2e cross-check instead of an incomparable second model. W1 verifies that equality against actual tensors rather than inheriting it from this reading. THE DTYPE POLARITY IS UPSTREAM'S. The conversion script defaults --dtype float32 (:267), gives transformer/condition_encoder/vocoder that dtype (:208-211) and forces the RVQ decoder to bf16 regardless (:214) -- matching the measured headers exactly, and SGLang-Omni's README confirms both placements run the acoustic stage in FP32. So fp32 there is a resolved upstream choice, not the too-wide accident AGENTS.md warns a token gate cannot see. It is mirrored, and annotated as this. ONE CONTRADICTION LEFT OPEN ON PURPOSE. The model card and SGLang-Omni's README say 32 kHz stereo; every config says 44100 (vocoder sampling_rate, and condition_encoder output_sampling_rate with hop 512). Both cannot be right and the difference is a resample -- which no tensor-parity check on the latents would catch, so it is exactly the class of defect that survives a green gate. W0 resolves it from source before ANY waveform golden is captured. It is recorded as unresolved rather than guessed. ORACLES PINNED. diffusers advances from UNPINNED to the PR #14456 head c6da9936 -- the head SHA and not the branch name, because huggingface:minimax-music3-integration can be rebased under us and "whatever the branch was that day" is not reproducible. SGLang-Omni gets its OWN record at 748a0b43: it is a third repository with its own cadence and its own registry, and folding it into sglang.md would have hidden that a model can be in one and in neither of the others. Both stay gateable = no -- read, never executed here -- which is the honest state and what W0 exists to change. GATE SHAPE, stated up front because conflating the halves is the failure mode: the LLM half emits discrete RVQ codes and gets a real token-exact gate; the acoustic half is a flow-matching loop with no logits and no sampler, so it gets per-stage tensor parity at fixed seed and reduced dimensions, following H3. A correlation coefficient is NOT a gate there -- Pearson is scale-invariant, so a uniformly scaled latent passes it while sounding wrong. Records: MODEL row ratchet 370 -> 371 (the test that mutates it to expected-1 keeps the bump load-bearing), checklist entry + rollup SPIKE 8 -> 9, the 11 non-pin architectures become 12, roadmap issue table, and a claim file. Gate: scripts/agent-preflight.sh -- all checkers and all suites ok EXCEPT test_cpu_x86_llamacpp_floor, a contention-sensitive harness that failed identically (NO_QUIET_WINDOW, exit 4) on an unmodified tree at base SHA cefacd2d0 under load average 35. Pre-existing and environmental, verified rather than assumed. Issue: #672, #647 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] * spec(MODEL-MUSIC-MUSIC3): the 32 kHz / 44100 split is a STAGE BOUNDARY, not a contradiction (#672) W0 owed this from source before any waveform golden could be captured. Resolved, and the answer is that both numbers were right about different points in the pipeline. THE VOCODER NATIVELY EMITS 44100 Hz STEREO, and it is derivable rather than merely declared -- which matters, because a declared sample rate in a config is exactly the kind of value that goes stale without anything noticing. The condition encoder's output_sampling_rate 44100 / output_hop_length 512 set a latent frame rate of 86.133 Hz (condition_embedder_minimax_music3.py:40-41), the decoder applies one ConvTranspose1d per upsampling_ratios entry for 8*8*4*2 = 512x (minimax_music3_vocoder.py:84,92-95), and 86.133 * 512 = 44100. The declared sampling_rate matches its own convolution stack. SGLang-Omni's independent implementation lands on the identical numbers (dav.py:94,115), which is the strongest form of confirmation available here: two codebases that were not copied from each other. DIFFUSERS RETURNS THAT UNRESAMPLED (modular_pipeline.py:32-36, decoders.py:84-92). SGLANG-OMNI'S SERVER RESAMPLES 44100 -> 32000 ON DELIVERY (constants.py:18-19, acoustic.py:55-58,422-431). So "32 kHz stereo" in the model card and in SGLang-Omni's README is neither wrong nor stale: it is the reference SERVER's delivery rate. diffusers' own docs state the split in as many words. DECISION: goldens are captured at 44100 stereo -- the model's native generative rate, resample-free, and what the primary oracle hands the caller. The 32 kHz form is a downstream delivery transform, gated separately if SGLang-Omni byte parity is ever wanted. That transform is NOT free, and the spec says so rather than leaving it to be discovered: acoustic.py:58 passes no lowpass_filter_width, rolloff or resampling_method, so reproducing those bytes means reproducing torchaudio's DEFAULT sinc filter, not converting 44.1 -> 32 by any correct method. And the reason this had to be settled before the first golden, not after: a latent-tensor parity check sits entirely upstream of the resample call and cannot see the difference. A golden captured at the wrong rate would be wrong by a resample while every tensor gate stayed green -- the class of defect that survives a green gate, which is the one this project spends its evidence discipline on. Also corrected: the 24000/960 pair in condition_encoder/config.json is the AR stage's 25 Hz frame rate, unrelated to output. It reads like a third sample rate and is not one. W0 now owes only the oracle stand-up. The §1.1 stop condition is removed because it is discharged; the W5 row names 44100 stereo instead of "the resolved rate". Issue: #672 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --------- Co-authored-by: Ettore Di Giacinto --- .agents/claims/CLAIM-MODEL-MUSIC3-W0.md | 5 + .agents/model-matrix.md | 21 +- .agents/oracles/diffusers.md | 20 +- .agents/oracles/sglang-omni.md | 44 ++++ .agents/oracles/sglang.md | 15 +- .agents/roadmap_v1.md | 1 + .agents/specs/minimax-music3.md | 298 ++++++++++++++++++++++++ AGENTS.md | 3 +- docs/FEATURES.md | 2 + scripts/check-agent-record.py | 9 +- tests/scripts/test_agent_record.py | 35 ++- 11 files changed, 430 insertions(+), 23 deletions(-) create mode 100644 .agents/claims/CLAIM-MODEL-MUSIC3-W0.md create mode 100644 .agents/oracles/sglang-omni.md create mode 100644 .agents/specs/minimax-music3.md diff --git a/.agents/claims/CLAIM-MODEL-MUSIC3-W0.md b/.agents/claims/CLAIM-MODEL-MUSIC3-W0.md new file mode 100644 index 000000000..2a6e49cf4 --- /dev/null +++ b/.agents/claims/CLAIM-MODEL-MUSIC3-W0.md @@ -0,0 +1,5 @@ +# CLAIM-MODEL-MUSIC3-W0 + +| Claim | Row IDs | Agent | Worktree / remote dir | Branch | Owned scope | State | Last update | +|---|---|---|---|---|---|---|---| +| `CLAIM-MODEL-MUSIC3-W0` | `MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation` (`SPIKE`) | Claude Code (opus-5), operator role | isolated worktree `/home/mudler/_git/wt-music3`; oracle checkouts read-only at `/home/mudler/_git/sglang-omni` | `row/MODEL-MUSIC-MINIMAX-MUSIC3`, issue [#672](https://github.com/mudler/vllm.cpp/issues/672) | Owns: NEW `.agents/specs/minimax-music3.md`; the Music3 rows in `.agents/roadmap_v1.md` and `.agents/model-matrix.md` (detailed row, checklist entry, rollup counts, and the MODEL ratchet `370 -> 371`); NEW `.agents/oracles/sglang-omni.md` plus the `diffusers` pin advance to the PR #14456 head and the AGENTS.md registry row that admits `sglang-omni`. EXCLUDES: all engine code — no `src/`, `include/` or `tests/` change is in W0's scope, and W1 onward are dispatched to fresh implementers from the committed spec rather than written here. Also excludes the native `AbabForCausalLM` checkpoint layout, which the spec refuses by name and records as owed rather than silently mis-loading | `ACTIVE` | 2026-08-13 — **MERGE AUTHORITY RECORDED**: developer-directed "land minimax music 3 support complete, to vllm.cpp, wired to the ABI and to the example http server, merge to main, tested e2e". W0 spec committed. The oracle stand-up (proving `diffusers` builds and runs this model, which flips `.agents/oracles/diffusers.md` to `gateable = yes`) and the sample-rate resolution of spec §1.1 are the remaining W0 work; W1 is not dispatched until both land. | diff --git a/.agents/model-matrix.md b/.agents/model-matrix.md index cd53f75c3..2e7846ffd 100644 --- a/.agents/model-matrix.md +++ b/.agents/model-matrix.md @@ -19,15 +19,18 @@ a practical unit that one agent can spike without silently dropping aliases. ## Architecture-support checklist At-a-glance view of which architectures we have actually engaged, and how far. -**355 architecture rows are inventoried at the pin, plus 13 rows that the pinned -registry does not contain = 368 architecture rows.** Those 13 are, by why they +**355 architecture rows are inventoried at the pin, plus 14 rows that the pinned +registry does not contain = 369 architecture rows.** Those 14 are, by why they are not at the pin: `KimiK3ForConditionalGeneration` and `MuseGlimmerForConditionalGeneration`, both released after the pin; `BailingMoeV3ForCausalLM`, a pin-lag row registered on vLLM `main` and absent only at the pin (#609); `MiniMaxH3DiTModel` and `LTX2VideoTransformer3DModel`, both DIFFUSION architectures released after the pin and absent from the vLLM repository entirely, reached through the -separate `vllm-omni` repository (#435); the six TTS / audio-generation architectures +separate `vllm-omni` repository (#435); `MiniMaxMusic3ForConditionalGeneration`, +absent from the pin, from vLLM `main` and from `vllm-omni` alike, whose only +reference implementations are an open `diffusers` PR and SGLang-Omni (#672); the +six TTS / audio-generation architectures registered in that same `vllm-omni` repository — `MossTTSDelayModel`, `MossTTSRealtime`, `Qwen3TTSForConditionalGeneration`, `HiggsMultimodalQwen3ForConditionalGeneration`, and IndexTTS-2.5's two halves @@ -37,12 +40,12 @@ in neither core vLLM `main` nor `vllm-omni` — `VoxtralRealtimeForConditionalGeneration` and `BailingMMNativeForConditionalGeneration` (#610). -The rollup below counts **372 rows**, which is those 368 plus the four that are +The rollup below counts **373 rows**, which is those 369 plus the four that are not architectures at all and say so in their own sections: `MODEL-FACTORY-registry` (the cross-cutting registry contract), the two `MODEL-AUDIO` encoder-component rows, and the one `MODEL-HFDYNAMIC` row (dynamic Transformers compatibility is capability-driven and excluded from finite counts). -Of those 372, 49 are past `INVENTORIED` (engaged) and the remaining 323 are the +Of those 373, 50 are past `INVENTORIED` (engaged) and the remaining 323 are the known-but-not-started long tail — the same two numbers the rollup table states, and the way to re-derive every count here is to re-run [`scripts/check-agent-record.py`](../scripts/check-agent-record.py)'s parser over @@ -69,14 +72,14 @@ Rollup by lifecycle state (must equal the detailed per-state row counts): | INVENTORIED | 323 | | PARTIAL | 20 | | ACTIVE | 9 | -| SPIKE | 8 | +| SPIKE | 9 | | BLOCKED | 5 | | DONE | 3 | | READY | 3 | | GATING | 1 | -| **Total** | **372** | +| **Total** | **373** | -Engaged architectures (the 49 non-`INVENTORIED` rows): +Engaged architectures (the 50 non-`INVENTORIED` rows): | Support | Architecture | Family / example | Status | Row | |---|---|---|---|---| @@ -109,6 +112,7 @@ Engaged architectures (the 49 non-`INVENTORIED` rows): | 📋 | `MuseGlimmerForConditionalGeneration` | Muse Glimmer 30B (Meta, agentic multimodal; iRoPE + gated attn + perception encoder + DFlash) | **W0-W7 ON `row/MODEL-MUSE-GLIMMER` (PR #279, NOT merged): 52-layer text forward, 50-layer perception encoder, mm wiring, ATEM reasoning + tool parsers — the forward no longer refuses.** The evidence behind that is NARROW and says so: text agrees with a torch transcription of #51655 and with HF's own `muse_glimmer` on REAL 30B tensors at **reduced depth 4/52 only** (5 prefill argmax positions, no generated tokens); full depth never ran, multi-step decode is untested, the perception encoder has NO reference check, nothing has run through the server, and the ATEM parsers' channel scoping does not work at server defaults (the seam has no `adjust_request` dispatch site and `skip_special_tokens` defaults true, so the framing is stripped first). The lifecycle token stays `SPIKE`/📋 deliberately: advancing it owes `docs/STATUS.md` and `.agents/NOW.md` rows, and both are byte-exactly at their shrink-only ratchet/budget, so the advance belongs to the landing commit that can pay for them. Released 2026-08-08, **BEYOND-PIN**: no Muse code at `555967922` nor on vLLM `main`; the only upstream impl is the OPEN PR [#51655](https://github.com/vllm-project/vllm/pull/51655) head `075d645af` (approved, 3/20 CI red), ported from on explicit developer direction as a tracked exception (porting-inventory §9 deviation 16). **No oracle ⇒ no speed axis is claimable**; correctness gates against the HF reference. Text tower fully traced (Gemma-style sandwich norms w/ baked +1 offset + split eps, Llama-4-style iRoPE NoPE/full vs RoPE/sliding, weightless pre-RoPE QK-norm, ~3.87 query pre-scale w/ dual config schema, Qwen3.5-style attn output gate reading the LAYER INPUT); 50-layer perception encoder (linear patchify, bilinear pos-emb interp, width-first 2D RoPE, block-windowed attn, pixel-shuffle merge) reuses the Qwen3-VL vision seam; DFlash is recognition + `is_neox_style` threading on the existing speculator, not a new drafter | `MODEL-MM-muse-glimmer-muse-glimmer-for-conditional-generation` | | 📋 | `KimiK3ForConditionalGeneration` | Kimi K3 (2.8T MoE + MoonViT-V2, DERIVE-AND-SHIP) | **W2/W5 CPU scaffolding landed** (registry stub + nested text/vision/quant config descent + text-backbone structural name-map + REFUSE-by-name forward + MXFP4-refuse loader; clean CPU build, scaffold gate 6/6). text backbone IS `KimiLinearForCausalLM` (KDA+MLA+MoE hybrid, HEAVY reuse); **does NOT fit GB10 (~1.56 TB MXFP4, ~12×)** and NOT in the pinned oracle ⇒ no on-box golden — DERIVED, proxy-gated on Kimi-Linear-48B; forward + MXFP4 + KDA delta + MoonViT-V2 not implemented (NOT-YET-BUILDABLE) | `MODEL-MM-kimi-k3-kimi-k3-for-conditional-generation` | | 🚧 | `MiniMaxH3DiTModel` | MiniMax-H3 (33.1B omni-modal video+audio DiT, DERIVE-AND-SHIP) | **W1/W2 landed**: packed layout (fl2va + ref2va, fp64 position grid BIT-EXACT), latent packing, euler-ancestral eta0 scheduler, and the full DiT forward all parity-gated against the UPSTREAM vLLM-Omni modules executed at reduced dimensions (**max abs diff 1.6e-7**, 10/10 cases / 2539 assertions). NOT autoregressive (no KV cache, no sampler, no logits) and **e2e HW-BLOCKED** (~354 GB checkpoint, ~133 GB/rank on 4x B300 vs 119 GiB unified); bf16 production stream + request planning + the ComfyUI-GGUF arm also landed (535 REAL tensors resolve onto our contract, geometry from shapes alone). **HW verdict CORRECTED: quantized arms FIT (~41 GB in 119 GiB)**, so e2e + speed are reachable; encoder/VAEs/audio VAE DONE (4.2e-9 vs the checkpoint's remote code); NVFP4 layout GATED as identical to ours (speed path is loader wiring); BOTH VAE DECODERS done (audio 4.2e-9, video ViT3D 8.9e-8); video tiling + 3D-CNN encoder (conditioning only) pending; encoder TEXT tower done (1.2e-7); **serving `/v1/videos` DONE and the DEVICE-RESIDENT forward (W2b, f32) LANDED + GPU-VERIFIED on Thor sm_110 at video 1.49e-7 / audio 8.94e-8**; bf16 stream + fusion folds + the FP4 path (needs sm_121a) + a real-checkpoint run pending. **2026-08-05: the AUDIO-VAE ENCODER is ported** (DAC analysis stack + `pre_block` AttnProjection + `mean_proj`, gated stage by stage vs the checkpoint's own remote code at 2.98e-8 / 1.64e-7 / 1.86e-8) with its own checkpoint loader gated on the real 1087-tensor manifest — so **ref2va AUDIO and VIDEO+AUDIO references are now WIRED** (audio rows move by 0.51 / 0.71; a different waveform still moves them by 7.1e-4). Both VAEs are now complete in both directions. **bf16 13-SHARD RELEASE INDEXES 2026-08-07 (`row/H3-BF16-SHARDED-DIT`)**: `MiniMaxH3ShardedCheckpoint` resolves the ORIGINAL 66.3 GB release through its own `model.safetensors.index.json` (a tensor named in the index but missing from its shard throws BY NAME), `EnumerateMiniMaxH3ShardedTensors` feeds the shared shapes-only geometry parser, and `LoadMiniMaxH3DitFromShards` is the host-f32 reference loader. Gated CPU-only at 72/72/54497 (post-rebase): every tensor resolves to the shard the index named AND to the bytes written there, the derived geometry equals the single-file path field for field, and a SPARSE 13-shard release with the REAL 535 tensors at REAL shapes (66.3 GB declared, 144 KB on disk) derives the SHIPPED geometry (50/5376/56/128/14336/24/32/1x2x2/5120). **STREAMS 2026-08-07 (`row/H3-BF16-SHARDED-STREAM`)**: `StreamMiniMaxH3ShardedToDeviceBf16` uploads it one tensor at a time — a BF16 tensor bound for a bf16 device slot goes straight from the mmap with ZERO host buffer, so peak host is bounded by ONE tensor (observed `host_peak=8192`, `direct=37 converted=9`); bit-exact vs the non-streamed `StageMiniMaxH3DitWeights` reference over all 46 views with identical logits, rope.inv_freq host-resident, 73/73/55203. Spec §8.14. **bf16 TEXT ENCODER + THE CONDITIONING NUMBER 2026-08-07 (`row/H3-ENC-BF16-COND-DIFF`)**: the 14-shard 63 GB bf16 Qwen3-VL-32B encoder streams to device too (`StreamMiniMaxH3EncoderShardsToDevice`, q/k/v and gate/up fused ON DEVICE), `--encoder-only` runs the tower alone (peak ~96 -> ~49 GiB by not loading the DiT first), and the widening is gated BIT-IDENTICAL vs an f32-staged tower so the A/B cannot be confounded. MEASURED on Thor over 233 tokens: Q4_K_M vs bf16 conditioning is cos 0.99745 mean / 0.909 min, rel RMS 6.85% excluding the attention sink, median rotation 3.5 deg — same energy as a ONE-WORD prompt edit but DIFFUSE (232/233 tokens rotate vs 172/233). Whether the RENDER changes is NOT established. 75/75/55609. Spec §8.15. This UNBLOCKS the quantization-quality question; no bf16-vs-quant render or speed number is claimed. Spec §8.13. **W-FP4a LANDED (CPU) 2026-08-06 (`row/H3-FP4-SPEED`)**: the device DiT forward now routes the NVFP4 projections through the shared Marlin W4A16 dispatcher (fp4 kept packed; no new quant code), fp4-vs-bf16 wiring gate GREEN (62/62·30039). **W-FP4a GB10 leg LANDED 2026-08-06 (`row/H3-FP4-GPU-E2E`, PR #64):** on sm_121a the Marlin W4A16 path RAN for all 11 projections (`dense_gemms==11` default — VT_MARLIN_DENSE is default-ON → vLLM's own DENSE Marlin GEMM, not the grouped route; `marlin_gemms==11` under VT_MARLIN_DENSE=0; `fallback_gemms==0`), fp4-vs-bf16 BYTE-EXACT (max\|diff\|=0), and the fp4 arm is a MEMORY win not a diffusion-forward speed win (per-forward bf16/fp4 3.47× @seq64 → 0.79–0.83× @seq4224–7040; ~16 vs ~66 GB device). Real-checkpoint fp4-resident t2va e2e RUNS (real 18.75 GB NVFP4 DiT + VAEs + GGUF Qwen3-VL-32B encoder → valid mp4/wav; DiT s/step 5.45/20.0/209 s @512/768/REF-209f) but frames are a non-scene patch-grid at 12/20/50 steps → OPEN render bug (device VAE/denoise). vLLM-Omni has no quantized H3 arm (BF16-only) so any comparison is HW/loader-forced-indirect — spec §8. **ONE-SURFACE ROW 2 LANDED 2026-08-08 (`row/H3-VIDEO-ABI`)**: the whole assembly pipeline is library-owned (`vllm::multimodal::MiniMaxH3VideoEngine`, `minimax_h3_video.cpp`) behind the ABI v12 `vllm_video_*` entry points; `/v1/videos` routes through the SAME seam; `minimax_h3_gen`+`minimax_h3_mux` are thin `vllm.h` clients, frames+WAV byte-identical to the pre-fold binary on the committed fold fixture (`test_minimax_h3_video_fold` 3-arm gate + the v12 `test_capi` section); GB10 real-video re-verify via the v12 ABI = named residual. **ROW 2 DEVICE-SEAM FOLLOW-UP (#135; replaces #134):** ABI 0/1 maps once to `vt::DeviceType`; shared code dispatches through `GetBackend(device_type)`, restoring DSR 34→32 without a baseline/allowlist change; CPU compile/fold test pending in CI due shared-disk pressure **PRUNED CHECKPOINTS LOAD AND RUN 2026-08-10 (`row/H3-PRUNED`, #241, spec §8.21):** the community `pruned` variants (`unsloth/MiniMax-H3-GGUF` Q2_K..Q8_0, `lilcheaty/MiniMax-H3-NVFP4` `*_pruned_nvfp4`) are not lossily pruned but ComfyUI's AdaLN timestep-CURVE refactor (`comfy/ldm/minimax/model.py:419-432,610-615`): an `adaln_t_table` [1025, 8] lerp replaces the sinusoidal+MLP time embedder, no SiLU before the AdaLN linear, and its in_features drop 2688 -> 8, collapsing `adaln_proj` from 13.04B of 33.12B to 0.04B and the DiT to 20.11B — so a pruned Q8_0 (21.4 GB) costs what our unpruned Q4_K_M (19.9 GB) costs. Gated on the REAL 532-tensor pruned GGUF manifest (532 = 535 - 4 `time_embedder.*` + 1 table, header-only), the clamped-lerp golden, and a CONSTRUCTED-curve test proving the pruned forward is numerically identical to the unpruned one; 79/79/57299. | `MODEL-DIFFUSION-minimax-h3-mini-max-h3-dit` | +| 📋 | `MiniMaxMusic3ForConditionalGeneration` | MiniMax-Music3 (text-to-music: ~8.6B Qwen3 global LLM + 0.646B 8-codebook RVQ depth decoder + 2.4B fp32 flow-matching DiT + 0.054B DAC Flow-VAE) | **W0 spec committed 2026-08-13 (#672, [spec](specs/minimax-music3.md)).** Geometry MEASURED from every component `config.json` plus safetensors headers by range request. First row to exercise the fallback-oracle rule (#647): absent from vLLM AND vLLM-Omni, so its primary oracle is the OPEN diffusers PR #14456 head `c6da9936` with SGLang-Omni `748a0b43` as the e2e cross-check. Not implemented | `MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation` | | 📋 | `LTX2VideoTransformer3DModel` | LTX-2.5 (21.00B joint video+audio flow-matching DiT, Lightricks) | **L0 spec committed 2026-08-11 (#435, [spec](specs/ltx-2-5.md)).** Geometry MEASURED from the FP8 checkpoint's own safetensors header by HTTP range request (6124 tensors, 881,048-byte header, no payload downloaded): **21.00B** params — 48 blocks @ 386.7M = 18.560B, audio embeddings connector 2.016B, global 0.427B. The filename says `22b` and the Diffusers card says ~19B; the MEASURED count is what this row uses. Video stream 4096 (32 heads x 128), audio stream 2048 (32 heads x 64), in/out channels 128 both. Per block SIX attentions — `attn1` (video self), `attn2` (video<->text, cross 4096), `audio_attn1`, `audio_attn2` (cross 2048), and the two CROSS-MODAL `audio_to_video_attn` / `video_to_audio_attn` — which is the structural break from MiniMax-H3: H3 packs every modality into ONE sequence with per-row token tags, LTX runs TWO streams coupled by explicit cross-attention. **Per-head gated attention on every attention** (`to_gate_logits` = `Linear(query_dim, heads, bias=True)`, `attention.py:513-514`, applied AFTER the attention output at `:577`) — H3 has no analogue and getting it wrong renders plausibly-wrong rather than erroring. FFN is gelu-approximate 4096->16384->4096 with **NO bias** while `audio_ff` 2048->8192->2048 **HAS** bias, which independently confirms `ff_bias=false` / `audio_ff_bias=true` from `model_configurator.py:78-80` — checkpoint and source agreeing, not either alone. **RETRACTED 2026-08-12, was billed as a FREE WIN.** The spec claimed 2.5 sets `use_prompt_adaln_single=false` so the cross-attention K/V are timestep-free and cacheable. The shipped checkpoint DISPROVES it: it carries 12 `prompt_adaln_single`/`audio_prompt_adaln_single` tensors including a `timestep_embedder.linear_1 [4096, 256]` (256 = the sinusoidal timestep width), and `model.py:223-227` builds that module ONLY when the flag is TRUE. `transformer.py:441` was quoted as proof of no timestep term, but `:442-443` add one whenever `prompt_timestep` is not None, and the comment above them says exactly that. NO SHIPPED DEFECT: `ltx2_dit.cpp:672` refuses the cache by name when the flag is on, so the feature is correct-and-inapplicable rather than silently wrong, and stays gated bit-identical and prompt-bound for any checkpoint that does set it false. **ORACLE:** vLLM-Omni does NOT support 2.5 — `resolve_ltx_pipeline_recipe` keys on 2 / 2.3 only and RAISES otherwise (`ltx2_recipes.py:162-166`), with upstream [vllm-omni#6066](https://github.com/vllm-project/vllm-omni/issues/6066) filed 2026-08-11 — but its `DiffusersAdapterPipeline` is generic (`DiffusionPipeline.from_pretrained`, `pipeline_diffusers_adapter.py:116`), so vLLM-Omni CAN execute 2.5 via `--load-format diffusers`. Binding oracle = that adapter; immediate cross-check = Lightricks `ltx-pipelines`. **HW: FITS ONE GB10** at ~29 GB (NVFP4 DiT 18.72 + NVFP4 Gemma-4 TE 7.40 + VAEs 1.83 + upscaler 1.00) vs H3's ~41 GB. **OWED UP FRONT, not to be discovered later:** the speed axis lands `PENDING` because the adapter is a black box (`supports_step_execution=False`, `supports_request_batch=False`) and therefore NOT vLLM's production configuration, which AGENTS.md requires as the denominator; DiffVAE (`NADiffusionDecoder`, neighborhood attention) is REFUSED BY NAME until its own row rather than silently downgraded to the Conv VAE; and no render-quality claim is made from structural e2e. | `MODEL-DIFFUSION-ltx-2-5-ltx2-video-transformer-3d-model` | | ✅ | `LagunaForCausalLM` | Poolside Laguna-S-2.1 (118B/8B MoE) | **LONG-CTX DECODE LEVERS LANDED + MEASURED (2026-08-03, `CLAIM-LAGUNA-LONGCTX-LEVERS`): window-bounded SWA reads (`VT_LAGUNA_SWA_WINDOW`, default-ON, BYTE-EXACT) bound the four `DecodeAttnGqa*` kernels' read to the ~512 sliding window (vLLM `laguna.py:412`) — GB10 A/B token-IDENTICAL `=1` vs `=0` at 520-token context (truncation active), MEASURED −0.30 ms/step at ~2k (~0 at ≤512, grows linearly). bf16 paged KV (`VT_LAGUNA_KV_BF16`, default-OFF opt-in) a distributional near-tie left UNRATIFIED. See BENCHMARKS `CLAIM-LAGUNA-LONGCTX-LEVERS`.** — **NVFP4 W4A4 ARM RAN on GB10 (N4, 2026-08-01, `CLAIM-LAGUNA-NVFP4-N4`): the additive safetensors NVFP4 arm (N1a/N1b/N2/N3 — `Nvfp4Weight` expert fields + `LoadLagunaForCausalLMWeights` + `LqGemmNvfp4Fp4` per-expert TRUE-W4A4 + `LagunaFfnBlock` `fp4` branch + `laguna_gen` dir-autodetect; CPU-gated `test_laguna_nvfp4_loader` 3/3·61, GGUF path byte-identical) generates COHERENTLY on the real 67 GiB `poolside/Laguna-S-2.1-NVFP4`. vs the vLLM MARLIN golden (vLLM's exact prompt ids injected): FIRST 2 TOKENS MATCH exactly, then near-tie divergence (our TRUE-W4A4 fp4-activations vs the MARLIN golden's W4A16 bf16-activations — different precision, EXPECTED; shares golden vocab). SPEED (N5, trace-driven, 2026-08-01): 0.16 → ~4.5 tok/s (~28× THIS SESSION), now ~4× from vLLM 18.8. **Lever #2** (nsys found the bf16 tower running host `MatmulNK` on the CUDA queue): route it to the GPU (`LqGemm` bf16 → `CastBf16` + `MatmulBT`, weight stays bf16) → 6.34 → 0.39 s/tok (16×). **Lever #1** (nsys found the emulation expert GEMM at 92%, GPU 87% busy): the engine's native sm120a fp4 tensor-core MMA (`MatmulNvfp4Fp4Native`) reads the SAME linear scales — it was gated OFF behind `VT_NVFP4_FP4_NATIVE`; default it ON in the driver → 0.39 → ~0.20-0.24 s/tok (~2×). Both coherent + near-tie (byte-identical ids to emulation; first token matches golden). Two GB10 memory fixes landed to run (shard-release + context-before-load). OPEN #234 (remaining ~4×): grouped W4A4 MoE (top_k×3 launches → 3), `ResidentNvfp4`, decode CUDA-graph + on-GPU sampling (the host-orchestration tail). Spec `.agents/specs/laguna-nvfp4-arm-2026-07-31.md` §N4/§N5. The GGUF-Q4_K track (below) is the separate keep-quant vehicle.** Prior **FASTER DECODE (W9, 2026-07-31, `CLAIM-LAGUNA-W9-GROUPED`): the 30 un-grouped per-expert keep-quant GEMV launches/step (top_k × {gate,up,down} `LqGemmRowSlice`) fold onto the SHARED `vt::MatmulBTQuantGrouped` op — per token, Pk experts' gate/up/down each collapse to ONE grouped launch over the already-stacked `[E*N,H]` tower (no loader change). Same-binary A/B on real UD-Q4_K_XL (GB10, `--gpu`, drop_caches cold, 24 tok): grouped (`VT_LAGUNA_GROUPED_MOE=1`, default) == per-expert (`=0`) BYTE-IDENTICAL (md5 `754728c6`, both == W6 golden) + decode 0.18 → 0.13 s/tok (1.38×). Routes through the shared vt op (fold policy). Cumulative with W8: decode 0.66 → 0.13 s/tok (5.1×; 1.5 → 7.7 tok/s; 18× → 3.6× vs llama.cpp 27.8). Next lever: device-resident decode (#1). See spec §W9.** Prior **FASTER DECODE (W8, 2026-07-31, `CLAIM-LAGUNA-W8-EMBED`): `LagunaEmbed` no longer converts the whole 1.23 GB embed table to f32 every token (it gathered T rows out of the whole [Vsz,H] table via `ReadF32` — ~311M host element-converts/token, the DOMINANT decode cost the W7 profile under-filed as "#5"); now gathers only the T needed rows directly (BIT-IDENTICAL — same per-element conversion, same rows). GATED on the real 3-shard UD-Q4_K_XL GGUF (GB10, `--gpu`, W6 cached, drop_caches cold, 24 tok): TOKEN-IDENTICAL to the W5/W6 golden (`22345 83 350 785 …`, coherent " Paris.") + decode 0.66 → 0.17 s/tok = 3.9× (1.5 → 5.9 tok/s; 18× → 4.7× vs llama.cpp 27.8). See `.agents/specs/laguna-s21-w7-speed-2026-07-31.md` §W8. Next: grouped-expert GEMM (=A3) then device-resident decode.** Prior **DECODE-SPEED ATTRIBUTED (W7 profile-only, 2026-07-31, `CLAIM-LAGUNA-W7-SPEED`): `nsys` of the W6 decode (real UD-Q4_K_XL GGUF, GB10) attributes the 0.66 s/tok (~1.5 tok/s vs llama.cpp 27.8 on identical bytes, ~15-18x) to HOST-ORCHESTRATION, not kernel compute — GPU active only 32.7% of the step, 67.3% host/idle; 22,115 `cudaStreamSynchronize` (~2,764/step, zero GPU overlap) from the ~1,795 per-GEMM `DrainQueue` in `LagunaForwardGgufCached` + scalar host glue; 39.4% of GPU time is `QuantizeQ8K` activation-quant (per-GEMM), weight GEMVs un-grouped at ~22% of the 240 GB/s peak (llama.cpp ~76%); no H2D/D2H (unified memory). Ranked levers (all in-tree from ds4): device-resident decode 1.5->~5-7 tok/s, grouped-expert GEMM (`MatmulBTQuantGrouped`) +1.5-2x + dedupes the activation-quant, decode CUDA-graph, tuned MMVQ; + free host cleanups (`LagunaEmbed` copies the whole 1.23 GB embed table/token, per-token RoPE-cache rebuild). Honest reachable ~13-20 tok/s, 27.8 a stretch. NO code changed. See `.agents/specs/laguna-s21-w7-speed-2026-07-31.md`. Prior RUNNABLE + FAST DECODE (W6, 2026-07-31): a per-layer K/V cache + single-token incremental decode replaces W5's O(n²) STATELESS recompute — TOKEN-IDENTICAL (byte-equal ids, md5 `754728c6…` match, == the W5 golden) and 5.05× faster per token: decode 3.33 → 0.66 s/tok on the real UD-Q4_K_XL GGUF (GB10, `--gpu`, keep-quant), same " Paris.…" text. `LagunaKvCache` (mirrors `DeepseekV4KvCache`, MLA-latent → GQA multi-head K/V; caches post-QK-RMSNorm/post-RoPE K + raw V at f32 — bit-exact since RoPE/QK-norm are position-only and attention is causal), MIXED attention per-layer: 12 GLOBAL layers grow unbounded + 36 SLIDING-WINDOW-512 layers EVICT rows beyond the 512 window (gemma2/3 `is_sliding`); `LagunaForwardGgufCached` + shared `LagunaAttention`/`LagunaFfnBlock` helpers used by BOTH forwards (identical float ops; recompute ids unchanged after refactor), `examples/laguna_gen --stateless` A/B flag. No cache bug (bit-exact first run). Next speed = grouped-expert GEMM + device-resident decode (both in-tree from ds4). See `.agents/specs/laguna-s21-w6-2026-07-31.md`. Prior RUNNABLE (W5, 2026-07-31): our engine greedy-generates COHERENT text on the REAL 3-shard UD-Q4_K_XL GGUF (GB10 keep-quant) — "The capital of France is" → " Paris. …", first token "Paris." matches the llama.cpp-Poolside reference. Multi-shard GGUF reader + keep-quant tower (`LoadLagunaFromGgufShards`) + `LagunaForwardGguf` (ds4 keep-quant Gemm/GemmRowSlice) + `examples/laguna_gen`; load 20.6s, peak 71 GiB, 3.27 s/tok stateless recompute (speed=W6).** Prior W3: **W3 REAL forward + 3 new ops landed** (`laguna_ops.cpp`: per-head softplus attn out-gate + ungrouped sigmoid-noaux router + dual per-layer RoPE cos/sin builders; `LagunaModel::Forward` now a REAL runnable host-reference composition — variable-Q-head GQA + dual RoPE + sliding-window mask + softplus gate + dense L0 / ungrouped-MoE L1..47 + untied lm_head — replacing the W1/W2 `VT_CHECK(false)` stub; CPU `-Werror` full-library build clean; `test_laguna_scaffold` **8/8·166** incl. softplus math, router selection+tie-break RED-first, dual-RoPE cos/sin bit-match, variable-Q-head shapes, forward composition on synthetic weights; `test_model_registry` 24/24). W1 oracle DECISION: vLLM native `laguna.py` in pin ⇒ config constructs; dual-oracle = vLLM-NVFP4/-FP8 (fits GB10, BF16 235 GiB does NOT) + llama.cpp-Q4_K token-exact. DEFERRED to W4 (needs 73 GB checkpoint): GGUF keep-quant tower materialization + device/paged production forward + strict dual-oracle greedy gate. ~85-90% reuse (ds4-MoE + gemma-sliding + olmo3-dual-rope + landed Q4_K keep-quant); NEW = the 3 landed host ops + name-map + variable-Q-head device runner. **W4 (2026-07-31, `CLAIM-LAGUNA-W4`, in progress):** the UD-Q4_K_XL GGUF (73.4 GiB, 3 shards) FETCHED to dgx + its metadata/tensor-map READ AUTHORITATIVELY (814 tensors, arch `laguna`, `expert_gating_func=2` sigmoid, `leading_dense_block_count=1`, `expert_weights_scale=2.5`). Three CPU-verified FIDELITY corrections the W1-W3 scaffold got wrong, each grounded in the real GGUF + llama.cpp: (1) **per-head QK-RMSNorm** (`attn_q_norm`/`attn_k_norm` F32[128]) added to params+forward — the scope MISSED it (surfaces only in the tensor map); (2) **dual-RoPE mscale** now uses llama.cpp's `yarn_attn_factor·(1+0.1·ln(factor))` off the GGUF-authoritative `factor=32`/`yarn_attn_factor=1.0` (256K-ctx build, NOT HF's factor-128/1.4852 1M-ctx scalar) — resolves the numerics-delicate residual; (3) **separate** `ffn_gate_exps`/`ffn_up_exps` (Q4_K) + `ffn_down_exps` (Q5_K) + Q8_0 shared/attn (the scaffold assumed merged gate_up). GGUF keep-quant tower materialization (`Mw`/`Sew` mirror of ds4) + keep-quant `ForwardGguf` (vt::MatmulBT/GemmRowSlice) + the real-model greedy run vs the llama.cpp-laguna same-quant oracle remain the W5 close (73 GB single-GB10, host-orchestrated) | `MODEL-TEXT-laguna-laguna-for-causal-lm` | | 🚫 | `DeepseekV3ForCausalLM` / `DeepseekV32ForCausalLM` | DeepSeek-V3 / V3.2 | HW-blocked (671B, ~642 GiB fp8 vs 119 GiB unified memory); V3.2 additionally DEP-blocked (DSA indexer) | `MODEL-TEXT-deepseek-v2-deepseek-v3-for-causal-lm` | @@ -446,6 +450,7 @@ Transformers compatibility is capability-driven and excluded from finite counts. | `MODEL-MM-voxtral-realtime-voxtral-realtime-for-conditional-generation` | `VoxtralRealtimeForConditionalGeneration` (target-pending) | **TARGET-PENDING — the architecture string is registered NOWHERE we can find.** Recipe `mistralai/Voxtral-Mini-4B-Realtime-2602` (one of the 157 recipes at `vllm-project/recipes` `86c7777a`) declares `architectures: ["VoxtralRealtimeForConditionalGeneration"]` with an inner `audio_config.model_type: "voxtral_realtime_encoder"`, but that exact string appears in none of: (a) the pinned registry `555967922`; (b) any of the ten dictionaries of `vllm/model_executor/models/registry.py` on vLLM `main`; (c) `_OMNI_MODELS` in `vllm_omni/model_executor/models/registry.py` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8`; (d) `vllm-omni` `docs/models/supported_models.md`. **Related but NOT equal, recorded so nobody re-derives it:** `VoxtralRealtimeGeneration` — a DIFFERENT string — is registered at the pin (`registry.py:585`, `main` `:603`) and already has its own row here, `MODEL-MM-voxtral-realtime-voxtral-realtime-generation`; `VoxtralForConditionalGeneration` (pin `registry.py:584`) is the audio→TEXT model; and `vllm-omni` `registry.py:305-309` registers `VoxtralTTSForConditionalGeneration` for the SEPARATE `mistralai/Voxtral-4B-TTS-2603` recipe. The most likely reading is that this checkpoint's `config.json` names a renamed or not-yet-landed spelling of the existing `VoxtralRealtimeGeneration` target, but that is a hypothesis: no anchor is asserted until one of those is shown to be what actually resolves. Tracked by [#610](https://github.com/mudler/vllm.cpp/issues/610) | conditional generation / audio in, AUDIO OUT (realtime speech-to-speech) — modality inferred from the recipe and config, NOT from an upstream implementation | unresolved until the target is located | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-bailing-mm-native-bailing-mm-native-for-conditional-generation` | `BailingMMNativeForConditionalGeneration` (target-pending) | **TARGET-PENDING — the architecture string is registered NOWHERE we can find.** Recipe `inclusionAI/Ming-omni-tts-0.5B` (one of the 157 recipes at `vllm-project/recipes` `86c7777a`) declares `architectures: ["BailingMMNativeForConditionalGeneration"]`, but that exact string appears in none of: (a) the pinned registry `555967922`; (b) any dictionary of `vllm/model_executor/models/registry.py` on vLLM `main`; (c) `_OMNI_MODELS` in `vllm_omni/model_executor/models/registry.py` @ `bbe6ccc512a404a2df8c977ea29003002f2683e8`; (d) `vllm-omni` `docs/models/supported_models.md`. **Related but NOT equal:** `vllm-omni` `docs/models/supported_models.md:75` lists that SAME checkpoint under a different architecture name, `MingTTSForConditionalGeneration` (`registry.py:250-254` → module `ming_tts/ming_tts.py`, class `MingTTSForConditionalGeneration`); and `vllm-omni` `registry.py:417-421` carries a near-miss alias `BailingMM2NativeForConditionalGeneration` — note the `2` — commented "HF repo currently ships this architecture name in config.json", pointing at module `ming_flash_omni/ming_flash_omni.py`, class `MingFlashOmniForConditionalGeneration`, for the DIFFERENT Ming-flash-omni-2.0 checkpoint. So the shape of the gap is a missing config-string alias upstream, but that is an observation, not an anchor, and none is asserted here. Tracked by [#610](https://github.com/mudler/vllm.cpp/issues/610) | conditional generation / text+image+audio in, AUDIO OUT (TTS) — modality from the recipe and config, NOT from a resolved upstream target | unresolved until the target is located | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-DIFFUSION-ltx-2-5-ltx2-video-transformer-3d-model` | `LTX2VideoTransformer3DModel` | **BEYOND-PIN AND OUT-OF-REPO** — not in `555967922` (LTX-2.5 released 2026-08) and not in the vLLM repository at all. Architecture reference is Lightricks' own `LTX-2` (`packages/ltx-core/src/ltx_core/`: `model/transformer/{transformer,model,attention,rope,model_configurator}.py`, `model/audio_vae/`, `components/`, `guidance/`). vLLM-Omni carries an `ltx2` module but its recipes stop at 2.3 (`vllm_omni/diffusion/models/ltx2/ltx2_recipes.py:162-166`); 2.5 is upstream-OPEN at [vllm-omni#6066](https://github.com/vllm-project/vllm-omni/issues/6066) and [#4985](https://github.com/vllm-project/vllm-omni/issues/4985). Serving oracle reaches 2.5 through the GENERIC `DiffusersAdapterPipeline` (`pipeline_diffusers_adapter.py:116`) against `Lightricks/LTX-2.5-Diffusers`. | diffusion generation / video + audio (text/image/video in, video+audio out) | flow-matching denoise loop; TWO coupled modality streams with audio<->video cross-attention; per-head gated attention; AdaLN-single with 9-vector per-block tables; timestep-INDEPENDENT prompt K/V (cacheable once per request); split/interleaved 3D RoPE; gelu-approximate FFN; Gemma-4 12B text encoder with dual caption projections (4096 video / 2048 audio); Conv video VAE + DiffVAE; audio VAE + vocoder; latent spatial/temporal x2 upsamplers; duration head; distilled two-stage recipe | [ltx-2.5](specs/ltx-2-5.md) | `SPIKE` | **L0 LANDED 2026-08-11 (#435).** Spec committed BEFORE implementation per AGENTS.md. Geometry measured from the ungated `vonkaiser/LTX-2.5-FP8-NVFP4` FP8 checkpoint header by range request — the same no-payload technique used for H3's manifests: 21.00B params, 48 blocks, 1775 F8_E4M3 tensors each with a per-tensor F32 `weight_scale`, biases/norms BF16. Cross-modal projection shapes are ASYMMETRIC and are a named test trap: `audio_to_video_attn.to_q` is [2048, 4096] while `to_k`/`to_v` are [2048, 2048] and `to_out` is [4096, 2048], so a transposition still type-checks against a square assumption. **CHECKPOINT ACCESS (verified against the HF API 2026-08-11):** `Lightricks/LTX-2.5` is `gated: auto` (accepting the license opens it; holds the first-party NVFP4 DiT at 18.72 GB), `Lightricks/LTX-2.5-Diffusers` is RESTRICTED behind manual approval (needed for the binding oracle), and `vonkaiser/LTX-2.5-FP8-NVFP4` is UNGATED and unblocks L1-L2 today. All artifacts land under `$CHECKPOINT_ROOT` (`/mnt/nas_share/checkpoints`) so dgx.casa and the cluster nodes share one copy. **PHASES (one PR, developer-directed):** L1 `vllm::multimodal::VideoEngine` interface + checkpoint-detected registry with MiniMax-H3 moved behind it UNCHANGED (gated on frames+WAV byte-identical to the pre-refactor fold fixture) and ABI **v13 by ADDING fields only**; L2 DiT forward; L3 Gemma-4 TE reusing `gemma4.cpp`; L4 Conv video VAE + audio VAE + vocoder; L5 pipeline/recipes/upsampler/duration head; L6 NVFP4 arms + GB10 load-time residency; L7 e2e on dgx.casa under `flock $HOME/gpu.lock`. **OPEN, same as H3:** there is no vllm-omni parity PIN — `.agents/upstream-sync.md` covers the vLLM repo only, so each golden records the vllm-omni SHA inline. | `CLAIM-LTX25-L0-L7` | +| `MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation` | `MiniMaxMusic3ForConditionalGeneration` | **BEYOND-PIN AND OUT-OF-REPO** — not in `555967922` and not in the vLLM repository at all; absent from `vllm-omni` too. Its reference implementation is the OPEN `diffusers` PR [#14456](https://github.com/huggingface/diffusers/pull/14456) head `c6da9936` (`src/diffusers/models/transformers/transformer_minimax_music3.py`, `.../minimax_music3_rvq_depth_decoder.py`, `src/diffusers/models/autoencoders/minimax_music3_vocoder.py`, `src/diffusers/models/condition_embedders/condition_embedder_minimax_music3.py`, `src/diffusers/modular_pipelines/minimax_music3/`), with SGLang-Omni `748a0b43` (`sglang_omni/models/minimax_music3/`) serving the NATIVE layout as the e2e cross-check. **The first row to exercise the fallback-oracle rule (#647)** | music generation / text -> audio (lyrics + structured description in, multi-minute stereo song out) | Qwen3 dense forward + paged KV (LANDED); flow-matching denoise loop (H3/LTX-2.5 precedent); DAC-style audio VAE decode + WAV writer (H3/LTX-2 precedent); NEW: 8-codebook RVQ frame path, 4-layer depth decoder, learned 8-layer condition mix, snake activations with weight-norm folding, and an LLM->diffusion handoff on CONTINUOUS hidden states rather than discrete tokens | [minimax-music3](specs/minimax-music3.md) | `SPIKE` | **W0 spec committed 2026-08-13 (#672).** Spec committed BEFORE implementation per AGENTS.md. Geometry MEASURED, not inferred: every component `config.json` read, plus safetensors headers by HTTP range request — `transformer` 231 tensors/1.240B per shard **all F32** (so the card's 2.4B is right and 9.73 GB is fp32 storage, NOT a 4.9B bf16 model), `rvq_depth_decoder` 47/0.646B BF16, `vocoder` 121/0.054B F32 with `weight_g`/`weight_v` weight-norm, `condition_encoder` **4 tensors** (`layer_scale`, `layer_weight_logits`, `proj`) which makes it a learned weighted mix over 8 LLM layers rather than an encoder tower. `language_model` is our LANDED `Qwen3ForCausalLM` architecture exactly (36L/4096/32-8/head_dim 128) at `vocab_size 200000`. The repo ships TWO packagings of ONE set of weights (native `qwen_7B/`+`flowmatching_vae.pth`+`dav.pth` vs the diffusers six); `scripts/convert_minimax_music3_to_diffusers.py@c6da9936` `:29-38,47-189` proves the conversion is a pure re-layout, so the diffusers arm is the port target at **~28.5 GB resident** (no pickle, no `trust_remote_code`) and SGLang-Omni stays a valid e2e cross-check. **Sample rate RESOLVED 2026-08-13** (spec §1.1): not a contradiction but a stage boundary. The vocoder natively emits 44100 stereo (86.133 Hz latent frames x 512 upsampling, derived from `minimax_music3_vocoder.py:84,92-95` and confirmed by `dav.py:94,115`); diffusers returns that unresampled, while SGLang-Omni's SERVER resamples 44100 -> 32000 on delivery (`constants.py:18-19`, `acoustic.py:55-58,422-431`). Goldens are captured at 44100 stereo, the native resample-free rate; 32 kHz is a downstream delivery transform gated separately, and NOT free — reproducing its bytes needs torchaudio's default sinc filter, and a latent-parity check sits upstream of that call and cannot see it | `CLAIM-MODEL-MUSIC3-W0` | | `MODEL-MM-moss-transcribe-diarize-moss-transcribe-diarize-for-conditional-generation` | `MossTranscribeDiarizeForConditionalGeneration` (v0.25.0 target-pending) | v0.25.0 target `registry.py:450-453`; `vllm/model_executor/models/moss_transcribe_diarize.py::MossTranscribeDiarizeForConditionalGeneration` @ `702f481` | conditional generation / audio | MM processor; Whisper encoder; VQ adaptor; Qwen3 decoder; speech-to-text/diarization frontend | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-lightonocr-light-on-ocrfor-conditional-generation` | `LightOnOCRForConditionalGeneration` | `registry.py:450-453`; `vllm/model_executor/models/lightonocr.py::LightOnOCRForConditionalGeneration` | conditional generation / image | MM processor; encoder/merge; vision encoder | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-lfm2-vl-lfm2-vlfor-conditional-generation` | `Lfm2VlForConditionalGeneration` | `registry.py:454`; `vllm/model_executor/models/lfm2_vl.py::Lfm2VLForConditionalGeneration` | conditional generation / image | MM processor; encoder/merge; Mamba/SSM state; vision encoder; video path | ☐ required | `INVENTORIED` | none | unassigned | diff --git a/.agents/oracles/diffusers.md b/.agents/oracles/diffusers.md index 995231feb..b3bf9d560 100644 --- a/.agents/oracles/diffusers.md +++ b/.agents/oracles/diffusers.md @@ -12,16 +12,28 @@ Prefer vLLM-Omni wherever it implements the pipeline; reach here when it does not, and record which of the two a given stage was gated against, because they do not always agree on scheduler details. +**The pin is an OPEN pull request, deliberately.** MiniMax-Music3's diffusers +integration is PR [#14456](https://github.com/huggingface/diffusers/pull/14456) +(`Add MiniMax Music 3`), branch `huggingface:minimax-music3-integration`, which is +not merged to `main`. A branch name is not a pin — it can be rebased or +force-pushed under us — so the recorded revision is the exact head commit. If the +PR merges, moving to the merge commit is a pin advance with its own +reconciliation, not a silent follow. The same shape applies to any future +diffusers-only model: the H3 integration landed the same way (#14355, refactored +by #14371). + **Not gateable yet:** nothing in this repository has executed `diffusers` as an -oracle, so there is no measured revision to pin. #647 owes both. +oracle. #647 owes the measurement; [`../specs/minimax-music3.md`](../specs/minimax-music3.md) +W0 is where it gets taken, and this record flips to `gateable = yes` when the +oracle demonstrably builds and runs the model. ```oracle-pin id = diffusers role = secondary upstream = https://github.com/huggingface/diffusers -scope = schedulers, VAEs and diffusion pipelines vLLM-Omni does not implement -pin = UNPINNED -pin_label = none +scope = schedulers, VAEs and diffusion pipelines vLLM-Omni does not implement, including models whose only reference implementation is an unmerged diffusers PR +pin = c6da9936e4bda83107943a16eb8682e9a37d8527 +pin_label = PR #14456 head (minimax-music3-integration) pinned_on = 2026-08-13 gateable = no evidence = #647 diff --git a/.agents/oracles/sglang-omni.md b/.agents/oracles/sglang-omni.md new file mode 100644 index 000000000..8e7c14507 --- /dev/null +++ b/.agents/oracles/sglang-omni.md @@ -0,0 +1,44 @@ +# SGLang-Omni — the omni/speech/TTS serving runtime + +A **third** repository, `sgl-project/sglang-omni`, distinct from both +`sgl-project/sglang` ([`sglang.md`](sglang.md)) and from vLLM-Omni. It owns the +multi-stage pipeline topology for omni, speech and TTS models and composes with +SGLang for the autoregressive scheduling underneath. It gets its own record +because it has its own cadence and its own model registry: a model can be in +SGLang-Omni and in neither of the other two. + +**Why it exists here.** `MiniMaxAI/MiniMax-Music3` is served by SGLang-Omni +(`sglang_omni/models/minimax_music3/`) and is registered nowhere in the pinned +vLLM. It is the cross-check for the e2e output and the speed axis of +[`../specs/minimax-music3.md`](../specs/minimax-music3.md), where `diffusers` is +the per-component primary. + +**It serves the NATIVE checkpoint layout, not the diffusers one.** +`sglang_omni/models/minimax_music3/checkpoint.py:35-56` resolves exactly +`qwen_7B/qwen_7B`, `flowmatching_vae.pth` and `dav.pth`, and pulls the RVQ depth +decoder out of the Qwen shards by the `model.audio_decoder.` / +`model.audio_extra_embedding` prefixes. That is a different packaging of the same +weights (the diffusers conversion script is a pure re-layout), so a comparison +against it is meaningful — but only at the whole-pipeline level, since its tensor +names do not line up component-by-component with ours. + +**Speed comparisons use its production defaults**, which its own README lists as +on without further flags: backbone decode CUDA graph, RVQ depth CUDA graph, +compiled DIT blocks, compiled DAV decoder, and batched seeded sampling. Measuring +against it with those disabled would be the dishonest-denominator mistake the +benchmark protocol forbids. + +**Not gateable yet:** cloned and read, never executed here. It requires CUDA and +its own pinned `sglang.multimodal_gen` / `flashinfer-python==0.6.14` stack. + +```oracle-pin +id = sglang-omni +role = secondary +upstream = https://github.com/sgl-project/sglang-omni +scope = omni, speech, TTS and music models served by the SGLang-Omni pipeline runtime and absent from vLLM; e2e output and the speed axis, not per-component parity +pin = 748a0b437e4a8faad44d7bbfd5a0ae55d1fef830 +pin_label = main @ 2026-08-13 +pinned_on = 2026-08-13 +gateable = no +evidence = #672 +``` diff --git a/.agents/oracles/sglang.md b/.agents/oracles/sglang.md index c8d64c195..5ba964d42 100644 --- a/.agents/oracles/sglang.md +++ b/.agents/oracles/sglang.md @@ -1,4 +1,4 @@ -# SGLang, including SGLang-Omni +# SGLang — cross-check, performance floor, and vLLM-absent model paths Two distinct roles, and one forbidden one — the methodology is [`../specs/sglang-parity-oracle.md`](../specs/sglang-parity-oracle.md) and the @@ -10,9 +10,9 @@ enumerated surface is [`../sglang-matrix.md`](../sglang-matrix.md): - **Performance floor.** Wherever SGLang beats vLLM on an equivalent workload, SGLang is the binding floor. - **Model paths vLLM does not implement at all.** This is the case the fallback - rule was written for, and today it is SGLang-Omni: `MiniMax-Music3` - (`minimax_music3`) is served by SGLang-Omni and registered nowhere in vLLM. - Here SGLang is not a cross-check, it is the only available reference. + rule was written for. Where that path is served by the SGLang-Omni pipeline + runtime rather than SGLang proper — `MiniMax-Music3` is the first — the binding + record is [`sglang-omni.md`](sglang-omni.md), not this one. - **Forbidden:** porting SGLang's data structures as a second, incompatible abstraction. A SGLang-distinct behavior is an opt-in over our vLLM-derived design, never a fork of the engine. @@ -25,9 +25,10 @@ separately pins tag `v0.5.13` / `28b095c01005d4a3a2a5b637b7d028b07fba31b2` and its runtime image, and that pin is never silently substituted into the other's evidence. -**SGLang-Omni is a third repository and has no pin of its own yet.** It is -covered by this record's scope, not by this record's revision; a Music3-class -lane that needs it owes the pin before it can claim a gate. +**SGLang-Omni is a third repository and has its own record**, since #672: +[`sglang-omni.md`](sglang-omni.md). This record covers SGLang proper. A model +present in one and absent from the other is the normal case, which is why the two +are not folded together. **Not gateable yet:** no SGLang run has been recorded on this project's hardware. The parity-oracle spec is explicitly a read-only scoping spike ("no engine code, diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 29ab578da..fd0b33e06 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -194,6 +194,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#660](https://github.com/mudler/vllm.cpp/issues/660) | — | `check-device-leakage`'s `kcuda` bucket is the token grep `\bkCUDA\b`, so `minimax_h3_video.cpp:221-226`'s `static_cast(device)` hardcodes CUDA as enum value 1 and counts as 0. Gate strength plus an enum-ordering hazard; the H3 video lane should ask the same seam `ltx2_video.cpp` now does (found while reviewing #553) | bug | | [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | **W0 (record backfill) landed 2026-08-13; row `INVENTORIED` → `PARTIAL`, 41 shipped names / 37 families recorded.** Re-derived from the two REGISTRIES rather than from recipe usage: **five** `--tool-call-parser` names are upstream-only at the pin — `openai`, `inkling`, `minimax_m3` (W1, recipe demand) and `cohere_command3`, `cohere_command4` (W2, ZERO recipe uses, so usage-driven audits miss them). **Only `inkling` is PORTABLE from vLLM source**: `minimax_m3` is backed by the Rust crate, `openai` is a declared Harmony stub that raises on both methods, and both Cohere names are shims over the out-of-tree `cohere_melody` package, so W1/W2 each owe a recorded decision before code rather than a text port. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry and arrive with the pin advance, not here. W3 ports upstream's shared `ToolParserTestConfig` harness. The earlier "six missing" framing was usage-derived: it listed `nemotron_json` as portable (it is not registered at the pin) and missed both Cohere entries | feature | | [#647](https://github.com/mudler/vllm.cpp/issues/647) | — | Oracle policy had no fallback and no pin concept: five upstreams beyond vLLM are already compared against (vLLM-Omni, SGLang, llama.cpp, `transformers`, tt-forge) with their pins scattered across individual specs or absent entirely. AGENTS.md now admits a named secondary oracle where vLLM implements nothing, `.agents/oracles/.md` pins each one file-per-oracle, and `check-oracle-pins.py` enforces both directions. The gateability debts for `sglang`, `diffusers` and `tt-forge` stay open on this issue | feature | +| [#672](https://github.com/mudler/vllm.cpp/issues/672) | `MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation` | MiniMax-Music3: port the text-to-music lane — ~8.6B Qwen3 global LLM (our LANDED architecture at vocab 200000) + 8-codebook RVQ depth decoder + 2.4B fp32 flow-matching DiT + DAC Flow-VAE. Our first music-generating model, and the first row whose primary oracle is not vLLM: absent from the pin, from vLLM `main` and from `vllm-omni`, so it gates against the OPEN diffusers PR #14456 with SGLang-Omni as the e2e cross-check, under the fallback rule added in #647 | feature | | [#649](https://github.com/mudler/vllm.cpp/issues/649) | `TOOLS-CALLING-CORE` | That row's prose still records `tool_parser_names()` 40 / `reasoning_parser_names()` 7; both enumerations have grown since 2026-07-24 and are now **41** (`tool_parsers/abstract.cpp:269`) and **12** (`reasoning_parsers/abstract.cpp:72`). Code and tests are correct — `test_detect.cpp:221` already pins 41 — only the record drifted. Halves belong to two other rows (#608, #605), so it is filed rather than repaired inside #643 (found while implementing #643's review findings) | bug | ## Top-level portfolio diff --git a/.agents/specs/minimax-music3.md b/.agents/specs/minimax-music3.md new file mode 100644 index 000000000..63bff1170 --- /dev/null +++ b/.agents/specs/minimax-music3.md @@ -0,0 +1,298 @@ +# MiniMax-Music3 — text-to-music, and our first music-generating model + +**Rows:** `MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation` +(model-matrix). +**Issue:** [#672](https://github.com/mudler/vllm.cpp/issues/672). +**Claim:** `CLAIM-MODEL-MUSIC3-W0`. +**Checkpoint:** `MiniMaxAI/MiniMax-Music3`, 57.4 GB total — but the arm we port is +**~28.5 GB** (see §2). +**Upstream:** `diffusers` PR +[#14456](https://github.com/huggingface/diffusers/pull/14456), head +`c6da9936e4bda83107943a16eb8682e9a37d8527` — **OPEN, not merged**. +**Cross-check:** SGLang-Omni `748a0b437e4a8faad44d7bbfd5a0ae55d1fef830`. +**Status:** W0 — spec committed, no engine code. +**Developer directive (2026-08-13):** "land minimax music 3 support complete, to +vllm.cpp, wired to the ABI and to the example http server, merge to main, tested +e2e." That fixes W6's shape (the ABI surface and the example server are in scope, +not optional follow-ups) and records merge authority for this campaign. Merge is +still gated on PROVED: fresh review PASS, the operator's own gate rerun, and no +red bought by weakening a detector. + +--- + +## 0. Honesty statement — what is and is not claimed + +Nothing has been ported. Nothing has been measured on hardware. This spec records +what was **read from the checkpoint and from upstream source**, and separates that +from what is still assumed. + +**Measured** (safetensors headers by HTTP range request, and each component's +`config.json`): every geometry and dtype in §1. **Read** (upstream source at the +pinned SHAs): the component decomposition, the native↔diffusers relationship, and +the dtype policy. **Not established:** that the oracle runs here at all — no +diffusers execution of this model has happened on this project's hardware, which +is why `.agents/oracles/diffusers.md` still records `gateable = no`. W0 is not +complete until it does. + +**This model has no token-exact gate on its generative half.** Like MiniMax-H3, the +acoustic path is a flow-matching denoise loop with no logits and no sampler, so the +SACRED near-tie methodology does not apply to it. What *is* token-exact is the +global LLM half, which emits discrete RVQ codes. §5 states which gate binds where; +conflating the two is the failure mode this section exists to prevent. + +--- + +## 1. What the model is, measured + +Lyrics (with `[Verse]` / `[Chorus]` section tags) plus a structured music +description in; a multi-minute stereo song out. Hierarchically: a global LLM +predicts a semantic frame sequence, a small depth decoder expands each frame into +eight RVQ codebooks, a flow-matching DiT synthesises continuous latents, and a +DAC-style Flow-VAE decodes them to a waveform. + +| Component | Class | Geometry | Params | dtype on disk | +|---|---|---|---|---| +| `language_model` | `Qwen3ForCausalLM` (transformers) | 36L, hidden 4096, 32 heads / 8 KV, head_dim 128, ffn 12288, **vocab 200000**, rope_theta 1e6, max_pos 10240, `tie_word_embeddings: false` | ~8.6B | BF16 | +| `condition_encoder` | `MiniMaxMusic3ConditionEncoder` | **4 tensors**: `layer_scale`, `layer_weight_logits`, `proj.{weight,bias}`; `num_condition_layers: 8`, `condition_hidden_dim: 4096`, `out_dim: 2048` | 0.025B | F32 | +| `rvq_depth_decoder` | `MiniMaxMusic3RVQDepthDecoder` | 4L, hidden 4096, 16 heads, ffn 6144, `num_codebooks: 8`, `audio_vocab_size: 1024`, `max_position_embeddings: 16` | 0.646B | BF16 | +| `transformer` | `MiniMaxMusic3Transformer1DModel` | 36L, 32 heads × `attention_head_dim: 64` (hidden 2048), `ff_inner_dim: 8192`, `in_channels: 128`, `condition_dim: 2048`, `fourier_embedding_dim: 256`, **`rotary_dim: 32`** | 2.4B | **F32** | +| `scheduler` | `FlowMatchEulerDiscreteScheduler` | `invert_sigmas: true`, `num_train_timesteps: 1`, `shift: 1.0`, `time_shift_type: exponential`, no dynamic shifting | — | — | +| `vocoder` | `MiniMaxMusic3Vocoder` | DAC-style, `latent_channels: 128`, `upsampling_ratios: [8,8,4,2]` (hop 512), decoder hidden 1536 / in 1024, snake activations, `weight_g`/`weight_v` weight-norm | 0.054B | F32 | + +Header measurement: `transformer` shard 1 of 2 carries 231 tensors / 1.240B +params, all `F32` — so the model card's "2.4B" is correct and the 9.73 GB on disk +is **fp32 storage, not a 4.9B bf16 model**. `vocoder` is 121 tensors / 0.054B with +`weight_g`+`weight_v` pairs, so weight-norm must be folded at load or reproduced. +`condition_encoder` has only four tensors, which is the finding that corrects the +obvious reading of its name: it is a **learned weighted mix over 8 LLM hidden +layers**, not an encoder tower. + +**`language_model` is our existing `Qwen3ForCausalLM` architecture exactly**, +retrained on a 200 000-entry music vocabulary. Vocabulary size is a config value, +not an architecture change, and `MODEL-TEXT-qwen3-qwen3-for-causal-lm` is ✅ +(token-exact 16/16). This is the single largest brick and it is already built. + +### 1.1 Sample rate — RESOLVED 2026-08-13: a stage boundary, not a contradiction + +The model card and SGLang-Omni's README say **32 kHz** stereo; every config says +**44100**. Both are right, about different points in the pipeline. Read from +source at the pinned SHAs: + +**The vocoder natively emits 44100 Hz, 2 channels**, and that is derivable rather +than merely declared. The condition encoder's `output_sampling_rate: 44100` / +`output_hop_length: 512` set a latent frame rate of 44100/512 = **86.133 Hz** +(`condition_embedder_minimax_music3.py:40-41`; `modular_pipeline.py:48-53` +documents `latent_hop_length` as "waveform samples per Flow-VAE latent frame"). +The decoder applies one `ConvTranspose1d` per `upsampling_ratios` entry +(`minimax_music3_vocoder.py:84,92-95`), so 8·8·4·2 = **512×**, and +86.133 × 512 = 44100. The declared `sampling_rate: 44100` +(`minimax_music3_vocoder.py:85`) matches the convolution stack rather than being a +stale annotation. SGLang-Omni's independent implementation agrees exactly +(`dav.py:94,115`). Stereo comes from folding the 128 latent channels into two +64-channel streams (`minimax_music3_vocoder.py:110,115`; `dav.py:140-142`). + +**diffusers returns 44.1 kHz with no resample** (`modular_pipeline.py:32-36`; +`decoders.py:84-92`, whose block description says it "stitches the windows into +the final stereo waveform at 44.1 kHz"). **SGLang-Omni's server resamples +44100 → 32000 on the way out** (`constants.py:18-19` `DAV_SAMPLE_RATE` / +`OUTPUT_SAMPLE_RATE`; `acoustic.py:55-58,422-431`). diffusers' own docs state the +split: the pipeline "returns the vocoder's native 44.1 kHz stereo output. The +reference server additionally resamples to 32 kHz." + +The 24000 / 960 pair in `condition_encoder/config.json` is the AR stage's 25 Hz +frame rate and is unrelated to output. + +**Decision: goldens are captured at 44100 stereo** — the model's native generative +rate, resample-free, and what the primary oracle hands the caller. The 32 kHz form +is a **downstream delivery transform**, gated separately if and when SGLang-Omni +byte parity is wanted. That is not a free conversion: `acoustic.py:58` passes no +`lowpass_filter_width`, `rolloff` or `resampling_method`, so reproducing its bytes +means reproducing torchaudio's default sinc filter, not merely converting +44.1 → 32 by any correct method. **A latent-tensor parity check sits entirely +upstream of that call and cannot see the difference** — which is why the rate is +fixed here, before the first waveform golden, rather than discovered later. + +--- + +## 2. Two packagings, one set of weights + +The repository ships the model twice, which is why it is 57.4 GB: + +**Native arm** — `qwen_7B/qwen_7B/` (`AbabForCausalLM`, `model_type: mixtral`, +`num_local_experts: 1`, `auto_map` → remote `modeling_abab.py`), +`flowmatching_vae.pth` (the DiT plus the condition projection), `dav.pth` (the DAC +Flow-VAE decoder). The RVQ depth decoder and the audio embedding live *inside* the +Qwen shards, as `model.audio_decoder.*` and `model.audio_extra_embedding`. + +**Diffusers arm** — the six components of §1, safetensors only. + +**SGLang-Omni serves the native arm**, exclusively: +`sglang_omni/models/minimax_music3/checkpoint.py:35-56` resolves exactly +`qwen_7B/qwen_7B`, `flowmatching_vae.pth` and `dav.pth`, and `load_audio_state` +(`:84-102`) selects the `model.audio_decoder.` / `model.audio_extra_embedding` +prefixes out of the Qwen shard index. + +**The two are the same weights.** +`scripts/convert_minimax_music3_to_diffusers.py` at the pinned diffusers SHA loads +those three native artefacts (`:29-38`) and renames tensors into the diffusers +modules — `convert_transformer` `:47`, `convert_condition_encoder` `:86`, +`convert_vocoder` `:99`, `convert_rvq_depth_decoder` `:131`, +`convert_language_model` `:170`, which is the Qwen state *minus* the two audio +prefixes (`:189`). No retraining, no fusion, no numerical step: a re-layout. + +**Decision: port the diffusers arm.** ~28.5 GB resident (17.17 + 9.73 + 1.29 + +0.22 + 0.10), safetensors only, no `torch.load` pickle path, no +`trust_remote_code`, and every component has an upstream class to gate against +one at a time. Because the conversion is a re-layout, SGLang-Omni remains a valid +**e2e and speed** cross-check rather than an incomparable second model — but that +claim is verified in W1 by comparing converted tensors against native ones, not +assumed from reading the script. + +**The native arm is explicitly out of scope for loading**, and a checkpoint in +that layout is **refused by name** with a message saying so. It is not silently +mis-loaded, and it is recorded as owed rather than discovered later. + +### 2.1 The dtype policy is upstream's, and it is deliberate + +`convert_minimax_music3_to_diffusers.py:267` defaults `--dtype float32`; the +transformer, condition encoder and vocoder take that dtype (`:208-211`) while the +RVQ depth decoder is forced to bf16 regardless (`:214`). That matches the measured +headers exactly, and SGLang-Omni's README states both of its placements "run the +acoustic stage in FP32". + +So fp32 on the DiT and the vocoder is **upstream's resolved choice, not a +too-wide accident** — the case AGENTS.md warns about (a token gate cannot see a +dtype that is too wide) does not apply here, because the oracle is fp32 too. Each +fp32 buffer on this path still carries the one-line reason the policy requires, +naming this section. Any future narrowing to bf16 is a measured change with its +own evidence, never a silent default. + +--- + +## 3. Oracles + +Per AGENTS.md §"When vLLM has no implementation": **`minimax_music3` is absent +from the pinned vLLM**. There are no source files under `vllm/` and the registry +carries only the MiniMax M2/M3 *text* architectures. This is the first row to +exercise the fallback rule. + +| Role | Oracle | Pin | Answers | +|---|---|---|---| +| primary | `diffusers` | PR #14456 head `c6da9936` | per-component correctness, the scheduler, the conversion mapping | +| cross-check | `sglang-omni` | `748a0b43` | e2e output and the speed axis | +| supporting | `transformers` | 5.14.1 | the `Qwen3ForCausalLM` half | + +**The primary oracle is an unmerged PR branch**, so the pin is the exact head SHA +and not a branch name: `huggingface:minimax-music3-integration` can be rebased or +force-pushed under us, and a comparison against "whatever the branch was that day" +is not reproducible. If the PR merges, advancing to the merge commit is a pin +advance with its own reconciliation, not a silent follow. + +Both pins go into `.agents/oracles/` in W0. SGLang-Omni gets its **own record** +rather than riding on `sglang.md`: it is a third repository with its own cadence, +and this row binds to it directly. + +--- + +## 4. What we already own + +| Need | Have | Anchor | +|---|---|---| +| Qwen3 dense forward, paged KV, sampling | ✅ token-exact 16/16 | `MODEL-TEXT-qwen3-qwen3-for-causal-lm` | +| flow-matching denoise loop | H3: fixed-step loop, DiT forwarded once per step | [minimax-h3.md](minimax-h3.md) | +| audio VAE decode + WAV writing | H3 audio VAE, LTX-2 audio VAE, `minimax_h3_wav.cpp` | `src/vllm/model_executor/models/` | +| diffusion request planning / pipeline shape | H3 planner + pipeline, LTX-2.5 pipeline | `minimax_h3_planner.cpp`, `ltx2_pipeline.cpp` | +| quantized arms on a diffusion model | H3 GGUF + NVFP4 arms | [minimax-h3.md](minimax-h3.md) §0 | + +Genuinely new: the eight-codebook RVQ frame path, the depth decoder, the learned +8-layer condition mix, snake-activated DAC decoding with weight-norm, and the +LLM→diffusion handoff on *continuous hidden states* rather than discrete tokens. + +--- + +## 5. Gates + +**LLM half — token-exact.** The global LLM and the depth decoder emit discrete RVQ +codes. Greedy decode of the code sequence is compared against the oracle +token-for-token on a fixed prompt. This is a real token gate and it binds. + +**Acoustic half — per-stage tensor parity.** No logits, no sampler, so no token +gate exists to have. Each stage is compared against the oracle's own output for +the same input at a fixed seed: condition mix, DiT output per step, VAE latents, +waveform. Following H3, the exact correctness gate runs upstream at **reduced +dimensions on CPU**, which is available today and does not depend on the 57 GB +checkpoint fitting anywhere. + +**A correlation coefficient is not a gate on this path.** Pearson is +scale-invariant, so a uniformly scaled latent passes it while sounding wrong; +bounds are on absolute and relative error with a stated tolerance, per component. + +**Speed** is measured against SGLang-Omni in its production configuration — +its documented defaults are backbone decode CUDA graph, RVQ depth CUDA graph, +compiled DIT blocks, compiled DAV decoder and batched seeded sampling. Comparing +against it with those off would be a dishonest denominator. + +--- + +## 6. Phases + +Each phase is dispatched to a **fresh implementer** from this spec, reviewed by a +**fresh reviewer** who mutates the claimed guarantees, and its gate is rerun by +the operator. Phases are separately claimable except where noted. + +| Phase | Scope | Done when | +|---|---|---| +| **W0** | This spec; both oracle records pinned; §1.1 sample rate settled from source (**DONE**); **stand the diffusers oracle up and prove it builds and runs** | oracle executes the model and `diffusers.md` flips to `gateable = yes` with a path as evidence | +| **W1** | Modular loader: the six-component layout, weight-norm folding, the fp32/bf16 policy of §2.1, native-arm refusal by name | every component loads with shapes asserted against §1; converted-vs-native tensor equality checked, not assumed | +| **W2** | Global LLM on our landed Qwen3 path at vocab 200 000 | hidden-state parity vs `transformers`, then token-exact RVQ code parity vs the oracle | +| **W3** | Condition mix (8-layer weighted) + RVQ depth decoder, 8 codebooks | per-stage tensor parity; the depth decoder's 16-position window exercised at its boundary | +| **W4** | Flow-matching DiT + `FlowMatchEulerDiscreteScheduler` with `invert_sigmas` | per-step latent parity against the oracle at a fixed seed | +| **W5** | Vocoder: snake activations, weight-norm, `[8,8,4,2]` upsampling, WAV at **44100 stereo** (§1.1) | waveform parity within a stated absolute tolerance | +| **W6** | e2e through the shared seams; **`include/vllm.h` surface**; **the example HTTP server as a thin ABI client**, never including internal headers | a song generates end to end from an HTTP request; SGLang-Omni cross-check; speed axis recorded with values and ratios | +| **W7** | Quantized arms — GGUF k-quants are a standing requirement, not a per-model choice | each arm gated, or refused by name and recorded as owed | + +**W0 blocks everything.** Until the oracle demonstrably runs, no phase can produce +evidence, and an implementer told to "gate against diffusers" would have nothing +to gate against. + +--- + +## 7. Risks + +**The oracle is an open PR.** It may be rebased, refactored in review, or renamed +before merge — the H3 integration had exactly that follow-up (#14371 refactoring +#14355). Pinning the head SHA makes us reproducible but not immune: a merged +version that renames tensors invalidates the W1 mapping. Re-check at merge. + +**fp32 on the acoustic path is 2.4B + 0.054B of fp32 weights and fp32 compute.** +That is upstream's choice and we mirror it, but it sets the speed baseline in a +regime this project has mostly not optimised for, and the quantized arms in W7 are +where that becomes interesting rather than a footnote. + +**The 5 000-token prompt and 9 000-frame ceilings** are documented model limits. +They are context limits on our side too and must be enforced, not discovered. + +**Non-streaming only, upstream.** Refuse a streaming request by name rather than +buffering silently and calling it streaming. + +--- + +## 8. Stop conditions + +Stop and report `NEEDS_DECISION` rather than proceeding if: the diffusers PR is +closed unmerged or force-pushed to an incompatible tree; or the converted-vs-native +tensor check in W1 finds the two packagings are *not* the same weights, which +invalidates the SGLang-Omni cross-check and this spec's §2 decision. + +Stop and report `NEEDS_CONTEXT` if the checkpoint cannot be fetched to the box the +gate runs on, or if a component's upstream class has no readable definition at the +pinned SHA. + +--- + +## Now + +W0 — spec committed, no engine code. Next: pin both oracles in `.agents/oracles/`, +stand the diffusers oracle up far enough to prove it builds and runs this model, +and resolve the sample-rate contradiction in §1.1 from source. W1 is not +dispatched until W0's oracle is gateable. diff --git a/AGENTS.md b/AGENTS.md index dbf443930..edcf53b0d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -176,7 +176,8 @@ pin: | vLLM-Omni | `vllm-omni` | diffusion, TTS and the omni-only architectures vLLM proper never registers | | HuggingFace `transformers` | `transformers` | a model, processor or tokenizer's own reference implementation — the source vLLM itself mirrors | | `diffusers` | `diffusers` | schedulers, VAEs and diffusion pipelines | -| SGLang, including SGLang-Omni | `sglang` | a model or serving path SGLang implements and vLLM does not | +| SGLang | `sglang` | a model or serving path SGLang implements and vLLM does not | +| SGLang-Omni | `sglang-omni` | omni, speech, TTS and music models served by SGLang's pipeline runtime — a third repository, not SGLang | | llama.cpp | `llama-cpp` | CPU and GGUF k-quant floors | | Tenstorrent tt-forge | `tt-forge` | Tenstorrent hardware, which vLLM has no backend for at all | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index d835bbd30..26317660c 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -162,6 +162,7 @@ in `ltx2_text_encoder.cpp` is the call that would have to change. | Whisper audio encoder | openai/whisper-small; whisper-large-v3 (Voxtral cfg) | encoder tower 77/77; large-v3 tower 203/203 | pending | | MiniMax-H3 DiT (`MiniMaxH3DiTModel`, vllm-omni lane) | MiniMax-H3 (33.1B video+audio) | portable 79/79; all three modalities COHERENT on Q4_K_M (§8.20); PRUNED ckpts run, Q8_0 seam 0.9941 (§8.21); ref2va grid was NVFP4 quant error, §8.9 REFUTED; GGUF/NVFP4/bf16 shards stream | FP4/Marlin landed; speed pending; no bf16 render yet. Render from the Q4_K_M GGUF, not the NVFP4 arm. Krea 2 text-to-image (roadmap C11) is scoped to reuse these DiT seams | | LTX-2.5 DiT (`LTX2VideoTransformer3DModel`, Lightricks lane) | LTX-2.5 (21.00B video+audio) | `SPIKE`. DiT, VAEs+ENCODERS, conditioning, pipeline, quant loaders gated at reduced dims. Prompt-side AdaLN ported, host+device. Typed prompt to Gemma-4 to cross-attn, FIXTURE-gated. A prompted render is OWED | Family `ltx-2.5`, `ltx2-gen`. ~29 GB NVFP4/GB10, FP8 ~44 GB, +~24 GB tower. FP8/torchao/1st-party NVFP4 load; `keyframes_abs_pos_embedding` alone needs `allow_unported`. DiffVAE, LoRA, image cond refused. Speed PENDING | +| MiniMax-Music3 (`MiniMaxMusic3ForConditionalGeneration`, diffusers lane) | MiniMax-Music3 (8.6B Qwen3 LLM + 0.646B RVQ decoder + 2.4B fp32 DiT + DAC Flow-VAE); diffusers arm, ~28.5 GB | `SPIKE`. Scoped only, nothing implemented. Geometry measured from each `config.json` and the safetensors headers. Output is 44100 Hz stereo (the model card's 32 kHz is SGLang-Omni's server-side resample) | Not measured. The denominator will be SGLang-Omni in its production configuration (both CUDA graphs, compiled DIT and DAV, batched seeded sampling) | | MTP speculator | Qwen3.6-27B, Qwen3.6-35B-A3B | token-identical to vLLM `mtp` at c1 | ~4% faster c1; +16% output tput (MoE) | | DFlash block-diffusion | Qwen3 (DFlash draft) | near-tie e2e 27/27 vs vLLM | 2.9x over spec-off, 1.003x vs vLLM DFlash-on | | DeepSeek-V4 MTP | DeepSeek-V4-Flash (nextn head) | lossless 5/5; real-model weight-blocked | pending | @@ -194,6 +195,7 @@ on the committed fixture); reranking/classify models are not yet registered. | Audio | ✅ correctness-gated | ✅ | ◐ | ◐ | | Video+audio GENERATION (MiniMax-H3 DiT, LTX-2.5 DiT) | ◐ H3: all three modalities COHERENT on Q4_K_M (t2va, fl2va, ref2va; §8.20); the NVFP4 arm carries the patch grid; GGUF/NVFP4/bf16 loaders, pruned too (§8.21). LTX-2.5: a second lane, `SPIKE`, gated at reduced dims | ✅ H3 (vllm-omni, BF16-only, no quantized arm); LTX-2.5 only through the generic diffusers adapter, no native recipe ([vllm-omni#6066](https://github.com/vllm-project/vllm-omni/issues/6066)) | ☐ | ☐ | | Speech / audio GENERATION (TTS, vLLM-Omni lane) | ☐ not started. Eight architectures inventoried in `.agents/model-matrix.md`: six from the recipes sweep (#610) plus IndexTTS-2.5 (#634). Inventoried is not supported; all block on the absent `vllm-omni` pin (#633) | ✅ (vllm-omni: MOSS-TTS, Qwen3-TTS, Higgs Audio v3, Voxtral TTS, IndexTTS-2.5) | not assessed | not assessed | +| MUSIC generation (MiniMax-Music3) | ☐ scoped, not started ([spec](../.agents/specs/minimax-music3.md), #672). Lyrics plus a structured description in, a multi-minute stereo song out | ☐ absent from the pin, from vLLM `main` and from `vllm-omni` alike | ◐ served by SGLang-Omni, a third repository, which loads the NATIVE checkpoint layout | ☐ | | Multimodal over the OpenAI server | ◐ image request path wired, forward pending | ✅ | ✅ | ◐ | Image, video and audio are correct through the CLI and library. Over the HTTP diff --git a/scripts/check-agent-record.py b/scripts/check-agent-record.py index f005a6952..b14570750 100644 --- a/scripts/check-agent-record.py +++ b/scripts/check-agent-record.py @@ -71,7 +71,14 @@ # moves this pin by two. Both land `INVENTORIED`, unclaimed and blocked on the # absent vllm-omni pin (#633). Bumped because two rows EXIST, never to make a # transition pass. #634. - "MODEL": (AGENTS / "model-matrix.md", 372), + # 373 since 2026-08-13: +1 for `MiniMaxMusic3ForConditionalGeneration`, landing + # `SPIKE` with its spec committed (#672). Two independent rows moved this pin on + # the same day and BOTH branches read 371, so an auto-merge taking either side + # would have left the matrix internally consistent while short a real + # architecture. Re-derived, which is the only way this pin is ever allowed to + # move. test_music3_and_indextts_rows_both_survive_their_collision names all + # three rows, because a count alone cannot see that failure. + "MODEL": (AGENTS / "model-matrix.md", 373), # 82 since 2026-07-21: +`QUANT-NVFP4-CT-W4A16` (compressed-tensors NVFP4A16 / # W4A16 — NVFP4 weights with BF16 activations, distinct from the existing # `QUANT-NVFP4-CT-W4A4` and `QUANT-NVFP4-MO-W4A16` rows in both scheme diff --git a/tests/scripts/test_agent_record.py b/tests/scripts/test_agent_record.py index 988825888..8cb71bf53 100644 --- a/tests/scripts/test_agent_record.py +++ b/tests/scripts/test_agent_record.py @@ -291,6 +291,35 @@ def test_omni_pin_row_is_inside_the_engine_ratchet(self) -> None: self.assertEqual(len(found), 1, item_id) self.assertEqual(found[0].path.name, "engine-matrix.md", item_id) + def test_music3_and_indextts_rows_both_survive_their_collision(self) -> None: + """373 needs BOTH rows named, because the merge that produced it collided. + + The same hazard the omni-pin assertion above records, on the MODEL pin + and on the same day. `main` took the constant 370 -> 372 for IndexTTS-2.5 + (two architectures) while the Music3 branch took 370 -> 371 for its own + row. Neither side was wrong about its own change, and neither number was + 373 -- so whichever side an auto-merge kept, the tree would have been + internally consistent while silently short a real architecture. + + A count assertion alone cannot see that: it only knows the pin matches + the rows it can find. Naming the three rows is what makes 373 checkable + rather than plausible. + """ + + errors: list[str] = [] + rows, _ = agent_record.check_matrices(errors) + self.assertEqual([error for error in errors if "MODEL rows" in error], []) + + collided = ( + "MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation", + "MODEL-MM-indextts2-index-tts2-talker-for-conditional-generation", + "MODEL-MM-indextts2-index-tts2-s2-mel-decoder", + ) + for item_id in collided: + found = [row for row in rows if row.item_id == item_id] + self.assertEqual(len(found), 1, item_id) + self.assertEqual(found[0].path.name, "model-matrix.md", item_id) + def test_model_row_ratchet_is_load_bearing(self) -> None: """The MODEL row pin must catch a row appearing or vanishing. @@ -299,8 +328,10 @@ def test_model_row_ratchet_is_load_bearing(self) -> None: architecture re-pins it by hand. Muse Glimmer took it 361 -> 362 (`c8fc24a50`); the seven recipe architectures that had no row at all took it 362 -> 369 (#609, #610, `eba6ab7c7`); LTX-2.5 took it 369 -> 370 - (#435, `cefacd2d0`). Without this, bumping the number to silence a - failure is indistinguishable from bumping it because a row really landed. + (#435, `cefacd2d0`); IndexTTS-2.5 took it 370 -> 372, being two + architectures (#634); MiniMax-Music3 took it to 373 (#672). Without this, + bumping the number to silence a failure is indistinguishable from bumping + it because a row really landed. """ clean: list[str] = [] agent_record.check_matrices(clean) From 1e2a0af6a42e9b81e804cc85c31f89010b9db47c Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 09:38:18 +0200 Subject: [PATCH 17/55] =?UTF-8?q?feat(MODEL-MM-indextts2):=20W3=20?= =?UTF-8?q?=E2=80=94=20FCM=202-D=20front=20end,=20verified=20on=20x86=5F64?= =?UTF-8?q?=20AND=20aarch64=20(#634)=20(#701)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL CAMPPlus's head treats the [1, feat_dim, T] spectrogram as a 2-D image, so this adds genuine Conv2d/BatchNorm2d and BasicResBlock. Gated against `campplus/layers.py` executed directly: 9 cases / 19 assertions, within 1e-5. THE TRAP THIS PINS: BasicResBlock strides `(stride, 1)` (layers.py:224) — FREQUENCY only, TIME untouched. Striding both still yields a well-formed tensor at half the frame rate, which every later layer accepts. Goldens confirm 16x20 -> 8x20 empirically, and the case asserts the WIDTH is unchanged alongside the values, so a symmetric stride cannot pass by matching numbers on a smaller tensor. CROSS-ARCHITECTURE VERIFICATION: the suite was rebuilt from source and re-run on kairos-4db2 (NVIDIA Thor, aarch64, g++ 13.3, no cmake — compiled directly) and reports the same 9/9 and 19/19. The goldens were generated on x86_64 with torch, so this establishes the port's numerics are not x86-specific and no FMA-contraction difference breaks the 1e-5 tolerance on another arch. MUTATION EVIDENCE with compile status per result: striding both axes, dropping the post-residual relu, and dropping the residual add each RED at compile_err=0; restored tree SUCCESS, verified by grepping the restored line. Generator's hard-coded upstream path generalized to $CAMPPLUS_LAYERS. REMAINING in CAMPPlus: the forward assembly over 3 blocks of 12/24/16 layers. Then w2v-bert-2.0, EnhancedCodec, S2Mel, compose, routes/ABI. NO e2e render and none claimed; the lane still refuses by name. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- include/vllm/model_executor/models/campplus.h | 21 +++++ scripts/gen-campplus-goldens.py | 14 ++- src/vllm/model_executor/models/campplus.cpp | 84 +++++++++++++++++ tests/vllm/models/campplus_goldens.inc | 90 +++++++++++++++++++ tests/vllm/models/test_campplus.cpp | 32 +++++++ 5 files changed, 240 insertions(+), 1 deletion(-) diff --git a/include/vllm/model_executor/models/campplus.h b/include/vllm/model_executor/models/campplus.h index 447980119..19a275ce9 100644 --- a/include/vllm/model_executor/models/campplus.h +++ b/include/vllm/model_executor/models/campplus.h @@ -113,6 +113,27 @@ std::vector DenseTdnnBlock(const std::vector& x, int64_t in_channe int64_t dilation, int64_t seg_len, const std::vector& layers, double eps); + +// ── FCM 2-D front end (DTDNN.py:13-47) ────────────────────────────────────── +// The head runs over the [1, feat_dim, T] spectrogram as a 2-D image, so these +// are genuine Conv2d/BatchNorm2d, not the 1-D forms above. + +struct ResBlock2dWeights { + std::vector conv1, bn1_gamma, bn1_beta, bn1_mean, bn1_var; + std::vector conv2, bn2_gamma, bn2_beta, bn2_mean, bn2_var; + // Present only when the block downsamples or changes width. + std::vector short_conv, short_gamma, short_beta, short_mean, short_var; + bool has_shortcut = false; +}; + +// BasicResBlock (layers.py:218-252). THE STRIDE IS (stride, 1): it subsamples +// the FREQUENCY axis and leaves TIME untouched. Striding both still yields a +// well-formed tensor at half the frame rate, which every later layer accepts. +// Returns [planes, ceil(h/stride), w]; `out_h` reports the height. +std::vector ResBlock2d(const std::vector& x, int64_t in_planes, int64_t h, int64_t w, + int64_t planes, int64_t stride, const ResBlock2dWeights& weights, + double eps, int64_t* out_h); + } // namespace campplus } // namespace models } // namespace vllm diff --git a/scripts/gen-campplus-goldens.py b/scripts/gen-campplus-goldens.py index 3b75f02dc..4f6fed371 100644 --- a/scripts/gen-campplus-goldens.py +++ b/scripts/gen-campplus-goldens.py @@ -1,6 +1,6 @@ import importlib.util, sys, types, numpy as np, torch from pathlib import Path -SRC = Path("/tmp/claude-1000/-home-mudler--git-vllm-cpp/38b1c7ca-4dbc-4077-a36b-a9495dd81893/scratchpad/it2/indextts/s2mel/modules/campplus/layers.py") +SRC = Path(__import__("os").environ.get("CAMPPLUS_LAYERS", "/tmp/campplus/layers.py")) # load the UPSTREAM module by file path (no package __init__, no vllm deps) spec = importlib.util.spec_from_file_location("cp_layers", SRC) m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) @@ -74,6 +74,13 @@ def fill(mod, prefix): blk = m.CAMDenseTDNNBlock(NL, IN, GROWTH, BN, K, dilation=D).eval(); fill(blk, "blk") blk_out = blk(xt) + +# ---- BasicResBlock: Conv2d strides (stride, 1) -- FREQUENCY axis only ---- +FIN, FPLANES, FH, FW = 2, 3, 16, 20 +x4 = P("x4", (1, FIN, FH, FW), 1.0) +rb = m.BasicResBlock(FIN, FPLANES, stride=2).eval(); fill(rb, "rb") +rb_out = rb(x4) + def emit(f, name, t): a = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) f.write(f"inline constexpr float {name}[] = {{\n") @@ -100,5 +107,10 @@ def emit(f, name, t): f.write("\n") emit(f, "kTransit", transit_out); emit(f, "kDense2d", dense_out) emit(f, "kDenseTdnnLayer", dl_out); emit(f, "kDenseTdnnBlock", blk_out) + for n, v in (("kFcmIn", FIN), ("kFcmPlanes", FPLANES), ("kFcmH", FH), ("kFcmW", FW), + ("kFcmOutH", rb_out.shape[2]), ("kFcmOutW", rb_out.shape[3])): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write("\n") + emit(f, "kResBlock", rb_out) f.write("} // namespace campplus_goldens\n") print("wrote", out, "T=", T, "seg segments=", -(-T//100)) diff --git a/src/vllm/model_executor/models/campplus.cpp b/src/vllm/model_executor/models/campplus.cpp index c41297d2a..1a8706b95 100644 --- a/src/vllm/model_executor/models/campplus.cpp +++ b/src/vllm/model_executor/models/campplus.cpp @@ -206,6 +206,90 @@ std::vector DenseTdnnBlock(const std::vector& x, int64_t in_channe return acc; } + +namespace { + +// 2-D convolution over [C, H, W], zero-padded, stride (sh, sw). +std::vector Conv2d(const std::vector& in, int64_t in_ch, int64_t h, int64_t w, + const std::vector& weight, int64_t out_ch, int64_t kernel, + int64_t sh, int64_t sw, int64_t pad, int64_t* oh, int64_t* ow) { + const int64_t H = (h + 2 * pad - kernel) / sh + 1; + const int64_t W = (w + 2 * pad - kernel) / sw + 1; + std::vector out(static_cast(out_ch * H * W), 0.0F); + for (int64_t o = 0; o < out_ch; ++o) { + for (int64_t y = 0; y < H; ++y) { + for (int64_t x = 0; x < W; ++x) { + double acc = 0.0; + for (int64_t c = 0; c < in_ch; ++c) { + for (int64_t ky = 0; ky < kernel; ++ky) { + const int64_t sy = y * sh + ky - pad; + if (sy < 0 || sy >= h) continue; + for (int64_t kx = 0; kx < kernel; ++kx) { + const int64_t sx = x * sw + kx - pad; + if (sx < 0 || sx >= w) continue; + acc += static_cast( + weight[static_cast(((o * in_ch + c) * kernel + ky) * kernel + kx)]) * + static_cast(in[static_cast((c * h + sy) * w + sx)]); + } + } + } + out[static_cast((o * H + y) * W + x)] = static_cast(acc); + } + } + } + *oh = H; *ow = W; + return out; +} + +// BatchNorm2d in eval: per-CHANNEL running statistics over the H*W plane. +void BatchNorm2dEvalInPlace(std::vector& x, int64_t channels, int64_t plane, + const std::vector& g, const std::vector& b, + const std::vector& mean, const std::vector& var, + double eps) { + for (int64_t c = 0; c < channels; ++c) { + const double m = static_cast(mean[static_cast(c)]); + const double inv = 1.0 / std::sqrt(static_cast(var[static_cast(c)]) + eps); + const double gc = g.empty() ? 1.0 : static_cast(g[static_cast(c)]); + const double bc = b.empty() ? 0.0 : static_cast(b[static_cast(c)]); + for (int64_t i = 0; i < plane; ++i) { + const size_t k = static_cast(c * plane + i); + x[k] = static_cast((static_cast(x[k]) - m) * inv * gc + bc); + } + } +} + +} // namespace + +std::vector ResBlock2d(const std::vector& x, int64_t in_planes, int64_t h, int64_t w, + int64_t planes, int64_t stride, const ResBlock2dWeights& wt, + double eps, int64_t* out_h) { + int64_t h1 = 0, w1 = 0; + // stride is (stride, 1): FREQUENCY only. + std::vector out = + Conv2d(x, in_planes, h, w, wt.conv1, planes, 3, stride, 1, 1, &h1, &w1); + BatchNorm2dEvalInPlace(out, planes, h1 * w1, wt.bn1_gamma, wt.bn1_beta, wt.bn1_mean, wt.bn1_var, eps); + for (float& v : out) v = v > 0.0F ? v : 0.0F; + + int64_t h2 = 0, w2 = 0; + out = Conv2d(out, planes, h1, w1, wt.conv2, planes, 3, 1, 1, 1, &h2, &w2); + BatchNorm2dEvalInPlace(out, planes, h2 * w2, wt.bn2_gamma, wt.bn2_beta, wt.bn2_mean, wt.bn2_var, eps); + + std::vector shortcut; + if (wt.has_shortcut) { + int64_t sh = 0, sw = 0; + shortcut = Conv2d(x, in_planes, h, w, wt.short_conv, planes, 1, stride, 1, 0, &sh, &sw); + BatchNorm2dEvalInPlace(shortcut, planes, sh * sw, wt.short_gamma, wt.short_beta, wt.short_mean, + wt.short_var, eps); + } else { + shortcut = x; + } + VT_CHECK(shortcut.size() == out.size(), "campplus: residual shape mismatch"); + for (size_t i = 0; i < out.size(); ++i) out[i] += shortcut[i]; + for (float& v : out) v = v > 0.0F ? v : 0.0F; + *out_h = h2; + return out; +} + } // namespace campplus } // namespace models } // namespace vllm diff --git a/tests/vllm/models/campplus_goldens.inc b/tests/vllm/models/campplus_goldens.inc index 3ee0b4fd0..d04196044 100644 --- a/tests/vllm/models/campplus_goldens.inc +++ b/tests/vllm/models/campplus_goldens.inc @@ -1551,4 +1551,94 @@ inline constexpr float kDenseTdnnBlock[] = { -3.652353212e-02F, -3.359469771e-02F, }; +inline constexpr int64_t kFcmIn = 2; +inline constexpr int64_t kFcmPlanes = 3; +inline constexpr int64_t kFcmH = 16; +inline constexpr int64_t kFcmW = 20; +inline constexpr int64_t kFcmOutH = 8; +inline constexpr int64_t kFcmOutW = 20; + +inline constexpr float kResBlock[] = { + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 1.226864755e-02F, 6.262823939e-03F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 1.425197721e-02F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.857282758e-03F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 4.972621799e-03F, 1.497329772e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 9.866669774e-03F, 0.000000000e+00F, 3.219053149e-02F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.271835506e-02F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.215474844e-03F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 1.232348382e-02F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 4.191338271e-02F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 3.232419491e-03F, 0.000000000e+00F, + 2.962237597e-02F, 2.158388495e-02F, 0.000000000e+00F, 4.582896829e-03F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 1.177278161e-02F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 1.608072221e-02F, 1.379291713e-02F, 1.121512055e-02F, + 5.553573370e-03F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 1.033280045e-02F, 5.006045103e-04F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 6.827648729e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 1.461188495e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.106375992e-02F, 2.191109955e-02F, + 5.181013420e-02F, 0.000000000e+00F, 6.815840304e-02F, 2.414838970e-02F, 2.552305162e-02F, 3.594734520e-02F, + 4.851581529e-02F, 3.102770448e-02F, 4.096218944e-02F, 1.063403487e-02F, 4.677934572e-02F, 2.818197012e-02F, + 3.783828393e-02F, 1.723372191e-02F, 1.549981534e-02F, 6.509393454e-02F, 1.448051631e-02F, 3.863230348e-02F, + 2.629788220e-02F, 6.688506901e-02F, 0.000000000e+00F, 5.052400380e-02F, 6.214394048e-02F, 7.563121617e-03F, + 4.554143921e-02F, 5.002205446e-02F, 3.567316011e-02F, 6.089086086e-02F, 2.397954091e-02F, 1.678481698e-02F, + 5.761355907e-02F, 2.901215851e-02F, 4.981091246e-02F, 1.001742482e-02F, 4.109093174e-02F, 3.553612903e-02F, + 3.287031129e-02F, 5.032710359e-02F, 1.433280855e-02F, 7.540643215e-03F, 4.804858938e-02F, 3.473021835e-02F, + 3.590406105e-02F, 3.867190331e-02F, 5.048725009e-02F, 2.547872066e-02F, 4.942491651e-02F, 2.623683959e-02F, + 2.415902540e-02F, 3.653448820e-02F, 0.000000000e+00F, 2.808360010e-02F, 0.000000000e+00F, 3.759223595e-02F, + 5.648859218e-02F, 1.880568266e-02F, 5.955946445e-02F, 4.283543304e-02F, 4.934296012e-04F, 1.694876701e-02F, + 7.979288697e-03F, 6.222858652e-02F, 5.344008654e-02F, 3.204744309e-02F, 5.245997757e-02F, 5.821742490e-02F, + 3.420508280e-02F, 3.282252699e-02F, 5.654715002e-02F, 0.000000000e+00F, 5.276655778e-02F, 8.359923959e-03F, + 5.143108964e-02F, 3.885035962e-02F, 2.085985988e-02F, 4.250334203e-02F, 5.526085943e-02F, 3.648327664e-02F, + 2.833005041e-02F, 4.755805805e-02F, 6.117519364e-02F, 1.568746567e-02F, 1.140167564e-02F, 3.857549652e-02F, + 3.045142442e-02F, 3.919832781e-02F, 2.716479450e-02F, 3.540351614e-02F, 6.008240953e-02F, 6.600952148e-02F, + 2.983138710e-02F, 7.833473384e-02F, 2.415983379e-02F, 4.017937928e-02F, 3.792792559e-03F, 1.723368466e-02F, + 2.640491724e-02F, 5.090114474e-02F, 1.118876785e-02F, 7.407718152e-02F, 5.112230405e-02F, 1.436237991e-02F, + 2.503218502e-02F, 4.598768428e-02F, 5.169083551e-02F, 6.838047504e-02F, 3.353457898e-02F, 1.199209690e-02F, + 2.306780219e-02F, 1.368828118e-02F, 5.317941681e-02F, 1.428230852e-02F, 7.239236683e-02F, 4.990409687e-02F, + 3.066601604e-02F, 1.390904188e-02F, 5.738214776e-02F, 2.597145364e-02F, 3.265890479e-02F, 6.277833879e-02F, + 4.754843563e-02F, 2.360434085e-02F, 7.315268368e-02F, 3.152197599e-02F, 2.877490222e-02F, 5.312669277e-02F, + 4.500828311e-02F, 6.060604006e-02F, 4.285365343e-02F, 3.580210358e-02F, 2.875938267e-02F, 8.715456724e-02F, + 2.553912997e-02F, 4.779919237e-02F, 4.038460925e-02F, 4.215922579e-02F, 4.608033597e-02F, 4.844048619e-02F, + 2.991946787e-02F, 2.470814437e-02F, 2.096760273e-02F, 4.553592578e-02F, 5.043823645e-02F, 2.214299142e-02F, + 2.895935252e-02F, 3.565961868e-02F, 3.672346845e-02F, 3.426755220e-02F, 5.032075942e-02F, 9.054563940e-03F, + 3.197994828e-02F, 8.915141225e-04F, 2.477519214e-02F, 5.414127931e-02F, 2.954189479e-02F, 2.365710586e-02F, + 3.983588889e-02F, 4.560589790e-02F, 0.000000000e+00F, 0.000000000e+00F, 3.062242270e-02F, 1.141862273e-01F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.061420679e-02F, 0.000000000e+00F, + 1.460221410e-02F, 0.000000000e+00F, 2.682161331e-02F, 6.856563687e-02F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 1.554569602e-02F, 0.000000000e+00F, 4.305285215e-02F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 6.516161561e-02F, 3.305086493e-02F, 0.000000000e+00F, 0.000000000e+00F, + 2.773272991e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 5.749866366e-02F, + 6.103956699e-02F, 3.541925550e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 1.654607058e-02F, 5.087879300e-02F, 0.000000000e+00F, 7.968577743e-02F, + 0.000000000e+00F, 4.050701857e-02F, 9.575495124e-02F, 0.000000000e+00F, 0.000000000e+00F, 1.208704710e-02F, + 3.030458093e-02F, 3.908932209e-03F, 3.593245149e-02F, 0.000000000e+00F, 1.960983872e-02F, 0.000000000e+00F, + 0.000000000e+00F, 2.285602689e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 5.004584789e-03F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 5.270469189e-02F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 4.214704037e-02F, 0.000000000e+00F, 7.384070754e-02F, 2.777042985e-02F, 0.000000000e+00F, + 6.994596124e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 1.813420653e-02F, 0.000000000e+00F, 0.000000000e+00F, 1.709672809e-02F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 1.821893454e-02F, + 0.000000000e+00F, 2.131399512e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 4.428297281e-03F, 2.007254958e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 8.642047644e-03F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.056974173e-02F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.743014693e-02F, + 6.141868234e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 4.877567291e-03F, 0.000000000e+00F, + 5.405968428e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 4.099830985e-02F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 8.955776691e-03F, 0.000000000e+00F, 0.000000000e+00F, +}; + } // namespace campplus_goldens diff --git a/tests/vllm/models/test_campplus.cpp b/tests/vllm/models/test_campplus.cpp index 6bc890d50..99b5b7fc4 100644 --- a/tests/vllm/models/test_campplus.cpp +++ b/tests/vllm/models/test_campplus.cpp @@ -186,3 +186,35 @@ TEST_CASE("campplus CAMDenseTDNNBlock GROWS its channel count by concatenation") REQUIRE(got.size() == static_cast((kIn + kNumLayers * kGrowth) * kFrames)); CHECK(Worst(got, kDenseTdnnBlock, got.size()) < 1e-5); } + +TEST_CASE("campplus BasicResBlock strides FREQUENCY only, never time") { + // stride=(stride, 1) in layers.py:224. Striding both axes still yields a + // well-formed tensor -- at half the frame rate -- which every later layer + // accepts, so the WIDTH is asserted unchanged alongside the values. + const std::vector x = Rand("x4", kFcmIn * kFcmH * kFcmW, 1.0); + vllm::models::campplus::ResBlock2dWeights w; + auto var = [](std::vector v) { for (float& f : v) f = std::fabs(f) + 0.5F; return v; }; + w.conv1 = Rand("rb.conv1.weight", kFcmPlanes * kFcmIn * 9, 0.3); + w.bn1_gamma = Rand("rb.bn1.weight", kFcmPlanes, 0.3); + w.bn1_beta = Rand("rb.bn1.bias", kFcmPlanes, 0.3); + w.bn1_mean = Rand("rb.bn1.running_mean", kFcmPlanes, 0.4); + w.bn1_var = var(Rand("rb.bn1.running_var", kFcmPlanes, 0.2)); + w.conv2 = Rand("rb.conv2.weight", kFcmPlanes * kFcmPlanes * 9, 0.3); + w.bn2_gamma = Rand("rb.bn2.weight", kFcmPlanes, 0.3); + w.bn2_beta = Rand("rb.bn2.bias", kFcmPlanes, 0.3); + w.bn2_mean = Rand("rb.bn2.running_mean", kFcmPlanes, 0.4); + w.bn2_var = var(Rand("rb.bn2.running_var", kFcmPlanes, 0.2)); + w.has_shortcut = true; // stride != 1 and in_planes != planes + w.short_conv = Rand("rb.shortcut.0.weight", kFcmPlanes * kFcmIn, 0.3); + w.short_gamma = Rand("rb.shortcut.1.weight", kFcmPlanes, 0.3); + w.short_beta = Rand("rb.shortcut.1.bias", kFcmPlanes, 0.3); + w.short_mean = Rand("rb.shortcut.1.running_mean", kFcmPlanes, 0.4); + w.short_var = var(Rand("rb.shortcut.1.running_var", kFcmPlanes, 0.2)); + + int64_t out_h = 0; + const std::vector got = vllm::models::campplus::ResBlock2d( + x, kFcmIn, kFcmH, kFcmW, kFcmPlanes, /*stride=*/2, w, 1e-5, &out_h); + CHECK(out_h == kFcmOutH); // 16 -> 8: frequency halved + REQUIRE(got.size() == static_cast(kFcmPlanes * kFcmOutH * kFcmOutW)); + CHECK(Worst(got, kResBlock, got.size()) < 1e-5); +} From 7138d1618c32a27c8468d7c104cad8ff788533bf Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:01:07 +0200 Subject: [PATCH 18/55] test(#669): pin that the token budget SPLITS a prefill wave, and label what is not paired test(#669): pin that the token budget SPLITS a prefill wave, and label what is not paired A measured claim of mine was wrong and this row is the disproof. I measured our 35B prefill as INVARIANT to --max-num-batched-tokens (0.6814s at 8192 vs 0.6828s at 2048, c4) while vLLM's swung 30%, and concluded we were failing to chunk. We are not. The budget reaches the scheduler -- chain verified hop by hop from server_main.cpp:400,806 through model_loader.cpp:626-641,704-717,1051-1058 to scheduler.cpp:233-234,465 -- and a composition probe shows chunking happening: budget 8192 gives one 4096-token step; budget 2048 gives 2048 / 2048 / 5, with the clamp firing mid-prompt (a=1, b=1, c=1024, d=1022). We are invariant because 1x4096 and 2x2048 are THE SAME TOTAL PREFILL WORK. The encoder-decoder disable cannot fire, for a stronger reason than "the condition is false": SchedulerConfig::PostInit has ZERO call sites outside its own test, so it is never invoked on the engine path at all. No timing gate can separate "chunking works and the totals are equal" from "chunking never happened" -- which is why the test pins the COMPOSITION. Its 1022-token remainder is load-bearing: a reviewer's mutation that skips the running loop keeps total_num_scheduled_tokens at exactly 2048 and is caught only by that value. Six mutations RED, three of them the reviewer's own. WHAT IS ESTABLISHED, and what is not: * intra-pin, paired: the pin's own 4096-token forward costs 42% more wall clock than two 2048s for identical work (0.8371/0.8147/0.8148 vs 0.5789/0.5789/0.5774; spreads 2.7%/0.3%; n=3; interleaved; clock pinned per leg; boot_id logged; oracle asserted). Our invariance likewise (1.4%/1.0%). So the 1.21x we appeared to win at 8192/c4 is us beating an upstream pathology, not headroom. * cross-arm: NOT paired and not differenceable. `ours` came from the chunkprobe barrier-client with --num-blocks 2048; `pin` from mnbt.sh's curl spawn-loop with --gpu-memory-utilization 0.6; never interleaved with each other. The residual "+3.0% for us vs +11% for the pin" is a HYPOTHESIS. * --language-model-only was PIN-ONLY, not a shared condition. * the ours@8192/c1 cell is a mean of TWO legs; its rep-1 leg recorded 6135.86s -- an artifact of a wedged harness whose resume ledger re-imported it, which the reducer correctly flagged NOT ESTABLISHED on a 34902x spread. Two upstream anchors corrected: the cudagraph cap is 64, not 512 (both budgets still tie), and "upstream's flat 2048" is a pydantic TESTING default that EngineArgs._set_default_args overrides to 8192/16384 on a >=70 GiB GPU. GB10 is one, so our MoE-8192 may BE upstream's effective default -- the framing that choosing it flattered us against a "true" 2048 bar is withdrawn. Filed, not fixed: #682, the sweep gave our arm 8,192 tokens of KV against the pin's 1,819,368 -- a 222x asymmetry that suppressed the very batching being varied. Provisioned, our arm ran slightly FASTER, so the 0.85x is conservative rather than flattering, but it is owed a re-measure before being banked. No product change: the scheduler already mirrors upstream. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-opus-5 [ClaudeCode] --- .agents/roadmap_v1.md | 2 + .../perf-chunked-prefill-budget-2026-08-13.md | 326 ++++++++++++++++++ tests/vllm/v1/test_scheduler.cpp | 106 ++++++ 3 files changed, 434 insertions(+) create mode 100644 .agents/specs/perf-chunked-prefill-budget-2026-08-13.md diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index fd0b33e06..fe3f709d3 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -160,6 +160,8 @@ issue is not yet placed. Keyed record: update in place, never append. | [#223](https://github.com/mudler/vllm.cpp/issues/223) | `SAMPLE-PROMPT-LOGPROBS` | `prompt_logprobs` is a silent no-op: the runner never computes logits at prompt positions | bug | | [#294](https://github.com/mudler/vllm.cpp/issues/294) | `SERVE-ASYNC-LLM` | `test_async_llm`: reusing an aborted request id races the core abort — 26% failure rate under contention, on main | bug | | [#222](https://github.com/mudler/vllm.cpp/issues/222) | `SERVE-GATE-ONLINE` | The online-serving gate aborts on current main: harness looks for `examples/server`, the build produces `examples/vllm-server` — paths repaired by `2b262622`/`8fce04d3`; residue is the drift guard, the `27n` key and a model gate that recorded a skip as passed | bug | +| [#669](https://github.com/mudler/vllm.cpp/issues/669) | `SERVE-GATE-ONLINE` | Chunked prefill: our TTFT is budget-invariant because 1x4096 and 2x2048 are the same total work, NOT because the budget is ignored — composition now pinned, so no timing gate can re-litigate it; the pin's own 4096-token forward is the outlier (spec [`perf-chunked-prefill-budget-2026-08-13.md`](specs/perf-chunked-prefill-budget-2026-08-13.md)) | verification | +| [#682](https://github.com/mudler/vllm.cpp/issues/682) | `SERVE-GATE-ONLINE` | The 35B mnbt sweep gave our arm 8,192 tokens of KV (256 blocks x 32, no `--num-blocks`) and the pin 1,819,368 — a 222x asymmetry that suppressed the very batching the sweep was varying; measured, our arm could not form a wave >2048 tokens at EITHER budget until KV was provisioned (spec [`perf-chunked-prefill-budget-2026-08-13.md`](specs/perf-chunked-prefill-budget-2026-08-13.md) §Runtime) | bug | | [#277](https://github.com/mudler/vllm.cpp/issues/277) | `SERVE-METRICS` | `/metrics` is dead on the production server: AsyncLLM never folds IterationStats into the logger | bug | | [#321](https://github.com/mudler/vllm.cpp/issues/321) | `SERVE-METRICS` | AsyncLLM output thread outlives metrics logger and uses freed PromRegistry | bug | | [#81](https://github.com/mudler/vllm.cpp/issues/81) | `SPEC-MTP` | Speculative decoding: MTP k>1, dynamic and adaptive depth | feature | diff --git a/.agents/specs/perf-chunked-prefill-budget-2026-08-13.md b/.agents/specs/perf-chunked-prefill-budget-2026-08-13.md new file mode 100644 index 000000000..4a0490cdd --- /dev/null +++ b/.agents/specs/perf-chunked-prefill-budget-2026-08-13.md @@ -0,0 +1,326 @@ +# Chunked prefill: the budget IS applied — the invariant TTFT is arithmetic + +**Row:** `SERVE-GATE-ONLINE` · **Issue:** [#669](https://github.com/mudler/vllm.cpp/issues/669) +· **Pin:** `5559679229bc961848b121ccdeaa8fa5d79bec98` (vLLM 0.26.0.dev0) + +This is a grounding/verdict spike with one test attached, not an implementation +plan. It resolves whether our budget-invariant prefill time is **chunking that is +not happening** (a bug) or **chunking that costs the same either way** (physics), +and it records why the upstream mechanism the sweep appeared to reveal is not a +mechanism we lack. + +## The measurement that raised it + +35B `nvidia/Qwen3.6-35B-A3B-NVFP4`@`491c2f1e`, TTFT (`max_tokens=1`, 1024-token +prompts), clock pinned 2190/2184 MHz, single `boot_id`, oracle by explicit path, +GRAPHED, `--language-model-only` (**PIN ARM ONLY** -- our server has no such +flag and its log shows `multimodal image seam wired`; it is not a shared +condition), `VT_SERVER_SSE_PING_S=0` on our arm, arms interleaved, +n=3 except where marked (operator's measurement, 2026-08-13). + +**Two reductions to disclose.** The `ours@8192/c1` cell is a mean of **2** legs: +its rep-1 leg recorded **6135.86 s** and was discarded. That leg is not data -- +it is an artifact of a wedged harness whose resume ledger re-imported it, and +the harness's own reducer correctly flagged that cell NOT ESTABLISHED on a +34902x spread. Figures here are **means**; the harness prints **medians** with a +>5%-spread flag, so the two do not correspond cell-for-cell. + +| `--max-num-batched-tokens` | conc | ours (s) | pin (s) | pin/ours | +|---|---|---|---|---| +| 8192 | 1 | 0.1766 [n=2] | 0.1605 | 0.91 | +| 8192 | 4 | 0.6814 | 0.8222 | 1.21 | +| 2048 | 1 | 0.1789 | 0.1599 | 0.89 | +| 2048 | 4 | 0.6828 | 0.5784 | 0.85 | + +## Verdict + +**Chunked prefill IS applied, and our composition is identical to upstream's. +There is no scheduler divergence and no product change to make.** Confirmed +twice: statically in the scheduler (§Evidence, §Upstream) and at runtime on the +GPU with the real 35B (§Runtime). + +Our c4 time is invariant because one 4096-token forward and two 2048-token +forwards are the same total prefill work. That is the expected result, not the +absence of chunking. The two hypotheses produce *the same number*, which is +exactly why the sweep could not separate them — and why the composition needed +pinning (§Tests). + +One thing the sweep *did* hide, and it is a measurement defect rather than a +product one: with no `--num-blocks`, our arm ran on 8192 tokens of KV against +the pin's 1,819,368, and under that starvation our arm could not form a wave +larger than 2048 tokens **at either budget** — so the two settings were +operationally identical on our side for a reason that has nothing to do with +chunked prefill (§Runtime, [#682](https://github.com/mudler/vllm.cpp/issues/682)). + +## Evidence — the budget reaches the scheduler + +Unbroken chain, every hop read: + +| Hop | Anchor | +|---|---| +| CLI flag parsed | `src/vllm/entrypoints/openai/server_main.cpp:400` | +| into `EngineParams` | `server_main.cpp:806` | +| resolved (explicit override wins) | `src/vllm/entrypoints/model_loader.cpp:626-641` | +| into `SchedulerConfig` | `model_loader.cpp:704-717` (`MakeSchedulerConfig`) | +| into the `Scheduler` | `model_loader.cpp:1051-1058` | +| into `max_num_scheduled_tokens` | `src/vllm/v1/core/sched/scheduler.cpp:233-234` | +| into the per-step budget | `scheduler.cpp:465` | + +`enable_chunked_prefill` is hard-`true` for this path (`model_loader.cpp:711`). +The encoder-decoder disable (`src/vllm/config/scheduler.cpp:52-57`) is not on it. +`AsyncScheduler` — the production default — does not override `schedule()` +(`include/vllm/v1/core/sched/async_scheduler.h:46-52`), so the budget path is the +same under async scheduling. + +## Evidence — the scheduler composition (CPU) + +Scheduler-composition probe, 4 x 1024-token prompts, `max_num_seqs=256`, CPU, no +model (`tests/vllm/v1/test_scheduler.cpp` helpers): + +``` +budget=8192 step0 total=4096 a=1024 b=1024 c=1024 d=1024 + step1 total=4 a=1 b=1 c=1 d=1 +budget=4096 step0 total=4096 a=1024 b=1024 c=1024 d=1024 (identical) +budget=2048 step0 total=2048 a=1024 b=1024 + step1 total=2048 a=1 b=1 c=1024 d=1022 (clamp fires) + step2 total=5 a=1 b=1 c=1 d=2 (d's tail) +budget=1024 step0 total=1024 a=1024 + step1 total=1024 a=1 b=1023 + step2 total=1024 a=1 b=1 c=1022 + step3 total=1024 a=1 b=1 c=2 d=1020 +``` + +Note that `8192` and `4096` are the *same* composition: 4096 <= both budgets, so +the operator's "8192 vs 2048" comparison is really **1x4096 vs 2x2048**. + +Note also which line ends the 2048 step 0: the loop guard, not the clamp. Two +1024-token prompts take the budget to exactly 0, so the third request is never +peeked and there is no partial third chunk. In step 1 the *running* loop is +served first, so a/b's two decode tokens come off the budget before the waiting +loop runs and "d" is genuinely split 1022 + 2 — the clamp firing mid-prompt. + +## Runtime — the budget binds on the GPU, and what the sweep hid + +dgx.casa (GB10), 35B NVFP4 @`491c2f1e`, `~/work/mnbt-src` server, clock pinned +2190 MHz (verified at each leg start/end), `VT_SERVER_SSE_PING_S=0`, +`--max-num-seqs 32 --no-enable-prefix-caching`, boot_id recorded. The sweep +launched its concurrent requests as `curl &` in a bash loop, which skews arrivals +by a process spawn each; these fire all four off one barrier on **pre-connected** +sockets, and record each request's OWN latency rather than only the burst's wall +clock. Raw: `dgx:~/work/chunkprobe/`. + +**Starved KV — the sweep's own provisioning (no `--num-blocks`; the server logs +`auto-fit max_model_len: reduced from 262144 to 8192 to fit the KV cache (256 +blocks x 32 tokens)` = 8192 tokens TOTAL):** + +| mnbt | per-request latency (s) | spread | burst wall | +|---|---|---|---| +| 8192 | 0.2598 0.5359 0.5364 0.6564 | 2.53 | 0.6573 | +| 8192 | 0.2620 0.5365 0.5366 0.6579 | 2.51 | 0.6587 | +| 2048 | 0.2601 0.5388 0.5391 0.6611 | 2.54 | 0.6623 | +| 2048 | 0.2608 0.5374 0.5376 0.6594 | 2.53 | 0.6598 | + +Identical to three decimals at both budgets, and the server's own prefill log +pairs at most **two** requests per step (equal `elapsed_s`: 0.224935 / 0.224910). +The scheduler never saw more than 2048 tokens of waiting work, so the budget +could not bind either way. Nothing here is about chunked prefill. + +**Provisioned KV (`--num-blocks 2048 --max-model-len 8192`), same everything +else:** + +| mnbt | per-request latency (s) | spread | co-scheduled per step (equal `elapsed_s`) | +|---|---|---|---| +| 8192 | 0.6650 0.6650 0.6654 0.6657 | **1.001** | 3 x 0.27737 / 0.27739 / 0.27735 | +| 8192 | 0.3062 0.6452 0.6453 0.6460 | 2.11 | 3 x 0.25551 / 0.25544 / 0.25548 | +| 2048 | 0.5090 0.5533 0.5541 0.6751 | 1.33 | **2** x 0.227394 / 0.227343 | +| 2048 | 0.2588 0.5330 0.5333 0.6556 | 2.53 | **2** x 0.210219 / 0.210237 | + +**That is the budget binding, measured on the GPU.** At 8192 the wave collapses +into a single step — all four requests return within 0.7 ms of each other — and +three share one forward. At 2048, with the identical client and identical KV, the +co-scheduled group is capped at exactly two, which is 2048 tokens. This is the +CPU composition table above, reproduced by the real engine. + +And the burst wall clock is *still* flat: 0.6659 at 8192 vs 0.6755 / 0.6564 at +2048, against c1 legs of 0.1721 / 0.1709. Same total work, same time. The +invariance was never evidence about chunking. + +Two consequences worth separating: + +* **Product:** none. The budget is applied, at both tiers, exactly as upstream + applies it. +* **Measurement:** the sweep compared an arm holding 8192 tokens of KV against + one holding 1,819,368 (`pin-mnbt8192-c4-r2.log`, `kv_cache_utils.py:2214`, at + `--gpu-memory-utilization 0.6`). That is a 222x asymmetry in a paired + comparison, and on our side it suppressed the very batching the sweep was + varying. Filed as [#682](https://github.com/mudler/vllm.cpp/issues/682). + +## Upstream — line for line + +Our waiting loop against the pin. Verified by reading, not inherited: + +| Element | Ours | Pinned vLLM | +|---|---|---| +| budget init | `scheduler.cpp:465` | `scheduler.py:447` | +| running loop drains budget first | `scheduler.cpp:478,512,592` | `scheduler.py:473,511,620-621` | +| waiting loop guard `token_budget > 0` | `scheduler.cpp:642` | `scheduler.py:671` | +| `max_num_seqs` break | `scheduler.cpp:643-645` | `scheduler.py:673-676` | +| `num_new = num_tokens - num_computed` | `scheduler.cpp:688` | `scheduler.py:825` | +| `long_prefill_token_threshold` cap (0 -> inert) | `scheduler.cpp:689-692` | `scheduler.py:845-847` | +| chunked-disabled break (inert, chunked ON) | `scheduler.cpp:694-696` | `scheduler.py:850-857` | +| `min(num_new, token_budget)` | `scheduler.cpp:697` | `scheduler.py:859-860` | +| `token_budget -= num_new` | `scheduler.cpp:742` | `scheduler.py:1018-1019` | +| post-conditions | `scheduler.cpp:763-764` | `scheduler.py:1054-1058` | + +`max_num_scheduled_tokens` falls back to `max_num_batched_tokens` on both sides +(`include/vllm/config/scheduler.h:156-158` <- `scheduler.py:109-113`); it is only +smaller under speculative decoding (`vllm/config/vllm.py:1699-1712`), which is +off here. + +Three things upstream does **not** have, checked because their absence would have +been the divergence: `max_num_partial_prefills` / `max_long_partial_prefills` +(removed upstream — zero hits at the pin; `vllm/v1/core/sched/utils.py` holds only +repetition helpers), a decode reservation or prefill/decode budget split (one flat +`token_budget`), and a structured-output budget carve-out (`scheduler.py:1248-1249` +never touches the budget). The encoder budget is separate but mnbt-derived on both +sides (`vllm/config/scheduler.py:238-239` <- `src/vllm/config/scheduler.cpp:59-60`) +and inert for text-only. + +## Why vLLM swings and we do not + +`max_num_batched_tokens` changes almost nothing else upstream: + +* **CUDA-graph capture sizes are identical.** `vllm/config/vllm.py:1795-1802` + caps at `min(max_num_seqs * decode_query_len * 2, 512)` then + `min(max_num_tokens, that)` -- which on this config resolves to **64**, not + 512. Both 2048 and 8192 give the same cap. Both 2048- and 4096-token batches + exceed it, so + `vllm/v1/cudagraph_dispatcher.py:272-281` returns `CUDAGraphMode.NONE` with + zero padding for both. Ruled out. +* **Runner buffers** (`gpu_model_runner.py:505,764-857`) and the **FlashInfer + prefill workspace** (`flashinfer.py:944-958`) scale with mnbt but are + allocate-once. Attention plan/split-kv/`reorder_batch` are not mnbt-dependent + (`flashinfer.py:643-681`, `backends/utils.py:665`). +* **One real difference:** mnbt is in the compilation hash + (`vllm/config/scheduler.py:206-216`, citing vllm#29585 — Inductor picks 32- vs + 64-bit indexing from the size hint) and bounds `compile_ranges` + (`vllm/config/vllm.py:1918-1920`). Different Inductor artifacts per budget. + Small, and it would also show at c1 — where both arms are flat, so it is not + the c4 effect. + +So vLLM's 30% is not a chunked-prefill fast path. The harness (`dgx:~/mnbt.sh`) +times `t1-t0` around the whole burst — **wall clock to drain it, not mean +TTFT** — so the mean-TTFT staggering artifact that a 1x4096-vs-2x2048 split +would otherwise produce is not in these numbers at all. Reading the columns as +prefill throughput (4096 tokens per c4 burst; ours from the provisioned-KV legs +above, which are the ones that actually run the stated forward shape): + +| tokens per forward | ours | pin | +|---|---|---| +| 1024 (c1) | 5971 tok/s | 6380 | +| 2 x 2048 | 6150 | **7081** | +| 1 x 4096 | 6151 | **4982** | + +**The two columns are NOT PAIRED and must not be differenced.** `ours` comes +from the chunkprobe barrier-client with `--num-blocks 2048`; `pin` from +`mnbt.sh`'s curl spawn-loop with `--gpu-memory-utilization 0.6` — different +client, different KV provisioning, and the two were never interleaved WITH EACH +OTHER. Each column is internally sound; across columns, `6151 vs 4982` and +`6150 vs 7081` are unpaired readings. + +**ESTABLISHED (intra-pin, paired):** vLLM's **4096-token forward is the outlier** — 30% less efficient per token than +its own 2048-token forward, while its 2048 number sits on the same near-linear +trend as its c1. Ours is flat across forward sizes: no collapse at 4096, and no +gain at 2048 either. The 1.21x at 8192/c4 is us beating an upstream pathology, +not a win to bank; the 0.85x at 2048/c4 is the honest number, and it is the same +~10-15% prefill deficit already visible at c1 (0.89-0.91x) — the separate problem +the operator correctly refused to conflate. + +The flat row is the finding to carry forward, **as a HYPOTHESIS rather than a +measurement**, because it is a cross-column comparison and the columns are +unpaired (above): we appear to extract no benefit from a larger prefill forward +(5971 -> 6150 -> 6151, +3.0% total) where the pin appears to extract +11% going +from 1024 to 2048. Sizing it needs one interleaved same-client run. Whatever it does with a +2048-token forward that it cannot do with a 1024-token one, we do not do — and +that, not chunking, is where the c4 prefill gap lives. + +## Tests + +`tests/vllm/v1/test_scheduler.cpp` — `Scheduler.schedule: the token budget splits +a 4x1024 prefill wave`. Two subcases pinning the composition above: 8192 admits +the wave in one 4096-token step with nobody chunked; 2048 splits it into two +2048-token steps and pins the **1022**-token remainder chunk and d's 2-token +tail. Mirrors `scheduler.py:671,825,859-860,1018-1019`. + +Assertion counts: `test_scheduler` 36 cases / 423 assertions -> **37 / 448**. + +**Mutation proof** (both in-place, tree restored byte-for-byte, `md5sum` +`6b88fd41533c097eaefabc7f8f2936d4` before and after, `git status` clean): + +| Mutation | Result | +|---|---| +| `token_budget = 1000000` (budget never reaches the scheduler — the hypothesis) | **RED**, 5 failed | +| drop `min(num_new_tokens, token_budget)` in the waiting loop | **RED**, 5 failed | + +## Gates + +CPU, clean `-Werror` build (`build_exit=0`), Release: + +| Suite | Result | +|---|---| +| `test_scheduler` | 37/37 cases, 448/448 assertions, SUCCESS | +| `test_scheduler_config` | 13/13, 32/32, SUCCESS | +| `test_sched_output` | 8/8, 52/52, SUCCESS | +| `test_request_queue` | 26/26, 1839/1839, SUCCESS | +| `test_scheduler_wave` | 3/3, 44/44, SUCCESS | + +`scripts/agent-preflight.sh` is green except `test_cpu_x86_llamacpp_floor`, +which is [#618](https://github.com/mudler/vllm.cpp/issues/618) — the known +load-dependent exit-4-instead-of-2 case, reproduced here at loadavg 201 on a +shared box. It touches no path in this change. + +The three GPU SACRED gates (`test_qwen36_paged_engine` 315/315, +`test_qwen27_paged_engine` 235/235, `test_qwen27n_fp8_tower_paged_engine` +236/236) are **PENDING**. `dgx.casa` was unreachable for most of the session and +came back with 60 GiB free on a 99%-full disk; the GPU window it did give was +spent on the runtime probes above, which is where the open question actually +was. This change touches only `tests/` and records — no product path — so it +cannot move a token, but the gates are owed before merge regardless. + +## Risks / decisions + +No vLLM-defined behavior is reopened. Our `ResolveMaxNumBatchedTokens` default +(`model_loader.cpp:626-641`: MoE 8192 at `max_num_seqs >= 32` else 4096; dense +2048) does NOT clearly diverge from upstream. The `2048` at +`vllm/config/scheduler.py:49` is a **pydantic testing default**; +`EngineArgs._set_default_args` overrides it to **8192/16384 on a >=70 GiB GPU**, +which GB10 is. So our MoE-8192 may BE upstream's effective default here, and the +earlier framing -- that choosing 8192 flattered us against a "true" 2048 bar -- +is withdrawn. +That deviation is unchanged here and is justified by its own recorded conc-64 +measurements — and this sweep is consistent with it being harmless for us, since +our throughput does not move with forward size. It is worth re-testing at conc-64 +now that we know the pin's own 4096-token forward is pathological, because the +`+2.7% at 8192` that motivated the default was measured against ratios that +include that pathology. + +## Stop conditions / open + +Two things this spike deliberately did not close, each with its instrument named +so neither becomes a declared ceiling. + +1. **Why a bigger prefill forward buys us nothing** (5971 -> 6150 -> 6151 tok/s + across 1024 / 2048 / 4096 tokens per forward, where the pin gains 11% over the + same first step). Next traceable hypothesis: profile BOTH arms on the + *identical forward shape* — a single 2048-token prefill step — and pair the + kernels by call count, per + [`.agents/benchmarking.md`](../benchmarking.md). Our flatness across forward + sizes is the signature of a per-token cost that does not amortize, which is + what a profile pairs off directly. Do not read the 1.21x at mnbt=8192 as + headroom: it is the pin's own 4096-token pathology. +2. **What the pin's 4096-token forward hits.** It is 30% less efficient per token + than its own 2048-token forward on this box. Worth knowing before we inherit + anything from its batching behaviour, and worth checking whether it is a + GB10/unified-memory effect rather than a vLLM one. + +Both are blocked only on GPU time, not on evidence. diff --git a/tests/vllm/v1/test_scheduler.cpp b/tests/vllm/v1/test_scheduler.cpp index 686b862c9..844ad96c9 100644 --- a/tests/vllm/v1/test_scheduler.cpp +++ b/tests/vllm/v1/test_scheduler.cpp @@ -1393,3 +1393,109 @@ TEST_CASE( CHECK(out.num_scheduled_tokens.at(req_id) == 1); CHECK(out.scheduled_spec_decode_tokens.empty()); } + +// --------------------------------------------------------------------------- +// The per-step token budget is what SPLITS a concurrent prefill wave (#669). +// +// This pins the c4 x 1024-token shape that the TTFT budget sweep runs, at two +// values of --max-num-batched-tokens, because the sweep produced a +// budget-INVARIANT wall time on our arm and the natural reading of that is "the +// budget never reaches the scheduler". It does. What the sweep measured is that +// 1 x 4096 and 2 x 2048 are the same total prefill work, not that the split +// failed to happen -- and this test is the thing that keeps that distinction +// falsifiable, since no timing gate can tell the two apart. +// +// Both arms mirror upstream's waiting loop exactly: +// budget init scheduler.cpp:465 <- scheduler.py:447 +// `token_budget > 0` guard scheduler.cpp:642 <- scheduler.py:671 +// num_new = tokens - computed scheduler.cpp:688 <- scheduler.py:825 +// min(num_new, token_budget) scheduler.cpp:697 <- scheduler.py:859-860 +// token_budget -= num_new scheduler.cpp:742 <- scheduler.py:1018-1019 +// (upstream anchors @ 5559679229bc961848b121ccdeaa8fa5d79bec98.) +// +// Note which line ENDS the 2048 step: the loop guard, not the chunking clamp. +// Two 1024-token prompts take the budget to exactly 0, so the third request is +// never even peeked and there is NO partial third chunk -- upstream stops at the +// same place for the same reason (scheduler.py:671). +// --------------------------------------------------------------------------- +TEST_CASE("Scheduler.schedule: the token budget splits a 4x1024 prefill wave") { + const int kPrompt = 1024; + + SUBCASE("budget 8192 admits the whole wave in ONE 4096-token step") { + auto scheduler = CreateScheduler(/*max_num_seqs=*/256, + /*max_num_batched_tokens=*/8192, + /*enable_chunked_prefill=*/true, + /*num_blocks=*/100000, /*block_size=*/16, + /*max_model_len=*/8192); + auto requests = CreateRequests(/*num_requests=*/4, kPrompt, + {"a", "b", "c", "d"}); + for (auto& r : requests) AddRequest(*scheduler, std::move(r)); + + auto out = scheduler->schedule(); + + CHECK(out.total_num_scheduled_tokens == 4 * kPrompt); // 4096 in one step. + CHECK(out.num_scheduled_tokens.size() == 4); + for (const char* id : {"a", "b", "c", "d"}) { + CHECK(out.num_scheduled_tokens.at(id) == kPrompt); // nobody is chunked. + } + CHECK(out.scheduled_new_reqs.size() == 4); + CHECK(scheduler->waiting->empty()); + } + + SUBCASE("budget 2048 splits the SAME wave into two 2048-token steps") { + auto scheduler = CreateScheduler(/*max_num_seqs=*/256, + /*max_num_batched_tokens=*/2048, + /*enable_chunked_prefill=*/true, + /*num_blocks=*/100000, /*block_size=*/16, + /*max_model_len=*/8192); + auto requests = CreateRequests(/*num_requests=*/4, kPrompt, + {"a", "b", "c", "d"}); + for (auto& r : requests) AddRequest(*scheduler, std::move(r)); + + // Step 0: "a" and "b" fill the budget exactly; "c"/"d" stay WAITING. + auto out0 = scheduler->schedule(); + CHECK(out0.total_num_scheduled_tokens == 2048); + CHECK(out0.num_scheduled_tokens.size() == 2); + CHECK(out0.num_scheduled_tokens.at("a") == kPrompt); + CHECK(out0.num_scheduled_tokens.at("b") == kPrompt); + // The loop guard stopped it, so there is no partial third chunk. + CHECK(out0.num_scheduled_tokens.count("c") == 0); + CHECK(out0.num_scheduled_tokens.count("d") == 0); + CHECK(scheduler->waiting->size() == 2); + + // "a" and "b" completed prefill, so each samples its first token and moves + // into decode. + scheduler->update_from_output( + out0, MakeRunnerOutput({{"a", {7}}, {"b", {7}}})); + + // Step 1: the RUNNING loop is served first (scheduler.cpp:478 <- + // scheduler.py:473), so a/b's two decode tokens come off the budget before + // the waiting loop runs. "c" then takes a full chunk and "d" gets only what + // is left -- 2048 - 2 - 1024 = 1022. THIS is the clamp at scheduler.cpp:697 + // (<- scheduler.py:859-860) firing: a genuine mid-prompt split, which the + // 8192 arm never performs. Pinning the exact 1022 is the point: an + // off-by-one in the budget arithmetic changes it, and no timing gate can. + auto out1 = scheduler->schedule(); + CHECK(out1.num_scheduled_tokens.at("a") == 1); // decode + CHECK(out1.num_scheduled_tokens.at("b") == 1); // decode + CHECK(out1.num_scheduled_tokens.at("c") == kPrompt); + CHECK(out1.num_scheduled_tokens.at("d") == 1022); // chunked remainder + CHECK(out1.total_num_scheduled_tokens == 2048); // budget filled exactly + CHECK(scheduler->waiting->empty()); + // "d" is mid-prompt, so it samples nothing this step. + Request* d = nullptr; + for (Request* r : scheduler->running) { + if (r->request_id == "d") d = r; + } + REQUIRE(d != nullptr); + CHECK(d->is_prefill_chunk); + + scheduler->update_from_output( + out1, MakeRunnerOutput({{"a", {7}}, {"b", {7}}, {"c", {7}}, {"d", {}}})); + + // Step 2: "d" finishes its prompt with the 2-token tail it was left. + auto out2 = scheduler->schedule(); + CHECK(out2.num_scheduled_tokens.at("d") == 2); + CHECK(out2.total_num_scheduled_tokens == 5); // a,b,c decode + d's tail + } +} From 87dbe377bf3565e9d3cf08a45219daaaedea88c9 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:20:17 +0200 Subject: [PATCH 19/55] feat(MODEL-MM-indextts2): CAMPPlus COMPLETE, plus a gate hole mutation exposed (#634) (#706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Completes CAMPPlus, the speaker-style encoder the talker consumes (spk_cond_mode="campplus"). FCM 2-D head -> TDNN -> three CAMDenseTDNN blocks (12/24/16) -> transits -> StatsPool -> dense, 12 cases / 27 assertions against the real upstream class executed directly. Weights are keyed by upstream state_dict NAME with the 815-tensor MANIFEST emitted into the goldens, so a tensor one side builds and the other does not is a failure; a missing tensor throws BY NAME, gated. A GATE HOLE, found by mutation and closed: changing the TDNN head's padding 2->1 compiled cleanly and the suite still PASSED, because it changes the frame count 20 -> 19 and StatsPool averages over time, so the pooled embedding absorbs it. Closed by tapping the post-TDNN activation upstream and exposing it via ForwardTrace — channels, frame count and values are gated where pooling cannot hide them. Both mutations now RED at compile_err=0. Also caught for the fourth time this campaign: a failed -Werror build re-ran the previous binary and printed SUCCESS, spotted because the CASE COUNT did not change while the compile-error count was 1. REMAINING: w2v-bert-2.0, EnhancedCodec, S2Mel, compose, routes/ABI. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- include/vllm/model_executor/models/campplus.h | 42 + scripts/gen-campplus-goldens.py | 46 + src/vllm/model_executor/models/campplus.cpp | 172 ++++ tests/vllm/models/campplus_goldens.inc | 867 ++++++++++++++++++ tests/vllm/models/test_campplus.cpp | 78 ++ 5 files changed, 1205 insertions(+) diff --git a/include/vllm/model_executor/models/campplus.h b/include/vllm/model_executor/models/campplus.h index 19a275ce9..371e3f2e2 100644 --- a/include/vllm/model_executor/models/campplus.h +++ b/include/vllm/model_executor/models/campplus.h @@ -11,6 +11,8 @@ #pragma once #include +#include +#include #include namespace vllm { @@ -134,6 +136,46 @@ std::vector ResBlock2d(const std::vector& x, int64_t in_planes, in int64_t planes, int64_t stride, const ResBlock2dWeights& weights, double eps, int64_t* out_h); + +// ── the whole encoder ─────────────────────────────────────────────────────── + +// Weights keyed by UPSTREAM state_dict name, which is what a real checkpoint +// carries. Looking a tensor up by name (rather than by position) is what makes a +// missing one throw BY NAME instead of reading as zeros. +struct CampplusWeights { + std::map> t; + const std::vector& Get(const std::string& name) const; + bool Has(const std::string& name) const { return t.count(name) != 0; } +}; + +struct CampplusParams { + int64_t feat_dim = 80; + int64_t embedding_size = 512; + int64_t growth_rate = 32; + int64_t bn_size = 4; + int64_t init_channels = 128; + int64_t m_channels = 32; // FCM's fixed width (DTDNN.py:17) + int64_t seg_len = 100; + double eps = 1e-5; +}; + +// Optional capture of an intermediate activation. StatsPool averages over time, +// so the final embedding CANNOT see a frame-count change: a wrong stride, +// dilation or padding in the TDNN head still yields a plausible embedding. The +// post-TDNN tensor is therefore gated directly rather than through the output. +struct ForwardTrace { + std::vector tdnn; + int64_t tdnn_channels = 0; + int64_t tdnn_frames = 0; +}; + +// CAMPPlus::forward (DTDNN.py:111-115). Input is [T, feat_dim] in (T, F) order, +// as `infer_v2_5.py` supplies it; the encoder permutes to (F, T) internally. +// Returns the embedding, [embedding_size]. +std::vector Forward(const CampplusParams& params, const CampplusWeights& weights, + const std::vector& feats, int64_t frames, + ForwardTrace* trace = nullptr); + } // namespace campplus } // namespace models } // namespace vllm diff --git a/scripts/gen-campplus-goldens.py b/scripts/gen-campplus-goldens.py index 4f6fed371..a31f95153 100644 --- a/scripts/gen-campplus-goldens.py +++ b/scripts/gen-campplus-goldens.py @@ -81,6 +81,38 @@ def fill(mod, prefix): rb = m.BasicResBlock(FIN, FPLANES, stride=2).eval(); fill(rb, "rb") rb_out = rb(x4) + +# ---- the WHOLE CAMPPlus at reduced config ---- +# Every parameter and buffer is rebuilt from the name stream by ONE rule the C++ +# mirrors, so the manifest below is the contract: a tensor one side builds and +# the other does not is a failure, not a silent zero. +import importlib.util as _il +_dt = _il.spec_from_file_location("dt", SRC.parent / "DTDNN.py") +_dtm = _il.module_from_spec(_dt) +import sys as _sys +_sys.path.insert(0, str(SRC.parents[4])) +_dt.loader.exec_module(_dtm) + +FEAT, EMB, GROW, BNSZ, INIT, FT = 32, 16, 2, 2, 8, 40 +full = _dtm.CAMPPlus(feat_dim=FEAT, embedding_size=EMB, growth_rate=GROW, + bn_size=BNSZ, init_channels=INIT).eval() +MANIFEST = [] +with torch.no_grad(): + for name, q in list(full.named_parameters()) + list(full.named_buffers()): + if name.endswith("num_batches_tracked"): + continue + vals = P(name, tuple(q.shape) if q.dim() else (1,), 0.3) + if name.endswith("running_var"): + vals = vals.abs() + 0.5 + q.copy_(vals.reshape(q.shape)) + MANIFEST.append((name, list(q.shape))) +feats = P("feats", (1, FT, FEAT), 1.0) # (B, T, F) -- forward permutes to (B,F,T) +_tap = {} +def _hook(_m, _i, o): _tap["tdnn"] = o.detach().clone() +full.xvector.tdnn.register_forward_hook(_hook) +full_out = full(feats) +tdnn_out = _tap["tdnn"] + def emit(f, name, t): a = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) f.write(f"inline constexpr float {name}[] = {{\n") @@ -112,5 +144,19 @@ def emit(f, name, t): f.write(f"inline constexpr int64_t {n} = {v};\n") f.write("\n") emit(f, "kResBlock", rb_out) + for n, v in (("kFeatDim", FEAT), ("kEmbedding", EMB), ("kGrowth2", GROW), + ("kBnSize", BNSZ), ("kInitChannels", INIT), ("kFullFrames", FT)): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write(f"\ninline constexpr int64_t kManifestSize = {len(MANIFEST)};\n\n") + f.write("struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2, d3; };\n") + f.write("inline constexpr ManifestEntry kManifest[] = {\n") + for nm, sh in MANIFEST: + d = list(sh) + [1, 1, 1, 1] + f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}, {d[2]}, {d[3]}}},\n') + f.write("};\n\n") + f.write(f"inline constexpr int64_t kTdnnChannels = {tdnn_out.shape[1]};\n") + f.write(f"inline constexpr int64_t kTdnnFrames = {tdnn_out.shape[2]};\n\n") + emit(f, "kTdnnOut", tdnn_out) + emit(f, "kFullEmbedding", full_out) f.write("} // namespace campplus_goldens\n") print("wrote", out, "T=", T, "seg segments=", -(-T//100)) diff --git a/src/vllm/model_executor/models/campplus.cpp b/src/vllm/model_executor/models/campplus.cpp index 1a8706b95..13aba1025 100644 --- a/src/vllm/model_executor/models/campplus.cpp +++ b/src/vllm/model_executor/models/campplus.cpp @@ -2,6 +2,8 @@ #include "vllm/model_executor/models/campplus.h" #include +#include +#include #include #include @@ -290,6 +292,176 @@ std::vector ResBlock2d(const std::vector& x, int64_t in_planes, in return out; } + +const std::vector& CampplusWeights::Get(const std::string& name) const { + const auto it = t.find(name); + VT_CHECK(it != t.end(), "campplus: missing checkpoint tensor '" + name + "'"); + return it->second; +} + +namespace { + +// Conv1d with an EXPLICIT stride and padding (the TDNN head strides TIME by 2, +// unlike every `same` convolution elsewhere in this file). +std::vector Conv1dStrided(const std::vector& in, int64_t in_ch, int64_t len, + const std::vector& w, const std::vector& bias, + int64_t out_ch, int64_t kernel, int64_t stride, int64_t dilation, + int64_t pad, int64_t* out_len) { + const int64_t L = (len + 2 * pad - dilation * (kernel - 1) - 1) / stride + 1; + std::vector out(static_cast(out_ch * L)); + for (int64_t o = 0; o < out_ch; ++o) { + for (int64_t t = 0; t < L; ++t) { + double acc = bias.empty() ? 0.0 : static_cast(bias[static_cast(o)]); + for (int64_t c = 0; c < in_ch; ++c) { + for (int64_t k = 0; k < kernel; ++k) { + const int64_t src = t * stride + k * dilation - pad; + if (src < 0 || src >= len) continue; + acc += static_cast(w[static_cast((o * in_ch + c) * kernel + k)]) * + static_cast(in[static_cast(c * len + src)]); + } + } + out[static_cast(o * L + t)] = static_cast(acc); + } + } + *out_len = L; + return out; +} + +ResBlock2dWeights ResW(const CampplusWeights& w, const std::string& p, bool shortcut) { + ResBlock2dWeights r; + r.conv1 = w.Get(p + ".conv1.weight"); + r.bn1_gamma = w.Get(p + ".bn1.weight"); r.bn1_beta = w.Get(p + ".bn1.bias"); + r.bn1_mean = w.Get(p + ".bn1.running_mean"); r.bn1_var = w.Get(p + ".bn1.running_var"); + r.conv2 = w.Get(p + ".conv2.weight"); + r.bn2_gamma = w.Get(p + ".bn2.weight"); r.bn2_beta = w.Get(p + ".bn2.bias"); + r.bn2_mean = w.Get(p + ".bn2.running_mean"); r.bn2_var = w.Get(p + ".bn2.running_var"); + r.has_shortcut = shortcut; + if (shortcut) { + r.short_conv = w.Get(p + ".shortcut.0.weight"); + r.short_gamma = w.Get(p + ".shortcut.1.weight"); r.short_beta = w.Get(p + ".shortcut.1.bias"); + r.short_mean = w.Get(p + ".shortcut.1.running_mean"); + r.short_var = w.Get(p + ".shortcut.1.running_var"); + } + return r; +} + +DenseTdnnLayerWeights DlW(const CampplusWeights& w, const std::string& p) { + DenseTdnnLayerWeights d; + d.bn1_gamma = w.Get(p + ".nonlinear1.batchnorm.weight"); + d.bn1_beta = w.Get(p + ".nonlinear1.batchnorm.bias"); + d.bn1_mean = w.Get(p + ".nonlinear1.batchnorm.running_mean"); + d.bn1_var = w.Get(p + ".nonlinear1.batchnorm.running_var"); + d.linear1 = w.Get(p + ".linear1.weight"); + d.bn2_gamma = w.Get(p + ".nonlinear2.batchnorm.weight"); + d.bn2_beta = w.Get(p + ".nonlinear2.batchnorm.bias"); + d.bn2_mean = w.Get(p + ".nonlinear2.batchnorm.running_mean"); + d.bn2_var = w.Get(p + ".nonlinear2.batchnorm.running_var"); + d.cam.linear_local = w.Get(p + ".cam_layer.linear_local.weight"); + d.cam.linear1_weight = w.Get(p + ".cam_layer.linear1.weight"); + d.cam.linear1_bias = w.Get(p + ".cam_layer.linear1.bias"); + d.cam.linear2_weight = w.Get(p + ".cam_layer.linear2.weight"); + d.cam.linear2_bias = w.Get(p + ".cam_layer.linear2.bias"); + return d; +} + +} // namespace + +std::vector Forward(const CampplusParams& p, const CampplusWeights& w, + const std::vector& feats, int64_t frames, ForwardTrace* trace) { + VT_CHECK(feats.size() == static_cast(frames * p.feat_dim), "campplus: feats shape"); + + // forward() permutes (T, F) -> (F, T), then FCM treats it as a 1-channel image + // of height feat_dim and width T. + std::vector img(feats.size()); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t f = 0; f < p.feat_dim; ++f) { + img[static_cast(f * frames + t)] = feats[static_cast(t * p.feat_dim + f)]; + } + } + + int64_t h = p.feat_dim, wid = frames, oh = 0, ow = 0; + std::vector x = Conv2d(img, 1, h, wid, w.Get("head.conv1.weight"), p.m_channels, 3, 1, 1, 1, + &oh, &ow); + BatchNorm2dEvalInPlace(x, p.m_channels, oh * ow, w.Get("head.bn1.weight"), w.Get("head.bn1.bias"), + w.Get("head.bn1.running_mean"), w.Get("head.bn1.running_var"), p.eps); + for (float& v : x) v = v > 0.0F ? v : 0.0F; + h = oh; wid = ow; + + // layer1 / layer2: two BasicResBlocks each, the FIRST striding frequency by 2. + for (int layer = 1; layer <= 2; ++layer) { + for (int b = 0; b < 2; ++b) { + const std::string pre = "head.layer" + std::to_string(layer) + "." + std::to_string(b); + const int64_t stride = (b == 0) ? 2 : 1; + const bool shortcut = w.Has(pre + ".shortcut.0.weight"); + int64_t nh = 0; + x = ResBlock2d(x, p.m_channels, h, wid, p.m_channels, stride, ResW(w, pre, shortcut), p.eps, + &nh); + h = nh; + } + } + + x = Conv2d(x, p.m_channels, h, wid, w.Get("head.conv2.weight"), p.m_channels, 3, 2, 1, 1, &oh, &ow); + BatchNorm2dEvalInPlace(x, p.m_channels, oh * ow, w.Get("head.bn2.weight"), w.Get("head.bn2.bias"), + w.Get("head.bn2.running_mean"), w.Get("head.bn2.running_var"), p.eps); + for (float& v : x) v = v > 0.0F ? v : 0.0F; + h = oh; wid = ow; + + // reshape (C, H, W) -> (C*H, W): the channel-major layout already matches. + int64_t channels = p.m_channels * h; + int64_t len = wid; + + // xvector.tdnn: Conv1d(k=5, stride=2, padding=(5-1)//2*1) then batchnorm-relu. + int64_t nl = 0; + x = Conv1dStrided(x, channels, len, w.Get("xvector.tdnn.linear.weight"), {}, p.init_channels, 5, 2, + 1, 2, &nl); + len = nl; + channels = p.init_channels; + x = BatchNormRelu(x, channels, len, w.Get("xvector.tdnn.nonlinear.batchnorm.weight"), + w.Get("xvector.tdnn.nonlinear.batchnorm.bias"), + w.Get("xvector.tdnn.nonlinear.batchnorm.running_mean"), + w.Get("xvector.tdnn.nonlinear.batchnorm.running_var"), p.eps); + + if (trace != nullptr) { + trace->tdnn = x; + trace->tdnn_channels = channels; + trace->tdnn_frames = len; + } + + const int64_t bn_channels = p.bn_size * p.growth_rate; + const int64_t counts[3] = {12, 24, 16}; + const int64_t dilations[3] = {1, 2, 2}; + for (int i = 0; i < 3; ++i) { + std::vector layers; + for (int64_t j = 0; j < counts[i]; ++j) { + layers.push_back(DlW(w, "xvector.block" + std::to_string(i + 1) + ".tdnnd" + + std::to_string(j + 1))); + } + x = DenseTdnnBlock(x, channels, len, bn_channels, p.growth_rate, 3, dilations[i], p.seg_len, + layers, p.eps); + channels += counts[i] * p.growth_rate; + + const std::string tp = "xvector.transit" + std::to_string(i + 1); + x = TransitLayer(x, channels, len, channels / 2, w.Get(tp + ".nonlinear.batchnorm.weight"), + w.Get(tp + ".nonlinear.batchnorm.bias"), + w.Get(tp + ".nonlinear.batchnorm.running_mean"), + w.Get(tp + ".nonlinear.batchnorm.running_var"), w.Get(tp + ".linear.weight"), + {}, p.eps); + channels /= 2; + } + + x = BatchNormRelu(x, channels, len, w.Get("xvector.out_nonlinear.batchnorm.weight"), + w.Get("xvector.out_nonlinear.batchnorm.bias"), + w.Get("xvector.out_nonlinear.batchnorm.running_mean"), + w.Get("xvector.out_nonlinear.batchnorm.running_var"), p.eps); + + const std::vector stats = StatsPool(x, channels, len); + // The final dense is `batchnorm_`: affine=false AND no relu. + return DenseLayer(stats, 2 * channels, 1, p.embedding_size, w.Get("xvector.dense.linear.weight"), + {}, {}, {}, w.Get("xvector.dense.nonlinear.batchnorm.running_mean"), + w.Get("xvector.dense.nonlinear.batchnorm.running_var"), p.eps, + /*apply_relu=*/false); +} + } // namespace campplus } // namespace models } // namespace vllm diff --git a/tests/vllm/models/campplus_goldens.inc b/tests/vllm/models/campplus_goldens.inc index d04196044..1aefb0896 100644 --- a/tests/vllm/models/campplus_goldens.inc +++ b/tests/vllm/models/campplus_goldens.inc @@ -1641,4 +1641,871 @@ inline constexpr float kResBlock[] = { 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 8.955776691e-03F, 0.000000000e+00F, 0.000000000e+00F, }; +inline constexpr int64_t kFeatDim = 32; +inline constexpr int64_t kEmbedding = 16; +inline constexpr int64_t kGrowth2 = 2; +inline constexpr int64_t kBnSize = 2; +inline constexpr int64_t kInitChannels = 8; +inline constexpr int64_t kFullFrames = 40; + +inline constexpr int64_t kManifestSize = 815; + +struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2, d3; }; +inline constexpr ManifestEntry kManifest[] = { + {"head.conv1.weight", 4, 32, 1, 3, 3}, + {"head.bn1.weight", 1, 32, 1, 1, 1}, + {"head.bn1.bias", 1, 32, 1, 1, 1}, + {"head.layer1.0.conv1.weight", 4, 32, 32, 3, 3}, + {"head.layer1.0.bn1.weight", 1, 32, 1, 1, 1}, + {"head.layer1.0.bn1.bias", 1, 32, 1, 1, 1}, + {"head.layer1.0.conv2.weight", 4, 32, 32, 3, 3}, + {"head.layer1.0.bn2.weight", 1, 32, 1, 1, 1}, + {"head.layer1.0.bn2.bias", 1, 32, 1, 1, 1}, + {"head.layer1.0.shortcut.0.weight", 4, 32, 32, 1, 1}, + {"head.layer1.0.shortcut.1.weight", 1, 32, 1, 1, 1}, + {"head.layer1.0.shortcut.1.bias", 1, 32, 1, 1, 1}, + {"head.layer1.1.conv1.weight", 4, 32, 32, 3, 3}, + {"head.layer1.1.bn1.weight", 1, 32, 1, 1, 1}, + {"head.layer1.1.bn1.bias", 1, 32, 1, 1, 1}, + {"head.layer1.1.conv2.weight", 4, 32, 32, 3, 3}, + {"head.layer1.1.bn2.weight", 1, 32, 1, 1, 1}, + {"head.layer1.1.bn2.bias", 1, 32, 1, 1, 1}, + {"head.layer2.0.conv1.weight", 4, 32, 32, 3, 3}, + {"head.layer2.0.bn1.weight", 1, 32, 1, 1, 1}, + {"head.layer2.0.bn1.bias", 1, 32, 1, 1, 1}, + {"head.layer2.0.conv2.weight", 4, 32, 32, 3, 3}, + {"head.layer2.0.bn2.weight", 1, 32, 1, 1, 1}, + {"head.layer2.0.bn2.bias", 1, 32, 1, 1, 1}, + {"head.layer2.0.shortcut.0.weight", 4, 32, 32, 1, 1}, + {"head.layer2.0.shortcut.1.weight", 1, 32, 1, 1, 1}, + {"head.layer2.0.shortcut.1.bias", 1, 32, 1, 1, 1}, + {"head.layer2.1.conv1.weight", 4, 32, 32, 3, 3}, + {"head.layer2.1.bn1.weight", 1, 32, 1, 1, 1}, + {"head.layer2.1.bn1.bias", 1, 32, 1, 1, 1}, + {"head.layer2.1.conv2.weight", 4, 32, 32, 3, 3}, + {"head.layer2.1.bn2.weight", 1, 32, 1, 1, 1}, + {"head.layer2.1.bn2.bias", 1, 32, 1, 1, 1}, + {"head.conv2.weight", 4, 32, 32, 3, 3}, + {"head.bn2.weight", 1, 32, 1, 1, 1}, + {"head.bn2.bias", 1, 32, 1, 1, 1}, + {"xvector.tdnn.linear.weight", 3, 8, 128, 5, 1}, + {"xvector.tdnn.nonlinear.batchnorm.weight", 1, 8, 1, 1, 1}, + {"xvector.tdnn.nonlinear.batchnorm.bias", 1, 8, 1, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear1.batchnorm.weight", 1, 8, 1, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear1.batchnorm.bias", 1, 8, 1, 1, 1}, + {"xvector.block1.tdnnd1.linear1.weight", 3, 4, 8, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd1.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd1.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd1.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd1.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd1.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear1.batchnorm.weight", 1, 10, 1, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear1.batchnorm.bias", 1, 10, 1, 1, 1}, + {"xvector.block1.tdnnd2.linear1.weight", 3, 4, 10, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd2.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd2.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd2.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd2.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd2.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear1.batchnorm.weight", 1, 12, 1, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear1.batchnorm.bias", 1, 12, 1, 1, 1}, + {"xvector.block1.tdnnd3.linear1.weight", 3, 4, 12, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd3.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd3.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd3.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd3.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd3.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear1.batchnorm.weight", 1, 14, 1, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear1.batchnorm.bias", 1, 14, 1, 1, 1}, + {"xvector.block1.tdnnd4.linear1.weight", 3, 4, 14, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd4.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd4.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd4.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd4.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd4.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear1.batchnorm.weight", 1, 16, 1, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear1.batchnorm.bias", 1, 16, 1, 1, 1}, + {"xvector.block1.tdnnd5.linear1.weight", 3, 4, 16, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd5.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd5.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd5.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd5.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd5.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear1.batchnorm.weight", 1, 18, 1, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear1.batchnorm.bias", 1, 18, 1, 1, 1}, + {"xvector.block1.tdnnd6.linear1.weight", 3, 4, 18, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd6.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd6.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd6.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd6.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd6.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear1.batchnorm.weight", 1, 20, 1, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear1.batchnorm.bias", 1, 20, 1, 1, 1}, + {"xvector.block1.tdnnd7.linear1.weight", 3, 4, 20, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd7.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd7.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd7.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd7.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd7.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear1.batchnorm.weight", 1, 22, 1, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear1.batchnorm.bias", 1, 22, 1, 1, 1}, + {"xvector.block1.tdnnd8.linear1.weight", 3, 4, 22, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd8.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd8.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd8.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd8.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd8.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear1.batchnorm.weight", 1, 24, 1, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear1.batchnorm.bias", 1, 24, 1, 1, 1}, + {"xvector.block1.tdnnd9.linear1.weight", 3, 4, 24, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd9.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd9.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd9.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd9.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd9.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear1.batchnorm.weight", 1, 26, 1, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear1.batchnorm.bias", 1, 26, 1, 1, 1}, + {"xvector.block1.tdnnd10.linear1.weight", 3, 4, 26, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd10.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd10.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd10.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd10.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd10.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear1.batchnorm.weight", 1, 28, 1, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear1.batchnorm.bias", 1, 28, 1, 1, 1}, + {"xvector.block1.tdnnd11.linear1.weight", 3, 4, 28, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd11.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd11.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd11.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd11.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd11.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear1.batchnorm.weight", 1, 30, 1, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear1.batchnorm.bias", 1, 30, 1, 1, 1}, + {"xvector.block1.tdnnd12.linear1.weight", 3, 4, 30, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd12.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block1.tdnnd12.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block1.tdnnd12.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block1.tdnnd12.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block1.tdnnd12.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.transit1.nonlinear.batchnorm.weight", 1, 32, 1, 1, 1}, + {"xvector.transit1.nonlinear.batchnorm.bias", 1, 32, 1, 1, 1}, + {"xvector.transit1.linear.weight", 3, 16, 32, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear1.batchnorm.weight", 1, 16, 1, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear1.batchnorm.bias", 1, 16, 1, 1, 1}, + {"xvector.block2.tdnnd1.linear1.weight", 3, 4, 16, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd1.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd1.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd1.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd1.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd1.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear1.batchnorm.weight", 1, 18, 1, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear1.batchnorm.bias", 1, 18, 1, 1, 1}, + {"xvector.block2.tdnnd2.linear1.weight", 3, 4, 18, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd2.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd2.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd2.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd2.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd2.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear1.batchnorm.weight", 1, 20, 1, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear1.batchnorm.bias", 1, 20, 1, 1, 1}, + {"xvector.block2.tdnnd3.linear1.weight", 3, 4, 20, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd3.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd3.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd3.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd3.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd3.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear1.batchnorm.weight", 1, 22, 1, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear1.batchnorm.bias", 1, 22, 1, 1, 1}, + {"xvector.block2.tdnnd4.linear1.weight", 3, 4, 22, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd4.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd4.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd4.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd4.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd4.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear1.batchnorm.weight", 1, 24, 1, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear1.batchnorm.bias", 1, 24, 1, 1, 1}, + {"xvector.block2.tdnnd5.linear1.weight", 3, 4, 24, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd5.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd5.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd5.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd5.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd5.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear1.batchnorm.weight", 1, 26, 1, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear1.batchnorm.bias", 1, 26, 1, 1, 1}, + {"xvector.block2.tdnnd6.linear1.weight", 3, 4, 26, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd6.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd6.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd6.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd6.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd6.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear1.batchnorm.weight", 1, 28, 1, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear1.batchnorm.bias", 1, 28, 1, 1, 1}, + {"xvector.block2.tdnnd7.linear1.weight", 3, 4, 28, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd7.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd7.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd7.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd7.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd7.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear1.batchnorm.weight", 1, 30, 1, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear1.batchnorm.bias", 1, 30, 1, 1, 1}, + {"xvector.block2.tdnnd8.linear1.weight", 3, 4, 30, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd8.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd8.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd8.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd8.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd8.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear1.batchnorm.weight", 1, 32, 1, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear1.batchnorm.bias", 1, 32, 1, 1, 1}, + {"xvector.block2.tdnnd9.linear1.weight", 3, 4, 32, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd9.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd9.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd9.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd9.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd9.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear1.batchnorm.weight", 1, 34, 1, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear1.batchnorm.bias", 1, 34, 1, 1, 1}, + {"xvector.block2.tdnnd10.linear1.weight", 3, 4, 34, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd10.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd10.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd10.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd10.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd10.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear1.batchnorm.weight", 1, 36, 1, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear1.batchnorm.bias", 1, 36, 1, 1, 1}, + {"xvector.block2.tdnnd11.linear1.weight", 3, 4, 36, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd11.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd11.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd11.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd11.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd11.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear1.batchnorm.weight", 1, 38, 1, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear1.batchnorm.bias", 1, 38, 1, 1, 1}, + {"xvector.block2.tdnnd12.linear1.weight", 3, 4, 38, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd12.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd12.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd12.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd12.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd12.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear1.batchnorm.weight", 1, 40, 1, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear1.batchnorm.bias", 1, 40, 1, 1, 1}, + {"xvector.block2.tdnnd13.linear1.weight", 3, 4, 40, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd13.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd13.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd13.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd13.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd13.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear1.batchnorm.weight", 1, 42, 1, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear1.batchnorm.bias", 1, 42, 1, 1, 1}, + {"xvector.block2.tdnnd14.linear1.weight", 3, 4, 42, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd14.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd14.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd14.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd14.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd14.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear1.batchnorm.weight", 1, 44, 1, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear1.batchnorm.bias", 1, 44, 1, 1, 1}, + {"xvector.block2.tdnnd15.linear1.weight", 3, 4, 44, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd15.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd15.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd15.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd15.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd15.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear1.batchnorm.weight", 1, 46, 1, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear1.batchnorm.bias", 1, 46, 1, 1, 1}, + {"xvector.block2.tdnnd16.linear1.weight", 3, 4, 46, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd16.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd16.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd16.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd16.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd16.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear1.batchnorm.weight", 1, 48, 1, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear1.batchnorm.bias", 1, 48, 1, 1, 1}, + {"xvector.block2.tdnnd17.linear1.weight", 3, 4, 48, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd17.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd17.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd17.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd17.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd17.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear1.batchnorm.weight", 1, 50, 1, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear1.batchnorm.bias", 1, 50, 1, 1, 1}, + {"xvector.block2.tdnnd18.linear1.weight", 3, 4, 50, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd18.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd18.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd18.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd18.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd18.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear1.batchnorm.weight", 1, 52, 1, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear1.batchnorm.bias", 1, 52, 1, 1, 1}, + {"xvector.block2.tdnnd19.linear1.weight", 3, 4, 52, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd19.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd19.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd19.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd19.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd19.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear1.batchnorm.weight", 1, 54, 1, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear1.batchnorm.bias", 1, 54, 1, 1, 1}, + {"xvector.block2.tdnnd20.linear1.weight", 3, 4, 54, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd20.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd20.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd20.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd20.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd20.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear1.batchnorm.weight", 1, 56, 1, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear1.batchnorm.bias", 1, 56, 1, 1, 1}, + {"xvector.block2.tdnnd21.linear1.weight", 3, 4, 56, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd21.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd21.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd21.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd21.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd21.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear1.batchnorm.weight", 1, 58, 1, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear1.batchnorm.bias", 1, 58, 1, 1, 1}, + {"xvector.block2.tdnnd22.linear1.weight", 3, 4, 58, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd22.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd22.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd22.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd22.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd22.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear1.batchnorm.weight", 1, 60, 1, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear1.batchnorm.bias", 1, 60, 1, 1, 1}, + {"xvector.block2.tdnnd23.linear1.weight", 3, 4, 60, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd23.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd23.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd23.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd23.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd23.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear1.batchnorm.weight", 1, 62, 1, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear1.batchnorm.bias", 1, 62, 1, 1, 1}, + {"xvector.block2.tdnnd24.linear1.weight", 3, 4, 62, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd24.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block2.tdnnd24.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block2.tdnnd24.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block2.tdnnd24.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block2.tdnnd24.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.transit2.nonlinear.batchnorm.weight", 1, 64, 1, 1, 1}, + {"xvector.transit2.nonlinear.batchnorm.bias", 1, 64, 1, 1, 1}, + {"xvector.transit2.linear.weight", 3, 32, 64, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear1.batchnorm.weight", 1, 32, 1, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear1.batchnorm.bias", 1, 32, 1, 1, 1}, + {"xvector.block3.tdnnd1.linear1.weight", 3, 4, 32, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd1.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd1.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd1.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd1.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd1.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear1.batchnorm.weight", 1, 34, 1, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear1.batchnorm.bias", 1, 34, 1, 1, 1}, + {"xvector.block3.tdnnd2.linear1.weight", 3, 4, 34, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd2.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd2.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd2.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd2.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd2.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear1.batchnorm.weight", 1, 36, 1, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear1.batchnorm.bias", 1, 36, 1, 1, 1}, + {"xvector.block3.tdnnd3.linear1.weight", 3, 4, 36, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd3.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd3.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd3.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd3.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd3.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear1.batchnorm.weight", 1, 38, 1, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear1.batchnorm.bias", 1, 38, 1, 1, 1}, + {"xvector.block3.tdnnd4.linear1.weight", 3, 4, 38, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd4.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd4.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd4.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd4.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd4.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear1.batchnorm.weight", 1, 40, 1, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear1.batchnorm.bias", 1, 40, 1, 1, 1}, + {"xvector.block3.tdnnd5.linear1.weight", 3, 4, 40, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd5.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd5.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd5.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd5.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd5.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear1.batchnorm.weight", 1, 42, 1, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear1.batchnorm.bias", 1, 42, 1, 1, 1}, + {"xvector.block3.tdnnd6.linear1.weight", 3, 4, 42, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd6.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd6.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd6.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd6.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd6.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear1.batchnorm.weight", 1, 44, 1, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear1.batchnorm.bias", 1, 44, 1, 1, 1}, + {"xvector.block3.tdnnd7.linear1.weight", 3, 4, 44, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd7.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd7.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd7.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd7.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd7.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear1.batchnorm.weight", 1, 46, 1, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear1.batchnorm.bias", 1, 46, 1, 1, 1}, + {"xvector.block3.tdnnd8.linear1.weight", 3, 4, 46, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd8.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd8.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd8.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd8.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd8.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear1.batchnorm.weight", 1, 48, 1, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear1.batchnorm.bias", 1, 48, 1, 1, 1}, + {"xvector.block3.tdnnd9.linear1.weight", 3, 4, 48, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd9.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd9.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd9.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd9.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd9.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear1.batchnorm.weight", 1, 50, 1, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear1.batchnorm.bias", 1, 50, 1, 1, 1}, + {"xvector.block3.tdnnd10.linear1.weight", 3, 4, 50, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd10.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd10.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd10.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd10.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd10.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear1.batchnorm.weight", 1, 52, 1, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear1.batchnorm.bias", 1, 52, 1, 1, 1}, + {"xvector.block3.tdnnd11.linear1.weight", 3, 4, 52, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd11.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd11.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd11.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd11.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd11.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear1.batchnorm.weight", 1, 54, 1, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear1.batchnorm.bias", 1, 54, 1, 1, 1}, + {"xvector.block3.tdnnd12.linear1.weight", 3, 4, 54, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd12.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd12.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd12.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd12.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd12.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear1.batchnorm.weight", 1, 56, 1, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear1.batchnorm.bias", 1, 56, 1, 1, 1}, + {"xvector.block3.tdnnd13.linear1.weight", 3, 4, 56, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd13.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd13.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd13.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd13.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd13.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear1.batchnorm.weight", 1, 58, 1, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear1.batchnorm.bias", 1, 58, 1, 1, 1}, + {"xvector.block3.tdnnd14.linear1.weight", 3, 4, 58, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd14.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd14.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd14.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd14.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd14.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear1.batchnorm.weight", 1, 60, 1, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear1.batchnorm.bias", 1, 60, 1, 1, 1}, + {"xvector.block3.tdnnd15.linear1.weight", 3, 4, 60, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd15.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd15.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd15.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd15.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd15.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear1.batchnorm.weight", 1, 62, 1, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear1.batchnorm.bias", 1, 62, 1, 1, 1}, + {"xvector.block3.tdnnd16.linear1.weight", 3, 4, 62, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear2.batchnorm.weight", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear2.batchnorm.bias", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd16.cam_layer.linear_local.weight", 3, 2, 4, 3, 1}, + {"xvector.block3.tdnnd16.cam_layer.linear1.weight", 3, 2, 4, 1, 1}, + {"xvector.block3.tdnnd16.cam_layer.linear1.bias", 1, 2, 1, 1, 1}, + {"xvector.block3.tdnnd16.cam_layer.linear2.weight", 3, 2, 2, 1, 1}, + {"xvector.block3.tdnnd16.cam_layer.linear2.bias", 1, 2, 1, 1, 1}, + {"xvector.transit3.nonlinear.batchnorm.weight", 1, 64, 1, 1, 1}, + {"xvector.transit3.nonlinear.batchnorm.bias", 1, 64, 1, 1, 1}, + {"xvector.transit3.linear.weight", 3, 32, 64, 1, 1}, + {"xvector.out_nonlinear.batchnorm.weight", 1, 32, 1, 1, 1}, + {"xvector.out_nonlinear.batchnorm.bias", 1, 32, 1, 1, 1}, + {"xvector.dense.linear.weight", 3, 16, 64, 1, 1}, + {"head.bn1.running_mean", 1, 32, 1, 1, 1}, + {"head.bn1.running_var", 1, 32, 1, 1, 1}, + {"head.layer1.0.bn1.running_mean", 1, 32, 1, 1, 1}, + {"head.layer1.0.bn1.running_var", 1, 32, 1, 1, 1}, + {"head.layer1.0.bn2.running_mean", 1, 32, 1, 1, 1}, + {"head.layer1.0.bn2.running_var", 1, 32, 1, 1, 1}, + {"head.layer1.0.shortcut.1.running_mean", 1, 32, 1, 1, 1}, + {"head.layer1.0.shortcut.1.running_var", 1, 32, 1, 1, 1}, + {"head.layer1.1.bn1.running_mean", 1, 32, 1, 1, 1}, + {"head.layer1.1.bn1.running_var", 1, 32, 1, 1, 1}, + {"head.layer1.1.bn2.running_mean", 1, 32, 1, 1, 1}, + {"head.layer1.1.bn2.running_var", 1, 32, 1, 1, 1}, + {"head.layer2.0.bn1.running_mean", 1, 32, 1, 1, 1}, + {"head.layer2.0.bn1.running_var", 1, 32, 1, 1, 1}, + {"head.layer2.0.bn2.running_mean", 1, 32, 1, 1, 1}, + {"head.layer2.0.bn2.running_var", 1, 32, 1, 1, 1}, + {"head.layer2.0.shortcut.1.running_mean", 1, 32, 1, 1, 1}, + {"head.layer2.0.shortcut.1.running_var", 1, 32, 1, 1, 1}, + {"head.layer2.1.bn1.running_mean", 1, 32, 1, 1, 1}, + {"head.layer2.1.bn1.running_var", 1, 32, 1, 1, 1}, + {"head.layer2.1.bn2.running_mean", 1, 32, 1, 1, 1}, + {"head.layer2.1.bn2.running_var", 1, 32, 1, 1, 1}, + {"head.bn2.running_mean", 1, 32, 1, 1, 1}, + {"head.bn2.running_var", 1, 32, 1, 1, 1}, + {"xvector.tdnn.nonlinear.batchnorm.running_mean", 1, 8, 1, 1, 1}, + {"xvector.tdnn.nonlinear.batchnorm.running_var", 1, 8, 1, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear1.batchnorm.running_mean", 1, 8, 1, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear1.batchnorm.running_var", 1, 8, 1, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd1.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear1.batchnorm.running_mean", 1, 10, 1, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear1.batchnorm.running_var", 1, 10, 1, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd2.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear1.batchnorm.running_mean", 1, 12, 1, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear1.batchnorm.running_var", 1, 12, 1, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd3.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear1.batchnorm.running_mean", 1, 14, 1, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear1.batchnorm.running_var", 1, 14, 1, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd4.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear1.batchnorm.running_mean", 1, 16, 1, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear1.batchnorm.running_var", 1, 16, 1, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd5.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear1.batchnorm.running_mean", 1, 18, 1, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear1.batchnorm.running_var", 1, 18, 1, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd6.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear1.batchnorm.running_mean", 1, 20, 1, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear1.batchnorm.running_var", 1, 20, 1, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd7.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear1.batchnorm.running_mean", 1, 22, 1, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear1.batchnorm.running_var", 1, 22, 1, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd8.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear1.batchnorm.running_mean", 1, 24, 1, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear1.batchnorm.running_var", 1, 24, 1, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd9.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear1.batchnorm.running_mean", 1, 26, 1, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear1.batchnorm.running_var", 1, 26, 1, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd10.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear1.batchnorm.running_mean", 1, 28, 1, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear1.batchnorm.running_var", 1, 28, 1, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd11.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear1.batchnorm.running_mean", 1, 30, 1, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear1.batchnorm.running_var", 1, 30, 1, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block1.tdnnd12.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.transit1.nonlinear.batchnorm.running_mean", 1, 32, 1, 1, 1}, + {"xvector.transit1.nonlinear.batchnorm.running_var", 1, 32, 1, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear1.batchnorm.running_mean", 1, 16, 1, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear1.batchnorm.running_var", 1, 16, 1, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd1.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear1.batchnorm.running_mean", 1, 18, 1, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear1.batchnorm.running_var", 1, 18, 1, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd2.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear1.batchnorm.running_mean", 1, 20, 1, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear1.batchnorm.running_var", 1, 20, 1, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd3.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear1.batchnorm.running_mean", 1, 22, 1, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear1.batchnorm.running_var", 1, 22, 1, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd4.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear1.batchnorm.running_mean", 1, 24, 1, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear1.batchnorm.running_var", 1, 24, 1, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd5.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear1.batchnorm.running_mean", 1, 26, 1, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear1.batchnorm.running_var", 1, 26, 1, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd6.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear1.batchnorm.running_mean", 1, 28, 1, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear1.batchnorm.running_var", 1, 28, 1, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd7.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear1.batchnorm.running_mean", 1, 30, 1, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear1.batchnorm.running_var", 1, 30, 1, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd8.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear1.batchnorm.running_mean", 1, 32, 1, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear1.batchnorm.running_var", 1, 32, 1, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd9.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear1.batchnorm.running_mean", 1, 34, 1, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear1.batchnorm.running_var", 1, 34, 1, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd10.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear1.batchnorm.running_mean", 1, 36, 1, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear1.batchnorm.running_var", 1, 36, 1, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd11.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear1.batchnorm.running_mean", 1, 38, 1, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear1.batchnorm.running_var", 1, 38, 1, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd12.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear1.batchnorm.running_mean", 1, 40, 1, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear1.batchnorm.running_var", 1, 40, 1, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd13.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear1.batchnorm.running_mean", 1, 42, 1, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear1.batchnorm.running_var", 1, 42, 1, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd14.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear1.batchnorm.running_mean", 1, 44, 1, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear1.batchnorm.running_var", 1, 44, 1, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd15.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear1.batchnorm.running_mean", 1, 46, 1, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear1.batchnorm.running_var", 1, 46, 1, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd16.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear1.batchnorm.running_mean", 1, 48, 1, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear1.batchnorm.running_var", 1, 48, 1, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd17.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear1.batchnorm.running_mean", 1, 50, 1, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear1.batchnorm.running_var", 1, 50, 1, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd18.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear1.batchnorm.running_mean", 1, 52, 1, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear1.batchnorm.running_var", 1, 52, 1, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd19.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear1.batchnorm.running_mean", 1, 54, 1, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear1.batchnorm.running_var", 1, 54, 1, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd20.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear1.batchnorm.running_mean", 1, 56, 1, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear1.batchnorm.running_var", 1, 56, 1, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd21.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear1.batchnorm.running_mean", 1, 58, 1, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear1.batchnorm.running_var", 1, 58, 1, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd22.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear1.batchnorm.running_mean", 1, 60, 1, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear1.batchnorm.running_var", 1, 60, 1, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd23.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear1.batchnorm.running_mean", 1, 62, 1, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear1.batchnorm.running_var", 1, 62, 1, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block2.tdnnd24.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.transit2.nonlinear.batchnorm.running_mean", 1, 64, 1, 1, 1}, + {"xvector.transit2.nonlinear.batchnorm.running_var", 1, 64, 1, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear1.batchnorm.running_mean", 1, 32, 1, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear1.batchnorm.running_var", 1, 32, 1, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd1.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear1.batchnorm.running_mean", 1, 34, 1, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear1.batchnorm.running_var", 1, 34, 1, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd2.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear1.batchnorm.running_mean", 1, 36, 1, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear1.batchnorm.running_var", 1, 36, 1, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd3.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear1.batchnorm.running_mean", 1, 38, 1, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear1.batchnorm.running_var", 1, 38, 1, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd4.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear1.batchnorm.running_mean", 1, 40, 1, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear1.batchnorm.running_var", 1, 40, 1, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd5.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear1.batchnorm.running_mean", 1, 42, 1, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear1.batchnorm.running_var", 1, 42, 1, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd6.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear1.batchnorm.running_mean", 1, 44, 1, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear1.batchnorm.running_var", 1, 44, 1, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd7.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear1.batchnorm.running_mean", 1, 46, 1, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear1.batchnorm.running_var", 1, 46, 1, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd8.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear1.batchnorm.running_mean", 1, 48, 1, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear1.batchnorm.running_var", 1, 48, 1, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd9.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear1.batchnorm.running_mean", 1, 50, 1, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear1.batchnorm.running_var", 1, 50, 1, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd10.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear1.batchnorm.running_mean", 1, 52, 1, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear1.batchnorm.running_var", 1, 52, 1, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd11.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear1.batchnorm.running_mean", 1, 54, 1, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear1.batchnorm.running_var", 1, 54, 1, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd12.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear1.batchnorm.running_mean", 1, 56, 1, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear1.batchnorm.running_var", 1, 56, 1, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd13.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear1.batchnorm.running_mean", 1, 58, 1, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear1.batchnorm.running_var", 1, 58, 1, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd14.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear1.batchnorm.running_mean", 1, 60, 1, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear1.batchnorm.running_var", 1, 60, 1, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd15.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear1.batchnorm.running_mean", 1, 62, 1, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear1.batchnorm.running_var", 1, 62, 1, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear2.batchnorm.running_mean", 1, 4, 1, 1, 1}, + {"xvector.block3.tdnnd16.nonlinear2.batchnorm.running_var", 1, 4, 1, 1, 1}, + {"xvector.transit3.nonlinear.batchnorm.running_mean", 1, 64, 1, 1, 1}, + {"xvector.transit3.nonlinear.batchnorm.running_var", 1, 64, 1, 1, 1}, + {"xvector.out_nonlinear.batchnorm.running_mean", 1, 32, 1, 1, 1}, + {"xvector.out_nonlinear.batchnorm.running_var", 1, 32, 1, 1, 1}, + {"xvector.dense.nonlinear.batchnorm.running_mean", 1, 16, 1, 1, 1}, + {"xvector.dense.nonlinear.batchnorm.running_var", 1, 16, 1, 1, 1}, +}; + +inline constexpr int64_t kTdnnChannels = 8; +inline constexpr int64_t kTdnnFrames = 20; + +inline constexpr float kTdnnOut[] = { + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 1.048381999e-01F, 4.664445296e-02F, 4.109911993e-02F, 4.128221795e-02F, + 3.988725320e-02F, 4.040486366e-02F, 4.041212797e-02F, 3.960426152e-02F, 4.010885581e-02F, 3.964221478e-02F, + 4.006348923e-02F, 4.058177769e-02F, 4.032431170e-02F, 3.996658698e-02F, 4.033358395e-02F, 3.980580717e-02F, + 4.026228935e-02F, 4.029135779e-02F, 3.737555072e-02F, 7.147096843e-02F, 5.651107430e-02F, 7.248625159e-02F, + 6.800870597e-02F, 6.803369522e-02F, 6.777735054e-02F, 6.798199564e-02F, 6.799477339e-02F, 6.799045205e-02F, + 6.798787415e-02F, 6.786625087e-02F, 6.786943227e-02F, 6.810820848e-02F, 6.814159453e-02F, 6.794924289e-02F, + 6.806984544e-02F, 6.794821471e-02F, 6.803853065e-02F, 6.779640913e-02F, 6.832714379e-02F, 4.376567900e-02F, + 6.050790194e-03F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 2.247087210e-01F, 3.480925262e-01F, + 3.566412926e-01F, 3.590562046e-01F, 3.587872088e-01F, 3.578426540e-01F, 3.583241105e-01F, 3.582077324e-01F, + 3.581512272e-01F, 3.592772186e-01F, 3.584191203e-01F, 3.579684794e-01F, 3.581817150e-01F, 3.575685024e-01F, + 3.581863046e-01F, 3.583062291e-01F, 3.582814336e-01F, 3.590258658e-01F, 3.547135592e-01F, 3.668274879e-01F, + 1.660445705e-02F, 8.468494751e-03F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, +}; + +inline constexpr float kFullEmbedding[] = { + 3.141055703e-01F, -1.706462353e-01F, -5.586713552e-01F, 6.542944908e-02F, -7.787918299e-02F, 1.204805598e-01F, + 3.899619281e-01F, -3.549714386e-01F, 2.166695744e-01F, 4.912429675e-02F, -1.501905769e-01F, -3.153222203e-01F, + 1.127850860e-01F, -3.556355536e-01F, -3.627812862e-02F, 5.542755723e-01F, +}; + } // namespace campplus_goldens diff --git a/tests/vllm/models/test_campplus.cpp b/tests/vllm/models/test_campplus.cpp index 99b5b7fc4..d80406365 100644 --- a/tests/vllm/models/test_campplus.cpp +++ b/tests/vllm/models/test_campplus.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -218,3 +219,80 @@ TEST_CASE("campplus BasicResBlock strides FREQUENCY only, never time") { REQUIRE(got.size() == static_cast(kFcmPlanes * kFcmOutH * kFcmOutW)); CHECK(Worst(got, kResBlock, got.size()) < 1e-5); } + +TEST_CASE("campplus forward reproduces the WHOLE upstream encoder") { + // The manifest is the contract: every tensor is rebuilt from its upstream + // state_dict NAME by the same rule the generator used, so a tensor one side + // builds and the other does not is a lookup failure by name, not a silent zero. + vllm::models::campplus::CampplusWeights w; + for (int64_t i = 0; i < kManifestSize; ++i) { + const auto& e = kManifest[i]; + int64_t n = 1; + const int64_t dims[4] = {e.d0, e.d1, e.d2, e.d3}; + for (int64_t d = 0; d < e.rank; ++d) n *= dims[d]; + if (e.rank == 0) n = 1; + std::vector v = Rand(e.name, n, 0.3); + const std::string name(e.name); + if (name.size() > 11 && name.compare(name.size() - 11, 11, "running_var") == 0) { + for (float& f : v) f = std::fabs(f) + 0.5F; + } + w.t[name] = std::move(v); + } + REQUIRE(static_cast(w.t.size()) == kManifestSize); + + vllm::models::campplus::CampplusParams p; + p.feat_dim = kFeatDim; + p.embedding_size = kEmbedding; + p.growth_rate = kGrowth2; + p.bn_size = kBnSize; + p.init_channels = kInitChannels; + + const std::vector feats = Rand("feats", kFullFrames * kFeatDim, 1.0); + const std::vector got = + vllm::models::campplus::Forward(p, w, feats, kFullFrames); + REQUIRE(got.size() == static_cast(kEmbedding)); + const double worst = Worst(got, kFullEmbedding, got.size()); + INFO("max abs diff vs upstream CAMPPlus.forward: ", worst); + CHECK(worst < 5e-5); +} + +TEST_CASE("campplus forward throws BY NAME on a missing tensor") { + vllm::models::campplus::CampplusWeights w; + vllm::models::campplus::CampplusParams p; + p.feat_dim = kFeatDim; // else the shape check fires before any name lookup + const std::vector feats = Rand("feats", kFullFrames * kFeatDim, 1.0); + CHECK_THROWS_WITH_AS(vllm::models::campplus::Forward(p, w, feats, kFullFrames), + doctest::Contains("head.conv1.weight"), std::runtime_error); +} + +TEST_CASE("campplus TDNN head geometry is gated where pooling cannot hide it") { + // A wrong stride/dilation/padding in xvector.tdnn changes the FRAME COUNT. + // StatsPool averages over time, so the final embedding absorbs it and still + // matches within tolerance -- discovered by mutation, which is why this case + // exists. The intermediate is gated directly. + vllm::models::campplus::CampplusWeights w; + for (int64_t i = 0; i < kManifestSize; ++i) { + const auto& e = kManifest[i]; + int64_t n = 1; + const int64_t dims[4] = {e.d0, e.d1, e.d2, e.d3}; + for (int64_t d = 0; d < e.rank; ++d) n *= dims[d]; + std::vector v = Rand(e.name, n, 0.3); + const std::string name(e.name); + if (name.size() > 11 && name.compare(name.size() - 11, 11, "running_var") == 0) { + for (float& f : v) f = std::fabs(f) + 0.5F; + } + w.t[name] = std::move(v); + } + vllm::models::campplus::CampplusParams p; + p.feat_dim = kFeatDim; p.embedding_size = kEmbedding; p.growth_rate = kGrowth2; + p.bn_size = kBnSize; p.init_channels = kInitChannels; + + vllm::models::campplus::ForwardTrace trace; + const std::vector feats = Rand("feats", kFullFrames * kFeatDim, 1.0); + vllm::models::campplus::Forward(p, w, feats, kFullFrames, &trace); + + CHECK(trace.tdnn_channels == kTdnnChannels); + CHECK(trace.tdnn_frames == kTdnnFrames); // 20, not 19: padding is (5-1)//2 + REQUIRE(trace.tdnn.size() == static_cast(kTdnnChannels * kTdnnFrames)); + CHECK(Worst(trace.tdnn, kTdnnOut, trace.tdnn.size()) < 1e-5); +} From 5e010c948e4cf49a331c97fb3d2a0912684bb6e3 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:29:45 +0200 Subject: [PATCH 20/55] feat(MODEL-MM-indextts2): w2v-bert-2.0 Conformer FFN + conv module (#634) (#707) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Starts w2v-bert-2.0, the semantic front end IndexTTS-2.5 runs over the reference clip (infer_v2_5.py:174). Ports the parts that make the encoder a CONFORMER: the macaron feed-forward and the depthwise convolution module. 3 cases / 181 assertions, within 1e-5. ORACLE: `transformers` executed DIRECTLY — the class IndexTTS itself instantiates, so no restatement. Admissible under AGENTS.md §"When vLLM has no implementation": a model's own reference implementation. THE TRAP: the conv module pads (kernel-1, 0) — LEFT ONLY, so a frame never sees the future. A symmetric pad yields an identically shaped output of the same length, so neither a shape nor a length check finds it. Gated on upstream values AND by an independent causality probe: perturbing the LAST frame must leave every earlier output bit-identical while the last one moves. Also pinned: swish not gelu, and the GLU gate orientation. MUTATION EVIDENCE with compile status: symmetric pad, gelu-for-swish and swapped GLU halves each RED at compile_err=0. The gelu mutation's first attempt did not APPLY (anchor count 0, Swish being a one-liner) and printed a meaningless SUCCESS; the anchor assertion caught it. DEFERRED deliberately: relative-key self-attention, so it lands whole. Then EnhancedCodec, S2Mel, talker head, compose, routes/ABI. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- CMakeLists.txt | 1 + include/vllm/model_executor/models/w2vbert.h | 59 ++++++ scripts/gen-w2vbert-goldens.py | 116 +++++++++++ src/vllm/model_executor/models/w2vbert.cpp | 152 ++++++++++++++ tests/CMakeLists.txt | 2 + tests/vllm/models/test_w2vbert.cpp | 123 ++++++++++++ tests/vllm/models/w2vbert_goldens.inc | 197 +++++++++++++++++++ 7 files changed, 650 insertions(+) create mode 100644 include/vllm/model_executor/models/w2vbert.h create mode 100644 scripts/gen-w2vbert-goldens.py create mode 100644 src/vllm/model_executor/models/w2vbert.cpp create mode 100644 tests/vllm/models/test_w2vbert.cpp create mode 100644 tests/vllm/models/w2vbert_goldens.inc diff --git a/CMakeLists.txt b/CMakeLists.txt index a31be7aae..c2e5896cb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -771,6 +771,7 @@ add_library(vllm STATIC src/vllm/multimodal/speech_engine.cpp src/vllm/model_executor/models/indextts2.cpp src/vllm/model_executor/models/campplus.cpp + src/vllm/model_executor/models/w2vbert.cpp src/vllm/model_executor/models/minimax_h3_video_vae.cpp src/vllm/model_executor/models/minimax_h3_video_vae_device.cpp src/vllm/model_executor/models/minimax_h3_vae_cnn.cpp diff --git a/include/vllm/model_executor/models/w2vbert.h b/include/vllm/model_executor/models/w2vbert.h new file mode 100644 index 000000000..21701d3aa --- /dev/null +++ b/include/vllm/model_executor/models/w2vbert.h @@ -0,0 +1,59 @@ +// w2v-bert-2.0 Conformer pieces — IndexTTS-2.5's semantic front end (W3, #634). +// +// `infer_v2_5.py:174` runs HuggingFace `Wav2Vec2BertModel` over the 16 kHz +// reference clip; its features are what EnhancedCodec quantizes. The encoder is +// a CONFORMER, not a plain transformer: macaron feed-forwards wrap the attention, +// and a depthwise convolution module sits between them. +// +// Gated against `transformers` executed directly — the class IndexTTS itself +// instantiates. Under AGENTS.md that is an admissible secondary oracle: a model's +// own reference implementation, which vLLM mirrors rather than replaces. +// +// Layout is [T, hidden] (row-major over time), matching the torch tensor. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace w2vbert { + +// LayerNorm over the last dimension. +std::vector LayerNorm(const std::vector& x, int64_t frames, int64_t dim, + const std::vector& gamma, const std::vector& beta, + double eps); + +// SiLU / swish, the config's `hidden_act`. +double Swish(double x); + +// Wav2Vec2BertFeedForward: intermediate_dense -> swish -> output_dense. +// +// THE MACARON HALF-STEP lives in the LAYER, not here: the caller adds +// `ffn(x) * 0.5 + residual`. Dropping the 0.5 still runs and still trains; it is +// simply a different architecture, so the factor is gated by the layer case. +std::vector FeedForward(const std::vector& x, int64_t frames, int64_t hidden, + int64_t intermediate, const std::vector& in_w, + const std::vector& in_b, const std::vector& out_w, + const std::vector& out_b); + +struct ConvModuleWeights { + std::vector ln_gamma, ln_beta; // pre layer_norm + std::vector pointwise1; // [2*hidden, hidden, 1] + std::vector depthwise; // [hidden, 1, k] + std::vector dw_ln_gamma, dw_ln_beta; // after the depthwise conv + std::vector pointwise2; // [hidden, hidden, 1] +}; + +// Wav2Vec2BertConvolutionModule: layer_norm -> pointwise1 -> GLU -> CAUSAL pad -> +// depthwise -> layer_norm -> swish -> pointwise2. +// +// THE PAD IS LEFT-ONLY, `(kernel - 1, 0)`. A symmetric pad still produces a +// correctly shaped output while letting each frame see the future, which is +// exactly the defect no shape check and no length check can find. +std::vector ConvModule(const std::vector& x, int64_t frames, int64_t hidden, + int64_t kernel, const ConvModuleWeights& weights, double eps); + +} // namespace w2vbert +} // namespace models +} // namespace vllm diff --git a/scripts/gen-w2vbert-goldens.py b/scripts/gen-w2vbert-goldens.py new file mode 100644 index 000000000..9ebcf0adc --- /dev/null +++ b/scripts/gen-w2vbert-goldens.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/w2vbert_goldens.inc — the w2v-bert-2.0 parity oracle. + +IndexTTS-2.5's reference-audio path runs `Wav2Vec2BertModel` (infer_v2_5.py:174) +over the 16 kHz clip to produce the semantic features EnhancedCodec quantizes. +The encoder is a CONFORMER: macaron feed-forwards around self-attention and a +depthwise convolution module. + +The oracle is HuggingFace `transformers` executed DIRECTLY — the class IndexTTS +itself instantiates, so there is no restatement and no risk of being faithful to +the wrong thing. `transformers` is an admissible secondary oracle under AGENTS.md +for exactly this case: a model's own reference implementation, which vLLM mirrors +rather than replaces. + +Reduced dimensions on CPU; weights rebuilt both sides from one FNV-1a -> +splitmix64 stream, so no weight byte is checked in. + +Usage: python3 scripts/gen-w2vbert-goldens.py --out tests/vllm/models/w2vbert_goldens.inc +""" +from __future__ import annotations +import argparse +from pathlib import Path +import numpy as np, torch +from transformers import Wav2Vec2BertConfig +from transformers.models.wav2vec2_bert.modeling_wav2vec2_bert import Wav2Vec2BertEncoderLayer + +MASK = (1 << 64) - 1 +def fnv(n: str) -> int: + h = 0xCBF29CE484222325 + for b in n.encode(): h ^= b; h = (h * 0x100000001B3) & MASK + return h +def sm(x: int) -> int: + x = (x + 0x9E3779B97F4A7C15) & MASK + z = x + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & MASK + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & MASK + return z ^ (z >> 31) +def rnd(name: str, n: int, scale: float) -> np.ndarray: + s = fnv(name); o = np.empty(n) + for i in range(n): o[i] = (((sm((s + i) & MASK) >> 11) * 2.0**-53) * 2 - 1) * scale + return o +def P(name, shape, scale=0.3): + n = 1 + for d in shape: n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + +def main() -> int: + ap = argparse.ArgumentParser(); ap.add_argument("--out", required=True) + a = ap.parse_args() + torch.manual_seed(0) + + cfg = Wav2Vec2BertConfig( + hidden_size=16, num_attention_heads=2, intermediate_size=32, + conv_depthwise_kernel_size=7, position_embeddings_type="relative_key", + hidden_act="swish", layer_norm_eps=1e-5, + ) + layer = Wav2Vec2BertEncoderLayer(cfg).eval() + manifest = [] + with torch.no_grad(): + for n, q in list(layer.named_parameters()) + list(layer.named_buffers()): + if n.endswith("num_batches_tracked"): continue + v = P(n, tuple(q.shape) if q.dim() else (1,)) + if n.endswith("running_var"): v = v.abs() + 0.5 + q.copy_(v.reshape(q.shape)); manifest.append((n, list(q.shape))) + T = 12 + x = P("hidden", (1, T, cfg.hidden_size), 1.0) + tap = {} + layer.ffn1.register_forward_hook(lambda m, i, o: tap.__setitem__("ffn1", o.detach().clone())) + layer.conv_module.register_forward_hook(lambda m, i, o: tap.__setitem__("conv", o.detach().clone())) + with torch.no_grad(): + out, _ = layer(x) + # the conv module's INPUT is the post-attention state; capture it so the + # module can be gated standalone rather than only through the whole layer. + conv_in = {} + layer.conv_module.register_forward_pre_hook( + lambda m, i: conv_in.__setitem__("x", i[0].detach().clone())) + with torch.no_grad(): + layer(x) + + def emit(f, name, t): + arr = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) + f.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(arr), 6): + f.write(" " + ", ".join(f"{v:.9e}F" for v in arr[i:i+6]) + ",\n") + f.write("};\n\n") + + p = Path(a.out) + with p.open("w") as f: + f.write("// GENERATED by scripts/gen-w2vbert-goldens.py -- do not edit.\n") + f.write("// HuggingFace transformers Wav2Vec2BertEncoderLayer executed DIRECTLY\n") + f.write("// (the class IndexTTS-2.5 instantiates, infer_v2_5.py:174), at reduced\n") + f.write("// dimensions on CPU. Weights rebuilt both sides from one FNV-1a ->\n") + f.write("// splitmix64 stream; no weight byte is checked in.\n") + f.write("#pragma once\n\n#include \n\nnamespace w2vbert_goldens {\n\n") + for n, v in (("kHidden", cfg.hidden_size), ("kHeads", cfg.num_attention_heads), + ("kIntermediate", cfg.intermediate_size), + ("kConvKernel", cfg.conv_depthwise_kernel_size), ("kFrames", T)): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write(f'inline constexpr double kLayerNormEps = {cfg.layer_norm_eps!r};\n') + f.write(f"\ninline constexpr int64_t kManifestSize = {len(manifest)};\n\n") + f.write("struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2; };\n") + f.write("inline constexpr ManifestEntry kManifest[] = {\n") + for nm, sh in manifest: + d = list(sh) + [1, 1, 1] + f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}, {d[2]}}},\n') + f.write("};\n\n") + emit(f, "kFfn1Out", tap["ffn1"]) + emit(f, "kConvIn", conv_in["x"]) + emit(f, "kConvOut", tap["conv"]) + emit(f, "kLayerOut", out) + f.write("} // namespace w2vbert_goldens\n") + print(f"wrote {p}: {len(manifest)} tensors, T={T}, hidden={cfg.hidden_size}") + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/vllm/model_executor/models/w2vbert.cpp b/src/vllm/model_executor/models/w2vbert.cpp new file mode 100644 index 000000000..5d6c9fae0 --- /dev/null +++ b/src/vllm/model_executor/models/w2vbert.cpp @@ -0,0 +1,152 @@ +// w2v-bert-2.0 Conformer pieces. See w2vbert.h for the upstream anchors. +#include "vllm/model_executor/models/w2vbert.h" + +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace w2vbert { + +double Swish(double x) { return x / (1.0 + std::exp(-x)); } + +std::vector LayerNorm(const std::vector& x, int64_t frames, int64_t dim, + const std::vector& gamma, const std::vector& beta, + double eps) { + VT_CHECK(x.size() == static_cast(frames * dim), "w2vbert: LayerNorm shape"); + std::vector out(x.size()); + for (int64_t t = 0; t < frames; ++t) { + const float* row = x.data() + static_cast(t * dim); + double mean = 0.0; + for (int64_t i = 0; i < dim; ++i) mean += static_cast(row[i]); + mean /= static_cast(dim); + double var = 0.0; + for (int64_t i = 0; i < dim; ++i) { + const double d = static_cast(row[i]) - mean; + var += d * d; + } + var /= static_cast(dim); + const double inv = 1.0 / std::sqrt(var + eps); + for (int64_t i = 0; i < dim; ++i) { + out[static_cast(t * dim + i)] = static_cast( + ((static_cast(row[i]) - mean) * inv) * static_cast(gamma[static_cast(i)]) + + static_cast(beta[static_cast(i)])); + } + } + return out; +} + +namespace { + +// y = x W^T + b, with W stored [out, in] as torch.nn.Linear does. +std::vector Linear(const std::vector& x, int64_t frames, int64_t in_dim, + int64_t out_dim, const std::vector& w, + const std::vector& b) { + std::vector out(static_cast(frames * out_dim)); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t o = 0; o < out_dim; ++o) { + double acc = b.empty() ? 0.0 : static_cast(b[static_cast(o)]); + for (int64_t i = 0; i < in_dim; ++i) { + acc += static_cast(w[static_cast(o * in_dim + i)]) * + static_cast(x[static_cast(t * in_dim + i)]); + } + out[static_cast(t * out_dim + o)] = static_cast(acc); + } + } + return out; +} + +} // namespace + +std::vector FeedForward(const std::vector& x, int64_t frames, int64_t hidden, + int64_t intermediate, const std::vector& in_w, + const std::vector& in_b, const std::vector& out_w, + const std::vector& out_b) { + std::vector h = Linear(x, frames, hidden, intermediate, in_w, in_b); + for (float& v : h) v = static_cast(Swish(static_cast(v))); + return Linear(h, frames, intermediate, hidden, out_w, out_b); +} + +std::vector ConvModule(const std::vector& x, int64_t frames, int64_t hidden, + int64_t kernel, const ConvModuleWeights& w, double eps) { + VT_CHECK(x.size() == static_cast(frames * hidden), "w2vbert: ConvModule shape"); + const std::vector normed = LayerNorm(x, frames, hidden, w.ln_gamma, w.ln_beta, eps); + + // torch works [C, T] from here; the weights are Conv1d [out, in/groups, k]. + std::vector ct(static_cast(hidden * frames)); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t c = 0; c < hidden; ++c) { + ct[static_cast(c * frames + t)] = normed[static_cast(t * hidden + c)]; + } + } + + // pointwise_conv1: [2H, H, 1] -> 2H channels, then GLU halves them back to H. + std::vector wide(static_cast(2 * hidden * frames)); + for (int64_t o = 0; o < 2 * hidden; ++o) { + for (int64_t t = 0; t < frames; ++t) { + double acc = 0.0; + for (int64_t c = 0; c < hidden; ++c) { + acc += static_cast(w.pointwise1[static_cast(o * hidden + c)]) * + static_cast(ct[static_cast(c * frames + t)]); + } + wide[static_cast(o * frames + t)] = static_cast(acc); + } + } + // GLU over the CHANNEL axis: first half gated by sigmoid(second half). + std::vector gated(static_cast(hidden * frames)); + for (int64_t c = 0; c < hidden; ++c) { + for (int64_t t = 0; t < frames; ++t) { + const double a = static_cast(wide[static_cast(c * frames + t)]); + const double b = static_cast(wide[static_cast((hidden + c) * frames + t)]); + gated[static_cast(c * frames + t)] = + static_cast(a * (1.0 / (1.0 + std::exp(-b)))); + } + } + + // CAUSAL pad: kernel-1 on the LEFT only, so a frame never sees the future. + const int64_t pad = kernel - 1; + std::vector dw(static_cast(hidden * frames)); + for (int64_t c = 0; c < hidden; ++c) { + for (int64_t t = 0; t < frames; ++t) { + double acc = 0.0; + for (int64_t k = 0; k < kernel; ++k) { + const int64_t src = t + k - pad; + if (src < 0) continue; // left pad only + acc += static_cast(w.depthwise[static_cast(c * kernel + k)]) * + static_cast(gated[static_cast(c * frames + src)]); + } + dw[static_cast(c * frames + t)] = static_cast(acc); + } + } + + // depthwise_layer_norm runs over the FEATURE axis, so transpose back first. + std::vector tc(static_cast(frames * hidden)); + for (int64_t c = 0; c < hidden; ++c) { + for (int64_t t = 0; t < frames; ++t) { + tc[static_cast(t * hidden + c)] = dw[static_cast(c * frames + t)]; + } + } + tc = LayerNorm(tc, frames, hidden, w.dw_ln_gamma, w.dw_ln_beta, eps); + for (float& v : tc) v = static_cast(Swish(static_cast(v))); + + // pointwise_conv2 back to [T, H]. + std::vector out(static_cast(frames * hidden)); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t o = 0; o < hidden; ++o) { + double acc = 0.0; + for (int64_t c = 0; c < hidden; ++c) { + acc += static_cast(w.pointwise2[static_cast(o * hidden + c)]) * + static_cast(tc[static_cast(t * hidden + c)]); + } + out[static_cast(t * hidden + o)] = static_cast(acc); + } + } + return out; +} + +} // namespace w2vbert +} // namespace models +} // namespace vllm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 291f8cfdb..c2eddb4c0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -113,6 +113,8 @@ vllm_cpp_add_test(test_vocoder1d vllm/models/test_vocoder1d.cpp) vllm_cpp_add_test(test_speech_engine vllm/multimodal/test_speech_engine.cpp) vllm_cpp_add_test(test_indextts2_family vllm/models/test_indextts2_family.cpp) vllm_cpp_add_test(test_campplus vllm/models/test_campplus.cpp) +vllm_cpp_add_test(test_w2vbert vllm/models/test_w2vbert.cpp) +target_include_directories(test_w2vbert PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_campplus PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) vllm_cpp_add_test(test_gpt2 vllm/models/test_gpt2.cpp) target_include_directories(test_gpt2 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) diff --git a/tests/vllm/models/test_w2vbert.cpp b/tests/vllm/models/test_w2vbert.cpp new file mode 100644 index 000000000..f7187410e --- /dev/null +++ b/tests/vllm/models/test_w2vbert.cpp @@ -0,0 +1,123 @@ +// w2v-bert-2.0 Conformer parity gate — IndexTTS-2.5's semantic front end (#634). +// +// Gated against HuggingFace `transformers` executed DIRECTLY: the class +// `infer_v2_5.py:174` instantiates, so there is no restatement. Weights are +// rebuilt from the same FNV-1a -> splitmix64 stream the generator used, keyed by +// the upstream parameter NAME. +#include +#include +#include +#include +#include +#include + +#include "doctest/doctest.h" +#include "vllm/model_executor/models/w2vbert.h" +#include "w2vbert_goldens.inc" + +namespace { +using namespace w2vbert_goldens; + +std::vector Rand(const std::string& name, int64_t n, double scale) { + uint64_t seed = 0xCBF29CE484222325ULL; + for (char c : name) { seed ^= static_cast(c); seed *= 0x100000001B3ULL; } + std::vector o(static_cast(n)); + for (int64_t i = 0; i < n; ++i) { + uint64_t x = seed + static_cast(i); + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + z ^= z >> 31; + o[static_cast(i)] = + static_cast(((static_cast(z >> 11) * 0x1.0p-53) * 2.0 - 1.0) * scale); + } + return o; +} + +std::map> Weights() { + std::map> w; + for (int64_t i = 0; i < kManifestSize; ++i) { + const auto& e = kManifest[i]; + const int64_t d[3] = {e.d0, e.d1, e.d2}; + int64_t n = 1; + for (int64_t k = 0; k < e.rank; ++k) n *= d[k]; + w[e.name] = Rand(e.name, n, 0.3); + } + return w; +} + +double Worst(const std::vector& got, const float* want, size_t n) { + double m = 0.0; + for (size_t i = 0; i < n; ++i) m = std::max(m, std::fabs(static_cast(got[i]) - want[i])); + return m; +} +} // namespace + +TEST_CASE("w2vbert FeedForward matches upstream (swish, not gelu)") { + const auto w = Weights(); + const std::vector x = Rand("hidden", kFrames * kHidden, 1.0); + // ffn1 runs on the LAYER-NORMED input, mirroring the encoder layer. + const std::vector normed = vllm::models::w2vbert::LayerNorm( + x, kFrames, kHidden, w.at("ffn1_layer_norm.weight"), w.at("ffn1_layer_norm.bias"), + kLayerNormEps); + const std::vector got = vllm::models::w2vbert::FeedForward( + normed, kFrames, kHidden, kIntermediate, w.at("ffn1.intermediate_dense.weight"), + w.at("ffn1.intermediate_dense.bias"), w.at("ffn1.output_dense.weight"), + w.at("ffn1.output_dense.bias")); + REQUIRE(got.size() == static_cast(kFrames * kHidden)); + CHECK(Worst(got, kFfn1Out, got.size()) < 1e-5); +} + +TEST_CASE("w2vbert ConvModule matches upstream, including the CAUSAL left pad") { + // The pad is (kernel-1, 0). A symmetric pad yields the same shape while + // letting every frame see the future -- no length or shape check can see it, + // which is why this is gated on values against the real module. + const auto w = Weights(); + vllm::models::w2vbert::ConvModuleWeights c; + c.ln_gamma = w.at("conv_module.layer_norm.weight"); + c.ln_beta = w.at("conv_module.layer_norm.bias"); + c.pointwise1 = w.at("conv_module.pointwise_conv1.weight"); + c.depthwise = w.at("conv_module.depthwise_conv.weight"); + c.dw_ln_gamma = w.at("conv_module.depthwise_layer_norm.weight"); + c.dw_ln_beta = w.at("conv_module.depthwise_layer_norm.bias"); + c.pointwise2 = w.at("conv_module.pointwise_conv2.weight"); + + const std::vector x(std::begin(kConvIn), std::end(kConvIn)); + const std::vector got = + vllm::models::w2vbert::ConvModule(x, kFrames, kHidden, kConvKernel, c, kLayerNormEps); + REQUIRE(got.size() == static_cast(kFrames * kHidden)); + CHECK(Worst(got, kConvOut, got.size()) < 1e-5); +} + +TEST_CASE("w2vbert conv module cannot see the future") { + // Direct causality probe, independent of the goldens: perturbing the LAST + // frame must leave every earlier output bit-identical. + const auto w = Weights(); + vllm::models::w2vbert::ConvModuleWeights c; + c.ln_gamma = w.at("conv_module.layer_norm.weight"); + c.ln_beta = w.at("conv_module.layer_norm.bias"); + c.pointwise1 = w.at("conv_module.pointwise_conv1.weight"); + c.depthwise = w.at("conv_module.depthwise_conv.weight"); + c.dw_ln_gamma = w.at("conv_module.depthwise_layer_norm.weight"); + c.dw_ln_beta = w.at("conv_module.depthwise_layer_norm.bias"); + c.pointwise2 = w.at("conv_module.pointwise_conv2.weight"); + + std::vector x(std::begin(kConvIn), std::end(kConvIn)); + const std::vector base = + vllm::models::w2vbert::ConvModule(x, kFrames, kHidden, kConvKernel, c, kLayerNormEps); + x[static_cast((kFrames - 1) * kHidden)] += 3.0F; + const std::vector moved = + vllm::models::w2vbert::ConvModule(x, kFrames, kHidden, kConvKernel, c, kLayerNormEps); + + // NOTE: the module's leading layer_norm is per-frame, so a change to the last + // frame cannot leak backwards through it either. + for (size_t i = 0; i < static_cast((kFrames - 1) * kHidden); ++i) { + CHECK(base[i] == moved[i]); + } + double delta = 0.0; + for (size_t i = static_cast((kFrames - 1) * kHidden); i < base.size(); ++i) { + delta = std::max(delta, std::fabs(static_cast(base[i] - moved[i]))); + } + CHECK(delta > 1e-6); // the last frame MUST move, or the probe proves nothing +} diff --git a/tests/vllm/models/w2vbert_goldens.inc b/tests/vllm/models/w2vbert_goldens.inc new file mode 100644 index 000000000..681e0ae18 --- /dev/null +++ b/tests/vllm/models/w2vbert_goldens.inc @@ -0,0 +1,197 @@ +// GENERATED by scripts/gen-w2vbert-goldens.py -- do not edit. +// HuggingFace transformers Wav2Vec2BertEncoderLayer executed DIRECTLY +// (the class IndexTTS-2.5 instantiates, infer_v2_5.py:174), at reduced +// dimensions on CPU. Weights rebuilt both sides from one FNV-1a -> +// splitmix64 stream; no weight byte is checked in. +#pragma once + +#include + +namespace w2vbert_goldens { + +inline constexpr int64_t kHidden = 16; +inline constexpr int64_t kHeads = 2; +inline constexpr int64_t kIntermediate = 32; +inline constexpr int64_t kConvKernel = 7; +inline constexpr int64_t kFrames = 12; +inline constexpr double kLayerNormEps = 1e-05; + +inline constexpr int64_t kManifestSize = 32; + +struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2; }; +inline constexpr ManifestEntry kManifest[] = { + {"ffn1_layer_norm.weight", 1, 16, 1, 1}, + {"ffn1_layer_norm.bias", 1, 16, 1, 1}, + {"ffn1.intermediate_dense.weight", 2, 32, 16, 1}, + {"ffn1.intermediate_dense.bias", 1, 32, 1, 1}, + {"ffn1.output_dense.weight", 2, 16, 32, 1}, + {"ffn1.output_dense.bias", 1, 16, 1, 1}, + {"self_attn_layer_norm.weight", 1, 16, 1, 1}, + {"self_attn_layer_norm.bias", 1, 16, 1, 1}, + {"self_attn.linear_q.weight", 2, 16, 16, 1}, + {"self_attn.linear_q.bias", 1, 16, 1, 1}, + {"self_attn.linear_k.weight", 2, 16, 16, 1}, + {"self_attn.linear_k.bias", 1, 16, 1, 1}, + {"self_attn.linear_v.weight", 2, 16, 16, 1}, + {"self_attn.linear_v.bias", 1, 16, 1, 1}, + {"self_attn.linear_out.weight", 2, 16, 16, 1}, + {"self_attn.linear_out.bias", 1, 16, 1, 1}, + {"self_attn.distance_embedding.weight", 2, 73, 8, 1}, + {"conv_module.layer_norm.weight", 1, 16, 1, 1}, + {"conv_module.layer_norm.bias", 1, 16, 1, 1}, + {"conv_module.pointwise_conv1.weight", 3, 32, 16, 1}, + {"conv_module.depthwise_conv.weight", 3, 16, 1, 7}, + {"conv_module.depthwise_layer_norm.weight", 1, 16, 1, 1}, + {"conv_module.depthwise_layer_norm.bias", 1, 16, 1, 1}, + {"conv_module.pointwise_conv2.weight", 3, 16, 16, 1}, + {"ffn2_layer_norm.weight", 1, 16, 1, 1}, + {"ffn2_layer_norm.bias", 1, 16, 1, 1}, + {"ffn2.intermediate_dense.weight", 2, 32, 16, 1}, + {"ffn2.intermediate_dense.bias", 1, 32, 1, 1}, + {"ffn2.output_dense.weight", 2, 16, 32, 1}, + {"ffn2.output_dense.bias", 1, 16, 1, 1}, + {"final_layer_norm.weight", 1, 16, 1, 1}, + {"final_layer_norm.bias", 1, 16, 1, 1}, +}; + +inline constexpr float kFfn1Out[] = { + 9.458056092e-02F, 7.375027984e-02F, 4.845884070e-02F, -3.806251884e-01F, 4.369759262e-01F, -1.899440959e-02F, + -9.286113083e-02F, -3.108807504e-01F, 2.782942355e-01F, 1.648317873e-01F, 1.476261765e-01F, 6.681033224e-02F, + 6.355185807e-02F, -2.789697051e-01F, 9.111861885e-02F, 1.834255904e-01F, 5.215142667e-02F, 3.868814092e-03F, + 5.846624449e-02F, -3.965765834e-01F, 4.203180671e-01F, -8.864852786e-02F, -6.307955086e-02F, -3.858075142e-01F, + 1.707401872e-01F, 2.159375548e-01F, 1.786170900e-01F, 8.643349260e-02F, 3.140187263e-02F, -2.465653270e-01F, + 1.100700945e-01F, 1.691114455e-01F, -1.234017015e-01F, 2.338757068e-01F, 1.845335960e-01F, -3.832044601e-01F, + 3.436498046e-01F, -1.719756424e-01F, -4.684753716e-02F, -4.140472114e-01F, 2.332292646e-01F, 1.352141500e-01F, + 3.120193183e-01F, 2.740583941e-02F, 3.715530783e-02F, -1.279052198e-01F, -1.232201904e-01F, 2.040165365e-01F, + -2.533769608e-02F, 1.016811579e-01F, 1.424319446e-01F, -3.538209200e-01F, 4.241016507e-01F, -1.257693619e-01F, + -4.842574894e-02F, -4.778069556e-01F, 2.183974683e-01F, 1.270245165e-01F, 3.012045622e-01F, 2.356783487e-02F, + 9.663455188e-02F, -1.765933931e-01F, 4.634700716e-03F, 2.211606205e-01F, -8.026662469e-02F, 1.386545300e-01F, + 1.470033526e-01F, -3.517656624e-01F, 4.533334672e-01F, -1.393049657e-01F, 7.979467511e-03F, -4.606643617e-01F, + 3.446011543e-01F, 1.963400692e-01F, 3.714741170e-01F, -3.987159580e-03F, 7.395972311e-02F, -4.622802138e-02F, + -1.643308550e-01F, 2.591983080e-01F, 4.020196199e-02F, 1.075275391e-01F, 7.314579189e-02F, -3.533778787e-01F, + 4.354904890e-01F, -3.906306624e-02F, -3.333567083e-02F, -3.227354288e-01F, 1.854066849e-01F, 1.585016847e-01F, + 2.216562629e-01F, 4.819995165e-02F, 7.849316299e-02F, -2.472498566e-01F, 8.202311397e-02F, 1.815164834e-01F, + 2.150775492e-02F, 6.783907115e-02F, 1.489281058e-01F, -3.171177804e-01F, 4.976298213e-01F, -4.715510458e-02F, + 3.619733453e-02F, -2.786783874e-01F, 4.209001064e-01F, 2.365422696e-01F, 2.710313499e-01F, -6.137739122e-02F, + -3.989995271e-02F, -6.480683386e-02F, -1.282405257e-01F, 3.216569722e-01F, 2.942129970e-02F, 1.014961824e-01F, + 7.320449501e-02F, -3.383392692e-01F, 4.472650886e-01F, -4.048651084e-02F, 3.417403996e-02F, -3.516429663e-01F, + 3.436288834e-01F, 2.407218516e-01F, 2.927176058e-01F, -5.166295171e-02F, -6.051175296e-03F, -7.509262115e-02F, + -1.147990897e-01F, 2.722811103e-01F, -4.038977623e-02F, 7.862614840e-02F, 5.370535702e-02F, -4.077659845e-01F, + 4.014229476e-01F, -1.265107989e-01F, -3.659971058e-02F, -4.252704084e-01F, 9.215159714e-02F, 1.934040487e-01F, + 2.737266421e-01F, 1.074575335e-01F, 8.925597370e-02F, -2.115573287e-01F, 8.586393297e-02F, 2.007994354e-01F, + -1.837365329e-02F, 1.692223251e-01F, 1.627409756e-01F, -3.531500995e-01F, 2.824624777e-01F, -8.177838475e-02F, + -2.957974374e-02F, -3.496959805e-01F, 3.793314993e-01F, 2.159483433e-01F, 1.584358066e-01F, 1.614598185e-02F, + -4.077486694e-03F, -5.686768889e-02F, -1.281075478e-01F, 2.312202603e-01F, -1.234170794e-02F, 9.371673316e-02F, + 1.837606579e-01F, -5.164481401e-01F, 3.321206272e-01F, -1.326540262e-01F, -8.569462597e-02F, -3.720048070e-01F, + 3.047538102e-01F, 2.827300131e-01F, 4.533749074e-02F, 1.348561943e-01F, -2.257831022e-02F, -1.258037686e-01F, + -1.082033813e-01F, 1.533289254e-01F, -7.941347361e-02F, 1.706589162e-01F, 1.633481681e-02F, -3.494674563e-01F, + 3.772192597e-01F, -1.003007889e-01F, -8.062641323e-02F, -4.079901576e-01F, 2.557799816e-01F, 1.222688109e-01F, + 3.370250165e-01F, 3.444140032e-02F, 9.096124768e-02F, -1.571010947e-01F, -5.689162761e-03F, 2.900101542e-01F, +}; + +inline constexpr float kConvIn[] = { + -8.724011779e-01F, 1.957807541e-01F, 8.037833869e-02F, 5.368308425e-01F, 9.111663103e-01F, 7.316671610e-01F, + -4.769195616e-01F, -5.700601339e-01F, -9.156332612e-01F, 1.051828265e+00F, -7.224025726e-01F, 1.020778418e+00F, + 7.474948168e-01F, -4.274995625e-02F, -2.355776429e-01F, 6.417070627e-01F, -8.121114969e-01F, -3.287333548e-01F, + 5.186696649e-01F, -5.334268808e-01F, 1.234492660e+00F, 6.066573858e-01F, -8.852920532e-01F, -5.736184120e-01F, + -6.627187729e-01F, 8.631956577e-01F, -6.529198885e-01F, -7.492985725e-01F, 1.056386709e+00F, -4.207279086e-01F, + 1.288247347e+00F, -1.886413693e-01F, 5.709376335e-01F, 6.068849564e-01F, -2.070104480e-01F, -6.802636981e-01F, + -3.944479823e-01F, -9.752333164e-01F, -3.696489632e-01F, -8.637001514e-01F, 9.973551035e-01F, -5.208499432e-01F, + -2.380944043e-02F, -2.855768800e-01F, -2.098206282e-01F, -4.114586711e-01F, 8.753656149e-01F, 5.584177971e-01F, + -4.866142869e-01F, -8.563913703e-01F, 8.746546507e-01F, 2.668507397e-01F, -2.450208217e-01F, 2.536143064e-01F, + 1.058121324e-01F, -6.738667190e-02F, 5.751165152e-01F, -4.932182729e-01F, -7.831240296e-01F, 5.224922299e-01F, + 1.095504284e+00F, -3.042085171e-01F, 4.974822402e-01F, -3.826408386e-01F, -6.016392708e-01F, 1.574947834e-01F, + 1.108960986e+00F, 4.642940164e-01F, -6.341261268e-01F, -7.638387084e-01F, -9.714404345e-01F, 5.202183723e-01F, + -7.124448419e-01F, 3.736396879e-02F, -2.029748410e-01F, 9.172434211e-01F, 4.254606366e-01F, -3.939873576e-01F, + -3.637137413e-01F, -2.997084260e-01F, -1.181686640e+00F, 4.833426476e-01F, -2.084318548e-01F, -4.135420322e-01F, + 6.550572515e-01F, -5.307286978e-03F, -2.174140215e-01F, -8.662804365e-01F, 4.885184765e-01F, -7.850236893e-01F, + -7.743704319e-01F, 4.787125587e-01F, -3.631080389e-01F, -1.922906041e-01F, -3.987210989e-01F, -2.438849807e-01F, + -1.177909374e+00F, -5.189346075e-01F, 1.036256671e+00F, -1.168899179e+00F, 2.075474858e-01F, -1.290116310e-01F, + 2.729539573e-01F, 5.061420202e-01F, 2.567559481e-01F, 1.070766330e+00F, 8.711252809e-01F, -1.879739761e-02F, + -1.791268289e-01F, 6.261062026e-01F, 9.026737213e-01F, 1.132543325e+00F, -6.387915015e-01F, -1.772878170e-01F, + 9.668577909e-01F, -1.131996989e+00F, 4.560361207e-01F, 2.465852052e-01F, 4.157814384e-02F, 7.189165950e-01F, + -6.370971203e-01F, 3.197085261e-01F, 5.494773388e-01F, -4.733316600e-02F, -2.028957903e-01F, 4.888159931e-01F, + 4.836843014e-01F, 8.360288143e-01F, -5.721133351e-01F, 3.169599771e-01F, 5.215917826e-01F, -3.216512203e-01F, + 7.962353230e-01F, -3.748431504e-01F, -1.012581825e+00F, -8.380628228e-01F, -2.166598886e-01F, -6.736834049e-01F, + -5.979527831e-01F, -7.434048653e-01F, 2.970492840e-01F, -7.710934877e-01F, 1.135793209e+00F, -2.914004326e-01F, + -4.273912311e-02F, 4.705700874e-01F, -5.465156436e-01F, -1.291823506e+00F, -3.410393298e-01F, -1.848499626e-01F, + -3.904516101e-01F, 4.905023277e-01F, 3.676137626e-01F, -3.513528705e-01F, -9.124355912e-01F, 4.789070189e-01F, + -3.346747756e-01F, -5.192252398e-01F, 3.201577067e-01F, 1.305468440e+00F, 4.142203927e-01F, -1.776134968e-02F, + -5.850735903e-01F, -1.778024584e-01F, 2.675288618e-01F, 2.053669095e-01F, -1.185129762e+00F, 7.011355162e-01F, + 2.053308040e-01F, 4.348277152e-01F, -4.770532250e-04F, 9.383711219e-01F, 1.083398819e+00F, -9.610711336e-01F, + 8.597213030e-01F, 1.282000780e+00F, 5.557244420e-01F, 1.596663296e-01F, 9.229416251e-01F, 5.156324506e-01F, + 3.449830711e-01F, -2.497646213e-02F, -7.254518270e-01F, -4.661429822e-01F, -8.684559464e-01F, 5.946820974e-01F, + -4.347987771e-01F, 6.686691642e-01F, -2.375256866e-01F, -6.946130395e-01F, 5.211485028e-01F, 1.276440859e+00F, +}; + +inline constexpr float kConvOut[] = { + 4.515829310e-02F, 4.355316050e-04F, -7.031263085e-04F, 1.158124730e-01F, -1.455900222e-01F, -5.437240005e-02F, + 1.215446889e-01F, 1.231990661e-02F, 9.647757560e-02F, -1.032936722e-01F, 1.245511509e-02F, 2.302438579e-02F, + -1.178264618e-01F, 5.786301941e-02F, -5.561802536e-03F, 3.827929869e-02F, -7.630213350e-02F, -1.163953319e-01F, + 2.594465623e-03F, 1.409251988e-01F, -1.190899312e-01F, -7.989531010e-02F, -2.468543313e-02F, 1.157450378e-01F, + 2.328251153e-01F, 3.778538108e-02F, 1.072646491e-02F, -4.293500260e-02F, -1.665162444e-01F, 2.218859643e-02F, + -6.715949625e-02F, -8.738575503e-03F, -8.284775913e-02F, -1.908820719e-01F, -2.622090047e-03F, 1.082843915e-01F, + -6.781728566e-02F, -1.354433298e-01F, -2.778419480e-02F, 1.554220766e-01F, 2.479928434e-01F, 1.218584105e-01F, + -2.099924721e-02F, -7.877701521e-02F, -1.347380579e-01F, -4.517671838e-02F, 1.251886133e-02F, -7.791142166e-02F, + 3.139571100e-02F, -2.189493366e-02F, 4.353155941e-02F, 5.489207059e-02F, -1.061824486e-01F, -4.619614780e-02F, + 9.736389667e-02F, -2.627888881e-02F, 1.193068698e-01F, -6.269971281e-02F, 7.319843862e-03F, -2.613437548e-02F, + -9.412848949e-02F, 1.291407971e-03F, -1.104714535e-02F, 1.418953855e-02F, 4.169519059e-03F, -3.969238698e-02F, + 4.996997491e-02F, 7.750446349e-02F, -1.207774729e-01F, -5.140297860e-02F, 1.178283617e-01F, -2.521997876e-02F, + 9.550988674e-02F, -9.431999922e-02F, -1.841869578e-02F, -3.045004793e-02F, -1.396294832e-01F, 5.985613167e-02F, + -6.193213165e-03F, 3.631759610e-04F, -1.292665675e-02F, -4.990204424e-02F, -3.676031157e-02F, 1.078752726e-01F, + -1.099936441e-01F, -9.318852425e-02F, 1.024430841e-01F, 3.582888097e-02F, 7.497097552e-02F, -3.709777072e-02F, + -7.487072609e-03F, -5.134741776e-03F, -1.109088063e-01F, 5.526524410e-02F, 4.735331610e-02F, 1.643713005e-02F, + -2.807003260e-02F, -4.996889457e-02F, 4.788093269e-02F, 1.231683865e-01F, -1.184726879e-01F, -5.752129480e-02F, + 4.249405116e-02F, 6.786552072e-02F, 1.289251298e-01F, -2.326386049e-02F, 1.219947916e-02F, 1.602186821e-02F, + -1.373478174e-01F, 7.302334998e-03F, 2.606555074e-02F, 1.883727685e-02F, -5.371613801e-02F, -1.394616067e-01F, + 6.770020723e-02F, 1.020083651e-01F, -1.120824292e-01F, -1.369697452e-01F, 1.385777742e-01F, 4.993152991e-02F, + 1.386227310e-01F, -1.108899992e-02F, -8.518473804e-02F, -7.216808945e-02F, -2.178985476e-01F, 2.809424140e-02F, + 1.874398626e-02F, -5.086634681e-02F, -5.252484232e-02F, -1.198095605e-01F, -2.226072550e-02F, 1.080725268e-01F, + -1.248513386e-01F, -8.890178055e-02F, 7.106729597e-02F, 2.992655151e-02F, 1.343602985e-01F, -1.300459448e-02F, + -2.825094759e-02F, -6.770692766e-02F, -1.621619910e-01F, 9.843541682e-02F, -2.888987772e-02F, -7.586547639e-03F, + -3.898276761e-02F, -5.686875805e-02F, 1.703663729e-02F, 1.147502288e-01F, -1.247625425e-01F, -6.036358699e-02F, + 4.759243876e-02F, 3.671608865e-02F, 9.946089983e-02F, -6.866593659e-02F, 1.104863011e-03F, -7.434052415e-03F, + -1.651563942e-01F, 8.344353735e-02F, -3.656276315e-02F, 1.794647658e-03F, -4.787502065e-02F, -7.282282412e-02F, + 2.350340225e-02F, 9.783364087e-02F, -1.046190187e-01F, -6.633496284e-02F, 1.017404571e-01F, 1.793720573e-02F, + 1.197516769e-01F, -4.082819074e-02F, -7.701212168e-02F, -4.211626202e-02F, -1.949109584e-01F, 9.009619057e-02F, + -4.091933370e-02F, 9.029779583e-03F, -7.061741501e-02F, -7.975177467e-02F, 2.709863009e-03F, 1.326532066e-01F, + -1.284633726e-01F, -4.453955591e-02F, 3.460511938e-02F, 3.939338773e-02F, 1.511777788e-01F, -3.315737098e-02F, + -3.116275556e-02F, -2.284259535e-02F, -1.903772354e-01F, 9.639869630e-02F, -3.337907419e-02F, 6.168184336e-03F, +}; + +inline constexpr float kLayerOut[] = { + 2.527845800e-01F, -2.336000055e-01F, 1.088471338e-01F, -1.027681977e-01F, -3.352453113e-01F, 1.572262682e-02F, + -3.573338091e-01F, -1.824536026e-01F, 3.303555250e-01F, 2.585901022e-01F, 3.979495168e-01F, 3.923315927e-02F, + 2.875576317e-01F, 1.260993481e-01F, 2.318130573e-03F, -3.967759311e-01F, 1.964756697e-01F, -1.677433252e-01F, + 2.272706181e-01F, 1.092910860e-02F, -4.298679531e-01F, 1.956647821e-02F, -4.471704066e-01F, -7.890137285e-02F, + 1.548970193e-01F, 2.562610507e-01F, 3.074790835e-01F, -6.684211642e-02F, 3.780903816e-01F, 7.106346637e-02F, + 4.158715308e-01F, -1.443193704e-01F, -4.286563098e-01F, -2.907156944e-01F, 1.149592400e-01F, 3.880482912e-02F, + -1.172228605e-01F, 2.370177358e-01F, -2.905786633e-01F, -1.804578006e-01F, -3.677141070e-01F, 5.616746470e-02F, + 1.428215802e-01F, -4.397271201e-02F, 7.929944247e-02F, 3.628486022e-02F, 3.860718906e-01F, -4.545464516e-01F, + 8.728837222e-02F, -9.713643044e-02F, 3.740858734e-01F, -7.494435459e-02F, -8.757960796e-02F, 6.780961156e-02F, + -1.469855160e-01F, 3.964904696e-02F, -1.838291734e-01F, -2.140735975e-03F, 4.257995188e-01F, 1.546995714e-02F, + 4.656132162e-01F, 1.587652974e-02F, 2.391280532e-01F, -2.036559395e-02F, 4.096840695e-02F, -2.608436644e-01F, + 4.246742129e-01F, -1.210184172e-01F, -4.911835864e-02F, 1.918002963e-01F, -4.440214634e-01F, 3.444426358e-01F, + 1.942468435e-01F, 1.170225069e-01F, 2.086432427e-01F, 4.659599438e-02F, 2.476732880e-01F, 7.995926589e-02F, + 1.046233438e-02F, -1.123485640e-01F, 4.105989039e-01F, -3.340527713e-01F, 1.266499162e-01F, -1.811466366e-02F, + -4.117712677e-01F, 7.426451147e-02F, -1.670511067e-01F, -2.130899876e-01F, -2.450747490e-01F, -6.616560277e-03F, + 3.691284359e-01F, 3.332995251e-02F, 4.862446710e-02F, 1.840033233e-01F, 8.454033732e-02F, -2.305914462e-01F, + 4.105282426e-01F, -1.291410327e-01F, 2.975715995e-01F, 1.210130155e-01F, -1.702654511e-01F, 1.392304748e-01F, + -1.906514168e-01F, 2.174590975e-01F, 7.022763602e-03F, 2.304970920e-01F, 1.917784452e-03F, -3.522084281e-02F, + 1.954441704e-02F, 3.119841218e-01F, 2.726018429e-01F, -5.007213950e-01F, 2.566322088e-01F, -1.558039486e-01F, + 3.614739180e-01F, 1.408091336e-01F, -2.508419454e-01F, 9.827382118e-02F, -1.880220175e-01F, 4.040111303e-01F, + 2.472414076e-01F, 1.481537819e-01F, 4.957186803e-02F, -3.840491921e-02F, -2.882581204e-02F, 3.326622844e-01F, + 2.279196978e-01F, -4.949271083e-01F, 5.613027886e-02F, -2.792892754e-01F, 3.197499216e-01F, -4.221092165e-02F, + -4.232698977e-01F, 1.330143660e-01F, -4.394678771e-01F, -1.862223148e-01F, -9.058532305e-03F, 2.284751274e-02F, + 2.838097513e-01F, -6.785900146e-02F, 2.407725155e-01F, -4.488169681e-03F, 5.292757750e-01F, -2.001641691e-01F, + -1.734632552e-01F, -2.887595892e-01F, 5.224955827e-02F, 1.257681996e-01F, -1.195666268e-01F, 1.152948663e-01F, + -2.752819359e-01F, 3.551256061e-01F, -1.123386621e-01F, 6.455591321e-02F, 3.891908228e-01F, 1.290869992e-02F, + 4.430642352e-02F, 4.916761443e-02F, 2.150588930e-01F, -7.500408888e-01F, -2.472868562e-01F, -1.886118352e-01F, + -2.917013876e-02F, 1.491404977e-02F, -1.885893047e-01F, 9.818186611e-02F, -5.738478303e-01F, 3.043635488e-01F, + 2.502072230e-02F, 1.445396990e-01F, 2.306597680e-01F, 2.270553075e-02F, 3.350983858e-01F, -1.847708374e-01F, + 2.692521513e-01F, -6.036086082e-01F, -3.308954537e-01F, -2.211558372e-01F, 3.298546076e-01F, -1.092607677e-01F, + -2.281228751e-01F, 1.176955625e-01F, -4.490956664e-01F, -1.451539844e-01F, 2.944637835e-01F, 1.941348165e-01F, + 3.420356214e-01F, 1.246291399e-02F, -1.659116335e-02F, -1.028273329e-01F, 2.067355216e-01F, -6.646477580e-01F, +}; + +} // namespace w2vbert_goldens From 8534b88c2ffa2efccc86fe010fec42f20b2fc311 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:34:19 +0200 Subject: [PATCH 21/55] =?UTF-8?q?feat(ENG-MM-INPUT-PIPELINE):=20multimodal?= =?UTF-8?q?=20input=20limits=20and=20the=20refusal=20they=20carry=20?= =?UTF-8?q?=E2=80=94=20L1=20(#607)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL 43 of the 157 official vLLM recipes pass --language-model-only and we rejected it. L1 ports the mechanism behind that flag: per-modality input limits, and the REFUSAL that makes a limit a limit. Precedence mirrored literally from multimodal.py:321-336 -- language_model_only returns 0 for EVERY modality BEFORE the map is consulted, else the map entry, else 999. The case that distinguishes the orderings is a modality absent from the map, and it is pinned. The refusal mirrors context.py:392-405 (the min() fold), :409-428 (message and its conditional hint, character-identical including the backticks), and both call sites, with the enable_mm_embeds escape in both spellings. The judgement call worth recording: the refusal throws vllm::v1::InputValidationError, MOVED into its own header rather than a fresh multimodal exception class. api_server.cpp already maps that type to HTTP 400, mirroring upstream's ValueError -> BadRequestError. A bespoke class would have passed its own tests and then surfaced a too-many-images request as a 500. The review confirmed exactly one definition, no include cycle, and all 17 TUs seeing the identical type -- and stated the honest limit: the 400 path is unproven END TO END until L2 wires a call site, so L2's reviewer must treat that as an owed gate rather than an inherited pass. Scope is L1 only, verified clean in both directions: no serve flags, no ABI field, no tower gating, no kernel gate -- and the refusal was NOT deferred out. chat_mm.cpp is untouched, so its first-image-and-break truncation stands, filed as #686 and named in the PR body and USAGE.md rather than silently left. Wiring it is L2, which is what makes #686 the reason L2 exists. Reviewed on aarch64 (a CI-supported lane; the dev box was at 100% disk and load ~100 and was not used). Clean build 1262/1262 under -Werror with zero warnings; ctest 424/425, the sole failure test_async_llm passing 1/1 serially. 8 mutations run, 8 caught, none survived. The reviewer discarded its own first sweep after finding cp -a preserved mtimes and ninja had skipped a rebuild, so one result came from a stale binary carrying the previous mutation. The implementer's self-declared coverage gap is real and was judged correct layering rather than a hole: the precedence is MultiModalConfig's own contract and its own suite owns it. docs/USAGE.md was forced by check-doc-checkpoint classifying any include/vllm/ edit as user-facing; the entry states no request is limited or refused today, so a reader learns strictly less capability than exists. Row stays READY: no lifecycle move, L2-L4 owed. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/engine-matrix.md | 2 +- .agents/roadmap_v1.md | 2 +- .agents/specs/multimodal-track.md | 43 +- CMakeLists.txt | 3 + docs/USAGE.md | 20 + include/vllm/config/multimodal.h | 92 ++++ include/vllm/multimodal/processing/context.h | 112 +++++ include/vllm/v1/engine/input_processor.h | 20 +- include/vllm/v1/engine/validation_error.h | 50 +++ src/vllm/multimodal/processing/context.cpp | 107 +++++ tests/CMakeLists.txt | 7 + tests/vllm/config/test_multimodal_config.cpp | 120 ++++++ .../multimodal/test_processing_limits.cpp | 398 ++++++++++++++++++ 13 files changed, 956 insertions(+), 20 deletions(-) create mode 100644 include/vllm/config/multimodal.h create mode 100644 include/vllm/multimodal/processing/context.h create mode 100644 include/vllm/v1/engine/validation_error.h create mode 100644 src/vllm/multimodal/processing/context.cpp create mode 100644 tests/vllm/config/test_multimodal_config.cpp create mode 100644 tests/vllm/multimodal/test_processing_limits.cpp diff --git a/.agents/engine-matrix.md b/.agents/engine-matrix.md index ae56ecf48..1aed14697 100644 --- a/.agents/engine-matrix.md +++ b/.agents/engine-matrix.md @@ -71,7 +71,7 @@ forensics: roadmap_v1.md and the parity ledger. | `ENG-DBO-UBATCH` | DBO and ubatch overlap | T2 | `vllm/config/parallel.py:208,524` | - | - | `planned: specs/dbo-ubatch.md` | `INVENTORIED` | - | | `ENG-MOE-SHARED-AUX` | MoE shared-expert MLP on an aux CUDA stream concurrent with the routed-expert router/align/grouped-GEMMs (mirror vLLM's decode overlap; the largest remaining 35B c1/c2 engine lever). Fork the shared MLP onto a 2nd persistent per-device stream, join before the combine → byte-identical to serial (independent shared/routed paths both complete before combine; overlap changes WHEN not WHAT). Gated `T <= threshold` decode + CUDA. The aux stream draws scratch from a SEPARATE `AuxPool` so the concurrent main-stream routed allocations never share a live block with it (the `DevicePool` reuse invariant is single-stream ordering; vLLM sidesteps this with its stream-aware caching allocator's `record_stream`). `VT_MOE_SHARED_AUX_STREAM` **DEFAULT ON** (`=0` rollback); `VT_MOE_SHARED_AUX_THRESHOLD` (default 128; GB10 48-SM calibration). Captured in the decode CUDA-graph via the fork/join event edges (`ThreadLocal` capture, no abort). Only the committed Marlin MoE decode path; wmma fallback/CPU/GGUF and 27B dense unaffected | T1 | `vllm/model_executor/layers/fused_moe/runner/shared_experts.py:99-104,125-142`; `vllm/utils/multi_stream_utils.py:20-58` (`maybe_execute_in_parallel`, TRT-LLM port); `vllm/utils/torch_utils.py:736-756` (`aux_stream`); `vllm/envs.py:260` (threshold 256) | fork/join `src/vllm/model_executor/models/qwen3_5.cpp:3999,4114` (`MoeBlockFusedMarlinCuda`); aux stream+events `src/vllm/model_executor/models/qwen3_5.cpp:3575,3581` (`MoeAuxStream`/`MoeAuxStreamFor`); predicates `:3553,3560`; aux-pool isolation `:496,3538` (`AuxPool`/`ActivePool`/`ActivePoolScope`) + `DBuf pool_` routing `:645` | **DGX (prod flags, one flock):** overlap ON==OFF BYTE-IDENTICAL — `tests/parity/test_qwen36_paged_engine.cpp:116` 35B **315/315** + `tests/parity/test_qwen27_paged_engine.cpp` 27B **235/235** under `VT_MOE_SHARED_AUX_STREAM`∈{0,1}; captured-vs-eager (`VLLM_CPP_CUDAGRAPH=0`, ON) 315/315; shipping default (no env) 315/315+235/235, rollback `=0` 315/315+235/235; `compute-sanitizer memcheck` (default ON, captured) 0 errors; in-situ interleaved TPOT A/B (drop cold rep1) c1 −5.6% / c2 −2.7% / c4 −3.7% / c8 −3.4% / c16 −1.6% / c32 −1.5% (WINS every conc, zero regression); ledger [parity-ledger.md](parity-ledger.md) 2026-07-19 row | [moe-shared-aux-stream.md](specs/moe-shared-aux-stream.md) | `ANCHOR-BACKFILL` | `CLAIM-MOE-SHARED-AUX-1` | | `ENG-RUNNER-MODELSHAPE` | **Runner is model-shape-agnostic over the KV-cache group structure** — the extensibility deliverable the first additive-model bring-up (Qwen3 dense) forced. Before W1 the `GPUModelRunner` had only ever executed the Qwen3.6 HYBRID topology and hardcoded it in two places: (#1) the KV-buffer alloc loop indexed `config_.layer_types[l]`, out-of-bounds on a pure-dense model's EMPTY `layer_types`; (#2) each `execute_model` step unconditionally built the GDN metadata (`gather_block_table(gdn_group_id_)` / `remap_gdn_state_slots` / `GDNAttentionMetadataBuilder`), which reads `block_table[-1]` when there is no mamba group. W1 drives both off the resolved KV-group structure — a model-agnostic `has_mamba_group` / `gdn_group_id_ >= 0` predicate (NOT a model-name check): empty/absent `layer_types` ⇒ all full-attention; no mamba group ⇒ the whole GDN metadata/state path is skipped and `gdn_meta` stays default-empty. A full-attention-only KV config (one FA group, no MambaSpec) now allocates + steps cleanly; the hybrid gate models keep their GDN group so their path is BYTE-IDENTICAL. This is a one-time generalization: every future dense/non-hybrid arch (Llama, Mistral) now adds new-files-only, zero further runner edits. **PER-LAYER KV head_dim extension (Gemma-4 G1b, 2026-07-28, `CLAIM-GEMMA4-G1B`):** the runner's full-attn alloc/view loops now consume an OPTIONAL `KVCacheConfig::per_layer_attn_specs` (index == layer) so a HETEROGENEOUS-head_dim model (Gemma-4: sliding 256 / global 512, same num_kv_heads) sizes each non-GDN layer's paged KV + PagedKvCache view from its OWN spec. The field is EMPTY for every uniform-KV model ⇒ the loop collapses to the single group spec ⇒ byte-identical allocation/view/indexing/dispatch (same additive-identical property as the model-shape generalization above). Block table / KV manager / scheduler stay head_dim-independent (num_blocks + block_size, uniform) so no per-group block table is introduced | T0 | model-agnostic runner drives off `kv_cache_config.kv_cache_groups` — `vllm/v1/worker/gpu/model_runner.py` `initialize_kv_cache` / attention-metadata build (per-group, no hardcoded hybrid) @ `e24d1b24` | `src/vllm/v1/worker/gpu/runner.cpp:458-470` (alloc loop: `has_mamba_group && !layer_types.empty()` gate) + `:651-680` (GDN metadata build gated on `gdn_group_id_ >= 0`, default-empty `gdn_meta` otherwise); per-layer KV head_dim: `include/vllm/v1/kv_cache_interface.h` (`KVCacheConfig::per_layer_attn_specs`) consumed in `src/vllm/v1/worker/gpu/runner.cpp` `initialize_kv_cache` (per-layer `FaDims` alloc+view), published by `src/vllm/model_executor/models/gemma4_registry.cpp` (`MakeGemma4ForConditionalGenerationKVCache`); the full-attention-only KV spec that exercises the base path `src/vllm/model_executor/models/qwen3_dense.cpp` (`MakeQwen3ForCausalLMKVCache`) | `tests/vllm/v1/worker/test_runner.cpp:1129` — "full-attention-only KV config allocates without the GDN path" + "full-attention-only step skips GDN metadata build (no OOB)" (RED→GREEN: both SIGSEGV pre-generalization; GREEN post). Behaviour-preservation gate: DGX **27B 235/235 + 35B 315/315 UNCHANGED** under the fix; per-layer-KV inertness: full CPU runner/KV suite green + **OLMo-2 SACRED GPU re-gate 16/16 UNCHANGED**; heterogeneous path proven by **Gemma-4 E4B STRICT 32/32** (`tests/parity/test_gemma4_paged_engine.cpp`); ASan/UBSan clean on the affected paths | [first-additive-model-qwen3-dense.md](specs/first-additive-model-qwen3-dense.md) §3 (seam gaps #1/#2), §6 (W1); [gemma4-multimodal.md](specs/gemma4-multimodal.md) §G1b | `ACTIVE` | `CLAIM-MODEL-QWEN3-DENSE` | -| `ENG-MM-INPUT-PIPELINE` | **Multimodal INPUT pipeline + encoder-cache engine seam (M1), INERT when no mm input.** The C++ mirror of `vllm/multimodal/`: `MultiModalKwargs`/`MultiModalFeatureSpec`/`MultiModalInputs`, the `MultiModalHasher` mm-hash (blake3), the Qwen3-VL image processor (smart_resize + fused rescale/normalize + patchify -> `pixel_values`+`image_grid_thw`) and placeholder-token expansion, plus the `EncoderCacheManager` (+`ComputeMmEncoderBudget`) and the LMCache `extra_keys` seam. Additive `mm_features` carried on `Request`/`EngineCoreRequest`; with NO mm input every field is empty and every path is byte-identical to the text engine. Processor output is BIT/BYTE-identical to the vLLM 0.25.0 oracle (M0 fixture). Does NOT build the vision tower / embed-merge (M2). **SERVING wiring (ROAD-V1-MM `MM-SERVE-ENGINE`, 2026-07-28, `CLAIM-MM-SERVING-W2`):** the OpenAI server now carries the parsed `MultiModalInputs` into the engine — additive `LLMEngine`/`AsyncLLM` `add_request(MultiModalInputs)`+`generate(MultiModalInputs)` overloads via `InputProcessor::process_inputs_mm` (mirror `input_processor.py:333-379`, empty mm_features == the tokens path), the chat-template placeholder-STRING helpers (`get_placeholder_str`/`_add_placeholder` mirror), and the serving_chat `MultiModalChatFn` seam (default unset ⇒ text byte-identical). **SEAM BODY (ROAD-V1-MM `MM-SERVE-E2E` W3, 2026-07-28, `CLAIM-MM-SERVING-E2E`):** `MakeQwen3VLImageChatFn` (chat_mm.cpp) is the seam body the server sets — messages → marker-inject → chat template → `EncodeWithSpecialTokens` (the single image_pad marker → one image_token_id) → `RouteImageRgb` EXPAND to 196 image tokens + mm_features; wired in `examples/server/main.cpp` (guarded on `preprocessor_config.json`; text-only unset ⇒ byte-identical). Gated `test_chat_mm` 8/8 + `test_openai_serving` (seam invoked + routed). **ENGINE MM-FORWARD LANDED (ROAD-V1-MM `MM-SERVE-E2E`, 2026-07-28, `CLAIM-ENGINE-MM-FORWARD`):** the engine model runner now HAS an mm forward — `ModelForwardInput` gains an ADDITIVE default-nullopt `std::optional mm` (merged inputs_embeds + 3-D MRoPE positions + DeepStack, borrowed handles; nullopt-for-text ⇒ shared runner path byte-identical BY CONSTRUCTION), `Qwen3VLForConditionalGeneration` is `REGISTER_VLLM_MODEL`-registered (`qwen3_vl_registry.cpp`), and the registered forward FOLDS the M2c decode into `ModelRegistry::Forward` via the SHARED `Qwen3VLForwardStepLastLogits` (`Qwen3VLGenerateGreedyViaRegistry` drives every step through the registry). GPU token-exact gate `test_qwen3vl_registry_e2e` (image→text THROUGH `ModelRegistry::Forward` == M2c golden 32/32 STRICT, dgx.casa GB10); text inertness `test_runner` 16/16 + `test_scheduler` 36/36 + `test_model_registry` 24/24 + `test_chat_mm` 8/8 + `test_openai_serving` 41/41 all green. RESIDUAL: the FULL in-runner scheduler-fed tower run (batched-loop mm building the field from staged encoder outputs) + the real server `/v1/chat/completions` GPU e2e — recipe in `specs/mm-serving.md`. | T1 | `vllm/multimodal/{inputs.py,hasher.py:50,processing/processor.py:1663,processing/inputs.py:62}`; `vllm/model_executor/models/qwen3_vl.py:{1400,1233}`; `vllm/v1/core/encoder_cache_manager.py:17`; transformers `image_processing_qwen2_vl.py:62`, `image_processing_backends.py:327`; tests `tests/multimodal/test_processing.py`, `tests/multimodal/test_hasher.py`, `tests/v1/core/test_encoder_cache_manager.py` @ `e24d1b24` | `src/vllm/multimodal/hasher.cpp`, `src/vllm/multimodal/qwen3vl_processor.cpp`, `include/vllm/multimodal/{inputs.h,hasher.h,qwen3vl_processor.h}`; `src/vllm/v1/core/encoder_cache_manager.cpp` + `include/vllm/v1/core/encoder_cache_manager.h`; additive inert fields `include/vllm/v1/request.h` + `src/vllm/v1/request.cpp` + `include/vllm/v1/engine/types.h`; `extra_keys` seam `include/vllm/v1/kv_offload/lmcache/chunked_token_database.h` + `.cpp`; M0 `scripts/mm/m0_oracle_capture.py` — anchor `src/vllm/multimodal/hasher.cpp:56` | `tests/vllm/multimodal/test_qwen3vl_processor.cpp` (processor-parity 23/23 BIT-identical vs the M0 oracle fixture `tests/vllm/multimodal/fixtures/qwen3vl/`, RED-first: wrong normalize shift -> 1.2M mismatches); `tests/vllm/v1/core/test_encoder_cache_manager.cpp` 32/32. Text-inertness: `test_request`/`test_engine_types`/`test_lmcache_codec`/`test_lmcache_key_agreement`/`test_openai_conformance` all green standalone; SACRED CUDA 27B/35B/Coder = GPU inertness proof; `check-device-leakage` OK — anchor `tests/vllm/multimodal/test_qwen3vl_processor.cpp:59` | [multimodal-track.md](specs/multimodal-track.md) §3 (M0/M1) | `READY` | - | +| `ENG-MM-INPUT-PIPELINE` | **Multimodal INPUT pipeline + encoder-cache engine seam (M1), INERT when no mm input.** The C++ mirror of `vllm/multimodal/`: `MultiModalKwargs`/`MultiModalFeatureSpec`/`MultiModalInputs`, the `MultiModalHasher` mm-hash (blake3), the Qwen3-VL image processor (smart_resize + fused rescale/normalize + patchify -> `pixel_values`+`image_grid_thw`) and placeholder-token expansion, plus the `EncoderCacheManager` (+`ComputeMmEncoderBudget`) and the LMCache `extra_keys` seam. Additive `mm_features` carried on `Request`/`EngineCoreRequest`; with NO mm input every field is empty and every path is byte-identical to the text engine. Processor output is BIT/BYTE-identical to the vLLM 0.25.0 oracle (M0 fixture). Does NOT build the vision tower / embed-merge (M2). **SERVING wiring (ROAD-V1-MM `MM-SERVE-ENGINE`, 2026-07-28, `CLAIM-MM-SERVING-W2`):** the OpenAI server now carries the parsed `MultiModalInputs` into the engine — additive `LLMEngine`/`AsyncLLM` `add_request(MultiModalInputs)`+`generate(MultiModalInputs)` overloads via `InputProcessor::process_inputs_mm` (mirror `input_processor.py:333-379`, empty mm_features == the tokens path), the chat-template placeholder-STRING helpers (`get_placeholder_str`/`_add_placeholder` mirror), and the serving_chat `MultiModalChatFn` seam (default unset ⇒ text byte-identical). **SEAM BODY (ROAD-V1-MM `MM-SERVE-E2E` W3, 2026-07-28, `CLAIM-MM-SERVING-E2E`):** `MakeQwen3VLImageChatFn` (chat_mm.cpp) is the seam body the server sets — messages → marker-inject → chat template → `EncodeWithSpecialTokens` (the single image_pad marker → one image_token_id) → `RouteImageRgb` EXPAND to 196 image tokens + mm_features; wired in `examples/server/main.cpp` (guarded on `preprocessor_config.json`; text-only unset ⇒ byte-identical). Gated `test_chat_mm` 8/8 + `test_openai_serving` (seam invoked + routed). **ENGINE MM-FORWARD LANDED (ROAD-V1-MM `MM-SERVE-E2E`, 2026-07-28, `CLAIM-ENGINE-MM-FORWARD`):** the engine model runner now HAS an mm forward — `ModelForwardInput` gains an ADDITIVE default-nullopt `std::optional mm` (merged inputs_embeds + 3-D MRoPE positions + DeepStack, borrowed handles; nullopt-for-text ⇒ shared runner path byte-identical BY CONSTRUCTION), `Qwen3VLForConditionalGeneration` is `REGISTER_VLLM_MODEL`-registered (`qwen3_vl_registry.cpp`), and the registered forward FOLDS the M2c decode into `ModelRegistry::Forward` via the SHARED `Qwen3VLForwardStepLastLogits` (`Qwen3VLGenerateGreedyViaRegistry` drives every step through the registry). GPU token-exact gate `test_qwen3vl_registry_e2e` (image→text THROUGH `ModelRegistry::Forward` == M2c golden 32/32 STRICT, dgx.casa GB10); text inertness `test_runner` 16/16 + `test_scheduler` 36/36 + `test_model_registry` 24/24 + `test_chat_mm` 8/8 + `test_openai_serving` 41/41 all green. RESIDUAL: the FULL in-runner scheduler-fed tower run (batched-loop mm building the field from staged encoder outputs) + the real server `/v1/chat/completions` GPU e2e — recipe in `specs/mm-serving.md`. **INPUT LIMITS L1 LANDED (#607, 2026-08-13):** the per-modality `limit_per_prompt` + `GetLimitPerPrompt` precedence (`language_model_only` ⇒ 0 BEFORE the map, else the map, else 999) and the refusal that gives those numbers effect — `AllowedMmLimits` folding by `min()` against the model's own ceiling, `ValidateNumItems` with upstream's exact message, and both call sites with the `enable_mm_embeds` escape. NO serve surface and NO live call site: nothing constructs a `MultiModalConfig` on a request yet, which is L2's. | T1 | `vllm/multimodal/{inputs.py,hasher.py:50,processing/processor.py:1663,processing/inputs.py:62}`; `vllm/model_executor/models/qwen3_vl.py:{1400,1233}`; `vllm/v1/core/encoder_cache_manager.py:17`; transformers `image_processing_qwen2_vl.py:62`, `image_processing_backends.py:327`; tests `tests/multimodal/test_processing.py`, `tests/multimodal/test_hasher.py`, `tests/v1/core/test_encoder_cache_manager.py` @ `e24d1b24` | `src/vllm/multimodal/hasher.cpp`, `src/vllm/multimodal/qwen3vl_processor.cpp`, `include/vllm/multimodal/{inputs.h,hasher.h,qwen3vl_processor.h}`; `src/vllm/v1/core/encoder_cache_manager.cpp` + `include/vllm/v1/core/encoder_cache_manager.h`; additive inert fields `include/vllm/v1/request.h` + `src/vllm/v1/request.cpp` + `include/vllm/v1/engine/types.h`; `extra_keys` seam `include/vllm/v1/kv_offload/lmcache/chunked_token_database.h` + `.cpp`; M0 `scripts/mm/m0_oracle_capture.py`; L1 limits `include/vllm/config/multimodal.h` + `include/vllm/multimodal/processing/context.h` + `src/vllm/multimodal/processing/context.cpp`, refusal type relocated to `include/vllm/v1/engine/validation_error.h` — anchor `src/vllm/multimodal/hasher.cpp:56` | `tests/vllm/multimodal/test_qwen3vl_processor.cpp` (processor-parity 23/23 BIT-identical vs the M0 oracle fixture `tests/vllm/multimodal/fixtures/qwen3vl/`, RED-first: wrong normalize shift -> 1.2M mismatches); `tests/vllm/v1/core/test_encoder_cache_manager.cpp` 32/32. Text-inertness: `test_request`/`test_engine_types`/`test_lmcache_codec`/`test_lmcache_key_agreement`/`test_openai_conformance` all green standalone; SACRED CUDA 27B/35B/Coder = GPU inertness proof; `check-device-leakage` OK — anchor `tests/vllm/multimodal/test_qwen3vl_processor.cpp:59`. L1 limits: `tests/vllm/config/test_multimodal_config.cpp` 7/7 (21 assertions) + `tests/vllm/multimodal/test_processing_limits.cpp` 19/19 (78 assertions), porting `tests/multimodal/test_processing.py:902-941,944-985`, `tests/entrypoints/multimodal/llm/test_mm_embeds_only.py:41-49` and `tests/entrypoints/unit_tests/test_chat_utils.py:1498-1560` @ `5559679229bc`; mutations proven RED: map-before-flag precedence, the dropped throw, the dropped `min()` fold | [multimodal-track.md](specs/multimodal-track.md) §3 (M0/M1) | `READY` | - | | `ENG-MM-VISION-TOWER` | **Qwen3-VL vision tower `Qwen3_VisionTransformer` (M2a), proven faithful vs vLLM 0.25.0 in isolation.** The reusable vision half of the whole Qwen3-VL family + Qwen3.6 (27B/35B share this exact tower). Pure-additive C++ forward composed from public vt:: ops: patch-embed (Conv3d-as-matmul + bias), host pos-embed bilinear-interp+spatial-merge-reorder, 24 ViT blocks (LayerNorm + vision attention with partial-rotary NeoX vision RoPE via `vt::RopeFromCache` + non-causal `vt::Attention(causal=false)` + tanh-GELU MLP), patch merger (LayerNorm + exact-erf-GELU + 2 FCs), DeepStack 3 post-shuffle-norm mergers at layers 5/11/17 → `[196,10240]`. Adds 2 additive elementwise vt ops (`GeluTanh`/`GeluErf`). NO runner/model/registry edit → text engines byte-identical by construction. Proven faithful in ISOLATION; the merge into `input_embeds` + the MRoPE/DeepStack text backbone + the e2e image gate are M2b/M2c. | T1 | `vllm/model_executor/models/qwen3_vl.py` `Qwen3_VisionPatchEmbed:347`, `Qwen3_VisionBlock:413`, `Qwen3_VisionPatchMerger:467`, `Qwen3_VisionTransformer:519`, `forward:800`, `pos_embed_interpolate_native:277`, `rot_pos_emb:667`; `qwen2_5_vl.py::Qwen2_5_VisionAttention.forward:397`; `rotary_embedding/common.py::ApplyRotaryEmb.forward_static:151` @ `e24d1b24` | `src/vllm/model_executor/models/qwen3_vl_vision.{h,cpp}`; 2 vt ops `include/vt/ops.h` + `src/vt/ops.cpp` + `src/vt/cuda/cuda_layernorm.cu` + `src/vt/cpu/cpu_layernorm.cpp`; dumps `scripts/mm/m2a_tower_{ref,weight}_dump.py`; fixtures `tests/vllm/multimodal/fixtures/qwen3vl_tower/` | `tests/vllm/multimodal/test_qwen3vl_tower.cpp` — 4 RED-first tower gates vs the dumped vLLM-0.25.0 reference 348/348 (patch-embed 2.1e-3, block0 6.8e-3, merger 6.5e-2, DeepStack 1.2e-2/3.3e-2/4.4e-2, full tower 5.1e-2; pos-embed 2.5e-3 + rope 1.9e-3 TIGHT); bf16-depth envelope RCA'd; RED = rope disabled → block0 0.149/tower 0.75/6 fails; cutlass-ON+FA2 banner; clean `-Werror`; compute-sanitizer 0 — anchor `tests/vllm/multimodal/test_qwen3vl_tower.cpp:96` | [multimodal-track.md](specs/multimodal-track.md) §3 (M2a) | `ACTIVE` | `CLAIM-MULTIMODAL-M2A` | | `ENG-MM-TEXT-BACKBONE` | **Qwen3-VL text-backbone numeric contracts `Qwen3VLGetRopeIndex`/`Qwen3VLMergeMultimodal`/`Qwen3VLComputeDeepstack` (M2b/M2c), unit-green vs vLLM 0.25.0.** The deterministic pieces that fork the plain Qwen3-dense text path for a vision-conditioned decode: (1) MRoPE 3-D `get_rope_index` positions [3,T] (image tokens get (t,h,w) grid positions, text sequential); (2) the 3-section MRoPE APPLICATION — proven to be the EXISTING `vt::RopeFromCache` mrope path (positions [3,T] + `mrope_section=[24,20,20]` interleaved), faithful to `MRotaryEmbedding.forward_native` for Qwen3-VL's exact config; (3) `_compute_deepstack_embeds` scatter → [L,T,H] decoder-injection tensor; (4) `_merge_multimodal_embeddings` masked scatter of the tower's `[:,:2560]` into `input_embeds`. Pure-additive TU — NO shared dense forward / runner / registry edit → text engines byte-identical by construction. The e2e image forward (VL weight loader + forked MRoPE/DeepStack decode loop) is the remaining M2c wire-up. | T1 | `vllm/model_executor/models/qwen3_vl.py` `_get_mrope_input_positions:2567`, `_iter_mm_grid_hw:2482`, `_compute_deepstack_embeds:2761`, `Qwen3LLMModel.forward` deepstack `:1589`; `vllm/model_executor/models/utils.py::_merge_multimodal_embeddings:524`; `vllm/model_executor/layers/rotary_embedding/mrope.py` MRotaryEmbedding @ `e24d1b24` | `src/vllm/model_executor/models/qwen3_vl_text.{h,cpp}`; existing `vt::RopeFromCache` mrope path (`src/vt/{cpu,cuda}/*`); dump `scripts/mm/m2b_text_ref_dump.py`; fixtures `tests/vllm/multimodal/fixtures/qwen3vl_text/` — anchor `src/vllm/model_executor/models/qwen3_vl_text.cpp:9` | `tests/vllm/multimodal/test_qwen3vl_text.cpp` — 4 RED-first gates vs the dumped vLLM-0.25.0 reference 85/85 (get_rope_index BIT-exact [3,204], delta −182; MRoPE q rel-L2 1.5e-3 / k 1.5e-3, RED interleaved-off >5e-2; DeepStack + merge BIT-exact); CPU-only, no weights; clean CPU `-Werror` — anchor `tests/vllm/multimodal/test_qwen3vl_text.cpp:99` | [multimodal-track.md](specs/multimodal-track.md) §3 (M2b/M2c) | `ACTIVE` | `CLAIM-MULTIMODAL-M2BC` | | `ENG-MM-QWEN36-VL-FORWARD` | **Qwen3.6-27B (`Qwen3_5ForConditionalGeneration`) GDN-hybrid VL forward — IMAGE (M3-b) + VIDEO (M3d) BOTH e2e, STRICT gates PASS 32/32. Our own gate model's image+video paths now work end-to-end (speed pending).** The genuinely-new integration completing our own gate model's mm paths: fork the landed bf16 `Qwen3_5DenseModel` GDN-hybrid forward (48 GDN + 16 full-attn) on gated, default-off points so a text-only 27B request stays byte-identical — (a) `inputs_embeds` entry (embed ids + `Qwen3VLMergeMultimodal` scatter of the 27B tower merger `[N,5120]` into the visual-token rows; 27B has EMPTY `deepstack_visual_indexes` ⇒ NO DeepStack); (b) 3-section MRoPE (`mrope_section=[11,11,10]` interleaved, rotary_dim 64, theta 1e7) in the 16 full-attn layers only via the proven `vt::RopeFromCache` mrope path (GDN layers carry no rope); (c) mixed load = the M2a `Qwen3_VisionTransformer` (27B vision config, empty deepstack) bf16 tower + the bf16 GDN-hybrid LLM via the EXISTING `LoadQwen3_5Dense`. **M3d (2026-07-25) added VIDEO by REUSE:** the M3-b image driver refactored into a shared `VLGenerateCoreGdn`, image+video wrappers differ ONLY in the merge mask (`image_token` vs `video_token` across frames) + the get_rope_index (`Qwen3VLGetRopeIndex` vs `Qwen3VLGetRopeIndexVideo`); the M3c processor/windowed-tower/video-MRoPE are reused verbatim. | T1 | `vllm/model_executor/models/qwen3_5.py:389` (`Qwen3_5ForConditionalGeneration` subclasses `Qwen3VLForConditionalGeneration`; `visual = Qwen3_VisionTransformer`, modalities {"image","video"}); `qwen3_vl.py` `_process_video_input:2165`, `_get_mrope_input_positions:2567` video branch, `get_video_repl:1479`; the 27B `config.json` (`mrope_section=[11,11,10]`, empty `deepstack_visual_indexes`) @ `e24d1b24` / vLLM 0.25.0 | **M3-b + M3d BUILT + GATED 2026-07-25:** vision-only loader `LoadQwen3VLVisionWeights` (`src/vllm/model_executor/models/qwen3_vl.cpp`, 27B config) + shared `VLGenerateCoreGdn` + image driver `Qwen3_5VLGenerateGreedy` + **video driver `Qwen3_5VLGenerateGreedyVideo`** + `BuildMropeCosSinHost` + the `mrope_cos_sin` param on `DenseForwardLayers` (`src/vllm/model_executor/models/qwen3_5.cpp`, nullptr on every text caller ⇒ byte-identical; the video driver is purely additive, the shared text forward UNTOUCHED per `git diff --stat`) reusing M2a tower + `LoadQwen3_5Dense` bf16 LLM | **IMAGE:** golden `tests/vllm/multimodal/fixtures/qwen3_5_27b/` (STRICT sha256 `ead4b484…`); STRICT image gate PASS **32/32** (`test_qwen3_5_vl_e2e.cpp`, 54/54, re-run post-refactor). **VIDEO (M3d):** oracle `scripts/mm/m3d_video_oracle_capture.py` on the M3c synthetic clip (raw sha `8a111599…`, grid `[4,8,8]`, 64 video tokens) K=5 DETERMINISTIC ⇒ STRICT golden; **STRICT video gate PASS 32/32** (`test_qwen3_5_vl_video_e2e.cpp`, 27/27; near-tie gaps 0.0000 nats everywhere), fixtures `tests/vllm/multimodal/fixtures/qwen3_5_27b_video/`. Text-inertness 27B 235/235, 35B 315/315, Coder 138/138 (by construction); clean `-Werror` 0 warn; compute-sanitizer 0 on the 27B video forward. **SPEED MEASURED (2026-07-26, `CLAIM-MULTIMODAL-SPEED`): image c1 vs vLLM 0.25.0 GRAPHED — decode TPOT 225.0 ms/tok vs 226.9 = AT PARITY (0.99×), LLM prefill 326 ms vs vLLM TTFT 321 ms = at parity; vision tower WAS 2114 ms vs vLLM encode ≤~250 ms = ~10× (THE gap). TOWER LEVER EXECUTED (2026-07-26, `CLAIM-MULTIMODAL-SPEED-TOWER`, [multimodal-speed.md](specs/multimodal-speed.md) §7): nsys `cuda_gpu_kern_sum` attributed 98.9 % of the tower forward to the naive `vt::cuda::AttentionKernel` (56 ms/block; NOT QKV/FA2-routing); fixed by a warp-scoped online-softmax op `AttentionDenseFast` (separate op ⇒ `kAttention`/text byte-identical) + one-time resident-weight load ⇒ per-image tower 2114 → 148 ms (14.3×), **0.59× vs vLLM eager encode = FASTER**. STRICT image/video e2e HELD 32/32 (+4B DeepStack 32/32), `test_ops_attention` 37239/37239, 27B text SACRED 235/235, compute-sanitizer memcheck 0, clean `-Werror`. `benchmark_binding=false`, single-seq driver (no c2+/server). Remaining: batched/graphed mm serving (c2+) + audio our-side — DONE bar not yet met.** | [multimodal-track.md](specs/multimodal-track.md) §M3 + [multimodal-speed.md](specs/multimodal-speed.md) §7 + §8 (decode lever #2 CLOSED 2026-07-27: on-GPU greedy argmax + decode embed round-trip removed on `VLGenerateCoreGdn`; bit-exact — image/video STRICT 32/32 held; 27B decode NEUTRAL at the ~222 ms bandwidth floor) + §9 (lever #3 FIRST BRICK 2026-07-27, `CLAIM-MULTIMODAL-SPEED-GRAPH`: the shared `VLGenerateCoreGdn` decode step now routes through the production `Qwen3_5DenseDecodeGraph` cold→warm→replay captured decode — the mm decode is now GRAPH-CAPTURABLE, closing the un-graphed-eager-loop structural gap; S==B==1 bit-identical rebuild; token-exact HELD image/video STRICT 32/32 with 30 graph replays confirmed; A/B graphed 232.5 vs eager 233.4 ms/tok = NEUTRAL at the 27B bandwidth floor; the launch-overhead win + batched c2+ + serving ingestion are the recorded W-plan W1-W3) + §16 (vision-forward flash kernel 2026-07-28, `CLAIM-MM-SPEED-QWEN-IMAGE`: ATTRIBUTION-FIRST nsys attributed ~85% of the 148 ms tower forward to the dense attention `AttentionWarpKernel` [4.66 ms/block×27]; routed it to the §14 flash-tiled `vt::AttentionDenseFlash` [head_dim 72, byte-identical — per-warp math verbatim, only K/V from shared-mem tiles]. STRICT image/video e2e HELD 32/32 [27B+4B], `test_ops_attention` 37239/37239, goldens md5 UNCHANGED, nsys proof `AttentionDenseFlashKernel` 24 inst/zero warp, RED 30/46→46/46, sanitizer 0. A/B warp 148.3→flash 142.3 ms = 1.04× — the profile REFUTED a big lever: at t=784 the vision attention is serial-latency-bound not bandwidth-bound [audio §14 was 1.82× at t=1500], flash recovers only ~6 ms. **HONEST: the tower ALREADY BEATS vLLM — 142 ms vs ~250 ms eager encode = 0.57×**; image/video mm-forward is correctness-DONE + speed-BEATS-vLLM; residual = tensor-core MMA hd-72 attention [not needed for parity] + batched c2+/serving) | `ACTIVE` | `CLAIM-MULTIMODAL-SPEED-TOWER` + `CLAIM-MULTIMODAL-SPEED-DECODE` + `CLAIM-MULTIMODAL-SPEED-GRAPH` + `CLAIM-MM-SPEED-QWEN-IMAGE` | diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index fe3f709d3..91e30750d 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -189,7 +189,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#558](https://github.com/mudler/vllm.cpp/issues/558) | — | `tests/parity/hf_snapshot.h` has no guard against declaration-order breaks: the C++ build catches them, but the records-only lane that broke it never builds C++, and all 14 TUs that include the header are checkpoint-gated so `ctest` reports the break as `***Not Run`. `fafa16f0f` (#546, #551) fixed the ordering and carried no guard | bug | | [#603](https://github.com/mudler/vllm.cpp/issues/603) | — | `windows-msvc-cpu` / `windows-msvc-vulkan` are RED on `main`: `test_backend_cross_device.cpp` calls POSIX `setenv`/`unsetenv`, which MSVC does not provide | bug | | [#606](https://github.com/mudler/vllm.cpp/issues/606) | — | `vllm-serve` aborts on `--enable-auto-tool-choice` (89/157 recipes) and `--trust-remote-code` (82/157), both of which are no-ops for us, so a copy-pasted official recipe command never reaches model load. Needs an accepted-and-inert seam with a per-flag reason; no row owns serve CLI recipe compatibility | feature | -| [#607](https://github.com/mudler/vllm.cpp/issues/607) | `ENG-MM-INPUT-PIPELINE` | **Premise corrected 2026-08-13, see `specs/multimodal-track.md` §1.5.** Not "skip the vision encoder": `--language-model-only` sets every modality limit to **0** (`multimodal.py:78,321-327`) and is sugar over `--limit-mm-per-prompt`. Two consequences follow, and we have NEITHER — upstream then **refuses every multimodal request** (`processing/context.py:409-428` raises "At most 0 image(s) may be provided in one prompt", from `:461` and `chat_utils.py:662`), and builds the tower uninitialised (`interfaces.py:293`). We have no `MultimodalConfig`, no `limit_per_prompt` and no enforcement at all, so this is a PORT of the limits mechanism (L1-L4), not the exposure of a boolean. 43 of 157 recipes pass the flag and we abort on it. The flag appears in this repo only in `tools/bench/run_serve_low.py`, which passes it to the ORACLE — a grep reads as coverage and is not | feature | +| [#607](https://github.com/mudler/vllm.cpp/issues/607) | `ENG-MM-INPUT-PIPELINE` | **Premise corrected 2026-08-13, see `specs/multimodal-track.md` §1.5.** Not "skip the vision encoder": `--language-model-only` sets every modality limit to **0** (`multimodal.py:78,321-327`) and is sugar over `--limit-mm-per-prompt`. Two consequences follow, and we have NEITHER — upstream then **refuses every multimodal request** (`processing/context.py:409-428` raises "At most 0 image(s) may be provided in one prompt", from `:461` and `chat_utils.py:662`), and builds the tower uninitialised (`interfaces.py:293`). This is a PORT of the limits mechanism (L1-L4), not the exposure of a boolean. **L1 LANDED 2026-08-13:** `vllm::MultiModalConfig` + `GetLimitPerPrompt` (`include/vllm/config/multimodal.h`) and the refusal it carries (`include/vllm/multimodal/processing/context.h`) are in, unit-gated, with NO serve surface — nothing constructs the config on a live request yet, so the two call sites still validate nothing. L2 (the flags + C-ABI field + wiring those call sites), L3 (tower skip) and L4 (kernel gate) are still owed. 43 of 157 recipes pass the flag and we abort on it. The flag appears in this repo only in `tools/bench/run_serve_low.py`, which passes it to the ORACLE — a grep reads as coverage and is not | feature | | [#651](https://github.com/mudler/vllm.cpp/issues/651) | — | `test_agent_record`'s MODEL-ratchet docstring is two contradictory paragraphs spliced together, and the surviving half records a pin transition that never happened | bug | | [#652](https://github.com/mudler/vllm.cpp/issues/652) | — | `model-matrix.md` prose counters drifted: LTX-2.5 reached the rows and the CI-enforced rollup but none of the five sentences that count them | bug | | [#659](https://github.com/mudler/vllm.cpp/issues/659) | — | LTX-2.5 device select adopts M3a's platform seam but not its companion capability guard: `ltx2_video.cpp` asks `CurrentPlatform().device_type()` and `TryGetBackend(...)` but never `supports_model_architecture`, so a PARTIAL backend (Metal 15/75 ops, Tenstorrent) is handed a queue and dies in a kernel bind where it used to be refused BY NAME (found while reviewing #553 for landing) | bug | diff --git a/.agents/specs/multimodal-track.md b/.agents/specs/multimodal-track.md index 262f976ea..2558f6491 100644 --- a/.agents/specs/multimodal-track.md +++ b/.agents/specs/multimodal-track.md @@ -251,9 +251,12 @@ That is what makes the limits the mechanism and the flag the sugar, and it is the half a port can most easily leave out, because omitting it breaks nothing that a text-only workload would notice. -**Our baseline is nothing.** `grep -rn 'limit_per_prompt\|MultimodalConfig' src/ -include/` returns no hits; no multimodal config surface exists, and nothing gates -tower construction on config. This is a port, not an exposure. +**Our baseline was nothing** (as of 2026-08-13, before L1): `grep -rn +'limit_per_prompt\|MultimodalConfig' src/ include/` returned no hits; no +multimodal config surface existed, and nothing gated tower construction on +config. This is a port, not an exposure. L1 has since landed the config and the +refusal (see the wave list below); tower construction is still ungated by +config, which is L3. **A second-order consequence worth naming.** `qwen3_next.py:325` proves the flag is not purely a memory knob — upstream uses it to select the *fused* QK-norm+RoPE+ @@ -273,6 +276,40 @@ comparing the two arms must set the flag on both sides or state that it did not. upstream's `enable_mm_embeds` escape. Unit-gated, no serve surface yet. The refusal belongs here, not in L3: it is the limits' own semantics, and a limit nothing enforces is not a limit. + + **LANDED 2026-08-13 (#607, `row/mm-limits-l1`).** `include/vllm/config/multimodal.h` + carries `vllm::MultiModalConfig` with the three ported fields and + `GetLimitPerPrompt`, mirroring `multimodal.py:78,81,98,321-336` — the config + directory is a 1:1 mirror of `vllm/config/.py`, so `multimodal.h` is the + only home that is not bespoke. The enforcement is + `include/vllm/multimodal/processing/context.h` + `src/…/context.cpp`: + `BaseProcessingInfo::{AllowedMmLimits,ValidateNumItems,ValidateParsedMmData, + ValidateTrackedChatItem}`, mirroring `processing/context.py:392-405,409-428, + 441-461` and `chat_utils.py:630-662` including the `enable_mm_embeds` escape in + both of its spellings. The refusal throws `vllm::v1::InputValidationError` — + the type `api_server.cpp:185,252` already maps to HTTP 400, which is what + upstream's `VLLMValidationError` gets; a second refusal class would have landed + a too-many-images request as a 500. That type moved from + `v1/engine/input_processor.h` into `v1/engine/validation_error.h` (same name, + same namespace, no behaviour change) so the multimodal layer can throw it + without including the input processor and without a header cycle at L2's call + site. Gated `test_multimodal_config` 7/7 (21 assertions) + + `test_processing_limits` 19/19 (78 assertions); both suites port the upstream + parametrizations verbatim from `tests/multimodal/test_processing.py:902-941, + 944-985`, `tests/entrypoints/multimodal/llm/test_mm_embeds_only.py:41-49` and + `tests/entrypoints/unit_tests/test_chat_utils.py:1498-1560`. + + **What L1 deliberately does NOT do, so L2 knows what it inherits.** Nothing + constructs a `MultiModalConfig` yet and nothing calls the validators on a live + request: no model config owns one, so `process_inputs_mm` + (`input_processor.cpp:321,352` — our `context.py:461`) and the chat seam + (`chat_mm.cpp`, our `chat_utils.py:662`) still validate nothing. Wiring those + two call sites is L2's, together with the flags and the C-ABI field, because it + is the config reaching them that the flags exist to set. Separately noted for + whoever takes L2: `chat_mm.cpp:256-270` takes the FIRST image part and silently + ignores the rest, so today a 5-image request is neither served nor refused — it + is quietly truncated to one. That is the divergence L2 closes by calling + `ValidateTrackedChatItem` per tracked item. - **L2** — `--limit-mm-per-prompt` and `--language-model-only` serve flags plus the C-ABI field, over L1. This is the point at which the 43 recipes stop aborting. diff --git a/CMakeLists.txt b/CMakeLists.txt index c2e5896cb..90923956a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -891,6 +891,9 @@ add_library(vllm STATIC src/vllm/model_executor/models/qwen3_vl_registry.cpp src/vllm/model_executor/models/voxtral.cpp src/vllm/multimodal/hasher.cpp + # Per-modality input-count limits and the refusal they carry + # (ENG-MM-INPUT-PIPELINE L1, #607). + src/vllm/multimodal/processing/context.cpp src/vllm/multimodal/qwen3vl_processor.cpp src/vllm/multimodal/audio_processor.cpp # Parakeet / FastConformer audio encoder + CTC head + its log-mel front end diff --git a/docs/USAGE.md b/docs/USAGE.md index 4f80a43ef..30c91c50a 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1802,6 +1802,26 @@ Accepted part types (`src/vllm/entrypoints/openai/chat_mm.cpp`): | `video_url` | video | | `input_audio` / `audio_url` | audio | +### Per-prompt input limits — the mechanism exists, the flags do not yet + +vLLM caps how many items of each modality one prompt may carry +(`--limit-mm-per-prompt`), and `--language-model-only` is sugar for setting every +one of those limits to 0, which makes the server refuse multimodal requests +outright. **Neither flag is accepted yet** — `vllm-server` still exits on both, +and there is no config key or C ABI field for them. + +What landed (#607, wave L1) is the mechanism underneath, as library-internal +headers only: `vllm::MultiModalConfig::GetLimitPerPrompt` +([`config/multimodal.h`](../include/vllm/config/multimodal.h)) resolving +upstream's precedence, and the refusal it carries +([`multimodal/processing/context.h`](../include/vllm/multimodal/processing/context.h)), +which raises `vllm::v1::InputValidationError` — the same type the API server +answers with HTTP 400. Nothing constructs that config on a live request, so +**today no request is limited or refused on item count**, and a chat request +carrying several images still has all but the first silently dropped by +`chat_mm.cpp`. Wave L2 adds the two flags, the C ABI field, and the call-site +wiring that makes the limits take effect. + ## MiniMax-H3 browser console (`vllm-video-studio`) A standalone browser console for MiniMax-H3, deliberately **separate** from the diff --git a/include/vllm/config/multimodal.h b/include/vllm/config/multimodal.h new file mode 100644 index 000000000..cc4b76979 --- /dev/null +++ b/include/vllm/config/multimodal.h @@ -0,0 +1,92 @@ +// Ported from: vllm/config/multimodal.py @ 5559679229bc +// +// Scope (ENG-MM-INPUT-PIPELINE wave L1, #607): the per-modality INPUT-COUNT +// limits and the accessor every consumer asks. This is the mechanism the +// `--limit-mm-per-prompt` and `--language-model-only` serve flags are sugar over, +// and it is deliberately ported BEFORE either flag: upstream's +// `--language-model-only` is not a "skip the encoder" boolean, it is +// "set every modality limit to 0" (:78-80), and everything else about it — +// the refusal, the tower skip, the encoder-cudagraph frame budget — falls out of +// the limits being zero. Porting the boolean first would build a bespoke path +// that does not exist upstream. See .agents/specs/multimodal-track.md §1.5. +// +// The limits are only half of it. A limit nothing enforces is not a limit, so +// the enforcement lands in the same wave, mirrored from where upstream keeps it: +// `vllm/multimodal/processing/context.h` (`AllowedMmLimits`, +// `ValidateNumItems`). +// +// Ported T0 fields: language_model_only (:78), limit_per_prompt (:81), +// enable_mm_embeds (:98). +// +// DEFERRED upstream fields, intentionally omitted — each is a separate knob with +// no bearing on the limits mechanism, and none is reachable from a flag we +// accept today: mm_processor_kwargs, mm_processor_cache_gb / _type, +// mm_shm_cache_max_object_size_mb, mm_encoder_tp_mode, mm_encoder_attn_backend / +// _attn_dtype / _fp8_scale_path, interleave_mm_strings, skip_mm_profiling, +// video_pruning_rate, media_io_kwargs, mm_tensor_ipc. The compute_hash() surface +// is omitted with them: we have no config-hash seam to feed. +// +// DEVIATIONS, recorded: +// * `limit_per_prompt` upstream is `dict[str, BaseDummyOptions]` (:81) — each +// entry carries a `count` PLUS dummy-profiling options (num_frames, width, +// height). Only `.count` participates in get_limit_per_prompt (:335), and the +// option half exists to size DUMMY inputs during memory profiling, which is a +// surface we do not have. So the map is `modality -> count` here, which is +// also upstream's own "legacy format (count only)" spelling (:87-88). When +// the profiling options land they extend the mapped type, not the accessor. +#ifndef VLLM_CONFIG_MULTIMODAL_H_ +#define VLLM_CONFIG_MULTIMODAL_H_ + +#include +#include + +namespace vllm { + +// The default limit for a modality nobody named (multimodal.py:331-333). +inline constexpr int kDefaultLimitPerPrompt = 999; + +// MultiModalConfig (L1 limits subset). Value type; the model config owns one. +struct MultiModalConfig { + // language_model_only (multimodal.py:78-80). Upstream's docstring is the + // authority on what it means, and it is not what the name suggests: "If True, + // disables all multimodal inputs by setting all modality limits to 0. + // Equivalent to setting `--limit-mm-per-prompt` to 0 for every modality." + bool language_model_only = false; + + // limit_per_prompt (multimodal.py:81-97): the maximum number of input items + // allowed per prompt, per modality. Absent modality == the 999 default, NOT 0 + // — an empty map is "no limits configured", not "nothing allowed". + std::map limit_per_prompt; + + // enable_mm_embeds (multimodal.py:98-110). Off by default. It is the ESCAPE + // from a zero limit for precomputed embeddings only: with it set, a modality at + // limit 0 accepts `*_embeds` input while still refusing raw media, which is how + // a deployment skips loading the encoder and still serves embeddings. + // "Limits greater than 0 still apply to embeddings" (:103-105). + bool enable_mm_embeds = false; + + // get_limit_per_prompt (multimodal.py:321-336). The single place every consumer + // asks, and the precedence is load-bearing in this exact order: + // + // 1. language_model_only -> 0, for EVERY modality, checked BEFORE the map is + // read (:326-327). An explicit non-zero entry does not survive the flag. + // 2. the map entry's count (:329,335). + // 3. 999 for a modality the map does not mention (:331-333). + // + // Reading the map first would be indistinguishable on every configuration + // except the one that matters: the flag set alongside explicit limits. + int GetLimitPerPrompt(const std::string& modality) const { + if (language_model_only) return 0; + + const auto it = limit_per_prompt.find(modality); + if (it == limit_per_prompt.end()) { + // Unspecified modality is set to 999 by default. + return kDefaultLimitPerPrompt; + } + return it->second; + } +}; + +} // namespace vllm + +#endif // VLLM_CONFIG_MULTIMODAL_H_ diff --git a/include/vllm/multimodal/processing/context.h b/include/vllm/multimodal/processing/context.h new file mode 100644 index 000000000..9f45e3821 --- /dev/null +++ b/include/vllm/multimodal/processing/context.h @@ -0,0 +1,112 @@ +// Ported from: vllm/multimodal/processing/context.py @ 5559679229bc +// +// Scope (ENG-MM-INPUT-PIPELINE wave L1, #607): the ENFORCEMENT half of the +// per-modality input limits — `allowed_mm_limits` (:392-405) and +// `validate_num_items` (:409-428), plus both of upstream's call sites: the +// validate block inside `parse_mm_data` (:441-461) and the chat tracker's +// per-item check (entrypoints/chat_utils.py:648-662). The limits themselves are +// in vllm/config/multimodal.h. +// +// Why this belongs in the same wave as the limits, and not later: a limit +// nothing reads is a number, not a limit. Upstream's `--language-model-only` +// zeroes every modality limit, and its main observable effect is that the server +// then REFUSES every multimodal request — "At most 0 image(s) may be provided in +// one prompt." Shipping the zeroing without the refusal would ACCEPT a request +// upstream rejects, silently, on exactly the axis a user tests first +// (.agents/specs/multimodal-track.md §1.5). +// +// Ported subset: BaseProcessingInfo's limit surface only. The rest of that class +// (get_hf_config / get_hf_processor / get_tokenizer / the dummy-input and +// max-tokens-per-item hooks) and InputProcessingContext are DEFERRED with the M1 +// processor work — they are the processor's plumbing, not the limits'. +// +// DEVIATIONS, recorded: +// * Upstream reads `self.ctx.get_mm_config()` through InputProcessingContext, +// which we do not have yet, so the MultiModalConfig is held by reference +// here. It must outlive the BaseProcessingInfo — the model config owns it. +// * `supported_mm_limits` upstream is a `@cached_property` over the model's own +// `get_supported_mm_limits()` (:387-390). We have no per-model hook to call +// yet (the models that would implement it are the M2 towers), so the map is +// passed in. `std::nullopt` mirrors upstream's `None` = unlimited; a modality +// ABSENT from the map mirrors "not supported at all" (:380-384). +// * Upstream raises VLLMValidationError with a `parameter=modality` field for +// structured error responses; we throw vllm::v1::InputValidationError, the +// type api_server.cpp already maps to HTTP 400, and carry the modality in the +// message exactly as upstream's own text does. +// * chat_utils.py:637-647 remaps image/video to a "vision_chunk" modality when +// `use_unified_vision_chunk_modality` is set. That is a per-model input +// feature we have not ported, so `ValidateTrackedChatItem` takes the modality +// it is given. When the vision-chunk models land, the remap goes in front of +// this call exactly as upstream has it. +#ifndef VLLM_MULTIMODAL_PROCESSING_CONTEXT_H_ +#define VLLM_MULTIMODAL_PROCESSING_CONTEXT_H_ + +#include +#include +#include +#include + +#include "vllm/config/multimodal.h" + +namespace vllm::multimodal { + +// One modality's worth of parsed input, as `parse_mm_data` sees it after the +// data parser has run (context.py:441-442: `for modality, items in +// mm_items.items()`). `is_embedding` mirrors upstream's +// `isinstance(items, (EmbeddingItems, DictEmbeddingItems))` test at :443. +struct ParsedMmItems { + std::string modality; + int num_items = 0; + bool is_embedding = false; +}; + +// BaseProcessingInfo's limit surface (context.py:373-461). +class BaseProcessingInfo { + public: + // `supported_mm_limits` mirrors get_supported_mm_limits() (:378-390): the + // model's OWN ceiling per modality. std::nullopt == unlimited; a modality + // absent from the map is not supported at all. + BaseProcessingInfo( + const MultiModalConfig& mm_config, + std::map> supported_mm_limits) + : mm_config_(mm_config), + supported_mm_limits_(std::move(supported_mm_limits)) {} + + const MultiModalConfig& mm_config() const { return mm_config_; } + const std::map>& supported_mm_limits() const { + return supported_mm_limits_; + } + + // allowed_mm_limits (context.py:392-405): the user's limit folded with the + // model's by min(). A user limit NEVER raises a model's ceiling — that is the + // whole point of the fold, and it is why `--limit-mm-per-prompt image=99` on a + // single-image model still refuses the second image. Keyed by the SUPPORTED + // modalities only (:396), so an unsupported modality is absent rather than 0. + std::map AllowedMmLimits() const; + + // validate_num_items (context.py:409-428). Throws vllm::v1::InputValidationError + // with upstream's exact message when `num_items` exceeds the limit. + void ValidateNumItems(const std::string& modality, int num_items) const; + + // The parse_mm_data validate block (context.py:441-461) — call site 1. Applies + // the enable_mm_embeds escape per modality, then validates the rest. + void ValidateParsedMmData(const std::vector& items) const; + + // The chat tracker's per-item check (entrypoints/chat_utils.py:630-662) — call + // site 2. `modality` is the content-part modality as written by the caller, + // e.g. "image" or "image_embeds"; the returned value is the resolved INPUT + // modality the limit was checked against, or std::nullopt for "prompt_embeds", + // which is not a multimodal modality at all (:632-633). `num_items` is the + // caller's RUNNING count including this item (:648-652), so the refusal fires + // across messages, not only within one. + std::optional ValidateTrackedChatItem( + const std::string& modality, int num_items) const; + + private: + const MultiModalConfig& mm_config_; + std::map> supported_mm_limits_; +}; + +} // namespace vllm::multimodal + +#endif // VLLM_MULTIMODAL_PROCESSING_CONTEXT_H_ diff --git a/include/vllm/v1/engine/input_processor.h b/include/vllm/v1/engine/input_processor.h index b30582040..8f86e3a06 100644 --- a/include/vllm/v1/engine/input_processor.h +++ b/include/vllm/v1/engine/input_processor.h @@ -62,6 +62,7 @@ #include "vllm/sampling_params.h" #include "vllm/transformers_utils/hf_config.h" #include "vllm/v1/engine/types.h" +#include "vllm/v1/engine/validation_error.h" // InputValidationError (was defined here) namespace vllm::tok { class Tokenizer; // vllm/tokenizer/tokenizer.h @@ -69,21 +70,10 @@ class Tokenizer; // vllm/tokenizer/tokenizer.h namespace vllm::v1 { -// A request the engine REFUSES because the caller asked for something it can -// never serve — today: a prompt longer than the resolved max_model_len -// (_validate_prompt_len, input_processor.py:387-432 @ 555967922). -// -// Upstream raises a bare ValueError here and the OpenAI server maps ValueError -// to `BadRequestError` / HTTP 400 in create_error_response -// (vllm/entrypoints/serve/utils/error_response.py:62-65). C++ has no equivalent -// of "the user-input exception class", so the mapping needs a NAMED type: the -// api_server handlers catch this ahead of their generic `std::exception` -> -// HTTP 500 arm, which is what makes the status code 400 rather than 500. -class InputValidationError : public std::invalid_argument { - public: - explicit InputValidationError(const std::string& msg) - : std::invalid_argument(msg) {} -}; +// InputValidationError — the refusal this processor throws — now lives in +// vllm/v1/engine/validation_error.h, included above, so that the multimodal +// limit check can throw the SAME type without including this header. Same name, +// same namespace: every existing thrower and catcher is unaffected. class InputProcessor { public: diff --git a/include/vllm/v1/engine/validation_error.h b/include/vllm/v1/engine/validation_error.h new file mode 100644 index 000000000..3179ba78d --- /dev/null +++ b/include/vllm/v1/engine/validation_error.h @@ -0,0 +1,50 @@ +// The engine's "the caller asked for something we can never serve" refusal. +// +// This class was defined inline in vllm/v1/engine/input_processor.h until +// ENG-MM-INPUT-PIPELINE L1 (#607) needed to throw it from +// vllm/multimodal/processing/context.h as well — that is where upstream's +// `validate_num_items` raises `VLLMValidationError` +// (vllm/multimodal/processing/context.py:409-428 @ 5559679229bc). Including the +// whole input_processor.h from the multimodal layer would drag the tokenizer, +// the HF config and SamplingParams into every multimodal TU, and would put a +// header cycle one step away the moment the input processor calls into the +// multimodal validators (upstream's context.py:461 call site). So the type moved +// here, unchanged, in the same namespace and under the same name: +// input_processor.h includes this header, api_server.cpp keeps catching +// `vllm::v1::InputValidationError` through it, and nothing else moved. +// +// There is deliberately only ONE of these. Upstream has one +// VLLMValidationError, and the OpenAI server answers it with HTTP 400 +// (src/vllm/entrypoints/openai/api_server.cpp:185,252 catch this type ahead of +// their generic std::exception -> HTTP 500 arm). A second, multimodal-only +// refusal class would compile, pass its own unit tests, and then surface a +// too-many-images request as a 500. +#ifndef VLLM_V1_ENGINE_VALIDATION_ERROR_H_ +#define VLLM_V1_ENGINE_VALIDATION_ERROR_H_ + +#include +#include + +namespace vllm::v1 { + +// A request the engine REFUSES because the caller asked for something it can +// never serve — a prompt longer than the resolved max_model_len +// (_validate_prompt_len, input_processor.py:387-432 @ 5559679229bc), or more +// multimodal items of a modality than the configured limit allows +// (validate_num_items, multimodal/processing/context.py:409-428). +// +// Upstream raises a bare ValueError / VLLMValidationError here and the OpenAI +// server maps it to `BadRequestError` / HTTP 400 in create_error_response +// (vllm/entrypoints/serve/utils/error_response.py:62-65). C++ has no equivalent +// of "the user-input exception class", so the mapping needs a NAMED type: the +// api_server handlers catch this ahead of their generic `std::exception` -> +// HTTP 500 arm, which is what makes the status code 400 rather than 500. +class InputValidationError : public std::invalid_argument { + public: + explicit InputValidationError(const std::string& msg) + : std::invalid_argument(msg) {} +}; + +} // namespace vllm::v1 + +#endif // VLLM_V1_ENGINE_VALIDATION_ERROR_H_ diff --git a/src/vllm/multimodal/processing/context.cpp b/src/vllm/multimodal/processing/context.cpp new file mode 100644 index 000000000..15269f0d9 --- /dev/null +++ b/src/vllm/multimodal/processing/context.cpp @@ -0,0 +1,107 @@ +// Ported from: vllm/multimodal/processing/context.py @ 5559679229bc +// (see include/vllm/multimodal/processing/context.h for scope and deviations). +#include "vllm/multimodal/processing/context.h" + +#include +#include + +#include "vllm/v1/engine/validation_error.h" + +namespace vllm::multimodal { +namespace { + +// The suffix upstream strips to get the input modality (chat_utils.py:635). +constexpr const char* kEmbedsSuffix = "_embeds"; + +bool EndsWithEmbeds(const std::string& modality) { + const std::string suffix(kEmbedsSuffix); + return modality.size() >= suffix.size() && + modality.compare(modality.size() - suffix.size(), suffix.size(), + suffix) == 0; +} + +} // namespace + +// context.py:392-405. +std::map BaseProcessingInfo::AllowedMmLimits() const { + std::map allowed_limits; + for (const auto& [modality, supported_limit] : supported_mm_limits_) { + const int user_limit = mm_config_.GetLimitPerPrompt(modality); + allowed_limits[modality] = supported_limit.has_value() + ? std::min(user_limit, *supported_limit) + : user_limit; + } + return allowed_limits; +} + +// context.py:409-428. +void BaseProcessingInfo::ValidateNumItems(const std::string& modality, + int num_items) const { + // A modality missing from either map defaults to 0 (:414-415) — the model does + // not support it, so nothing is allowed. + const auto supported_it = supported_mm_limits_.find(modality); + const std::map allowed = AllowedMmLimits(); + const auto allowed_it = allowed.find(modality); + const int allowed_limit = allowed_it == allowed.end() ? 0 : allowed_it->second; + + // An unlimited model limit collapses to the allowed limit (:417-418), so the + // user's limit becomes the only constraint. + int supported_limit = 0; + if (supported_it != supported_mm_limits_.end()) + supported_limit = supported_it->second.value_or(allowed_limit); + + const int limit = std::min(supported_limit, allowed_limit); + if (num_items <= limit) return; + + std::string msg = "At most " + std::to_string(limit) + " " + modality + + "(s) may be provided in one prompt."; + // The hint only when raising the user's limit would actually help: the model + // can take this many, the configuration is what refused them (:425-426). + if (num_items <= supported_limit) + msg += " Set `--limit-mm-per-prompt` to increase this limit."; + + throw v1::InputValidationError(msg); +} + +// context.py:441-461 — parse_mm_data's validate block. +void BaseProcessingInfo::ValidateParsedMmData( + const std::vector& items) const { + for (const ParsedMmItems& item : items) { + if (item.is_embedding) { + if (!mm_config_.enable_mm_embeds) { + throw v1::InputValidationError( + "You must set `--enable-mm-embeds` to input `" + item.modality + + "_embeds`"); + } + // The escape: embeddings at limit 0 skip the count check entirely, so an + // encoder-free deployment can still be fed precomputed embeddings + // (:453-459). A non-zero limit still applies to them. + if (mm_config_.GetLimitPerPrompt(item.modality) == 0) continue; + } + ValidateNumItems(item.modality, item.num_items); + } +} + +// entrypoints/chat_utils.py:630-662 — the chat tracker's per-item check. +std::optional BaseProcessingInfo::ValidateTrackedChatItem( + const std::string& modality, int num_items) const { + // Not a multimodal modality; it never reaches a limit (:632-633). + if (modality == "prompt_embeds") return std::nullopt; + + std::string input_modality = modality; + const std::string suffix(kEmbedsSuffix); + if (EndsWithEmbeds(input_modality)) + input_modality.erase(input_modality.size() - suffix.size()); + + // The same escape as parse_mm_data's, spelled as one conjunction upstream + // (:653-660): the flag, a zero limit, AND an `*_embeds` content part. All + // three, or the item is counted. + const bool escapes = mm_config_.enable_mm_embeds && + mm_config_.GetLimitPerPrompt(input_modality) == 0 && + EndsWithEmbeds(modality); + if (!escapes) ValidateNumItems(input_modality, num_items); + + return input_modality; +} + +} // namespace vllm::multimodal diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c2eddb4c0..35042be33 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -749,6 +749,13 @@ vllm_cpp_add_test(test_speculative_draft_max_position_embeddings # SPEC-DSPARK W1: the DSpark config slice (method acceptance, block-size floor, # lookahead slots, draft identity). vllm_cpp_add_test(test_speculative_dspark vllm/config/test_speculative_dspark.cpp) +# ENG-MM-INPUT-PIPELINE L1 (#607): get_limit_per_prompt precedence — +# language_model_only ⇒ 0 BEFORE the map, else the map, else the 999 default. +vllm_cpp_add_test(test_multimodal_config vllm/config/test_multimodal_config.cpp) +# ENG-MM-INPUT-PIPELINE L1 (#607): the refusal those limits carry — the min() +# fold against the model's own ceiling, upstream's exact message, and both call +# sites with the enable_mm_embeds escape. +vllm_cpp_add_test(test_processing_limits vllm/multimodal/test_processing_limits.cpp) vllm_cpp_add_test(test_request_queue vllm/v1/test_request_queue.cpp) vllm_cpp_add_test(test_sched_output vllm/v1/test_sched_output.cpp) vllm_cpp_add_test(test_scheduler vllm/v1/test_scheduler.cpp) diff --git a/tests/vllm/config/test_multimodal_config.cpp b/tests/vllm/config/test_multimodal_config.cpp new file mode 100644 index 000000000..60efbf489 --- /dev/null +++ b/tests/vllm/config/test_multimodal_config.cpp @@ -0,0 +1,120 @@ +// ENG-MM-INPUT-PIPELINE wave L1 (#607) — the per-modality input-limit accessor. +// +// Ported from (test-porting protocol .agents/porting.md), all @ 5559679229bc: +// * vllm/config/multimodal.py:78 — `language_model_only: bool = False`, whose +// docstring is the authority for what the flag MEANS: "disables all +// multimodal inputs by setting all modality limits to 0. Equivalent to +// setting `--limit-mm-per-prompt` to 0 for every modality." +// * vllm/config/multimodal.py:81 — `limit_per_prompt`, default empty. +// * vllm/config/multimodal.py:98 — `enable_mm_embeds: bool = False`. +// * vllm/config/multimodal.py:321-336 — `get_limit_per_prompt(modality)`, the +// precedence under test: `language_model_only` returns 0 FIRST (:326-327), +// BEFORE the map is consulted; then the map entry's count (:335); then the +// 999 default for a modality the map does not mention (:331-333). +// +// Upstream's own `tests/config/test_multimodal_config.py` @ 5559679229bc covers +// the hash/backend/mm-prefix behaviour of MultiModalConfig but has NO case for +// the get_limit_per_prompt precedence — it is exercised only indirectly, through +// e2e suites that need a GPU and a llava checkpoint +// (tests/entrypoints/multimodal/llm/test_mm_embeds_only.py, +// tests/multimodal/test_processing.py::test_limit_mm_per_prompt_apply, both of +// which this port covers in tests/vllm/multimodal/test_processing_limits.cpp). +// These cases are therefore AUTHORED here, not ported, exactly as +// tests/vllm/config/test_speculative_dspark.cpp records for the DSpark +// resolution: they pin the pure-config behaviour those e2e suites depend on. +// SKIPPED from upstream's file, with reasons: the compute_hash cases +// (:17-48,64-75) — we have no config-hash surface; the mm_encoder_attn_backend / +// media_io_kwargs / fp8-scale cases (:17-31,51-62) — those fields are NOT in the +// L1 scope (§1.5 ports the limits mechanism, not the encoder-attention knobs); +// the _supports_multimodal_for_mm_prefix cases (:78-166) — they exercise +// ModelConfig + the multimodal REGISTRY, which L1 does not touch. +// +// RED-first note: before wave L1 there is no `vllm/config/multimodal.h` at all, +// so this file does not compile — `MultiModalConfig` is undeclared. Every case +// below is red for that reason. Once the header exists, the precedence case +// "language_model_only zeroes a modality the map raises" is the one that reds if +// the map is consulted before the flag (the mutation run in the L1 report). +#include + +#include + +#include "vllm/config/multimodal.h" + +using vllm::MultiModalConfig; + +TEST_CASE("MultiModalConfig defaults mirror upstream") { + const MultiModalConfig config; + // multimodal.py:78, :81, :98. + CHECK(config.language_model_only == false); + CHECK(config.limit_per_prompt.empty()); + CHECK(config.enable_mm_embeds == false); +} + +TEST_CASE("an unspecified modality defaults to 999") { + // multimodal.py:331-333 — "Unspecified modality is set to 999 by default". + const MultiModalConfig config; + CHECK(config.GetLimitPerPrompt("image") == 999); + CHECK(config.GetLimitPerPrompt("video") == 999); + CHECK(config.GetLimitPerPrompt("audio") == 999); + // A modality nobody has heard of is still 999: the map miss is what decides, + // not a fixed list of known modalities. + CHECK(config.GetLimitPerPrompt("hologram") == 999); +} + +TEST_CASE("an explicit map entry is honoured") { + // multimodal.py:334-335 — `return limit_data.count`. + MultiModalConfig config; + config.limit_per_prompt["image"] = 16; + config.limit_per_prompt["video"] = 2; + CHECK(config.GetLimitPerPrompt("image") == 16); + CHECK(config.GetLimitPerPrompt("video") == 2); + // Unmentioned modalities keep the default — an entry for one modality never + // implies a limit for another. + CHECK(config.GetLimitPerPrompt("audio") == 999); +} + +TEST_CASE("an explicit zero is a real limit without the flag") { + MultiModalConfig config; + config.limit_per_prompt["image"] = 0; + CHECK(config.GetLimitPerPrompt("image") == 0); + CHECK(config.GetLimitPerPrompt("video") == 999); +} + +TEST_CASE("language_model_only zeroes EVERY modality, mapped or not") { + // multimodal.py:326-327 — the docstring's "setting all modality limits to 0" + // is literally what the accessor does, and it applies to modalities the map + // never mentions. + MultiModalConfig config; + config.language_model_only = true; + CHECK(config.GetLimitPerPrompt("image") == 0); + CHECK(config.GetLimitPerPrompt("video") == 0); + CHECK(config.GetLimitPerPrompt("audio") == 0); + CHECK(config.GetLimitPerPrompt("hologram") == 0); +} + +TEST_CASE("language_model_only zeroes a modality the map raises") { + // THE PRECEDENCE CASE. multimodal.py:326-327 returns 0 BEFORE :329 reads the + // map, so an explicit non-zero entry does NOT survive the flag. Consulting the + // map first would return 16 here and still pass every other case in this file. + MultiModalConfig config; + config.language_model_only = true; + config.limit_per_prompt["image"] = 16; + config.limit_per_prompt["video"] = 2; + CHECK(config.GetLimitPerPrompt("image") == 0); + CHECK(config.GetLimitPerPrompt("video") == 0); +} + +TEST_CASE("language_model_only is equivalent to zeroing every modality") { + // The docstring's own equivalence (multimodal.py:78-80): the flag and an + // all-zero map must be indistinguishable through the accessor. + MultiModalConfig by_flag; + by_flag.language_model_only = true; + + MultiModalConfig by_map; + for (const char* modality : {"image", "video", "audio"}) + by_map.limit_per_prompt[modality] = 0; + + for (const char* modality : {"image", "video", "audio"}) + CHECK(by_flag.GetLimitPerPrompt(modality) == + by_map.GetLimitPerPrompt(modality)); +} diff --git a/tests/vllm/multimodal/test_processing_limits.cpp b/tests/vllm/multimodal/test_processing_limits.cpp new file mode 100644 index 000000000..177d2c459 --- /dev/null +++ b/tests/vllm/multimodal/test_processing_limits.cpp @@ -0,0 +1,398 @@ +// ENG-MM-INPUT-PIPELINE wave L1 (#607) — the REFUSAL that makes a limit a limit. +// +// A limit nothing enforces is not a limit: `--language-model-only` zeroes every +// modality limit, and its main user-visible effect upstream is that the server +// then answers an image request with "At most 0 image(s) may be provided in one +// prompt." (multimodal-track.md §1.5). These cases pin that refusal. +// +// Ported from (test-porting protocol .agents/porting.md), all @ 5559679229bc: +// * tests/multimodal/test_processing.py:902-941 +// `test_limit_mm_per_prompt_apply` — the 7 (num_images, limit, is_valid) +// triples, refusal matched on "At most". Its model is +// llava-hf/llava-v1.6-mistral-7b-hf, whose get_supported_mm_limits is +// {"image": None} (vllm/model_executor/models/llava.py:183-184), i.e. +// UNLIMITED — mirrored here as a nullopt supported limit. Harness +// adaptation, unavoidable: upstream drives the check through +// `processor(...)` on a real checkpoint; we call the ported +// `ValidateNumItems` directly, which is the function upstream's call reaches +// at processing/context.py:461. No checkpoint, no image decode. +// * tests/multimodal/test_processing.py:944-985 +// `test_budget_caps_prevent_dummy_input_validation_failure` — all 10 +// (user_limit, supported_limit) pairs, and all three of its assertions +// (allowed <= supported, allowed <= user, allowed == min(user, supported)). +// * tests/entrypoints/multimodal/llm/test_mm_embeds_only.py:41-49 +// `test_raw_image_rejected` — the EXACT message at limit 0, which upstream +// matches as the regex r"At most 0 image\(s\)". Ported as an exact-string +// check; upstream's other two cases in that file (:29-38 embeddings still +// generate, :52-60 text-only still works) need a GPU + llava and are SKIPPED, +// with the escape they depend on covered below as a unit case instead. +// * tests/entrypoints/unit_tests/test_chat_utils.py:1498-1530,1534-1560 +// `test_parse_chat_messages_rejects_too_many_images_{in_one_message, +// across_messages}` — three images against phi3v's limit, matched on +// "At most". Harness adaptation: upstream drives `parse_chat_messages`; we +// call the ported chat-path validator, which is what +// entrypoints/chat_utils.py:662 reaches. The "across messages" half is the +// same assertion with the count accumulated by the caller's tracker, so it +// is ported as the running-count case rather than a second message shape. +// +// The behaviour under test, mirrored 1:1: +// * vllm/multimodal/processing/context.py:392-405 `allowed_mm_limits` — folds +// the USER limit with the model's own supported limit by min(); a user limit +// NEVER raises a model's ceiling. +// * vllm/multimodal/processing/context.py:409-428 `validate_num_items` — the +// message at :423 and the conditional hint at :425-426. +// * vllm/multimodal/processing/context.py:441-461 — parse_mm_data's validate +// block, including the `enable_mm_embeds` escape at :443-460. +// * vllm/entrypoints/chat_utils.py:648-662 — the per-tracked-item call site +// and its own spelling of the same escape at :653-660. +// +// AUTHORED, not ported (upstream has no unit case for either): the exact text of +// the " Set `--limit-mm-per-prompt` to increase this limit." hint and the +// conditions under which it does and does not appear (context.py:425-426), and +// the "You must set `--enable-mm-embeds`" refusal text (context.py:445-448). +// Both are message contracts a user reads, so they are pinned rather than left +// to drift. +// +// RED-first note: before wave L1 there is no +// `vllm/multimodal/processing/context.h`, so this file does not compile — +// `BaseProcessingInfo` is undeclared, and every case is red for that reason. +// Once it exists, "a request at the limit passes and one over it is refused" is +// the case that reds if the throw is removed, and "the user limit never raises +// the model ceiling" is the case that reds if the min() fold is dropped (both +// mutations are run in the L1 report). +#include + +#include +#include +#include +#include + +#include "vllm/config/multimodal.h" +#include "vllm/multimodal/processing/context.h" +#include "vllm/v1/engine/validation_error.h" + +using vllm::MultiModalConfig; +using vllm::multimodal::BaseProcessingInfo; +using vllm::multimodal::ParsedMmItems; +using vllm::v1::InputValidationError; + +namespace { + +// The llava-v1.6-mistral-7b-hf stand-in upstream's test_limit_mm_per_prompt_apply +// uses: get_supported_mm_limits() == {"image": None} (llava.py:183-184). +std::map> UnlimitedImages() { + return {{"image", std::nullopt}}; +} + +// Grab the message off the refusal so its exact text can be asserted. +std::string RefusalMessage(const BaseProcessingInfo& info, + const std::string& modality, int num_items) { + try { + info.ValidateNumItems(modality, num_items); + } catch (const InputValidationError& e) { + return e.what(); + } + return ""; +} + +} // namespace + +// --------------------------------------------------------------------------- +// test_processing.py:902-941 — test_limit_mm_per_prompt_apply +// --------------------------------------------------------------------------- + +TEST_CASE("a request at the limit passes and one over it is refused") { + // The upstream parametrization, verbatim: (num_images, limit, is_valid). + struct Case { + int num_images; + int limit; + bool is_valid; + }; + const Case cases[] = { + {0, 0, true}, {0, 1, true}, {1, 0, false}, {1, 1, true}, + {1, 2, true}, {2, 1, false}, {2, 2, true}, + }; + + for (const Case& c : cases) { + CAPTURE(c.num_images); + CAPTURE(c.limit); + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = c.limit; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + + if (c.is_valid) + CHECK_NOTHROW(info.ValidateNumItems("image", c.num_images)); + else + CHECK_THROWS_AS(info.ValidateNumItems("image", c.num_images), + InputValidationError); + } +} + +// --------------------------------------------------------------------------- +// test_processing.py:944-985 — test_budget_caps_prevent_dummy_input_validation_failure +// --------------------------------------------------------------------------- + +TEST_CASE("the user limit never raises the model ceiling") { + // The upstream parametrization, verbatim: (user_limit, supported_limit). + struct Case { + int user_limit; + int supported_limit; + }; + const Case cases[] = { + {0, 0}, {0, 1}, {1, 0}, {1, 1}, {1, 2}, + {2, 1}, {2, 2}, {5, 1}, {1, 5}, {10, 0}, + }; + + for (const Case& c : cases) { + CAPTURE(c.user_limit); + CAPTURE(c.supported_limit); + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = c.user_limit; + const BaseProcessingInfo info(mm_config, {{"image", c.supported_limit}}); + + const std::map allowed = info.AllowedMmLimits(); + REQUIRE(allowed.count("image") == 1); + // All three upstream assertions. + CHECK(allowed.at("image") <= c.supported_limit); + CHECK(allowed.at("image") <= c.user_limit); + CHECK(allowed.at("image") == + (c.user_limit < c.supported_limit ? c.user_limit : c.supported_limit)); + } +} + +TEST_CASE("an unlimited model limit leaves the user limit untouched") { + // context.py:401-404 — supported_limit None means the user limit stands as-is + // rather than being folded against anything. + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = 7; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + CHECK(info.AllowedMmLimits().at("image") == 7); +} + +TEST_CASE("allowed limits cover exactly the modalities the model supports") { + // context.py:396-397 iterates supported_mm_limits, so a modality the model + // does not support is ABSENT from the result — not present with a 0. + MultiModalConfig mm_config; + mm_config.limit_per_prompt["audio"] = 4; + const BaseProcessingInfo info( + mm_config, {{"image", std::nullopt}, {"video", 2}}); + const std::map allowed = info.AllowedMmLimits(); + CHECK(allowed.size() == 2); + CHECK(allowed.count("audio") == 0); + CHECK(allowed.at("image") == 999); // unspecified -> the 999 default + CHECK(allowed.at("video") == 2); +} + +// --------------------------------------------------------------------------- +// The message contract (context.py:423,425-426) — and test_mm_embeds_only.py:45 +// --------------------------------------------------------------------------- + +TEST_CASE("language_model_only refuses with upstream's exact message") { + // test_mm_embeds_only.py:45 matches r"At most 0 image\(s\)"; this is the whole + // string. No hint: the model supports images without limit, so + // supported_limit collapses to the allowed limit (context.py:417-418) and + // num_items <= supported_limit can never hold at :425. + MultiModalConfig mm_config; + mm_config.language_model_only = true; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + CHECK(RefusalMessage(info, "image", 1) == + "At most 0 image(s) may be provided in one prompt."); +} + +TEST_CASE("the hint appears only when raising the user limit would help") { + // context.py:425-426. The model supports 5 images; the user allowed 1. + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = 1; + const BaseProcessingInfo info(mm_config, {{"image", 5}}); + CHECK(RefusalMessage(info, "image", 2) == + "At most 1 image(s) may be provided in one prompt." + " Set `--limit-mm-per-prompt` to increase this limit."); +} + +TEST_CASE("no hint when the MODEL is the binding constraint") { + // The user asked for 5 but the model supports 1: raising the flag would not + // help, so upstream withholds the hint (num_items > supported_limit at :425). + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = 5; + const BaseProcessingInfo info(mm_config, {{"image", 1}}); + CHECK(RefusalMessage(info, "image", 2) == + "At most 1 image(s) may be provided in one prompt."); +} + +TEST_CASE("an unsupported modality is refused at 0 with no hint") { + // context.py:414-415 — both lookups miss, so both default to 0. The model + // cannot take audio at all, so the flag would not help either. + MultiModalConfig mm_config; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + CHECK(RefusalMessage(info, "audio", 1) == + "At most 0 audio(s) may be provided in one prompt."); +} + +TEST_CASE("the modality name is echoed into the message") { + MultiModalConfig mm_config; + mm_config.language_model_only = true; + const BaseProcessingInfo info( + mm_config, {{"video", std::nullopt}, {"audio", std::nullopt}}); + CHECK(RefusalMessage(info, "video", 3) == + "At most 0 video(s) may be provided in one prompt."); + CHECK(RefusalMessage(info, "audio", 3) == + "At most 0 audio(s) may be provided in one prompt."); +} + +// --------------------------------------------------------------------------- +// Call site 1 — parse_mm_data's validate block (context.py:441-461) +// --------------------------------------------------------------------------- + +TEST_CASE("parse_mm_data validates every parsed modality") { + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = 1; + const BaseProcessingInfo info( + mm_config, {{"image", std::nullopt}, {"video", std::nullopt}}); + + CHECK_NOTHROW(info.ValidateParsedMmData( + {ParsedMmItems{"image", 1, false}, ParsedMmItems{"video", 2, false}})); + CHECK_THROWS_AS(info.ValidateParsedMmData({ParsedMmItems{"image", 1, false}, + ParsedMmItems{"video", 2, false}, + ParsedMmItems{"audio", 1, false}}), + InputValidationError); +} + +TEST_CASE("embeddings need --enable-mm-embeds") { + // context.py:443-448. + MultiModalConfig mm_config; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + try { + info.ValidateParsedMmData({ParsedMmItems{"image", 1, true}}); + FAIL("expected a refusal"); + } catch (const InputValidationError& e) { + CHECK(std::string(e.what()) == + "You must set `--enable-mm-embeds` to input `image_embeds`"); + } +} + +TEST_CASE("embeddings at limit 0 skip count validation") { + // context.py:449-460 — THE escape. This is what makes an embeddings-only + // deployment possible: the encoder never loads, the raw-image path is refused, + // and precomputed embeddings still get through. + MultiModalConfig mm_config; + mm_config.enable_mm_embeds = true; + mm_config.limit_per_prompt["image"] = 0; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + + CHECK_NOTHROW(info.ValidateParsedMmData({ParsedMmItems{"image", 4, true}})); + // The raw-image arm of the very same config is still refused + // (test_mm_embeds_only.py:41-49). + CHECK_THROWS_AS(info.ValidateParsedMmData({ParsedMmItems{"image", 1, false}}), + InputValidationError); +} + +TEST_CASE("embeddings above a NON-zero limit are still counted") { + // context.py:453 gates the skip on limit == 0. multimodal.py:103-105: + // "Limits greater than 0 still apply to embeddings." + MultiModalConfig mm_config; + mm_config.enable_mm_embeds = true; + mm_config.limit_per_prompt["image"] = 1; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + + CHECK_NOTHROW(info.ValidateParsedMmData({ParsedMmItems{"image", 1, true}})); + CHECK_THROWS_AS(info.ValidateParsedMmData({ParsedMmItems{"image", 2, true}}), + InputValidationError); +} + +// --------------------------------------------------------------------------- +// Call site 2 — the chat tracker (chat_utils.py:648-662) +// --------------------------------------------------------------------------- + +TEST_CASE("the chat path refuses too many images") { + // test_chat_utils.py:1498-1530 — three image_url parts against a limit of 2. + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = 2; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + + CHECK(info.ValidateTrackedChatItem("image", 1) == "image"); + CHECK(info.ValidateTrackedChatItem("image", 2) == "image"); + CHECK_THROWS_AS(info.ValidateTrackedChatItem("image", 3), + InputValidationError); +} + +TEST_CASE("the chat path counts ACROSS messages, not within one") { + // test_chat_utils.py:1534-1560 — the running count is the caller's; the + // refusal is the same. Two messages of two images each trips a limit of 3 on + // the third item. + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = 3; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + CHECK_NOTHROW(info.ValidateTrackedChatItem("image", 3)); + CHECK_THROWS_AS(info.ValidateTrackedChatItem("image", 4), + InputValidationError); +} + +TEST_CASE("the chat path strips _embeds before consulting the limit") { + // chat_utils.py:635 — `input_modality = modality.replace("_embeds", "")`, so + // the image limit is what an `image_embeds` part is checked against, and it is + // the STRIPPED name that reaches the message. + MultiModalConfig mm_config; + mm_config.limit_per_prompt["image"] = 1; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + + CHECK(info.ValidateTrackedChatItem("image_embeds", 1) == "image"); + try { + info.ValidateTrackedChatItem("image_embeds", 2); + FAIL("expected a refusal"); + } catch (const InputValidationError& e) { + CHECK(std::string(e.what()) == + "At most 1 image(s) may be provided in one prompt."); + } +} + +TEST_CASE("the chat path escape needs the flag, the zero AND the _embeds name") { + // chat_utils.py:653-660 — all three conjuncts. Dropping any one of them must + // put the item back under the limit. + MultiModalConfig escaping; + escaping.enable_mm_embeds = true; + escaping.limit_per_prompt["image"] = 0; + CHECK_NOTHROW(BaseProcessingInfo(escaping, UnlimitedImages()) + .ValidateTrackedChatItem("image_embeds", 9)); + + // ... without enable_mm_embeds. + MultiModalConfig no_flag = escaping; + no_flag.enable_mm_embeds = false; + CHECK_THROWS_AS(BaseProcessingInfo(no_flag, UnlimitedImages()) + .ValidateTrackedChatItem("image_embeds", 9), + InputValidationError); + + // ... at a non-zero limit. + MultiModalConfig nonzero = escaping; + nonzero.limit_per_prompt["image"] = 1; + CHECK_THROWS_AS(BaseProcessingInfo(nonzero, UnlimitedImages()) + .ValidateTrackedChatItem("image_embeds", 9), + InputValidationError); + + // ... for a raw image rather than an `_embeds` part. + CHECK_THROWS_AS(BaseProcessingInfo(escaping, UnlimitedImages()) + .ValidateTrackedChatItem("image", 1), + InputValidationError); +} + +TEST_CASE("prompt_embeds is not a modality") { + // chat_utils.py:632-633 — it returns before any limit is consulted, so it is + // never refused however many there are. + MultiModalConfig mm_config; + mm_config.language_model_only = true; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + CHECK(info.ValidateTrackedChatItem("prompt_embeds", 99) == std::nullopt); +} + +TEST_CASE("language_model_only refuses the chat path too") { + // The flag's whole user-visible point, at the OpenAI call site. + MultiModalConfig mm_config; + mm_config.language_model_only = true; + const BaseProcessingInfo info(mm_config, UnlimitedImages()); + try { + info.ValidateTrackedChatItem("image", 1); + FAIL("expected a refusal"); + } catch (const InputValidationError& e) { + CHECK(std::string(e.what()) == + "At most 0 image(s) may be provided in one prompt."); + } +} From 15e031173b5875b3dc2b74f139351c0f896a56b1 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:34:45 +0200 Subject: [PATCH 22/55] spec(MODEL-MUSIC-MUSIC3): Music3 is a SpeechEngine family, not a new engine (#672) (#700) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `multimodal::SpeechEngine` did not exist when this spec was written. The IndexTTS-2.5 lane landed it on 2026-08-13, along with `Vocoder1D`, while W0 was in review. AGENTS.md is explicit that a capability not reachable through the shared surface is not done, and that a seam is EXTENDED rather than forked -- so the scope changes before an implementer builds the wrong thing, not after. IT FITS BETTER THAN IT LOOKS. `SpeechResult` already carries `channels`, and already documents `sample_rate` as "the family's native rate ... rather than a resampled one, so the caller decides whether to resample". That is exactly §1.1's finding arrived at independently: 44100 stereo is the model's rate, and SGLang-Omni's 32 kHz is a caller-side transform. `requires_reference_audio()` exists so a server can refuse before staging weights; Music3 returns false where IndexTTS-2 returns true, which is the difference between a voice cloner and a text-to-music model expressed in the seam rather than around it. THE ONE GENUINE GAP IS `SpeechGenParams`, which carries a single `text` field because IndexTTS-2 synthesises one utterance. Music3 takes TWO distinct inputs -- lyrics with section tags, and a structured music description -- plus generation controls (duration/frames, denoise steps, CFG). Packing both into `text` behind a separator would be a private protocol inside a shared struct, which is precisely the fork the rule exists to prevent. W6 extends the struct ADDITIVELY and leaves IndexTTS-2.5 byte-identical: a field an existing family ignores costs it nothing, while a second parallel params struct costs every future family a choice. If the extension cannot be made additive, that is a NEEDS_DECISION, not a fork. ALSO RECORDED: `SpeechEngine` is not on the ABI yet. include/vllm.h is at v18 and exposes the video engine but no `vllm_speech_*` surface, and no open PR adds one (checked the 15 open PRs). So W6 owns that surface, its version bump, and the example HTTP server as a thin client of it -- and the W5 row now names `Vocoder1D` as the thing to route through rather than leaving "the vocoder" to be invented. W5 and W6 both gain the same obligation as a done-when: H3 and IndexTTS-2.5 behaviour stays byte-identical. Touching a seam another lane owns is allowed; moving its numerics silently is not. Issue: #672 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] Co-authored-by: Ettore Di Giacinto --- .agents/specs/minimax-music3.md | 37 +++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/.agents/specs/minimax-music3.md b/.agents/specs/minimax-music3.md index 63bff1170..c9c4dccfa 100644 --- a/.agents/specs/minimax-music3.md +++ b/.agents/specs/minimax-music3.md @@ -203,6 +203,39 @@ and this row binds to it directly. | audio VAE decode + WAV writing | H3 audio VAE, LTX-2 audio VAE, `minimax_h3_wav.cpp` | `src/vllm/model_executor/models/` | | diffusion request planning / pipeline shape | H3 planner + pipeline, LTX-2.5 pipeline | `minimax_h3_planner.cpp`, `ltx2_pipeline.cpp` | | quantized arms on a diffusion model | H3 GGUF + NVFP4 arms | [minimax-h3.md](minimax-h3.md) §0 | +| **an audio-GENERATION engine seam** | `multimodal::SpeechEngine` + `SpeechRegistry`, landed 2026-08-13 by the IndexTTS-2.5 lane | `include/vllm/multimodal/speech_engine.h` | +| **a 1D vocoder** | `Vocoder1D`, same lane | `src/vllm/model_executor/models/vocoder1d.cpp` | + +### 4.1 Music3 routes through `SpeechEngine`, which needs ONE additive extension + +`multimodal::SpeechEngine` did not exist when this spec was first written. It does +now, and AGENTS.md is explicit that a capability not reachable through the shared +surface is not done, and that a seam is extended rather than forked. Music3 is a +speech-family registration, not a new engine. + +It fits better than it might look. `SpeechResult` already carries `channels` and +already documents `sample_rate` as "the family's native rate ... rather than a +resampled one, so the caller decides whether to resample" — which is exactly +§1.1's 44100 stereo, and exactly why SGLang-Omni's 32 kHz stays a caller-side +concern. `requires_reference_audio()` exists so a server can refuse before +staging; Music3 returns `false`, where IndexTTS-2 returns `true`. + +**The one genuine gap is `SpeechGenParams`.** It carries a single `text` field, +because IndexTTS-2 synthesises one utterance. Music3 takes **two** distinct +inputs — lyrics (with `[Verse]` / `[Chorus]` section tags) and a structured music +description — plus generation controls (duration or frame count, denoise steps, +CFG). Squeezing both into `text` with a separator would be a private protocol +inside a shared struct, which is the fork this rule exists to prevent. + +W6 therefore **extends `SpeechGenParams` additively** and leaves IndexTTS-2.5's +behaviour byte-identical. A field an existing family ignores costs it nothing; a +second parallel params struct costs every future family a choice. If the +extension cannot be made additive, that is a `NEEDS_DECISION`, not a fork. + +**`SpeechEngine` is not yet on the ABI.** `include/vllm.h` (v18) exposes the +video engine but no `vllm_speech_*` surface, and no open PR adds one. W6 owns +that: the ABI surface, the version bump, and the example HTTP server as a thin +client of it — never including internal headers. Genuinely new: the eight-codebook RVQ frame path, the depth decoder, the learned 8-layer condition mix, snake-activated DAC decoding with weight-norm, and the @@ -247,8 +280,8 @@ the operator. Phases are separately claimable except where noted. | **W2** | Global LLM on our landed Qwen3 path at vocab 200 000 | hidden-state parity vs `transformers`, then token-exact RVQ code parity vs the oracle | | **W3** | Condition mix (8-layer weighted) + RVQ depth decoder, 8 codebooks | per-stage tensor parity; the depth decoder's 16-position window exercised at its boundary | | **W4** | Flow-matching DiT + `FlowMatchEulerDiscreteScheduler` with `invert_sigmas` | per-step latent parity against the oracle at a fixed seed | -| **W5** | Vocoder: snake activations, weight-norm, `[8,8,4,2]` upsampling, WAV at **44100 stereo** (§1.1) | waveform parity within a stated absolute tolerance | -| **W6** | e2e through the shared seams; **`include/vllm.h` surface**; **the example HTTP server as a thin ABI client**, never including internal headers | a song generates end to end from an HTTP request; SGLang-Omni cross-check; speed axis recorded with values and ratios | +| **W5** | Vocoder **through the shared `Vocoder1D`** (§4.1): snake activations, weight-norm, `[8,8,4,2]` upsampling, the 128→2×64 stereo fold, WAV at **44100 stereo** (§1.1) | waveform parity within a stated absolute tolerance, and H3/IndexTTS-2.5 behaviour byte-identical | +| **W6** | Register as a `SpeechRegistry` family; extend `SpeechGenParams` ADDITIVELY for lyrics + description + controls (§4.1); NEW `vllm_speech_*` **`include/vllm.h`** surface with the ABI version bump; **the example HTTP server as a thin ABI client** | a song generates end to end from an HTTP request; IndexTTS-2.5 unchanged; SGLang-Omni cross-check; speed axis recorded with values and ratios | | **W7** | Quantized arms — GGUF k-quants are a standing requirement, not a per-model choice | each arm gated, or refused by name and recorded as owed | **W0 blocks everything.** Until the oracle demonstrably runs, no phase can produce From 487263b8c32102686bd8ab060c2bee3cd2f6fd91 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:36:54 +0200 Subject: [PATCH 23/55] feat(MODEL-MM-indextts2): w2v-bert relative-key self-attention (#634) (#709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Ports Wav2Vec2BertSelfAttention with position_embeddings_type="relative_key", gated against `transformers` executed directly. 5 cases / 184 assertions, within 1e-5. THREE THINGS THAT FAIL QUIETLY, each mutated: THE CLAMP IS ASYMMETRIC (64 left, 8 right). A symmetric clamp still produces well-formed attention and diverges only for keys further ahead than right_max. The goldens use T=12 precisely so the RIGHT clamp bites; at T<=9 the mutation would pass. THE DISTANCE IS KEY MINUS QUERY. The reverse is a mirrored bias that still attends and still trains. THE RELATIVE TERM IS SCALED SEPARATELY by 1/sqrt(d), after the scores already were. Also pinned: the encoder is NOT causal — a case perturbs the last frame and requires the FIRST output to move, the opposite of the conv module's guarantee gated in the same suite. MUTATION EVIDENCE with compile status: sign flip and missing scale RED at compile_err=0. The clamp mutation first read compile_err=1 (right_max unused under -Werror) and printed a void SUCCESS — the fifth occurrence this campaign. Generator fix: self_attn is called with KEYWORD args so a positional forward_pre_hook sees an empty tuple; the preceding layer_norm output is tapped. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- include/vllm/model_executor/models/w2vbert.h | 24 +++++++ scripts/gen-w2vbert-goldens.py | 10 +++ src/vllm/model_executor/models/w2vbert.cpp | 58 ++++++++++++++++ tests/vllm/models/test_w2vbert.cpp | 44 ++++++++++++ tests/vllm/models/w2vbert_goldens.inc | 73 ++++++++++++++++++++ 5 files changed, 209 insertions(+) diff --git a/include/vllm/model_executor/models/w2vbert.h b/include/vllm/model_executor/models/w2vbert.h index 21701d3aa..6cb77c30d 100644 --- a/include/vllm/model_executor/models/w2vbert.h +++ b/include/vllm/model_executor/models/w2vbert.h @@ -54,6 +54,30 @@ struct ConvModuleWeights { std::vector ConvModule(const std::vector& x, int64_t frames, int64_t hidden, int64_t kernel, const ConvModuleWeights& weights, double eps); + +struct SelfAttentionWeights { + std::vector q_w, q_b, k_w, k_b, v_w, v_b; // [H, H], [H] + std::vector out_w, out_b; + std::vector distance_embedding; // [left+right+1, head_size] +}; + +// Wav2Vec2BertSelfAttention with position_embeddings_type == "relative_key". +// +// scores = q k^T / sqrt(d) +// distance = clamp(pos_key - pos_query, -left_max, +right_max) +// scores += einsum("hld,lrd->hlr", q, embedding[distance + left_max]) / sqrt(d) +// +// THE CLAMP IS ASYMMETRIC (64 left, 8 right by default): a symmetric clamp still +// produces well-formed attention, and only diverges for key positions further +// ahead than right_max, which short fixtures never reach. The goldens use T=12 so +// the RIGHT clamp genuinely bites. +// +// Note the relative term is divided by sqrt(d) SEPARATELY, after the scores +// already were -- not folded into one division. +std::vector SelfAttentionRelativeKey(const std::vector& x, int64_t frames, + int64_t hidden, int64_t heads, int64_t left_max, + int64_t right_max, const SelfAttentionWeights& weights); + } // namespace w2vbert } // namespace models } // namespace vllm diff --git a/scripts/gen-w2vbert-goldens.py b/scripts/gen-w2vbert-goldens.py index 9ebcf0adc..46a09112d 100644 --- a/scripts/gen-w2vbert-goldens.py +++ b/scripts/gen-w2vbert-goldens.py @@ -67,6 +67,12 @@ def main() -> int: tap = {} layer.ffn1.register_forward_hook(lambda m, i, o: tap.__setitem__("ffn1", o.detach().clone())) layer.conv_module.register_forward_hook(lambda m, i, o: tap.__setitem__("conv", o.detach().clone())) + layer.self_attn.register_forward_hook(lambda m, i, o: tap.__setitem__("attn", o[0].detach().clone())) + attn_in = {} + # self_attn is called with KEYWORD args, so a positional pre-hook sees an + # empty tuple; the preceding layer_norm's OUTPUT is the same tensor. + layer.self_attn_layer_norm.register_forward_hook( + lambda m, i, o: attn_in.__setitem__("x", o.detach().clone())) with torch.no_grad(): out, _ = layer(x) # the conv module's INPUT is the post-attention state; capture it so the @@ -104,6 +110,10 @@ def emit(f, name, t): d = list(sh) + [1, 1, 1] f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}, {d[2]}}},\n') f.write("};\n\n") + f.write(f"inline constexpr int64_t kLeftMax = {cfg.left_max_position_embeddings};\n") + f.write(f"inline constexpr int64_t kRightMax = {cfg.right_max_position_embeddings};\n\n") + emit(f, "kAttnIn", attn_in["x"]) + emit(f, "kAttnOut", tap["attn"]) emit(f, "kFfn1Out", tap["ffn1"]) emit(f, "kConvIn", conv_in["x"]) emit(f, "kConvOut", tap["conv"]) diff --git a/src/vllm/model_executor/models/w2vbert.cpp b/src/vllm/model_executor/models/w2vbert.cpp index 5d6c9fae0..6f7ea0d9b 100644 --- a/src/vllm/model_executor/models/w2vbert.cpp +++ b/src/vllm/model_executor/models/w2vbert.cpp @@ -147,6 +147,64 @@ std::vector ConvModule(const std::vector& x, int64_t frames, int64 return out; } + +std::vector SelfAttentionRelativeKey(const std::vector& x, int64_t frames, + int64_t hidden, int64_t heads, int64_t left_max, + int64_t right_max, const SelfAttentionWeights& w) { + VT_CHECK(hidden % heads == 0, "w2vbert: hidden must divide by heads"); + const int64_t head_dim = hidden / heads; + const double scale = 1.0 / std::sqrt(static_cast(head_dim)); + + const std::vector q = Linear(x, frames, hidden, hidden, w.q_w, w.q_b); + const std::vector k = Linear(x, frames, hidden, hidden, w.k_w, w.k_b); + const std::vector v = Linear(x, frames, hidden, hidden, w.v_w, w.v_b); + + std::vector ctx(static_cast(frames * hidden)); + for (int64_t h = 0; h < heads; ++h) { + for (int64_t i = 0; i < frames; ++i) { + std::vector scores(static_cast(frames)); + for (int64_t j = 0; j < frames; ++j) { + double dot = 0.0; + for (int64_t d = 0; d < head_dim; ++d) { + dot += static_cast(q[static_cast(i * hidden + h * head_dim + d)]) * + static_cast(k[static_cast(j * hidden + h * head_dim + d)]); + } + dot *= scale; + + // distance is key MINUS query, clamped asymmetrically, then shifted into + // the embedding table by +left_max. + int64_t dist = j - i; + if (dist < -left_max) dist = -left_max; + if (dist > right_max) dist = right_max; + const int64_t row = dist + left_max; + double rel = 0.0; + for (int64_t d = 0; d < head_dim; ++d) { + rel += static_cast(q[static_cast(i * hidden + h * head_dim + d)]) * + static_cast( + w.distance_embedding[static_cast(row * head_dim + d)]); + } + // divided by sqrt(d) AGAIN, separately from the score above. + scores[static_cast(j)] = dot + rel * scale; + } + + double best = scores[0]; + for (const double s : scores) best = std::max(best, s); + double denom = 0.0; + for (double& s : scores) { s = std::exp(s - best); denom += s; } + for (int64_t d = 0; d < head_dim; ++d) { + double acc = 0.0; + for (int64_t j = 0; j < frames; ++j) { + acc += scores[static_cast(j)] * + static_cast(v[static_cast(j * hidden + h * head_dim + d)]); + } + ctx[static_cast(i * hidden + h * head_dim + d)] = + static_cast(acc / denom); + } + } + } + return Linear(ctx, frames, hidden, hidden, w.out_w, w.out_b); +} + } // namespace w2vbert } // namespace models } // namespace vllm diff --git a/tests/vllm/models/test_w2vbert.cpp b/tests/vllm/models/test_w2vbert.cpp index f7187410e..44a972644 100644 --- a/tests/vllm/models/test_w2vbert.cpp +++ b/tests/vllm/models/test_w2vbert.cpp @@ -121,3 +121,47 @@ TEST_CASE("w2vbert conv module cannot see the future") { } CHECK(delta > 1e-6); // the last frame MUST move, or the probe proves nothing } + +TEST_CASE("w2vbert relative-key self-attention matches upstream") { + const auto w = Weights(); + vllm::models::w2vbert::SelfAttentionWeights a; + a.q_w = w.at("self_attn.linear_q.weight"); a.q_b = w.at("self_attn.linear_q.bias"); + a.k_w = w.at("self_attn.linear_k.weight"); a.k_b = w.at("self_attn.linear_k.bias"); + a.v_w = w.at("self_attn.linear_v.weight"); a.v_b = w.at("self_attn.linear_v.bias"); + a.out_w = w.at("self_attn.linear_out.weight"); a.out_b = w.at("self_attn.linear_out.bias"); + a.distance_embedding = w.at("self_attn.distance_embedding.weight"); + + const std::vector x(std::begin(kAttnIn), std::end(kAttnIn)); + const std::vector got = vllm::models::w2vbert::SelfAttentionRelativeKey( + x, kFrames, kHidden, kHeads, kLeftMax, kRightMax, a); + REQUIRE(got.size() == static_cast(kFrames * kHidden)); + const double worst = Worst(got, kAttnOut, got.size()); + INFO("max abs diff vs upstream self-attention: ", worst); + CHECK(worst < 1e-5); +} + +TEST_CASE("w2vbert attention is NOT causal: every frame sees the whole sequence") { + // The Conformer encoder is bidirectional. Perturbing the LAST frame must move + // the FIRST output -- the opposite of the conv module's guarantee, and worth + // pinning so a stray causal mask cannot be introduced unnoticed. + const auto w = Weights(); + vllm::models::w2vbert::SelfAttentionWeights a; + a.q_w = w.at("self_attn.linear_q.weight"); a.q_b = w.at("self_attn.linear_q.bias"); + a.k_w = w.at("self_attn.linear_k.weight"); a.k_b = w.at("self_attn.linear_k.bias"); + a.v_w = w.at("self_attn.linear_v.weight"); a.v_b = w.at("self_attn.linear_v.bias"); + a.out_w = w.at("self_attn.linear_out.weight"); a.out_b = w.at("self_attn.linear_out.bias"); + a.distance_embedding = w.at("self_attn.distance_embedding.weight"); + + std::vector x(std::begin(kAttnIn), std::end(kAttnIn)); + const std::vector base = vllm::models::w2vbert::SelfAttentionRelativeKey( + x, kFrames, kHidden, kHeads, kLeftMax, kRightMax, a); + x[static_cast((kFrames - 1) * kHidden)] += 2.0F; + const std::vector moved = vllm::models::w2vbert::SelfAttentionRelativeKey( + x, kFrames, kHidden, kHeads, kLeftMax, kRightMax, a); + double first = 0.0; + for (int64_t d = 0; d < kHidden; ++d) { + first = std::max(first, std::fabs(static_cast(base[static_cast(d)] - + moved[static_cast(d)]))); + } + CHECK(first > 1e-6); +} diff --git a/tests/vllm/models/w2vbert_goldens.inc b/tests/vllm/models/w2vbert_goldens.inc index 681e0ae18..4efedbfb8 100644 --- a/tests/vllm/models/w2vbert_goldens.inc +++ b/tests/vllm/models/w2vbert_goldens.inc @@ -54,6 +54,79 @@ inline constexpr ManifestEntry kManifest[] = { {"final_layer_norm.bias", 1, 16, 1, 1}, }; +inline constexpr int64_t kLeftMax = 64; +inline constexpr int64_t kRightMax = 8; + +inline constexpr float kAttnIn[] = { + -9.594057500e-02F, -1.970379353e-01F, 3.091780543e-01F, -3.240059912e-01F, 1.977615952e-01F, 1.255996078e-01F, + -4.858963192e-02F, 5.233879760e-02F, -1.255234238e-02F, -1.907685995e-01F, 6.528320909e-02F, 2.642313540e-01F, + -8.213867992e-02F, -3.329517320e-02F, -3.094844520e-01F, -1.587512046e-01F, -3.040036745e-02F, -1.934452951e-01F, + 1.232634708e-01F, -2.382263690e-01F, 2.865678370e-01F, 1.302255243e-01F, -1.465308852e-02F, 1.607152447e-02F, + -4.142036662e-02F, -1.876932830e-01F, 1.008507535e-01F, 5.873824656e-02F, -9.436415136e-02F, -1.134831086e-01F, + 2.916490138e-01F, 1.468011886e-01F, 3.731196523e-01F, -2.031023800e-01F, 3.351595402e-01F, -2.281776965e-01F, + -4.783637449e-02F, 4.026417434e-01F, -9.537943453e-02F, 7.122701406e-02F, -1.566387266e-01F, -1.437232494e-01F, + 1.953136027e-01F, 1.202465668e-01F, -5.913529173e-02F, -9.265415370e-02F, 1.864432991e-01F, -2.221976370e-01F, + 2.689546160e-02F, -1.869216412e-01F, -3.797391057e-02F, -3.154406250e-01F, -5.477657169e-02F, 1.828956306e-01F, + -1.598789245e-01F, -4.703613743e-02F, -1.274202913e-01F, -1.364821047e-01F, 4.861719161e-02F, 2.193725407e-01F, + -1.013209298e-01F, -1.122159883e-01F, 7.220326806e-04F, 3.464583755e-01F, 3.142156452e-02F, -1.993997544e-01F, + -1.552890241e-01F, -3.468154073e-01F, -1.188600957e-01F, 3.750374019e-01F, 2.870892640e-03F, -2.039421052e-01F, + -3.541253507e-02F, -1.632450670e-01F, 1.685765237e-01F, 2.931116223e-01F, -8.022844791e-02F, -9.523830563e-02F, + -3.143519461e-01F, 2.165964097e-01F, -1.024083123e-01F, -2.040273249e-01F, 2.907871604e-01F, -2.673627734e-01F, + 2.375294864e-01F, 1.897076219e-01F, -1.429091990e-01F, 5.927206576e-02F, -1.357223243e-01F, -1.374285817e-01F, + 1.012486815e-01F, 2.460681498e-01F, -5.786541849e-02F, 4.831767082e-02F, -2.619094551e-01F, 1.077427492e-01F, + -2.304868996e-01F, -1.890733987e-01F, 8.056045510e-03F, -1.453087479e-01F, 1.655087434e-02F, 3.047699332e-01F, + -1.470915377e-01F, -1.330942810e-01F, -8.492638916e-02F, -1.896542609e-01F, 2.849264741e-01F, 1.111553386e-01F, + -4.937656969e-02F, 1.919773966e-01F, 8.279750496e-02F, -3.584702611e-01F, -7.791640610e-02F, -1.934121102e-01F, + -6.562650949e-02F, -1.300016791e-01F, 1.127798557e-01F, 2.018043399e-01F, -1.353916973e-01F, -2.106460333e-01F, + -1.487745810e-02F, -1.659884453e-01F, 2.678915858e-01F, 1.063346937e-01F, -4.734786227e-02F, 2.190852314e-01F, + -5.697561055e-02F, -3.007797599e-01F, 8.137982339e-02F, -2.022698522e-01F, 2.452115901e-02F, -2.757439315e-01F, + 2.718363702e-01F, 2.669361830e-01F, -1.292787772e-02F, 5.800317973e-02F, -8.275966346e-02F, -1.409328878e-01F, + 1.265061200e-01F, 6.705509871e-02F, -7.988994569e-02F, -1.967667341e-01F, 3.652049303e-01F, 1.408427358e-01F, + 2.138290256e-01F, -2.029635012e-01F, 4.710807204e-01F, -1.577045321e-01F, -4.262536392e-02F, 2.517201602e-01F, + -9.514356405e-02F, -2.007755488e-01F, -1.190679297e-01F, -1.483298540e-01F, 5.402959511e-02F, 2.299695015e-01F, + -5.392658710e-02F, -1.452789605e-01F, -2.095509693e-02F, -6.459453702e-01F, 2.648693919e-01F, -1.943423897e-01F, + 6.115119457e-01F, -2.447939068e-01F, 3.451765329e-02F, 2.341475189e-01F, 9.294255823e-02F, -1.802244484e-01F, + -8.295061439e-02F, -1.672326475e-01F, 1.521994025e-01F, 2.542237341e-01F, -9.356955439e-02F, -4.763713777e-01F, + 7.956431806e-02F, -4.758353531e-01F, 3.279388547e-01F, -1.970757693e-01F, 5.772187724e-04F, -3.284979761e-01F, + 7.529678941e-02F, 2.651734948e-01F, -3.603204619e-03F, 5.254752561e-02F, -8.360575885e-03F, -1.764774024e-01F, + 9.754610062e-02F, 2.233647704e-01F, -4.988276958e-02F, -3.085417449e-01F, -3.022241592e-02F, -4.983290136e-01F, +}; + +inline constexpr float kAttnOut[] = { + -3.346482515e-01F, -3.416739702e-01F, 7.383730263e-02F, -2.041810304e-01F, 4.112985730e-02F, -1.510453671e-01F, + -3.832879663e-01F, 2.457573116e-01F, -1.093910187e-01F, 9.676307440e-02F, -8.777497709e-02F, 1.684791446e-01F, + 1.414990127e-01F, -1.307449043e-01F, 4.117153883e-01F, 3.169855475e-01F, -3.351668119e-01F, -3.407789469e-01F, + 7.295091450e-02F, -2.040776610e-01F, 4.072932526e-02F, -1.512265801e-01F, -3.832966089e-01F, 2.449328005e-01F, + -1.092328131e-01F, 9.683275968e-02F, -8.967903256e-02F, 1.683218777e-01F, 1.427127570e-01F, -1.316067278e-01F, + 4.109132290e-01F, 3.169966936e-01F, -3.351168931e-01F, -3.411467671e-01F, 7.387860864e-02F, -2.035676092e-01F, + 4.097428173e-02F, -1.504634321e-01F, -3.828220963e-01F, 2.453436702e-01F, -1.100950539e-01F, 9.732291102e-02F, + -8.673097938e-02F, 1.685813665e-01F, 1.412827671e-01F, -1.307987273e-01F, 4.122491777e-01F, 3.171121478e-01F, + -3.347445726e-01F, -3.410186172e-01F, 7.340318710e-02F, -2.043937743e-01F, 4.074595496e-02F, -1.512848884e-01F, + -3.833324611e-01F, 2.453905195e-01F, -1.097783595e-01F, 9.692320228e-02F, -8.841823041e-02F, 1.685219109e-01F, + 1.420619637e-01F, -1.317910254e-01F, 4.114996195e-01F, 3.178913593e-01F, -3.345721662e-01F, -3.409538567e-01F, + 7.323183119e-02F, -2.045230120e-01F, 4.080395028e-02F, -1.515905261e-01F, -3.834736049e-01F, 2.450615466e-01F, + -1.091141105e-01F, 9.681682289e-02F, -8.872249722e-02F, 1.683278829e-01F, 1.417612880e-01F, -1.309886426e-01F, + 4.112285972e-01F, 3.168585300e-01F, -3.341332078e-01F, -3.406188488e-01F, 7.357932627e-02F, -2.052885145e-01F, + 4.109628126e-02F, -1.520739347e-01F, -3.838476837e-01F, 2.460578382e-01F, -1.103749946e-01F, 9.663260728e-02F, + -8.937001228e-02F, 1.685872972e-01F, 1.423272491e-01F, -1.329278350e-01F, 4.110841155e-01F, 3.183579445e-01F, + -3.336375654e-01F, -3.406330049e-01F, 7.414181530e-02F, -2.051817030e-01F, 4.203128815e-02F, -1.521051526e-01F, + -3.843359649e-01F, 2.453684509e-01F, -1.095422879e-01F, 9.631552547e-02F, -9.024112672e-02F, 1.685904264e-01F, + 1.424536407e-01F, -1.321679205e-01F, 4.103865027e-01F, 3.169798851e-01F, -3.344252706e-01F, -3.403348029e-01F, + 7.386630774e-02F, -2.047676742e-01F, 4.130426794e-02F, -1.517373770e-01F, -3.834614754e-01F, 2.447570562e-01F, + -1.100921780e-01F, 9.643036127e-02F, -8.860356361e-02F, 1.681041718e-01F, 1.412818730e-01F, -1.310496032e-01F, + 4.107560515e-01F, 3.171385229e-01F, -3.341879845e-01F, -3.406796455e-01F, 7.289415598e-02F, -2.055183947e-01F, + 4.082331061e-02F, -1.525539905e-01F, -3.840082586e-01F, 2.456952929e-01F, -1.093593463e-01F, 9.624898434e-02F, + -9.025508910e-02F, 1.685187817e-01F, 1.423951238e-01F, -1.324347258e-01F, 4.107007384e-01F, 3.181056380e-01F, + -3.341024220e-01F, -3.403537273e-01F, 7.387364656e-02F, -2.049708813e-01F, 4.149665311e-02F, -1.517015398e-01F, + -3.839422166e-01F, 2.440764606e-01F, -1.098005399e-01F, 9.677146375e-02F, -8.778537065e-02F, 1.688970625e-01F, + 1.409235895e-01F, -1.308816373e-01F, 4.117437899e-01F, 3.176770806e-01F, -3.343912363e-01F, -3.401717246e-01F, + 7.395431399e-02F, -2.045599371e-01F, 4.039792717e-02F, -1.514119208e-01F, -3.833756745e-01F, 2.442661524e-01F, + -1.108106226e-01F, 9.691027552e-02F, -8.818929642e-02F, 1.689623594e-01F, 1.415381283e-01F, -1.317610443e-01F, + 4.112513959e-01F, 3.174804449e-01F, -3.334779143e-01F, -3.410950601e-01F, 7.381123304e-02F, -2.055148482e-01F, + 3.997967392e-02F, -1.521602422e-01F, -3.832870424e-01F, 2.452318966e-01F, -1.108010039e-01F, 9.625761956e-02F, + -8.836896718e-02F, 1.691971421e-01F, 1.412135512e-01F, -1.323044151e-01F, 4.107890129e-01F, 3.192493320e-01F, +}; + inline constexpr float kFfn1Out[] = { 9.458056092e-02F, 7.375027984e-02F, 4.845884070e-02F, -3.806251884e-01F, 4.369759262e-01F, -1.899440959e-02F, -9.286113083e-02F, -3.108807504e-01F, 2.782942355e-01F, 1.648317873e-01F, 1.476261765e-01F, 6.681033224e-02F, From c57ca36f1b3acfaec38b28c32a95458dafdee4dd Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:42:35 +0200 Subject: [PATCH 24/55] feat(MODEL-MM-indextts2): assemble the w2v-bert Conformer encoder layer (#634) (#710) FOLLOWING_AGENTS_PROTOCOL Composes the three gated parts into Wav2Vec2BertEncoderLayer and gates the WHOLE block against `transformers` executed directly. 6 cases / 186 assertions, within 2e-5. WHY THE ASSEMBLY NEEDS ITS OWN CASE: the macaron 0.5 factors live in the LAYER, not in FeedForward, so every piecewise case already on main passes with or without them. Green parts do not imply a green whole. The conv module's absent pre-norm is the same trap in reverse: it normalizes internally, so adding one here double-normalizes and still produces a well-formed block. MUTATION EVIDENCE with compile status: dropping the first macaron 0.5, double-normalizing before the conv module, and dropping the final layer norm each RED at compile_err=0; restored tree SUCCESS. The w2v-bert ENCODER LAYER is complete. Remaining: model-level wrapper (feature projection + layer stack), then EnhancedCodec, talker head, length regulator, S2Mel. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- include/vllm/model_executor/models/w2vbert.h | 29 +++++++++++++ src/vllm/model_executor/models/w2vbert.cpp | 41 ++++++++++++++++++ tests/vllm/models/test_w2vbert.cpp | 45 ++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/include/vllm/model_executor/models/w2vbert.h b/include/vllm/model_executor/models/w2vbert.h index 6cb77c30d..0c24cf7ea 100644 --- a/include/vllm/model_executor/models/w2vbert.h +++ b/include/vllm/model_executor/models/w2vbert.h @@ -78,6 +78,35 @@ std::vector SelfAttentionRelativeKey(const std::vector& x, int64_t int64_t hidden, int64_t heads, int64_t left_max, int64_t right_max, const SelfAttentionWeights& weights); + +struct EncoderLayerWeights { + std::vector ffn1_ln_gamma, ffn1_ln_beta; + std::vector ffn1_in_w, ffn1_in_b, ffn1_out_w, ffn1_out_b; + std::vector attn_ln_gamma, attn_ln_beta; + SelfAttentionWeights attn; + ConvModuleWeights conv; + std::vector ffn2_ln_gamma, ffn2_ln_beta; + std::vector ffn2_in_w, ffn2_in_b, ffn2_out_w, ffn2_out_b; + std::vector final_ln_gamma, final_ln_beta; +}; + +// Wav2Vec2BertEncoderLayer: the CONFORMER block. +// +// x = ffn1(ln(x)) * 0.5 + x <- macaron HALF step +// x = attn(ln(x)) + x +// x = conv(x) + x <- conv module normalizes internally +// x = ffn2(ln(x)) * 0.5 + x <- macaron HALF step +// x = final_ln(x) +// +// THE 0.5 FACTORS are what make it macaron rather than two ordinary +// feed-forwards. Dropping them still runs, still trains, and is a different +// architecture -- so they are gated by comparing the whole layer, where the +// piecewise cases cannot see them. +std::vector EncoderLayer(const std::vector& x, int64_t frames, int64_t hidden, + int64_t heads, int64_t intermediate, int64_t conv_kernel, + int64_t left_max, int64_t right_max, + const EncoderLayerWeights& weights, double eps); + } // namespace w2vbert } // namespace models } // namespace vllm diff --git a/src/vllm/model_executor/models/w2vbert.cpp b/src/vllm/model_executor/models/w2vbert.cpp index 6f7ea0d9b..5e7f820b8 100644 --- a/src/vllm/model_executor/models/w2vbert.cpp +++ b/src/vllm/model_executor/models/w2vbert.cpp @@ -205,6 +205,47 @@ std::vector SelfAttentionRelativeKey(const std::vector& x, int64_t return Linear(ctx, frames, hidden, hidden, w.out_w, w.out_b); } + +std::vector EncoderLayer(const std::vector& x, int64_t frames, int64_t hidden, + int64_t heads, int64_t intermediate, int64_t conv_kernel, + int64_t left_max, int64_t right_max, const EncoderLayerWeights& w, + double eps) { + std::vector h = x; + + // 1. macaron feed-forward, HALF weighted. + { + const std::vector n = LayerNorm(h, frames, hidden, w.ffn1_ln_gamma, w.ffn1_ln_beta, eps); + const std::vector f = FeedForward(n, frames, hidden, intermediate, w.ffn1_in_w, + w.ffn1_in_b, w.ffn1_out_w, w.ffn1_out_b); + for (size_t i = 0; i < h.size(); ++i) h[i] = f[i] * 0.5F + h[i]; + } + + // 2. self-attention, FULL residual. + { + const std::vector n = LayerNorm(h, frames, hidden, w.attn_ln_gamma, w.attn_ln_beta, eps); + const std::vector a = + SelfAttentionRelativeKey(n, frames, hidden, heads, left_max, right_max, w.attn); + for (size_t i = 0; i < h.size(); ++i) h[i] = a[i] + h[i]; + } + + // 3. convolution module -- it applies its OWN layer_norm internally, so there + // is no norm here; adding one would double-normalize. + { + const std::vector c = ConvModule(h, frames, hidden, conv_kernel, w.conv, eps); + for (size_t i = 0; i < h.size(); ++i) h[i] = h[i] + c[i]; + } + + // 4. second macaron feed-forward, HALF weighted. + { + const std::vector n = LayerNorm(h, frames, hidden, w.ffn2_ln_gamma, w.ffn2_ln_beta, eps); + const std::vector f = FeedForward(n, frames, hidden, intermediate, w.ffn2_in_w, + w.ffn2_in_b, w.ffn2_out_w, w.ffn2_out_b); + for (size_t i = 0; i < h.size(); ++i) h[i] = f[i] * 0.5F + h[i]; + } + + return LayerNorm(h, frames, hidden, w.final_ln_gamma, w.final_ln_beta, eps); +} + } // namespace w2vbert } // namespace models } // namespace vllm diff --git a/tests/vllm/models/test_w2vbert.cpp b/tests/vllm/models/test_w2vbert.cpp index 44a972644..340d66bf4 100644 --- a/tests/vllm/models/test_w2vbert.cpp +++ b/tests/vllm/models/test_w2vbert.cpp @@ -165,3 +165,48 @@ TEST_CASE("w2vbert attention is NOT causal: every frame sees the whole sequence" } CHECK(first > 1e-6); } + +TEST_CASE("w2vbert EncoderLayer reproduces the whole upstream Conformer block") { + // The macaron 0.5 factors live HERE, not in FeedForward, so only a whole-layer + // comparison can see them: every piecewise case above passes with or without. + const auto m = Weights(); + vllm::models::w2vbert::EncoderLayerWeights w; + w.ffn1_ln_gamma = m.at("ffn1_layer_norm.weight"); + w.ffn1_ln_beta = m.at("ffn1_layer_norm.bias"); + w.ffn1_in_w = m.at("ffn1.intermediate_dense.weight"); + w.ffn1_in_b = m.at("ffn1.intermediate_dense.bias"); + w.ffn1_out_w = m.at("ffn1.output_dense.weight"); + w.ffn1_out_b = m.at("ffn1.output_dense.bias"); + w.attn_ln_gamma = m.at("self_attn_layer_norm.weight"); + w.attn_ln_beta = m.at("self_attn_layer_norm.bias"); + w.attn.q_w = m.at("self_attn.linear_q.weight"); w.attn.q_b = m.at("self_attn.linear_q.bias"); + w.attn.k_w = m.at("self_attn.linear_k.weight"); w.attn.k_b = m.at("self_attn.linear_k.bias"); + w.attn.v_w = m.at("self_attn.linear_v.weight"); w.attn.v_b = m.at("self_attn.linear_v.bias"); + w.attn.out_w = m.at("self_attn.linear_out.weight"); + w.attn.out_b = m.at("self_attn.linear_out.bias"); + w.attn.distance_embedding = m.at("self_attn.distance_embedding.weight"); + w.conv.ln_gamma = m.at("conv_module.layer_norm.weight"); + w.conv.ln_beta = m.at("conv_module.layer_norm.bias"); + w.conv.pointwise1 = m.at("conv_module.pointwise_conv1.weight"); + w.conv.depthwise = m.at("conv_module.depthwise_conv.weight"); + w.conv.dw_ln_gamma = m.at("conv_module.depthwise_layer_norm.weight"); + w.conv.dw_ln_beta = m.at("conv_module.depthwise_layer_norm.bias"); + w.conv.pointwise2 = m.at("conv_module.pointwise_conv2.weight"); + w.ffn2_ln_gamma = m.at("ffn2_layer_norm.weight"); + w.ffn2_ln_beta = m.at("ffn2_layer_norm.bias"); + w.ffn2_in_w = m.at("ffn2.intermediate_dense.weight"); + w.ffn2_in_b = m.at("ffn2.intermediate_dense.bias"); + w.ffn2_out_w = m.at("ffn2.output_dense.weight"); + w.ffn2_out_b = m.at("ffn2.output_dense.bias"); + w.final_ln_gamma = m.at("final_layer_norm.weight"); + w.final_ln_beta = m.at("final_layer_norm.bias"); + + const std::vector x = Rand("hidden", kFrames * kHidden, 1.0); + const std::vector got = vllm::models::w2vbert::EncoderLayer( + x, kFrames, kHidden, kHeads, kIntermediate, kConvKernel, kLeftMax, kRightMax, w, + kLayerNormEps); + REQUIRE(got.size() == static_cast(kFrames * kHidden)); + const double worst = Worst(got, kLayerOut, got.size()); + INFO("max abs diff vs upstream Wav2Vec2BertEncoderLayer: ", worst); + CHECK(worst < 2e-5); +} From 5562c47fb85c2c2e8ffdcdc6fc70e9120e7e28bd Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:50:09 +0200 Subject: [PATCH 25/55] feat(MODEL-MM-indextts2): w2v-bert semantic front end COMPLETE (#634) (#712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Completes w2v-bert-2.0, the stage that turns the reference clip into the features EnhancedCodec quantizes. 8 cases / 190 assertions against `transformers` executed directly. FeatureProjection normalizes THEN projects. The encoder is the layer stack and NOTHING else: embed_positions is None for relative_key, and there is NO final layer norm after the stack — the trap worth naming, since most encoders end with one and adding it yields a correctly shaped tensor only a value comparison can reject. MUTATION EVIDENCE with compile status: adding a final norm, and projecting before normalizing, each RED at compile_err=0. PROCESS NOTE: this change's worktree lost a ref-lock race against a concurrent session, so `cd` failed and the chained commands ran in the SHARED CHECKOUT, editing two tracked files. Caught on the next command and reverted; checkout clean. A failed `cd` does not stop a chain from running wherever the shell already was. Pipeline: w2v-bert DONE, CAMPPlus DONE, vocoder core DONE, GPT-2 backbone DONE. Remaining: EnhancedCodec, talker head, length regulator, S2Mel, compose, routes/ABI. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- include/vllm/model_executor/models/w2vbert.h | 22 +++ scripts/gen-w2vbert-goldens.py | 41 ++++- src/vllm/model_executor/models/w2vbert.cpp | 25 +++ tests/vllm/models/test_w2vbert.cpp | 78 +++++++++ tests/vllm/models/w2vbert_goldens.inc | 168 +++++++++++++++++++ 5 files changed, 333 insertions(+), 1 deletion(-) diff --git a/include/vllm/model_executor/models/w2vbert.h b/include/vllm/model_executor/models/w2vbert.h index 0c24cf7ea..74556ca0e 100644 --- a/include/vllm/model_executor/models/w2vbert.h +++ b/include/vllm/model_executor/models/w2vbert.h @@ -107,6 +107,28 @@ std::vector EncoderLayer(const std::vector& x, int64_t frames, int int64_t left_max, int64_t right_max, const EncoderLayerWeights& weights, double eps); + +// Wav2Vec2BertFeatureProjection: layer_norm THEN a linear projection from the +// feature dim to hidden. Upstream also returns the PRE-projection normalized +// tensor (it is what quantization consumes); this returns the projected one, and +// `norm_out` optionally captures the other. +std::vector FeatureProjection(const std::vector& x, int64_t frames, int64_t in_dim, + int64_t hidden, const std::vector& ln_gamma, + const std::vector& ln_beta, + const std::vector& proj_w, + const std::vector& proj_b, double eps, + std::vector* norm_out = nullptr); + +// Wav2Vec2BertEncoder for position_embeddings_type == "relative_key": the layer +// stack and NOTHING else. `embed_positions` is None for this type (only +// "relative" and "rotary" construct one), and there is NO final layer norm after +// the stack -- adding one is the obvious-looking mistake, since most encoders +// have it. +std::vector EncoderStack(const std::vector& x, int64_t frames, int64_t hidden, + int64_t heads, int64_t intermediate, int64_t conv_kernel, + int64_t left_max, int64_t right_max, + const std::vector& layers, double eps); + } // namespace w2vbert } // namespace models } // namespace vllm diff --git a/scripts/gen-w2vbert-goldens.py b/scripts/gen-w2vbert-goldens.py index 46a09112d..db0748708 100644 --- a/scripts/gen-w2vbert-goldens.py +++ b/scripts/gen-w2vbert-goldens.py @@ -22,7 +22,8 @@ from pathlib import Path import numpy as np, torch from transformers import Wav2Vec2BertConfig -from transformers.models.wav2vec2_bert.modeling_wav2vec2_bert import Wav2Vec2BertEncoderLayer +from transformers.models.wav2vec2_bert.modeling_wav2vec2_bert import ( + Wav2Vec2BertEncoderLayer, Wav2Vec2BertFeatureProjection, Wav2Vec2BertEncoder) MASK = (1 << 64) - 1 def fnv(n: str) -> int: @@ -83,6 +84,33 @@ def main() -> int: with torch.no_grad(): layer(x) + # ---- feature projection + a 2-layer encoder stack ---- + # `embed_positions` is None for relative_key (only "relative"/"rotary" build + # one), and the encoder applies NO final norm after the stack. + IN_DIM = 10 + cfg2 = Wav2Vec2BertConfig( + hidden_size=cfg.hidden_size, num_attention_heads=cfg.num_attention_heads, + intermediate_size=cfg.intermediate_size, + conv_depthwise_kernel_size=cfg.conv_depthwise_kernel_size, + position_embeddings_type="relative_key", hidden_act="swish", + layer_norm_eps=cfg.layer_norm_eps, feature_projection_input_dim=IN_DIM, + num_hidden_layers=2, + ) + proj = Wav2Vec2BertFeatureProjection(cfg2).eval() + enc = Wav2Vec2BertEncoder(cfg2).eval() + pman = [] + with torch.no_grad(): + for mod, pre in ((proj, "fp"), (enc, "enc")): + for n, q in list(mod.named_parameters()) + list(mod.named_buffers()): + if n.endswith("num_batches_tracked"): continue + nm = pre + "." + n + v = P(nm, tuple(q.shape) if q.dim() else (1,)) + q.copy_(v.reshape(q.shape)); pman.append((nm, list(q.shape))) + feats_in = P("feats_in", (1, T, IN_DIM), 1.0) + with torch.no_grad(): + projected, _ = proj(feats_in) + stacked = enc(projected).last_hidden_state + def emit(f, name, t): arr = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) f.write(f"inline constexpr float {name}[] = {{\n") @@ -118,6 +146,17 @@ def emit(f, name, t): emit(f, "kConvIn", conv_in["x"]) emit(f, "kConvOut", tap["conv"]) emit(f, "kLayerOut", out) + f.write(f"inline constexpr int64_t kInDim = {IN_DIM};\n") + f.write("inline constexpr int64_t kNumLayers = 2;\n") + f.write(f"inline constexpr int64_t kModelManifestSize = {len(pman)};\n\n") + f.write("inline constexpr ManifestEntry kModelManifest[] = {\n") + for nm, sh in pman: + d = list(sh) + [1, 1, 1] + f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}, {d[2]}}},\n') + f.write("};\n\n") + emit(f, "kFeatsIn", feats_in) + emit(f, "kProjected", projected) + emit(f, "kStacked", stacked) f.write("} // namespace w2vbert_goldens\n") print(f"wrote {p}: {len(manifest)} tensors, T={T}, hidden={cfg.hidden_size}") return 0 diff --git a/src/vllm/model_executor/models/w2vbert.cpp b/src/vllm/model_executor/models/w2vbert.cpp index 5e7f820b8..9856e033b 100644 --- a/src/vllm/model_executor/models/w2vbert.cpp +++ b/src/vllm/model_executor/models/w2vbert.cpp @@ -246,6 +246,31 @@ std::vector EncoderLayer(const std::vector& x, int64_t frames, int return LayerNorm(h, frames, hidden, w.final_ln_gamma, w.final_ln_beta, eps); } + +std::vector FeatureProjection(const std::vector& x, int64_t frames, int64_t in_dim, + int64_t hidden, const std::vector& ln_gamma, + const std::vector& ln_beta, + const std::vector& proj_w, + const std::vector& proj_b, double eps, + std::vector* norm_out) { + const std::vector normed = LayerNorm(x, frames, in_dim, ln_gamma, ln_beta, eps); + if (norm_out != nullptr) *norm_out = normed; + return Linear(normed, frames, in_dim, hidden, proj_w, proj_b); +} + +std::vector EncoderStack(const std::vector& x, int64_t frames, int64_t hidden, + int64_t heads, int64_t intermediate, int64_t conv_kernel, + int64_t left_max, int64_t right_max, + const std::vector& layers, double eps) { + std::vector h = x; + for (const EncoderLayerWeights& l : layers) { + h = EncoderLayer(h, frames, hidden, heads, intermediate, conv_kernel, left_max, right_max, l, + eps); + } + // No final layer norm: the encoder returns the last layer's output directly. + return h; +} + } // namespace w2vbert } // namespace models } // namespace vllm diff --git a/tests/vllm/models/test_w2vbert.cpp b/tests/vllm/models/test_w2vbert.cpp index 340d66bf4..02bb1d077 100644 --- a/tests/vllm/models/test_w2vbert.cpp +++ b/tests/vllm/models/test_w2vbert.cpp @@ -210,3 +210,81 @@ TEST_CASE("w2vbert EncoderLayer reproduces the whole upstream Conformer block") INFO("max abs diff vs upstream Wav2Vec2BertEncoderLayer: ", worst); CHECK(worst < 2e-5); } + +namespace { +std::map> ModelWeights() { + std::map> w; + for (int64_t i = 0; i < kModelManifestSize; ++i) { + const auto& e = kModelManifest[i]; + const int64_t d[3] = {e.d0, e.d1, e.d2}; + int64_t n = 1; + for (int64_t k = 0; k < e.rank; ++k) n *= d[k]; + w[e.name] = Rand(e.name, n, 0.3); + } + return w; +} + +vllm::models::w2vbert::EncoderLayerWeights LayerFrom( + const std::map>& m, const std::string& p) { + vllm::models::w2vbert::EncoderLayerWeights w; + w.ffn1_ln_gamma = m.at(p + "ffn1_layer_norm.weight"); + w.ffn1_ln_beta = m.at(p + "ffn1_layer_norm.bias"); + w.ffn1_in_w = m.at(p + "ffn1.intermediate_dense.weight"); + w.ffn1_in_b = m.at(p + "ffn1.intermediate_dense.bias"); + w.ffn1_out_w = m.at(p + "ffn1.output_dense.weight"); + w.ffn1_out_b = m.at(p + "ffn1.output_dense.bias"); + w.attn_ln_gamma = m.at(p + "self_attn_layer_norm.weight"); + w.attn_ln_beta = m.at(p + "self_attn_layer_norm.bias"); + w.attn.q_w = m.at(p + "self_attn.linear_q.weight"); w.attn.q_b = m.at(p + "self_attn.linear_q.bias"); + w.attn.k_w = m.at(p + "self_attn.linear_k.weight"); w.attn.k_b = m.at(p + "self_attn.linear_k.bias"); + w.attn.v_w = m.at(p + "self_attn.linear_v.weight"); w.attn.v_b = m.at(p + "self_attn.linear_v.bias"); + w.attn.out_w = m.at(p + "self_attn.linear_out.weight"); + w.attn.out_b = m.at(p + "self_attn.linear_out.bias"); + w.attn.distance_embedding = m.at(p + "self_attn.distance_embedding.weight"); + w.conv.ln_gamma = m.at(p + "conv_module.layer_norm.weight"); + w.conv.ln_beta = m.at(p + "conv_module.layer_norm.bias"); + w.conv.pointwise1 = m.at(p + "conv_module.pointwise_conv1.weight"); + w.conv.depthwise = m.at(p + "conv_module.depthwise_conv.weight"); + w.conv.dw_ln_gamma = m.at(p + "conv_module.depthwise_layer_norm.weight"); + w.conv.dw_ln_beta = m.at(p + "conv_module.depthwise_layer_norm.bias"); + w.conv.pointwise2 = m.at(p + "conv_module.pointwise_conv2.weight"); + w.ffn2_ln_gamma = m.at(p + "ffn2_layer_norm.weight"); + w.ffn2_ln_beta = m.at(p + "ffn2_layer_norm.bias"); + w.ffn2_in_w = m.at(p + "ffn2.intermediate_dense.weight"); + w.ffn2_in_b = m.at(p + "ffn2.intermediate_dense.bias"); + w.ffn2_out_w = m.at(p + "ffn2.output_dense.weight"); + w.ffn2_out_b = m.at(p + "ffn2.output_dense.bias"); + w.final_ln_gamma = m.at(p + "final_layer_norm.weight"); + w.final_ln_beta = m.at(p + "final_layer_norm.bias"); + return w; +} +} // namespace + +TEST_CASE("w2vbert FeatureProjection normalizes BEFORE projecting") { + const auto m = ModelWeights(); + const std::vector x(std::begin(kFeatsIn), std::end(kFeatsIn)); + const std::vector got = vllm::models::w2vbert::FeatureProjection( + x, kFrames, kInDim, kHidden, m.at("fp.layer_norm.weight"), m.at("fp.layer_norm.bias"), + m.at("fp.projection.weight"), m.at("fp.projection.bias"), kLayerNormEps); + REQUIRE(got.size() == static_cast(kFrames * kHidden)); + CHECK(Worst(got, kProjected, got.size()) < 1e-5); +} + +TEST_CASE("w2vbert EncoderStack applies NO final norm after the layers") { + // Most encoders end with a layer norm; this one does not. Adding one still + // produces a well-formed tensor of the right shape, so only a value + // comparison against the real Wav2Vec2BertEncoder can tell. + const auto m = ModelWeights(); + std::vector layers; + for (int64_t i = 0; i < kNumLayers; ++i) { + layers.push_back(LayerFrom(m, "enc.layers." + std::to_string(i) + ".")); + } + const std::vector x(std::begin(kProjected), std::end(kProjected)); + const std::vector got = vllm::models::w2vbert::EncoderStack( + x, kFrames, kHidden, kHeads, kIntermediate, kConvKernel, kLeftMax, kRightMax, layers, + kLayerNormEps); + REQUIRE(got.size() == static_cast(kFrames * kHidden)); + const double worst = Worst(got, kStacked, got.size()); + INFO("max abs diff vs upstream Wav2Vec2BertEncoder: ", worst); + CHECK(worst < 5e-5); +} diff --git a/tests/vllm/models/w2vbert_goldens.inc b/tests/vllm/models/w2vbert_goldens.inc index 4efedbfb8..9c660bd61 100644 --- a/tests/vllm/models/w2vbert_goldens.inc +++ b/tests/vllm/models/w2vbert_goldens.inc @@ -267,4 +267,172 @@ inline constexpr float kLayerOut[] = { 3.420356214e-01F, 1.246291399e-02F, -1.659116335e-02F, -1.028273329e-01F, 2.067355216e-01F, -6.646477580e-01F, }; +inline constexpr int64_t kInDim = 10; +inline constexpr int64_t kNumLayers = 2; +inline constexpr int64_t kModelManifestSize = 68; + +inline constexpr ManifestEntry kModelManifest[] = { + {"fp.layer_norm.weight", 1, 10, 1, 1}, + {"fp.layer_norm.bias", 1, 10, 1, 1}, + {"fp.projection.weight", 2, 16, 10, 1}, + {"fp.projection.bias", 1, 16, 1, 1}, + {"enc.layers.0.ffn1_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.0.ffn1_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.0.ffn1.intermediate_dense.weight", 2, 32, 16, 1}, + {"enc.layers.0.ffn1.intermediate_dense.bias", 1, 32, 1, 1}, + {"enc.layers.0.ffn1.output_dense.weight", 2, 16, 32, 1}, + {"enc.layers.0.ffn1.output_dense.bias", 1, 16, 1, 1}, + {"enc.layers.0.self_attn_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.0.self_attn_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.0.self_attn.linear_q.weight", 2, 16, 16, 1}, + {"enc.layers.0.self_attn.linear_q.bias", 1, 16, 1, 1}, + {"enc.layers.0.self_attn.linear_k.weight", 2, 16, 16, 1}, + {"enc.layers.0.self_attn.linear_k.bias", 1, 16, 1, 1}, + {"enc.layers.0.self_attn.linear_v.weight", 2, 16, 16, 1}, + {"enc.layers.0.self_attn.linear_v.bias", 1, 16, 1, 1}, + {"enc.layers.0.self_attn.linear_out.weight", 2, 16, 16, 1}, + {"enc.layers.0.self_attn.linear_out.bias", 1, 16, 1, 1}, + {"enc.layers.0.self_attn.distance_embedding.weight", 2, 73, 8, 1}, + {"enc.layers.0.conv_module.layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.0.conv_module.layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.0.conv_module.pointwise_conv1.weight", 3, 32, 16, 1}, + {"enc.layers.0.conv_module.depthwise_conv.weight", 3, 16, 1, 7}, + {"enc.layers.0.conv_module.depthwise_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.0.conv_module.depthwise_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.0.conv_module.pointwise_conv2.weight", 3, 16, 16, 1}, + {"enc.layers.0.ffn2_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.0.ffn2_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.0.ffn2.intermediate_dense.weight", 2, 32, 16, 1}, + {"enc.layers.0.ffn2.intermediate_dense.bias", 1, 32, 1, 1}, + {"enc.layers.0.ffn2.output_dense.weight", 2, 16, 32, 1}, + {"enc.layers.0.ffn2.output_dense.bias", 1, 16, 1, 1}, + {"enc.layers.0.final_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.0.final_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.1.ffn1_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.1.ffn1_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.1.ffn1.intermediate_dense.weight", 2, 32, 16, 1}, + {"enc.layers.1.ffn1.intermediate_dense.bias", 1, 32, 1, 1}, + {"enc.layers.1.ffn1.output_dense.weight", 2, 16, 32, 1}, + {"enc.layers.1.ffn1.output_dense.bias", 1, 16, 1, 1}, + {"enc.layers.1.self_attn_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.1.self_attn_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.1.self_attn.linear_q.weight", 2, 16, 16, 1}, + {"enc.layers.1.self_attn.linear_q.bias", 1, 16, 1, 1}, + {"enc.layers.1.self_attn.linear_k.weight", 2, 16, 16, 1}, + {"enc.layers.1.self_attn.linear_k.bias", 1, 16, 1, 1}, + {"enc.layers.1.self_attn.linear_v.weight", 2, 16, 16, 1}, + {"enc.layers.1.self_attn.linear_v.bias", 1, 16, 1, 1}, + {"enc.layers.1.self_attn.linear_out.weight", 2, 16, 16, 1}, + {"enc.layers.1.self_attn.linear_out.bias", 1, 16, 1, 1}, + {"enc.layers.1.self_attn.distance_embedding.weight", 2, 73, 8, 1}, + {"enc.layers.1.conv_module.layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.1.conv_module.layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.1.conv_module.pointwise_conv1.weight", 3, 32, 16, 1}, + {"enc.layers.1.conv_module.depthwise_conv.weight", 3, 16, 1, 7}, + {"enc.layers.1.conv_module.depthwise_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.1.conv_module.depthwise_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.1.conv_module.pointwise_conv2.weight", 3, 16, 16, 1}, + {"enc.layers.1.ffn2_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.1.ffn2_layer_norm.bias", 1, 16, 1, 1}, + {"enc.layers.1.ffn2.intermediate_dense.weight", 2, 32, 16, 1}, + {"enc.layers.1.ffn2.intermediate_dense.bias", 1, 32, 1, 1}, + {"enc.layers.1.ffn2.output_dense.weight", 2, 16, 32, 1}, + {"enc.layers.1.ffn2.output_dense.bias", 1, 16, 1, 1}, + {"enc.layers.1.final_layer_norm.weight", 1, 16, 1, 1}, + {"enc.layers.1.final_layer_norm.bias", 1, 16, 1, 1}, +}; + +inline constexpr float kFeatsIn[] = { + -9.729663730e-01F, -1.877194643e-02F, -9.478094578e-01F, -5.454129577e-01F, -7.211037874e-01F, 4.891924560e-02F, + -6.084530354e-01F, -7.936616987e-02F, 7.163162827e-01F, -6.197080612e-01F, -9.839168787e-01F, 2.636713386e-01F, + 3.498082757e-01F, -6.862804294e-01F, 7.245976925e-01F, -3.105559945e-02F, 2.406154722e-01F, 6.335238367e-02F, + 2.394145131e-01F, -9.677134752e-01F, -8.390795588e-01F, 5.967180058e-02F, 4.403698742e-01F, -9.287266731e-01F, + -7.361611724e-01F, -9.672871828e-01F, 1.268039197e-01F, 9.445686340e-01F, -7.676674724e-01F, 1.752774864e-01F, + 5.945228338e-01F, -1.798555404e-01F, 4.676217437e-01F, 6.383023262e-01F, -2.606000304e-01F, -9.536195397e-01F, + -9.252579212e-01F, 7.567312121e-01F, -5.993475392e-02F, -3.839307725e-01F, 7.882403731e-01F, 2.010141611e-01F, + 9.424477816e-02F, -2.708258331e-01F, 4.261337519e-01F, 9.640449882e-01F, -3.816821277e-01F, 1.034397334e-01F, + 2.612967975e-02F, -6.153866053e-01F, 5.667590499e-01F, 1.772314161e-01F, -5.453535914e-01F, -8.224639297e-02F, + -4.969646037e-01F, -5.540629104e-02F, -9.441369213e-03F, -5.740284324e-01F, 6.713719964e-01F, -8.681458235e-01F, + -7.547557354e-01F, -1.416098475e-01F, -4.985913634e-01F, -1.672186702e-01F, -5.792993903e-01F, -2.761027813e-01F, + 8.410109580e-02F, 3.255552351e-01F, -9.905063510e-01F, 4.881471395e-01F, -7.751033902e-01F, 2.454049140e-01F, + -1.979371719e-02F, -2.627256513e-01F, 8.799289167e-02F, 2.400344163e-01F, 5.619342905e-03F, -7.152653933e-01F, + 5.119222403e-01F, -2.407856882e-01F, 1.624261886e-01F, -3.325003088e-01F, 5.184461474e-01F, 1.614307314e-01F, + 2.664813399e-01F, -1.181972995e-01F, 2.447878569e-01F, 4.890331253e-02F, 5.808426738e-01F, -6.508605480e-01F, + -7.459224463e-01F, -8.051521182e-01F, 6.055964231e-01F, -7.420707494e-02F, 5.959674716e-01F, 5.215299726e-01F, + 1.472285986e-01F, 4.860627353e-01F, -8.931604028e-01F, 3.261833191e-01F, -2.799558938e-01F, 9.270120859e-01F, + -7.505232096e-01F, -8.236296475e-03F, 2.991412878e-01F, 8.672829270e-01F, -2.056950927e-01F, -1.981909126e-01F, + -9.349498898e-02F, -1.518978551e-02F, -2.498482615e-01F, 3.787626624e-01F, -7.677116394e-01F, 6.626555920e-01F, + -8.430274725e-01F, 8.859667778e-01F, 3.821387589e-01F, 4.214818403e-02F, -6.187896729e-01F, 9.489978850e-02F, +}; + +inline constexpr float kProjected[] = { + -1.390941441e-01F, -1.640020609e-01F, 1.649263203e-01F, -2.744371891e-01F, 1.320385188e-02F, -1.639689356e-01F, + 8.824825287e-03F, -2.901758552e-01F, -5.432158709e-01F, -1.815942079e-01F, 2.798099518e-01F, 3.004451990e-01F, + -3.188480437e-01F, 1.088864505e-01F, -2.827829123e-01F, -1.616226733e-01F, 1.378521025e-01F, -1.692471802e-01F, + 1.193902120e-01F, -3.148998916e-01F, 7.679226995e-02F, -2.590432763e-01F, 2.475081980e-01F, -2.717960477e-01F, + -3.725841343e-01F, -7.483701408e-02F, 2.947900891e-01F, 5.774461478e-02F, -4.057007134e-01F, 1.372387856e-01F, + -2.203515321e-01F, -2.495067865e-01F, 2.176637203e-02F, -1.157417744e-01F, 1.433520913e-01F, -9.782595932e-02F, + 1.594193727e-01F, -1.148334146e-01F, 2.019921541e-01F, -3.383265734e-01F, -4.406292737e-01F, -1.991659999e-01F, + 3.051699996e-01F, 1.495837867e-01F, -4.303810298e-01F, -1.161644906e-01F, -2.885826230e-01F, -3.113887906e-01F, + -1.264326572e-01F, 6.132016331e-02F, -1.754044294e-01F, 1.318529248e-02F, 1.767864972e-01F, -5.295403302e-02F, + 2.393022329e-01F, -2.703110576e-01F, -4.326461554e-01F, -3.820931017e-01F, 2.967341542e-01F, 2.453054339e-01F, + -2.925435007e-01F, -7.363779843e-02F, -1.496183872e-01F, -1.049265638e-01F, -6.687408686e-02F, 2.421289682e-02F, + -2.303522825e-02F, -1.897706836e-01F, 9.110026807e-02F, -1.282146722e-01F, 1.886465847e-01F, -2.684816122e-01F, + -3.790892065e-01F, -2.441152334e-01F, 3.388914764e-01F, 2.528614700e-01F, -1.237488538e-01F, -3.078475595e-03F, + 3.232917935e-02F, -1.162427887e-01F, -2.534731030e-01F, -1.820687056e-01F, 6.560140103e-02F, -1.592098624e-01F, + 1.850292459e-02F, -1.241612881e-01F, 1.117017567e-01F, -2.113558352e-01F, -5.332305431e-01F, -2.336282730e-01F, + 4.084531069e-01F, 4.187824726e-01F, -1.843363941e-01F, 4.269419611e-02F, -1.003449559e-01F, -1.541261971e-01F, + -6.187718362e-02F, -9.222647548e-02F, 1.400522590e-01F, -7.622991502e-02F, 2.097650170e-01F, -1.151743382e-01F, + 1.022486240e-01F, -3.782769442e-01F, -4.345555902e-01F, -1.562914848e-01F, 3.168174326e-01F, 4.738706350e-02F, + -4.020864964e-01F, -1.164536625e-01F, -1.720626056e-01F, -2.962014675e-01F, -2.970673889e-02F, -2.194575220e-01F, + 2.333582640e-01F, -3.925322294e-01F, -8.181959391e-03F, -2.302276194e-01F, 6.878289580e-02F, -2.125676274e-01F, + -4.276411235e-01F, -2.778645605e-02F, 2.469926924e-01F, 1.647571176e-01F, -3.555952013e-01F, 2.046339661e-01F, + -2.209144384e-01F, -2.665776908e-01F, -2.373240888e-03F, -1.289285421e-01F, -4.900442809e-02F, -2.590332031e-01F, + 1.152040809e-01F, -2.482749820e-01F, 2.934649289e-01F, -1.480159760e-01F, -3.954640031e-01F, -1.444105953e-01F, + 2.997227609e-01F, 1.465248168e-01F, -3.438683152e-01F, 1.324093938e-01F, -1.800514609e-01F, -2.624012232e-01F, + 1.365528703e-01F, -5.206828937e-03F, 2.367280424e-02F, -2.610731721e-01F, 2.358386219e-01F, -2.311209887e-01F, + 2.416234314e-01F, -2.791199684e-01F, -2.747524679e-01F, -5.835888535e-02F, 2.171350867e-01F, -1.278486848e-01F, + -4.012872577e-01F, 2.062498033e-02F, -1.043107659e-01F, -3.429572880e-01F, -1.114142686e-01F, -8.624090254e-02F, + 1.486461461e-01F, -1.907583773e-01F, 5.723176897e-02F, -1.235379428e-01F, 2.678944170e-02F, -3.631803691e-01F, + -4.436691105e-01F, -1.710724235e-01F, 3.403562307e-01F, 1.863520890e-01F, -2.308579385e-01F, 3.943784535e-02F, + -4.915340990e-02F, -1.066338643e-01F, -2.268194556e-01F, -7.383005321e-02F, 1.594858170e-01F, -4.396814108e-02F, + 1.701163054e-01F, -1.449559778e-01F, -2.053636312e-02F, -3.299090862e-01F, -5.021825433e-01F, -2.046889067e-01F, + 3.675042987e-01F, 1.099447012e-01F, -2.633743584e-01F, -4.229956865e-02F, -9.849750996e-02F, -1.879401207e-01F, +}; + +inline constexpr float kStacked[] = { + 1.820804924e-01F, 3.375263810e-01F, 2.105400115e-01F, 1.104546115e-01F, 9.580246359e-02F, -5.916581750e-01F, + -2.661771178e-01F, 6.365516037e-02F, -1.206416860e-01F, -1.310735792e-01F, -1.476151347e-01F, -1.599999070e-01F, + -1.928413520e-03F, 2.408004254e-01F, -1.561739668e-02F, 3.447858244e-02F, 1.777177602e-01F, 3.536210954e-01F, + 2.158203274e-01F, 1.085219085e-01F, 9.688911587e-02F, -6.520031691e-01F, -2.643207312e-01F, 7.446181029e-02F, + -1.073837951e-01F, -1.314688772e-01F, -1.467562318e-01F, -1.732757390e-01F, -2.377135865e-02F, 2.456599474e-01F, + -4.450276867e-02F, 2.147373185e-02F, 1.779650897e-01F, 3.324885368e-01F, 2.124644667e-01F, 8.470459282e-02F, + 8.607681841e-02F, -6.118202806e-01F, -2.635157108e-01F, 9.010746330e-02F, -1.051347554e-01F, -1.276033074e-01F, + -1.471947730e-01F, -1.597408056e-01F, -2.377105318e-02F, 2.022138536e-01F, 1.243934967e-02F, 2.541818842e-02F, + 1.808548570e-01F, 3.244057000e-01F, 1.612059623e-01F, 7.189858705e-02F, 9.379140288e-02F, -5.904875398e-01F, + -2.650890648e-01F, 8.854421228e-02F, -1.105441526e-01F, -1.134392619e-01F, -1.485736072e-01F, -1.466708332e-01F, + -2.455118857e-02F, 2.017247975e-01F, -3.775777668e-02F, 3.594360128e-02F, 1.829780340e-01F, 3.273119628e-01F, + 1.828860790e-01F, 1.065661088e-01F, 1.054433510e-01F, -5.974025726e-01F, -2.698188722e-01F, 5.876745284e-02F, + -1.075779870e-01F, -1.191810444e-01F, -1.479000151e-01F, -1.492502242e-01F, -3.606754914e-02F, 2.016970813e-01F, + -8.941548318e-02F, 3.483759239e-02F, 1.840072572e-01F, 3.353054523e-01F, 1.870488077e-01F, 1.100712493e-01F, + 1.028265432e-01F, -5.795761347e-01F, -2.700886428e-01F, 6.723739952e-02F, -1.088415235e-01F, -1.300844401e-01F, + -1.473331749e-01F, -1.468186527e-01F, -4.274513200e-02F, 2.026818544e-01F, -5.880094320e-02F, 3.975766525e-02F, + 1.811203957e-01F, 3.358216584e-01F, 2.233686298e-01F, 8.353888988e-02F, 9.079542011e-02F, -6.217302680e-01F, + -2.750058770e-01F, 5.628481880e-02F, -1.144226938e-01F, -1.288716346e-01F, -1.474637538e-01F, -1.651537716e-01F, + -3.891244531e-02F, 1.850834340e-01F, -3.155261651e-02F, 2.369362861e-02F, 1.798584759e-01F, 3.472229242e-01F, + 2.216350883e-01F, 1.213018969e-01F, 1.061209738e-01F, -6.043707728e-01F, -2.736145556e-01F, 7.096176594e-02F, + -1.012914851e-01F, -1.400043070e-01F, -1.475035697e-01F, -1.676436514e-01F, -3.790156543e-02F, 2.469661683e-01F, + -4.169369489e-02F, 3.567129746e-02F, 1.795067638e-01F, 3.489077389e-01F, 1.891365796e-01F, 1.020497754e-01F, + 9.949932247e-02F, -6.303945780e-01F, -2.611414790e-01F, 7.874415815e-02F, -1.080426574e-01F, -1.269062608e-01F, + -1.467669904e-01F, -1.593066305e-01F, -2.899877541e-02F, 2.298740298e-01F, -5.658680201e-02F, 2.598701417e-02F, + 1.788990349e-01F, 3.438121974e-01F, 2.173456103e-01F, 9.232312441e-02F, 9.294571728e-02F, -6.769651175e-01F, + -2.695276439e-01F, 6.813131273e-02F, -1.100567207e-01F, -1.244844645e-01F, -1.473467648e-01F, -1.782618761e-01F, + -3.114192933e-02F, 2.267815322e-01F, -5.608728155e-02F, 1.272763871e-02F, 1.817307770e-01F, 3.384398818e-01F, + 2.106374949e-01F, 1.075523794e-01F, 1.049444452e-01F, -5.991579890e-01F, -2.793086171e-01F, 4.614030570e-02F, + -1.092932075e-01F, -1.295213401e-01F, -1.477895379e-01F, -1.597559154e-01F, -4.183332250e-02F, 2.102298141e-01F, + -6.741872430e-02F, 3.819438443e-02F, 1.834909171e-01F, 3.331958652e-01F, 2.171078175e-01F, 8.803658187e-02F, + 9.579753131e-02F, -6.210545301e-01F, -2.819635868e-01F, 5.044926703e-02F, -1.173384860e-01F, -1.282457411e-01F, + -1.473869532e-01F, -1.657804549e-01F, -5.069936812e-02F, 1.885000020e-01F, -5.694797635e-02F, 3.277483582e-02F, +}; + } // namespace w2vbert_goldens From e54a242f952a42eb68de5ad0b323f9ab5cbfdcc2 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:57:13 +0200 Subject: [PATCH 26/55] =?UTF-8?q?feat(TOOLS-PARSER-BREADTH):=20register=20?= =?UTF-8?q?`inkling`=20=E2=80=94=20the=20ported=20engine=20had=20no=20regi?= =?UTF-8?q?stry=20face=20(#608=20W1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL W1 was scoped as "port a parser". The Inkling dialect was ALREADY ported and oracle-gated -- src/vllm/parser/inkling.cpp, inkling_config() with its arg carver, get_parser_engine("inkling"), golden scenarios in test_parser_engine_assembly. What was missing was the tool-parser REGISTRY FACE: get_tool_parser("inkling") returned nullptr, so --tool-call-parser inkling threw at startup and a fully ported dialect was unreachable. 41 -> 42 names, 37 -> 38 families. The change is ParserEngineToolAdapter (the TOOL half of upstream's make_adapters, twin of the reasoning adapter already shipped) plus InklingEngineToolParser, the factory branch and the tool_parser_names() entry together, and ParserRequestFromChatCompletion moved beside ParserRequest so the adapter and the serving path share one projection instead of two copies. The mutation that drops tool_choice from that projection reds 20 cases -- that is what gates the move. No detect.cpp marker row, for a reason no other excluded family has: the table matches a CHAT TEMPLATE string, and Inkling has no jinja template at the pin (rendering is vllm/renderers/inkling_encoding.py). Its marker collides with nothing; a row would look harmless while being unreachable. A secondary-oracle premise was REFUSED rather than followed. I briefed that GptOssToolParser's stub meant vLLM implements nothing for gpt-oss, so SGLang was admissible under the new registry rule. It is not: vllm/parser/harmony.py is a 358-line vLLM-owned HarmonyParser(DelegatingParser) with its own test, and the stub delegates INSIDE vLLM. Following my brief would have consulted a secondary oracle where the primary speaks. Recorded in the spec so nobody re-runs the check. Reviewed, FAILed on the record, repaired, and the corrections are larger than the original claim. "Upstream never constructs the adapter" was false -- tests/parser/engine/test_inkling.py:487 TestRegisteredAdapters does exactly that -- and that sentence was load-bearing, because it justified never looking outside two test classes. Four more cases ported, one declined with its reason; the split is 22 cases, 19 PORTED / 3 AUTHORED, not the 15/18 first reported. The authored CONTENT-seed case survives but its rationale was also wrong: the seed lives only in extract_tool_calls_streaming, not in the delegating non-streaming path. Two gaps left OPEN and recorded rather than papered over. finish_streaming() is functionally ungated -- mutating it to return nullopt leaves all 22 cases green; the repairer authored a streaming twin to try to move it, it did not, and the case was DELETED rather than ship a false rationale. And README's counts are stale by two waves, but check-doc-checkpoint REFUSES the fix ("Co-edited public projections never justify README churn") and refuses a README-only fix too, so #704 needs a landing-source change or an argued exception. #703 records that --reasoning-parser inkling still throws while the tool parser now resolves. Verified on aarch64 (kairos-4db2, CPU-only; the dev box was at 98% disk). Clean build 0 warnings under -Werror, ctest 432/433 with the one failure passing serially, test_tool_parser_inkling 22/22 133/133. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/engine-matrix.md | 2 +- .agents/roadmap_v1.md | 6 +- .agents/specs/tool-parser-breadth.md | 323 +++++++++- CMakeLists.txt | 1 + docs/FEATURES.md | 2 +- docs/STATUS.md | 2 +- docs/USAGE.md | 2 +- .../tool_parsers/parser_engine_adapter.h | 126 ++++ include/vllm/parser/engine/parser_engine.h | 10 + src/vllm/entrypoints/openai/serving_chat.cpp | 24 +- .../openai/tool_parsers/abstract.cpp | 14 +- .../openai/tool_parsers/detect.cpp | 11 + .../tool_parsers/parser_engine_adapter.cpp | 52 ++ src/vllm/parser/engine/parser_engine.cpp | 24 + tests/CMakeLists.txt | 2 + .../openai/tool_parsers/test_detect.cpp | 3 +- .../openai/tool_parsers/test_inkling.cpp | 581 ++++++++++++++++++ 17 files changed, 1147 insertions(+), 38 deletions(-) create mode 100644 include/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h create mode 100644 src/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.cpp create mode 100644 tests/vllm/entrypoints/openai/tool_parsers/test_inkling.cpp diff --git a/.agents/engine-matrix.md b/.agents/engine-matrix.md index 1aed14697..8c9bdca94 100644 --- a/.agents/engine-matrix.md +++ b/.agents/engine-matrix.md @@ -155,7 +155,7 @@ lifecycle are unchanged. | `TOOLS-GUIDANCE-OUTLINES` | Guidance, outlines, and LM-format-enforcer backends | T2 | `vllm/v1/structured_output/__init__.py:140-159` | - | - | `planned: specs/guidance-outlines-backends.md` | `INVENTORIED` | - | | `TOOLS-CALLING-CORE` | Auto, required, named tool choice; streaming deltas; Hermes and Qwen3 parsers | T0 | `vllm/entrypoints/openai/chat_completion/serving.py:428,688,872`; `vllm/tool_parsers/hermes_tool_parser.py:34`; `vllm/tool_parsers/qwen3_engine_tool_parser.py:7` | `src/vllm/entrypoints/openai/serving_chat.cpp:98,224,239`; `src/vllm/entrypoints/openai/tool_parsers/hermes.cpp:124,180`; `src/vllm/entrypoints/openai/tool_parsers/qwen3.cpp:13` | `tests/vllm/entrypoints/openai/tool_parsers/test_tool_parsers.cpp:27,121,159`; `tests/vllm/entrypoints/openai/tool_parsers/test_tool_choice_grammar.cpp:248,260,280`; `tests/vllm/entrypoints/openai/test_serving.cpp:625,741,903`; **SELECTION SURFACE 2026-07-24 (`CLAIM-DOCS-T2-FIXES`)** — the bundled OpenAI server no longer hardcodes `"hermes"`/`""`: `--tool-call-parser` and `--reasoning-parser` (vLLM's own flag names) select any registered dialect, `auto` runs the same chat-template detection the C ABI uses, `none` disables, and an unknown name aborts startup listing the registry. The name lists are ENUMERATED from the factories (`tool_parser_names()` 40 names / `reasoning_parser_names()` 7), not hand-written at the flag; the whole flag behaviour lives in `ResolveToolParserName` / `ResolveReasoningParserName` so it is unit-tested without a server. DEFAULTS REPRODUCE THE OLD HARDCODE EXACTLY (`hermes` / disabled), so an invocation naming neither flag is unchanged. Anchors: `examples/server/main.cpp` (flags), `src/vllm/entrypoints/openai/tool_parsers/{abstract,detect}.cpp`, `src/vllm/entrypoints/openai/reasoning_parsers/{abstract,detect}.cpp`; tests `tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp`, `tests/vllm/entrypoints/openai/reasoning_parsers/test_detect.cpp` | `planned: specs/tool-calling.md` | `PARTIAL` | - | | `TOOLS-STREAMING-PARSER` | Unified streaming parser engine for reasoning and tool calls, including token-ID scanning, coalesced deltas, replay adapters and parser-specific configurations | T1 | `vllm/parser/engine/streaming_parser_engine.py:89`; `vllm/parser/engine/token_id_scanner.py:29`; `vllm/parser/engine/incremental_lexer.py:80`; `vllm/parser/engine/events.py:11,22`; `vllm/parser/qwen3.py:88`; `vllm/parser/kimi_k2.py:52`; `vllm/parser/engine/parser_engine.py:79` (assembly, residual); `tests/parser/engine/test_engine.py`, `test_token_id_scanner.py`, `test_qwen3.py` @ `555967922` | CORE engine landed 2026-07-27 (`CLAIM-ROADMAP-C8-PARSER`): `include/vllm/parser/engine/{events,parser_engine_config,incremental_lexer,token_id_scanner,streaming_parser_engine,configs,registry}.h` + `src/vllm/parser/engine/{incremental_lexer,token_id_scanner,streaming_parser_engine,configs,registry}.cpp` (scanner + prefix-buffering lexer + transition state machine + JSON-arg brace hold-back + drop-info + qwen3/seed_oss/kimi_k2 configs + unified name->config registry). ASSEMBLY landed 2026-07-27 (`CLAIM-ROADMAP-C8-ASSEMBLY`): `include/vllm/parser/engine/{parser_engine,py_json}.h` + `src/vllm/parser/engine/parser_engine.cpp` (`ParserEngine`: SemanticEvent -> streaming `DeltaMessage` + one-shot `ExtractedToolCallInformation`, held-back streaming-arg prefix, tool_index++, finish() flush, qwen3 `` arg-converter) + `include/vllm/parser/{kimi_k2,parser_manager}.h` + `src/vllm/parser/{kimi_k2,parser_manager}.cpp` (kimi native-header id/name overrides + name->parser dispatch) + assembly fields on `parser_engine_config.h`/`configs.cpp`. SERVING-SSE dispatch swap landed 2026-07-27 (`CLAIM-ROADMAP-C8-SERVING`): `src/vllm/entrypoints/openai/serving_chat.cpp` (`ShapeChatDeltaEngine`/`ShapeChatMessageEngine`/`MakeParserEngine` + 3 name-selected drive-site branches + `ChatSseStream` engine member) + `include/vllm/entrypoints/openai/serving_chat.h`; faithful `include_reasoning` request field on `protocol.{h,cpp}`. CONFIG FAMILIES landed 2026-07-27 (`CLAIM-ROADMAP-C8-CONFIGS`): 5 more engine-backed families ported as additive `ParserEngineConfig` builders in `src/vllm/parser/engine/configs.cpp` (`minimax_m2_config`, `glm47_moe_config`, `deepseek_v4_config`, `deepseek_v32_config`, `nemotron_v3_config` + `_minimax_m2`/`_glm47`/`_dsml` std::regex arg-converters) + `registry.cpp`/`parser_manager.cpp` dispatch + `include/vllm/parser/glm47_moe.{h,cpp}` (name-`.strip()` over the existing hooks). CONFIG FAMILIES C8-2 landed 2026-07-27 (`CLAIM-ROADMAP-C8-CONFIGS-2`): the last 2 deferred families gemma4 + inkling PORTED — added 4 additive assembly-core virtual seams (default-inert for the other 8 families) `preprocess_feed` (parser_engine.py:210), virtual `events_to_delta` (:706), virtual `single_pass_parse` (:645), `args_wrapper_keys` from `_extract_args_value` (:1064) + virtual `reset`/`extract_reasoning`; `src/vllm/parser/engine/configs.cpp` (`gemma4_config` + `_gemma4_arg_converter` key:value scanner; `inkling_config` + `_inkling_arg_converter` JSON-span carver) + `include/vllm/parser/{gemma4,inkling}.{h,cpp}` (gemma4 `_preprocess_feed` channel-injection + `_events_to_delta` `thought\n`-strip + `extract_reasoning`; inkling `args_wrapper_keys` unwrap + `_single_pass_parse` trailing flush) + `registry.cpp`/`parser_manager.cpp` dispatch. JSON-SCHEMA ARG-TYPE COERCION landed 2026-07-28 (`CLAIM-C8-ARG-COERCION`): `_fix_arg_types` / `_streamable_string_keys` / `find_tool_properties` (parser_engine.py:227,269,365,348) ported over the ALREADY-ported `extract_types_from_schema` / `coerce_to_schema_type` helpers (`tool_parsers/utils.cpp`) — `ParserTool` now carries the function `parameters` JSON-schema (threaded from `serving_chat.cpp` `ToParserRequest`), so a request whose tools declare typed params (int/number/bool/string/array/null) has its assembled `tool_calls[].function.arguments` coerced to the declared types in BOTH streaming (`parse_delta`) and one-shot (`extract_tool_calls`/`parse`); no-schema/absent-tools = identity (byte-identical). Additive to `parser_engine.{h,cpp}` (recursive `_coerce_dict`/`_coerce_value` + `find_tool_properties`) + `serving_chat.cpp` schema threading; no other TU changed — anchor `src/vllm/parser/engine/streaming_parser_engine.cpp:143` | `tests/vllm/parser/engine/test_streaming_parser_engine.cpp` (586/586, 8 scenarios) + `test_parser_engine_assembly.cpp` (`..._goldens.inc`): 5038/5038 field-for-field over 30 scenarios (streaming DeltaMessage + one-shot extract_tool_calls + non-streaming parse()) vs vLLM 0.26 assembly — scenarios 27-30 (`CLAIM-C8-ARG-COERCION`) add qwen3 typed-schema coercion (whole+char, days`5`/active`true`/temp`3.14`/tags`[1,2,3]` coerced, unit stays string), qwen3 schema-mismatch (uncoercible `"abc"` left as-is + nullable `"null"`->null), kimi_k2 JSON-native `"5"`->int in extract (converter-less: streaming stays raw, extract coerces — divergence gated); RED-first proven (38 asserts, first boundary `qwen3_typed_schema_wholedelta extract tc[0] arguments`: identity `{"days": "5", …}` vs coerced `{"days": 5, "unit": "celsius", "active": true, "temp": 3.14, "tags": [1, 2, 3]}`); scenarios 10-19 (`CLAIM-ROADMAP-C8-CONFIGS`) add minimax_m2 / glm47_moe / deepseek_v4 / deepseek_v32 / nemotron_v3, scenarios 20-26 (`CLAIM-ROADMAP-C8-CONFIGS-2` 2026-07-27) add gemma4 (explicit + elided channel, whole+char) + inkling (think/tool/trailing-text + non-object-args fallback, whole+char), each whole-delta AND char-by-char; RED-first proven for all seams (32 asserts `_safe_arg_prefix`; 2 asserts glm47 name-`.strip()`; 13 asserts gemma4 `_events_to_delta` at `gemma4_channel_tool_wholedelta delta[0] reasoning`; 5 asserts gemma4 `_preprocess_feed` at `gemma4_elided_channel_wholedelta delta[0] content`; 4 asserts inkling `args_wrapper_keys` at `inkling_nonobject_args_wholedelta extract tc[0] arguments`; 2 asserts inkling `_single_pass_parse` at `inkling_think_tool_text_wholedelta parse content`); serving `tests/vllm/entrypoints/openai/test_serving_chat_stream.cpp` (`..._goldens.inc`): 210/210 chunk-for-chunk SSE parity over the 9 scenarios vs vLLM 0.26 `chat_completion_stream_generator` (role frame + per-delta reasoning/content/tool-call deltas + terminal `tool_calls` flip + name-selected dispatch), RED-first proven (6 CHECKs, first boundary `chunk[1]` reasoning-vs-raw-content); goldens byte-reproduced by `tools/parity/dump_{streaming_parser_engine,parser_engine_assembly,serving_chat_stream}.py` — anchor `tests/vllm/parser/engine/test_streaming_parser_engine.cpp:99` | [specs/streaming-parser-engine.md](specs/streaming-parser-engine.md), [specs/parser-assembly-c8.md](specs/parser-assembly-c8.md) | `ACTIVE` | `CLAIM-ROADMAP-C8-PARSER`, `CLAIM-ROADMAP-C8-ASSEMBLY`, `CLAIM-ROADMAP-C8-SERVING`, `CLAIM-ROADMAP-C8-CONFIGS`, `CLAIM-ROADMAP-C8-CONFIGS-2`, `CLAIM-C8-ARG-COERCION` | -| `TOOLS-PARSER-BREADTH` | Qwen-Coder XML, Mistral, pythonic, and the remaining `--tool-call-parser` dialects. **RECORD BACKFILL 2026-08-13 (#608 W0) — NO code shipped in this move; the surface below landed incrementally under other rows and was never recorded here, the same defect `SAMPLE-REASONING` carried before its own W0.** Landed: **41 accepted parser names over 37 parser families**, the count PINNED by the registry test rather than asserted in prose. Aliases account for the difference: `llama3_json`/`llama4_json` → one class, `qwen3_coder`/`qwen3_xml`/`mimo` → one, `glm45`/`glm47` → one. All three families this row names by title are among them (`qwen3_coder`, `mistral`, `pythonic`). Selection is three-way — explicit `--tool-call-parser NAME`, `auto` template sniffing over a **27-row ordered marker table**, or `none`; an unknown name throws enumerating the whole registry. **Known omission vs the pin, which is why this is `PARTIAL` and not further:** upstream's lazy registry carries **44** names to our 41, and **exactly 5 are upstream-only** — `openai` (`GptOssToolParser`), `inkling` (`InklingEngineToolParser`, ParserEngine-backed), `minimax_m3` (`MinimaxM3ToolParser`, the ONLY Rust-backed one of the five), `cohere_command3` + `cohere_command4` (plain Python, referenced by ZERO recipes, so a usage-driven audit cannot see them). Conversely **2 of our names are absent from upstream's registry at the pin**: `qwen3` (our local alias for the Hermes-JSON Qwen dialect) and `muse_glimmer` (`muse_glimmer` exists in NO vLLM revision in the pin's ancestry — it is decorator-registered at `muse_glimmer_tool_parser.py:183` on the UNMERGED [vllm#51655](https://github.com/vllm-project/vllm/pull/51655), head `075d645af`, the off-pin anchor exception recorded in [porting-inventory.md](porting-inventory.md) §16) — so 44 − 5 = 39 shared, 39 + 2 = 41. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry (post-pin; they arrive with the pin advance). W1 (`openai`, `inkling`, `minimax_m3`), W2 (both Cohere) and W3 (port `ToolParserTestConfig` as a shared harness) remain owed | T1 | registry `vllm/tool_parsers/__init__.py:24-201` (the 44-name `_TOOL_PARSERS_TO_REGISTER` dict) + `:204,210` (`register_lazy_tool_parsers`); manager `vllm/tool_parsers/abstract_tool_parser.py:223,236,318` (`ToolParserManager`, `get_tool_parser`, `register_module`); the 5 upstream-only classes `gptoss_tool_parser.py:17`, `inkling_tool_parser.py:7`, `minimax_m3_tool_parser.py:7`, `cohere_command_tool_parser.py:125,138` | factory `src/vllm/entrypoints/openai/tool_parsers/abstract.cpp:72` (`get_tool_parser`, 41 name branches → 37 classes) + `:269` (`tool_parser_names`, the enumeration the flag's error message is built from); autodetect `src/vllm/entrypoints/openai/tool_parsers/detect.cpp:65` (`kToolParserMarkers`, 27 ordered rows) + `:17` (the `ORDER MATTERS` collision analysis recording which families are deliberately EXPLICIT-ONLY) + `:100` (`DetectToolParser`, first-match with hermes fallback) + `:124` (`ResolveToolParserName`, `auto`/`none`/explicit plus the enumerating throw); per-family parsers under `src/vllm/entrypoints/openai/tool_parsers/` | registry pin `tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp:206` — every enumerated name resolves and every marker-table name is registered — with `:221` pinning `names.size() == 41`, so a factory branch added without listing its name fails the suite instead of silently shipping an unreachable dialect; **38 test files** under `tests/vllm/entrypoints/openai/tool_parsers/` = 34 per-family + 4 cross-cutting (`test_detect.cpp`, `test_tool_parsers.cpp:27` covering hermes+qwen3, `test_tool_choice_grammar.cpp`, `test_structural_tags.cpp`), and `deepseek_v31` shares `test_deepseek.cpp`, so all 37 families are covered. NOT a ported common suite: every file is hand-written per parser, so the floor differs per parser — upstream's `ToolParserTestConfig` is unported (W3) | [tool-parser-breadth.md](specs/tool-parser-breadth.md) | `PARTIAL` | - | +| `TOOLS-PARSER-BREADTH` | Qwen-Coder XML, Mistral, pythonic, and the remaining `--tool-call-parser` dialects. **RECORD BACKFILL 2026-08-13 (#608 W0) — NO code shipped in this move; the surface below landed incrementally under other rows and was never recorded here, the same defect `SAMPLE-REASONING` carried before its own W0.** Landed: **42 accepted parser names over 38 parser families**, the count PINNED by the registry test rather than asserted in prose. Aliases account for the difference: `llama3_json`/`llama4_json` → one class, `qwen3_coder`/`qwen3_xml`/`mimo` → one, `glm45`/`glm47` → one. All three families this row names by title are among them (`qwen3_coder`, `mistral`, `pythonic`). Selection is three-way — explicit `--tool-call-parser NAME`, `auto` template sniffing over a **27-row ordered marker table**, or `none`; an unknown name throws enumerating the whole registry. **Known omission vs the pin, which is why this is `PARTIAL` and not further:** upstream's lazy registry carries **44** names to our 42, and **4 are still upstream-only** — `openai` (`GptOssToolParser`, a declared Harmony STUB that raises on both methods), `minimax_m3` (`MinimaxM3ToolParser`, Rust-crate-backed), `cohere_command3` + `cohere_command4` (shims over the out-of-tree `cohere_melody` package, referenced by ZERO recipes, so a usage-driven audit cannot see them). `inkling` (`InklingEngineToolParser`) LANDED 2026-08-13 under W1 — it was the only one of the five with a grammar in vLLM source to port. Conversely **2 of our names are absent from upstream's registry at the pin**: `qwen3` (our local alias for the Hermes-JSON Qwen dialect) and `muse_glimmer` (`muse_glimmer` exists in NO vLLM revision in the pin's ancestry — it is decorator-registered at `muse_glimmer_tool_parser.py:183` on the UNMERGED [vllm#51655](https://github.com/vllm-project/vllm/pull/51655), head `075d645af`, the off-pin anchor exception recorded in [porting-inventory.md](porting-inventory.md) §16) — so 44 − 4 = 40 shared, 40 + 2 = 42. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry (post-pin; they arrive with the pin advance). W1 is PARTLY landed (`inkling` done; `openai` and `minimax_m3` remain), and W2 (both Cohere) and W3 (port `ToolParserTestConfig` as a shared harness) remain owed | T1 | registry `vllm/tool_parsers/__init__.py:24-201` (the 44-name `_TOOL_PARSERS_TO_REGISTER` dict) + `:204,210` (`register_lazy_tool_parsers`); manager `vllm/tool_parsers/abstract_tool_parser.py:223,236,318` (`ToolParserManager`, `get_tool_parser`, `register_module`); the upstream-only classes `gptoss_tool_parser.py:17`, `minimax_m3_tool_parser.py:7`, `cohere_command_tool_parser.py:125,138`, plus the LANDED `inkling_tool_parser.py:7` over `vllm/parser/engine/adapters.py:128` (`ParserEngineToolAdapter`) and `registered_adapters.py:68-70` | factory `src/vllm/entrypoints/openai/tool_parsers/abstract.cpp:73` (`get_tool_parser`, 42 name branches → 38 classes) + `:281` (`tool_parser_names`, the enumeration the flag's error message is built from); the engine-backed tool face `src/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.{h,cpp}` (`ParserEngineToolAdapter` + `InklingEngineToolParser`); autodetect `src/vllm/entrypoints/openai/tool_parsers/detect.cpp:76` (`kToolParserMarkers`, 27 ordered rows, unchanged by W1 — `inkling` is EXPLICIT-ONLY because it has NO jinja chat template upstream, so this table has nothing to match) + `:17` (the `ORDER MATTERS` collision analysis recording which families are deliberately EXPLICIT-ONLY) + `:111` (`DetectToolParser`, first-match with hermes fallback) + `:135` (`ResolveToolParserName`, `auto`/`none`/explicit plus the enumerating throw); per-family parsers under `src/vllm/entrypoints/openai/tool_parsers/` | registry pin `tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp:206` — every enumerated name resolves and every marker-table name is registered — with `:222` pinning `names.size() == 42`, so a factory branch added without listing its name fails the suite instead of silently shipping an unreachable dialect; **39 test files** under `tests/vllm/entrypoints/openai/tool_parsers/` = 35 per-family + 4 cross-cutting (`test_detect.cpp`, `test_tool_parsers.cpp:27` covering hermes+qwen3, `test_tool_choice_grammar.cpp`, `test_structural_tags.cpp`), and `deepseek_v31` shares `test_deepseek.cpp`, so all 38 families are covered. NOT a ported common suite: every file is hand-written per parser, so the floor differs per parser — upstream's `ToolParserTestConfig` is unported (W3) | [tool-parser-breadth.md](specs/tool-parser-breadth.md) | `PARTIAL` | - | ## Speculative decoding diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 91e30750d..68ac9401f 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -194,10 +194,12 @@ issue is not yet placed. Keyed record: update in place, never append. | [#652](https://github.com/mudler/vllm.cpp/issues/652) | — | `model-matrix.md` prose counters drifted: LTX-2.5 reached the rows and the CI-enforced rollup but none of the five sentences that count them | bug | | [#659](https://github.com/mudler/vllm.cpp/issues/659) | — | LTX-2.5 device select adopts M3a's platform seam but not its companion capability guard: `ltx2_video.cpp` asks `CurrentPlatform().device_type()` and `TryGetBackend(...)` but never `supports_model_architecture`, so a PARTIAL backend (Metal 15/75 ops, Tenstorrent) is handed a queue and dies in a kernel bind where it used to be refused BY NAME (found while reviewing #553 for landing) | bug | | [#660](https://github.com/mudler/vllm.cpp/issues/660) | — | `check-device-leakage`'s `kcuda` bucket is the token grep `\bkCUDA\b`, so `minimax_h3_video.cpp:221-226`'s `static_cast(device)` hardcodes CUDA as enum value 1 and counts as 0. Gate strength plus an enum-ordering hazard; the H3 video lane should ask the same seam `ltx2_video.cpp` now does (found while reviewing #553) | bug | -| [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | **W0 (record backfill) landed 2026-08-13; row `INVENTORIED` → `PARTIAL`, 41 shipped names / 37 families recorded.** Re-derived from the two REGISTRIES rather than from recipe usage: **five** `--tool-call-parser` names are upstream-only at the pin — `openai`, `inkling`, `minimax_m3` (W1, recipe demand) and `cohere_command3`, `cohere_command4` (W2, ZERO recipe uses, so usage-driven audits miss them). **Only `inkling` is PORTABLE from vLLM source**: `minimax_m3` is backed by the Rust crate, `openai` is a declared Harmony stub that raises on both methods, and both Cohere names are shims over the out-of-tree `cohere_melody` package, so W1/W2 each owe a recorded decision before code rather than a text port. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry and arrive with the pin advance, not here. W3 ports upstream's shared `ToolParserTestConfig` harness. The earlier "six missing" framing was usage-derived: it listed `nemotron_json` as portable (it is not registered at the pin) and missed both Cohere entries | feature | +| [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | **W0 (record backfill) landed 2026-08-13; row `INVENTORIED` → `PARTIAL`. W1 then shipped `inkling`, taking the registry to 42 names / 38 families.** Re-derived from the two REGISTRIES rather than from recipe usage: **five** `--tool-call-parser` names are upstream-only at the pin — `openai`, `inkling`, `minimax_m3` (W1, recipe demand) and `cohere_command3`, `cohere_command4` (W2, ZERO recipe uses, so usage-driven audits miss them). **Only `inkling` was PORTABLE from vLLM source, and it LANDED** (a `ParserEngineToolAdapter` over the already-ported Inkling engine; the gap was the registry face, not the grammar). Of the four left: `minimax_m3` is backed by the Rust crate; `openai` is a stub delegating to `vllm/parser/harmony.py`, which IS vLLM source but wraps the out-of-tree `openai_harmony` package (the SGLang secondary-oracle check was run and REFUSED — vLLM implements this path, so the rule does not admit a secondary); both Cohere names are shims over the out-of-tree `cohere_melody` package. So W1-remaining/W2 each owe a recorded decision before code rather than a text port. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry and arrive with the pin advance, not here. W3 ports upstream's shared `ToolParserTestConfig` harness. The earlier "six missing" framing was usage-derived: it listed `nemotron_json` as portable (it is not registered at the pin) and missed both Cohere entries | feature | | [#647](https://github.com/mudler/vllm.cpp/issues/647) | — | Oracle policy had no fallback and no pin concept: five upstreams beyond vLLM are already compared against (vLLM-Omni, SGLang, llama.cpp, `transformers`, tt-forge) with their pins scattered across individual specs or absent entirely. AGENTS.md now admits a named secondary oracle where vLLM implements nothing, `.agents/oracles/.md` pins each one file-per-oracle, and `check-oracle-pins.py` enforces both directions. The gateability debts for `sglang`, `diffusers` and `tt-forge` stay open on this issue | feature | | [#672](https://github.com/mudler/vllm.cpp/issues/672) | `MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation` | MiniMax-Music3: port the text-to-music lane — ~8.6B Qwen3 global LLM (our LANDED architecture at vocab 200000) + 8-codebook RVQ depth decoder + 2.4B fp32 flow-matching DiT + DAC Flow-VAE. Our first music-generating model, and the first row whose primary oracle is not vLLM: absent from the pin, from vLLM `main` and from `vllm-omni`, so it gates against the OPEN diffusers PR #14456 with SGLang-Omni as the e2e cross-check, under the fallback rule added in #647 | feature | | [#649](https://github.com/mudler/vllm.cpp/issues/649) | `TOOLS-CALLING-CORE` | That row's prose still records `tool_parser_names()` 40 / `reasoning_parser_names()` 7; both enumerations have grown since 2026-07-24 and are now **41** (`tool_parsers/abstract.cpp:269`) and **12** (`reasoning_parsers/abstract.cpp:72`). Code and tests are correct — `test_detect.cpp:221` already pins 41 — only the record drifted. Halves belong to two other rows (#608, #605), so it is filed rather than repaired inside #643 (found while implementing #643's review findings) | bug | +| [#703](https://github.com/mudler/vllm.cpp/issues/703) | `SAMPLE-REASONING` | `--reasoning-parser inkling` still aborts startup while `--tool-call-parser inkling` resolves, an asymmetry #608 W1 created: upstream registers `inkling` in BOTH registries (`vllm/reasoning/__init__.py:131` -> `InklingParserReasoningAdapter`, the reasoning half of the same `make_adapters(InklingParser)` call whose tool half landed), and `reasoning_parser_names()` has no row for it. Inkling is a thinking dialect, so the reasoning flag is exactly the one an Inkling recipe passes (2 of 157 official recipes do). The fix belongs to #605, whose table already lists `inkling`; filed separately so the asymmetry is visible from the row that created it | bug | +| [#704](https://github.com/mudler/vllm.cpp/issues/704) | `TOOLS-PARSER-BREADTH` | `README.md` states the tool-parser registry size twice (`:81`, `:219`) and both were stale by two waves: 36 families / 40 names against an actual 38 / 42, re-derived from `tool_parser_names()` (42 entries) and `get_tool_parser` (42 branches over 38 distinct classes). Already wrong at `43a6c5518` (36/40 vs 37/41), so it missed `muse_glimmer` before #608 W1 too. Neither `check-readme-structure.py` nor `check-public-doc-tables.py` cross-checks a prose count against the registry. #649 covers only the engine-matrix row, a different surface. FIXED IN FLOW in #683's review repair | bug | ## Top-level portfolio @@ -608,7 +610,7 @@ degraded run — it is no run at all. | Axis | State | Issue | |---|---|---| -| `--tool-call-parser` | 41 names, **82/90 recipe uses (91%)** — the healthy axis | [#608](https://github.com/mudler/vllm.cpp/issues/608) for the last 8 | +| `--tool-call-parser` | 42 names, **84/90 recipe uses (93%)** — the healthy axis; `inkling` (2 uses) landed 2026-08-13 under #608 W1 | [#608](https://github.com/mudler/vllm.cpp/issues/608) for the last 6 | | `--reasoning-parser` | 10 of 28 names, **15/76 uses (20%)**; `qwen3` (18) rejected on our own gate models | [#605](https://github.com/mudler/vllm.cpp/issues/605) | | `--enable-auto-tool-choice`, `--trust-remote-code` | no-ops for us, yet **abort startup** on 89 and 82 recipes | [#606](https://github.com/mudler/vllm.cpp/issues/606) | | `--language-model-only` | absent; 43 recipes use it. It zeroes every modality limit — the freed encoder VRAM **and** the refusal of every mm request both follow from that | [#607](https://github.com/mudler/vllm.cpp/issues/607) | diff --git a/.agents/specs/tool-parser-breadth.md b/.agents/specs/tool-parser-breadth.md index 5d5a05885..25e59f691 100644 --- a/.agents/specs/tool-parser-breadth.md +++ b/.agents/specs/tool-parser-breadth.md @@ -51,7 +51,13 @@ at all**. Verified by reading the pinned checkout at capability declaration via HarmonyParser.tool_parser_cls." **Both** methods raise — `:31` and `:45` — with `NotImplementedError("GptOssToolParser is a stub. Use HarmonyParser for tool parsing.")`. Registering the name is not - mirroring the behaviour; the behaviour lives in Harmony. + mirroring the behaviour; the behaviour lives in Harmony. **W1 correction: + Harmony is IN vLLM** — `vllm/parser/harmony.py` (358 lines, + `HarmonyParser(DelegatingParser)`) with `tests/parser/test_harmony.py` — so + this is a delegation inside vLLM, not an absence of it. What is out-of-tree is + only the `openai_harmony` package the wrapper drives, which makes this a + W2-shaped grammar decision rather than a missing parser. See the W1 `openai` + bullet for the secondary-oracle check this triggered and why it was refused. - **Both Cohere classes are shims over `cohere_melody`.** `vllm/tool_parsers/cohere_command_tool_parser.py:6-13` imports `PyFilter`/`PyFilterOptions` from that out-of-tree package and raises a hard @@ -86,10 +92,18 @@ has one (`test_minimax_m3_tool_parser.py`). There is no `gptoss`, `inkling`, or instantiates** via `ToolParserTestConfig` (`common_tests.py:17`), not a sweep over `ToolParserManager`. A parser with no test file gets **zero** coverage from it. -So for four of five, "port its tests in the same change" has nothing to port. The -test is ours to author, its fidelity rests entirely on reading the parser source, -and it is recorded as from-scratch in `porting-inventory.md` §9. Say so on the -row rather than implying an inherited test. +**Corrected 2026-08-13 by W1: that is true of `tests/tool_parsers/` and false of +the repository.** `inkling`'s test lives in the OTHER place, next to the engine +it is built on — `tests/parser/engine/test_inkling.py` @ `5559679` — because +upstream's `InklingEngineToolParser` is a bare `make_adapters(InklingParser)` +subclass with no behaviour of its own to test. It ported, so `inkling` owed no +authored test after all. Searching one test directory is not searching upstream; +`gptoss` and `cohere` were re-checked the same way and genuinely have none. + +So for three of five, "port its tests in the same change" has nothing to port. +The test is ours to author, its fidelity rests entirely on reading the parser +source, and it is recorded as from-scratch in `porting-inventory.md` §9. Say so +on the row rather than implying an inherited test. ### The harness is worth porting on its own @@ -113,8 +127,15 @@ is the highest-value item here and deliberately does not block W1. CI-enforced). No new parser. - **W1 — the three with recipe demand**, which are three different jobs, not one batch (see "Only one of the five is a port" above): - - `inkling` — the ordinary one. A ParserEngine adapter port over the - `inkling` engine config we already carry. Test authored from scratch. + - `inkling` — the ordinary one. **LANDED 2026-08-13; see `## Now`.** A + ParserEngine adapter port over the `inkling` engine config we already carry. + Two things this bullet had wrong, both corrected by doing it: the work was + smaller than "port a parser" (the engine, its config and its arg carver were + already ported and golden-gated; only the tool-parser REGISTRY face was + missing, so `--tool-call-parser inkling` threw at startup), and the test was + **not** authored from scratch — upstream has + `tests/parser/engine/test_inkling.py`, which is the executable description of + the dialect and ports directly. - `minimax_m3` — the grammar is in the Rust crate, so the implementation is written from the wire format its docstring describes and is recorded from-scratch in `porting-inventory.md` §9. Its upstream test @@ -127,6 +148,43 @@ is the highest-value item here and deliberately does not block W1. the missing piece, per AGENTS.md's "an arm that is not implemented is refused with a message naming the missing piece and recorded as owed". Do **not** plan it as an ordinary text parser. + + **SECONDARY-ORACLE CHECK, run under AGENTS.md §"When vLLM has no + implementation" and answered NO (W1, 2026-08-13).** The question was whether + SGLang — a registered oracle (id `sglang`, + [`.agents/oracles/sglang.md`](../oracles/sglang.md), pin + `f63458b5beaceabbd9d749b9fc956370e1b649e6` / `v0.5.15`, pinned 2026-07-27; + the local checkout at `/home/mudler/_git/sglang` is clean AT that SHA) — + should be the source for gpt-oss tool parsing, since it carries + `python/sglang/srt/parser/harmony_parser.py` and + `test/registered/unit/parser/test_harmony_parser.py`. **It should not, and + reaching for it would have been a rule violation.** That section admits a + secondary oracle only "where it implements nothing" and only for "a path + vLLM cannot produce at all". vLLM produces this one: `vllm/parser/harmony.py` + is a 358-line vLLM-owned `HarmonyParser(DelegatingParser)` — it is what + `GptOssToolParser`'s docstring defers TO — and it has its own upstream test, + `tests/parser/test_harmony.py`. The stub is a delegation inside vLLM, not an + absence of vLLM. **`openai` therefore mirrors `vllm/parser/harmony.py`, and + SGLang is not consulted.** + + Two things the check did establish, and they are the real W1-remaining + scoping: + 1. **The residual gap is the same SHAPE as Cohere's, not a missing parser.** + `HarmonyParser` wraps the third-party `openai_harmony` package + (`get_streamable_parser_for_assistant()` / `StreamableParser`), so the + `<|channel|>` token grammar itself is out-of-tree, exactly as + `cohere_melody` is for W2. vLLM's glue ports; the grammar under it is a + from-scratch or vendor decision this spec still owes — and it is a W2-class + decision, not the "register the name with an explicit refusal" the bullet + above assumed. + 2. **`openai` is a HARMONY-SEAM row, which this spec already said is a + different row.** It is materially larger than `inkling` was, which is why + W1 shipped `inkling` alone. + + Had the answer been yes, SGLang would still have been **source-only**: its + oracle file records `gateable = no` (evidence: #647) — no SGLang run has ever + been recorded on this project's hardware — so no gate may be taken by + executing it. Recorded here so the next agent does not re-run the check. - **W2 — complete the mirror**: `cohere_command3`, `cohere_command4` — **also not a text port.** All the grammar lives in `cohere_melody`, an out-of-tree package we do not vendor and upstream does not ship; there is nothing in vLLM @@ -188,7 +246,13 @@ new test did. ## Now -`PARTIAL` — **W0 landed 2026-08-13 (#608).** The row now records the shipped +`PARTIAL` — **W0 and the `inkling` half of W1 have landed (2026-08-13, #608).** +The live position is **42 accepted names over 38 families**; the W1 subsection +below is the current front, and `openai` / `minimax_m3` / both Cohere names +remain owed, which is what keeps the row `PARTIAL`. The two paragraphs that +follow are W0's record, kept for provenance. + +**W0 landed 2026-08-13 (#608).** The row now records the shipped surface (41 names / 37 families, the factory and `tool_parser_names()` anchors, the 27-row autodetect table, the 38 test files and the count-pinning registry test), and moved `INVENTORIED` → `PARTIAL`; the engine-matrix summary counts and @@ -235,5 +299,244 @@ on both methods) and the Cohere pair are shims over the out-of-tree source**. See "Only one of the five is a port" above for the read anchors; W1 and W2 are scoped to that reading, not to the "plain Python" one. -Next: W1 (`openai`, `inkling`, `minimax_m3`), then W2 (both Cohere), then W3 -(the `ToolParserTestConfig` harness) — each its own change and its own review. +**W1 in turn corrects one word of that.** "`openai` … delegate[s] outside vLLM's +source entirely" is too strong: the delegation target `vllm/parser/harmony.py` +IS vLLM source and is tested upstream; only the `openai_harmony` package under it +is out-of-tree. The conclusion the paragraph draws survives — of the five, only +`inkling` was a straight port, and it is the one W1 shipped. + +### W1 · `inkling` LANDED 2026-08-13 (#608) + +`--tool-call-parser inkling` now resolves. **41 → 42 accepted names, 37 → 38 +families**, re-derived from `abstract.cpp` rather than incremented (42 names, 38 +distinct factory classes, 27 marker rows unchanged); `test_detect.cpp:222`, +`docs/USAGE.md:1126`, `docs/FEATURES.md:232`, `docs/STATUS.md` and the +engine-matrix row moved with it. + +**Correction (2026-08-14, review repair of #683).** An earlier version of this +paragraph said "`README.md` and `docs/FEATURES.md` carry no parser count, so +neither is owed." Both do. `docs/FEATURES.md:232` carries `Tool-call parsers | ✅ +N families` and this change moved it 37 → 38 — the sentence contradicted its own +diff. `README.md` carries the count TWICE (`:81`, `:219`) and both were ALREADY stale +before W1: at `43a6c5518` they read 36 families / 40 names against a true 37/41, +so README had missed the `muse_glimmer` addition too, and W1 widens the drift to +38/42. The true values, re-derived from `abstract.cpp` rather than incremented, +are **38 families / 42 accepted names** (42 `tool_parser_names()` entries; 42 +`name ==` branches over 38 distinct `std::make_unique` classes). + +**README is NOT corrected here, and the reason is a gate, not a preference.** +`check-doc-checkpoint.py --staged` refuses any README edit in a change that does +not touch a landing source (`.agents/mission.md`, `CMakeLists.txt`, the three +`benchmarks/demo/*.json`, `examples/{cli,server}/main.cpp`) — "Co-edited public +projections can NEVER justify README churn", a rule the checker's own comment +calls deliberate and directly tested. This change touches none of them, and +weakening a checker to make a transition pass is forbidden. So the correction is +filed as [#704](https://github.com/mudler/vllm.cpp/issues/704) and linked from +the `roadmap_v1.md` issue table, which is the other option the review named. +#704 carries one thing worth knowing before someone picks it up: the same rule +refuses a README-ONLY fix too, so closing it needs either a change that +legitimately moves a landing source or an argued exception in its own commit +message. + +Neither `check-readme-structure.py` nor `check-public-doc-tables.py` +cross-checks a prose count against the registry, which is why the drift survived +two waves; [#649](https://github.com/mudler/vllm.cpp/issues/649) covers only the +`TOOLS-CALLING-CORE` engine-matrix row, a different surface. + +**The gap was smaller and stranger than the spec assumed.** The Inkling engine +was ALREADY fully ported — `src/vllm/parser/inkling.cpp`, `inkling_config()` with +the JSON-span arg carver in `engine/configs.cpp`, dispatched by +`parser::get_parser_engine("inkling")`, and golden-gated against the pinned +oracle in `test_parser_engine_assembly` (`inkling_think_tool_text_*`, +`inkling_nonobject_args_*`). What was missing was only the tool-parser REGISTRY +face, so `ResolveToolParserName("inkling", …)` threw at startup and a fully +ported, oracle-gated dialect was unreachable from the flag. + +Landed: + +- `tool_parsers/parser_engine_adapter.{h,cpp}` — `ParserEngineToolAdapter` + (`vllm/parser/engine/adapters.py:128`, the TOOL half of `make_adapters`, twin + of the reasoning adapter we already carried) + `InklingEngineToolParser` + (`vllm/tool_parsers/inkling_tool_parser.py:7`). +- The factory branch and `tool_parser_names()` entry, in the same change, as + `abstract.cpp`'s own comment demands. +- `ParserRequestFromChatCompletion` moved out of `serving_chat.cpp`'s anonymous + namespace to sit next to `ParserRequest`, so the adapter and the serving path + share ONE request projection instead of two copies of it. + +**`inkling` is EXPLICIT-ONLY in `detect.cpp` — no autodetect row, and for a +reason no other row has.** Every other EXPLICIT-ONLY family is excluded for +marker collision; Inkling is excluded because there is nothing to sniff. That +table matches a CHAT TEMPLATE string, and Inkling has no jinja template at the +pin — rendering is `vllm/renderers/inkling_encoding.py` (mirrored by the Rust +`rust/src/chat/src/renderer/inkling/`), and `examples/` carries no +`tool_chat_template_inkling.jinja`. Its `<|content_invoke_tool_json|>` marker +collides with nothing, so a row would LOOK harmless while being unreachable +through the only input the function gets and claiming a template-stability we +cannot demonstrate. + +**No structural-tag row is owed.** `inkling_tool_parser.py:10-11` sets +`structural_tag_model = None` / `supports_required_and_named = False`; our +`ToolChoiceStructuralTagSpecFor` already returns nullopt for every mode of an +unmapped family, which is that behaviour exactly. Since the 2026-08-14 repair +that equivalence is GATED, not just argued: the ported `test_adapters_resolve` +checks nullopt for auto, required and named. + +**The test PORTS; it was not authored.** This spec said "test authored from +scratch" on the assumption that a missing `tests/tool_parsers/ +test_inkling_tool_parser.py` meant no upstream test. There is one, in the other +place: `tests/parser/engine/test_inkling.py` @ `5559679`. **19 of its tool-facing +cases port** into `tests/vllm/entrypoints/openai/tool_parsers/test_inkling.cpp` +keeping their upstream names verbatim, with four documented harness adaptations +(a `<|message_model|>` prefix reproduces upstream's MESSAGE_HEADER initial state +on the CONTENT-seeded tool adapter; the streaming cases use upstream's +`_stream_text_only` character-chunk harness because our ToolParser seam is +text-only, which is a strictly stronger split; a registered CLASS assertion +becomes an instance `dynamic_cast`; and the `tool_choice="none"` case drops the +reasoning third of its assertion, which this seam does not return). Its +`TestArgConverter` class and the token-id/reasoning cases are deliberately NOT +re-ported: they gate the engine layer, which the assembly goldens already gate +against the oracle. + +**Correction (2026-08-14, review repair of #683).** The first version of this +paragraph said **15**, and both it and the commit, PR body and test-file header +claimed upstream "drives `InklingParser` directly and never constructs the +adapter". That was FALSE, and it hid two portable cases. `test_inkling.py:487 +class TestRegisteredAdapters` resolves through the registry and constructs the +adapter: + +- `:488 test_adapters_resolve` — PORTED. Upstream asserts + `tool_cls._parser_engine_cls is InklingParser` and + `tool_cls.supports_required_and_named is False`. Adaptation 3: our registry + returns an instance, so the engine binding is a `dynamic_cast` to + `InklingEngineToolParser`, whose ctor is `get_parser_engine("inkling")` and + admits no other engine. `supports_required_and_named = False` is asserted + through the surface that flag controls here — + `ToolChoiceStructuralTagSpecFor("inkling", …)` must be nullopt for auto, + required AND named. Its reasoning half + (`ReasoningParserManager.get_reasoning_parser("inkling")`) is DECLINED: we have + no such registry row (see "Still owed"). +- `:498 test_adapter_round_trip` — PORTED verbatim. + +`TestToolCallFiltering` (`:430`) was likewise never assessed. Two of its three +cases port and now do: + +- `:465 test_tool_choice_none_non_streaming` — PORTED with adaptation 4 (the + reasoning third of the assertion is dropped; content and tool suppression are + kept verbatim). It is the case that exercises the `tool_choice` field of the + `ParserRequestFromChatCompletion` projection this wave MOVED. +- `:477 test_tool_choice_none_streaming` — PORTED verbatim. +- `:436 test_skip_tool_parsing_round_trip` — **DECLINED, with reason.** It sets + `skip_tool_parsing = True` on one `InklingParser`, calls `extract_reasoning`, + and re-extracts from the returned content with a second parser. Our `ToolParser` + ABC exposes neither a skip-tool-parsing setter nor `extract_reasoning`, and the + reasoning half has no registry face at all, so porting it would mean driving + `parser::engine::ParserEngine` directly — the engine layer this file + deliberately does not re-gate. + +The review's two coverage gaps: one CLOSED, one MEASURED AND STILL OPEN. + +- **Closed.** `StreamTextOnly` now calls `finish_streaming()` and appends its + delta, exactly as upstream's `_stream_text_only` does. The port had omitted it + and compensated by appending `<|end_message|>` to the authored case's input; + the compensation is gone and the harness matches upstream. +- **Still open, and now measured.** That gives + `ParserEngineToolAdapter::finish_streaming()` its only CALLER — it has none in + `src/`, `include/` or `examples/`, and will have none while `MakeToolParser` + routes engine-backed names away from this adapter + (`serving_chat.cpp:546-548`) — but **not a guarantee**. Mutating the body to + `return std::nullopt` leaves all 22 cases GREEN. A streaming twin of + `test_text_after_tool_call` was authored to try to move it and did not: the + trailing text is not deferred past the last delta on this path, so that case + was DELETED rather than shipped with a false rationale. The method ships + functionally ungated, and the header and the test harness both say so. An + unmoved mutation is a measurement, not a licence to claim coverage. + +**3 of the 22 cases are AUTHORED**, each saying so at its site: + +- "the registry name resolves and is enumerated" and "EXPLICIT-ONLY — no + autodetect row" gate OUR packaging surface, which has no upstream analogue to + port: upstream's registry is a lazy dict and it has no chat-template marker + table at all, so `DetectToolParser` is an ORIGINAL component. +- "the tool adapter seeds the engine in CONTENT state" gates UPSTREAM behaviour + (`adapters.py:178`) that upstream's own suite never exercises. The corrected + reason: upstream DOES construct the adapter (`test_adapter_round_trip`), but + only through the NON-streaming `extract_tool_calls`, which delegates to + `extract_tool_calls_from_content` with no seed (`adapters.py:158`); the CONTENT + seed lives exclusively in `extract_tool_calls_streaming` (`adapters.py:178`), + which no upstream case reaches. It exists *because* a mutation survived without + it. + +These are test-side authorship on an otherwise straight port, recorded here and +at each case rather than as a new numbered entry in `porting-inventory.md` §9 — +that section enumerates forced STRUCTURAL deviations of the port itself, and this +change has none. The fresh review of #683 read §9 the same way and agreed, so the +judgement stands; what it flagged was a test COMMENT that asserted a §9 entry had +been written when none had. That sentence is deleted. + +Mutation results (each restored byte-for-byte, verified by sha256): + +| Mutation | Caught by | +|---|---| +| `InklingEngineToolParser` builds a base `ParserEngine` over `inkling_config()` instead of `InklingParser` (drops the trailing-text flush hook, `inkling.py:376`) | NEW `test_inkling.cpp:385` (`test_text_after_tool_call`, case at `:373`) | +| drop `"inkling"` from `tool_parser_names()`, keep the factory branch | NEW `test_inkling.cpp:243` (case at `:233`), AND the pre-existing count pin `test_detect.cpp:222` — the existing guard catches it too | +| drop the adapter's `initialize_streaming(CONTENT)` seed | **SURVIVED** the first suite; caught only after adding the authored case, at `test_inkling.cpp:284` (case at `:256`) | +| `inkling_arg_converter` returns the raw `{"name":…,"args":…}` wrapper | NEW `test_inkling.cpp` (5 cases / 14 assertions) AND the pre-existing `test_parser_engine_assembly` goldens (3 cases / 33 assertions) — the existing gate catches this one first, since the carver is engine-layer | + +**Mutations for the cases the 2026-08-14 repair added** (aarch64, each restored +by byte copy and sha256-verified; the restore is `cp` + `touch`, never `cp -p` — +a preserved mtime makes ninja skip the relink and the NEXT mutation then runs +against the PREVIOUS one's object, which is how the first attempt at this table +produced four identical-looking failures): + +| Mutation | Result | +|---|---| +| `get_tool_parser("inkling")` returns `HermesToolParser` instead of `InklingEngineToolParser` (breaks `_parser_engine_cls is InklingParser`) | **CAUGHT** — `test_adapters_resolve` at `:539`/`:540`, `test_adapter_round_trip` at `:575`/`:576`, and the 18 `MakeParser()` cases at `:121`. 2 passed / 20 failed | +| `ToolChoiceStructuralTagSpecFor` gives `inkling` the hermes spec (i.e. `supports_required_and_named = True`) | **CAUGHT** — `test_adapters_resolve` at `:557`, all three modes | +| `ParserRequestFromChatCompletion` drops `tool_choice` and hardcodes `"auto"` (the projection this wave MOVED) | **CAUGHT** — `test_tool_choice_none_non_streaming` at `:510`/`:511` and `test_tool_choice_none_streaming` at `:522`. 20 passed / 2 failed | +| `ParserEngineToolAdapter::finish_streaming()` returns `std::nullopt` | **SURVIVED — 22/22 still green.** Recorded, not hidden: see "Still open, and now measured" above | + +### Owed by W1's landing, but NOT this row's to fix + +Two gaps that `inkling`'s registration made real. Both are recorded here so they +are visible from the row that created them, and both are owned elsewhere. + +- **`--reasoning-parser inkling` still throws + ([#703](https://github.com/mudler/vllm.cpp/issues/703)).** Upstream registers + `inkling` in BOTH registries — `vllm/reasoning/__init__.py:131` names + `InklingParserReasoningAdapter`, the reasoning half of the SAME + `make_adapters(InklingParser)` call (`registered_adapters.py:67-70`) whose tool + half W1 landed. `reasoning_parser_names()` has no `inkling` row, so a user + following an Inkling recipe now gets a tool parser that resolves and a hard + startup abort on the reasoning flag in the same command line. Before W1 both + flags refused it, which was at least consistent. The FIX belongs to + `SAMPLE-REASONING` ([#605](https://github.com/mudler/vllm.cpp/issues/605)), + whose table already lists `inkling`; it is out of scope here and deliberately + not implemented. It is also why the reasoning half of the ported + `test_adapters_resolve` had to be declined. +- **Engine-backed names run with `skip_special_tokens=true` + ([#695](https://github.com/mudler/vllm.cpp/issues/695)).** `adjust_request` + (`adapters.py:151`) is dropped, and the shared `ToolParser` seam has no + dispatch site for it at all — `KimiK2ToolParser::adjust_request` + (`kimi_k2.cpp:87`) has no callers either, the same pre-existing seam gap + `reasoning_parsers/muse_glimmer.h` records. It is MATERIAL for Inkling, whose + entire grammar is special tokens: `skip_special_tokens` defaults `true` + (`protocol.h:240`/`:461`), is forwarded verbatim by `to_sampling_params` + (`protocol.cpp:583`) and honoured at `v1/engine/detokenizer.cpp:68`, so at + server defaults the markers are stripped before the parser runs. The cases in + `test_inkling.cpp` pass because they feed the adapter marker text directly; + they do not traverse the detokenizer, and the header now says so. Closing it is + a seam change owned by no parser row. What IS fixed here is the silence: + `docs/USAGE.md:1126` now tells a user the one thing they can do about it today, + which is send `"skip_special_tokens": false` on the request + (`protocol.cpp:484` parses it), rather than leaving them to discover a dialect + that resolves and then returns no tool calls. + +### Still owed on this row + +Next: the rest of W1 — `openai` (a `vllm/parser/harmony.py` port gated by +`tests/parser/test_harmony.py`, with the out-of-tree `openai_harmony` grammar +under it as a W2-shaped decision; the SGLang secondary-oracle check was run and +answered NO, see the W1 bullet) and `minimax_m3` (from the wire format, its +upstream test the fidelity gate) — then W2 (both Cohere), then W3 (the +`ToolParserTestConfig` harness). Each its own change and its own review. diff --git a/CMakeLists.txt b/CMakeLists.txt index 90923956a..96aa8d767 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1103,6 +1103,7 @@ add_library(vllm STATIC src/vllm/entrypoints/openai/tool_parsers/gemma4.cpp src/vllm/entrypoints/openai/tool_parsers/seed_oss.cpp src/vllm/entrypoints/openai/tool_parsers/muse_glimmer.cpp + src/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.cpp src/vllm/entrypoints/openai/reasoning_parsers/muse_glimmer.cpp src/vllm/parser/engine/incremental_lexer.cpp src/vllm/parser/engine/token_id_scanner.cpp diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 26317660c..7ca95a30f 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -229,7 +229,7 @@ the only ☐ in our column here. | GBNF grammars | ✅ | ☐ | ☐ | ✅ | | xgrammar backend | ✅ | ✅ | ✅ | ☐ | | Jump-forward decoding | ✅ opt-in | ☐ | ✅ | ☐ | -| Tool-call parsers | ✅ 37 families | ✅ | ✅ | ◐ | +| Tool-call parsers | ✅ 38 families | ✅ | ✅ | ◐ | | Reasoning-content parsers | ✅ 10 | ✅ | ✅ | ☐ | | Muse Glimmer ATEM parsers (`muse_glimmer`) | ◐ UNIT-GATED ON STRINGS; **CHANNEL SCOPING FAILS AT SERVER DEFAULTS**: no `adjust_request` seam, so `skip_special_tokens: true` strips the framing. OPEN GAP, [spec](../.agents/specs/muse-glimmer.md) §6.7 | ✅ | ☐ | ☐ | | Custom logits processors | ◐ CPU-verified | ✅ | ✅ | ☐ | diff --git a/docs/STATUS.md b/docs/STATUS.md index c93061da2..1b79a1dd3 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -162,7 +162,7 @@ token-for-token correctness against the pinned oracle. | KV-cache events (for external routers) | Built, off by default; generation and payload gated, live ZMQ transport deferred | Block store/remove/clear events (`BlockStored`/`BlockRemoved`/`AllBlocksCleared`) emitted at the prefix-cache sites when enabled, with a `msgpack` payload byte-identical to vLLM's `msgspec` encoding. Behind a publisher seam faithful to `--kv-events-config`; the live ZMQ transport is not wired yet. Off by default, so the prefix-cache path is byte-identical. | | Sampling | Supported | Greedy, temperature, top-k/p, min-p, presence/frequency/repetition penalties, seed, stop/stop_token_ids, min_tokens, logit_bias, allowed_token_ids, bad_words, in vLLM's exact order. Custom logits processors are supported through a per-request C-ABI callback (`vllm_logits_processor`, ABI v8), absent by default (byte-identical). Sample logprobs are emitted end-to-end for `/v1/completions` and `/v1/chat/completions` (`logprobs`/`top_logprobs`). A request may instead score an EXPLICIT set of vocab ids (`logprob_token_ids`, generative scoring): exactly those ids plus the sampled token, whose rank is still over the full vocab, and no full-vocab sort — library surface only, the OpenAI request field is not wired yet. Parallel sampling (`n>1`) is supported, returned as n indexed `choices` (`n==1` byte-identical). Beam search is supported through the `BeamSearch` driver — an outer engine loop that scores beams by cumulative logprob with a length penalty and returns the top `beam_width` sequences (deterministic, token-exact vs vLLM's algorithm); it is wired on the OpenAI `use_beam_search` request field for `/v1/completions` and `/v1/chat/completions` over BOTH the synchronous engine AND the production AsyncLLM HTTP server (an async `BeamSearchAsync` driver, token-identical); the C-ABI beam params and streaming beam are not exposed yet; per-beam concurrent stepping is a named residual — beams are stepped sequentially). `best_of` is supported on both endpoints (`best_of==n` is the default no-op). `prompt_logprobs` is computed and returned on `RequestOutput`; the OpenAI `echo` serialization is not wired yet. All four `logprobs_mode` values work (`processed_*` shows the top-k mask); in-library only. | | Structured output | Supported (subset), engine-enforced; xgrammar backend W1 (CPU, not yet production-wired) | JSON schema, JSON object, regex, choice, GBNF grammar. Constrained decoding runs in the production engine (native grammar backend, per-step logits bitmask) and is reachable from OpenAI `response_format` and the C ABI (ABI v2 `structured_*` fields). A second, xgrammar-faithful backend (`XgrammarStructuredOutputBackend`, vLLM's default `auto`) is built behind the same seam: it reuses the native pushdown-FSM/trie matcher (xgrammar's own algorithm) and adds the xgrammar JSON-schema→EBNF converter that preserves property declaration order + `any_whitespace` + the `basic_*` grammar, closing the key-order/whitespace/exotic-schema parity gap. CPU-gated (`test_backend_xgrammar` 6/6, RED-first); production wiring + the `auto` fallback + GPU oracle parity are the named residuals (see `.agents/specs/xgrammar-backend.md`) | -| Tool-call parsing (`TOOLS-PARSER-BREADTH`, PARTIAL) | 37 parser families / 41 accepted names, streaming | 39 of upstream's 44 registered names at the pin, plus 2 that upstream's registry does not carry there (`qwen3`, our alias for the Hermes-JSON Qwen dialect, and `muse_glimmer`) = 41 accepted names; pure-text parsers ported 1:1, the engine-backed families reimplemented from their wire formats. FIVE upstream names are NOT ported, and only ONE of them is a port we could make from vLLM source: `inkling`, a ParserEngine adapter. `minimax_m3` is backed by upstream's Rust crate; `openai` (`GptOssToolParser`) is a declared Harmony stub that raises on both methods; `cohere_command3` and `cohere_command4` are shims over the out-of-tree `cohere_melody` package. So for four of the five there is no grammar in vLLM source to mirror — tracked as W1/W2 in `.agents/specs/tool-parser-breadth.md`, which records the decision each one owes. Upstream's shared `ToolParserTestConfig` harness is also unported, so the per-parser test floor is set file by file rather than enforced (W3). Selection via `--tool-call-parser` (server), `tool_parser` (C ABI), or template auto-detection over a 27-row ordered marker table; native-syntax forced tool_choice where expressible. Tables: docs/BENCHMARKS.md | +| Tool-call parsing (`TOOLS-PARSER-BREADTH`, PARTIAL) | 38 parser families / 42 accepted names, streaming | 40 of upstream's 44 registered names at the pin, plus 2 that upstream's registry does not carry there (`qwen3`, our alias for the Hermes-JSON Qwen dialect, and `muse_glimmer`) = 42 accepted names; pure-text parsers ported 1:1, the engine-backed families reimplemented from their wire formats. `inkling` landed 2026-08-13 (W1) as a `ParserEngineToolAdapter` over the already-ported Inkling ParserEngine — the only one of the five upstream-only names that was a port from vLLM source at all. FOUR remain NOT ported, and none of them has its GRAMMAR in vLLM source: `minimax_m3` is backed by upstream's Rust crate; `openai` (`GptOssToolParser`) is a stub that raises on both methods and delegates to `vllm/parser/harmony.py`, which IS vLLM source (and has its own test) but itself wraps the out-of-tree `openai_harmony` package; `cohere_command3` and `cohere_command4` are shims over the out-of-tree `cohere_melody` package — tracked as W1-remaining/W2 in `.agents/specs/tool-parser-breadth.md`, which records the decision each one owes. Upstream's shared `ToolParserTestConfig` harness is also unported, so the per-parser test floor is set file by file rather than enforced (W3). Selection via `--tool-call-parser` (server), `tool_parser` (C ABI), or template auto-detection over a 27-row ordered marker table (`inkling` is EXPLICIT-ONLY: it has no jinja chat template upstream, so there is nothing to sniff); native-syntax forced tool_choice where expressible. Tables: docs/BENCHMARKS.md | | Reasoning parsing (`SAMPLE-REASONING`, ACTIVE, partial coverage) | 12 names, streaming | think_auto (auto-detect default: content unless markers appear), deepseek_r1, deepseek_v3 (passthrough) / holo2 (thinking→R1), mistral ([THINK]), minimax_m2 (+append_think), step3, olmo3, muse_glimmer, and qwen3 / mimo - reasoning split engine-side BEFORE tool parsing, streamed as `reasoning` deltas in the chat chunks. qwen3+mimo are the first ENGINE-BACKED adapter (one reasoning face over the shared `src/vllm/parser/engine/` parser, so `` ends reasoning with no ``); the rest are text parsers. Coverage: 12 of upstream's ~28 registered names (remaining engine-backed adapters + text families tracked as W3/W2 in specs/reasoning-parsers.md); each ported parser doctest-gated vs its tests/reasoning case | | Unified streaming parser engine | Core, assembly, serving-SSE dispatch landed, gated; all 10 engine-backed families ported (family parity closed); JSON-schema tool-arg type coercion landed | The vLLM 0.26 declarative `parser/engine/` (shared state machine plus all 10 configs: qwen3, seed_oss, kimi_k2, minimax_m2, glm47_moe, deepseek_v4/v32, nemotron_v3, gemma4, inkling) and assembly layer, gated field-for-field vs vLLM 0.26. An engine-backed `--tool-call-parser` name drives the live chat SSE chunks, off by default. When a request's tools declare typed parameters, the assembled tool-call arguments are coerced to the declared JSON types (int/number/bool/string/array/null) 1:1 with vLLM `_fix_arg_types`, in both streaming and one-shot; no schema means the arguments pass through as strings unchanged. Details: .agents/specs/parser-assembly-c8.md | | OpenAI server | Subset; v0.0.2 publishes eight server bundles; Windows v0.0.3-pre.1 pending | Completion/chat (SSE), models, health/version/ping, metrics, tokenize/detokenize, tokenizer/server info, prefix-cache reset, abort, and Sora-shaped video creation/content. Tokenizer info and abort are flag-gated; cache reset lacks live async backing. Details: docs/USAGE.md | diff --git a/docs/USAGE.md b/docs/USAGE.md index 30c91c50a..7896638f7 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1123,7 +1123,7 @@ a stop token early. | `--enable-radix-attention` / `--disable-radix-attention` | model default | SGLang-named alias for the prefix-cache toggle | | `--enable-jump-forward` | off | Jump-forward decoding for structured output (token-unique subset) | | `--enable-force-include-usage` | off | Force the usage block in responses | -| `--tool-call-parser ` | `hermes` | Tool-call dialect (41 names over 37 families). `auto` detects from the chat template, `none` disables. For `gemma4`, OpenAI chat uses the text-seam parser (wrapped `<\|tool_call>` **or** bare `call:NAME{ARGS}`) so free-form / detokenized tool bodies still become `tool_calls` | +| `--tool-call-parser ` | `hermes` | Tool-call dialect (42 names over 38 families). `auto` detects from the chat template, `none` disables. For `gemma4`, OpenAI chat uses the text-seam parser (wrapped `<\|tool_call>` **or** bare `call:NAME{ARGS}`) so free-form / detokenized tool bodies still become `tool_calls`. **`inkling` needs `"skip_special_tokens": false` on the request today** — its whole grammar is special tokens and we have no `adjust_request` seam to force the flag off for you, so at the `true` default the detokenizer strips the markers before the parser runs ([#695](https://github.com/mudler/vllm.cpp/issues/695)). `--reasoning-parser inkling` is not registered at all ([#703](https://github.com/mudler/vllm.cpp/issues/703)) | | `--reasoning-parser ` | `none` | Reasoning parser (`think_auto`, `deepseek_r1`, `deepseek_v3`, `holo2`, `mistral`, `minimax_m2`, `minimax_m2_append_think`, `step3`, `olmo3`, `muse_glimmer`, `qwen3`, `mimo`). `auto` detects, `none` disables. `qwen3` and its `mimo` alias are the engine-backed adapter (one upstream class, two registry names): thinking is ON, so a marker-less stream is reasoning and a `` ends reasoning with no ``. `auto` never selects it — a generic `` template resolves to `think_auto`, which is the right default for hybrid-thinking models that may answer with no think block at all | | `--kv-transfer-config ''` | (unset) | External KV connector, same JSON as vLLM's flag. See [docs/KV-OFFLOAD.md](KV-OFFLOAD.md) | | `--speculative-config ''` | (unset) | Speculative decoding (`mtp`, `dflash`, `ngram`), same JSON as vLLM's flag. `dspark` speculates on the Qwen3.6 gate models (native + Speculators drafts), token-identically to speculative-off, but is not gated on speed (currently ~2% behind at c1). A GGUF target, or a target with no aux multi-tap, is refused by name (`SPEC-DSPARK`). Its sequential Markov sampling runs on device by default; `VT_DSPARK_DEVICE_SAMPLE=0` restores the host loop (token-identical, cost only). The speculative verify runs from a captured CUDA graph, worth +12.2%/+3.5% on the 35B cells; `VT_SPEC_DECODE_GRAPH=0` restores the eager verify (also token-identical). See [docs/SPECULATIVE-DECODING.md](SPECULATIVE-DECODING.md) | diff --git a/include/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h b/include/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h new file mode 100644 index 000000000..67b2bb349 --- /dev/null +++ b/include/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h @@ -0,0 +1,126 @@ +// Ported from: vllm/parser/engine/adapters.py:128 (ParserEngineToolAdapter) + +// vllm/parser/engine/registered_adapters.py:67-70 (make_adapters(InklingParser) +// -> InklingParserToolAdapter) + vllm/tool_parsers/inkling_tool_parser.py:7 +// (InklingEngineToolParser, the class the registry entry +// vllm/tool_parsers/__init__.py:177 names) @ 555967922 (vLLM 0.26.0.dev0). +// +// The TOOL half of `make_adapters`. Its reasoning twin already lives in +// reasoning_parsers/parser_engine_adapter.h; this is the same re-shaping in the +// other direction — a ParserEngine presented through the legacy ToolParser ABC, +// so an engine-backed dialect is reachable from `get_tool_parser(name)` (and +// therefore from `--tool-call-parser NAME`, whose validation runs through +// ResolveToolParserName -> get_tool_parser). +// +// WHY THIS EXISTS AT ALL, given src/vllm/parser/inkling.cpp already ports the +// whole Inkling engine: the serving path picks the engine over the legacy seam +// (serving_chat.cpp MakeParserEngine -> parser::get_parser_engine, which has +// answered "inkling" since the assembly work landed), but the NAME was never in +// the tool-parser registry, so `--tool-call-parser inkling` threw at startup and +// the ported dialect was unreachable. Registering it is the whole gap. +// +// DEVIATIONS from adapters.py:128, all inherited from the legacy ToolParser seam +// documented in tool_parsers/abstract.h: +// - The ABC has no tokenizer and no `tools` list, so the ctor takes the built +// engine instead of a (tokenizer, tools) pair, exactly as the reasoning +// adapter does. +// - The streaming signature drops the three token-ID spans; the engine is fed +// text-only deltas and its incremental lexer holds a marker split across +// deltas. Inkling opts INTO text-lexer terminal recognition upstream +// (inkling.py:286 `token_id_terminals={}`), so this seam loses nothing for +// this family. +// - adjust_request (adapters.py:151) sets skip_special_tokens=False for the +// detokenizer. IT IS DROPPED, AND THAT IS AN OPEN GAP — nothing on this side +// handles it. The shared ToolParser seam has no `adjust_request` DISPATCH +// SITE at all (`KimiK2ToolParser::adjust_request`, kimi_k2.cpp:87, has no +// callers either; see reasoning_parsers/muse_glimmer.h, which records the +// same seam gap), and the engine-backed serving path does not compensate: +// `serving_chat.cpp` applies no skip_special_tokens override for the engine +// branch. The consequence is MATERIAL for Inkling specifically, because its +// whole grammar is special tokens — `skip_special_tokens` is declared `= +// true` at protocol.h:240/:461, forwarded verbatim by to_sampling_params +// (protocol.cpp:583) and honoured at v1/engine/detokenizer.cpp:68, so at +// server defaults `<|content_invoke_tool_json|>` and its siblings are +// stripped BEFORE the parser runs. The cases in tests/.../test_inkling.cpp +// pass because they feed the adapter marker text directly; they do not +// traverse the detokenizer. Tracked as issue #695, NOT fixed here. +// - finish_streaming (adapters.py:189) is exposed on this class but is NOT on +// the ToolParser ABC, so the legacy seam never calls it. Production serving +// of an engine-backed name does not go through this adapter — it drives +// ParserEngine::parse_delta(..., finished=true), which IS the flush — so the +// method is here for parity of shape and for direct callers/tests. Stated +// plainly: it has NO production caller anywhere in src/, include/ or +// examples/, and it never will while `MakeToolParser` routes engine-backed +// names away from this adapter (serving_chat.cpp:546-548, the +// `get_parser_engine(tool_parser_name_) != nullptr` early return). Its only +// caller is the ported upstream `_stream_text_only` harness in +// tests/.../test_inkling.cpp, which calls it exactly as upstream does — and +// that is a caller, NOT a guarantee: replacing this body with `return +// std::nullopt` leaves that whole suite green, because no ported input +// leaves anything deferred past the last delta. So the method ships +// functionally UNGATED. Said plainly here because the alternative is a +// header that implies coverage the mutation says does not exist. +// +// STRUCTURAL TAGS. inkling_tool_parser.py:10-11 sets `structural_tag_model = +// None` and `supports_required_and_named = False`, i.e. named/required tool +// choice falls back to unconstrained auto parsing. Our +// ToolChoiceStructuralTagSpecFor returns nullopt for every mode of an unmapped +// family (structural_tags.h COVERAGE), which is that behaviour already, so no +// registry row is added there. That equivalence is GATED, not merely asserted: +// tests/.../test_inkling.cpp `test_adapters_resolve` (the port of +// test_inkling.py:488, which asserts `supports_required_and_named is False`) +// checks nullopt for auto, required and named. +#ifndef VLLM_ENTRYPOINTS_OPENAI_TOOL_PARSERS_PARSER_ENGINE_ADAPTER_H_ +#define VLLM_ENTRYPOINTS_OPENAI_TOOL_PARSERS_PARSER_ENGINE_ADAPTER_H_ + +#include +#include +#include + +#include "vllm/entrypoints/openai/protocol.h" +#include "vllm/entrypoints/openai/tool_parsers/abstract.h" +#include "vllm/parser/engine/parser_engine.h" + +namespace vllm::entrypoints::openai { + +// adapters.py:128 (ParserEngineToolAdapter). Holds ONE engine for the whole +// request, like every streaming tool parser on this seam. +class ParserEngineToolAdapter : public ToolParser { + public: + explicit ParserEngineToolAdapter( + std::unique_ptr engine); + ~ParserEngineToolAdapter() override; + + // adapters.py:158 — straight delegation to extract_tool_calls_from_content. + // NOTE: the class docstring (adapters.py:131) says extract_tool_calls "starts + // the parser engine in CONTENT state"; the CODE seeds CONTENT only in the + // STREAMING entrypoint (adapters.py:178). This mirrors the code. + ExtractedToolCallInformation extract_tool_calls( + const std::string& model_output, + const ChatCompletionRequest& request) override; + + // adapters.py:167/:178 — initialize_streaming(CONTENT) then delegate. The + // initialize is idempotent after the first delta (parser_engine.py:_reset is + // guarded by _streaming_initialized), so the CONTENT seed applies once. + std::optional extract_tool_calls_streaming( + const std::string& previous_text, const std::string& current_text, + const std::string& delta_text, + const ChatCompletionRequest& request) override; + + // adapters.py:189. Not an ABC method here — see the DEVIATIONS note above. + std::optional finish_streaming(); + + protected: + std::unique_ptr engine_; +}; + +// inkling_tool_parser.py:7 (InklingEngineToolParser) over +// registered_adapters.py:67-70's make_adapters(InklingParser) tool half. +// Registered as "inkling" (tool_parsers/__init__.py:177). +class InklingEngineToolParser final : public ParserEngineToolAdapter { + public: + InklingEngineToolParser(); +}; + +} // namespace vllm::entrypoints::openai + +#endif // VLLM_ENTRYPOINTS_OPENAI_TOOL_PARSERS_PARSER_ENGINE_ADAPTER_H_ diff --git a/include/vllm/parser/engine/parser_engine.h b/include/vllm/parser/engine/parser_engine.h index 78505c6ed..3072e91b3 100644 --- a/include/vllm/parser/engine/parser_engine.h +++ b/include/vllm/parser/engine/parser_engine.h @@ -60,6 +60,16 @@ struct ParserRequest { int history_tool_call_cnt = 0; // count_history_tool_calls(request) }; +// The ChatCompletionRequest -> ParserRequest projection. Upstream has no such +// function (chat_completion/serving.py hands the request object straight to +// parse_delta / parse); it exists here only because ParserRequest models the +// subset the assembly path reads. Shared so the serving path and the +// tool_parsers ParserEngineToolAdapter cannot drift into two projections of the +// same request. history_tool_call_cnt stays 0 for a fresh request, matching +// abstract_parser.py _initialize_history_tool_call_cnt. +ParserRequest ParserRequestFromChatCompletion( + const oai::ChatCompletionRequest& request); + // Ported from: parser_engine.py:48 (ToolCallSlot). struct ToolCallSlot { std::string id; diff --git a/src/vllm/entrypoints/openai/serving_chat.cpp b/src/vllm/entrypoints/openai/serving_chat.cpp index 0c704bd2e..6e61f6a61 100644 --- a/src/vllm/entrypoints/openai/serving_chat.cpp +++ b/src/vllm/entrypoints/openai/serving_chat.cpp @@ -225,28 +225,12 @@ namespace { // subset parser_engine.py reads: include_reasoning, tool_choice, tools, and the // history tool-call count). chat_completion/serving.py passes the request object // straight to parse_delta / parse; we model only the fields the assembly path -// consumes. history_tool_call_cnt is derived only for kimi_k2's id_type (base -// count_history_tool_calls == 0 for a fresh request), matching -// abstract_parser.py:_initialize_history_tool_call_cnt. +// consumes. The body now lives next to ParserRequest itself +// (parser_engine.h ParserRequestFromChatCompletion) so the tool_parsers +// ParserEngineToolAdapter projects the request the SAME way this path does. vllm::parser::engine::ParserRequest ToParserRequest( const ChatCompletionRequest& request) { - vllm::parser::engine::ParserRequest pr; - pr.include_reasoning = request.include_reasoning; - pr.tool_choice = request.tool_choice.has_value() ? request.tool_choice->mode - : std::string("auto"); - if (request.tools.has_value()) { - for (const ChatCompletionToolsParam& t : *request.tools) { - vllm::parser::engine::ParserTool pt; - pt.name = t.function.name; - // Carry the function's JSON-Schema parameters so the assembly can coerce - // argument values to their declared types (parser_engine.py _fix_arg_types / - // find_tool_properties). Absent parameters => no schema (identity path). - pt.parameters = t.function.parameters; - pr.tools.push_back(std::move(pt)); - } - } - pr.history_tool_call_cnt = 0; - return pr; + return vllm::parser::engine::ParserRequestFromChatCompletion(request); } } // namespace diff --git a/src/vllm/entrypoints/openai/tool_parsers/abstract.cpp b/src/vllm/entrypoints/openai/tool_parsers/abstract.cpp index d617868f1..0e1be8577 100644 --- a/src/vllm/entrypoints/openai/tool_parsers/abstract.cpp +++ b/src/vllm/entrypoints/openai/tool_parsers/abstract.cpp @@ -38,6 +38,7 @@ #include "vllm/entrypoints/openai/tool_parsers/llama.h" #include "vllm/entrypoints/openai/tool_parsers/llama4_pythonic.h" #include "vllm/entrypoints/openai/tool_parsers/olmo3.h" +#include "vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h" #include "vllm/entrypoints/openai/tool_parsers/pythonic.h" #include "vllm/entrypoints/openai/tool_parsers/qwen3.h" #include "vllm/entrypoints/openai/tool_parsers/qwen3_coder.h" @@ -258,6 +259,17 @@ std::unique_ptr get_tool_parser(const std::string& name) { if (name == "muse_glimmer") { return std::make_unique(); } + // inkling_tool_parser.py:7 (name "inkling", __init__.py:177) - Inkling's typed + // content blocks (<|content_thinking|>/<|content_text|>/ + // <|content_invoke_tool_json|>) parsed by the SHARED ParserEngine, not a + // hand-rolled text parser: upstream's class is a bare + // make_adapters(InklingParser) tool adapter, and our engine + config already + // ship (parser/inkling.cpp, engine/configs.cpp inkling_config, golden-gated in + // test_parser_engine_assembly). This branch is the registry face over it - + // without it the name threw at startup and the ported dialect was unreachable. + if (name == "inkling") { + return std::make_unique(); + } return nullptr; } @@ -288,7 +300,7 @@ const std::vector& tool_parser_names() { "kimi_k2", "glm45", "glm47", "minimax_m2", "gemma4", "seed_oss", - "muse_glimmer", + "muse_glimmer", "inkling", }; return names; } diff --git a/src/vllm/entrypoints/openai/tool_parsers/detect.cpp b/src/vllm/entrypoints/openai/tool_parsers/detect.cpp index fb32d6e08..6745225d2 100644 --- a/src/vllm/entrypoints/openai/tool_parsers/detect.cpp +++ b/src/vllm/entrypoints/openai/tool_parsers/detect.cpp @@ -51,6 +51,17 @@ namespace { // hermes), GigaChat 3.0's header form (3.1's <|function_call|> is rowed), // qwen3_coder/qwen3_xml/mimo (surface byte-identical to step3p5's), and // glm45/glm47 (surface identical to poolside_v1/hy_v3's arg tags). +// - inkling: EXPLICIT-ONLY for a different reason than every row above — not +// marker collision but the ABSENCE of anything to sniff. This table matches +// against a CHAT TEMPLATE string, and Inkling has no jinja chat template at +// the pin: its prompt rendering is a native renderer +// (vllm/renderers/inkling_encoding.py, mirrored by the Rust +// rust/src/chat/src/renderer/inkling/), and `examples/` carries no +// tool_chat_template_inkling.jinja. Its "<|content_invoke_tool_json|>" +// marker collides with no row here, so a row would be harmless-looking, but +// it would also be unreachable through the only input this function gets +// while claiming template-stability we cannot demonstrate. Select it with +// --tool-call-parser inkling. // - muse_glimmer's "" is a full literal that no other // row contains and that contains no other row's marker, so its position // is free; it sits first because it is the most specific. The Muse diff --git a/src/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.cpp b/src/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.cpp new file mode 100644 index 000000000..52a973cb8 --- /dev/null +++ b/src/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.cpp @@ -0,0 +1,52 @@ +// See parser_engine_adapter.h. Ported from vllm/parser/engine/adapters.py:128 + +// registered_adapters.py:68-70 + vllm/tool_parsers/inkling_tool_parser.py:7 +// @ 555967922. +#include "vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h" + +#include +#include +#include +#include + +#include "vllm/parser/parser_manager.h" + +namespace vllm::entrypoints::openai { + +namespace pe = vllm::parser::engine; + +ParserEngineToolAdapter::ParserEngineToolAdapter( + std::unique_ptr engine) + : engine_(std::move(engine)) {} + +ParserEngineToolAdapter::~ParserEngineToolAdapter() = default; + +// adapters.py:158. +ExtractedToolCallInformation ParserEngineToolAdapter::extract_tool_calls( + const std::string& model_output, const ChatCompletionRequest& request) { + return engine_->extract_tool_calls_from_content( + model_output, pe::ParserRequestFromChatCompletion(request)); +} + +// adapters.py:167. +std::optional +ParserEngineToolAdapter::extract_tool_calls_streaming( + const std::string& previous_text, const std::string& current_text, + const std::string& delta_text, const ChatCompletionRequest& request) { + engine_->initialize_streaming(pe::ParserState::CONTENT); + return engine_->extract_tool_calls_streaming( + previous_text, current_text, delta_text, + pe::ParserRequestFromChatCompletion(request)); +} + +// adapters.py:189. +std::optional ParserEngineToolAdapter::finish_streaming() { + return engine_->finish_streaming(); +} + +// inkling_tool_parser.py:7 over registered_adapters.py:68-70. The engine itself +// is built by parser::get_parser_engine("inkling") (parser_manager.cpp), which +// is the C++ analogue of make_adapters binding InklingParser to the adapter. +InklingEngineToolParser::InklingEngineToolParser() + : ParserEngineToolAdapter(vllm::parser::get_parser_engine("inkling")) {} + +} // namespace vllm::entrypoints::openai diff --git a/src/vllm/parser/engine/parser_engine.cpp b/src/vllm/parser/engine/parser_engine.cpp index 4b9400fe5..35210e471 100644 --- a/src/vllm/parser/engine/parser_engine.cpp +++ b/src/vllm/parser/engine/parser_engine.cpp @@ -218,6 +218,30 @@ std::string safe_arg_prefix( } // namespace +// See parser_engine.h. Relocated here from serving_chat.cpp's anonymous +// namespace so the tool_parsers ParserEngineToolAdapter shares ONE projection +// with the serving path rather than carrying a second copy of it. +ParserRequest ParserRequestFromChatCompletion( + const oai::ChatCompletionRequest& request) { + ParserRequest pr; + pr.include_reasoning = request.include_reasoning; + pr.tool_choice = request.tool_choice.has_value() ? request.tool_choice->mode + : std::string("auto"); + if (request.tools.has_value()) { + for (const oai::ChatCompletionToolsParam& t : *request.tools) { + ParserTool pt; + pt.name = t.function.name; + // Carry the function's JSON-Schema parameters so the assembly can coerce + // argument values to their declared types (parser_engine.py _fix_arg_types / + // find_tool_properties). Absent parameters => no schema (identity path). + pt.parameters = t.function.parameters; + pr.tools.push_back(std::move(pt)); + } + } + pr.history_tool_call_cnt = 0; + return pr; +} + ParserEngine::ParserEngine(ParserEngineConfig config, const EngineTokenizer* tokenizer) : config_(std::move(config)), diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 35042be33..8f689b7bf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -864,6 +864,8 @@ vllm_cpp_add_test(test_longcat vllm/entrypoints/openai/tool_parsers/test_longcat.cpp) vllm_cpp_add_test(test_kimi_k2 vllm/entrypoints/openai/tool_parsers/test_kimi_k2.cpp) +vllm_cpp_add_test(test_tool_parser_inkling + vllm/entrypoints/openai/tool_parsers/test_inkling.cpp) vllm_cpp_add_test(test_granite_tool_parser vllm/entrypoints/openai/tool_parsers/test_granite_tool_parser.cpp) vllm_cpp_add_test(test_granite4_tool_parser diff --git a/tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp b/tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp index 5294fc206..fa2a1682b 100644 --- a/tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp +++ b/tests/vllm/entrypoints/openai/tool_parsers/test_detect.cpp @@ -218,7 +218,8 @@ TEST_CASE("Registry: every enumerated tool-parser name resolves") { // dialect. 40 accepted names over 36 parser families (aliases: // llama3_json/llama4_json, qwen3_coder/qwen3_xml/mimo, glm45/glm47). // 2026-08-10 (MODEL-MUSE-GLIMMER-W7): 40 -> 41, adding "muse_glimmer". - CHECK(names.size() == 41); + // 2026-08-13 (TOOLS-PARSER-BREADTH W1, #608): 41 -> 42, adding "inkling". + CHECK(names.size() == 42); // Every name the marker table can emit must itself be a registered name. std::size_t marker_count = 0; const ToolParserMarker* markers = ToolParserMarkerTable(&marker_count); diff --git a/tests/vllm/entrypoints/openai/tool_parsers/test_inkling.cpp b/tests/vllm/entrypoints/openai/tool_parsers/test_inkling.cpp new file mode 100644 index 000000000..ccf8d01a3 --- /dev/null +++ b/tests/vllm/entrypoints/openai/tool_parsers/test_inkling.cpp @@ -0,0 +1,581 @@ +// Tests for the "inkling" tool-parser registry name (parser_engine_adapter.h): +// the ParserEngineToolAdapter face over the already-ported Inkling ParserEngine. +// +// FIDELITY ANCHOR: tests/parser/engine/test_inkling.py @ 555967922 (vLLM +// 0.26.0.dev0). Upstream has NO tests/tool_parsers/test_inkling_tool_parser.py — +// `InklingEngineToolParser` (inkling_tool_parser.py:7) is a bare +// make_adapters(InklingParser) subclass, so the executable description of the +// dialect is the engine test. +// +// PROVENANCE, case by case: 22 cases here, of which **19 are PORTED** and keep +// their upstream names verbatim, and **3 are AUTHORED** (each says so at its +// site): +// - "the registry name resolves and is enumerated" and "EXPLICIT-ONLY — no +// autodetect row" gate OUR packaging surface. Upstream has no analogue to +// port: its registry is a lazy dict and it has no chat-template marker +// table at all, so `DetectToolParser` is an ORIGINAL component (see +// detect.cpp's header). +// - "the tool adapter seeds the engine in CONTENT state" gates UPSTREAM +// behaviour (adapters.py:178) that upstream's own suite never exercises. +// Upstream DOES construct the adapter — test_inkling.py:498 +// test_adapter_round_trip, ported below — but only through the +// NON-streaming entrypoint, and the CONTENT seed lives exclusively in +// `extract_tool_calls_streaming` (adapters.py:178); `extract_tool_calls` +// (adapters.py:158) delegates straight to extract_tool_calls_from_content +// with no seed at all. So no upstream case reaches the seed, and this one +// was added because a mutation survived without it. +// The upstream TestArgConverter class and the token-id / reasoning cases are +// deliberately NOT here: they belong to the engine layer, which is already +// golden-gated against the pinned oracle in tests/vllm/parser/engine/ +// test_parser_engine_assembly.cpp (scenarios inkling_think_tool_text_* and +// inkling_nonobject_args_*). What this file gates is the TOOL-PARSER SEAM: +// that `--tool-call-parser inkling` resolves at all, and that the adapter hands +// the engine the right initial state so the dialect extracts correctly through +// ToolParser. +// +// The one tool-facing upstream case DECLINED rather than ported is +// TestToolCallFiltering::test_skip_tool_parsing_round_trip +// (test_inkling.py:436). It drives ONE InklingParser with `skip_tool_parsing = +// True`, calls `extract_reasoning`, then re-extracts from the returned content +// with a SECOND parser. Neither half is on this seam: our `ToolParser` ABC +// exposes no skip-tool-parsing setter and no `extract_reasoning`, and the +// REASONING half has no registry face at all — `reasoning_parser_names()` has +// no "inkling" row, the owed gap recorded in the spec (upstream registers it in +// both registries, vllm/reasoning/__init__.py:131). Porting it would mean +// driving `parser::engine::ParserEngine` directly, which is the engine layer +// this file deliberately does not re-gate. +// +// HARNESS ADAPTATIONS, all forced and all documented: +// 1. Upstream drives InklingParser directly, so its non-streaming cases start +// in the config's MESSAGE_HEADER state. adapters.py:178 makes the TOOL +// adapter start in CONTENT (it parses reasoning-stripped content), and we +// mirror that. Cases whose assertion depends on the header state are +// prefixed with the "<|message_model|>" token, which is exactly the +// (CONTENT, MSG_MODEL) -> MESSAGE_HEADER transition (inkling.py:186), so +// the assertion is preserved rather than weakened. +// 2. Our ToolParser streaming seam is TEXT-only (tool_parsers/abstract.h), so +// the port uses upstream's _stream_text_only harness (arbitrary character +// chunks, markers split mid-marker) rather than _stream. Inkling opts into +// text-lexer terminals upstream (inkling.py:286 token_id_terminals={}), so +// this is the same grammar path, and character chunks are a STRICTLY +// STRONGER split than token boundaries. +// 3. `test_adapters_resolve` (test_inkling.py:488) asserts on the registered +// CLASS (`tool_cls._parser_engine_cls is InklingParser`); our registry +// hands back an INSTANCE, so the same fact is asserted as a dynamic_cast +// to `InklingEngineToolParser`, whose constructor is +// `get_parser_engine("inkling")` and admits no other engine +// (parser_engine_adapter.cpp:49). Its `supports_required_and_named is +// False` half is asserted through the surface that flag controls here — +// `ToolChoiceStructuralTagSpecFor` (see that case). Its reasoning half +// (`reasoning_cls._parser_engine_cls`) is NOT portable: see the declined +// case above. +// 4. `test_tool_choice_none_non_streaming` (test_inkling.py:465) asserts +// reasoning, content and tools from one `parser.parse()` call. This seam +// returns no reasoning, so the THINK block and its assertion are dropped +// and the case keeps the two halves the tool seam owns (content survives, +// tools are suppressed). +#include + +#include +#include +#include +#include +#include + +#include + +#include "vllm/entrypoints/openai/protocol.h" +#include "vllm/entrypoints/openai/tool_parsers/abstract.h" +#include "vllm/entrypoints/openai/tool_parsers/detect.h" +#include "vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h" +#include "vllm/entrypoints/openai/tool_parsers/structural_tags.h" + +using namespace vllm::entrypoints::openai; + +namespace { + +// inkling.py:48-56 — every marker is a dedicated special token. +const std::string MSG_MODEL = "<|message_model|>"; +const std::string TEXT_START = "<|content_text|>"; +const std::string TOOL_JSON = "<|content_invoke_tool_json|>"; +const std::string TOOL_TEXT = "<|content_invoke_tool_text|>"; +const std::string TOOL_ERROR = "<|content_tool_error|>"; +const std::string END_MESSAGE = "<|end_message|>"; +const std::string END_SAMPLING = "<|content_model_end_sampling|>"; + +// Upstream _tool_block(). +std::string ToolBlock(const std::string& name, const std::string& args) { + return TOOL_JSON + "{\"name\":\"" + name + "\",\"args\":" + args + "}" + + END_MESSAGE; +} + +// Resolve through the REGISTRY, exactly as upstream's cases do +// (ToolParserManager.get_tool_parser("inkling")), then narrow to the +// engine-backed adapter: the ported `_stream_text_only` harness must call +// `finish_streaming()` (adapters.py:189), which is NOT on the ToolParser ABC. +std::unique_ptr MakeParser() { + std::unique_ptr p = get_tool_parser("inkling"); + REQUIRE(p != nullptr); + ParserEngineToolAdapter* adapter = + dynamic_cast(p.get()); + REQUIRE(adapter != nullptr); + p.release(); + return std::unique_ptr(adapter); +} + +// Upstream _stream_text_only(): chunk the text at arbitrary character +// boundaries, accumulate previous/current exactly as serving_chat does, then +// call finish_streaming() and append its delta if there is one. That trailing +// flush is part of the upstream harness and is reproduced here rather than +// compensated for by appending a block-end marker to the input. +// +// HONEST LIMIT, measured not assumed. This gives +// ParserEngineToolAdapter::finish_streaming() its only CALLER; it does NOT give +// it a GUARANTEE. Mutating its body to `return std::nullopt` leaves all 22 cases +// green (verified on aarch64, 2026-08-14), because no input in the ported set +// leaves anything deferred past the last delta — every streamed text either ends +// on a block-end marker or has already been emitted. An attempt to author a +// streaming twin of `test_text_after_tool_call` did not move that mutation +// either, so the trailing text is not deferred on this path. The method +// therefore remains functionally UNGATED, which the header of +// parser_engine_adapter.h records rather than papers over. +std::vector StreamTextOnly(ParserEngineToolAdapter& parser, + const std::string& text, + std::size_t chunk_size) { + std::vector out; + std::string previous; + ChatCompletionRequest req; + for (std::size_t start = 0; start < text.size(); start += chunk_size) { + const std::string delta = text.substr(start, chunk_size); + const std::string current = previous + delta; + std::optional d = + parser.extract_tool_calls_streaming(previous, current, delta, req); + if (d.has_value()) out.push_back(*d); + previous = current; + } + std::optional finish = parser.finish_streaming(); + if (finish.has_value()) out.push_back(*finish); + return out; +} + +// Same, with a caller-supplied request (upstream's `none_request` fixture, +// test_inkling.py:459). +std::vector StreamTextOnlyWith(ParserEngineToolAdapter& parser, + const ChatCompletionRequest& req, + const std::string& text, + std::size_t chunk_size) { + std::vector out; + std::string previous; + for (std::size_t start = 0; start < text.size(); start += chunk_size) { + const std::string delta = text.substr(start, chunk_size); + const std::string current = previous + delta; + std::optional d = + parser.extract_tool_calls_streaming(previous, current, delta, req); + if (d.has_value()) out.push_back(*d); + previous = current; + } + std::optional finish = parser.finish_streaming(); + if (finish.has_value()) out.push_back(*finish); + return out; +} + +// Upstream `none_request` fixture (test_inkling.py:459): one function tool, +// tool_choice "none". +ChatCompletionRequest NoneRequest() { + ChatCompletionRequest req; + ChatCompletionToolsParam tool; + tool.function.name = "f"; + req.tools = std::vector{tool}; + ToolChoice choice; + choice.mode = "none"; + req.tool_choice = choice; + return req; +} + +// Upstream streaming_helpers.collect_content. +std::string CollectContent(const std::vector& deltas) { + std::string s; + for (const DeltaMessage& d : deltas) + if (d.content.has_value()) s += *d.content; + return s; +} + +// Upstream streaming_helpers.collect_function_name (the first name emitted). +std::string CollectFunctionName(const std::vector& deltas) { + for (const DeltaMessage& d : deltas) { + if (!d.tool_calls.has_value()) continue; + for (const DeltaToolCall& tc : *d.tool_calls) + if (tc.function.name.has_value() && !tc.function.name->empty()) + return *tc.function.name; + } + return std::string(); +} + +// Upstream streaming_helpers.collect_tool_arguments (index 0). +std::string CollectToolArguments(const std::vector& deltas, + int index = 0) { + std::string s; + for (const DeltaMessage& d : deltas) { + if (!d.tool_calls.has_value()) continue; + for (const DeltaToolCall& tc : *d.tool_calls) + if (tc.index == index && tc.function.arguments.has_value()) + s += *tc.function.arguments; + } + return s; +} + +nlohmann::json J(const std::string& s) { return nlohmann::json::parse(s); } + +} // namespace + +// ── The registry surface this wave adds ────────────────────────────────────── + +TEST_CASE("inkling: the registry name resolves and is enumerated") { + // __init__.py:177 registers "inkling" -> InklingEngineToolParser. Before this + // wave the engine was fully ported (parser/inkling.cpp) but the NAME was not + // in the tool-parser registry, so --tool-call-parser inkling threw at startup. + CHECK(get_tool_parser("inkling") != nullptr); + CHECK(ResolveToolParserName("inkling", "") == "inkling"); + + const std::vector& names = tool_parser_names(); + bool listed = false; + for (const std::string& n : names) listed = listed || n == "inkling"; + CHECK(listed); +} + +TEST_CASE("inkling: EXPLICIT-ONLY — no autodetect row, on purpose") { + // Inkling has no jinja chat template at the pin (rendering is + // vllm/renderers/inkling_encoding.py, not a template), so there is nothing for + // the marker table to sniff. A template that merely CONTAINS the marker must + // therefore NOT resolve to inkling; it falls through to the hermes default. + // See detect.cpp's ORDER MATTERS block. + CHECK(DetectToolParser(TOOL_JSON) == "hermes"); + CHECK(DetectToolParser("") == "hermes"); +} + +TEST_CASE("inkling: the tool adapter seeds the engine in CONTENT state") { + // adapters.py:178 — the STREAMING entrypoint of the TOOL adapter (unlike the + // engine's own entrypoints, which start in the config's initial_state) starts + // in CONTENT so it parses REASONING-STRIPPED content: what + // ReasoningParser::extract_reasoning handed back has already lost its leading + // block markers. Inkling's config initial_state is MESSAGE_HEADER + // (inkling.py:279), where bare text is metadata and is SWALLOWED + // (inkling.py:214), so dropping the CONTENT seed silently deletes the + // assistant's visible text. This case is the only one that separates the + // adapter from a bare ParserEngine over inkling_config(). + // + // AUTHORED, not ported. Upstream constructs the adapter exactly once + // (test_inkling.py:498 test_adapter_round_trip, ported below), and only + // through the NON-streaming `extract_tool_calls`, which delegates to + // extract_tool_calls_from_content with no seed (adapters.py:158). The seed + // lives only in extract_tool_calls_streaming (adapters.py:178), which no + // upstream case reaches, so there was nothing to port. Added because deleting + // the seed survived the suite without it. + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = + p->extract_tool_calls("Sure, checking that now.", req); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "Sure, checking that now."); + + std::unique_ptr s = MakeParser(); + const std::vector out = + StreamTextOnly(*s, "Sure, checking that now.", 5); + CHECK(CollectContent(out) == "Sure, checking that now."); +} + +// ── TestNonStreaming (test_inkling.py) ─────────────────────────────────────── + +TEST_CASE("inkling test_plain_text") { + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = + p->extract_tool_calls(TEXT_START + "hello world" + END_MESSAGE, req); + CHECK(info.tools_called == false); + CHECK(info.tool_calls.empty()); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "hello world"); +} + +TEST_CASE("inkling test_tool_header_name_is_not_visible_content") { + // Upstream: the optional function name between the model-role and + // content-kind markers is metadata, not visible assistant content + // (inkling.py:214). Adaptation 1: MSG_MODEL prefix reproduces upstream's + // MESSAGE_HEADER initial state on the CONTENT-seeded tool adapter. + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = p->extract_tool_calls( + MSG_MODEL + "get_weather" + ToolBlock("get_weather", "{\"city\":\"SF\"}"), + req); + CHECK(!info.content.has_value()); + REQUIRE(info.tool_calls.size() == 1); + CHECK(info.tool_calls[0].function.name == "get_weather"); +} + +TEST_CASE("inkling test_parallel_tool_calls") { + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = p->extract_tool_calls( + ToolBlock("a", "{}") + MSG_MODEL + ToolBlock("b", "{\"x\":[1,2]}"), req); + CHECK(info.tools_called); + REQUIRE(info.tool_calls.size() == 2); + CHECK(info.tool_calls[0].function.name == "a"); + CHECK(info.tool_calls[1].function.name == "b"); + CHECK(J(info.tool_calls[0].function.arguments) == J("{}")); + CHECK(J(info.tool_calls[1].function.arguments) == J("{\"x\":[1,2]}")); +} + +TEST_CASE("inkling test_nested_args") { + // The hand-rolled span scanner must be string/escape aware: "a}b" carries a + // closing brace inside a string value. + const std::string args = "{\"q\":{\"deep\":{\"list\":[{\"k\":\"v\"}]}},\"s\":\"a}b\"}"; + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = + p->extract_tool_calls(ToolBlock("f", args), req); + REQUIRE(info.tool_calls.size() == 1); + CHECK(J(info.tool_calls[0].function.arguments) == J(args)); +} + +TEST_CASE("inkling test_invoke_tool_text_is_visible_text") { + // Raw tool blocks render as visible text, never as a tool call + // (inkling.py:200). + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = + p->extract_tool_calls(TOOL_TEXT + "do something" + END_MESSAGE, req); + CHECK(info.tools_called == false); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "do something"); +} + +TEST_CASE("inkling test_tool_error_is_visible_text") { + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = + p->extract_tool_calls(TOOL_ERROR + "boom" + END_MESSAGE, req); + CHECK(info.tools_called == false); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "boom"); +} + +TEST_CASE("inkling test_end_sampling_closes_blocks") { + // <|content_model_end_sampling|> ends a block exactly like <|end_message|> + // (inkling.py:256). + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = + p->extract_tool_calls(TEXT_START + "hi" + END_SAMPLING, req); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "hi"); +} + +TEST_CASE("inkling test_text_after_tool_call") { + // inkling.py:376 _single_pass_parse — the base engine DEFERS content that + // follows tool-call events; Inkling allows text blocks after tool blocks, so + // the trailing text must be flushed. This is the ONLY case that exercises the + // InklingParser subclass hook through the tool seam. + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = p->extract_tool_calls( + ToolBlock("f", "{}") + MSG_MODEL + TEXT_START + "done" + END_MESSAGE, req); + REQUIRE(info.tool_calls.size() == 1); + CHECK(info.tool_calls[0].function.name == "f"); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "done"); +} + +TEST_CASE("inkling test_incomplete_tool_call_at_eos") { + // Engine convention: best-effort with what arrived (the Rust parser instead + // errors with "incomplete Inkling tool call"). + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = p->extract_tool_calls( + TOOL_JSON + "{\"name\":\"d\",\"args\":{\"k\":\"v\"", req); + REQUIRE(info.tool_calls.size() == 1); + CHECK(info.tool_calls[0].function.name == "d"); +} + +TEST_CASE("inkling test_prose_marker_without_token_ids_is_structural") { + // Inkling opts into text-lexer terminal recognition, so a marker that arrives + // as held-back detokenized TEXT is still parsed structurally. + std::unique_ptr p = MakeParser(); + ChatCompletionRequest req; + ExtractedToolCallInformation info = p->extract_tool_calls( + TEXT_START + "see " + TEXT_START + " token" + END_MESSAGE, req); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "see token"); +} + +// ── TestStreaming (test_inkling.py) ────────────────────────────────────────── + +TEST_CASE("inkling test_chunk_invariance_tool_call_text_only") { + // Upstream parametrizes chunk_size over [1, 3, 7, 64]; 4096 (one whole + // delta) is added from the token-id variant's parametrization since character + // chunking makes it the same shape. + for (std::size_t chunk : {std::size_t{1}, std::size_t{3}, std::size_t{7}, + std::size_t{64}, std::size_t{4096}}) { + CAPTURE(chunk); + std::unique_ptr p = MakeParser(); + const std::string text = TEXT_START + "Check this." + END_MESSAGE + + MSG_MODEL + + ToolBlock("get_weather", "{\"city\":\"San Francisco\"}"); + const std::vector out = StreamTextOnly(*p, text, chunk); + CHECK(CollectContent(out) == "Check this."); + CHECK(CollectFunctionName(out) == "get_weather"); + CHECK(J(CollectToolArguments(out)) == J("{\"city\":\"San Francisco\"}")); + } +} + +TEST_CASE("inkling test_split_marker_held_across_chunks") { + // Mirrors the Rust `inkling_streaming_holds_split_markers`: chunk 9 slices + // "<|content_text|>" mid-marker, so a lexer that emitted eagerly would leak + // marker bytes into the content. + std::unique_ptr p = MakeParser(); + const std::vector out = + StreamTextOnly(*p, TEXT_START + "hello" + END_MESSAGE, 9); + CHECK(CollectContent(out) == "hello"); +} + +TEST_CASE("inkling test_name_streams_before_args_complete") { + // Feed only up to the name's closing quote — the name delta must already be + // emitted before any args arrive. + std::unique_ptr p = MakeParser(); + const std::vector out = + StreamTextOnly(*p, TOOL_JSON + "{\"name\":\"get_weather\",", 4096); + CHECK(CollectFunctionName(out) == "get_weather"); +} + +TEST_CASE("inkling test_streamed_args_are_object_only") { + // inkling.py:146 _inkling_arg_converter — the streamed `arguments` must be + // the bare args object, NEVER the {"name":...,"args":...} wrapper. Without + // the converter the engine streams the wrapper verbatim into the OpenAI + // arguments field. + std::unique_ptr p = MakeParser(); + const std::vector out = + StreamTextOnly(*p, ToolBlock("f", "{\"a\":1}"), 3); + const std::string args = CollectToolArguments(out); + CHECK(J(args) == J("{\"a\":1}")); + CHECK(args.find("name") == std::string::npos); +} + +TEST_CASE("inkling test_parallel_calls_streaming") { + for (std::size_t chunk : {std::size_t{1}, std::size_t{9}}) { + CAPTURE(chunk); + std::unique_ptr p = MakeParser(); + const std::string text = ToolBlock("a", "{\"i\":1}") + MSG_MODEL + + ToolBlock("b", "{\"i\":2}"); + const std::vector out = StreamTextOnly(*p, text, chunk); + std::string name0, name1; + for (const DeltaMessage& d : out) { + if (!d.tool_calls.has_value()) continue; + for (const DeltaToolCall& tc : *d.tool_calls) { + if (!tc.function.name.has_value() || tc.function.name->empty()) continue; + if (tc.index == 0) name0 = *tc.function.name; + if (tc.index == 1) name1 = *tc.function.name; + } + } + CHECK(name0 == "a"); + CHECK(name1 == "b"); + CHECK(J(CollectToolArguments(out, 0)) == J("{\"i\":1}")); + CHECK(J(CollectToolArguments(out, 1)) == J("{\"i\":2}")); + } +} + +// ── TestToolCallFiltering (test_inkling.py:430) ────────────────────────────── +// +// Upstream's own docstring: these are the Inkling equivalents of the generic +// tool-call-filtering replay tests, which exclude Inkling because its structural +// role/kind tokens and shared block-end token do not fit the generic +// reasoning/tool split. They are here because they exercise `tool_choice` +// through the request projection this wave MOVED out of serving_chat.cpp's +// anonymous namespace (ParserRequestFromChatCompletion, parser_engine.h:70) — +// the projection the adapter and the serving path now share. The engine reads +// `tool_choice == "none"` with a non-empty tool list to set suppress_tool_calls +// (parser_engine.cpp:339), so a projection that dropped the field would ship +// tool calls on a request that forbade them. + +TEST_CASE("inkling test_tool_choice_none_non_streaming") { + // Adaptation 4: upstream also asserts reasoning == "plan" from parse(); this + // seam returns no reasoning, so the THINK block is dropped and the two halves + // the tool seam owns are kept verbatim. + std::unique_ptr p = MakeParser(); + const ChatCompletionRequest req = NoneRequest(); + ExtractedToolCallInformation info = p->extract_tool_calls( + MSG_MODEL + TEXT_START + "visible" + END_MESSAGE + MSG_MODEL + + ToolBlock("f", "{\"a\":1}"), + req); + REQUIRE(info.content.has_value()); + CHECK(*info.content == "visible"); + CHECK(info.tools_called == false); + CHECK(info.tool_calls.empty()); +} + +TEST_CASE("inkling test_tool_choice_none_streaming") { + std::unique_ptr p = MakeParser(); + const ChatCompletionRequest req = NoneRequest(); + const std::string text = TEXT_START + "visible" + END_MESSAGE + MSG_MODEL + + ToolBlock("f", "{\"a\":1}"); + const std::vector out = StreamTextOnlyWith(*p, req, text, 3); + CHECK(CollectContent(out) == "visible"); + for (const DeltaMessage& d : out) + CHECK(!(d.tool_calls.has_value() && !d.tool_calls->empty())); +} + +// ── TestRegisteredAdapters (test_inkling.py:487) ───────────────────────────── + +TEST_CASE("inkling test_adapters_resolve") { + // Upstream (test_inkling.py:488): + // tool_cls = ToolParserManager.get_tool_parser("inkling") + // assert tool_cls._parser_engine_cls is InklingParser + // assert tool_cls.supports_required_and_named is False + // Adaptation 3: the registry hands back an instance, so the engine binding is + // asserted as the concrete adapter type. InklingEngineToolParser's ctor is + // `ParserEngineToolAdapter(parser::get_parser_engine("inkling"))` + // (parser_engine_adapter.cpp:49) and takes no engine argument, so being that + // type IS `_parser_engine_cls is InklingParser`. + std::unique_ptr tool_cls = get_tool_parser("inkling"); + REQUIRE(tool_cls != nullptr); + CHECK(dynamic_cast(tool_cls.get()) != nullptr); + CHECK(dynamic_cast(tool_cls.get()) != nullptr); + + // supports_required_and_named = False (inkling_tool_parser.py:11): named and + // required tool choice fall back to UNCONSTRAINED auto parsing. The surface + // that flag controls here is the structural-tag spec, which must be nullopt + // for every mode (structural_tags.h COVERAGE — inkling is unmapped). + ChatCompletionRequest req; + ChatCompletionToolsParam tool; + tool.function.name = "f"; + req.tools = std::vector{tool}; + for (const std::string& mode : {std::string("auto"), std::string("required"), + std::string("function")}) { + CAPTURE(mode); + ToolChoice choice; + choice.mode = mode; + if (mode == "function") choice.function_name = "f"; + req.tool_choice = choice; + CHECK(!ToolChoiceStructuralTagSpecFor("inkling", req).has_value()); + } + + // The reasoning half of the upstream assertion + // (ReasoningParserManager.get_reasoning_parser("inkling")) is NOT ported: we + // have no "inkling" reasoning registry row. See the DECLINED note in this + // file's header, and the spec's "Owed by W1's landing, but NOT this row's to + // fix" (issue #703). +} + +TEST_CASE("inkling test_adapter_round_trip") { + // test_inkling.py:498 — construct through the registry and round-trip one + // tool block. This is the ONLY upstream case that constructs the adapter at + // all, and it goes through the non-streaming entrypoint. + std::unique_ptr adapter = get_tool_parser("inkling"); + REQUIRE(adapter != nullptr); + ChatCompletionRequest req; + ExtractedToolCallInformation result = + adapter->extract_tool_calls(ToolBlock("f", "{\"a\":1}"), req); + CHECK(result.tools_called); + REQUIRE(result.tool_calls.size() == 1); + CHECK(result.tool_calls[0].function.name == "f"); + CHECK(J(result.tool_calls[0].function.arguments) == J("{\"a\":1}")); +} + From bf33b75d74f82ca838eb573d02b0e6cf6ef3e29d Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 10:59:18 +0200 Subject: [PATCH 27/55] =?UTF-8?q?feat(MODEL-MM-indextts2):=20EnhancedCodec?= =?UTF-8?q?'s=20factorized=20VQ=20=E2=80=94=20the=20semantic=20codes=20(#6?= =?UTF-8?q?34)=20(#713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Ports Amphion's FactorizedVectorQuantize (use_l2_normlize=True) — the `semantic_codec.quantize(...)` of infer_v2_5.py:293, whose DISCRETE output is the semantic code the talker consumes. Gated against that module executed directly. 3 cases / 17 assertions. INDICES ARE GATED EXACTLY: an index off by one codebook entry is a different utterance, not a small numerical error. THE NORMALIZATION IS SEARCH-ONLY: distances use L2-normalized encodings and codebook rows, but decode_code returns the RAW row. Returning the normalized row leaves every index identical and moves only z_q, so one case pins indices and the other pins values. Weight-normed projections carry the legacy weight_g/weight_v spelling; g*v/||v|| is pinned by a hand-computed case. ONE MUTATION SURVIVED, AND SHOULD HAVE. Removing the encoding normalization is a provable NO-OP: dist = |e|^2 - 2 e.c + |c|^2, |e|^2 is constant per frame and a normalized codebook makes |c|^2 = 1, so argmin reduces to argmax(e.c), which scaling e cannot change. This repo's rule is that an unmoved mutation usually marks a FIXTURE GAP, so it was checked rather than assumed; it is an algebraic identity, and the reasoning is recorded at the call site. Other mutations RED at compile_err=0: returning the normalized row, and dropping the sqrt from weight-norm. REMAINING: EnhancedCodec's VocosBackbone encoder, then talker head, length regulator, S2Mel. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- CMakeLists.txt | 1 + include/vllm/model_executor/models/fvq.h | 52 +++++++++ scripts/gen-fvq-goldens.py | 96 +++++++++++++++++ src/vllm/model_executor/models/fvq.cpp | 128 +++++++++++++++++++++++ tests/CMakeLists.txt | 2 + tests/vllm/models/fvq_goldens.inc | 75 +++++++++++++ tests/vllm/models/test_fvq.cpp | 96 +++++++++++++++++ 7 files changed, 450 insertions(+) create mode 100644 include/vllm/model_executor/models/fvq.h create mode 100644 scripts/gen-fvq-goldens.py create mode 100644 src/vllm/model_executor/models/fvq.cpp create mode 100644 tests/vllm/models/fvq_goldens.inc create mode 100644 tests/vllm/models/test_fvq.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 96aa8d767..5dce3a9d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -772,6 +772,7 @@ add_library(vllm STATIC src/vllm/model_executor/models/indextts2.cpp src/vllm/model_executor/models/campplus.cpp src/vllm/model_executor/models/w2vbert.cpp + src/vllm/model_executor/models/fvq.cpp src/vllm/model_executor/models/minimax_h3_video_vae.cpp src/vllm/model_executor/models/minimax_h3_video_vae_device.cpp src/vllm/model_executor/models/minimax_h3_vae_cnn.cpp diff --git a/include/vllm/model_executor/models/fvq.h b/include/vllm/model_executor/models/fvq.h new file mode 100644 index 000000000..a7fc9cbb2 --- /dev/null +++ b/include/vllm/model_executor/models/fvq.h @@ -0,0 +1,52 @@ +// FactorizedVectorQuantize — EnhancedCodec's quantizer (W3/W4, #634). +// +// `infer_v2_5.py:293` calls `semantic_codec.quantize(...)`, and its DISCRETE +// output is the semantic code the talker consumes. Amphion's implementation with +// `use_l2_normlize=True`. +// +// Layout is [dim, frames] (channel-major), matching the torch [B, D, T] tensor. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace fvq { + +struct Weights { + // torch weight_norm stores (g, v); the effective weight is g * v / ||v||, + // with the norm taken over every dimension except the first. The checkpoint + // carries the LEGACY spelling weight_g / weight_v. + std::vector in_g, in_v, in_bias; // [codebook_dim, input_dim, 1] + std::vector out_g, out_v, out_bias; // [input_dim, codebook_dim, 1] + std::vector codebook; // [codebook_size, codebook_dim] +}; + +// Materialize a weight-normed 1x1 conv weight: w = g * v / ||v||. +std::vector MaterializeWeightNorm(const std::vector& g, const std::vector& v, + int64_t out_channels); + +struct QuantizeResult { + std::vector indices; // [frames] + std::vector z_q; // [input_dim, frames], AFTER out_project +}; + +// FactorizedVectorQuantize::forward. +// +// z_e = in_project(z) (1x1 conv, weight-normed) +// distance = |e|^2 - 2 e.c^T + |c|^2 over L2-NORMALIZED e and c +// indices = argmin(distance) +// z_q = out_project(codebook[indices]) +// +// THE NORMALIZATION IS SEARCH-ONLY. Distances are computed on normalized +// vectors, but the entry returned by `decode_code` is the RAW codebook row. +// Returning the normalized row instead still produces plausible embeddings of +// the right shape, and the indices would be identical -- so only the VALUES +// catch it. +QuantizeResult Quantize(const std::vector& z, int64_t frames, int64_t input_dim, + int64_t codebook_dim, int64_t codebook_size, const Weights& weights); + +} // namespace fvq +} // namespace models +} // namespace vllm diff --git a/scripts/gen-fvq-goldens.py b/scripts/gen-fvq-goldens.py new file mode 100644 index 000000000..21644bc85 --- /dev/null +++ b/scripts/gen-fvq-goldens.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/fvq_goldens.inc — the EnhancedCodec quantizer oracle. + +`infer_v2_5.py:293` calls `semantic_codec.quantize(inputs)`, whose discrete +output IS the semantic code the talker consumes. The quantizer is Amphion's +FactorizedVectorQuantize with use_l2_normlize=True. + +Upstream is executed DIRECTLY (indextts/codec/amphion_codec/quantize), loaded by +file path so no package __init__ is needed. + +Usage: + CODEC_QUANTIZE= \ + python3 scripts/gen-fvq-goldens.py --out tests/vllm/models/fvq_goldens.inc +""" +from __future__ import annotations +import argparse, importlib.util, os, sys +from pathlib import Path +import numpy as np, torch + +MASK = (1 << 64) - 1 +def fnv(n): + h = 0xCBF29CE484222325 + for b in n.encode(): h ^= b; h = (h * 0x100000001B3) & MASK + return h +def sm(x): + x = (x + 0x9E3779B97F4A7C15) & MASK + z = x + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & MASK + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & MASK + return z ^ (z >> 31) +def rnd(name, n, scale): + s = fnv(name); o = np.empty(n) + for i in range(n): o[i] = (((sm((s + i) & MASK) >> 11) * 2.0**-53) * 2 - 1) * scale + return o +def P(name, shape, scale=0.3): + n = 1 + for d in shape: n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + +def main() -> int: + ap = argparse.ArgumentParser(); ap.add_argument("--out", required=True) + a = ap.parse_args() + src = Path(os.environ["CODEC_QUANTIZE"]) + spec = importlib.util.spec_from_file_location("fvq", src) + m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) + + torch.manual_seed(0) + IN_DIM, CB_DIM, CB_SIZE, T = 12, 4, 32, 9 + q = m.FactorizedVectorQuantize(input_dim=IN_DIM, codebook_size=CB_SIZE, + codebook_dim=CB_DIM, commitment=0.15, + codebook_loss_weight=1.0, use_l2_normlize=True).eval() + manifest = [] + with torch.no_grad(): + for n, p in list(q.named_parameters()) + list(q.named_buffers()): + if n.endswith("num_batches_tracked"): continue + p.copy_(P(n, tuple(p.shape) if p.dim() else (1,)).reshape(p.shape)) + manifest.append((n, list(p.shape))) + z = P("z", (1, IN_DIM, T), 1.0) + with torch.no_grad(): + out = q(z) + z_q, indices = out[0], out[3] + + def emit_f(f, name, t): + arr = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) + f.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(arr), 6): + f.write(" " + ", ".join(f"{v:.9e}F" for v in arr[i:i+6]) + ",\n") + f.write("};\n\n") + + p = Path(a.out) + with p.open("w") as f: + f.write("// GENERATED by scripts/gen-fvq-goldens.py -- do not edit.\n") + f.write("// Amphion FactorizedVectorQuantize executed DIRECTLY (the quantizer\n") + f.write("// EnhancedCodec uses; infer_v2_5.py:293). Weights rebuilt both sides from\n") + f.write("// one FNV-1a -> splitmix64 stream.\n") + f.write("#pragma once\n\n#include \n\nnamespace fvq_goldens {\n\n") + for n, v in (("kInputDim", IN_DIM), ("kCodebookDim", CB_DIM), + ("kCodebookSize", CB_SIZE), ("kFrames", T)): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write(f"\ninline constexpr int64_t kManifestSize = {len(manifest)};\n\n") + f.write("struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2; };\n") + f.write("inline constexpr ManifestEntry kManifest[] = {\n") + for nm, sh in manifest: + d = list(sh) + [1, 1, 1] + f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}, {d[2]}}},\n') + f.write("};\n\n") + emit_f(f, "kZ", z) + emit_f(f, "kZq", z_q) + f.write("inline constexpr int64_t kIndices[] = {\n " + + ", ".join(str(int(v)) for v in indices.reshape(-1).tolist()) + ",\n};\n\n") + f.write("} // namespace fvq_goldens\n") + print(f"wrote {p}: {len(manifest)} tensors, T={T}, codebook={CB_SIZE}x{CB_DIM}") + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/vllm/model_executor/models/fvq.cpp b/src/vllm/model_executor/models/fvq.cpp new file mode 100644 index 000000000..d79574086 --- /dev/null +++ b/src/vllm/model_executor/models/fvq.cpp @@ -0,0 +1,128 @@ +// FactorizedVectorQuantize. See fvq.h for the upstream anchors. +#include "vllm/model_executor/models/fvq.h" + +#include +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace fvq { + +std::vector MaterializeWeightNorm(const std::vector& g, const std::vector& v, + int64_t out_channels) { + VT_CHECK(out_channels > 0 && v.size() % static_cast(out_channels) == 0, + "fvq: weight_v does not divide by out_channels"); + const int64_t per = static_cast(v.size()) / out_channels; + std::vector w(v.size()); + for (int64_t o = 0; o < out_channels; ++o) { + double norm = 0.0; + for (int64_t i = 0; i < per; ++i) { + const double x = static_cast(v[static_cast(o * per + i)]); + norm += x * x; + } + norm = std::sqrt(norm); + const double scale = (norm > 0.0) ? static_cast(g[static_cast(o)]) / norm : 0.0; + for (int64_t i = 0; i < per; ++i) { + w[static_cast(o * per + i)] = + static_cast(static_cast(v[static_cast(o * per + i)]) * scale); + } + } + return w; +} + +namespace { + +// 1x1 convolution over [C_in, T] -> [C_out, T]. +std::vector Pointwise(const std::vector& x, int64_t in_dim, int64_t frames, + int64_t out_dim, const std::vector& w, + const std::vector& b) { + std::vector out(static_cast(out_dim * frames)); + for (int64_t o = 0; o < out_dim; ++o) { + for (int64_t t = 0; t < frames; ++t) { + double acc = b.empty() ? 0.0 : static_cast(b[static_cast(o)]); + for (int64_t i = 0; i < in_dim; ++i) { + acc += static_cast(w[static_cast(o * in_dim + i)]) * + static_cast(x[static_cast(i * frames + t)]); + } + out[static_cast(o * frames + t)] = static_cast(acc); + } + } + return out; +} + +} // namespace + +QuantizeResult Quantize(const std::vector& z, int64_t frames, int64_t input_dim, + int64_t codebook_dim, int64_t codebook_size, const Weights& wts) { + VT_CHECK(z.size() == static_cast(input_dim * frames), "fvq: z shape"); + + const std::vector in_w = MaterializeWeightNorm(wts.in_g, wts.in_v, codebook_dim); + const std::vector z_e = Pointwise(z, input_dim, frames, codebook_dim, in_w, wts.in_bias); + + // Codebook rows are normalized ONCE, for the search only. + std::vector cb_norm(static_cast(codebook_size * codebook_dim)); + for (int64_t c = 0; c < codebook_size; ++c) { + double n = 0.0; + for (int64_t d = 0; d < codebook_dim; ++d) { + const double x = static_cast(wts.codebook[static_cast(c * codebook_dim + d)]); + n += x * x; + } + n = std::sqrt(n); + // torch's F.normalize guards with eps=1e-12 rather than dividing by zero. + const double inv = 1.0 / std::max(n, 1e-12); + for (int64_t d = 0; d < codebook_dim; ++d) { + cb_norm[static_cast(c * codebook_dim + d)] = + static_cast(wts.codebook[static_cast(c * codebook_dim + d)]) * inv; + } + } + + QuantizeResult r; + r.indices.resize(static_cast(frames)); + std::vector picked(static_cast(codebook_dim * frames)); + for (int64_t t = 0; t < frames; ++t) { + std::vector e(static_cast(codebook_dim)); + double n = 0.0; + for (int64_t d = 0; d < codebook_dim; ++d) { + e[static_cast(d)] = static_cast(z_e[static_cast(d * frames + t)]); + n += e[static_cast(d)] * e[static_cast(d)]; + } + // Kept for fidelity to upstream, though it is provably a NO-OP for the + // SEARCH: dist = |e|^2 - 2 e.c + |c|^2, and for a fixed frame |e|^2 is + // constant across candidates while a normalized codebook makes |c|^2 = 1, + // so argmin reduces to argmax(e.c) -- which scaling e by 1/||e|| cannot + // change. Mutating this line away therefore CANNOT be caught, and that is + // an algebraic identity rather than a gap in the gate. + const double inv = 1.0 / std::max(std::sqrt(n), 1e-12); + for (double& v : e) v *= inv; + + int64_t best = 0; + double best_dist = std::numeric_limits::infinity(); + for (int64_t c = 0; c < codebook_size; ++c) { + double dist = 0.0; + for (int64_t d = 0; d < codebook_dim; ++d) { + const double diff = e[static_cast(d)] - + cb_norm[static_cast(c * codebook_dim + d)]; + dist += diff * diff; + } + if (dist < best_dist) { best_dist = dist; best = c; } + } + r.indices[static_cast(t)] = best; + // decode_code returns the RAW row, not the normalized one used for search. + for (int64_t d = 0; d < codebook_dim; ++d) { + picked[static_cast(d * frames + t)] = + wts.codebook[static_cast(best * codebook_dim + d)]; + } + } + + const std::vector out_w = MaterializeWeightNorm(wts.out_g, wts.out_v, input_dim); + r.z_q = Pointwise(picked, codebook_dim, frames, input_dim, out_w, wts.out_bias); + return r; +} + +} // namespace fvq +} // namespace models +} // namespace vllm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8f689b7bf..60c2fa4b9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -114,6 +114,8 @@ vllm_cpp_add_test(test_speech_engine vllm/multimodal/test_speech_engine.cpp) vllm_cpp_add_test(test_indextts2_family vllm/models/test_indextts2_family.cpp) vllm_cpp_add_test(test_campplus vllm/models/test_campplus.cpp) vllm_cpp_add_test(test_w2vbert vllm/models/test_w2vbert.cpp) +vllm_cpp_add_test(test_fvq vllm/models/test_fvq.cpp) +target_include_directories(test_fvq PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_w2vbert PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_campplus PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) vllm_cpp_add_test(test_gpt2 vllm/models/test_gpt2.cpp) diff --git a/tests/vllm/models/fvq_goldens.inc b/tests/vllm/models/fvq_goldens.inc new file mode 100644 index 000000000..b96fc2763 --- /dev/null +++ b/tests/vllm/models/fvq_goldens.inc @@ -0,0 +1,75 @@ +// GENERATED by scripts/gen-fvq-goldens.py -- do not edit. +// Amphion FactorizedVectorQuantize executed DIRECTLY (the quantizer +// EnhancedCodec uses; infer_v2_5.py:293). Weights rebuilt both sides from +// one FNV-1a -> splitmix64 stream. +#pragma once + +#include + +namespace fvq_goldens { + +inline constexpr int64_t kInputDim = 12; +inline constexpr int64_t kCodebookDim = 4; +inline constexpr int64_t kCodebookSize = 32; +inline constexpr int64_t kFrames = 9; + +inline constexpr int64_t kManifestSize = 7; + +struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2; }; +inline constexpr ManifestEntry kManifest[] = { + {"in_project.bias", 1, 4, 1, 1}, + {"in_project.weight_g", 3, 4, 1, 1}, + {"in_project.weight_v", 3, 4, 12, 1}, + {"out_project.bias", 1, 12, 1, 1}, + {"out_project.weight_g", 3, 12, 1, 1}, + {"out_project.weight_v", 3, 12, 4, 1}, + {"codebook.weight", 2, 32, 4, 1}, +}; + +inline constexpr float kZ[] = { + -1.545574367e-01F, -4.588297307e-01F, -7.250324488e-01F, -6.182825565e-01F, 1.034929678e-01F, 6.819719076e-01F, + -9.664313793e-01F, 4.269169271e-01F, -3.343501091e-01F, -2.566210926e-01F, -9.266157746e-01F, 2.457471751e-02F, + 8.934663534e-01F, 9.778553843e-01F, -5.203005672e-01F, 7.219227552e-01F, -1.672242433e-01F, -3.613638580e-01F, + 6.429681182e-01F, 8.688468933e-01F, -2.695875168e-01F, 7.728545666e-01F, 7.947573662e-01F, 6.507601142e-01F, + 4.110848308e-01F, 9.342327714e-02F, -7.623992562e-01F, 3.121811748e-01F, -5.033293962e-01F, 3.527414799e-01F, + 3.447940052e-01F, -8.835764527e-01F, 6.781489849e-01F, 5.691328049e-01F, 5.222323537e-01F, 6.693283916e-01F, + -8.700820208e-01F, -4.845757484e-01F, 7.896404862e-01F, 1.397003531e-01F, 1.464140713e-01F, 2.252997831e-02F, + 3.749421239e-01F, -3.735295236e-01F, -2.727279663e-01F, 8.405709267e-01F, -2.722047642e-02F, 3.740082681e-01F, + -3.692361414e-01F, 2.317368239e-01F, 6.472280622e-02F, 4.815174937e-01F, -9.114632607e-01F, -8.229241371e-01F, + 1.043168008e-01F, -9.531282783e-01F, 6.968171000e-01F, 2.977925539e-01F, -3.657130599e-01F, 3.666213751e-01F, + 7.639725804e-01F, -1.741219759e-01F, -1.222161725e-01F, 1.030891016e-01F, -8.173470497e-01F, 6.352062821e-01F, + 9.372111559e-01F, 8.375169635e-01F, -4.527049661e-01F, 6.348447800e-01F, 1.231873706e-01F, -3.594002724e-01F, + -5.291005969e-01F, -7.785803676e-01F, 9.411838651e-01F, -2.118016332e-01F, 2.483974099e-01F, 2.394061536e-01F, + 2.114423066e-01F, 8.547052145e-01F, 9.278769046e-02F, -5.048191920e-02F, 7.198385596e-01F, 9.768359661e-01F, + -1.134842783e-01F, 5.925132632e-01F, 6.489360332e-02F, -4.185374677e-01F, -3.063302636e-01F, -7.021018863e-01F, + 7.806903869e-02F, -3.715365529e-01F, 1.037695482e-01F, -5.093406886e-02F, 1.636363566e-01F, -3.398062289e-01F, + -3.734751046e-01F, -4.197919667e-01F, -9.634903669e-01F, -4.567942321e-01F, 9.322252274e-01F, 3.790197968e-01F, + 5.927465558e-01F, 6.054370999e-01F, -7.905610204e-01F, -1.072184294e-01F, -7.597165704e-01F, 3.475798070e-01F, +}; + +inline constexpr float kZq[] = { + 1.343785375e-01F, 1.344062090e-01F, 1.343785375e-01F, 1.343785375e-01F, 1.343785375e-01F, 1.343785375e-01F, + 1.343547255e-01F, 1.343785375e-01F, 1.343785375e-01F, 2.585926950e-01F, 2.583979070e-01F, 2.585926950e-01F, + 2.585926950e-01F, 2.585926950e-01F, 2.585926950e-01F, 2.581163347e-01F, 2.585926950e-01F, 2.585926950e-01F, + 7.042406499e-02F, 1.253251582e-01F, 7.042406499e-02F, 7.042406499e-02F, 7.042406499e-02F, 7.042406499e-02F, + 4.849734157e-02F, 7.042406499e-02F, 7.042406499e-02F, -3.096361160e-01F, -3.102814555e-01F, -3.096361160e-01F, + -3.096361160e-01F, -3.096361160e-01F, -3.096361160e-01F, -3.027741313e-01F, -3.096361160e-01F, -3.096361160e-01F, + 1.126777381e-01F, 1.405702233e-01F, 1.126777381e-01F, 1.126777381e-01F, 1.126777381e-01F, 1.126777381e-01F, + 1.124831811e-01F, 1.126777381e-01F, 1.126777381e-01F, 1.645249575e-01F, 1.804752946e-01F, 1.645249575e-01F, + 1.645249575e-01F, 1.645249575e-01F, 1.645249575e-01F, 1.440110952e-01F, 1.645249575e-01F, 1.645249575e-01F, + 4.379250854e-02F, 5.276123434e-02F, 4.379250854e-02F, 4.379250854e-02F, 4.379250854e-02F, 4.379250854e-02F, + 4.905284941e-02F, 4.379250854e-02F, 4.379250854e-02F, 2.032171488e-01F, 2.056553811e-01F, 2.032171488e-01F, + 2.032171488e-01F, 2.032171488e-01F, 2.032171488e-01F, 2.081153691e-01F, 2.032171488e-01F, 2.032171488e-01F, + -1.048087887e-02F, -1.550098229e-02F, -1.048088074e-02F, -1.048088074e-02F, -1.048088074e-02F, -1.048087887e-02F, + -1.097315736e-02F, -1.048088074e-02F, -1.048088074e-02F, -3.485272452e-02F, -3.860617802e-02F, -3.485272452e-02F, + -3.485272452e-02F, -3.485272452e-02F, -3.485272452e-02F, -3.762440011e-02F, -3.485272452e-02F, -3.485272452e-02F, + 2.548277676e-01F, 2.289771736e-01F, 2.548277676e-01F, 2.548277676e-01F, 2.548277676e-01F, 2.548277676e-01F, + 3.120260239e-01F, 2.548277676e-01F, 2.548277676e-01F, 3.199279308e-02F, 1.896337979e-02F, 3.199279308e-02F, + 3.199279308e-02F, 3.199279308e-02F, 3.199279308e-02F, 4.320196435e-02F, 3.199279308e-02F, 3.199279308e-02F, +}; + +inline constexpr int64_t kIndices[] = { + 12, 6, 12, 12, 12, 12, 2, 12, 12, +}; + +} // namespace fvq_goldens diff --git a/tests/vllm/models/test_fvq.cpp b/tests/vllm/models/test_fvq.cpp new file mode 100644 index 000000000..96cec2e5f --- /dev/null +++ b/tests/vllm/models/test_fvq.cpp @@ -0,0 +1,96 @@ +// EnhancedCodec quantizer parity gate (#634). +// +// The DISCRETE output here is the semantic code the talker consumes, so an +// index that is off by one entry is a different utterance -- indices are gated +// EXACTLY, not within a tolerance. +#include +#include +#include +#include +#include +#include + +#include "doctest/doctest.h" +#include "fvq_goldens.inc" +#include "vllm/model_executor/models/fvq.h" + +namespace { +using namespace fvq_goldens; + +std::vector Rand(const std::string& name, int64_t n, double scale) { + uint64_t seed = 0xCBF29CE484222325ULL; + for (char c : name) { seed ^= static_cast(c); seed *= 0x100000001B3ULL; } + std::vector o(static_cast(n)); + for (int64_t i = 0; i < n; ++i) { + uint64_t x = seed + static_cast(i); + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + z ^= z >> 31; + o[static_cast(i)] = + static_cast(((static_cast(z >> 11) * 0x1.0p-53) * 2.0 - 1.0) * scale); + } + return o; +} + +vllm::models::fvq::Weights BuildWeights() { + std::map> m; + for (int64_t i = 0; i < kManifestSize; ++i) { + const auto& e = kManifest[i]; + const int64_t d[3] = {e.d0, e.d1, e.d2}; + int64_t n = 1; + for (int64_t k = 0; k < e.rank; ++k) n *= d[k]; + m[e.name] = Rand(e.name, n, 0.3); + } + vllm::models::fvq::Weights w; + w.in_g = m.at("in_project.weight_g"); + w.in_v = m.at("in_project.weight_v"); + w.in_bias = m.at("in_project.bias"); + w.out_g = m.at("out_project.weight_g"); + w.out_v = m.at("out_project.weight_v"); + w.out_bias = m.at("out_project.bias"); + w.codebook = m.at("codebook.weight"); + return w; +} +} // namespace + +TEST_CASE("fvq indices match upstream EXACTLY") { + const vllm::models::fvq::Weights w = BuildWeights(); + const std::vector z(std::begin(kZ), std::end(kZ)); + const auto r = vllm::models::fvq::Quantize(z, kFrames, kInputDim, kCodebookDim, kCodebookSize, w); + REQUIRE(r.indices.size() == static_cast(kFrames)); + for (int64_t t = 0; t < kFrames; ++t) { + // An index off by one entry is a different utterance, not a small error. + CHECK(r.indices[static_cast(t)] == kIndices[t]); + } +} + +TEST_CASE("fvq dequantized output matches upstream") { + // Distances are computed on NORMALIZED vectors, but the entry returned is the + // RAW codebook row. Returning the normalized row keeps the indices identical + // and only moves these values, so this case is what separates the two. + const vllm::models::fvq::Weights w = BuildWeights(); + const std::vector z(std::begin(kZ), std::end(kZ)); + const auto r = vllm::models::fvq::Quantize(z, kFrames, kInputDim, kCodebookDim, kCodebookSize, w); + REQUIRE(r.z_q.size() == static_cast(kInputDim * kFrames)); + double worst = 0.0; + for (size_t i = 0; i < r.z_q.size(); ++i) { + worst = std::max(worst, std::fabs(static_cast(r.z_q[i]) - kZq[i])); + } + INFO("max abs diff vs upstream z_q: ", worst); + CHECK(worst < 1e-5); +} + +TEST_CASE("fvq weight-norm materialization is g * v / ||v||") { + // The checkpoint stores (g, v), never the effective weight. Getting the norm + // axis wrong still produces a weight of the right shape. + const std::vector g{2.0F, 3.0F}; + const std::vector v{3.0F, 4.0F, 0.0F, 5.0F}; // rows [3,4] (norm 5), [0,5] (norm 5) + const std::vector w = vllm::models::fvq::MaterializeWeightNorm(g, v, 2); + REQUIRE(w.size() == 4U); + CHECK(w[0] == doctest::Approx(2.0 * 3.0 / 5.0).epsilon(1e-6)); + CHECK(w[1] == doctest::Approx(2.0 * 4.0 / 5.0).epsilon(1e-6)); + CHECK(w[2] == doctest::Approx(0.0).epsilon(1e-6)); + CHECK(w[3] == doctest::Approx(3.0 * 5.0 / 5.0).epsilon(1e-6)); +} From ba0732d77449e0900e3ec2153298e87aef44c11f Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:08:55 +0200 Subject: [PATCH 28/55] =?UTF-8?q?feat(MODEL-MM-indextts2):=20VocosBackbone?= =?UTF-8?q?=20=E2=80=94=20EnhancedCodec=20COMPLETE=20(#634)=20(#715)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Completes EnhancedCodec: VocosBackbone (ConvNeXt-1D) joins the factorized VQ already on main. 2 cases / 91 assertions against upstream executed directly. THE OUTPUT IS [T, dim], not [dim, T] — the final layer norm runs on the transposed tensor and is never transposed back. Same element count either way, so the layout is asserted alongside the values. A TOLERANCE THAT ACCEPTED A WRONG ACTIVATION, found by mutation and fixed in three steps, each correcting a wrong hypothesis: 1. Swapping exact-erf GELU for tanh-approx PASSED at 2e-5. First guess was small activations, so the INPUT scale went 1.0 -> 4.0. No effect: the LayerNorm before pwconv1 normalizes input scale away entirely, so the pre-activation magnitude is set by the WEIGHTS. 2. Raised pwconv1's weight scale 0.3 -> 1.0, moving |x| from ~0.85 to ~2.8 where the GELU forms differ most. Still passed. 3. MEASURED instead of guessing again: the true end-to-end difference is 1.41e-5, genuinely below the 2e-5 bound. Tightened to 2e-6, which the correct port clears comfortably; the mutation now goes RED. A bound must be set by what the DEFECT costs, not by what feels safe. Fixture and tolerance are annotated with the measurement. Four of six pipeline stages complete. Remaining: talker head, length regulator, S2Mel. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- CMakeLists.txt | 1 + include/vllm/model_executor/models/vocos.h | 49 ++++++++ scripts/gen-vocos-goldens.py | 103 ++++++++++++++++ src/vllm/model_executor/models/vocos.cpp | 137 +++++++++++++++++++++ tests/CMakeLists.txt | 2 + tests/vllm/models/test_vocos.cpp | 94 ++++++++++++++ tests/vllm/models/vocos_goldens.inc | 89 +++++++++++++ 7 files changed, 475 insertions(+) create mode 100644 include/vllm/model_executor/models/vocos.h create mode 100644 scripts/gen-vocos-goldens.py create mode 100644 src/vllm/model_executor/models/vocos.cpp create mode 100644 tests/vllm/models/test_vocos.cpp create mode 100644 tests/vllm/models/vocos_goldens.inc diff --git a/CMakeLists.txt b/CMakeLists.txt index 5dce3a9d7..616e63532 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -773,6 +773,7 @@ add_library(vllm STATIC src/vllm/model_executor/models/campplus.cpp src/vllm/model_executor/models/w2vbert.cpp src/vllm/model_executor/models/fvq.cpp + src/vllm/model_executor/models/vocos.cpp src/vllm/model_executor/models/minimax_h3_video_vae.cpp src/vllm/model_executor/models/minimax_h3_video_vae_device.cpp src/vllm/model_executor/models/minimax_h3_vae_cnn.cpp diff --git a/include/vllm/model_executor/models/vocos.h b/include/vllm/model_executor/models/vocos.h new file mode 100644 index 000000000..7ae296318 --- /dev/null +++ b/include/vllm/model_executor/models/vocos.h @@ -0,0 +1,49 @@ +// VocosBackbone — EnhancedCodec's ConvNeXt-1D encoder (#634). +// +// `EnhancedCodec.quantize` runs its input through this before the quantizer +// (indextts/codec/models.py:187). Each block is ConvNeXt adapted to 1-D audio: +// depthwise conv, layer norm, two pointwise linears with GELU, and a LEARNED +// per-channel layer scale. +// +// TWO THINGS THAT ARE EASY TO GET WRONG: +// eps IS 1e-6 here, not the 1e-5 used elsewhere in this codebase. +// THE OUTPUT IS [T, dim], not [dim, T]: the final layer norm is applied to the +// transposed tensor and never transposed back, which is why the caller writes +// `encoder(x.transpose(1,2)).transpose(1,2)`. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace vocos { + +struct BlockWeights { + std::vector dw_weight, dw_bias; // depthwise conv, k=7, groups=dim + std::vector ln_gamma, ln_beta; + std::vector pw1_w, pw1_b; // [intermediate, dim] + std::vector pw2_w, pw2_b; // [dim, intermediate] + std::vector gamma; // layer scale, [dim] +}; + +struct BackboneWeights { + std::vector embed_w, embed_b; // Conv1d(input_channels -> dim), k=7 + std::vector norm_gamma, norm_beta; + std::vector blocks; + std::vector final_gamma, final_beta; +}; + +// One ConvNeXt block over a [dim, frames] signal; returns [dim, frames]. +std::vector ConvNeXtBlock(const std::vector& x, int64_t dim, int64_t frames, + int64_t intermediate, const BlockWeights& weights, double eps); + +// VocosBackbone: embed -> norm -> blocks -> final norm. +// Input is [input_channels, frames]; OUTPUT IS [frames, dim]. +std::vector Backbone(const std::vector& x, int64_t input_channels, int64_t frames, + int64_t dim, int64_t intermediate, const BackboneWeights& weights, + double eps); + +} // namespace vocos +} // namespace models +} // namespace vllm diff --git a/scripts/gen-vocos-goldens.py b/scripts/gen-vocos-goldens.py new file mode 100644 index 000000000..c1b6fe405 --- /dev/null +++ b/scripts/gen-vocos-goldens.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/vocos_goldens.inc — EnhancedCodec's VocosBackbone oracle. + +`EnhancedCodec.quantize` runs its input through a VocosBackbone before the +quantizer (indextts/codec/models.py:187). The backbone is ConvNeXt-1D: depthwise +conv, layer norm at eps=1e-6, two pointwise linears with GELU, and a LEARNED +per-channel layer scale. + +Upstream executed DIRECTLY, loaded by file path. + +Usage: + VOCOS_SRC= python3 scripts/gen-vocos-goldens.py --out +""" +from __future__ import annotations +import argparse, importlib.util, os +from pathlib import Path +import numpy as np, torch + +MASK = (1 << 64) - 1 +def fnv(n): + h = 0xCBF29CE484222325 + for b in n.encode(): h ^= b; h = (h * 0x100000001B3) & MASK + return h +def sm(x): + x = (x + 0x9E3779B97F4A7C15) & MASK + z = x + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & MASK + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & MASK + return z ^ (z >> 31) +def rnd(name, n, scale): + s = fnv(name); o = np.empty(n) + for i in range(n): o[i] = (((sm((s + i) & MASK) >> 11) * 2.0**-53) * 2 - 1) * scale + return o +def P(name, shape, scale=0.3): + n = 1 + for d in shape: n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + +def main() -> int: + ap = argparse.ArgumentParser(); ap.add_argument("--out", required=True) + a = ap.parse_args() + spec = importlib.util.spec_from_file_location("vocos", Path(os.environ["VOCOS_SRC"])) + m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) + + torch.manual_seed(0) + IN_CH, DIM, INTER, LAYERS, T = 6, 8, 16, 3, 11 + b = m.VocosBackbone(input_channels=IN_CH, dim=DIM, intermediate_dim=INTER, + num_layers=LAYERS, adanorm_num_embeddings=None).eval() + manifest = [] + with torch.no_grad(): + for n, p in list(b.named_parameters()) + list(b.named_buffers()): + if n.endswith("num_batches_tracked"): continue + # pwconv1 gets a LARGER scale on purpose. The LayerNorm before it + # normalizes the input away, so the pre-activation magnitude is set + # by THESE weights alone. At the default 0.3 the pre-activations sit + # near |x| ~ 0.85, where exact-erf and tanh-approx GELU differ by + # ~1.5e-4 -- which pwconv2 and the 1/num_layers layer scale then + # attenuate below any sane tolerance, letting a wrong activation + # pass. At 1.0 the pre-activations reach |x| ~ 2.8 and the + # difference survives to the output. Found by mutation. + scale = 1.0 if "pwconv1.weight" in n else 0.3 + p.copy_(P(n, tuple(p.shape) if p.dim() else (1,), scale).reshape(p.shape)) + manifest.append((n, list(p.shape))) + # Scale 4.0, not 1.0: the pointwise pre-activations must reach |x| ~ 2-3, + # where the exact-erf and tanh-approximation GELUs differ by ~1e-3. At + # scale 1.0 they agree to ~1e-6 and a wrong activation passes the gate -- + # found by mutation, which is what a fixture gap looks like. + x = P("vx", (1, IN_CH, T), 4.0) + with torch.no_grad(): + out = b(x) # NOTE: returns (B, T, C) -- not transposed back + + def emit(f, name, t): + arr = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) + f.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(arr), 6): + f.write(" " + ", ".join(f"{v:.9e}F" for v in arr[i:i+6]) + ",\n") + f.write("};\n\n") + + p = Path(a.out) + with p.open("w") as f: + f.write("// GENERATED by scripts/gen-vocos-goldens.py -- do not edit.\n") + f.write("// VocosBackbone executed DIRECTLY (indextts/codec/kmeans/vocos.py).\n") + f.write("// The backbone returns (B, T, C): its final layer norm is applied to the\n") + f.write("// TRANSPOSED tensor and never transposed back, which is why EnhancedCodec\n") + f.write("// writes `self.encoder(x.transpose(1,2)).transpose(1,2)`.\n") + f.write("#pragma once\n\n#include \n\nnamespace vocos_goldens {\n\n") + for n, v in (("kInChannels", IN_CH), ("kDim", DIM), ("kIntermediate", INTER), + ("kLayers", LAYERS), ("kFrames", T)): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write(f"\ninline constexpr int64_t kManifestSize = {len(manifest)};\n\n") + f.write("struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2; };\n") + f.write("inline constexpr ManifestEntry kManifest[] = {\n") + for nm, sh in manifest: + d = list(sh) + [1, 1, 1] + f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}, {d[2]}}},\n') + f.write("};\n\n") + emit(f, "kVx", x); emit(f, "kVocosOut", out) + f.write("} // namespace vocos_goldens\n") + print(f"wrote {p}: {len(manifest)} tensors, layers={LAYERS}, out shape {tuple(out.shape)}") + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/vllm/model_executor/models/vocos.cpp b/src/vllm/model_executor/models/vocos.cpp new file mode 100644 index 000000000..066a8b4ed --- /dev/null +++ b/src/vllm/model_executor/models/vocos.cpp @@ -0,0 +1,137 @@ +// VocosBackbone. See vocos.h for the upstream anchors. +#include "vllm/model_executor/models/vocos.h" + +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace vocos { +namespace { + +// LayerNorm over the CHANNEL axis of a [dim, frames] signal, i.e. per frame. +void LayerNormChannels(std::vector& x, int64_t dim, int64_t frames, + const std::vector& gamma, const std::vector& beta, + double eps) { + for (int64_t t = 0; t < frames; ++t) { + double mean = 0.0; + for (int64_t c = 0; c < dim; ++c) mean += static_cast(x[static_cast(c * frames + t)]); + mean /= static_cast(dim); + double var = 0.0; + for (int64_t c = 0; c < dim; ++c) { + const double d = static_cast(x[static_cast(c * frames + t)]) - mean; + var += d * d; + } + var /= static_cast(dim); + const double inv = 1.0 / std::sqrt(var + eps); + for (int64_t c = 0; c < dim; ++c) { + const size_t i = static_cast(c * frames + t); + x[i] = static_cast((static_cast(x[i]) - mean) * inv * + static_cast(gamma[static_cast(c)]) + + static_cast(beta[static_cast(c)])); + } + } +} + +// nn.GELU default is the EXACT erf form, not the tanh approximation. +double Gelu(double x) { return 0.5 * x * (1.0 + std::erf(x / std::sqrt(2.0))); } + +} // namespace + +std::vector ConvNeXtBlock(const std::vector& x, int64_t dim, int64_t frames, + int64_t intermediate, const BlockWeights& w, double eps) { + VT_CHECK(x.size() == static_cast(dim * frames), "vocos: block shape"); + constexpr int64_t kKernel = 7; + const int64_t pad = 3; + + // depthwise: groups == dim, so each channel has its own [1, 7] kernel. + std::vector h(static_cast(dim * frames)); + for (int64_t c = 0; c < dim; ++c) { + for (int64_t t = 0; t < frames; ++t) { + double acc = w.dw_bias.empty() ? 0.0 : static_cast(w.dw_bias[static_cast(c)]); + for (int64_t k = 0; k < kKernel; ++k) { + const int64_t src = t + k - pad; + if (src < 0 || src >= frames) continue; + acc += static_cast(w.dw_weight[static_cast(c * kKernel + k)]) * + static_cast(x[static_cast(c * frames + src)]); + } + h[static_cast(c * frames + t)] = static_cast(acc); + } + } + + LayerNormChannels(h, dim, frames, w.ln_gamma, w.ln_beta, eps); + + // pointwise linears act per FRAME over the channel axis. + std::vector out(static_cast(dim * frames)); + std::vector mid(static_cast(intermediate)); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t o = 0; o < intermediate; ++o) { + double acc = static_cast(w.pw1_b[static_cast(o)]); + for (int64_t c = 0; c < dim; ++c) { + acc += static_cast(w.pw1_w[static_cast(o * dim + c)]) * + static_cast(h[static_cast(c * frames + t)]); + } + mid[static_cast(o)] = Gelu(acc); + } + for (int64_t c = 0; c < dim; ++c) { + double acc = static_cast(w.pw2_b[static_cast(c)]); + for (int64_t o = 0; o < intermediate; ++o) { + acc += static_cast(w.pw2_w[static_cast(c * intermediate + o)]) * + mid[static_cast(o)]; + } + // LEARNED layer scale, then the residual. + acc *= static_cast(w.gamma[static_cast(c)]); + out[static_cast(c * frames + t)] = + static_cast(acc + static_cast(x[static_cast(c * frames + t)])); + } + } + return out; +} + +std::vector Backbone(const std::vector& x, int64_t input_channels, int64_t frames, + int64_t dim, int64_t intermediate, const BackboneWeights& w, + double eps) { + VT_CHECK(x.size() == static_cast(input_channels * frames), "vocos: backbone shape"); + constexpr int64_t kKernel = 7; + const int64_t pad = 3; + + std::vector h(static_cast(dim * frames)); + for (int64_t o = 0; o < dim; ++o) { + for (int64_t t = 0; t < frames; ++t) { + double acc = static_cast(w.embed_b[static_cast(o)]); + for (int64_t c = 0; c < input_channels; ++c) { + for (int64_t k = 0; k < kKernel; ++k) { + const int64_t src = t + k - pad; + if (src < 0 || src >= frames) continue; + acc += static_cast( + w.embed_w[static_cast((o * input_channels + c) * kKernel + k)]) * + static_cast(x[static_cast(c * frames + src)]); + } + } + h[static_cast(o * frames + t)] = static_cast(acc); + } + } + + LayerNormChannels(h, dim, frames, w.norm_gamma, w.norm_beta, eps); + for (const BlockWeights& b : w.blocks) { + h = ConvNeXtBlock(h, dim, frames, intermediate, b, eps); + } + LayerNormChannels(h, dim, frames, w.final_gamma, w.final_beta, eps); + + // The final norm is applied to the TRANSPOSED tensor upstream and never + // transposed back, so the result is [frames, dim]. + std::vector out(static_cast(frames * dim)); + for (int64_t c = 0; c < dim; ++c) { + for (int64_t t = 0; t < frames; ++t) { + out[static_cast(t * dim + c)] = h[static_cast(c * frames + t)]; + } + } + return out; +} + +} // namespace vocos +} // namespace models +} // namespace vllm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 60c2fa4b9..24ca5975b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -115,6 +115,8 @@ vllm_cpp_add_test(test_indextts2_family vllm/models/test_indextts2_family.cpp) vllm_cpp_add_test(test_campplus vllm/models/test_campplus.cpp) vllm_cpp_add_test(test_w2vbert vllm/models/test_w2vbert.cpp) vllm_cpp_add_test(test_fvq vllm/models/test_fvq.cpp) +vllm_cpp_add_test(test_vocos vllm/models/test_vocos.cpp) +target_include_directories(test_vocos PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_fvq PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_w2vbert PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_campplus PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) diff --git a/tests/vllm/models/test_vocos.cpp b/tests/vllm/models/test_vocos.cpp new file mode 100644 index 000000000..803e98ce3 --- /dev/null +++ b/tests/vllm/models/test_vocos.cpp @@ -0,0 +1,94 @@ +// VocosBackbone parity gate — EnhancedCodec's ConvNeXt-1D encoder (#634). +#include +#include +#include +#include +#include +#include + +#include "doctest/doctest.h" +#include "vllm/model_executor/models/vocos.h" +#include "vocos_goldens.inc" + +namespace { +using namespace vocos_goldens; + +std::vector Rand(const std::string& name, int64_t n, double scale) { + uint64_t seed = 0xCBF29CE484222325ULL; + for (char c : name) { seed ^= static_cast(c); seed *= 0x100000001B3ULL; } + std::vector o(static_cast(n)); + for (int64_t i = 0; i < n; ++i) { + uint64_t x = seed + static_cast(i); + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + z ^= z >> 31; + o[static_cast(i)] = + static_cast(((static_cast(z >> 11) * 0x1.0p-53) * 2.0 - 1.0) * scale); + } + return o; +} + +vllm::models::vocos::BackboneWeights Build() { + std::map> m; + for (int64_t i = 0; i < kManifestSize; ++i) { + const auto& e = kManifest[i]; + const int64_t d[3] = {e.d0, e.d1, e.d2}; + int64_t n = 1; + for (int64_t k = 0; k < e.rank; ++k) n *= d[k]; + m[e.name] = Rand(e.name, n, std::string(e.name).find("pwconv1.weight") != std::string::npos ? 1.0 : 0.3); + } + vllm::models::vocos::BackboneWeights w; + w.embed_w = m.at("embed.weight"); w.embed_b = m.at("embed.bias"); + w.norm_gamma = m.at("norm.weight"); w.norm_beta = m.at("norm.bias"); + w.final_gamma = m.at("final_layer_norm.weight"); + w.final_beta = m.at("final_layer_norm.bias"); + for (int64_t i = 0; i < kLayers; ++i) { + const std::string p = "convnext." + std::to_string(i) + "."; + vllm::models::vocos::BlockWeights b; + b.dw_weight = m.at(p + "dwconv.weight"); b.dw_bias = m.at(p + "dwconv.bias"); + b.ln_gamma = m.at(p + "norm.weight"); b.ln_beta = m.at(p + "norm.bias"); + b.pw1_w = m.at(p + "pwconv1.weight"); b.pw1_b = m.at(p + "pwconv1.bias"); + b.pw2_w = m.at(p + "pwconv2.weight"); b.pw2_b = m.at(p + "pwconv2.bias"); + b.gamma = m.at(p + "gamma"); + w.blocks.push_back(std::move(b)); + } + return w; +} +} // namespace + +TEST_CASE("vocos backbone matches upstream and returns [T, dim]") { + // The output orientation is the trap: the final layer norm runs on the + // TRANSPOSED tensor and is never transposed back. Returning [dim, T] has the + // same element count, so only the layout assertion and the values catch it. + const auto w = Build(); + const std::vector x(std::begin(kVx), std::end(kVx)); + const std::vector got = + vllm::models::vocos::Backbone(x, kInChannels, kFrames, kDim, kIntermediate, w, 1e-6); + REQUIRE(got.size() == static_cast(kFrames * kDim)); + double worst = 0.0; + for (size_t i = 0; i < got.size(); ++i) { + worst = std::max(worst, std::fabs(static_cast(got[i]) - kVocosOut[i])); + } + INFO("max abs diff vs upstream VocosBackbone: ", worst); + // 2e-6, not 2e-5. Measured: the exact-erf vs tanh-approx GELU difference + // reaches only 1.41e-5 on this fixture, so a 2e-5 tolerance accepts the wrong + // activation outright -- found by mutation. The correct port agrees far more + // tightly than that, so the bound is set by what the DEFECT costs, not by + // what felt safe. + CHECK(worst < 2e-6); +} + +TEST_CASE("vocos block residual keeps a zeroed layer scale as the identity") { + // gamma is a LEARNED per-channel scale on the block's branch. With gamma = 0 + // the block must reduce to exactly its input -- which pins that the residual + // is added AFTER the scale, not before. + auto w = Build(); + for (float& g : w.blocks[0].gamma) g = 0.0F; + const std::vector x = Rand("blockx", kDim * kFrames, 4.0); + const std::vector got = vllm::models::vocos::ConvNeXtBlock( + x, kDim, kFrames, kIntermediate, w.blocks[0], 1e-6); + REQUIRE(got.size() == x.size()); + for (size_t i = 0; i < x.size(); ++i) CHECK(got[i] == x[i]); +} diff --git a/tests/vllm/models/vocos_goldens.inc b/tests/vllm/models/vocos_goldens.inc new file mode 100644 index 000000000..16d61ba10 --- /dev/null +++ b/tests/vllm/models/vocos_goldens.inc @@ -0,0 +1,89 @@ +// GENERATED by scripts/gen-vocos-goldens.py -- do not edit. +// VocosBackbone executed DIRECTLY (indextts/codec/kmeans/vocos.py). +// The backbone returns (B, T, C): its final layer norm is applied to the +// TRANSPOSED tensor and never transposed back, which is why EnhancedCodec +// writes `self.encoder(x.transpose(1,2)).transpose(1,2)`. +#pragma once + +#include + +namespace vocos_goldens { + +inline constexpr int64_t kInChannels = 6; +inline constexpr int64_t kDim = 8; +inline constexpr int64_t kIntermediate = 16; +inline constexpr int64_t kLayers = 3; +inline constexpr int64_t kFrames = 11; + +inline constexpr int64_t kManifestSize = 33; + +struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1, d2; }; +inline constexpr ManifestEntry kManifest[] = { + {"embed.weight", 3, 8, 6, 7}, + {"embed.bias", 1, 8, 1, 1}, + {"norm.weight", 1, 8, 1, 1}, + {"norm.bias", 1, 8, 1, 1}, + {"convnext.0.gamma", 1, 8, 1, 1}, + {"convnext.0.dwconv.weight", 3, 8, 1, 7}, + {"convnext.0.dwconv.bias", 1, 8, 1, 1}, + {"convnext.0.norm.weight", 1, 8, 1, 1}, + {"convnext.0.norm.bias", 1, 8, 1, 1}, + {"convnext.0.pwconv1.weight", 2, 16, 8, 1}, + {"convnext.0.pwconv1.bias", 1, 16, 1, 1}, + {"convnext.0.pwconv2.weight", 2, 8, 16, 1}, + {"convnext.0.pwconv2.bias", 1, 8, 1, 1}, + {"convnext.1.gamma", 1, 8, 1, 1}, + {"convnext.1.dwconv.weight", 3, 8, 1, 7}, + {"convnext.1.dwconv.bias", 1, 8, 1, 1}, + {"convnext.1.norm.weight", 1, 8, 1, 1}, + {"convnext.1.norm.bias", 1, 8, 1, 1}, + {"convnext.1.pwconv1.weight", 2, 16, 8, 1}, + {"convnext.1.pwconv1.bias", 1, 16, 1, 1}, + {"convnext.1.pwconv2.weight", 2, 8, 16, 1}, + {"convnext.1.pwconv2.bias", 1, 8, 1, 1}, + {"convnext.2.gamma", 1, 8, 1, 1}, + {"convnext.2.dwconv.weight", 3, 8, 1, 7}, + {"convnext.2.dwconv.bias", 1, 8, 1, 1}, + {"convnext.2.norm.weight", 1, 8, 1, 1}, + {"convnext.2.norm.bias", 1, 8, 1, 1}, + {"convnext.2.pwconv1.weight", 2, 16, 8, 1}, + {"convnext.2.pwconv1.bias", 1, 16, 1, 1}, + {"convnext.2.pwconv2.weight", 2, 8, 16, 1}, + {"convnext.2.pwconv2.bias", 1, 8, 1, 1}, + {"final_layer_norm.weight", 1, 8, 1, 1}, + {"final_layer_norm.bias", 1, 8, 1, 1}, +}; + +inline constexpr float kVx[] = { + -2.594892979e+00F, -2.654025555e+00F, 2.764772892e+00F, 2.306895256e+00F, -5.863245726e-01F, 3.514606714e+00F, + -3.809034348e+00F, -2.424288511e+00F, -9.962548018e-01F, 1.994585991e-02F, 2.209203720e+00F, 3.545157194e+00F, + 9.789897203e-01F, 2.786373377e+00F, 2.382820368e+00F, -2.274915695e+00F, -2.738338947e+00F, 6.457907557e-01F, + -2.141479254e+00F, -3.542307377e+00F, 3.527634621e+00F, 2.772752047e+00F, 2.121381760e+00F, 2.454514027e+00F, + 2.448149584e-02F, 5.973197818e-01F, -3.604137659e+00F, 8.046765924e-01F, 6.961173415e-01F, -2.181510448e+00F, + 2.083607912e+00F, 2.272261858e+00F, 2.050409317e+00F, -3.951352596e+00F, 1.547806501e+00F, -1.082729340e+00F, + -2.001280785e-01F, -3.757426500e+00F, 1.480759501e+00F, 9.745687991e-02F, -5.254653692e-01F, 2.370080948e+00F, + -1.849179864e+00F, 1.094127297e-01F, -6.882314086e-01F, -8.164075613e-01F, -2.324311256e+00F, -8.980875611e-01F, + -1.003364921e+00F, -3.564796686e+00F, 3.297758579e+00F, 6.788129807e-01F, -7.408863306e-01F, -5.855057761e-02F, + -2.101770401e+00F, -6.941277981e-01F, 3.633866072e+00F, 1.237495422e+00F, 4.798901379e-01F, -2.220849752e+00F, + -2.894403934e+00F, 1.655400515e+00F, 2.845009089e+00F, 2.457281828e+00F, 1.107348323e+00F, -2.147860050e+00F, +}; + +inline constexpr float kVocosOut[] = { + -5.358966589e-01F, -3.448932171e-01F, 1.188943759e-01F, 1.041682288e-01F, -7.551536709e-02F, 3.550323099e-02F, + -1.434633434e-01F, -2.012435198e-01F, -6.032375097e-01F, -3.371537924e-01F, 1.056333706e-01F, 7.798317075e-02F, + -1.010268331e-01F, 5.712328013e-03F, -4.335879087e-01F, -2.015507035e-02F, -4.627881348e-01F, -1.490730345e-01F, + 3.429289162e-02F, -1.017466262e-01F, -1.365793943e-01F, 3.211809695e-02F, 9.091620892e-02F, -4.019576907e-01F, + -7.476106882e-01F, -2.688550949e-01F, 9.243204445e-02F, 2.214258537e-02F, -2.830515504e-01F, 2.505271696e-02F, + -4.880561531e-01F, -2.722174823e-01F, -5.863966942e-01F, -1.691246629e-01F, 8.310119621e-03F, -1.762967855e-01F, + -1.514714509e-01F, 3.122581169e-02F, -1.888974160e-01F, -3.892194033e-01F, -3.088415861e-01F, -2.107795775e-01F, + 9.027997404e-02F, -2.557885274e-02F, -1.859297007e-01F, 3.249079362e-02F, 1.429478675e-01F, -5.068831444e-01F, + -5.606637001e-01F, -3.661773503e-01F, 1.147978380e-01F, 8.820290118e-02F, -1.535376757e-01F, 4.392971098e-02F, + -3.330028355e-01F, -1.857010871e-01F, -3.789850175e-01F, -3.787411153e-01F, 1.021277979e-01F, 9.783924371e-02F, + -1.168206856e-01F, 1.709593460e-02F, -2.305363715e-01F, -1.687848419e-01F, -2.832316756e-01F, -3.580487072e-01F, + 1.062791646e-01F, 9.573794901e-02F, -3.345124722e-01F, 3.214867786e-02F, -2.999492586e-01F, -2.900395691e-01F, + -7.502313852e-01F, -1.842864752e-01F, 1.206018925e-01F, 4.783287644e-02F, -2.584510744e-01F, -3.423907980e-02F, + -3.089868426e-01F, -3.627388179e-01F, -6.686926484e-01F, -2.731008828e-01F, 6.225382909e-02F, 5.050810426e-02F, + -6.136716530e-02F, -9.981620125e-03F, -9.728300571e-02F, -3.202885687e-01F, +}; + +} // namespace vocos_goldens From bbdccee181db75a501cdb9ead4db042fd347b179 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:16:05 +0200 Subject: [PATCH 29/55] feat(MODEL-MM-indextts2): S2Mel length-regulator primitives (#634) (#716) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Ports the three primitives behind InterpolateRegulator (infer_v2_5.py:650), which stretches the semantic sequence onto the target mel length. Gated against torch, which IS upstream here since these are stock nn modules. 5 cases / 278 assertions. NEAREST INTERPOLATION uses src = floor(i * in_len / out_len). Rounding or a half-offset (i + 0.5) * ratio AGREES at exact multiples and diverges only at fractional ratios, where it shifts every frame by one and still produces audio. Goldens cover 7 -> 17 (non-integer), 7 -> 14 (exact double) and 7 -> 3 (downsample), compared EXACTLY rather than with a tolerance: nearest interpolation copies samples, it never blends them. GROUPNORM shares statistics across each GROUP and all frames; per-channel statistics also normalize and are a different model. MISH is x*tanh(softplus(x)) with torch's large-x guard; SiLU resembles it closely enough to earn a case. MUTATION EVIDENCE with compile status: round-instead-of-floor, half-offset index, per-channel GroupNorm statistics and SiLU-for-Mish each RED at compile_err=0. The SiLU mutation's first attempt reported compile_err=1 (unused `sp` under -Werror) so its verdict was discarded and re-run — the sixth time this campaign that compile status decided whether a result meant anything. Five of six stages have their pieces. Remaining: talker head, S2Mel CFM/DiT. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- CMakeLists.txt | 1 + include/vllm/model_executor/models/lenreg.h | 38 ++++++ scripts/gen-lenreg-goldens.py | 93 +++++++++++++++ src/vllm/model_executor/models/lenreg.cpp | 79 +++++++++++++ tests/CMakeLists.txt | 2 + tests/vllm/models/lenreg_goldens.inc | 123 ++++++++++++++++++++ tests/vllm/models/test_lenreg.cpp | 85 ++++++++++++++ 7 files changed, 421 insertions(+) create mode 100644 include/vllm/model_executor/models/lenreg.h create mode 100644 scripts/gen-lenreg-goldens.py create mode 100644 src/vllm/model_executor/models/lenreg.cpp create mode 100644 tests/vllm/models/lenreg_goldens.inc create mode 100644 tests/vllm/models/test_lenreg.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 616e63532..396bc4b38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -774,6 +774,7 @@ add_library(vllm STATIC src/vllm/model_executor/models/w2vbert.cpp src/vllm/model_executor/models/fvq.cpp src/vllm/model_executor/models/vocos.cpp + src/vllm/model_executor/models/lenreg.cpp src/vllm/model_executor/models/minimax_h3_video_vae.cpp src/vllm/model_executor/models/minimax_h3_video_vae_device.cpp src/vllm/model_executor/models/minimax_h3_vae_cnn.cpp diff --git a/include/vllm/model_executor/models/lenreg.h b/include/vllm/model_executor/models/lenreg.h new file mode 100644 index 000000000..cb59cfdd8 --- /dev/null +++ b/include/vllm/model_executor/models/lenreg.h @@ -0,0 +1,38 @@ +// S2Mel length regulator primitives (#634). +// +// `infer_v2_5.py:650` stretches the semantic sequence onto the target mel length +// through `InterpolateRegulator`, whose defining op is +// `F.interpolate(mode='nearest')`. Its stack is Conv1d(k=3)/GroupNorm/Mish +// repeated, then a 1x1 Conv1d. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace lenreg { + +// Nearest-neighbour resample along time, [channels, in_frames] -> +// [channels, out_frames]. +// +// THE INDEX RULE IS `src = floor(i * in_len / out_len)`, computed in floating +// point exactly as torch does. Rounding instead of truncating, or using +// `(i + 0.5) * ratio`, shifts frames by one at non-integer ratios and still +// produces audio -- so the goldens include an upsample to a NON-INTEGER +// multiple (7 -> 17), an exact multiple (7 -> 14), and a downsample (7 -> 3). +std::vector InterpolateNearest(const std::vector& x, int64_t channels, + int64_t in_frames, int64_t out_frames); + +// torch.nn.GroupNorm over [channels, frames]: statistics are shared across each +// GROUP of channels and all frames, not per channel. +std::vector GroupNorm(const std::vector& x, int64_t channels, int64_t frames, + int64_t groups, const std::vector& gamma, + const std::vector& beta, double eps); + +// Mish: x * tanh(softplus(x)). Easy to confuse with SiLU, which it resembles. +double Mish(double x); + +} // namespace lenreg +} // namespace models +} // namespace vllm diff --git a/scripts/gen-lenreg-goldens.py b/scripts/gen-lenreg-goldens.py new file mode 100644 index 000000000..f0d3d12c9 --- /dev/null +++ b/scripts/gen-lenreg-goldens.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/lenreg_goldens.inc — the S2Mel length-regulator oracle. + +`infer_v2_5.py:650` calls `s2mel.models['length_regulator']` to stretch the +semantic sequence onto the target mel length. Its defining operation is +`F.interpolate(..., mode='nearest')`, whose index rule is +`src = floor(i * in_len / out_len)` -- an off-by-one here shifts every frame and +still produces audio. + +The regulator's stack (Conv1d k=3 / GroupNorm / Mish, then a 1x1 Conv1d) is +gated alongside it, plus Mish itself, which is x * tanh(softplus(x)) and is easy +to confuse with SiLU. + +Torch is the oracle: these are stock nn modules, so upstream IS torch here. + +Usage: python3 scripts/gen-lenreg-goldens.py --out +""" +from __future__ import annotations +import argparse +from pathlib import Path +import numpy as np, torch +import torch.nn.functional as F + +MASK = (1 << 64) - 1 +def fnv(n): + h = 0xCBF29CE484222325 + for b in n.encode(): h ^= b; h = (h * 0x100000001B3) & MASK + return h +def sm(x): + x = (x + 0x9E3779B97F4A7C15) & MASK + z = x + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & MASK + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & MASK + return z ^ (z >> 31) +def rnd(name, n, scale): + s = fnv(name); o = np.empty(n) + for i in range(n): o[i] = (((sm((s + i) & MASK) >> 11) * 2.0**-53) * 2 - 1) * scale + return o +def P(name, shape, scale=0.3): + n = 1 + for d in shape: n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + +def main() -> int: + ap = argparse.ArgumentParser(); ap.add_argument("--out", required=True) + a = ap.parse_args() + torch.manual_seed(0) + + CH, IN_T, GROUPS = 8, 7, 4 + x = P("lrx", (1, CH, IN_T), 1.0) + # Three ratios: upsample by a NON-INTEGER factor (the interesting one), + # an exact double, and a downsample. + targets = [17, 14, 3] + interps = [F.interpolate(x, size=t, mode="nearest") for t in targets] + + gn = torch.nn.GroupNorm(GROUPS, CH).eval() + with torch.no_grad(): + gn.weight.copy_(P("gn.weight", (CH,), 0.5) + 1.0) + gn.bias.copy_(P("gn.bias", (CH,), 0.3)) + gn_out = gn(x) + + mish_in = P("mishx", (1, CH, IN_T), 3.0) + mish_out = F.mish(mish_in) + + def emit(f, name, t): + arr = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) + f.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(arr), 6): + f.write(" " + ", ".join(f"{v:.9e}F" for v in arr[i:i+6]) + ",\n") + f.write("};\n\n") + + p = Path(a.out) + with p.open("w") as f: + f.write("// GENERATED by scripts/gen-lenreg-goldens.py -- do not edit.\n") + f.write("// torch is the oracle here: the length regulator's pieces are stock nn\n") + f.write("// modules, so upstream IS torch. F.interpolate(mode='nearest') uses\n") + f.write("// src = floor(i * in_len / out_len).\n") + f.write("#pragma once\n\n#include \n\nnamespace lenreg_goldens {\n\n") + f.write(f"inline constexpr int64_t kChannels = {CH};\n") + f.write(f"inline constexpr int64_t kInFrames = {IN_T};\n") + f.write(f"inline constexpr int64_t kGroups = {GROUPS};\n") + f.write("inline constexpr int64_t kTargets[] = {" + ", ".join(str(t) for t in targets) + "};\n\n") + emit(f, "kX", x) + for t, y in zip(targets, interps): + emit(f, f"kInterp{t}", y) + emit(f, "kGroupNorm", gn_out) + emit(f, "kMishIn", mish_in); emit(f, "kMishOut", mish_out) + f.write("} // namespace lenreg_goldens\n") + print(f"wrote {p}: targets {targets} from {IN_T}") + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/vllm/model_executor/models/lenreg.cpp b/src/vllm/model_executor/models/lenreg.cpp new file mode 100644 index 000000000..83f8eebc5 --- /dev/null +++ b/src/vllm/model_executor/models/lenreg.cpp @@ -0,0 +1,79 @@ +// S2Mel length regulator primitives. See lenreg.h. +#include "vllm/model_executor/models/lenreg.h" + +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace lenreg { + +std::vector InterpolateNearest(const std::vector& x, int64_t channels, + int64_t in_frames, int64_t out_frames) { + VT_CHECK(x.size() == static_cast(channels * in_frames), "lenreg: interpolate shape"); + VT_CHECK(in_frames > 0 && out_frames > 0, "lenreg: frame counts must be positive"); + std::vector out(static_cast(channels * out_frames)); + const double ratio = static_cast(in_frames) / static_cast(out_frames); + for (int64_t t = 0; t < out_frames; ++t) { + // torch: src = floor(i * in / out), NOT a rounded or half-offset index. + int64_t src = static_cast(std::floor(static_cast(t) * ratio)); + if (src >= in_frames) src = in_frames - 1; + for (int64_t c = 0; c < channels; ++c) { + out[static_cast(c * out_frames + t)] = + x[static_cast(c * in_frames + src)]; + } + } + return out; +} + +std::vector GroupNorm(const std::vector& x, int64_t channels, int64_t frames, + int64_t groups, const std::vector& gamma, + const std::vector& beta, double eps) { + VT_CHECK(x.size() == static_cast(channels * frames), "lenreg: GroupNorm shape"); + VT_CHECK(groups > 0 && channels % groups == 0, "lenreg: channels must divide by groups"); + const int64_t per_group = channels / groups; + std::vector out(x.size()); + for (int64_t g = 0; g < groups; ++g) { + // Statistics span the whole GROUP: every channel in it and every frame. + double mean = 0.0; + const int64_t count = per_group * frames; + for (int64_t c = g * per_group; c < (g + 1) * per_group; ++c) { + for (int64_t t = 0; t < frames; ++t) { + mean += static_cast(x[static_cast(c * frames + t)]); + } + } + mean /= static_cast(count); + double var = 0.0; + for (int64_t c = g * per_group; c < (g + 1) * per_group; ++c) { + for (int64_t t = 0; t < frames; ++t) { + const double d = static_cast(x[static_cast(c * frames + t)]) - mean; + var += d * d; + } + } + var /= static_cast(count); + const double inv = 1.0 / std::sqrt(var + eps); + for (int64_t c = g * per_group; c < (g + 1) * per_group; ++c) { + for (int64_t t = 0; t < frames; ++t) { + const size_t i = static_cast(c * frames + t); + out[i] = static_cast((static_cast(x[i]) - mean) * inv * + static_cast(gamma[static_cast(c)]) + + static_cast(beta[static_cast(c)])); + } + } + } + return out; +} + +double Mish(double x) { + // softplus is log1p(exp(x)), guarded the way torch does for large x so the + // exponential cannot overflow. + const double sp = (x > 20.0) ? x : std::log1p(std::exp(x)); + return x * std::tanh(sp); +} + +} // namespace lenreg +} // namespace models +} // namespace vllm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 24ca5975b..598a29662 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -116,6 +116,8 @@ vllm_cpp_add_test(test_campplus vllm/models/test_campplus.cpp) vllm_cpp_add_test(test_w2vbert vllm/models/test_w2vbert.cpp) vllm_cpp_add_test(test_fvq vllm/models/test_fvq.cpp) vllm_cpp_add_test(test_vocos vllm/models/test_vocos.cpp) +vllm_cpp_add_test(test_lenreg vllm/models/test_lenreg.cpp) +target_include_directories(test_lenreg PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_vocos PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_fvq PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_w2vbert PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) diff --git a/tests/vllm/models/lenreg_goldens.inc b/tests/vllm/models/lenreg_goldens.inc new file mode 100644 index 000000000..8dbb77c9e --- /dev/null +++ b/tests/vllm/models/lenreg_goldens.inc @@ -0,0 +1,123 @@ +// GENERATED by scripts/gen-lenreg-goldens.py -- do not edit. +// torch is the oracle here: the length regulator's pieces are stock nn +// modules, so upstream IS torch. F.interpolate(mode='nearest') uses +// src = floor(i * in_len / out_len). +#pragma once + +#include + +namespace lenreg_goldens { + +inline constexpr int64_t kChannels = 8; +inline constexpr int64_t kInFrames = 7; +inline constexpr int64_t kGroups = 4; +inline constexpr int64_t kTargets[] = {17, 14, 3}; + +inline constexpr float kX[] = { + -9.374305606e-01F, -3.718495667e-01F, 2.367317230e-01F, 1.042240784e-01F, 5.110236406e-01F, 3.230898678e-01F, + -2.560909092e-02F, 6.337113380e-01F, 1.592544615e-01F, -5.957312584e-01F, 1.887753904e-01F, -6.373646855e-01F, + 1.395002306e-01F, -3.828124702e-02F, -3.787322044e-01F, -4.390954077e-01F, -8.821833730e-01F, -1.043934375e-01F, + -5.300186276e-01F, -5.760548711e-01F, 5.244931579e-02F, 2.744870484e-01F, -5.008615851e-01F, 4.867260456e-01F, + -3.550205529e-01F, -5.032106638e-01F, 2.539642453e-01F, 8.236359358e-01F, -2.723409235e-01F, -5.542259216e-01F, + 1.496479958e-01F, -7.812023163e-01F, 1.611241400e-01F, -5.665724874e-01F, 3.051100671e-01F, 2.570289373e-01F, + -7.603486776e-01F, 6.932491660e-01F, 6.765042543e-01F, -9.569998980e-01F, -1.027239859e-01F, 8.133552074e-01F, + 3.360463977e-01F, 2.385810465e-01F, 8.510169983e-01F, -5.203963518e-01F, -9.110032320e-01F, 4.885039926e-01F, + -5.877836347e-01F, -4.910004437e-01F, -5.201669931e-01F, -8.159059882e-01F, 5.903115869e-01F, 2.807071209e-01F, + 4.119786620e-01F, 1.906565763e-02F, +}; + +inline constexpr float kInterp17[] = { + -9.374305606e-01F, -9.374305606e-01F, -9.374305606e-01F, -3.718495667e-01F, -3.718495667e-01F, 2.367317230e-01F, + 2.367317230e-01F, 2.367317230e-01F, 1.042240784e-01F, 1.042240784e-01F, 5.110236406e-01F, 5.110236406e-01F, + 5.110236406e-01F, 3.230898678e-01F, 3.230898678e-01F, -2.560909092e-02F, -2.560909092e-02F, 6.337113380e-01F, + 6.337113380e-01F, 6.337113380e-01F, 1.592544615e-01F, 1.592544615e-01F, -5.957312584e-01F, -5.957312584e-01F, + -5.957312584e-01F, 1.887753904e-01F, 1.887753904e-01F, -6.373646855e-01F, -6.373646855e-01F, -6.373646855e-01F, + 1.395002306e-01F, 1.395002306e-01F, -3.828124702e-02F, -3.828124702e-02F, -3.787322044e-01F, -3.787322044e-01F, + -3.787322044e-01F, -4.390954077e-01F, -4.390954077e-01F, -8.821833730e-01F, -8.821833730e-01F, -8.821833730e-01F, + -1.043934375e-01F, -1.043934375e-01F, -5.300186276e-01F, -5.300186276e-01F, -5.300186276e-01F, -5.760548711e-01F, + -5.760548711e-01F, 5.244931579e-02F, 5.244931579e-02F, 2.744870484e-01F, 2.744870484e-01F, 2.744870484e-01F, + -5.008615851e-01F, -5.008615851e-01F, 4.867260456e-01F, 4.867260456e-01F, 4.867260456e-01F, -3.550205529e-01F, + -3.550205529e-01F, -5.032106638e-01F, -5.032106638e-01F, -5.032106638e-01F, 2.539642453e-01F, 2.539642453e-01F, + 8.236359358e-01F, 8.236359358e-01F, -2.723409235e-01F, -2.723409235e-01F, -2.723409235e-01F, -5.542259216e-01F, + -5.542259216e-01F, 1.496479958e-01F, 1.496479958e-01F, 1.496479958e-01F, -7.812023163e-01F, -7.812023163e-01F, + 1.611241400e-01F, 1.611241400e-01F, 1.611241400e-01F, -5.665724874e-01F, -5.665724874e-01F, 3.051100671e-01F, + 3.051100671e-01F, 2.570289373e-01F, 2.570289373e-01F, 2.570289373e-01F, -7.603486776e-01F, -7.603486776e-01F, + 6.932491660e-01F, 6.932491660e-01F, 6.932491660e-01F, 6.765042543e-01F, 6.765042543e-01F, -9.569998980e-01F, + -9.569998980e-01F, -9.569998980e-01F, -1.027239859e-01F, -1.027239859e-01F, 8.133552074e-01F, 8.133552074e-01F, + 3.360463977e-01F, 3.360463977e-01F, 3.360463977e-01F, 2.385810465e-01F, 2.385810465e-01F, 8.510169983e-01F, + 8.510169983e-01F, 8.510169983e-01F, -5.203963518e-01F, -5.203963518e-01F, -9.110032320e-01F, -9.110032320e-01F, + -9.110032320e-01F, 4.885039926e-01F, 4.885039926e-01F, -5.877836347e-01F, -5.877836347e-01F, -4.910004437e-01F, + -4.910004437e-01F, -4.910004437e-01F, -5.201669931e-01F, -5.201669931e-01F, -8.159059882e-01F, -8.159059882e-01F, + -8.159059882e-01F, 5.903115869e-01F, 5.903115869e-01F, 2.807071209e-01F, 2.807071209e-01F, 2.807071209e-01F, + 4.119786620e-01F, 4.119786620e-01F, 1.906565763e-02F, 1.906565763e-02F, +}; + +inline constexpr float kInterp14[] = { + -9.374305606e-01F, -9.374305606e-01F, -3.718495667e-01F, -3.718495667e-01F, 2.367317230e-01F, 2.367317230e-01F, + 1.042240784e-01F, 1.042240784e-01F, 5.110236406e-01F, 5.110236406e-01F, 3.230898678e-01F, 3.230898678e-01F, + -2.560909092e-02F, -2.560909092e-02F, 6.337113380e-01F, 6.337113380e-01F, 1.592544615e-01F, 1.592544615e-01F, + -5.957312584e-01F, -5.957312584e-01F, 1.887753904e-01F, 1.887753904e-01F, -6.373646855e-01F, -6.373646855e-01F, + 1.395002306e-01F, 1.395002306e-01F, -3.828124702e-02F, -3.828124702e-02F, -3.787322044e-01F, -3.787322044e-01F, + -4.390954077e-01F, -4.390954077e-01F, -8.821833730e-01F, -8.821833730e-01F, -1.043934375e-01F, -1.043934375e-01F, + -5.300186276e-01F, -5.300186276e-01F, -5.760548711e-01F, -5.760548711e-01F, 5.244931579e-02F, 5.244931579e-02F, + 2.744870484e-01F, 2.744870484e-01F, -5.008615851e-01F, -5.008615851e-01F, 4.867260456e-01F, 4.867260456e-01F, + -3.550205529e-01F, -3.550205529e-01F, -5.032106638e-01F, -5.032106638e-01F, 2.539642453e-01F, 2.539642453e-01F, + 8.236359358e-01F, 8.236359358e-01F, -2.723409235e-01F, -2.723409235e-01F, -5.542259216e-01F, -5.542259216e-01F, + 1.496479958e-01F, 1.496479958e-01F, -7.812023163e-01F, -7.812023163e-01F, 1.611241400e-01F, 1.611241400e-01F, + -5.665724874e-01F, -5.665724874e-01F, 3.051100671e-01F, 3.051100671e-01F, 2.570289373e-01F, 2.570289373e-01F, + -7.603486776e-01F, -7.603486776e-01F, 6.932491660e-01F, 6.932491660e-01F, 6.765042543e-01F, 6.765042543e-01F, + -9.569998980e-01F, -9.569998980e-01F, -1.027239859e-01F, -1.027239859e-01F, 8.133552074e-01F, 8.133552074e-01F, + 3.360463977e-01F, 3.360463977e-01F, 2.385810465e-01F, 2.385810465e-01F, 8.510169983e-01F, 8.510169983e-01F, + -5.203963518e-01F, -5.203963518e-01F, -9.110032320e-01F, -9.110032320e-01F, 4.885039926e-01F, 4.885039926e-01F, + -5.877836347e-01F, -5.877836347e-01F, -4.910004437e-01F, -4.910004437e-01F, -5.201669931e-01F, -5.201669931e-01F, + -8.159059882e-01F, -8.159059882e-01F, 5.903115869e-01F, 5.903115869e-01F, 2.807071209e-01F, 2.807071209e-01F, + 4.119786620e-01F, 4.119786620e-01F, 1.906565763e-02F, 1.906565763e-02F, +}; + +inline constexpr float kInterp3[] = { + -9.374305606e-01F, 2.367317230e-01F, 5.110236406e-01F, 6.337113380e-01F, -5.957312584e-01F, -6.373646855e-01F, + -3.787322044e-01F, -8.821833730e-01F, -5.300186276e-01F, 2.744870484e-01F, 4.867260456e-01F, -5.032106638e-01F, + -2.723409235e-01F, 1.496479958e-01F, 1.611241400e-01F, 2.570289373e-01F, 6.932491660e-01F, -9.569998980e-01F, + 3.360463977e-01F, 8.510169983e-01F, -9.110032320e-01F, -4.910004437e-01F, -8.159059882e-01F, 2.807071209e-01F, +}; + +inline constexpr float kGroupNorm[] = { + -2.259250641e+00F, -7.026850581e-01F, 9.722238779e-01F, 6.075425148e-01F, 1.727117181e+00F, 1.209894657e+00F, + 2.502217591e-01F, 1.337996602e+00F, 5.424974561e-01F, -7.233510017e-01F, 5.919938087e-01F, -7.931557894e-01F, + 5.093765259e-01F, 2.112987936e-01F, -6.643185019e-01F, -8.085104227e-01F, -1.866931796e+00F, -8.994906209e-03F, + -1.025702238e+00F, -1.135670781e+00F, 3.656614423e-01F, 6.890298724e-01F, -4.461547136e-01F, 9.997680783e-01F, + -2.326294780e-01F, -4.495939910e-01F, 6.589825749e-01F, 1.493036389e+00F, -3.423536569e-02F, -4.776074588e-01F, + 6.295037866e-01F, -8.346147537e-01F, 6.475543976e-01F, -4.970271587e-01F, 8.740274310e-01F, 2.032104693e-02F, + -9.111450315e-01F, 4.197050631e-01F, 4.043741524e-01F, -1.091190219e+00F, -3.090528548e-01F, 5.296688676e-01F, + 2.204018980e-01F, 1.200922057e-01F, 7.504009604e-01F, -6.610345244e-01F, -1.063040614e+00F, 3.773086965e-01F, + -7.303884029e-01F, -4.236407280e-01F, -4.512450397e-01F, -7.311432958e-01F, 5.997527242e-01F, 3.067316711e-01F, + 4.309718907e-01F, 5.910457298e-02F, +}; + +inline constexpr float kMishIn[] = { + -3.435344994e-01F, -2.294931561e-01F, -2.441115379e-01F, 1.456725240e+00F, -1.218505859e+00F, 5.111691952e-01F, + -1.841573596e+00F, 7.700219750e-01F, 2.667907000e+00F, 2.254930735e+00F, 1.937724471e+00F, -2.883944511e+00F, + -2.715227127e+00F, 1.983557463e+00F, 2.427527428e+00F, -2.792313814e+00F, -9.084396958e-01F, 1.455227137e+00F, + -1.745534182e+00F, -8.948470950e-01F, 7.710260153e-01F, -1.059514403e+00F, -2.269505978e+00F, -5.790882707e-01F, + -1.085253596e+00F, -2.387687206e+00F, -1.841941953e+00F, -1.444696039e-01F, 1.777103662e+00F, 1.204748392e+00F, + 2.059045315e+00F, -1.215561509e+00F, -1.294264078e+00F, 2.289056331e-01F, 2.214196920e+00F, -2.533252239e+00F, + -6.423967481e-01F, 2.551105976e+00F, 2.751206636e+00F, -7.423872948e-01F, -1.916234493e-01F, 1.614797592e+00F, + -1.057207108e+00F, 1.888743877e+00F, -1.042161822e+00F, 1.312608719e+00F, -2.347611666e+00F, -1.976006746e+00F, + 1.222919583e+00F, 6.711555123e-01F, -2.937012911e+00F, -1.228143096e+00F, -2.263543367e+00F, 9.922250509e-01F, + -9.856703281e-01F, 7.769262195e-01F, +}; + +inline constexpr float kMishOut[] = { + -1.683318019e-01F, -1.207749173e-01F, -1.273261905e-01F, 1.356275320e+00F, -3.087538779e-01F, 3.851749301e-01F, + -2.691106200e-01F, 6.298194528e-01F, 2.645530939e+00F, 2.214656353e+00F, 1.877255797e+00F, -1.567507833e-01F, + -1.737850010e-01F, 1.926368952e+00F, 2.395805359e+00F, -1.658774167e-01F, -2.964563072e-01F, 1.354644895e+00F, + -2.784347534e-01F, -2.951542437e-01F, 6.308157444e-01F, -3.063219786e-01F, -2.225182205e-01F, -2.419068366e-01F, + -3.072248697e-01F, -2.092583776e-01F, -2.690736055e-01F, -7.997453213e-02F, 1.704249859e+00F, 1.083057880e+00F, + 2.006995916e+00F, -3.087728620e-01F, -3.075301945e-01F, 1.537937373e-01F, 2.171658993e+00F, -1.931790859e-01F, + -2.564300597e-01F, 2.524535656e+00F, 2.731456757e+00F, -2.752575874e-01F, -1.031726003e-01F, 1.528267026e+00F, + -3.062307239e-01F, 1.824640632e+00F, -3.055928946e-01F, 1.199644089e+00F, -2.137424499e-01F, -2.550908029e-01F, + 1.102638841e+00F, 5.333535671e-01F, -1.516085714e-01F, -3.086762428e-01F, -2.231891602e-01F, 8.569477797e-01F, + -3.025167584e-01F, 6.366768479e-01F, +}; + +} // namespace lenreg_goldens diff --git a/tests/vllm/models/test_lenreg.cpp b/tests/vllm/models/test_lenreg.cpp new file mode 100644 index 000000000..7419f83ac --- /dev/null +++ b/tests/vllm/models/test_lenreg.cpp @@ -0,0 +1,85 @@ +// S2Mel length-regulator primitives (#634), gated against torch. +#include +#include +#include +#include +#include + +#include "doctest/doctest.h" +#include "lenreg_goldens.inc" +#include "vllm/model_executor/models/lenreg.h" + +namespace { +using namespace lenreg_goldens; + +std::vector Rand(const std::string& name, int64_t n, double scale) { + uint64_t seed = 0xCBF29CE484222325ULL; + for (char c : name) { seed ^= static_cast(c); seed *= 0x100000001B3ULL; } + std::vector o(static_cast(n)); + for (int64_t i = 0; i < n; ++i) { + uint64_t x = seed + static_cast(i); + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + z ^= z >> 31; + o[static_cast(i)] = + static_cast(((static_cast(z >> 11) * 0x1.0p-53) * 2.0 - 1.0) * scale); + } + return o; +} +double Worst(const std::vector& g, const float* w, size_t n) { + double m = 0.0; + for (size_t i = 0; i < n; ++i) m = std::max(m, std::fabs(static_cast(g[i]) - w[i])); + return m; +} +} // namespace + +TEST_CASE("lenreg nearest interpolate matches torch at a NON-INTEGER ratio") { + // 7 -> 17 is the case that separates floor(i*in/out) from a rounded or + // half-offset rule; both alternatives agree at exact multiples. + const std::vector x(std::begin(kX), std::end(kX)); + const std::vector got = + vllm::models::lenreg::InterpolateNearest(x, kChannels, kInFrames, 17); + REQUIRE(got.size() == static_cast(kChannels * 17)); + // Exact: interpolation COPIES samples, it never blends them. + for (size_t i = 0; i < got.size(); ++i) CHECK(got[i] == kInterp17[i]); +} + +TEST_CASE("lenreg nearest interpolate matches torch at an exact multiple") { + const std::vector x(std::begin(kX), std::end(kX)); + const std::vector got = + vllm::models::lenreg::InterpolateNearest(x, kChannels, kInFrames, 14); + REQUIRE(got.size() == static_cast(kChannels * 14)); + for (size_t i = 0; i < got.size(); ++i) CHECK(got[i] == kInterp14[i]); +} + +TEST_CASE("lenreg nearest interpolate DOWNsamples the same way") { + const std::vector x(std::begin(kX), std::end(kX)); + const std::vector got = + vllm::models::lenreg::InterpolateNearest(x, kChannels, kInFrames, 3); + REQUIRE(got.size() == static_cast(kChannels * 3)); + for (size_t i = 0; i < got.size(); ++i) CHECK(got[i] == kInterp3[i]); +} + +TEST_CASE("lenreg GroupNorm shares statistics across a GROUP, not a channel") { + // Per-channel statistics would also normalize, and would be a different + // model; only the values distinguish them. + const std::vector x(std::begin(kX), std::end(kX)); + std::vector gamma = Rand("gn.weight", kChannels, 0.5); + for (float& g : gamma) g += 1.0F; + const std::vector beta = Rand("gn.bias", kChannels, 0.3); + const std::vector got = vllm::models::lenreg::GroupNorm( + x, kChannels, kInFrames, kGroups, gamma, beta, 1e-5); + REQUIRE(got.size() == x.size()); + CHECK(Worst(got, kGroupNorm, got.size()) < 1e-5); +} + +TEST_CASE("lenreg Mish is x * tanh(softplus(x)), not SiLU") { + const std::vector in(std::begin(kMishIn), std::end(kMishIn)); + std::vector got(in.size()); + for (size_t i = 0; i < in.size(); ++i) { + got[i] = static_cast(vllm::models::lenreg::Mish(static_cast(in[i]))); + } + CHECK(Worst(got, kMishOut, got.size()) < 1e-6); +} From 3d8e3f9db5d2e5bd5d299b4095a4d97c03ebbc55 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:24:39 +0200 Subject: [PATCH 30/55] feat(MODEL-MM-indextts2): S2Mel flow-matching scaffolding, and a float32 fidelity defect (#634) (#717) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FOLLOWING_AGENTS_PROTOCOL Ports the scaffolding around S2Mel's DiT: the sinusoidal timestep embedding and the Euler step with classifier-free guidance. 4 cases / 24 assertions against upstream executed directly. TWO UPSTREAM CHOICES THAT ARE NOT THE COMMON CONVENTION: scale = 1000 multiplies t BEFORE the frequencies apply, and the halves are cat([cos, sin]) — COSINE FIRST. Sine-first yields an embedding that is still smooth, still periodic and entirely wrong; beyond the oracle comparison an EXACT structural case pins it (at t = 0 the first half must be all ones and the second all zeros). THE PROMPT REGION IS RE-ZEROED AFTER EVERY EULER STEP (flow_matching.py:113). A FLOAT32 FIDELITY DEFECT the gate caught: the first port computed the frequency table in double, which is more accurate and WRONG. Upstream registers it as a float32 BUFFER and computes scale * t * freqs on float32 tensors; at scale = 1000 the arguments reach ~130 where float32 spacing is ~7.6e-6. Both table and argument now round to float32 where upstream does. A double-precision port still cannot agree below ~6.5e-6 (MEASURED), so the tolerance is 2e-5 — just above the floor, still tight enough that an ordering swap or a missing scale fail loudly. MUTATION EVIDENCE with compile status: sine-first, scale dropped, prompt zeroing skipped, CFG subtracting the conditional twice — each RED at compile_err=0. Oracle note: the generator adds the package root to sys.path and STUBS `munch` (which TimestepEmbedder never touches) rather than installing it, keeping the oracle reproducible with no network and no version drift. Remaining: the DiT blocks and the talker head, then composition. No e2e claim. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- CMakeLists.txt | 1 + include/vllm/model_executor/models/cfm.h | 40 ++++++++ scripts/gen-cfm-goldens.py | 113 +++++++++++++++++++++++ src/vllm/model_executor/models/cfm.cpp | 68 ++++++++++++++ tests/CMakeLists.txt | 2 + tests/vllm/models/cfm_goldens.inc | 54 +++++++++++ tests/vllm/models/test_cfm.cpp | 90 ++++++++++++++++++ 7 files changed, 368 insertions(+) create mode 100644 include/vllm/model_executor/models/cfm.h create mode 100644 scripts/gen-cfm-goldens.py create mode 100644 src/vllm/model_executor/models/cfm.cpp create mode 100644 tests/vllm/models/cfm_goldens.inc create mode 100644 tests/vllm/models/test_cfm.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 396bc4b38..f9d4ffaca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -775,6 +775,7 @@ add_library(vllm STATIC src/vllm/model_executor/models/fvq.cpp src/vllm/model_executor/models/vocos.cpp src/vllm/model_executor/models/lenreg.cpp + src/vllm/model_executor/models/cfm.cpp src/vllm/model_executor/models/minimax_h3_video_vae.cpp src/vllm/model_executor/models/minimax_h3_video_vae_device.cpp src/vllm/model_executor/models/minimax_h3_vae_cnn.cpp diff --git a/include/vllm/model_executor/models/cfm.h b/include/vllm/model_executor/models/cfm.h new file mode 100644 index 000000000..4848c6f43 --- /dev/null +++ b/include/vllm/model_executor/models/cfm.h @@ -0,0 +1,40 @@ +// S2Mel flow-matching scaffolding (#634). +// +// The S2Mel decoder is a conditional flow-matching model whose estimator is a +// DiT. This header covers the scaffolding AROUND the DiT blocks: the sinusoidal +// timestep embedding, and the Euler step with classifier-free guidance. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace cfm { + +// Sinusoidal timestep features, [num_t, freq_dim]. +// +// TWO DEVIATIONS FROM THE COMMON CONVENTION, both deliberate upstream: +// * `t` is multiplied by `scale` (1000) BEFORE the frequencies apply. +// * the halves are concatenated as [cos, sin] -- COSINE FIRST. Most +// implementations emit sine first, and swapping them yields an embedding +// that is still smooth, still periodic and completely wrong. +std::vector TimestepFeatures(const std::vector& t, int64_t freq_dim, + double max_period, double scale); + +// The Euler update with classifier-free guidance: +// +// dphi = (1 + rate) * conditional - rate * unconditional +// x = x + dt * dphi +// x[:, :prompt_len] = 0 +// +// THE PROMPT REGION IS ZEROED AFTER EVERY STEP (flow_matching.py:113). Skipping +// it lets the solver integrate over the prompt frames, which still yields a mel +// of the right shape and corrupts the region the prompt was meant to pin. +std::vector EulerStepCfg(const std::vector& x, const std::vector& cond, + const std::vector& uncond, int64_t channels, + int64_t frames, double dt, double cfg_rate, int64_t prompt_len); + +} // namespace cfm +} // namespace models +} // namespace vllm diff --git a/scripts/gen-cfm-goldens.py b/scripts/gen-cfm-goldens.py new file mode 100644 index 000000000..7fff3b10e --- /dev/null +++ b/scripts/gen-cfm-goldens.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/cfm_goldens.inc — S2Mel's timestep embedder oracle. + +The S2Mel decoder is a flow-matching CFM whose estimator is a DiT. This gates the +scaffolding that surrounds the DiT blocks: the sinusoidal TIMESTEP EMBEDDING and +its MLP. + +TWO THINGS UPSTREAM DOES THAT ARE NOT THE COMMON CONVENTION, both pinned here: + * `scale = 1000` multiplies t BEFORE the frequencies are applied. + * the embedding is `cat([cos, sin])` -- COSINE FIRST. Most implementations put + sine first, and swapping them still yields a smooth, well-formed embedding. + +Upstream executed directly (indextts/s2mel/modules/diffusion_transformer.py). + +Usage: DIT_SRC= python3 scripts/gen-cfm-goldens.py --out +""" +from __future__ import annotations +import argparse, importlib.util, os, sys, types +from pathlib import Path +import numpy as np, torch + +MASK = (1 << 64) - 1 +def fnv(n): + h = 0xCBF29CE484222325 + for b in n.encode(): h ^= b; h = (h * 0x100000001B3) & MASK + return h +def sm(x): + x = (x + 0x9E3779B97F4A7C15) & MASK + z = x + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & MASK + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & MASK + return z ^ (z >> 31) +def rnd(name, n, scale): + s = fnv(name); o = np.empty(n) + for i in range(n): o[i] = (((sm((s + i) & MASK) >> 11) * 2.0**-53) * 2 - 1) * scale + return o +def P(name, shape, scale=0.3): + n = 1 + for d in shape: n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + +def main() -> int: + ap = argparse.ArgumentParser(); ap.add_argument("--out", required=True) + a = ap.parse_args() + src = Path(os.environ["DIT_SRC"]) + # The module imports siblings; load it with a stub package so only the + # class we need is constructed. + # the module imports its siblings, so the package root must be importable + sys.path.insert(0, str(src.parents[3])) + # Stub the third-party helpers the module imports at top level but that the + # TimestepEmbedder never touches. Stubbing beats installing: it keeps the + # oracle reproducible with no network and no version drift. + for name in ("munch",): + if name not in sys.modules: + stub = types.ModuleType(name) + stub.Munch = dict + sys.modules[name] = stub + spec = importlib.util.spec_from_file_location("dit", src) + m = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(m) + except Exception as exc: # pragma: no cover - reported, not hidden + print("could not import upstream DiT module:", exc, file=sys.stderr) + return 2 + + torch.manual_seed(0) + HIDDEN, FREQ = 16, 8 + te = m.TimestepEmbedder(hidden_size=HIDDEN, frequency_embedding_size=FREQ).eval() + manifest = [] + with torch.no_grad(): + for n, p in list(te.named_parameters()): + p.copy_(P(n, tuple(p.shape) if p.dim() else (1,)).reshape(p.shape)) + manifest.append((n, list(p.shape))) + ts = torch.tensor([0.0, 0.13, 0.5, 0.87, 1.0]) + with torch.no_grad(): + raw = te.timestep_embedding(ts) + emb = te(ts) + + def emit(f, name, t): + arr = np.asarray(t.detach().numpy(), dtype=np.float32).reshape(-1) + f.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(arr), 6): + f.write(" " + ", ".join(f"{v:.9e}F" for v in arr[i:i+6]) + ",\n") + f.write("};\n\n") + + p = Path(a.out) + with p.open("w") as f: + f.write("// GENERATED by scripts/gen-cfm-goldens.py -- do not edit.\n") + f.write("// S2Mel TimestepEmbedder executed DIRECTLY. Note scale=1000 and the\n") + f.write("// cat([cos, sin]) ordering -- COSINE FIRST, unlike most implementations.\n") + f.write("#pragma once\n\n#include \n\nnamespace cfm_goldens {\n\n") + f.write(f"inline constexpr int64_t kHidden = {HIDDEN};\n") + f.write(f"inline constexpr int64_t kFreqDim = {FREQ};\n") + f.write(f"inline constexpr int64_t kNumT = {len(ts)};\n") + f.write(f"inline constexpr double kMaxPeriod = {te.max_period};\n") + f.write(f"inline constexpr double kScale = {te.scale};\n\n") + f.write("inline constexpr float kT[] = {" + + ", ".join(f"{float(v):.9e}F" for v in ts.tolist()) + "};\n\n") + f.write(f"inline constexpr int64_t kManifestSize = {len(manifest)};\n") + f.write("struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1; };\n") + f.write("inline constexpr ManifestEntry kManifest[] = {\n") + for nm, sh in manifest: + d = list(sh) + [1, 1] + f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}}},\n') + f.write("};\n\n") + emit(f, "kRawEmbedding", raw) + emit(f, "kTimestepEmbedding", emb) + f.write("} // namespace cfm_goldens\n") + print(f"wrote {p}: {len(manifest)} tensors, freq_dim={FREQ}, timesteps={len(ts)}") + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/vllm/model_executor/models/cfm.cpp b/src/vllm/model_executor/models/cfm.cpp new file mode 100644 index 000000000..5ea26c390 --- /dev/null +++ b/src/vllm/model_executor/models/cfm.cpp @@ -0,0 +1,68 @@ +// S2Mel flow-matching scaffolding. See cfm.h. +#include "vllm/model_executor/models/cfm.h" + +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace cfm { + +std::vector TimestepFeatures(const std::vector& t, int64_t freq_dim, + double max_period, double scale) { + VT_CHECK(freq_dim > 0, "cfm: frequency dim must be positive"); + const int64_t half = freq_dim / 2; + const int64_t num_t = static_cast(t.size()); + std::vector out(static_cast(num_t * freq_dim)); + for (int64_t n = 0; n < num_t; ++n) { + for (int64_t i = 0; i < half; ++i) { + // FLOAT32, deliberately: upstream computes this table once as a float32 + // buffer (). Computing it in double is more + // accurate and WRONG -- with scale = 1000 the arguments reach ~130, where + // float32's relative error becomes ~1e-5 absolute and moves cos/sin by + // ~6.7e-6. Measured; the gate caught it. + const float freq = static_cast( + std::exp(-std::log(max_period) * static_cast(i) / static_cast(half))); + // The ARGUMENT is float32 too: upstream computes `scale * t * freqs` on + // float32 tensors. Mirrored here so the rounding happens at the same + // place, though it cannot make a double-precision port bit-exact -- see + // the tolerance note in the gate. + const float arg_f32 = static_cast(scale * static_cast( + t[static_cast(n)])) * freq; + const double arg = static_cast(arg_f32); + // COSINE FIRST, then sine. + out[static_cast(n * freq_dim + i)] = static_cast(std::cos(arg)); + out[static_cast(n * freq_dim + half + i)] = static_cast(std::sin(arg)); + } + } + return out; +} + +std::vector EulerStepCfg(const std::vector& x, const std::vector& cond, + const std::vector& uncond, int64_t channels, + int64_t frames, double dt, double cfg_rate, int64_t prompt_len) { + const size_t n = static_cast(channels * frames); + VT_CHECK(x.size() == n && cond.size() == n && uncond.size() == n, "cfm: euler shapes"); + VT_CHECK(prompt_len >= 0 && prompt_len <= frames, "cfm: prompt_len out of range"); + + std::vector out(n); + for (size_t i = 0; i < n; ++i) { + const double dphi = (1.0 + cfg_rate) * static_cast(cond[i]) - + cfg_rate * static_cast(uncond[i]); + out[i] = static_cast(static_cast(x[i]) + dt * dphi); + } + // The prompt frames are re-zeroed AFTER the update, every step. + for (int64_t c = 0; c < channels; ++c) { + for (int64_t t = 0; t < prompt_len; ++t) { + out[static_cast(c * frames + t)] = 0.0F; + } + } + return out; +} + +} // namespace cfm +} // namespace models +} // namespace vllm diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 598a29662..6984b5030 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -117,6 +117,8 @@ vllm_cpp_add_test(test_w2vbert vllm/models/test_w2vbert.cpp) vllm_cpp_add_test(test_fvq vllm/models/test_fvq.cpp) vllm_cpp_add_test(test_vocos vllm/models/test_vocos.cpp) vllm_cpp_add_test(test_lenreg vllm/models/test_lenreg.cpp) +vllm_cpp_add_test(test_cfm vllm/models/test_cfm.cpp) +target_include_directories(test_cfm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_lenreg PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_vocos PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) target_include_directories(test_fvq PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) diff --git a/tests/vllm/models/cfm_goldens.inc b/tests/vllm/models/cfm_goldens.inc new file mode 100644 index 000000000..44ca94461 --- /dev/null +++ b/tests/vllm/models/cfm_goldens.inc @@ -0,0 +1,54 @@ +// GENERATED by scripts/gen-cfm-goldens.py -- do not edit. +// S2Mel TimestepEmbedder executed DIRECTLY. Note scale=1000 and the +// cat([cos, sin]) ordering -- COSINE FIRST, unlike most implementations. +#pragma once + +#include + +namespace cfm_goldens { + +inline constexpr int64_t kHidden = 16; +inline constexpr int64_t kFreqDim = 8; +inline constexpr int64_t kNumT = 5; +inline constexpr double kMaxPeriod = 10000; +inline constexpr double kScale = 1000; + +inline constexpr float kT[] = {0.000000000e+00F, 1.299999952e-01F, 5.000000000e-01F, 8.700000048e-01F, 1.000000000e+00F}; + +inline constexpr int64_t kManifestSize = 4; +struct ManifestEntry { const char* name; int64_t rank; int64_t d0, d1; }; +inline constexpr ManifestEntry kManifest[] = { + {"mlp.0.weight", 2, 16, 8}, + {"mlp.0.bias", 1, 16, 1}, + {"mlp.2.weight", 2, 16, 16}, + {"mlp.2.bias", 1, 16, 1}, +}; + +inline constexpr float kRawEmbedding[] = { + 1.000000000e+00F, 1.000000000e+00F, 1.000000000e+00F, 1.000000000e+00F, 0.000000000e+00F, 0.000000000e+00F, + 0.000000000e+00F, 0.000000000e+00F, -3.672913313e-01F, 9.074471593e-01F, 2.674988806e-01F, 9.915618896e-01F, + -9.301059246e-01F, 4.201661646e-01F, 9.635581970e-01F, 1.296341419e-01F, -8.838492632e-01F, 9.649650455e-01F, + 2.836622000e-01F, 8.775825500e-01F, -4.677717984e-01F, -2.623785436e-01F, -9.589242935e-01F, 4.794255197e-01F, + -9.756425619e-01F, 5.697440505e-01F, -7.486464977e-01F, 6.448265910e-01F, 2.193664461e-01F, -8.218221664e-01F, + 6.629693508e-01F, 7.643288970e-01F, 5.623790622e-01F, 8.623149991e-01F, -8.390715122e-01F, 5.403023362e-01F, + 8.268795609e-01F, -5.063722134e-01F, -5.440211296e-01F, 8.414709568e-01F, +}; + +inline constexpr float kTimestepEmbedding[] = { + -8.523900062e-02F, 2.120184749e-01F, 6.785100698e-02F, 1.624629796e-01F, 1.827961206e-02F, 1.713014245e-01F, + -1.573002040e-01F, 2.256771624e-01F, -1.585931778e-01F, -2.534797788e-01F, 2.061229050e-01F, 1.954075992e-01F, + -1.032320410e-02F, 1.625695825e-02F, 2.110688984e-01F, -3.338660002e-01F, 3.307674825e-02F, 2.798489332e-01F, + 7.195293903e-02F, 1.383567750e-01F, 1.273325086e-02F, 2.861092687e-01F, -2.723523974e-01F, 1.657553017e-01F, + -2.106506974e-01F, -1.430782527e-01F, -4.393711686e-03F, 1.652503014e-01F, -3.821713850e-02F, 2.680559456e-02F, + 1.731814742e-01F, -2.959118485e-01F, 7.047767192e-02F, 3.227063417e-01F, 1.892082244e-01F, 1.655146480e-01F, + 6.129595265e-02F, 1.913011372e-01F, -3.384203911e-01F, 1.085601300e-01F, -1.822721660e-01F, -1.392091066e-01F, + 2.570436895e-02F, 3.801564574e-01F, 2.319864929e-02F, 5.713671446e-03F, 1.463794708e-01F, -3.003319204e-01F, + 3.467754275e-02F, 2.521759272e-01F, 1.421998721e-03F, 1.530980915e-01F, 2.455092520e-01F, 6.876826286e-02F, + -1.758338362e-01F, 1.069766432e-01F, -5.949525535e-02F, 1.464406401e-01F, -1.098050177e-02F, -9.816683829e-03F, + 4.367858171e-03F, 6.718331575e-02F, 1.512954384e-01F, -4.430445731e-01F, -4.137481377e-02F, 2.038155645e-01F, + -5.256882682e-02F, 1.632943153e-01F, 1.476015002e-01F, 4.015991092e-02F, -1.350112259e-02F, 1.327890009e-01F, + 1.951578259e-02F, 6.259177625e-02F, 1.815352291e-01F, 4.497404397e-02F, 3.777318448e-02F, -4.186710715e-02F, + 1.344874352e-01F, -4.750812650e-01F, +}; + +} // namespace cfm_goldens diff --git a/tests/vllm/models/test_cfm.cpp b/tests/vllm/models/test_cfm.cpp new file mode 100644 index 000000000..7980b26db --- /dev/null +++ b/tests/vllm/models/test_cfm.cpp @@ -0,0 +1,90 @@ +// S2Mel flow-matching scaffolding (#634). +#include +#include +#include +#include + +#include "cfm_goldens.inc" +#include "doctest/doctest.h" +#include "vllm/model_executor/models/cfm.h" + +namespace { +using namespace cfm_goldens; +double Worst(const std::vector& g, const float* w, size_t n) { + double m = 0.0; + for (size_t i = 0; i < n; ++i) m = std::max(m, std::fabs(static_cast(g[i]) - w[i])); + return m; +} +} // namespace + +TEST_CASE("cfm timestep features put COSINE first and scale t by 1000") { + // Swapping the halves yields an embedding that is still smooth and periodic, + // and completely wrong; dropping the 1000x scale changes which part of the + // frequency sweep each timestep lands on. Both are invisible to any shape or + // finiteness check. + const std::vector t(std::begin(kT), std::end(kT)); + const std::vector got = + vllm::models::cfm::TimestepFeatures(t, kFreqDim, kMaxPeriod, kScale); + REQUIRE(got.size() == static_cast(kNumT * kFreqDim)); + const double worst = Worst(got, kRawEmbedding, got.size()); + INFO("max abs diff vs upstream timestep_embedding: ", worst); + // 2e-5, and the reason is measured rather than guessed. With scale = 1000 the + // arguments reach ~130, where float32's spacing is ~7.6e-6; upstream keeps the + // frequency table AND the argument in float32, so torch's input to cos/sin + // already carries that much representation error. A double-precision port + // therefore cannot agree below ~6.5e-6 no matter how faithful it is. The bound + // is set just above the measured floor -- still tight enough that a swapped + // cos/sin ordering or a missing 1000x scale, which move values by O(1), fail + // loudly. The ordering additionally has its own exact case below. + CHECK(worst < 2e-5); +} + +TEST_CASE("cfm timestep features at t=0 are cos=1 then sin=0") { + // A direct structural read of the ordering, independent of the goldens: at + // t = 0 every argument is 0, so the first half must be ALL ONES and the + // second ALL ZEROS. Sine-first would invert that exactly. + const std::vector t{0.0F}; + const std::vector got = + vllm::models::cfm::TimestepFeatures(t, kFreqDim, kMaxPeriod, kScale); + for (int64_t i = 0; i < kFreqDim / 2; ++i) { + CHECK(got[static_cast(i)] == 1.0F); + CHECK(got[static_cast(kFreqDim / 2 + i)] == 0.0F); + } +} + +TEST_CASE("cfm euler step applies CFG and RE-ZEROES the prompt region") { + // dphi = (1+rate)*cond - rate*uncond, then x += dt*dphi, then the prompt + // frames are zeroed. Skipping the zeroing still yields a mel of the right + // shape while the solver integrates over frames the prompt was meant to pin. + const int64_t C = 2, T = 5, PROMPT = 2; + std::vector x(static_cast(C * T), 1.0F); + std::vector cond(static_cast(C * T), 2.0F); + std::vector uncond(static_cast(C * T), 0.5F); + const double dt = 0.25, rate = 0.7; + + const std::vector got = + vllm::models::cfm::EulerStepCfg(x, cond, uncond, C, T, dt, rate, PROMPT); + REQUIRE(got.size() == x.size()); + + // Hand-computed: dphi = 1.7*2 - 0.7*0.5 = 3.4 - 0.35 = 3.05; x = 1 + 0.25*3.05 = 1.7625 + for (int64_t c = 0; c < C; ++c) { + for (int64_t t = 0; t < T; ++t) { + const float v = got[static_cast(c * T + t)]; + if (t < PROMPT) { + CHECK(v == 0.0F); + } else { + CHECK(v == doctest::Approx(1.7625).epsilon(1e-6)); + } + } + } +} + +TEST_CASE("cfm with rate 0 reduces to a plain Euler step") { + const int64_t C = 1, T = 3; + const std::vector x(static_cast(C * T), 2.0F); + const std::vector cond(static_cast(C * T), 4.0F); + const std::vector uncond(static_cast(C * T), 99.0F); // must be ignored + const std::vector got = + vllm::models::cfm::EulerStepCfg(x, cond, uncond, C, T, 0.5, 0.0, 0); + for (const float v : got) CHECK(v == doctest::Approx(4.0).epsilon(1e-6)); // 2 + 0.5*4 +} From 34dc578760d0e36d6e002709e6c4f02f97a31d9c Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 11:29:16 +0200 Subject: [PATCH 31/55] oracle(MODEL-MUSIC-MUSIC3): the diffusers oracle GENERATES AUDIO, and the dtype set on disk does not run (#672) (#708) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * oracle(MODEL-MUSIC-MUSIC3): the diffusers oracle GENERATES AUDIO, and the dtype set on disk does not run (#672) W0's last item. `.agents/oracles/diffusers.md` said `gateable = no` because nothing in this repository had ever executed diffusers as an oracle. It has now: `tools/oracle/music3_oracle.py` loads all seven MiniMax-Music3 components from the local diffusers-arm checkpoint and generates a 44100 Hz stereo waveform. The bar is "builds and RUNS the model", so a pipeline object would have proved nothing — the evidence is a waveform, and it is committed. WHAT RAN. 1.0 s of audio, seed 7, 4 Euler steps, `--dtype-policy reference`, on CPU: 25 AR frames -> 86 latent frames -> 44032 samples, one denoise window, `(1, 2, 44032)` fp32 at 44100 Hz. 111 s to load, 926 s to generate. The autoregressive stage dominates — 8.6B params per 40 ms frame — so this is the shortest request that still drives every stage, which is exactly why it was chosen: the goldens have to exercise the condition mix, the 8 codebooks, the DiT loop, the vocoder and the stitch, and nothing beyond that buys anything but megabytes. THE ON-DISK DTYPE SET IS NOT A RUNNABLE CONFIGURATION, and that is the finding W1's loader most needs. Spec §2.1 records that upstream's converter writes the language model and depth decoder as bf16 and the condition encoder, transformer and vocoder as fp32. Loading each component AT its on-disk dtype raises RuntimeError: Input type (c10::BFloat16) and bias type (float) should be the same [condition_embedder_minimax_music3.py:64, self.proj] because the pipeline casts in exactly two places — `denoise.py:83` condition -> transformer.dtype and `decoders.py:84` latents -> vocoder.dtype — and nowhere else. So the condition encoder and the depth decoder consume the language model's hidden states uncast, and every runnable configuration satisfies dtype(language_model) == dtype(rvq_depth_decoder) == dtype(condition_encoder). The gated configuration is bf16 AR half / fp32 acoustic half, which keeps the DiT and the vocoder at upstream's converter default and at what SGLang-Omni's README says it runs. `--dtype-policy on-disk` is kept selectable so the failure stays reproducible instead of becoming folklore. SPEC §1 CONFIRMED AGAINST THE LOADED MODULES, not against its own prose. The script re-derives every claim and fails on disagreement; there were none. transformer 2 431 905 920 params all fp32, 36L / 32 heads x 64 / rotary_dim 32; rvq_depth_decoder 646 025 216 all bf16, 8 codebooks, 16 positions; condition encoder exactly FOUR tensors (layer_scale, layer_weight_logits, proj.weight, proj.bias); vocoder 60 weight_g/weight_v tensors with [8,8,4,2] upsampling whose product is 512, matching latent_hop_length; language_model Qwen3ForCausalLM, 8 584 475 648 params, vocab 200000, 36L, 8 KV heads, untied. §1.1 CONFIRMED AT RUNTIME, not only from source. The returned waveform is 44100 Hz stereo with no resample, and the rate is derived rather than read: frame rate 25 Hz x (44100/24000) x (960/512) = 86.1328125 latent frames/s, x 512 hop = 44100. THE ORACLE'S IDENTITY IS ASSERTED, NOT TRUSTED. The pin is an unmerged PR head, so the script reads the installed distribution's recorded VCS commit (falling back to `git rev-parse HEAD` for a source checkout) and refuses to load a weight unless it equals c6da9936. Proven red first: `--expect-commit deadbeef` refuses with the resolved SHA in the message. A venv silently holding a revision the record does not claim is a failure this project has already paid for once. EVERY RECORDER ASSERTS IT FIRED. The RVQ codes never leave the AR loop and the per-step latents are not pipeline outputs, so they are taken at source — two module hooks, a scoped wrap of `_generate_depth_codes`, and a scoped wrap of `scheduler.step`, all restored on exit. A hook that never fires would otherwise present as a clean run with a quietly incomplete manifest, so an empty recorder aborts. GOLDENS: 4.7 MB under tests/parity/goldens/minimax_music3_oracle/ — frame hiddens (the condition mix's input), RVQ codes [26, 8] (row 0 is the priming decode that emits no frame, so rows[1:] align with the frames), the condition mix output, the first and last denoise step's input/velocity/output, the vocoder input latents, the waveform and a WAV. bf16 tensors are widened to f32 for storage, exactly and reversibly, with the device dtype recorded next to each. manifest.json carries shapes, dtypes, sha256 and the full resolved environment. WHAT THIS IS NOT. It ran on CPU: nothing here is a speed measurement, and the speed axis stays owed. The SGLang-Omni oracle record is still owed in W0. Issue: #672 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] * docs(MODEL-MUSIC-MUSIC3): record the download command that was RUN, not one that looks right (#672) The recipe carried an `--exclude 'qwen_7B/*' --exclude '*.pth'` invocation I had not executed — plausible, unverified, and in the weaker direction. What actually fetched this checkpoint was a per-component allow-list, so that is what the file now says. An allow-list also cannot pick up a 29 GB native arm that some future exclude pattern fails to match, which is the failure the exclude form invites. Issue: #672 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] * spec(MODEL-MUSIC-MUSIC3): §2.1 claimed the on-disk dtypes were the RUNTIME policy; they do not run (#672) The oracle stand-up in this PR refuted the section it was supposed to confirm, and §2.1 still asserted the refuted claim while `## Now` recorded the finding. A spec that contradicts itself is worse than one that is silent, and §2.1 is the section a W1 implementer was explicitly told to mirror -- so the correction lands here, attached to the evidence that produced it, rather than as a later cleanup. WHAT THE SECTION GOT WRONG. It read the converter (--dtype float32 default at :267, transformer/condition_encoder/vocoder taking it at :208-211, the RVQ depth decoder forced to bf16 at :214), observed that this matches the measured safetensors headers exactly, and concluded the on-disk set WAS upstream's resolved runtime policy. The measurement was right. The inference was not. WHAT ACTUALLY HAPPENS. Loading those dtypes and running upstream's own pipeline raises RuntimeError: Input type (c10::BFloat16) and bias type (float) should be the same, at condition_embedder_minimax_music3.py:64. Upstream casts in exactly TWO places -- denoise.py:83 (condition -> transformer.dtype) and decoders.py:84 (latents -> vocoder.dtype) -- and nowhere else, so the condition encoder and the depth decoder consume the language model's hidden states UNCAST. THE INVARIANT: every runnable configuration satisfies dtype(language_model) == dtype(rvq_depth_decoder) == dtype(condition_encoder) and the gated one is bf16 AR half / fp32 acoustic half. WHY THIS IS WORTH ITS SPACE RATHER THAN A QUIET EDIT. On-disk dtype and runtime dtype are DIFFERENT FACTS about a checkpoint, and a per-tensor header read answers only the first. This project reaches for header reads constantly because they are cheap and exact, and this is the boundary where that technique stops being sufficient -- worth a named finding, not a silent fix. Note also that the original CONCLUSION survives for the DiT and vocoder (fp32 there is upstream's choice, not a too-wide accident) even though the reasoning that reached it did not; the correction narrows the claim rather than reversing it. W1 consequence, already relayed to the implementer: enforce the equality at load time and refuse a violating configuration BY NAME, naming the three components and their dtypes, instead of letting it surface as a type error deep in a forward pass. The oracle keeps --dtype-policy on-disk selectable so the failure stays reproducible. Verified independently before this commit rather than taken from the report: the committed WAV is 2 channels at 44100 Hz with 44032 frames (0.998 s) and amplitude +/-0.30 (real audio, not zeros); condition_chunk0 is [86, 2048], matching out_dim 2048 and the 86.13 Hz latent rate derived in §1.1; rvq_codes is [26, 8] int32. Issue: #672 FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --------- Co-authored-by: Ettore Di Giacinto --- .agents/oracles/diffusers.md | 32 +- .agents/specs/minimax-music3.md | 89 ++- .../condition_chunk0.npy | Bin 0 -> 704640 bytes .../denoise_first_latents_out.npy | Bin 0 -> 44160 bytes .../denoise_first_sample_in.npy | Bin 0 -> 44160 bytes .../denoise_first_velocity.npy | Bin 0 -> 44160 bytes .../denoise_last_latents_out.npy | Bin 0 -> 44160 bytes .../denoise_last_sample_in.npy | Bin 0 -> 44160 bytes .../denoise_last_velocity.npy | Bin 0 -> 44160 bytes .../minimax_music3_oracle/frame_hiddens.npy | Bin 0 -> 3276928 bytes .../minimax_music3_oracle/manifest.json | 344 ++++++++ .../minimax_music3_oracle/rvq_codes.npy | Bin 0 -> 960 bytes .../vocoder_input_chunk0.npy | Bin 0 -> 44160 bytes .../minimax_music3_oracle/waveform.npy | Bin 0 -> 352384 bytes .../minimax_music3_oracle/waveform.wav | Bin 0 -> 176172 bytes tools/oracle/README.md | 108 +++ tools/oracle/music3_oracle.py | 737 ++++++++++++++++++ 17 files changed, 1279 insertions(+), 31 deletions(-) create mode 100644 tests/parity/goldens/minimax_music3_oracle/condition_chunk0.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/denoise_first_latents_out.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/denoise_first_sample_in.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/denoise_first_velocity.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/denoise_last_latents_out.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/denoise_last_sample_in.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/denoise_last_velocity.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/frame_hiddens.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/manifest.json create mode 100644 tests/parity/goldens/minimax_music3_oracle/rvq_codes.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/vocoder_input_chunk0.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/waveform.npy create mode 100644 tests/parity/goldens/minimax_music3_oracle/waveform.wav create mode 100644 tools/oracle/README.md create mode 100644 tools/oracle/music3_oracle.py diff --git a/.agents/oracles/diffusers.md b/.agents/oracles/diffusers.md index b3bf9d560..e5e4333c6 100644 --- a/.agents/oracles/diffusers.md +++ b/.agents/oracles/diffusers.md @@ -22,10 +22,28 @@ reconciliation, not a silent follow. The same shape applies to any future diffusers-only model: the H3 integration landed the same way (#14355, refactored by #14371). -**Not gateable yet:** nothing in this repository has executed `diffusers` as an -oracle. #647 owes the measurement; [`../specs/minimax-music3.md`](../specs/minimax-music3.md) -W0 is where it gets taken, and this record flips to `gateable = yes` when the -oracle demonstrably builds and runs the model. +**Gateable since 2026-08-14, because it generated audio.** The bar is that the +oracle demonstrably *builds and runs the model* — constructing a pipeline object +proves nothing — and it now does: +[`tools/oracle/music3_oracle.py`](../../tools/oracle/music3_oracle.py) loads all +seven MiniMax-Music3 components from the local diffusers-arm checkpoint and +generates a 44100 Hz stereo waveform of shape `(1, 2, 44032)` from a fixed seed. +The per-stage reference tensors, the resolved environment and the request that +produced them are in +[`tests/parity/goldens/minimax_music3_oracle/manifest.json`](../../tests/parity/goldens/minimax_music3_oracle/manifest.json). + +The script **asserts the installed revision** against the pin below before it +loads a weight, reading the distribution's recorded VCS commit rather than +trusting the venv, because a venv silently holding a different revision than the +record claims is a failure this project has already paid for once. + +Two facts that record scope rather than success. The capture ran **on CPU**, so +nothing here is a speed measurement — it is a correctness reference. And the +on-disk dtypes are **not** a runnable configuration: upstream's pipeline casts +only condition→transformer and latents→vocoder, so the condition encoder and the +depth decoder must share the language model's dtype. The gated configuration is +bf16 autoregressive half / fp32 acoustic half; `tools/oracle/README.md` records +the exact error the alternative raises. ```oracle-pin id = diffusers @@ -34,7 +52,7 @@ upstream = https://github.com/huggingface/diffusers scope = schedulers, VAEs and diffusion pipelines vLLM-Omni does not implement, including models whose only reference implementation is an unmerged diffusers PR pin = c6da9936e4bda83107943a16eb8682e9a37d8527 pin_label = PR #14456 head (minimax-music3-integration) -pinned_on = 2026-08-13 -gateable = no -evidence = #647 +pinned_on = 2026-08-14 +gateable = yes +evidence = tests/parity/goldens/minimax_music3_oracle/manifest.json ``` diff --git a/.agents/specs/minimax-music3.md b/.agents/specs/minimax-music3.md index c9c4dccfa..ba8b50cea 100644 --- a/.agents/specs/minimax-music3.md +++ b/.agents/specs/minimax-music3.md @@ -10,7 +10,7 @@ [#14456](https://github.com/huggingface/diffusers/pull/14456), head `c6da9936e4bda83107943a16eb8682e9a37d8527` — **OPEN, not merged**. **Cross-check:** SGLang-Omni `748a0b437e4a8faad44d7bbfd5a0ae55d1fef830`. -**Status:** W0 — spec committed, no engine code. +**Status:** W0 — spec committed, diffusers oracle gateable, no engine code. **Developer directive (2026-08-13):** "land minimax music 3 support complete, to vllm.cpp, wired to the ABI and to the example http server, merge to main, tested e2e." That fixes W6's shape (the ABI surface and the example server are in scope, @@ -29,10 +29,11 @@ from what is still assumed. **Measured** (safetensors headers by HTTP range request, and each component's `config.json`): every geometry and dtype in §1. **Read** (upstream source at the pinned SHAs): the component decomposition, the native↔diffusers relationship, and -the dtype policy. **Not established:** that the oracle runs here at all — no -diffusers execution of this model has happened on this project's hardware, which -is why `.agents/oracles/diffusers.md` still records `gateable = no`. W0 is not -complete until it does. +the dtype policy. **Established 2026-08-14, after this section was +written:** the oracle runs here — `tools/oracle/music3_oracle.py` loaded all seven +components and generated audio, so `.agents/oracles/diffusers.md` records +`gateable = yes` against a golden path. That measurement was taken on CPU; +nothing about speed is established. **This model has no token-exact gate on its generative half.** Like MiniMax-H3, the acoustic path is a flow-matching denoise loop with no logits and no sampler, so the @@ -152,20 +153,47 @@ assumed from reading the script. that layout is **refused by name** with a message saying so. It is not silently mis-loaded, and it is recorded as owed rather than discovered later. -### 2.1 The dtype policy is upstream's, and it is deliberate - -`convert_minimax_music3_to_diffusers.py:267` defaults `--dtype float32`; the -transformer, condition encoder and vocoder take that dtype (`:208-211`) while the -RVQ depth decoder is forced to bf16 regardless (`:214`). That matches the measured -headers exactly, and SGLang-Omni's README states both of its placements "run the -acoustic stage in FP32". - -So fp32 on the DiT and the vocoder is **upstream's resolved choice, not a -too-wide accident** — the case AGENTS.md warns about (a token gate cannot see a -dtype that is too wide) does not apply here, because the oracle is fp32 too. Each -fp32 buffer on this path still carries the one-line reason the policy requires, -naming this section. Any future narrowing to bf16 is a measured change with its -own evidence, never a silent default. +### 2.1 dtype — ON DISK IS NOT RUNNABLE (corrected 2026-08-14 by the oracle) + +**An earlier revision of this section was wrong, and the correction is the point.** +It read the converter — `convert_minimax_music3_to_diffusers.py:267` defaults +`--dtype float32`, transformer/condition_encoder/vocoder take it (`:208-211`), the +RVQ depth decoder is forced to bf16 (`:214`) — saw that it matched the measured +headers exactly, and concluded that the on-disk set *was* upstream's resolved +runtime policy, to be mirrored as-is. Standing the oracle up refuted that. + +**Loading the on-disk dtypes and running upstream's own pipeline raises** +`RuntimeError: Input type (c10::BFloat16) and bias type (float) should be the +same` at `condition_embedder_minimax_music3.py:64`. The reason is that upstream +casts in exactly **two** places and nowhere else — `denoise.py:83` (condition → +`transformer.dtype`) and `decoders.py:84` (latents → `vocoder.dtype`) — so the +condition encoder and the depth decoder consume the language model's hidden states +**uncast**. + +**The invariant every runnable configuration satisfies:** + +``` +dtype(language_model) == dtype(rvq_depth_decoder) == dtype(condition_encoder) +``` + +**The gated configuration is bf16 AR half / fp32 acoustic half**: language model, +depth decoder and condition encoder in bf16; transformer and vocoder in fp32. That +is the converter's default for the DiT and vocoder, and what SGLang-Omni states it +runs ("both layouts run the acoustic stage in FP32"). + +Two things follow, and they are the reason this correction is worth its space. +**On-disk dtype and runtime dtype are different facts about this checkpoint**, and +a per-tensor header read answers only the first — the measurement in §1 is still +correct, the inference drawn from it was not. And **fp32 on the acoustic half is +still upstream's choice rather than a too-wide accident**, so the original +conclusion survives for the DiT and vocoder even though its reasoning did not; +each fp32 buffer carries the one-line reason AGENTS.md requires, naming this +section. + +**W1 therefore enforces the equality above at load time and refuses a violating +configuration BY NAME**, naming the three components and their dtypes, rather than +letting it surface as a type error deep inside a forward pass. The oracle keeps +`--dtype-policy on-disk` selectable so the failure stays reproducible. --- @@ -275,7 +303,7 @@ the operator. Phases are separately claimable except where noted. | Phase | Scope | Done when | |---|---|---| -| **W0** | This spec; both oracle records pinned; §1.1 sample rate settled from source (**DONE**); **stand the diffusers oracle up and prove it builds and runs** | oracle executes the model and `diffusers.md` flips to `gateable = yes` with a path as evidence | +| **W0** | This spec; both oracle records pinned; §1.1 sample rate settled from source (**DONE**) and confirmed at runtime (**DONE**); stand the diffusers oracle up and prove it builds and runs (**DONE**, `tools/oracle/music3_oracle.py`) | oracle executes the model and `diffusers.md` flips to `gateable = yes` with a path as evidence | | **W1** | Modular loader: the six-component layout, weight-norm folding, the fp32/bf16 policy of §2.1, native-arm refusal by name | every component loads with shapes asserted against §1; converted-vs-native tensor equality checked, not assumed | | **W2** | Global LLM on our landed Qwen3 path at vocab 200 000 | hidden-state parity vs `transformers`, then token-exact RVQ code parity vs the oracle | | **W3** | Condition mix (8-layer weighted) + RVQ depth decoder, 8 codebooks | per-stage tensor parity; the depth decoder's 16-position window exercised at its boundary | @@ -325,7 +353,20 @@ pinned SHA. ## Now -W0 — spec committed, no engine code. Next: pin both oracles in `.agents/oracles/`, -stand the diffusers oracle up far enough to prove it builds and runs this model, -and resolve the sample-rate contradiction in §1.1 from source. W1 is not -dispatched until W0's oracle is gateable. +W0 — spec committed, the diffusers oracle **stood up and running**, no engine +code. `.agents/oracles/diffusers.md` is `gateable = yes`: the oracle generated a +44100 Hz stereo waveform from a fixed seed, §1's geometry and dtypes were +re-derived from the loaded modules and agree, and the per-stage reference tensors +W3–W5 gate against are committed under +`tests/parity/goldens/minimax_music3_oracle/` with +[`tools/oracle/music3_oracle.py`](../../tools/oracle/music3_oracle.py) as the +reproducible recipe. §1.1 is confirmed at runtime, not merely from source: the +pipeline returns 44100 Hz stereo with no resample. + +Two things W0 hands forward rather than closes. The on-disk dtype set of §2.1 is +**not runnable through upstream's own pipeline** — the condition encoder and the +depth decoder consume the language model's hidden states uncast, so they must +share its dtype — which W1's loader has to mirror deliberately rather than +inherit by accident. And the capture ran on CPU, so it is a correctness +reference and no part of the speed axis. Still owed in W0: the SGLang-Omni +oracle record. W1 is unblocked. diff --git a/tests/parity/goldens/minimax_music3_oracle/condition_chunk0.npy b/tests/parity/goldens/minimax_music3_oracle/condition_chunk0.npy new file mode 100644 index 0000000000000000000000000000000000000000..da995e8131d8c37cfc24d8db5a1c01beb0212e84 GIT binary patch literal 704640 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

~Ooy;NW&N)i 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 Kygwb>@c#kU!4JUz literal 0 HcmV?d00001 diff --git a/tests/parity/goldens/minimax_music3_oracle/denoise_first_latents_out.npy b/tests/parity/goldens/minimax_music3_oracle/denoise_first_latents_out.npy new file mode 100644 index 0000000000000000000000000000000000000000..ac02589ad804db759fb4b4303ffea61e38aace92 GIT binary patch literal 44160 zcmbT7`8!tM_r@ial%WWfBzc=cLK-}0ElrYC5|St(NrhArD)W#bWsC?JOA;#aoW0N_ zNgAmn&9j6`n!Y~&!uR}k_D|vi8NX{GH7t98Rf{6qpZ+&1rW_0^cKq9JfM z($G=SaQF7z?Yqfqqqncy=KrmmZQ8zT^U(S(k4-+Chu$@38O+sDnQN@2qq1M+|My5- zf?}-q9)n?HblD5jW$ey^2{_}*5ww^XN)}_XD4aizCb#98O^Ok1Z&0AYGlx0L4?js( zVjUa((iV5TDqv&qH)j6qpbdL{AwZaqcH5_8yFxqL@Dow4a9Ev^7SKGEU9_;!1`~cO zTVMPejD6fM*wUNwd zFqG0}2a(Ltab%ipiUv-){J+&nSb*K|So`{g;w=~q3o+o+W?gCG2J!wu^amegE)i6F0X#Gl8nm;OxD!X?v{qsHK z_k+5b2J5`Rv! z6H}}(YOWk>+cXiC-zt*TX(_zDS_S9y)G(=gJD|WjkhP4tK$E`3qFE-#TOD9bJtvy_ zq$We!zO_tGES`DWjbti!>}o@Otms-iVrjV%+&s09M8_XtM>j;mRJ|1Za&k4ZDtBb< z!)@3EEJqUqbO3FNGAN|EE_(RhyuS!qSGI*Ef2A5p=~(vVa{dU)CREP>adQ_SeT zAD*6fg1y`Jnmevg#u{QK;WK_Ks@}QJ1^k%J>{O$$`nCXzpP8f8t*f+fa|pz*%c4^^ zLQq_F5p{o`it`4nn0m=Ww%L0cx*RH?nV#XWYOFQPIGD~FU(KMLU&E*)d=q3(KZT~r ztLCe|n#YMdm&08B6de0|9GXU)F)`QDMd#@0?9`HT@ZoC>d_5NjPJ52x>x(aN?S*k5 zImr}n7&%e7S_+H_XAlnG8Q6`yM#@E9iXF`d8BXPzAOKkVqh+8xEV)MosVEteX!sq2+nx4sB zGcdv{XXCJQ{CIjHmBJdL7Qv=p<hZ)aYN&DJbnm^|-6L~fTr)S<^4{-|~ z65UEU6WsaZ{gBo=eC#~;%RPz( z%$hyG;V5Ks7aQnU z4`sj0IJtNF=p!&hyYJ<6-t#l4?2l(#Pkv%+w#bt~$q#0dvX|{x7{(_EHbC)oWmK~P zUgonr-#ld|o)@%0$>rGC#&VlaAkKc zsLmdXWeE|`(H02{-DZ&c?{b#o;EWr6_rfh<2) zvSb)LlKl5&Q%_7id!8tu=BDS&)LvK6F;9RS46S*Gk6C=`i+BuwGm5=5^h2}Rm#JK? z5Twd8@mgOV`m}|zf2wbo)~{=HLO&lCN5u#icpRq-i}KmROR8j3a{!y27cnW}dv-Xm zggNxov(&vGK~=#6652plj+7m8FHRr-zVCcQ3bV{(QEWMMA)`;iND522Q=djx|fiqWRw!My)oIjVV_&OG+J z;J|HJTrhJLTAj+^Ee}s)H|D8Q*MNf1`|(W7P^^ZLBRJGGEX6XvsaUl#5_%oxlffN5 z?5I{`3qBvl$DLum{OB6xhTBp3&~yYRW+bW&txEc!+(XSq882X3Cir? zOKE*v0*+jL56USHQV&F6zNs$?Zv~)AO%}Suo#5*vm(YJ~5T0$k#|G+@P`&XO$^Dr? zmZvSbf_(b~Z5P;x14hMr4ug1-76?2rbMV)SFmURh0l zcGcsUCG*jHpES!Gvjl5GTcA2Nj_bJ=z|HLb%6A<)O2~3kNAp9)u<+8#O(ayjfDMB3Yzx>Wlz5kC?d38!@ zx6TyShEooX0eXBPZ`omo^)YVVD3e1H(7Tr(hHcqZjc z9mAegUt%q;yU1w7PtIHa8`NheL38W}E_0y?q_U|v`^z$Xk`zKG-+jWav7y-S_#O7e zrh~A}0OwUb!>@5SA>q3oIhafU&4=bpzorU3PY5us_YJ39B8C5gM&p%FhAdy&8`RgE ziAsht z=w(z5j!#}drhE|ghsSWsYc|vFch^DmcPcBqw~$`!>19%TWYCdm<6p&ew%n~6r>Shh z#>jWvMu&cAej!6rYZib>wl*y~9zhPV$3X4nBf98tl%zjTqZ-}IU^AGFIZ6}paYPBW zAG*s|>wd=QQ4yrl5Xu+65MZ!$8}t~Br2V}k$$s@hmLjK!&9mY#-LC+jf7-&gTo}&X zniUPE<+CVpaR%$Q$)LF^`$*pG7{6#qF>h6q%-vV>0{KPp=miN^31rz;a;onR%3~^DT zE%{ejnNbLNEuBnNf#qC&dlfshVl=1(XJX(<6YAgmi1}1WpkmfJ&fwE7^wBlpJtA}2 zz;6><_9?Hy4bvvEpN%JR zVbOAk>~h5>QC&7Z?l^OwR|j)reYuHITQFZio5ub63(X>c4^?-O@39Tk^yn-!&$nUa z_iplP`~#4jD~2ueFQCuaZVY;Qi257#sZ{<6Y&br^8Kgv@vtS8~t8vEi5C3tRTcoh( z^CCWRm=`~zOOf`y+z0<%%wQ_Dcerb(C!x5lF=|yPuxoeTvkO}f!?m;1D1^RKkU=;l zF6-yIY==?7qxI0dJ%^gJ7E_a(9vy27BblW4GHz8K)R?EQ0jR z$!K-yrLaiq5fh2ep(L?bbbQWZ;si@t_;3_7dF5k5k_59JlZP@tGa>l>N6^?D1wr$s zP{oBPj6OV`t~`?@k>|PsajD77seLCtRB=Y%U41a{-*;#XOyXR|E=TpU@uqP)S*+z| zI5jm~0-3&%>}z}{7#nHhwOI}5KJ_F{b}z@iH!5HxX9)Js4Ea`136|%q3p;K|VSiR5 zCVz6^lDxN5d*BLI>l+Ss?%~*3V1v$TD)6RO(KI*75W}~NvBR&E(BM=8x}3knDo4M7 zW!}Xo;`5yiR-52`K_s541Cw_Ki$jU{NB%tanzpd9XFcGx-fT4O zSOpE76xxQy)7SF{DEed=h#VRw-1qbnwWt!fthS+;7rD%)egd-^(vq5Qlj-()J?YHCmJA?EyD0wk;1xV2TtPvzswvn|7R3wf=&zD3J@%47gRlS) zy>N#+pB4-y=T=Z6m2k_9Pm_g$D0l11Hca-@#uQCesB|02M81C}Pv1Etik{s3MIGRJ z!4Vq0zGI%*T5P=W74m#8;_e-3bR%mtt}}0kj@~i2@UA@G4SoohXO^=2Xh2o+cX^|A z9h`x@3-+g_v$qGFXr=X3s@We0YR+NAr@f(~K4L-1Pq}B{iq{0SOe*LNTcQ0Q)m4k& z3mtfQ=W(KWaqmB%mDISB&;a+oD5iLxyP)S#8b>2Xh^8-_gw9XQ>9<%q_+%x~Ab%QLa!g2j(qwAT*vAx~Eu#^}{bYMYmt5WlW1`P_ zE?m5VZ|ygtpN&VzZT2}xSY?j?*8PC^BWbAeM8I#~F&cM?T|lQ(xp1lNJXSF*^f-IcRX(JKR}7@rEoiDNbBsr&t%j_2<`@iVXNzBCf@rMik?5`?(3gp zZ%$6cL!UG-n{}{?396Lt)>k|G$T6z8+sY!A`{2mP26lDc5%@h@j9nR&GIgABj$iyE zjdndBi)`(Aird-57C)b0Dz!F&Q{Hw3#8j=>xE5o4zgULy)P~XBk4Y>rO%%`M%F-+2 zTaekK4kNYuAUZ~jJ8pj*l7CMpwfX|?OZFMMS64|sv#WXMDUpIYhaOJq$TL`=_J+(? zA_Xgq;Iej&!bKNsNhQBou<-dKe*HT&GLqE>pT%3aP1omA;buDwjxDXVU8F{FB`bMB z_B`6Q$N#`OVZ?IEKcp3*E#1e2~2-t z4^!dY`H~z7`o$e)oo}`>wg1Lo|Dsw_Y3pRqTZh$&Y!WwVp85us1;?@0i(*)5HH~iX zttG|(Bv4-|lir;?0ezR;sPgkTa=7h44G;8b!K)bDxZyvNO5Q}qgV$-TY$SO+G+-5~ zw^+^^NATE~LvGF0(61&|_uG0m&E2Sv+RB4$+ulx=*rounW=Ba}`5|Xyc$_$~H}pX| zk(^C3_-cvMpdNIQ4w{K#N7`iayP<*Jp`$@J^A;yzZ;0vUBBl~shq0Qz04PyDM|V8- zz=*^(s60cAbrqTlD|O6q-3?dFQ@9QGzQl*Gf{T5A6J&WPSkqU6}IK5(X?6dv~&J+ zv@A}A;%PDzUgd;IiJv$@wlk|Sjbip!wjg`*g%ioU!v5|vjMi zhjqT=+4enRm>9De8?UrdUAGV4u3bg@wk$*K)oM(4#cn)e>`FZmMv!wdle2vyMqLsK zlu~0(i9MBUu51yv;P7I0OxGJPc+8;>hQ8>0I78Sz!5p47Cu7Q!TQF$zge4ABWxnTX z*;o15m^d+?U2xe*QmbU^b}NrV?Y?NJYZI`dJ@cq%LxJGh?=U9!&JUY=tN1$eCX?>r zd)eP6QOpr<=NEX3a7#WP&``QXbEdIj zI$4dEmv1zr=eBB_J5x|i~CLTRdfB5gXj{SM1DK7oy&a(RzEWy}MA@S65Z zu~{d8d8toD57Stv+h>mL$0NAGr$_O?B@L8MH^9RQcR;Pi8@g92V`s60=~#<4R7%xA zk$);Cf@8|2GK&-VhlVla(bp;1zs-x>#tPU7`_)}5&Z?sCt8Z-SlKuF1f*5xGcMkeK zsN&QgyHIZZXYjmJg9GD(u@|l~gMYf{FPchmN0ph|t4J`aE`~_m3J8BZi`H&CKrYQ6 zAoBD&ip`0^QSZKi%BK+abn$&QIdePNEGa^f)rU#5Nt3RfJB$LyZ_v47Bgof&fcmx8 zl>a&rx++%-k~1oC-VpPP)*Ok>ud&;#g&{Hu@jk&R+a?jQZV{ zQnIfyi5~jFl)_p#LD?g&>GU4d-c||2W4kDLK`4Iuoj|dmk2U?9=*r+EsyVm_7gmh?}f((N%U`sf?9LPofFC)9}vD0My?z8iOir;efjUnMW#pAGv}? z)VQEZ<_wb3VeEKU0y~*|nbNCja6wTeG_%^Gy5EaYzpti85hw3Tikz{i-7#B$`_^5>Vbxtwbx{p1>nCyZ zmjvSa{o&AdehGV=u#}eN7co6eP3-rJgS&1X*x6=DJu3I;@2YZ0^t#I@XEpE-x>sU? zelZ68Nn=ZI24c8xI$Wb1T~@X2$%=|$Q;Y$!3MjZ=#uCPI|5lrlllRvBMA^$DYM zgK$yxFtU926zazGL;K5m;i;l^Xmlcio8y;?8N0OU*P$aYx$`bf514|UDisiQaSI*F zcBE*DUEJZ13;Bm1uR?y7F;y4rfCZU-+cx}x0k#I^K{6Qe)1?(EdL z4YYQjJqy1Qho!U6lf>2c@J2q4I!XsY{lsjvJf%nNp9k2^m8IP4lt{j0{t{dn8w}cJ z;#~FVe$Fp3iOpRdN0J*y(BCDI_aD3?B zd(!=ZOxB}5hm=a*L>NFvc1?YE5-MO-4iC=JAm}E26<4%vfw%}R6h#O z^ycHcaW7CQR)>|!TH=WeUm_Id~oPYfpptmdpI(6`H2jD()~p>=G|w6N?!!CL|rT zgKbOoL8VI*X&P7v)w9b`%)tffZr`L6+rp{P;tY*Uab}M?jLm_I^>^V zED52(9DPjq(#7}O@CVKJlTrJW0P`lAVAuO{R$MfLlEtp0=j(qszC{*W&kynv2gcw; z*M8LgSP#DE_2JI@xG^(O|5_FQOwdo)*Y>cVamn4+f8-Tpyheg+9io+ z>c`_FFHwB+VL$enxq!jw6bey&O#THwaAxd89R6P=4z~4UfL#)t`}iIvRo3F;DZtjO zss;69>(TZ0WGpV5f(CCaFw;;TV~^^=g!&pf*?OEEe67o@1u9JDXFRn#n2&d98 z8ot<;wA}xKf?@n(jc|XTyZMwZiFx+EyIc zpn;_q*OJD@I*d=u6k^Gz{+Lmg-9I`)=RMUQnOOd9@*%R=w$w&Ur@e&HSd5A)n z?w}x*3N}zK1wPe*Y`e@qI99iVMOqD=p(g{#%wa7Vy*x!SMMcbNOFRA9a}`W)9pabO zPo>ijOfaF?7CUR^0@)^#Lun+t;w(AD@E)>(w3TSH*@1TSf8>M0fkqe?AXLRpY)6+|zK0<#rAnWEKVR z2E%iiMyEErX(2=9+yZhxHH&(-9c5>}=YdbcWNyHy8K!o)QQZAAth{|6OlqvlGzNoUKV-FbPt!O)z zC>1KK7v99+?RseT!Hj-4eSw0GCf2h|vMyRIjhV_m6OJ`b!3?gHE!I(i?Kij34Y?Q0 zIco`(eRpQxjCE+y;7~T9!i2O`9y8yf(WG$813x?%4h^%G;KTAQ=&LM2pPrlJ%OP(} zL^7I2cq-wk-Aia?zCK;qG744RD$$0pN!Y$-EB5revYMPfY=a%oEZKEzf4!J##N2>u zyN5V=)PJ=>=k)mkbr~w1wt%Lo{Dz0U#&l`&6#CuQz|73Nx#>m%+OBx9Mt!;qjy`4y zCX2*;$|;gB9NnjK%Niucc`(v#!G_&~T4w-{%j>GmCL zePb@%D9m71qb+f_OE-AP_i^knkWrGcps{u+b8>hKJqjv89py^;RW=iCq*`E`+cVOz z%w;!ny;%51cc?B|L5G^V*#jD7nmFqaD#cxg0nI2hcgUrgPkV9skIzhR`bl=mLJmZ% z^C|ao4I8HZm8B@3M7y}}tfQ}h>BnZ^eTn}_zdoCebQN<}{>aVfUxxLCA-EHgz#(D^ zwtY#a{4xReHt!=;+vveFcXRB2v4BEG`%zD-CjHiX0EZrYWI5lKbNQ==`pk}@EJB?M z6>(aq6J0_#bgt60AV<#cw-ob94uye#3HVr6ow?oa0q*lq`Q)5u(eD+ZUq+p>-xxDl zHw3ldb8OcW1$z2#E7)1=WmQ_TT%Pwh>>c---Cm!@Y?MaV1sNq$d6^@N^^2zs3vTi) zPNghLxC{r<=c2f226@O1<1VR{!oxZJY#CPp#)}JJc)S9d+v)Hhca5$qY758X;otZU znepVJ(?TV2z#(@io5Tw{6~#d^F&4jfHVN95{(|K2dt4X0E3nkR2PY>;(Mans z_~AX|?>fw{-4G~O=OXyV)`p6Z!D(CCV3y39yGm&0-&AVM{|-~joapl1Q2frf(bCHy z=rKMBE3CG`;I|THy`~#_SuP89$biA|N5KDY5^U~!1^(G3aIr3k-JTpm7XSL8gBO4) z-8H$Jr9|Co^4R9~A3isHM&|eK(3BTN^mn-l3M2j4V(aB(`AvbOLxu!=!-*I;3a-c}q)h-UD;= z&?{$FIVm*lHd24YYkqi69o7|o5b7>bK%Mmw=r~!MiJx4=gxg}USZzP{IyB+n!5+4Br)MlyTSv1mB64^& z){hi}s`-|(Y!!l#imqi;HHJ{F2P@e3)rD3**= zcQaM_({N{mCT0Ko!I|xxf^uH(nON#6{&MFGCh}_=KV|-Bc1q5LHiTYA!9X)B8>T_s z$#s#J@)sWw}@3%?Gx{2h(m80?FkGOsBZffYC1{H!+8Ys8n51g>*V)aY2>B7u6V6-5SSDpNqi9GM5 zFIqMj*628t?aE@##j}{xSX(Ijah^8ZTY-CjhSU1Qv*6}3k46uQ!@JqDplXdaZOUwe zQ0Xj08~!byr5?d9TxFn}qd}tU{Gnh;Dho_~134CQOvir~3S?H1aO+MuSyoHyo~^^+ zuyZK&#Twtmr(@WPKTM^f3oppH3RZI6EOA4hFu>);P~JdXn4d5l8vynpF|Q8|c=Ss=dwA_KgSiq^u<9#|{FFj&BePgdP$YocaN6H~ z3>`l{hap>otY$3~ERqZ(u}lRLHtmAC*&zgDmFg~izDN&#t)v69Ey3WJ6RXuI%&r0}RWVt37Rb@R; zkT1uzx5uE|!vOqYc1O6e))z7g>M?btJDQEFWS`nXNMd*#OZ1imvwBZz(T`+MI~zwG ztYm7!@!0*$3sWCAlF`-Y%y*y)Uhj`!Nh?ND;jBIQV_7b%n^h*5*AfCA|LKC;+3zfJ zLkBCci-I0oak`mZ3$I@N1)1a}^u{QJRYeZRsnu50Yci8N?QBMuLXtSkU8=b9Q5XxF z5kUr{rST&Zqt34;IL@wue;ZAcBVFzTx&sm5J{TU9x* zG&d2YEM3qlsmr86Ivs9LO2FjorKtaEEi(cG+#aEHc7$NDO&;Vg z{|H41?@%u-i|JXYk@I<3QVEEHI@xo~?{^kuoY6}CW0zpJw;}deCGr6ueewN& zO8BQj13dQ~1(WUr%)s_C8zbCA8gjE(OWSc4wYDCDQ@)|s{RtHCM2{r<&OngoQTUd8 zo~+mGV9JROxRReu*X|8vA{Q>=)!xWZQ_ebE9v?~%djly*X2{dXUraf*sRB{kzpTkB z3QlP}g6c6=w5_ck%B)7#75}V&?f27ITc8Q$d>sMRlA36Wxk4rJMqYI3S~hmjhxIN< z#BZ*jS!tUEjX!geenuyPRka*nnUO&E740ed@>zOx=_uQ|qL6(Fl%Nmk6Cv_aKAgEL z#YCdlk@kfjAUf5K4Z9r7Mil@h*1AK~-7TbT@|9n!8Av(B&p>bIR$A@8nabZrf^OM2 zjDDj?Hn*H;4-2Q}v|wD2)yUp!ouM0JR-vtaD$NWOr|O+zT*c-S&`=vo4@)_gKBa`^ zrG-#$oGj{1AB`qA{jlZR7Z#{-0ONlq4fQnZnWxTuaL_z}gP%3wVTvRBEi(Z=)x7~7 zkrSZOFdVDy>f-U|BJ`qiGWV+17R8!m>Kv}|yyAm#f}G{0Fv_zGt_wmDzD1+Rk(6bEtF%M3=fBbHY4;dk+NS%Cf>?$IhaNohhXKFv7kMQRrjrP9lda!L95z^(KU4?zL$2UmpQC z4c4(+M%onZV8Yj-J61lG8=8xVV%pWau#NL)*AC@E{-#*GE1rUlO%=?3DCc)^laO`H zx955Jl_-7VAl&c%3~A=07)=hQC4M2G;p`0d$Br}KyW*fZs)v;|DKPz<#P3c)1 z*y%Z0V6FLuh2C6?pS1MR{#+fW5)s0i8jKcNud09-hBfFsxd8&AUSf}Bwy<}d83qZa zQ23T^7MYYyHw$hs^Z4B?uHK)%C?CfQs|s1w6>Ux_Qk8sfhq0WH5yC7lTP!o#NAn#L z00MG3k=kHV%11LdC_dZCR48~}?T^MTC#-51kVd2S07(4thHD60* zHCO*a?1mrQ$Zl<{_MJ@v?iNaWOsA%k)3Ieu9rHOcff5#12nN<=^5e^+u>ZC$sl?=Q z#>Sa!L4+fw-jw8r*QZe8q62j3PdsR? zh@igX2p6eP_gr2c8QS+rHHhkUBoFDxHjf*=;>BvV~pGh?L$_jqu@)uxJ)&QN) z4bW-VV!D#sKud4ik!^4!8#8Vc{cSvf74->Voz%}KT(U&_2nX=h-_9H+oE_p-(oE6& zF^NWR!p#F?G5w+>{w=HKcdRv~TS4rA%WKJa03;` z;}Cf?nDr!h3zdRa;N#V%6wqWzag~yIe$!)iC}Ryb$?GjsnjQ<^4(;I#f1O}H^}FDr zZWy?m-Vokj{1a*}wX(tFov0$+FO<8VN#SM#EK2+b)0=f3H=ap`qf7H~`WF=x%vQkQ z)3eaU-Pa**rLTde{b%9BYcpJGVFHgo&SlShi)p)9F(`_BU`yk2&}rLg(B!=#N7(^o z+uyU!kB*q)=@LTQ*Ha~MA`v13v(>onR?_agTs{PYwm|-k;A~&3k zUiJhAW{R6GT;;&#tuDmcv`C!n;z*XmZ<2GXC@UM+%&K>81(&4PT+(1Gvu_o}8C7x^ zd+0MeHZG1HTX~>a^${!`(xFezts$kLiI_Tj5l;H}h|%M6dKG(&(i`@{#_LBw?N=(P zRn?=Y(SMYp%I^CY0z{i?SrY(x$kYhDo@Fw#zB+h%!=?_lkTAgO_ zHf9iCNt-#@-iAu-i0^GaFR}aVGmfabt#%ciKul zv$VO6^$T#>zflwwzmk7ZGMyQ9kAWvjIrKoul~vR)$BorXXoFV-OVxgjYO-4dxe?E;YHYBT~ z1x&>9urOef2-S_B3HP^TaKpVHGqDXL$id?+^V=s%?~3-*Ya3aVt1D;wHDg#(TP{J+ zYbJhRI}K6_6{pRmrrvB8SDehmM&6}K*Zxu0tZaTv&0flyHoGn{VG-GDYzOs!hpGS0 zxVmBMPg9ShC=CfJ(*GHYM-RTB-{;-f3$0RSyz3^|$ttlI-9!0dsU`HzTLI3#wkNM* zNAfzngVwEBN&7o$NzKTC5+D1bv3fl77``8ZCrJxhUg?s(ya;VKk|PJjND{qa22(S~ z(P5jh=Yo4|Rx3WqkC+QPf^}l1iNuhBB|x)bM%|$x0rj{cE1m4`Xj; z6FLGN^CIDdbsGh&I*UHXO=wz<49)30&h(EyWJ;|q>{!xI=sTuQ5lGHU~xxd3i@k;VU8nqx9kL~cL zXDa8h_^BtSeqkEsIhjDVU?C;GZehX7-&pO!UjBV^C}nP%37nS!mZY2jtJk*$BHxO5 zpSy>J$zLUT>B=T(vUR26^{=>9)xqRsxQ0n?HK6l*D~Haj*L;r8S?-pBJc+%}f`7}a zpy{CphWc9LlC(Oge0`nSy?)9>3|9|v{7Y>5#>cGgaRd5L48@&{h2ruw()bojw)Z4x z%E(mKK5&A0&)p1FRSNv1;5+=6+I~3TnZ$kEISK}<+nMK;5j4FuoPHm48Jcx8Vc%62 zurOzBOx2Mf+fUEQA}@?Llh-E6)^`xsJ&nmb1@qnsUzvSNBz2gY(w_Ad~iKJ+?a~Gt>>BB@@)K@%hN0GK;En=8k{eW!WQm1`ruNC?)}ajB2Cd! zX%q#-hf!7iRR|ydADn+;Ob_=tQ{1O%;{FuW{}I3$@qEe0?cyvV|`f#-2KO6Ft6qgQ%!xrUi^^-H-AIC(AR-g*7G=ck^t(i z;`F+wiJ5M_M3;?JFyN&YE8et_QgV)f_PuqmakW4G#@Up_z2b{f^ic3B2@?PN#Ws!A zW39(e^ZlU{=?JGyIu397Am#Pouk}{YHZGjZMpaWt~rxnrF=B zT~k!h_xo1Z9V|x2$gc5fcuo8xr9u{(mS0Hr>?=O1>;HQY%o<# zorE#*&q;sT4yr!UM8Er^U}pXiv=LDzvH;QR%&M<}mUy^g)r(matvXwH?2;H;ve}#6wspdF?un#)UY+EM-f`hOrw!@K zORQ?uH?S+*f^)uQQ-FUtzi2Rv=AXI4I(9U($C_E7vZ)BWOm*l^#u_}BSk26b*W>rv zaFqC($N%`MN8h_bS!YEu2AAID+Se~6#r+&7s-}e=vOcgq;2DEE4X`Cqsm}jhKC3>G zK*6oU_zk+tnE1J0Z0x2#?9fjumc3+<>Ah>F3Ek0bt?om7*qw+oQxfrBS`KF9EhL%s zo>Zi(!K+6`f*TZbm z2^ZkE>}E1AE%^gkdax{_i}mYeGFy+)IJ;g8ie(Ss_c}RT`fxc7-aj~`l}|%^kr_N( zk;1H3Jpk>A?s)IOEw24m9{0#8lyu|0G0-A{TxPtaac{-J?#)-G=J|gVooP5#Z5PK) z8B<6?NJ3Id65{N8ouraf8Yv}7(Ig~P9+^o+9l|^ zXN-P|sz}49!&ti&@zh_4KQ;%^z>U)&JS`hHt`DMDisz!F`6Nt||3owtuV9Rr3>x-* zCFx&$=}mW*bg5La{s%T0pc9=MC86>r2rV;fYl`pII+_O1C*v=L;G9et+oZl5Bw&}R*aICb{;&c$)(B}A~5miMz-p4 z7A$!#!uOkR&Ho-8&hOqO%=ek)$=AyE;peM@4n;@rw%7w--R&zo{^Jxi)`j6GQ%9)8u!qDFCg1Gtrlbqk5z< zbSq1<7rN9zs(ucfXe(tQ23AyA^zfU z9zw`W+qppgErui;9ub)@2Nz9(2~!dVRfo0sO{zX5X-hKE=JHTgQK>L0`;}OgoTJGh zM{xbvPV9~l1|y$(lIatM0S~zQr%w%O5RIUBwpHO8F$RV1HdB$QPWY=MkERqXfeyn^ zveecTdM0X;;c3e7VDKr_AHB~wbe|^qpVgry;|Vh>Iu*Q6Pa=Eg&BwExuF%pjfzyXM z-*oysC<$*NF-5M_(K`V2FZ`i54%W~K-WeqB{VX)ODaou9-wqAl5}>^AFk4!-ABHvv z!_w0p(97sjC(EnoEOH8M7(47r*i3_BHE_$8Xoy9Dyt>doGx@L?t;tHH-YM+NEnP&1(9hhP-OK4@RhzqgUz4Po?-vW{wPc8xNbdk zzO*I%!`dLGl}sF8717f+EH3%!K`-@A1e4>kj7dfcq3`y)AhWK zz0=8K!yNW7y0Ka~cSHcXH?r~YR}-}AeM{oJwDH-JW3(?rj-ZSJ&NSvx?VGNc5G4vr zeIseW+F)k9-gY|BHxE;yGr{q}LC^?pXXN6?3q)F->6mRFsXcrPCv2xA)V`PoODN$` zSSBy4JrtMB|IXgowSqWjd(peOw~6(?cuW{P1J>EQF)1$(#ha(2k?dyrT%iX7S1Ca4 zWg%>F+(jh3gi*TYDR!?vftPE3p!KASP@lMo7F%Y6YI7oOwmwH{!>d?V&o1~{GM=w5 zFTvk-r+{d_B8ZV+nZ-2$n6fDaZBtmP{yd9@JL;lyhd%pox+2)+i$R!6FDs#_2T=!Z zk)MlCZS*1=X3k+7Y6k?(^P;G-K{JU-1CWzSXP(&}h4`VX zH2bj!Q+)O|wzl0R4`<79e)vNo?29CMxf{mldI|2I@I!^R(`cYQAG2>>Ad~rjNk&f* zlbR}r8}7-o)rl%xzmm{5S$D{xbCHlbcoAz}+k-FXQ`>R<%aCDfi2j&BKDkcDcOet- zrQ3aCH6t0kItMVmxQXR+x%rcwC#kA&7w$jrKz2IkllV{7SS}@r`?nQ?fmjb6IC7p& zDAWKo?>Ks~QWb(oqoaSwV`F0oPZ z<6y$rIc!=VgDIoJTt96i9)0P6ecy_pG;A6yxVxCNAGnPCE(_XR`-oJzT4C9{)r{@N;q*ObCpHueY3l^a`WhKZ5)R zevHl3QVft!Vves|iQNxls7YJ{`QGr6HT27bE^l+p`}CNGN!3t?M{B{;q6&^=hSDKL zNeJ5Fh8O465$)cqu=-3Aq-Lx|UA5zQt+ohmMePDl#hXmfRvkfN-3JJgILUKR+eicX z^RU6bpLK593sHv^_@U}n(6h?`ckTa8Jx9c#ROAB88c8O-T=rl1O*R^tWKxm8PH1^8 zknCI2f(?(np!?Pk7{56W`F2aGeX$Se)Xjn;tJ44*IQ?sQ1>61k1kc6A6}QMWk)Z!( zLDG0N5-u|fx)pjVZ%7@dk>9vH^pg%Mv-1Qdy{jdBH)9GHq+p(FI&DgMOH)3)Avqq( zpt8vgo$h&|WP}t}t*=1y6H$0k-;ah??nc|0<;3^XB3K)_otzPx2#S;bBe@mNm~(yS zxMvmN?X!bucjpovU;dV@*lLFEj1DW38A1(14#SyLA*>z|!yF|;m_Pq32%Vn`NjvSZ zFW?1^T-3(Z?zUKH4A@xQPi%u#uuemSSVbhEAk2a`SUey$@ouE-pCf!Peh(JXl^An% z6WpwuK`bvA;eLf=808Tx-c-0_YUTsOTyIaO7O28HG#NvV2rfb?PD`UR?%4hy?8A}1&lX~!S}|0u*b-nMD;nN zZG{r-3fc!jy2`AaPb3U|y~J2g2qZMlj%;@3;iiu{)amOA7`ytD1a8$tSFdDJQoRNq z{2Qg&+f;D%^w}tC{fpFE1<-(-_ejHv7re5tui#`q6Z3U1k;!|liS@;ma6UH|oDVmX z8`{^PbIBMqnv_wwM=!{%wkde-cO)jcZGg+~zR~WoSdc8Jf>WWfM8&xsqt_k6LeDs& z8M2I78#WO|#4;g%+!cz=iYS|-MEq~&Vtn@(TGeia2YqHjuVEvtcsPa8bW=l{g{EZc zp|hZ-<_>eXzw^D^0}MZ*O^8ka7FYcvg$;{QQ+67X*l0MuI}!%gb<>k&-in!;&Lt6t9S2kCJO*p5rT7**4^ zWb006HpY4x7G+%G?$!z5?3xCi4=1CIr7Q;S(IFq?UdWu&6|jS3eJLSYcwfdb(YP} zd`=2e9H&B6PSbIk&@j{boC8l|*5+=kuZDFlla89?^$HePAWIaGbHlN?LYf|9|T zL`cdBe(J5q?Ln(y(UIryXQ?P;_)enxjtIlcheCK(28&N8KwAAZs9QC%gz{$x?=vNbhGYqHVN0+;}^rk!*T#v`!!qd^|_cL7L|Bvq7Fhm7h zCSNT{j_V$TQtRe*%=$!O=%{*5DkaWhPe&FmGn$E`n^UkZECLI=9)QsAbHv>*g((-8 z<@)k>up)3ZEsT_cIq;q1C;7wIN;~|bw+F6DEk+ylSzx#3GWEFWPP4YWB9YTilC9M| zQhk3V7|7K?xA-ldc*!r!r*$sGU{SlM;|_qq+6Q&@Lu6^ zTysLUYR|U`Tn<5+wrrAP9-WFpli7(x(rF*_Z{b&Z=T{MuL)@>$eoF%U%c^?@&eTq28MqqbxAXN)KM$8%?(LRk{TCq`+ zuF=lNwZ}&3#8;?}e2~_R24vzJ7Gt=Jq;+A<`Xm|K2HQwP*5o_>g7I^5B>#%GOYq%qcB4Ewv!9k$DH1CiSFDa4<^5lL*M5r8Z{hG__j#i zL^^_qt`P|w)+YO;iplD(c4pI9EM8yqiD-pJKwPsTv#-+0R;@IpJ*ykJ0eGSClHQ~^&B1r5+3I~8JqWm zBsu9k1pG;$pRkY4^~{3(Yi6NG(KwQH$P>~>Y+?6_R!9)fp;=+4sCdC^N_(nNiR0!i z_+vpWv?8c;usU0vPUwTfZg|DZfO?8HG0eA2y!TiPC&%}p($eqL!7G%@9q(o~N`=xS zZoP46Ju7J4!Lcs9HP9&_8Rlo5ruXIE)4C1Av}l5WJ|#WWD(oq}o%I^WX1(NqW(lm6 z%XKzyG6Vd7i$H&5Ca$0ANq#y9LwCv>s5U60-5ZuMvmp1!82CWi3q_3c}oni^;>97qrta z2zH!uAqQ7+IlDG(QlD{{R^6Ti>t0VmkttU&UhfGtkvE3STz7JkoFTn^s<_;CJQdsR zNZW0>e)TvL952^FeeHQM`FCoLNHgId(1pKdGpRqX}!zdNB zu&c{GdArXUpl|FXjNaNsB5o!MYNuC|p9PkfzU)5o|I1*_xNLLrfdoNRvMc?-5OA^?0eI8Y~6AMl`@i^}D336-IIARn?+0t^L zFMe-_tIAJ_vsxS&HRiOVbgv19w6 zLHx2rX5s4v5PE}Sw(WVuyK_ARHM*0ioalHMzbG7|)8=4VR|K^hie_4xzmatTZrI2> zLMQwM{QRVfD7P=h@=#aMy2iu)PX#c#CV}KLmKKtGVcjh zty@WWq0tcIs|XKTB2e=CB${(slC(^D3vXkZ=(ep^B*2xytbMD|2eODv#U@M(G$h?x zAyhcv3ZryggzuGg61vU4vko`xsm&c@67tarJW?y*=-)WH+e`wyJjaOPQ9Z~#cns`p zltEmSV@I~_WuF``z%%ONe93W~Pin=7%%6L~OzRtU5V8SZ2MOYrG9F+`JXBp-A#f;; zWjX315xc>IPdisY)dy~Um(oLz=Xb#9AGgkF%k!lP3r_Dhp;m_#3{Bd}+MRj_g4C5L zWN?qT+Kckj7yFPS2OCJni)oPCA*g%lOFcz~qw}_j8X|;(aG&F#e?mOT)T#Y`Ps2uO5qL zSH!Txu#~RV8ldfqPg3VQqO7v-NyZbim|34n@WUi0G~hbty?MQ~Mqd&OG>fq_vxOS} zH^k1*--lIcd?--OM1Q+S@ZQ=Pe9h${BP9o|{;Wiuoidmz7EFW2WibICR6$0v620$< zqN_?j6C`O#{2k*MA>pLTmuvhmL@XPV4y7|c3!PcNVL3cBE*b*Y-lu<;PbL!@wt>>= z{p^To0U9l>!iKGxOiyAb-K8^~MtJ4W!FpwUn#6gvz1y*CV;(vA{UFBv@9U){`YB%i%iK9xO!k}?=k&u4=0U#)60+TU`FL}$FYmG4xD?+LBsUXz*QbU`6 zHB+VPG*Dc#hu3+i940LU8(NZ~0(C6~haqf@VgncC>pvaq8LO9R4!j4I_ z&cFcvN1mZV0r^kn05Aup@C!4R(#Xt-7%)!=&U`M%{_P389;4kv+W8b5%af$7&x28L zmdivGrDB5G9I(B9mU?;$W0AcJRqWkIoS4a&IQK16vtN@~ukr+DbPCnB5P`;{X^f3m zCb`+Bfj77R2LsGn82OXI(`;QXu-f>FD&(Gpy-z=pPhST~JeOf#BeM@2h%eb|Yy;8D zW|DaEFc=UP<^uKtF>G_MP`2-oXla+;1gIw~{(}}%5 z`3CE{)EM&Zuac?dmb6oarBy5MR)mZjp*1ZcsA3w1N9|wY_!J>nd)E)cLM|s72iBXUg2yFI1->Oz>x8gH% z{{2#-*e?aP;fpbUw-p^;PzZxAli+x@FdBU9=JeklNU41Q1MCV2nQMeA<18R+$$DaL zs7jt5y$imRbIDM(As&(Z3HQ4Ea8PnSE}EEzx_8A%Xqz9jC>ILc9aV99xiwXJP(?oM zO{d=zI!JxkHnc2@qOpu9Ids^W1bw%I4IH!P+3X~=S1zWHJ{eOr^*^zNgzDhHMdq_l$1K#eW%)+sL ztSD9opD9)BDXAIQATXorLJ}G0J31^G+loyoFVJD@Wpwe?0ku~hyo+P=;g`Hr)ya}b z>^Aqn?+gRqL-=Ssvxydz1f#@Q9y58TIocHcN0mSIfl@{ThE1J;0g+AMz56#c=&XZc zji(k#H&n3Jm+L*}%F%NcMkq93L-KCSAX>lT$j6QFyF5g4ufOj^`K*i=s-YSa9QdQb)KOPZ*?JS-_#9z-q^#wef6Y?W7_<;CxT-~e5ErU zydxVv94G6FIcEL4OhNJ955!vI1{AxE$2s5k!CO;V{!0J1B!&@%sN=ot_rmGq;z|vw zss4~z|7sF62R@`tf3!$+X(kMay5Wy#X^^>CM)IG(LI=s^)U~FHtq9N~>)!mP0SCNk z^A>M3X$ZsQ=dE;GVgUFI{3N{Z(=kbMJL`v>W~jmG^kq6kLC}fU%+G?JfrLQePdF8t zca<_vydgbH420zj!0xAj$1=@!xo9QDmIyAnUR9 z5@Ex~h)je5+Dm+;wOxv^z-ux2VXce_2SS-IXC;W~FC*C9%!3`j6If@rF^f}m=gB7P zYhXK{Fqe7| z--&AQD?N$uBCheqlxEY}q8l+;h%%bt%0qw~1z`XYwp~VnnukGTgI0j&GWgtUj`qOuM}r?H)S8Qd?Q<|Etd& z=ypf@W zaESp{HiOz0+d--Ld)iz1opqa+0@m+85$8xx9*06C*|F2%Ra6Ic*X8)VecYY7!kHHP zX+TAhE@}T71v%C3U~}#pG~N!P72N$*Z~6v&cD9Gut7Q_Uo7WlHqnn{35pa%O8QIpQ zjZvPPX#H(ZfSoanad|HMo6`U_ItiHEu@F-O?vdtJF4KBd0|kuV`;U7x(!v>UhbTEvaWU-=8PioVS)^%rQ}ocXM+a+%V;h7tuTVlnGJShE%0% ztdq?srA`uHW;>pE7Nv5`h0nAq!pb7O&Xnw$IEz*p=aZb*GN`w8C;Sc82IEcpVDnsi zkmNWkp7pOuVf6&)lsQks9@dkADFQh2yp)&?g}~Djy38tDP5$nM7Q{yS0t9iINV<|K zXr`qwUjuYu6l5UxTQFqdx#k+#M& zYk~)|YEY&qL^|W+>3B&$cyChxBN0bnuRJI2T)a&h*!QHsGX*w0U4-{Pnp44>K$@Cs zL-cCm*!%-r|F~U_bYU^|a2W^YQUcnOVU4|;+sMQ6Z?x)$3j6ly)T#wV*4R~_KqX_p z5QWiT9AwlWY|DK%Y{?3aO;<|u%_7*rEemLneof`4sdM1^s}zv15<@fhCf0EH7R|Zn zf&EjT(~RS-DAzhj&d-j-{22}GX=iOz&tmEQPw7?4wWFZA%`&oo)UoF(8A}JogjDs+BlS<`;P<&p=ph1O92f zO~)r!k=T;=88p8jK45z?R3` zam&X;WWCvG5;SfScUN7;gbmsOulhWBwLlee>Sus<+d^dRCqk9GHkKD;Sa zPF~$GzydG^{%S+0{SXJM-Tx!f9tvC++6!N7zd`b1D&b(_VhF4fgFQmcFv|XA56u&W z;e)}rplK3xMBat=IvXy#E7J%W0o`$U9VQLcquGsJ?7-L#Y%p1X#m%+Y zI?ozfW}C4|qOs^Q%MVR0xpni`26T~JiiQRI+3v@BsJ&t#MhW~-$Ygt^8n+LaVH&gS z$vrexPDgI|4>U}RM6zTfZha<%R5A>T-dLjFx7jGK@(SlnnT|r)l00oUcVc9Ind>>( zfzJo!swY)-R8`Rg6EsB7N{i!tjk-|hCO&ym9*FDyhT(|0B@WL$3B7a2SCv>OQP&Dr zqLBBMnO|MU6q|n(9Eg&EpA*gDX6vja!M$u%*`b%$#?jgI3X(?#HGp^Zm$4w23w%JzFQ)cU_@-mA08-1{X0 z(R)StW{3YV-<3s(=1@E=cFkux?)$^jrD5P+cLf)VOTgr26)@d!8kc?@=GLYn40)9Z z9&ZoKx2?#lzM6B8tVXOVB z?hUk!&gO?~<~mYItMQV=Kc?rfF8nA7h4HyDPcme5{)d_1o;8!@i)`UdoBas&9)>cOGcTa# zZ&BjVUx??1pTd75kEmugFr71|V$^_iRrlWo^zy3^wCq%)LRF>Z6$z`caSb6a%=I8O ze28AUErLPYkK8|103M6s1k=qkSp-4fH#yCDEwQdBWY{s)t@{2U&V&&GLPCg?n| z7hh`HqPtBVYg%;`Yrf6nc(%$|Cj1f`q93s8%c9V+BOR42PEqIP9xk?T^DKv}zn=^(@C1?0n2WZ9`-)hGOcy9@4rgj>y?6vxU?9$k9iI zv@X9E`NmI(s<9mQ-L^x|!$nM4l{=M-{6qrEf~lyUImUdQ0# zA>+ljLucP{P#uNN`Fs!AB~?Z0SA^5v-vlfwf?-am3;pOJL}tg{B2@=O z*{j}SRPjhRtP<}f>$i21y`u*)(M%4HpGJDw{w2BhVl$X5Rm1n4ukoahE~IvTW!%@^ z2l4lV)G#eiAbxX@j8?s;X48Y2;KXrN+c`bDVcZn<^EzF;DmMk3?SdHjUy;0K3_5eSNe-$>8)HM-VdnlcbUuJ zxKg>#l2zi_qx5#=9QrwQ8x;OFK>udWC)nA@OY%!*zXj}|Hg7Y@bn##mYW!?rcr{#* z=68bRAG}MIme60m*7g_PSPH}mwGr0fm5#vMs)T8 zU;Ls#bn5|7?P-FAfv<_$yo0bL_$yvKQBH!Us4UB7&O0iVy6SVx{ zax|mEY=){MjI~SzkzHA!?R$+FHteQ`dlPvt1JdE5;(hSmsRf-^HAq*Z9|*mV5qOn2 zgMpPOSWWPO<@6Vg;T@oPHrlxN zWG@dAvTP{q|7-;pSZStR1aMfT>sPpqPUJY3{7%oNuKTJJxOZWT=@ zS#ud0VYHa=yKV?>>0hBCE;neR*Cl)|`kLd?vFKf`MFtb3L7~JMwPoxvL%kF|=jAdv z7b5T>`XO&-0^aJ*Lm}Y@l}YJmDDS`va_)g})nASkrI3<<2>tM!c{lGIGr=+( zRi>Q7js+Uno-c)azXaii+m~t5f3Db}J`sg&ZE0h_u=b0`L_LmXOrd=ThOH6#40SB&ikeY|XtN&41UlLtlDh*`xVHj0VmIsZ_@ zE8AkpT;DGwua?1MgC~Igs-qtRqM?VE4|luNfp_696*}qx8SUTbBGU@Ac8G?4ZA&Wk z{wc{GT8pl7X(&+`PVYrLr8Wop$+csa^tIu1c+bsQ7_*p17kvna{IkDl^f8V}*fjw? zx!GI)m;z?$Y=2t$`~b<9Fa_Cd_o$=I5nSe(iBp}0$nNkkXpVkDmO4uiOHYowwmXI> zM*SgAXM91)I59!c69ue0Qi9LV{=!&p_Ml#HDCv2zfMaH=;FFpl=GcLy(CZXNw@8Jf z+PN`&_frQtcA7!ns2APyMiwV(ox|~lH_+#94;|i9N&9q7*=U;u-2RyZ`I>XVp?L}9 zw11&JvPJkaRvv9`%j4{r3M&0DnJNBhEU=Err(y0|c;l}R$(g?%M9a$Y)z33zON=Jz zT(cimJOE7Za)XWIwqw%M`*ht;OBi`3iF2nd1P`SGfz!uk8hrZ-7&!>j>~JFpmlsE$ zPrG4*wt$s2aKo!NIsf~>JPhi#fRcsx1WgP2>By^Y;`?wf#+~sXVA>R9bd9>iRGkPXu>rs7Nk&Th4WlDP*s zU#=Q5R=09&{AP}GIE#IqISmXOJm~J;3&0z`LQA}7fycH#q~T;Hn*0+ccS|GT#8i%1 zRyazVertn9*-7frqRii5m`RHtjnHw=#G%b;B^=Nhrn~>Tz#BPEGk#%;$}e*;L#~zT z)vagUdv6ilh6HjkxQ(8hxRoY}oWmVcF41?xzUU;7#NMOp&_72UYZ7X?zO^1spHqP8 z)9a8f`$A{tw$nbvjU;AD1>KdfgdX+FfQb(pVBOwMntNM?G1EE4h zvmPOP+7gMJwJ@D_d^f1YTQFghLx?;>pr^GK;&*6cQcEK6TV}A8BF6B3*&JA8p9B}& zIo8}E3p8w07wC9MVc&%sy2SV`?aLEKlV{oN?(!@uGB1G$o%AHV5|hyUP!5i)eG2^> z)&bvhm*DoZZDh-gKO~OJs>P4~qRVC#kOgN-;UVW9KeU&CF)?9uT(lNfbP!zja{?Wf z8)mg7IFEehPG+CqD2?Ge3W%pFt9K+5l~g>SKkxyqg~)}*Vtw5;j0o_g zuNpPUZ2d8+6YU88`4>pwx;{)HJe<&?37Ydx6(^8&8B?Te$uDWrU15VL0a818{|`xxRcZ$cdQxC0jCarxWj%nq4K@?FLhO#`@R&N+l^;kZGQ zXJ5x#uT!al>^j`6u^d0dsi5h>d!&vzM<>jGM*2HyFl1H=tN(P2eA%gsmp?a>X=4sh z+!jP<9*x4>25&03CeE<{FH@gG3sCK%CAj@8Cv)c2L$%Xk7>yLgZ(X}lD!7-v{nCTR zObYe$?PDU2PRGHnB=+yVm&~;*LQrvV0&!k{iFAjZAgY@j;arX$bzKof`-2})7u$2x zeOV2&?94D`-?nF_I|^aa(UWv6(~k&BPQ$w&u>g@%;os67!0Xb4<@(QQ!ha^9woe_N z*(PIWg%wes#NDR~Pm(+J$*9?I5qaCJ@qtz~YIHQxg;gOmwCFZgtl;uuK{MzMp9Hju z2|>BzqMV2Jh-sTX1wC*1@)|Ob@N?uLAYYT^E#o>3oc`3}5X_xh{<615r$Z&VUb6-g)3pqz@$#G1G;xp!FV^mQ>9w>@uN~bsNZ2u@jKgq=J=?grVKw z8&`2V2$g#A%w=v?mv;C7S+`LI=ghaDyGIIPOw zSIv1UqYUh!=bI@p{tX}ACBgY}U&_4mB`?!#SVg%bFj;XDbFcRRK9`&T{`2Rs`)=Q7 z$5!i-g5W7oIP5|+UDuO|?kYIxV;MR`s?rB;zc}XLLgI2r3**0VETI*tz^0yphpAEM zjgLiaQ%+$Ih2_xABNjLpZn>Uyl%pePeQFO26br?b;}GTA(y1Ub8izvVk>CCiZmvqb~+Ub{%k%ebT`Zyd_t>I zF0q#t#b}wT3ys@%ot`}q4|(>DxXAK9sxY2og(@=eDd8A9oAVws?T^AL@13x}`WO9v z=nj@GeM-i2v$kae>!CTL2@7+|VO_F4^~+|6zfJ~uQ07GQMAv{Lmk%&j{YmRJ7rF#AUZFjeg=wwM%Q6&?;Xn-==|fAC}Xn{d>{)$VD_4;__iTS}{d-Ep_-Y zz`T>-Q~e!_uvtu&I!8{WDSy{vAYQ`1%hO3`pBY?ul8lpm1v!qOAg#P1?9_z8TYMo$!_;EaP{tC z3~Jbp>64AIc4Rgq-ZTYj->74s&Sfg65^uqGGo$WW>%egJBix^`0Qzft>DlfOBGj#+8HUsdkabh%lgup@e29eZZ=>q-|Y=5hPnp|F~ zCuB91I~z(*rBA|xr&K^CE}L?JUi4j^g;pyZaC;=D-R(aLf2VCh?&yj>ZaZLd_#e)% zc@4I@J~&vw@m`lrB=vctB*8ZhE}pF>o7@Va>OdGf_hu-5UtEaMbMB%;l?*%YeTU*`gc2%j+PE?ex5z3+_nK@mkfv>9)zi1#A)=I6;RQC zjNH)(qwnswGciubX!h|oqVlZ=dzWy1Z~sL0qT+knxotZb-G4;N#z$gW>=JZUtA~My zaTt6!0vhLpLRY>F+}u)1mLBnjQau%1d1pMhrlktk$953mOEo=4v?snAS&G?Pus6s z65g}fXc-XM@Zza-B?pUo9fgO9#;P=iD zxK6FXFY5u^Q!SyQr~24At3Okh>%%ii3D~Vs_^i8D5~gGf`Zo=$}RpHf2n9Ou#u(msqDm0$RxRPBXHjvHqJI z9c#M*f#Nf9T9Z2H*cNg##eyu{<(vp#br$UO2**G1a(FB`i$?F4N9nFpq%yq)eLSjkLw-6g51U#BsVl(SeK};;rKb$}1Zg z&vBCMP@5hNyWYn09D2(Pi8_E>xhU!VlFRgaO@i15`J9ffOMZH#(4-j)aj;z(dz2lq zv0^fc37Jsqf_U_tA&M!>V(6!_3c=us2I5Q)T3A~QksR8@t~L}$AtfuaXZRit`me*( z+D%}>&C`o1y9~i*sTlPy9NelV0d~j389xh5UXczpf7@Ayqw56S76<95c{*8ibqY+Z zUqJN#sG^dKGj{d-#dcc4@#Nf4HzOH)Rpsav?*Ew06D9Y@wLosj6?#Tw5q=X~!TOmW z(IoIB6>3)HHO%q6i53ev&zgPa?UsO_N)hNhdSgYQjhVX%&K$<Kco9&|Nh=F8d9XJ-bHyV79Ks%TuV12oP2Bt&;UCPwiOsG)lT z&Ef3_FPRw9d&iuP6lJlt4y&N{`v#JJR2%kYOv4FB&ezll ze`Odr`7DMt<=VmnvR4FyZ3Udm^)+qZNXZH48M{p`{M(tWN6th)>p$q$P zUE*^e~57 zyjVwEX8PdQH)*u`O$pN;SdDJRFG$$W9LCg62l@s=X^_EG^d1sWdvL+N(#cdQ@ILc# zh6?A5EW}$fTy`i;1K!z%(thb)v^%mDoyC`8=@~_6;M-E8wY!;~Z86X#ZjXV%pQwZ4 zC@*_Bfco6}K&SouLOU=0W9L;^fO_FDiK$x(&f9so!`>bp4yZAke-u;kQ4y4#$$2hX zI3In$DYM+W85pru69T!8dTE9%eqT+A)jbj6Jk6W5ZF>;5L@Pv}vv9nsQ48HaQm8}XCq_6ui-@Sun=+ck%)D`vsC9P+TFD161s4{; z6uCd-KdwV!s5u2$8(-K~VN4fBSdb*`Ec&~h@YrK@VA8x1icfX1ayu)TH!Y#ye)bT4 z@GC{%ttuolY6@DF!|DqPh$JbbU+=wV7^g&9 zV|EFLKCQuKS!tA9Y=ipT+}3F8FE;73J~j<%lYuNPW}}~w?y=uW?{_bz9UIP537_vI zOO-G-m-KNsS_umRyvVk?VyOP{ld4L4(TjV^8R@lW@P+Lis?jQg*I%3?8}DY)jJiY^ zCtU&j%TtKS*HMylq>m1-2w@&uBtl#0QelfK$Ck>TfvOyDRPl*1{wJ4-Mylh5igadQz)mK6ZWBEv{guwzP(uq(M}xzLYk211UE(mDPyUSDWV)OgP`xw` z_8gUf(>^!omfTaQwl797us#(9-?)D9_-N2D4rY|J$3c;IESnX0iLhoP^f||6H@4^` zTh*W8&cUNlIywtRq6Vq1$^=mK8zk%Z*n-?jecT|PCQx^9$FCX9DC;!=D14@G?@R}W zAUPW1olJBJ_tNKm1SReZNtR3vZT#bhsoHutCiq0Ygq&j?FLNviK{)nuo-(q>1u6Q(a~ayUMuOkCw^Tv$Ea=sYgYdv&sPk0! zLh+I+=8${~y<;Xoe@#m=)oc;j>+z8Jwop-Mp&5s!n*|VdWD{QKE`liTz8+O`0%D|- z$l%J!;IM-wHKWIwm_I>S@E{nw+6Q^2YbJoG(4SoIU5aDtPC?hJ<#eiD0Lb$cnBu&v zg5z%<689;hu&P@L0_$#*fHXC_!n}w*Q0zvGBaWl5L@fM^N`pf$;=rVe635@g^ktqn zoSd)=Tpc4ZD9)lLKUg1f&gV1BGF0G$Qwn)}d@dfCaTW^ZAE!yBn%JF*#B^a3kvCd` zI?Ny$-ExwZ(Q^X>Lop%m$$rw;VF|ach=b%DBkEb*Om}u!BjxJD;XcEg=ae^OsWpE=oG4I1$gN>x_u?C0_Qq zI@r8D96r~_fjqwAS$m#_bg2QhC_@*P?aX4-`a3XJ)1JC)ZJ^VirV}Tc2k*OC*xuKT z)N2hb4e%p(Wv!|6*k^Y2^DJ=wQpJp^j!@x+2(UaOWOkR$K_B!}IK!LZylH=lx@l5SYVc2W*Dk zpnuf!#vj(mri~VQ7qGrv$+U5uDWsR!!6(;6cq`)u*{WX*1*)sjUv>*zs!hY)U8BU? zu8G>t^kFsZO$BX-qj?)g-U7?);!yNhIK6(*2Ym0Z2JOVhWZ#MuIwo@in~wTG zMekzHmwX0oKS$sR`j0$4B8UAkYss7i$uL7Sf#!bu%A|^j+LnxG5>`+p zrF&@XZi`O)1enzEizwxOWOIENF*TXxqvLA4 z@SSHk?wWlXFG+-=+qYq6*svV=OUrotxtFQ*a~1lm*%p2O{Y3L^cd;aF8+ry?5)rFF z#&*{@;qRvg=szQZM1JVRHIvJ^*~x6E$bF6J9gXbqUjTpgFH`5Twb(yZN@!jO!qqxn zm~~nL##eB=*4LRlrLsIY+!ckw({Gu`3yo+XahIgaJR_?NcM;E-+3XzI<*;Y9ALj;` z4kF4HJVoOO3|z_m{v$XSjOt5f%73@n!&}_w0*(u}bcY?5#80Hc#2}7k!zUSPA&_c3 z9yR2D@ZVCi9D_<5AF|)~sNh7oB-Au` zPYs9{RC6;CrETlrXyh(xG%V`P zxANFyCcg6_ufk80*hB?!ebiYP^Qwh?DisW4YB4CBd|kl5x{9>kAEkaGW<*vzmn7^D zqs#mD0C#o3;n}gE+&TwzP3188sx)Yb*3kBrHk5pR5Kg{tgt(LxY;13*Ax-bd3|~_; zzFY>fnk(T;Y#ML8^C?=m&x<}^uoxmNw=>o6mXYtbT=2xLE-X69<+@c_R90S1RCnn? zyB-U#%A|Pw%T|m=`$b05T92FK3uxD_v!J4COayyBG1=)t^!w6FE9W5dfO7*a<#JIO z{x`|bl8@BLZaXmvy)7061`e~? zQJ>W$)n+S*mUFpxt3vA5nPV0uGaeTVyd;NKN0CWFXUIG=874R`M_bNkd&DaNyw?oV zjy>GDsds`Le5;5zWEawt*(R{z(XnEBzeam-Z$5}pkr?!jhVU!V#M>nZt!7;pB{}JwAmjo^D`+6|x zI(+I%pi|`}F+OC8Aojvxy0>u={j6#RIqTLz+Z$JbZbCP)Q{0U=(=v(Z4#3QzoiKMp zFKsLuW_Lb30Xw?AF;&NfESWHkG{Jf%f@8IAH82Ogzm{-)lRQ{Sw9@akqonfHdXl-+ zp5vk@(O;dT(EL6gz$J@>E$t<-0X7)(@CL@Vt|wK;55Ss@V|1j9%QT)Z8a`S=xx>k%xg&(0T{n?q z(2HX6mmubO-zE4L;z89N#|s@FxZuxcL)7O-G}Tqi=jQ%$RA^O(S-1Snf~N~f*sfU^ zw#SP#4bjC<&--c2@!9yKG8`Z07LgzkQQ()x^E`d@G3#Ct^XiX=@T%BKY?lf``qdL{ z@dDavZ^Gw>@2T9i*>FGQ6HzSOgbjTKxGJrb@YkA<6p1vd7Q~=O=W}{eQwJ_iy^Ood zIQ>Rbgg9K^It^~;O&6*f=M(1x`_TKVJN&%nPD7HnQT=7p;Om07 z#HnHnz9=cfQg#YmA?HDjzpTSYPXQ)==qLZOUeX;GjoJS_yMd3rqRHl8yCD9{ zBW7qrFyR?mpex5Q$WuCv8uQwC4$IfUOIJ;l*qly9#TB6`))Q0TC*Wt@^TfWW66$o_ zsPp0;QkxtiOu)vGHC5|FCIFCV`WUX1AAN$opyc|{SN zOx1wdnL_FzO0aIo49eC;pfx{)%uBO@ka}tGY(J0B&F4X(MG(z;S4`^W6|)7+QB-a5 z6ILPPGEAGKixTdkOyvT38YNdnHDuPK*0vHHZx)3+hw`x9r4q0Fo`!2D&lIL+x?p7- z=gOPgMgI<8p+3^w9=OT?OoHsO^VegxabT2`+TW#v0UWnW{Vpk${77bB-$?puo)Y~W zBhE+2eJ&sU$ZfV3zieKGqd$IA<4sHG-jgUhyT%K z_V%>?!znbl(~V1d@{r}`{kGwXxU#>PIvLtvgxGTQ+{kf?W^Ti}(x-T*vW`uQ79qh+ zl6Y0#5XY3S;%mcZ)-P-}rmf$E$=n=Rcuo^%)vw3c%%#M^Y9cr;v*D`+1@hA)^!cg&-Ec9y9F)R-f!2m= zut6F4;yoUGwIxdYwf*9J4edUd=I6uzuCSfo(-^|{6AR-r*Y@#Sy_WHflyvyxi|zP5 zx>w+qk1pS9++R>RKZ`$_<;)*H`UaL>YlSY$0+@eElwb4oH<;*O1|yx9WL0My-O)3h z|IXixFB`!F;X=;IkUS!2);R%Yi95m1m)&6h+JNZv{SVs~rJ>oJ<2Y^I8G4~D8l>gV6aFkgZP#2W z5KXc`b*a19P{C(Q{-(jM92FAewvY|*aDarx59qgeCluLF(P8o_c82u;j{R>2sFhiu z$T?S7ly(C=)Q4z?>kb&v;?sS}gQtkF-v4A_u z!gFy2bjPnV1Vz6hFE)XU4N5|e#&LKufpf$=chT;9nl#1hFnz>1w*sc9WBTDpx=?W% zWNfd%8Mm6)K9AEFc>EbX>aByRPg;2T7HN1y;VKloeaw!eO`)#?_Tt``+>Thjn;0@f z^zWK%@HRWe#=cF3>d|=KLq91x;+4;sYCfWte>g_o$vmuG^M@5#b=d5=R4!KjH3w4? zNV}${QRUEUP}+F{wK?yF;{ye#($vSE3~xBqu?FtwT7!*p97aD7C$g7cuq%HCP!;9* zFc_!-CiWx5@5vE#oWO%a$`e6h>Q_=AKcD_D>lPTJG_y3c9yV~DL*JJL0TH`THHu>nqH%2i>A`8H&pO)6UP;u#1ous znT~xg>e%1Xi%`5Q3gc(1z{}JKl$>KuUT!T!mJLBSB9w0rNSp@3XVqu3Y~r@XmKrqtWpw(`!c)TDE}qcMlR@I3JGbC{UmL zLXtY&9n#$9lYnH3mc|ks59K@Qyt&X<6_M}yglLjmN^k{Oub zeh1uz*`RhV0V18QlhW9&n7SwgGzOaJzk%%|E}Ng#Ce zh0B`qaLzduzO*$mJIcIZz#O2+#1}>iB_ZYNc+OiIiG?Rl5$(8MBD}ByEMA^vO64Cg zDUTvS`a=e-{(hetVi|NcXHnC?kIBnr?}=sdA(FpiEAHR53k<$)CsE#4$hVsp!ItZu z>Dt+|@m1D>%MzB5yEGaVY720oek(cPf0StGE};F#2k0HOo6ORfE~>lG0j*y-!_RhD-!@(h#Z6&Zgy0YT(r2t8|FV=f1!67JILY zL*Y$nIAh`nzH@$J#`Rs~M7%e^k_Dg~6+q8_iNJ(AJK_3h41{0ophrL1qw1t6*zn5@ zjZg}c_O;Woy6tGB)Ieo|OIf>+6Iggrj_>wlJ{`(gPiHH-(l6Pq)aZb+Kt8gU@eYxt zGI6%hUs*=on>yj6!9`M;riK1v=b^945q{`+)8|P(?7)+GRL3ow&2P562;CrW$z^y{n3#|_dN)#*MEais zVL&JG)~+FzK8M+!d$mOL#$=edLKgX4ze1$$g`kY%v;WN0z)bZQT!yljblO~^E-^Ey zl0-ip38uuRAOx(fRiTi%M3latML~!Ms_-hP?FU7g`FREm>aN9+7$-aw%w^J5U14!_ z6pW79(%9+mSvRH;!lq}#;#aMtwPYgb4T{lHsVbu3t_fR$@?pLCO%PLyp=+*KQn}{S z(6#v-b^3OP)zFP5=}y;S)Z-g1Pc`Pv@(U+#JF-a2cq>RLJr6T9PLWPeRrXZ1CA9nV zAn9{9Q#nZjzc_Pm4&&!kqkI`Vw(KCS`7sp&!b3sRTnb+oU7($FqVd8mbJ(}~9ci1= zAh6o%M5I5OfLD(%sSb0Zhu7?Z%9RfE-sX5xKfIC_$PidzGL9Bp;+&C}#o)#KWHRm3 zHB6g$5BfX8p?ZW8JDD~(Ke8UZ^Ul*b>ohRRBM4>p?8JPxN)T$@CNpbQL5jX3y9y#u zMC%G%oqdX0=Q`ur>`Bm6oeBNJ0obRr3hwD{1W~tJ%=3vm!Pg?3{!iB&MCP}Hr|d2| zmfgru1qB>v?Ym*Z-0D_p-emQE`R=F&meV$Nj8Af^K(#| z;fr_XpN30UEeU_iV%juuB7Yz$fsNdMlzdViX4>8*LaGcUEf!W5YTr381-iJ|h ztu>KtSVUAi&cn<^ORUs9fOBlFkSTAj5Sa%6wNK9xqn<jfKQ-niQRg%u`9!Wk=#KJO{hf#t2XRQSH`6jWqUG zGQB7EfrR}i#xAMXG=AJktWnjq1swxVHtq~kbO6e*ySx7oDz(#Yu(Y~NfDE4q7 z+HVS=CPz-fFmsUVSS|(kB6aS&I2U5vFCues51h7?Lrsvvw=c7plDoUn=Gy`~JLf)W zQwV|YZzqGgmMTtLbds)`;6QsSG+|GMGHO|w(K|T6?3Y*xRhS9dvUVI z(H+NLuf(&_?%2icv;?1=(N$d!8mnGV=?7E6?r<<0K1UP;PZna}5j|8pW=OOe-;n3V z2Vk_egB)np69P9rx@I7Q$ph% zU$Z~Urr~xKNzmWdj*Dc)pnSv$R8CL9pmiElwmgT+pyraom>YEY>N)uQb|aJyHqd;Z zTB!cmgo&e{$(Xzn=D+$&u5oPlk;qG2&+abS7!-`+pL9r5fD+YsSOeS7HnOi4o59~n z%g|)uD!k|ugzJyU!Si4-W~GP;?hYOY&b`5`oJA>wxChX?iUt_en!r;@@&f6Ce@xbw zyD;VHN$S2o6Bb>XOTNnZp=tJFBTyW)64P38v4!pSiXQDzD4RnNBUWql_iS(v$h~#QylGf z??7bjr-nKP7m@t*E^t1w1_obLV|8e+V17GN@4tD}LF6j$x%owUkINOlJ~tVRi}P@U z)eJ0?&LV0*meA~nWsIESIQ}8MV)|cj26QQIM5X*HuKN*;zs|iO0q*CBThJz6`TE27 zQR4@Bq@IX{FS~G`bv|qrT0lcy8RKc;$o#V0L1V%z+21E!aE8AT`bb%GJ?KAVZ%Y>w z!;514`u>oI`hzstx{z&}P{NoD%dx&^%gBzGGeP)~`xg@_qu10M;Ed}DVj1j;ny%)^ zQ=3S~WK(h1eMPW5a)%UG-DAFY-bT+^D)>sn0`1a#A-pz@IBakxCKpO^ua!R0p5=-< z11c!FLI;DdJ|g;A26VvdINC3I$n>nc#3prz(z0cb*!ONT@Xg;y5I8q*?~XdsKXWgN zSbeA7qdC;5UkA%uPms)Id2~%fK6B-11kotI3S$PrIB@j|wKL2h<;i*|YP1mTwG6@1 zHUY}Pozb5^lUhEUMq<`#F^#6%z*Ei{`@aat|F()E$;iWBb^RcHDvs=um<*jepOZto z84y)oLOZ!!&W?a5tnWK#G`8uZ^|?EMS#|)3VD(&ZXjSs)_4jYmWKq2>Q3< z$#gE~_~-FTs{A+wpYBniHhU+rb6nifgL6_&;by{(TdL_XxkW@p!W_hZD`LIOA7atQ z@oN5opuVLF#WwE8&a7KRxdnmSLel&NDL~~D(f^(V(K8;QQNt|TQY;`lL|!rjCy$WZ S2CWbhs6xw!yE)EGE%SecKTmf6 literal 0 HcmV?d00001 diff --git a/tests/parity/goldens/minimax_music3_oracle/denoise_first_sample_in.npy b/tests/parity/goldens/minimax_music3_oracle/denoise_first_sample_in.npy new file mode 100644 index 0000000000000000000000000000000000000000..094f31c8ee9d43993b81a3d8ed337a733b4a4473 GIT binary patch literal 44160 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 J-ZJAgeE1p{ZRlz5KEtJFonNbm`}$?$j|z>IuQu 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 K!;XtL)Bgds?Nx06 literal 0 HcmV?d00001 diff --git a/tests/parity/goldens/minimax_music3_oracle/denoise_last_latents_out.npy b/tests/parity/goldens/minimax_music3_oracle/denoise_last_latents_out.npy new file mode 100644 index 0000000000000000000000000000000000000000..f0736659059a525a59e0d6d3ff6b80a8002820a6 GIT binary patch literal 44160 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 IHOqzm5Aa@*%m4rY literal 0 HcmV?d00001 diff --git a/tests/parity/goldens/minimax_music3_oracle/denoise_last_sample_in.npy b/tests/parity/goldens/minimax_music3_oracle/denoise_last_sample_in.npy new file mode 100644 index 0000000000000000000000000000000000000000..0571bd11539b061c14010e0e9192356d70ee28be GIT binary patch literal 44160 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≻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

Ba3&KMp#7D0 z()f1`b@{gjy*6*f^UCRHx_6KcY^lW`ip|*HeHgP+Mfje>8gxEvNc%)j(ruT@aKnov z%xD=#x%(pcyCoBI={vk=kc5LO9;g|s#7xez}(f)vg?2Um=vAg}WT8XNuj$65yHI!tJwl}DDqamHDh-)mv@g4B=7gdU{|RKb(5pYZPn5}(MXf5xP^ zX#pAhhtGHRjS z?^y6M+KiVKJD7V-3HaoxI3C*Lhn};$(B)(j*HTaEw;PrCJ2f0D8#bULQ%c8w5Yi~6 z2wW~&giRC6F~DXvPCPvk%vKyl@+2Gez8pmvkLlRj^O}mpNL5HV*5epM1(bd`6IW~r z!U~Q-v&YxbR`mp)UULNpU0>qbF|Tkap$l!La#3E~63v3&V@$djcKzpv2}%x_h-KvJ zCRt(7Wo64*ildp7c6B;Erk}Re20=-wBhk{EM}~gQp*IS)QEkgR^!Dr!I;_}C=Xg}o z%PFI9!>Tp-zQhKH3@=b7c{|2`a6_BMVsx#ZhR1L74x6TM>hbM1{dRsGMyHz6==UqJ zJ(2IOHfcd@_({&z@BwGO%L8Q`^Qql|2pCEiVY3d(fV^=CESr@Hq1El=Z;v{wRviSt zj?_ZNVJWtMpan$iI^pi4bZn6+fv|;Rnf4jIaMpPuo47a`=&&fuq)cHO{a%u)`vJ5# z$c3wjI!WxNWiolI<*C2?c$OAuvvNlKY{jV*QZg?BdwV>4F5L@#g4VN+j-#2r>lcVZ zTM3A4I0q{(2%tmKiz&5oVnTLa2cyMP>C_7v{E<~>hM z{5gNw8mVL_jmh1_|28AgAYe5*MP$Rcv@RkowFon(6=6--D$;zNpqy$bo=|y0$q7kP zn3(_z4w{gPVGh^qDgou|`+#liWMbCcA&c<%2%OiO6S^AEmY>Qfn@z5*au)~0d{E-Ud9+sM&>}wr z_b&X0I$27*NB;qRbxMy$kJA>0p4o(wwu-p&#sqNcw?;Nn0Zp35Fy5oWiLq7#-PQV`MNBIq-@j~A(CNT33U30S#jgC>I3)Z0OL%!D) z?tn|OqOh62Vc0|uQH#Ar+^5VXglka|&J$mS#kNwkVYfK%7H3d1;y35aZUEP!7R#*Z zecaZx$C$G!5>=D?NFKVu^@aKvAZNl!r<~$<+C}nid4G6!yBDWjXd%ngbI^C}9~u;w zLEjl=VCn&a^4b@u!NXQ6qUE}vcb*@5I7V}kJAV+tH*0VWe`p!oWNO*3I2DvnQ#$)^ zFev)&ARFjW5SVTN`}^Bq5F(k0tAb(pL?VrUFOT}~Vqu$ECeazM4PC;m#A)mcx_;VR zdgi_vX3eN2S4*yPd+ZuXY{6uhZ}OK`g-wFm;4qp#_9d5^|D;!0!De53CXKjdgoCtW;$z=Zo2D9ImK-Ta^dOnKZg&`G)LjALlX+Md6oUsm*qqHDI zI-knMgur3Rji3o%&7{yy|G@ ztVV&=wOmvit;tm$N`PsifYR%)(K8#*(Ek?lePqvE3ZAFnZ*?e?Xib9Ij|<7``UDyo z)J6xEilAWUT{3C;cUp0NfTRiX$V~p3J|?M^zM2L2PWJ@J{yIORH2SB}HP_fhby z;~ajR=7X&9TU-;(b2Td^@y&L7?4LuRQ&SmI9CY!>sxp!?x1O^<*$dxRwL$FV^RT|w zjNNhnAsm@LfmKPdV~adR*{7CMSetxVwtrCtj5u|`o=57i>8J>M#c&lUwWY!4G2ASb1r<5uwpBcx* z?Gs^rHM?nbh#}Veb%pSZIgq(M6JOsdBGdT}^!kNvDA5s&kLCto+QX?ROg_Uc<>Z3G!La-{(=kv;>-&z;iuylPr>B6jVto0@))lP_gHSzgGfj6` z#XCn?3X)&Ro8QvJy_OEH@l=?#~M=6z%XrUJGeU=a$NI8Sfzs&Jn(=QmfehKWqi-5@sJy4a3 zgQGZ&4Y14uwg1Xs>7-HY-P}&_Uy}>_UIow;JzW?~Ux;05yP%pqM@+gWL2@1eK|wJ& zzCM83N~^=>_h+c0ZvsT!^FboJ1cn<@AWVENjq-U#75xWDT*VJKprsEFd>#;^iYC%K z?+aaYJO<<&Ho~DvF=W7CD{OPABr)O)zsHP?*dA zH2pSRgGuhEAUhxuHguU{XsHR_i#b809kzk%9{zp%T@;Fh-LqWH_i=p1OyHhFDD*rG zB+E8QQZ_ph1|y}=-z}PmTdEPuxl_Suk0NL*$bwhBh|qD77=E;O#?fI#^!a4IZ`&n` zVGWT)m1!o6Z+s#_0(D01&=<}l`+?<#JuH#YZlP}jIWqTK8P51`EqqJA#mRadf}<5R zaC^jx3iA)b#XSokFTMff!lu$6>62mVl{{2?UdSoUX&2m?bAyIVyN1HTZQQe0j%c_! z6W4Bv#xeURQSXrRw0-(CvggSc?0;fRBVT5ahKOQnXw=7OCfh@#w+^bw#-nj;8l9!V zcXzFLkJP^@csY6*bg1uwu);@RVua-K3~Nk`8;^ObztZ{j^1^%kv)We9iFRr}qSl{J zb5odl#?|97v;Ise-NL)FOjDJ>bxs9Yttke6ql2+}{TZTt$QUDfd4^`pd%X2-H|=dk zLBkF)9M)e>?XIdo_`hrjy7HcBI(m`lshwgTSWiJ)^U)Y)<^vbc)MDQ5Afh*a2Yy?2 zk(@o@h6?>((U-qF$a!Byf#XxUD((n2z%Z6rlw;#cS(5a69{Tkc;kP#t*ibCMV(XQR zT!szEWeyN_OdnDFK88-ZWq{ARozd-!0S zZj#Hjp^z!QAF3;BNRwMXIi`J(XdE98yAOCnuX7<|FkuB)D4pZFoMJ(4;|b#5)6a1Y zapZ+U5{Y>;ma9=O0NWYis1|+$QS(e3)+jeulIhkiuh4 zJ2;h5{0_6y-*nQVdC<8&gS5WrAS%9NNm=7HEO;hQX8p5+ZYkvD|6$+Cb08;06&|rz|`%AP<`AGycO(W)_!ZQr(ho#8Q0Tk zu5vaRyxpefd_3}u~VjyzB+4xg}Y`_pP%BwNjyIjCXmL` zxw_C<>u34={3YUlNd`VY))!p1D4=GC7f?0#qomr*AHC*m#;k+OEuCZDF@v|)fzszH zWaM|?uYYqPC?^{Y4PMa5Nne=E7n)>_t{N74&&2!u9v)Ryq=_;~B*>oM%i~)`xXJ3+ z7jY6hN}H(jws1N@`Wk)LzZ}(!`shY;1MD#$i%VrD(<5vC@qQd>+&r;@N~FJ};cIj7 zK!ku+S3iMAfxmEQg9}YN9nP3FnUkh5i*VG_G<1COhwS;22YpAnDc$-76_0h2dtuw* z>YyPQcwNQcJENfF`FBRnm!q~xp!?6Q;}3I<-?frmZ~)sYQ|X;qI+&7j0#cuMQPFA#+_L`yx}AJL=dP)ueKw=eYGf*I zo6?NwTeHxnq?bGVP#HDvCG#E68;tyv2(r)fG+b7(r%Gd$=%-_+;mJ8GY%ET}VrOM+ z@xDN|2@jD&65+T(a)7&WSqZu;?$UCc0V$yzI-31QvgU8K{4}i1Txp*Puijk&_Vnq> zwJMvy?9*Dov(?fV5Ef2rrE^G-lD);PA8q8%_&>C1zXB}YT?=YE zRH$E|AMJ5^K}t+Ulda8*q3+pRk{(_RrgMUrZ<=TD`8Q6{zFX^Qf9qQ=Z1Z!0i1KU8!SyTgqz{WB zHp*CB_LA!fT+Yw)a+qVQ8>qLQDAt6_F&gwF`1)s%efv2O4?Yk2fehtICo{iY z_(?+=;x84@bvdiZg=bR210$BWn^_J@{2VdRzm3T=d}Mf)(wdJ&dHCqw)$ zGnkXpiozMSmH}60z?-3LCPzYp7>{jLP(w)dX5l|V8P|)7z0htv>{2jT4 zYg1kUnKrwKVT~W}dFjuUI%!i4*>e3t%% zjC&o*`x7Wr7!U>7#0eCOld+fYdVX`|IktVeR7cka6t;;|p|s0_lv}P~eIgvij9<|m zSxLA#y&Bi9zJVu)?C{%xFXZln5}dT*9C@|k2=8n&BF8ht!B8O*#|2B`P6s9QS~7y- zLsr}~69Wv_8pDiE7%gl{sKmzi0+foskJbqb;evY&y5z)RfnbWzSmPtaOw{53e-FSf zsFH3@wfmv`NIEvo7D69N0rT zXPjV;jX8ss&0#3n5l;Fgrjqovuc+5>AWkxp$L-p|xMU{JxeOGd;fiX({7zXmv!DoL za<_v2@g9ix-c2N(+BkCg3>iOcfUdJwlByfm(YJLqnmSj*(>XHitLkf5zVH^K8X-iM zcR*F>RZ;GFBoVijrlvFax!&w(a5xvqW$e97Rn8t~#(S#am`P`G(YLc`_-h7UbZ;ZQ z!s!fs7fQl-mdEnc1c;nBOytLw(OXw_cppzC9nx8Zj%Qb)7QYp!@0bfdXd8>APe0IW zJO|~W7EV;uIU2O&R)uL{0Qz>d(v~lK@t)dYa9#g}ai5(;a?N-KPsxWwrDx)0??rf1 zxsJ16Gf4g&dO?g{+^4NgVYIV998BlmM4_lR@s(>NN2=qf?~L2@Xn8CSJ`F={8#Pos z&+l=YB#x;+`{oTS*MBw_vb6YTiX*PS+Ijs6^X`N(=yBEbK@aU{yyh%Mv445?Tsfs z$KbjbZ5Z&RjceMu03G@7+38F%j2+y72374`^*)-U(D186|uc@QXG~ z?x&?fG2E!Wobmhdl{!a_#r-0p@L$rY|5bD*ZZ*AK7*3N)5~WEcl}eH%B<**dq9Umz zNkm9ONRlK;lT=h1HEB})k~ES!`&|bii3TAuB*_#)Whi|6`wz}_u6^FK-?g6SzKKP# zJ?fJHvMl}(xt>>xN^O$NKrPoB}!wfXQ(dpaE1zMXi#ILSKwwMQ@6Mzo`wq19;#Hs4x6m-MZqVbVpg zzMX;POW$Iy@({frb`}o$3aDtl4Cz;2fNT5!`ktmSW;6zO=Zc`hL@m(2>W8Vb%24g? zG{K!YNf=rgx+M2r5TiG-os@ZfoSr9-1I?6KxYiUU&ZwZ6xfv>TFGa&5Gt^zD z!?D&FocSq5-%ml{oZ61+bI}ZmIbBjxxLOLp{jx}=5sHc&FH|O=zI%8w1 z5h!sPH5U#An8$iNhpzXIw8?xLckG-RGIblqSQ^ zryPUZ%mUR5u7Fo}Eu+8jHwlh+ApLoVhz#e;9g)-kw^he+_S5&|aKSq0O3|98mr?n{*i5poY`$+^5UuXfr*M zTbP2+N8z~qInvVLLUy&sLBD=I2|tz#iube0HJ3&d(Hw{NmnM*o?F;Y#m#_WtX#(sD zFQ(7CD?x9LE>+n#ADB@!*gg81V-(7O&7&+beQYI!PI*$_lx9Qi`-FLIqZeRKbpq## zJY`U#D0^J+K z(L3li-@aNJ%y)2Hh5QG!zhyg>xe-Xc`vgpYLLXiFU?LTL@c%v36Rw}h@woqPz>#bN zyw$x2O~kWkmuw#iYly%d4f@z)&}oFPsZxkOv>vyLN&K$QX{b}`n@2A^sRHj5Su)bx^D;F z_dt@Ht$2a{X3qDmDhs*yrEtm)F9=OcF+Y0Z0{WdxV*ahYPO|;{L6423abiVGkHk#! zyk!pbROi8eKHF&S>LeQBjjV@K6hG!@9y#iRI;ZVFj#6|lmm79=Zdpy<+ba5B|`@<){rZT*Xh3b%r0j|0^B z&|b(J&SvbamymZ$xLog=B8c!%0JAg;IN-P(juvWyxpO9QY_NuqglnLYuo5bUO5x#i z5s?0(17o?*p*Gk9POcAQintup{ctJp(Z56nxvZJ;ixbe`6-D;XcupMq^`Tw=I(RM{ zVg!dcU(o$@l0W(owAD)B%j5r`b7TZmUA}NV{!g&?ttBWAMUyClOT_)BC@5fh-NRw~@6N#Oy ze_{j#6=o8SB8Gah+vw`^|7iWIYw$1A2VO_bC$pzgis!kz#^P`|YPgo{tSblCxrcD9 z*o%lc6p)4h19JDVJhuy>Y}TUVtO}Rywvi7Y^|Tp`pQu6h=Z)k>nK_kuvXTiMa3)6{ zDq!t2SAy=#7^R+4;_p(1ElIbi5Vxz18SjctGm2o5?oMjvy%O}|50Go~pAn^tVderS zcbfY5AL8O||H4~aoTwxJKN`4UKeMnQgicZ)hn9jY6bLz!c&k$weAk-molx{TeUszVt;b&O zY*3z8Lp!GKBZUsPY0acEe5A*5$Tnu-2g2p#o2FrQ?^X0)I16tT+2eZeGdL(ZfidD3 zx5=GOwB^ZJdgs_DDm7w=(M%w&9-58rz7o~jn;Ce`;=D|e^m=ebqWKU`c zy}vIJYqheV!Xk)X41Px)H>Sd%?pH8K)Pt(F6X5*R1om$`$>qR!tjU(wu+z+vd++vw zYxSOR>^|q>STYX=y~QClL8yLLX0I@E&Sm$g>XtF7R&HXDN$>;y|zGyadD|hy< z&&5`WK&&vV$7^Z1XgszS-HxYY=9)QVis@});3!#R1Rf1wl>z76F1qFwZ^|7)^cvmKg_rbGE)Ai1^v9+_g$h$a8H zOi7Rgm~xIB+mWYKvqIHL=~elkr^{X@Q4FtF+Dcp_o3my|wFK-p+z-2K)Nzg^71``JbG)cA6YlJp_R zi+X9-e(wGGTqH`3h9JGfu{I;+sOh*aCS!al8H5o!#d#YBa`R2+3CEybE)-jSaJ|j- zC$O<(FDZCgi!GlZkFRsE8+sjN=gbwMt zQ%0{3m$O|n#=&?2=UDdZA^rVRAn3dc**v2f<0`&E(B5$vU71B?st3VTa~IT?KO!Et zE2%-2Gn5_iq#sQ=o^^WzU&vGk99BDH-NV^vk`j%E9p%^+@Eu970Zxw-L;FQhF#ARd zo%Sk$*0T#RYOW({Sg4}u+B9a?Jx#ph{1QD5-KM(v$8ldq0!IHKsQmL4=BsZ({_SQ= zcs0O&dn1Yc2ck*Y_6LDho8Xw{rX7S~T=m5V-Ll zq2l^1oOSIVM&G-Kec9=#U@Iy3qMw6yDmmEPI*9I*rwej7`3NS*1qg(L)djYH`!GYl z8yi!GG3Z!0G8@MW9BMPr_K~O{e#93$h2GJV1LA1#-x8ej!x-zf$6#uB0hg!JL4|vL z7^@eBO;_GgYC&oD!-)_!mQSYcnFIC_)3JqGW2k<{5~)aSax`~@NPqmm)<_~EZ$B~4IV9(-f=F{cAiEbM5xy=yxGZ4b~!i=E>rt zkOJCk16aIT8+}e~L%EfwF*u85&vy2c+9|=r*ZLOYX=el1LK2vSW)E`FyO~Y);+P=z zd&!9?hRZq0qw0cnL_EO(HV`{JDlNmj>6%P~pS)&Rv~b4@ ze!f;S-7mh6b{2Lp@k;93J}@6Kgp)q8Nm{xG;+;)9o0E2DUaD18}s9yaa=^n5f# z177{6zRT2D?|X+(d%`T(p`1!*46dd>uo^9At;6&8-qDYB52$;01I^2wM?4!wXvLYG z=)cnmb2HCyPT*X$K4^pDA_eqw<21Z#mPMD@6_ac+QA}!H3uNXU8usokRsEm=5|^yV zY0kawd@GmHoZ3zgbk>q&5hJ=TjZfl2a#^KyUfkKR62I0Z6UU+kYQ8U>REVEr?cP){ zWlkqaPf#TEXx`)}YXZu?{zG}s&eCgl=fWd<4OFU$gpy7X{*%4JAT?K=2#jv>CmffD zfvFmh%q8z$9B3l!B(6hoBMLIA|1f7ymeZfnuR$b`2N>1v~ zJK)FdZG&O^u5oyJ)hyWHG6Sw3?&i9&@lX&t2}ep(iF{EXDUad7zr0_hKE5A*9m)mv z9s}l?MiAU43lg&5*lAe`@J7}d40P?NSHBjx&+sHdYt7hOy4+b&YQT7DR8j5JRKmX2 zfv8;xXJar_S{l;QbgEr>mlN zaykiVs35Q2|DfML7GsT1HM=5lgzdNZLhCxUF`}cL@j0&x7e-HD-nx%??5GPCCT>RU zpmZGUze6((&Xd=iV_Kf;HnCimR4{KJJ$`s8N{`dRtk!D&`S=pDv1tW)US|tV*48-U zACK3Uox*N@4)Ug$u>17Z;N`O_jK;-{Y}lYPTweNuzOXGPoBYdZ+pSsr9lk}>f?tK^ zwZb_4$t(=os)ntDC(@3>r0ICn}AH$+;_V&PGuncSVsZ zo+UHmsa2vOw4Bty=cNT)R=S1}{?$sj_&q)Rx|3|WcNtF% z8E|=<1niOMXB;nbci_lc?AUk=BMtrVbn`X*-F$@>y4j)8EH5-O_)(6sY9y6)_+IfPs#AZ-v zy3I5!G{&+8^O@@o>1g;c55IBw|EB?x;Baj(j5+Ki)#eMx`Di^{=Pe5-s|eUSC_%W$ zAw2zJ2`cr5Gw(BA()+V2sk~km=~=fQETkv%ge_0NI(Jd3{4t60cvE43n}Ag;Y#>UT z@@YhA8kuzZJi^aNI8bH;v0w5@5#GQTSqV_Ds)_r9{}55nQ93yi2-lP*^;?ICe`g9^ z@miSUTdL#Q)zKWwpc$K&4|6VRVZp9FQw09?KTt{kA{wob7Wluuh@Q7&G5^(V%rX|> z2X3Zw&2lWbx zyc(W0B=SrBv*low=#O^PKVp+KwyoN=;OFcZ6klC#B_hsYmDXT*}t) zl5gwt%B#(IQpF~`U>R55k1h+|{5D74s-#jlUbL3-UEkj8EEF?2#U74ujKQr&A|NO2V{kI9XF=V0P8CwMUKFj4s&O7stU z(t`mrX=K|6qPI*7uHCmJ(<80n*M)pySiTqqA3rjy0~PW9Ar}%;T1RvKmXna~6za1u z3OuPOs_)Q4p5txyI@ci#kUK>SANR0A*N^gLk6$4X>CtF5J`HT8!Fi` zIMjO#>(U?7Zuctuuxty*Gg*vPFJiIyWjNAWMU?G-f=dmf(LXwl*;84IngJKlIMf|Q zS1&`2_g2XJ9fHdyd_lf^GTY3}LJCJVp!IfNJn6gxh21#DhoLOlFz+GyaPQ7FuR>9{ zK@5fkE;v^%o2o{t@(=5D;aiCh)Wc*3?HM`Cl&{goeAVA{{dZIB;QqU$?|q`3c}H01 z*h2d5N(%Lr(?E%w1$cO706O1!Pyg6*zL@rT>{agkGcHIXii+#-hTRY~-lK=Unah}w zw`*WH^bTwKV>SG-&0*@IST=gt4a+L)aL?qkBv`nJ$(?)=Z{c_Wf59H2ml;iRpvSZ7at37mytx#pn~{O5SZtV^^sjK;~N+Y4Z1Gn>{*-Mrk#A^sNJJm00@h z$V@!>fS;5%sTo`Tg#cpp`*kcn0;t|fE#`SzGqMks+qkMj!{TEQ#R|EKn+e5^E zr!i_8uza2jYT1cEl3ff9G7BU3YF1$UxNUe{dOm28l`wK}28cYX1+N*;NWh^>WNY9{ zaP+q4GA5bS$}1k)cV>~MZ6WZeAQM*XxlB)G8iHU)CJR3m;Mv_u2yD6pwtKnfCDszE zpNc@*eL_uGVQ_7o4u35gXmyq?$6HpW(Z;vvy(Pcd0gG86ZW%`x&MPOv+>WqYArIy0 zDaf$1hPXp1bYrp($Atca1p7$r4pZU;3(cCA={&qm3r<{KF4a=r2W0h)q|7SiLz=8{SE}8qV;qZTw1g zPrf9f6MM{z#M}AK2?Pv7%FD29Z+U8 zzHt7uzSFegW*LQJdr+%-J(~5ep{16)2&{2OV@j}ByN-D$sSQ6RVgZs~l8ooaK-S6w zgx=XSH2qbEGdD|VQc^c{sN^#0sx{QlMx7XBv&1|qnZdvvTfP~P6w6?; zAa6c~T{R-_@3zrZ`tEpr_H=q-lNmTE%F`8Z=iy+yCjMME4Fp1lL_g>~l?{-?IOEIg zKc($pbEbp1`Ws=;?^e>JxDcMLk2C)^mO{y&QdVtYIILhUQFp0fGAR0-xn{+H#|bs_ z-(Rw+$K#D)o|VDnDgLq>4#|R7BhZm=U$;@v<` z_%xL|fYoC9TlPeSL5d61eJ zKtFbGf?M0SpwE(XT!+w#3K>~&nYT@B*-}lcan59hm1aU6H#gq3_!8N`u{_MJtGT?A z2b^x2NK2Qig1%xFd~}M0pLfrKCg)>+v#FO19!Vj1XdmfaGXx5SXJD?5G&DpML(%eV z$aCgc@@>1|ahNSU9}b{H%iLi1^$?gG-$+jDCK7`)6PSQOcZjsiL~GediV0?7ini z{SKuQlDi&J(OV4tZ~rstC0+3$I^+Tk~I6N9;Q!OO?vG<(*&{OG+bYaI)1%up6lwv zOniBhjB##^&~|J5Euut@?kga&ikrcar%m-;FVcBS%t;5gSNr_>DUDg73_?y0WcCdo zB2;#n2wxEgbI)ih@ze~96Ruy8sKp}lG#_GTaO^4Nw5E-RBRd!J(lN3*IQO(5hu!i#Nr;8iCL`5BK$)|FxiwOV7o z@jTaQU%UmLZF#^kw&LlB-?th6A|-NWcP~-h5)a2b>L6D>95(0(6ZtQ0pdh&hgq7W4 z_iS0^(_%@^M!25YdyF>`v_6Vctw^;j*(xrT;b)j6Z_!fD(LO#BChQw;AOIcV?S;n>95Dw@;i6H zB3BxETJ)jnSS8SVSLnvtzw~&06yDr8jmxzH@mW^`8gnCI$^Hab_|glC#`wf$$#dv& z)+6snu26-2t>oFr2iVkf3Pbs==80K5VYyii$5@>K3pKcT*uqElo2Gx*2HOZfaJ4A37A*67di2ByrAPy6X9NR>LB~Y33jZz3BRRwf?I<>WVKucJL4_TH+Ksx zJ7x(93Po(m>1Dj}>=SC1Tuxj9!y#a93Y=c~mjs%YgVx!NXj~;rp4HF6h#zY7gWo#R z5_t?f7*iMu*-R|gl|t7YfRk$iNXG3>@^sBX%$a?jRP@+`Vccryn)VbvIYdL3V+pJ) z;&NHV8qixT!V@|f3kPc+z`EaeA#llTu$2*lDK6Zzc4>fQNNr@OU)f@gz^7ehAy^JsIOdF=Bj_$(?cRy3LL$a78 zUXNYf+i?-C#2fCqxHEh=CR^3AL&qMH?F%o`_6|?N9yw29=gNRr{V)1=xfwmswUU^1 zsKAFIG4#$82H7o{jC8gxH8@I9$j_hL%}v01Hx(@TZHCU=J896t#c1u?MK37H<0XrH zVqaf?ZEDsSQMeoHa+We<4YRSy9x9Pb zoVUr~NMa(<{TE5MD>&i2a9OO5b3;Xy2&@t4GjAv6Q&kyH=x|FTH44w!!l&;U(xQfo z`1!=LI|AzjJBew1B)uZ8MC|=8!2Da?*sOJziaZ#{^RnGWM87yfI$yG()It^354zIs z={qrXk0P0MV=8g^Zw`5!YX_m*RADTw4tQ6FXrS*0cC6|gUg4d?yZLX}%S-auV7FY9 zj~^pd?yoq1&_5=5PXuiKJRJ*;b373PG58rzSnVQ3Y~8z-^RFD%QjEs&xp&_>t$bgR?n|ppahE}M7w?jL%)vU)5 zEn~C{ziyuOgGUDP25IWFgIM~m7N@k&gpJX@MCGc40RHR1bz`wayH*UpSxXY1NdhYT za3L`YsbQU-oJFD%jx`(1L8{mRx0Y42h4z=($R}w;GlEY94R4p+r8`jSQyLCgM^U#b zj-k5dEmhp>1EueDD4oZBwvS2>_o#<4pR5EouMJ@hmrwG4*vaPThvRuUZ zv2IX};|eS#71cMGiurfV3lz^%#`hRnPU1|2JAA0n@Fg_JiN!@dyD?yw3CW0?L_7xD zu%@?#Sz33Gm@bMTbC$+o=nqpI`Y(tzyctGyYfI=!S0j!I6pMD!8L0EghxLX0 z44a2mD>ER%m7zY%t+A$?9G&{-;+e(Q0- z@sp3xt$y;fZJHu{+mHmM)`s9DG=aK~xuU_xh2U`UI~_22#`bLCctam`@!G6h6oPp2 zaNkzE{g>;YRd2@6H%q8NeK+o&G!^=!mErFy2S)DKKm7fD5|P^{jjOT_fh{+4H!4-QpW0(ew)Zc^!uwiw(b7jzj&8cWJJM12LL*0#p}OlM<l3Jcdjfj*T6=iB!0N%-jiVDk9k-Ns=Uy8B(dVRj9Ni4Gj&F*6aB=QX~zO zQBu)BM&T_9`S$r2zUQ}duIpUaIp=y_k8>Zl=Y`c992~ds@*L$kspr1iZ`UC`OHDm% zkNJ8=ntC1w4jn$U(`UzlL+-o(cim>Ex8Lrm>wca)eRoft>(4c_Fw(RzpKYXhTJ!&R zh<0oZ1Y7OhQ0KD>w3AQLWu%bYh@$XlvJ?$JI zZdkq)*E<_BwTaW%LjU8;rH2u0UKAf}I~KxO)er}v55(D?@jxifiexX1rJ{L0!}c}t zGl_}!z_xS?hHC<-HOiCeu$pyxJeRqyxC>Thtj4U*5Al8OCaPd%0RQz^!26;%WXG09 z+WD0L_hdB|o>+)qwz*(d056lEU(;KyAacICDv{I={j zwr;;dk}ehCu#Pw}cyyT-Bu-=gjYzZK_R3R#-I=)mh%p%a{0(ldLvVX+22SuJv0nck zlhfaEOMU#fozEpwpy1;chFL8NxTxJ$go~FiOSP(RMJSon}!-_yFn4u z=J=6DujzPp!zw(Z;t2PGlgLi*xiI=+optBp!`MA{n0!uF#eTWn)OhJ4uw#3U&1TF7ak;98y5S)at^zpUYsAJCfR4R&V9CMlLK9miGL2bC|Y#p>$ z?VyQDdJw~5Kq4N=w{=Ez%*6x8KDN=5PO<1SNI}=#oy!KrTISeFL%!Z62QwP zfpmD!5Y)X&z|HSnKi=E*yD$jw?9Ijp%`4d;{qK#22AkYPS{oE z$;7VT!A$TvG1oXIjKN$Hruv)+^Y4Qmldw1jPOmX%40c_Huz5O6lcoUUm?zCVJ;a5+ zm&VK|H7mw6ydEZwJ2SzB@=WswGp4xs6!bKRFwfe>7~vob=A6tE!uc)8$g2b~H3lY( z`s6v{6{kxqW_9bKlBy-nwMsnM7x54Bu9qfDGX7zH{Dyrqr19mMvu(u=! z?;5Kx&AUtCR=^~ujJF&PDos7tD|g5p%tynY#?15Y#u(k61Jz|ZcxWID%-IO27cpW| zv>UB97XJiC{+p=jJprOu&Y_QLG%b_61`@H~xV4*&;mh3B)l$~j z`)Dq@Ow$J^dn53dy+<>yn?UQ*JWO9y2)#Aa@cPaq@b1_Sl4ml&WBNyMxS9d>AHL9< zxL9iY*c4&bLsHPEOk00BVy$xx+LwN!tydk1+mINJI$X6bdgO@tcG)Ob8xMmKa;%0) zJL#F8h#XaY&^|}$^==!?@v|rMQ~%?hO|Zkf@4KMZF#$SW7jpz!x8tcMb?)&KtDv~d z6C5|6vhHxOg#If@*hRvzH1ZV9GI|G2`v8qCmLcsDgp5C4NHG&hS~6aLAB6>fc~R6| z1$pw1*Y=I&Qtw?==ql)dwNK}u(P?$`0(CkkaVCBW3!?L~9&!RN)mhE4e?rTBJz??B zLP!>y2Ceiwq>vF}lyDFQL&MQxRv6lsoI}rfAJMBbmsD%^ar#eJVECth*gs1Hj#uac zYG|PP7za;sZK3&NVcJD~kW>u;^U_#-cUPaa9_T&AJBUF9J z5E{E55GMr*T(4Tl^_uODHt!`NVzB_0{0OFND+|GE!3U!FSs&@QGOp%>VAM5sq$e#U zNz>?iQhOi_;u|CBpIh;C)ba@_j?e{tX*q84?JKbRgdr*fa~P}3H*n^yb&O(R8EmMS zjgGp9Fg^b(C`=zf!CDs@tRTybZdwNPLOe4OA;NUPFDO-Lhb?82w0zYV%o&$ux9`zn zLbN;JYH>X3uP{N5a52uS%z$CTd@we8M4t}|VcWVv2or212ddN|Vn7}&27i;lL!MUJ zvt<}w5nfh!Z5~X&a{=wY{D40_Jf!<)0{50}66x#SPmDq@UEk{|0Pi97cUG++41RsOS@*8Mw% za!)k~iTF*glqUnHVKH%SUdrKHJR3bH(rH&iG(IRvz-@2x(EJ5OsqQu$|E0ZREj@tdC2Q!RU1#CjyFiZk!rPppuRQ2F*B0K%ByrO= z6_V@n?l^kNi1ax}*S0r(A(j2JG3)kp;2ZWQqA%3pr0HFV(SAWzjrzf_Iob5U`$Sqi z&j)myia2gn!DPGndEjFM$#`o%Jnj4n<0dMT3z&&Cb7Y@Nu?l2}=1qpK6bcf_| z@ZA_}J)E`~Zpxow1TTipt^n6S zU+dEC9Q>!WmHgOu9PWL3LX5gZahb3SJ~^O_J!YjCg8e9&ECdq{u9%=?hiwP8;ITjv zHpk;5Cg@htu}w8F^VesRP_hw}B!odwuayq(mBkS6zj*N4R%m)vMFTdevUJf5*!lSp zIa01dd-jCGboX)GFgXn>tnY&6+5^-`wGB)AEuq5JifX(r!wuetAWPvNwk+60w@j;o zL#Lh*lXWlY(*N{Oxc4A^Z){C2s9nQpzMm*~`Qu+l6Lb~vz^4a1z)!Grry(#*&_E4V#$2Qvi1^0$~^(+&?ZpY zoCF)&dLdIW9-`h_F=qsez`9?L=?uFJbFU+;(0T&-PggMe(!?0YY&FJLvI5N7{{x}* zC8TO10jkBvp;!8qQ6_rNA2PE5nAr^v zA?$=K*%f}A`MSxM;n|l21=sc>dAS-V{8xhO$1U)=c^7&Z55aUF2PR#>iUz2@q^DZ; z!vGv*VjB#YHE&jvjwE5mLsJ84{mOvH=`UCVvvr zhTnO3@%j>c_1gsBo~>qjx)y;sV}nVllr#B?7pf2HLSkYfskS#muMK+Cz{Udn#g^#zm!BN+_OCtdH8f2iR{Y-k{gf0YYe#Qnld&nH`AiTfYu_)~viE-;7 zLLs(b8*mjTj@6J^Y7)#6VKKO;l0sZ&|K{jzvjH|h3b$6j1?rfKE9x^~XON%1pInKl$3{zqt0?;cK?Zau!2{P`4O>* z@l3~)Heh`Wuq5(mC0;!R!?68-N0iZ)GYq(xY}dfyQBKmQ2FG(S?^0X5`FN(ZeI z3=@4(iX=|o&UgiU2HiFXSg)nUcr@p8_DX(&8KZyUS9cKz$2%}*=L<4AHM5v&2akZ@ z4lyR9WE=2|){uizw}D69lH;rQ3EsCoN4A4b&7)7Xo&GOKfv*^hwNi-|&Vi1Z3%QHJEq7`*BZi z0zT1tfnNh};{nmr=uusSdOP`8o6%pWzd94;_-^9Z!z>(jh{HwQF?j#q4@^w|NGoRO zvC`%|Z0+l2ba?v&CDp#7tLM2I@0-K;@#jrcReDL&UnZmEdLz_{j^iHCZAUNWJT{bk zB9UQB@q6=k5@P<4&TbHcA@4d$D-Ympp$JlXKMw5^0)bD?jqn7G)Vc_m(w8qh!TRY- zD1jSLzRZZUc044Oe6iqt;3{{%r!v>Y#1U<-|07p5AJfL_;hNXi8%eC84>1Xk177YW zyfXU+-ci*+$=QC$6~99sjD}&AEk7z5oyCyguT(UCC;j+s4?3G z!^^5U|6-27pY@J3`M-JS|MnYhOA*GZfH>F}dX}mTB%y!n0ydnZh!PLdh=jKzJX?~1 zH;+YO=2$)o9nkSyog%vTYv{?PO&761L!a8cMKjQ|~z)lPCWof9_l?-J-;`EcAry z(_}H=a~Nw=v=E;+>|mMl0nDqHViOM7AdPll?PUrv;Nc+Uv&_Yy^uyS&PmoOv%tEb< ztr)4-ir*YJ;C!!J6n1ejYgRTkJAL5FQU%&2osH&aSv>vXIv)7ghp+7=SXJ{blpEGy zZ~U}jecT^m`NGB6bFhje?66~(&u_&T^Lgx8!5XZ+xt6_j--I<2d5p3H%h;YKSJpXp zADRVihVTFVA;Qk``1zkX4BDEI3~wVwV1pTBEMEnowG=M!O@pxDZgSu2F~pI*@bNW2 z;}fXJ7>U=y&budIJm~@K_I*G+Ryxt=2M&VW>xblXY%cV*tU%#P2Yj3F1%FQlQNat9 zRQK6hC{8_tGEqKI5x{{TtIR0apJe~T z<b#{A@GMbiq=L`RA36%$}dBNxrH0NWS*Bx}?q z*`vXmSbZ~TR=0OP8yI{E?FJQC*V-j0nIOT|=JBxA%@gQoKZXSf0buTr8@C&v;Z6EW5}MKEyG^iWy4;y|A|{ptFyt6 zu49MZ3btFHpS3xB1!YyVSzAqWcGqk^_AY-9cGYm$g;RRd>xl&$pcjp}{uXlY-o$e& zYcOa^$s}(nLms<5*2{MO#;iRTXqSZoNi7y7tlVpSCAAQHaFC3DT?AU&lJU>*ExZ)A z7+s!aqwQHWsyjZ2*IVMTeDfXj(6D5eHr_?Wc^o#df-0!DvPi10pkhzI)+ODL7>+Cz+ zQmY=ws)&KrO%GsHxq>UerUP&ObVkR$mp-d4BFR7H!1cll61_Z=V6t@xhvZ1{ zEHe;FRi;?*fEdve98*DYJY)0}E0iz8#=;`U>W(K96OsXk81+<{Zv z0Ai?HNL=cdK*t6_csnExJEd2l(v-pbCUO`Kp(&V`m_W?nKgJuX{C)?`I@gJn-@@?b2%<$JFRP#HhuR_SxHISmPAZS#=A}IB=i`#B z@A+4_UM+|Wm)4-s+smYGycBi(WN=YR55An5!}Bg>;I@m)+2dl?Y}zU*_O9r0e6)zA z{w1H#7{t(ZRus;Ne~JE7ofW!n%sw0&#>xZ<^eFg@`{XNWQ*Iy*WQF1ZzE;#eD9Wbz z-^QSw!MGt>niXjj#q(1+T*o^DJax>KhPcMU`-XIUs~L#yMKaW#Z!77&x{PRgZLPgF z$O}t_vti&!673otu~xHCMghk*)U}Ak#uDT*85I!Y$c@Slm$8ep4<&EUz@sHf ztf7_;uC9-!Pn#4lbKZ0u?e*fGu?@p#E>BSA>1lZG6^MtMhas)tKd`tV#VjALf!Nj4 zAlFS2KKaaK)_?J)jd9U%UH2n6D&2<4uMMzl`BW~VuEb2<_JA|yO3anH+DwRZFK~8k zX0pv@!YjE#!tiC$JKu$v`I_ZiXICxo>>hxLr<%l6GZ<0}f59{Bk1(TU0dvw=jHzlj z0=~bCnB;18P+Zduxg$5h>Z&%=R}cdQE&*_)DiAA66c}aWIP@FkXY{(6hK8iVaHX*57asx?6KR}VJDrsH86ghp-M)a3YE+;p2m57kAJvj38B^Uz@u z?&(Bx0y2sC;j@r+c>(Tfcuanre+|ENXRyv6R}BY_)pNr_`U1k9G@ z8s6&!X08qOJ7q`C***b9uP5}ohzg12$${vb3ut1K7cfKKz?J+`dsrh7-%1$J+CeYw z!&5s@Kd&FVA}(;}zOe7q{+qTdEyer>={Az1JWqla}3wtID#oByJ}~Cse^>Ah4e;h5kB}+ zi3u$nk{j~}Epv}!=CK%R@=6Jp1ic`>rVW_fqe|BPH%@06R}ldrYq~w40`m)F;Yxfx zWKH>~?^lGlNoyWK%p4b}&j!9Z&d_kf)=2L3x?t#YCO`#kbc z)maX!n|9*+p2et=^8-aY1n^E|DYm}jMWT5TmjuY5Td^SUeE5P-bZ)@oh2gb}{wcE? zwTm&Z(uiBFQ;33YN%-}qESs@x7q*PfrCK2@QEIct1A9f#-@am%?2nzxpJ|Y|lcW%6(v&FakXne?v}W zAX+5V(vzonNPXZbx_;+bn2{pKURp6hc5d2*CbRZ~h3ggUldd8)V-dJ)TO4OhOq1nn z4W&m;>S3yM9<^pt@$q|c)@ScqV&0ZVU5+Oc?vc$L%Pcqiy=5D0xb+|1*Z2(QSiQ#C z{B3lwv>#))Y{Sac%jxf}4tSz349lVfuqQkbhtzq&FFz0^)T~h3`#-vRmKELXw~Mes z8MsTXm4u#+h9`y3sJl4eGv{{veApRNUdUjKDh~$BYT#Ie1iIWcl+9lkY5XAmAJx<9UZRZ(Gn~TR+|TSdiWRj3tZKn?U`? z`Lx^qD!03{f-Akw83V&RIP>+TQK={jrm3W0fma-A?w{%f6nCTOKnv}UQ$jf@X==W= z2{{|?;zo-)w3bQ0_wg%X`29_C#`*cXh+;C2=_MY%Xrrdqz`lTqLT$l|bC< z6M4M*JOtbvotl{wT!%SHoT9a7xVwt?!8GwYh?86e{DoFz#;J2qu;oAK8|$YPOJ9Oa zMKvhPC^6&vu0r0Mtq`;PJgKhnpn2^UIHNNhD%X~Ri(>#ipIJq(tNTH(KofAMa>Lx( zAUL7(g=pmQF_j&?*5y-u-#04`6swdI5#}jev@!+NVqVC+ssvyDnu7ql5X?ezp|Yv~ zSk<*45F^O+y^&>B$2Y;RBh#6Ml5-i^i-gIH>jj4yDon<{#f)+HHt0!DBf?uunexaq z;DKb&7!m^8FMQCVxB&WAc5+T1+X|NMlf-q66x<(}%H4yFp(i(3_Tsa_-fhF4LyZWcJE zm(W9Zwa_FMacE*b$R0kA$K14-4aq}L?9c*P8~7kB<~+S1V@4Nk&}IrOMyZ#23Q2Fs zBc7})SNiH@bQIl<+DAG_Tt@?{cu(c!#^=y!RtxyqD&n_`FUX>1dFJSyZ!~0oA6fMB zC|r|h;2f8Z@5*c3*by?@pbl}rtZ-<2?~*Hl-fSh<5tlP^cE z+eff5*@-%e+H9ptvo+e@hI2R#dOn_pioc6dFd+?zG~xeN}&{IJ`p9unV-k-Yo`DEP+$tAZB8 zUEVb0c4=VBqcT)veDJup6iROL!W|M}z~9x4iZ^00w>6e1H=o9hxe;(9q7`RG=8*Z< zmsm>#eI=&ssLf7#Nw;GiDOUN8O*{nHA_Xh0(<^a&b_%uz@nZGk z3uIJK3gj+6rjMCxsJz;dZ77gq4{g4Ncj{|skY5vqrRAf}X-OPth(wFhFSTydf8p}% zPHHTF4+S-|sd3J9_Efbodzr4Gh3XlY=URm(2L&*Efi8yk%CK1?d34MDS+qEL5lHXK zr-q_3=rEc_54~DJN9X^eUgxht!KpDiF0%<6Gh&Iw%5b8aI18Hi4nv&GoZ83gj_|fb z8zv8$LR!cCsde5>(t`}4;>CK{|N1*XO$nq7Oj3ty(?R8gKHPZN1L@rbpq6O>SN7h5 zfgAChK+O-JojM4wUBA%4RtI=5Ra8^%`T%k!w1F#;O4926tp}SYY2t)894^m>mAx~m zPtitdD;a`666=YY%W=C^pXaG2_N z{Z5gk&%8*&3@v6zc`Y_8SCF2AZ7`_%5Ez9R$Y?nQpQd*~IDab?Km#;eAB7!O0kC&Z z45YD<^!;~HrfW|%l*casHz!;0ReVkK^0+XVzzne-Sm4w(^>A|pGE)B1*l;A3k zXW$|qGYtGzYW>&L5Kir}L*M&igtw)I(ve$G^kWP1n0Z-?l{wN4&-vJ6X${0VBoB9- z5U1_8uY&AC2R6$m9hY7#q;|d!DbJZ;JdoT&VjH`t=41$z`Akp+(+l`kHxk#X?m&~` za424}3PR_dp&c_vFkgNg=bgWZ`ilvjzh@4bi$>yAUn87X`hpA#?8S)hp}3K2#QMJ# z!ru5-Q$gx?+&L@^8Z)zDyM_+?dHq4SsdNMb1Q}8%)cs@;}$IEQ#scVL4FwY2Ey~n7lUX`_qeIwT2is#yw=cCuR zQM&$NERII*K+T0qfjw4hCF49y$3!l1b*ev5m;4iWF|h=*4!WY0LK(i;K1z@6zkzPN zL$vGYQoOb0F1;sFiCxYNK30~-0F@F9c3XnmHdk=7Hr&Ga?kV4t@r6F2Lb!3mH@eV6 z1QN`~@NIu6<+;{hO9q1wcDSJ8pym2Pj;%s5obuZZJMAOD`awFJ`ni(+T4MnMTaVJf+T~VxOKR!FffY2S z;Vk8T5oA5<`>o~tzLJz~6^^)a8t06q6v$qAK;@Ohu|LO?(xQ) zh5B@%H5XHfH0IszK)aCTSa9+?N@o$=+1rB(SFhv3w^#9vUmz@35r*%xXVMv0li*ZK zIyjFVgr-AxfqQv^D9wLT8#sQBT=FQPD=T)u_j_-t#i7geRPA;uy5uy>T-QNL=kw5( zjECgVU^wvXy??Yud?WqgtJd|wCi4>yo+ zEkCd^F9eQg@iC<%<1o-G1j|p@LWRa`v=52~wqqy68DHdVJ`xUxjzz)BXC07s&)1x9mAL?o+Zr4)B6YWH0qy=BVbue8wM5`4eskSXIqoTM5UOqiW4hPO) z5~`;(xw;j6{jdy;C!UbX>vrJdGD4r+O~$^yV9=O&M~YT)IZ5Kv@M`%yP`-AE+|qHR z*(w%HVCpK4xkwd=8Q!9G>kPnq!3~rgDTB1*ayS$g2Vr^(Q2ft(8esVXKH12TltT(c zwXy=EmmS5b^GDgwGbhj{<`(+-P35om6E?O96fT-!B}TFsolgti zG)#SSE%r|R8=UDfP7c3`#@(lm;DSvlQ@*W|j4B7RR(B*B^U^jPYCB7g+^4v7Lq5o6 zr-M>M0lnXU7yb7irnaxfxfXVx$pZayeAOZbK7Su#&(}4yeosC6?UrP#P2+*H^*j`v zDCH#E#c)%-*W&Zn(lD|A5^36C2nmPt(IT~k>vCTMiTDzj7cvA+=hBf!;=7fTnhLe? z3Z^3UqU>8yKa6l}h44BCk^_ZN*2x|BXKRDwX$5l3YA-I4%A^lF!eHib9_1O=$ILf- zIS+rWf`ARN7~*k_Yx3#?R#%x~j8iSRMmTep>NJ3#`DgInG(=KeYe3EOGMU_124nx- zg9M2N(9;uyoySBm$WIdstWI(TPspOgQB{a3I1IC%Srfjo^HhBM)IUMK5+^>?VxUL| z*?c{YoEcEXFkeHe`%Z()1j&M0PX|ZcA_aBrS5p@!f_+5`Xl$kmO6r})#V%azkXu51 zy4NDLGKUDSQ0~Ypbr2k{qDQuMq0bnB+@`H~c+XBawEZt*rhE%i3IARV{3dn_kI#-I9!dsy@1?j+to<1HW|9=x3oxFq4cPDR=fU9Hc$|d_%;wD_+@0&Sp!H@El@(IJZ$`PeIjs`i zjAg)4FU^{Fx)!R1n&QhP>+sTS2iAY(Ga7pD6>fHU$hlwOMD+J1a9SVgvOO+7sB$S7 zZv~H_b*dX4zHN=?wD&=X$7Sl@Ax2zn&f~kRRI+6C2pt!%0^QSpIC+^8nEXot`6?8! zFDVP1B^MB%@pIT_avF7CaZ&ED8WszmCEo+%@$Wo)GODr{zodBJd^rm^7k?ho7(Ngz z>Yn01HetjkW6~863ZG(?F{8mAB%24R-RK0Q9#(`U{Eukttan6T{uv5<9U|hX`51I( z0QMZcPTu?P;&lC$<#1Ed32W6r%)CRbwKwFF&&#@~{~Z}TKVn0UcB+$$v;#DkKC&Ln zd4UF_cJRsjF$BC(1ugBFXtsd^7KwGW9{WT|UqtBC`^<&C3v;0)b}cpcyhT5(QpTZI zHQ=AB49|9_pnUgM8h1Q|Tl3&1P5ONt{M8W)46mc+O9fVBpEv4y*I7F(vE{7xN~wMP zVJbgccmU7lj8ndm0qh*$XAir^qsq69c%~o<)i#Brsv0lq7G-cU!xv!G_D<}KyNvmf zdiaeSiTi7e@$&RAEaDDQpN+C8>}7$k|5{?knuF+@lYw&U8TFN{aKZJDxLwu@ z$$$jwVwHlzC+>sqLVs*NE{c*Tm0_~z0{-ohpljajM*lu>x<0Ir@Wz&snwqm<ViTZqt_YT_{mtpj-vTa04`4;q6errI#~dcAOoYoHSgtI^ zIJL~68tiKjRn>)b303B;(FdrMJ`bG|^pj_>K$q-#MC}6am95@Gq7vNY_7z;K=S3H2L}J_A(mo} z)G1;kD#mWbuXFg2Z5gAQ_L3as_Z+m;{KU=gUxjBn6EIwM85Z^I!oZ+)T<-QRvO8fa z13hC)Rwd@q(vy-H?G{O!rx@Ez-xa7MBh9_CT>^}jPSEWK7&!i}l1Am0pv9tQdTqxS zI^L2`6;+?o-t-(Qt!l|hY@DJPlU{O){^WCxHf#m|vmWrQO9K2#2Ej>l2E+X~jrlY; zmOG*Q96!z5O|uu?;rxkGhYlSVICUo)z0;>=Kt(nzi?tx7lCi+*IpR|pd5+{!Av|{3 z0_s+$QJ;nTAY#rvsGnHNQ7RD!ciur1oW2G2SO#IoC0%-MqyjSUN-)do?ty9ddiXxl z2^#zCNuom+FkjO^&Nl>`pFgk;Xpf+YJ7Xbya2^xbau9ebLgCQ1n`D$%nv6|xMusO4 zio{&usNEQ>^y{RBSrgzq?n#ec5@#d}e?YL2GwdFoAZy;vhVXsw$(yOnYR}(hJhamf zRNmeoI*&VHkLiBMd8bWm>^DtyBL84V_#wy{p9R^6W5M%%D)>B;f_<6ufL-U#tx5KP zV%P1QjXT~@;Vca-onrY0h&~)tOQH8--hgG#Zx}zKz_{|*z=HSz5?+xDX>HA5p7{ls z6Pv7>n;OZ{_!-RJ$Jy{zuNO)k?h*d}Fw{>-hdHmM8Tsx&=2Mvwqq+Jt#0v7#w@=={ zP}DxY80@l4+nXytdJCUz?DYQ>a>k;_6;zQrhCwx83dV8zMO%A&{3 zc*yI7KBpGvtHVF~=MYl-Hw{zUC z&UUIBaE`3M5dub8r*PQr8tivl_4IURC$IZuXvlObPu(zIZZgsg}q z9@7)xruzlBrF;h1tJ676`)-iImr*pr?H?>Ys*kn)*KojpHBNUwfoA&3v|Vf~5sXRa ze7blPM?Hn$Lxc!zNOvX2{{mg!KMnt$6rbY7*qUbV0ebl*52H7;1>9;Mg1Xgna@SN5 zEr;FlPo4|?rlW{2);S@wMvik2rcwES9=LhF8#HaxWzMVVt7y~AIc(8;9jdgChyAlM4E^8iroQ~j z=us$$6NNT7@kN|{DW*(ao}WYex-lBOKAjXc>Ek|wwWK-99~~q(bWx5dek&3LwXLlf zm-~V!j?O@dS4ao6Wns}qV<>JKgj|7r@Mi98JnZ`pQjf$?vBX%yx6}w^nYnbht`&y& zsX*+_omhKvKFXCRQ%<=nnyfHFa>)RHUoeECsl4@Izdb%z z{3m+am_Ay;?noPr7s%<24=6cf#PXX;_SYBb*#~>U^^ksCNk4?1wKr0JzN<3tn!)Li z_gN{vj(BgmxAS`_o9}~_rQ)~GvuNGLGk)!8^W<-b`_QN07x+iBb1!WSFVil9sq{1W ztswWzoXczIhv9rzEucj|0dkkPQ>;0xDi^;2&tgxJ+mC;)oI(7-?Bw5y>+qXG=FPLv z6;2b&IdL6ie$2-&(lc)tu%Cu!;a;%~ar$^B9>K3|?w!W&Nn4|zMr-3y{KK_jcl@M1 zC9(8h4gLVgo>EiU5$b=3y@fJa=QpZrhq5&xb`#D$=x2KFXm#N#{z0D61iwlDOa94n zvkva14TqiOjlzTFedgKwiEYgPzGUAJ+s8BCROSv?i=T5Q=R@xHd&!vwpYoel75mlp z?2?@0@m91a)Rl5)T#R2Yb!6Yk-RD|b=6Lq*jP){le*c)kzFTaL z*t+ap>FJvd*t>}j!b{*)_>;a+Ue<2ADr3IPc+qyF?WCRmk+Y1S-(9l~A7Q_a*O#C6 z{-hn}%FWnci?he=hz}J@-{jjNXV~ui^j&XtK7g~2Zi@eba;f+m^xpU(bvMH4`@h)P zbJEW2N%fSun4f*F0c}#*-Hd&UHg7Nfg!46XCV%TZPAq+pyGh!0p}g1mE7+gHuI#MW zwB-$DKUeMu`YyB=l<&r$Pp^Ti=(*n>%dSIf#a{*)!%BQ0yvNSIcAuO!a=Y^PbnYy` zOP~X-IlNse*5eHQ)?vBomC2r(c4ZFtm2(=bQRW@^B+uk;;<-o4d5FC>dr!Ot|4#9# z@-pA{qCZZnrlqfTW^W5wBPWTkPaj!rucKuh-ng`QhpdgGY5i##zw_9~u&2XH_F-^6 zdkb}b!Ok9=u{;*%yCH4N{j;*F_#Nsdu><9fp>67UgZTH#O&|ZkevUmHcNAO2&R*9H z*PvYhhrn+98S48Q(l^`4$+PmC{IBBqZ^1REZ^y0;nJ6+HIkqG zEART9*j6}sZ;D-syW?kI2lj4aN6P;LZh@Kbyja@sEG*?W<6pv_05c%x#u2m;kTuu_ zzXyMN)(EjJ*j?}mxHdkeRO~5PJD0+SaF24?!;fR9FK5B}FsZ^mQ`XP)!8l`bD&(6W zeZL)j?@HrIYs?-e=R+9e*_k8%$jLL8;MD!ea_6S}Wn!6k_5Lkai=Vle{7JOw?Ck&f z4ZEJ$dGG;yUHYDo{`r}|CH-#xBKU;eP2K6wwZy)}UuefKxM4}VXjii{_j32#l$L#} z8GC1ZzdCc5d6IrVya>6!Y$KMwZwvR9tz-VS^6unkELJI(xt%@!DEt;2Mqkb!iBtbX zcGlED*b4G{^hD@Ge+jZrkAszPpEl3MLtq#jP%h5z?7_b(lewDy$sX`CJP0|jPB$jm zOZR|Y{5R;=va_dWY|rPP2!B@;-%3A<{mws2Ec@Vebtk?Ht}C`NEqhoGelKNqrFG)x zzLa}U%KS{rUcHX^eqtqBKk?k_(wEJ}7sIcRXPryygR=%kYHtVn-?%p|{k*%fO=-vT zvp!p~r|XZ|xCuXN=qz>}S{MGOP{Ds1{)B7!FT*|TMv&hc^0%5i|8;uqBlogfYH#+M zTiMySp7q=|{MK;2oICLZIY)?X%Wfdvo_%AfIIFG^`966+WZ&5dHWDA(v-o$bSMiTiK5PAPd6}PmA#3bW$XeYWXFT)2oSi3^i+Q#X z=UjXN51}_z$Cmg$eq%fee^x5y?`>i^8wc>Szs$v#z&en3xEQ{KPW)*&{d-AeaW7w= zpLgocuLW5m#o}2p4re&`7mHPtyI{vM= zJ7lb1ruSs0jaiR}!gv41^4r4{_I~o7g5~UO)zuO|SK-SO>f{oUD!k^z5C3>E9{ejNei$dvFi_BFMfqjXs`sso3`X^>Mvw zlM87Jik!2`%j&r7bV<7b^1? zZC^Q=r@4z=$Im@HeL5XyZ?8>%80Y^^^=5n*J^h(ytq0rl50KM^-IhJ5+OuFzMRDei z@ZQPK?<`YkY0p{wtg*o`hMsX)$j(_b2Y*}E7P%X;kA?M=$)5HFY$5k8+T*Yz{im|` zq2Gbm5_`3(xL0M(^ro+bOW4Qbn{m#$!|~9P`!BAC=hD`~tNG*D*|XN=AEUl+@lViN zY;&l=PXFcJo%eh`^|2Ru&wgSb&<>&P1j)_1pDKO}Zn(VYpEs4inU=fr_Z9X{u?rz{ zb}QOBy^3=$-|O4)KcYY7_q{)q+ku~ZOV09)@zXeCm$@8Wdfdgg6M z_U7`^2g_;ed0q{5RnaSP=F$lI0=Y}@EI7NOSo@P`=gLcN-fgD${~O<-Oda*~l6Sj# zc$#?bg?HlKIQ!9V>;u>};1qeMde()?^`(!c)rGn8KIS)PcVf@Mr@`~=U82ix%g$V$ z&OQTf6u(rggeR&u^W-A-0?1yPK5L9m!ZqPs@%25sHQs|>MVpo~w6pkI@gK&$@UQga z*(2E}Ll5}xvf{V)T2+2q^SoC2AbLA^44S~z@T0N^I%GL)eddUhcKYT?g`<%(1C*4q=aj{rQ_NFV2Rnfqguy5xpVI z68sU3>uETF#YtvREV7M(z-t zzYYB+_d@aPxohO_%g(<6n{Za1eI%_jY$E4+YRH&hjBCOXaA>9ZN?V_u^|3pDBWMecLFUIrp8t+E4y`ty@FLts zJKn{ae>+y%&vEwLlK3R}gZ3D12zA8rtc+J(&-#TnfqmDqqK&UrXq)_|avl-OxBX%K zHgFUz>tzMB)xMpT+l757dyMC8Q|Ub5=Zs0;XCKHoX02o#-)6rL*`v?q=eb#D`FqOo z_&T_$(s>Wf;Trxj?=}RFR9D9PO?LV&XHwSA+w9DrtlO-MhqZUPSo-BU+J*4D=e&eZ zrFHhsv9Ez<+I$eSMC1by{|@>bxJNAOav{Awe<9>fF$>p#5tZg%sW@Y6(ua7DAL47@6a3WszwzT|{xrcm z&_0m!CqL)!2Y4Dh0=Fu^ywW^|bNK21%*T1^Y7Yb90cC!s)q{H>^Sl|p4WEQp%UQ+V zvr;|m4L$P~c5=4DM=v)<^f~OO@qInropAP+^yPW@T>b>{^!3Kha zLd&^47!IoTZa8&kkIZ;YHx8LshwNL7AF|`$!_J){_rBfuxp!S+j9an? zLiU36du_-(&i&&hya8m59gc_6&V)DB^&IURT208>e@nR?#Rkxi#@YXCtN&~MNcK^1 z2>XNT;(oA@J&QILUyLuq>xn&6cGuu%9z2TQ^Q?RmeMw&i@6tD;uYg+0bDW=VjhzZMI{NklO)w;Lnuv4DQN*4qpr(vpXp_TrBH# z1?>iC$X@6jZ^cXaePF4&R?6Fs-Hu*esZDr6a@d)Z8P`*)i}^Z>{x^IDjcDsb?jl>u z`3PUm-;RA}Nq-sB1?I=)^ml0&iao{7{rp$)R?wC<7V_P+pSf_2_N@_5ztwbKm@Vgh zb{E=4l|`SQ#(ztG+MK;E>pSf@o1OhP>-1B&i+=}~1U zUmnCmV1hPpDpuD1M)DsMOFg-_zYE*JMazq`@O<`cxw#9yp-l_f{b3P1WB#K$9xl7T zvRlyqH~#FWU=e?fys_;0@QS*=kbfZkX!U1JwV(~>?+I6`=NY(@-3IOvTU_ZJ_UwFn zt;e24%h*h2e*;hQOVxgZff9WP?gfvtAMot|R#^9y-c{aNv?JMH(6^U!p4bptH+CoJ zLa&l{6ni0k0?wHHA@4SJzE`uZw`MnhgW*8_Uvf_nJCpw)^r7vHGw)ik?}YQ^%*8o} z55m*%HR3(-2Kr({oOAYWv8<=enQ<@}G6!CTyV;xQizh3K-&!`4pK)Hm&mNh1o4GMk zUi$EUcHSlDLE81P_;a)+cr`wjc43vV63<-8vuDEV?Cs@bj?ZK-g}b0;r9Ft2d9ph{ zchT$FH`4djt_Ec>`WsN2_8;iMZX&NE`yt5vGh=YHa^JIiia#0~Pp7{E_poy}$l7nf z&v?$2dm8OTTGr^%^yYY!*#5KwU@Lu*b#f?f0=Kenq2DaGiQFS;dESPwn4U8udvP;< z&dQeT|FAcPOU3fOW7t!x?2)*dpE*Ae#)!|MWo`C_%jMjMv))IG?+?GRS3}y{6dtDK zSs9EN@qw^7COHJ#*?(m}gBit}gDYZTTg6S=XP^ z^V?_6l$QK|#4dtb=|BEy@(#m0JY3Apr4{C&x^JLm-sfyPRQ|JgYur;^yW{)Cvz9ZK z;dXL&gXid(3pvLo!fmiCq-?b|bgnMGAM;)-EU75hyaU&LMcSBM=4t-bGFwAX1HE0evfHUDpzCZ0S0 z3U(WL*-NvBeM$RJENdbC@ql>7WhcmeGh^SBoqI-moHJoI`(s#Nypeca`n$L*{O>vV zunOa+?ib(^{vOr(UfIv++t7yc8^JWO?QnjhYlbg|skA5Y!>}9dz<*IZdr!U>Drkp$ zb~Aq3cO3MBD)ny!2g3}0?`prnDpwCTqYYhVOz}tje_(c{apbqgt@&S<%w?SZ975}l z-|)PQML($9yI7m!>Bq=@46<)$Ok2S1v9+sSWpM}il;547cRq)9EsWHjd2+9Y1>(o? z`_Q*#FM_G`KILMM+Z5+L9)R@MweSKv{qu^vjN$X@u8Ciy9RbVKcd=)t?~a9+;BYyA z$Z3S%;h)0JTs;NOWj~EyHlNn&Rs0T+aeIlDy{|h?+cH*j`Pomu65p7AmFFBm`>|Zy z-)kzL`T8Pc&8#hF6TB<#OB;r3%Y6tMDElBz&ew20WUpI{*RL$@CNGOmp^f9OqF3TO z@lVi+{Qy1ydZ?=w&ieR8eq(n2mb`}DwbEL_o0N<5uv=o{c|m8&PW5&lc89Xro1vlp39JL0@++L6Bc1vkgtaDJDajQ_xySLv4{amHpo zJMW)y-51)zCE-eagzph+FK0)7A3U3XFZ)(?4Z!J_LF`B6HrA$taevyk^3D{$pFaWr z0H3jIR_I^Yi$6!X-uMu>-8&2ruOs#vWN%(ueHY{GziI!{e{n5;f@kDjuzh7QpFgC{ zp^f3^H__Mm*_YZWvnkw3KN@a=v-vs0@|)0lp1U=_hr0g88Q&`*bG)AVu98;|T0K&% zi|k=p8#&7}FLT$avAnn^JW|P~ZKG@z{SSWTTVLE*{!Baz7Kr6{^~|wd`S-v@?DY3v zV(EuZ<$p)d-$U1vTU$LjJ9eQx33<=-b?%Wf<^G4ghPEC%ceAYN{1%k=uE~Bz9c{Jq zB=x<{??7LKGbb}olJ_!<;&z*gZ+lQ25__ZYxr~8=6D0h zzEqpP6&wQ>K;CZ@ehhcUneUlztJ#C_Pvzo!XFB^@v7FttwBriArJM`tuRwo(p4XB+ z6TaqW-+C11+{###*m;lp>1(lnW;f_noTWL>C-WaL?(c}7r%XHASa#a|CHoL{WN*5L zmVWO?&poy-y&n5feOU=B#k2n2#W@S|zE8n?_CMlz*FLb2HbePd_~WwgEojTX0QO{0 zg1TZE@BF>w++M|9yJ22Ri8he-L98$ zXJ@~CLGCSLgXqu8y;fT?NAholSK-CtJ>+hy-r@X3axcaU=+8p#!}(opJo_zm+#=Q) z?|_e1?hg7%(43vNbQ8OqeYJWTdq~dFOd^$9TX>gIcE9IvA6?iRJAHLxqA+{a+ zKz7cRjoFu1TEFT&fZc+fHl**b;@6XxxsvtK9?teYZ;5{bQ}}tOk=~<)dRMbEw|9dI zv1)amk26b+fCTBHVb;hmB`bqiK;+yv>&f+QbtcPu3 z0Nli1PkeXwdGa%#-^SUm7Au=~83(oG<@`MuwiLUamh*fXtVL;-hGtJTL#h zjGD3U6U#pNJ6y`H<6RD8A`tnroa}EwUFOPazE-&UF-?>va`0P zK~LIh?R**X-t)v}urGpj*m;-t;Q_IH=jK`I>xIhhLHn4W@y@smQ)XSU{GI7n{;}d& zw0g-6+=Y1wNJzztv}`#IdKy7)a}82blWcf7i6oYdWpeK4%gPhZ|k zOU|Fw_Aq_cp8X4)&YvNE68;Q6XRm7w9;005Yu=$XeF}ebyaw*2hLUTuHfs?5E`ID>hZm9Gvgw zPig6olc6uI1D;Lmxm-W6H-yan5n|ow8`0P0*Wjm(Z&cgY;T^HO>kG7uYucWDZ(Xt6 zeXhlsTPKP|>jL?=XrJ-tv2S&D_7KlIrT^Z7>@_#>50jHK;V)>#Pub+|gX`0F#qZ;* z@uu{L*dy541mj-SKn*9Vl_R$iW;d2k1N(_RJNO*>WIaN6x+ z_4ww7&A2LkD&}TK1}(TZeo1oHZxWv$t&_R^fRsDxdu9 z2{+)(!xQC<$6Mm8`Frq@_*JVe&F=R37J0h5_}!@*f93bVp&rY*!4Us{Wy@HIhpl;BToNkpEy8nOMb>M`|IuU z_r#|``f(Ai33aQBa@iX)&fD|XheoC1TOi-aO{|TK>8bGFO6yFUjxq)}v(KhwT;El8 zp)!Zz`m}oDqm&)S&z_(A*q!`(FkAjTw4q|Tzg6Kw?8R@hCn~!$%2k#r(o7FLiop0m+&@+~&;QarZ z9YoLAca`%E?S1yk_%xh;-np_kn`f5nm+W=nZn>L@eZy{qYv9M=ZuVIG2z(*`NA+YK z--hRi-KE?b_EPo$IVZzM>RY(x6|ajRaNwNOMcew0PopQ`~iQb7)GlF!{lU*r{71)*{Ezj(g#6vS}R(wfBhO_ zcfnl#t;)QOGfqvRbG5Te>>m6kbTFpp;nQi^pU$f^Mlc$h&@Y5-*qMjj;BM_W8fS06 zlRi}Y^Q;SKTk$i8cY#rm=eFuq^l|R?O<{#+q#dbifA;6F4u2T`Eqt@OPGD!x`3FAa zZ_Ll%&9fdJVh>U#<8v^-3SYv{+Utyum3Iicf!Ic3hv9vpF6<2*<>YtQ?(ApSUqQj93Hc^V-HV|=Kj&IkeYtCOv9^Dp<=<`% z#cgRPi$BB8yuAhv=8uF9?3Hj%skpCvil5_eUs>$Uw`y1BR@Qy?s@X6e=D@wQpP*E! zeK>dK8Tcq|yG#6B&z~%owQ!wS+BqE7hX(R0an8E=)pGHc_th}?>+4sMKoP}xE zNSrl37j{za5xj(U7`wmm-jkpCleuy~?K^GCZ|-@w%!6}q=0Tp9v*9c`*+WjG<$W^G zPNAI%l>^?c2KsaNrL>80#paJPD% zQBO^J#-PkUSbam-gTymdUDUT9uFZdp-2~T>_mVnK?<0G&k6^E*&h6M) zSJ&ZR<+r6(vQLA};mbA}{~7KJZ_)mX*Ts+FPu+>fu=5+v0=y4xFTB6;c^vPi>{IH@zLE7c z3I@<8X;0=)euK*XwJGg!{50(#NS|#AovVxQne*@fdfv4QeE?jfOwNN*?3>~HYIi5? z*arV;KE5oz2mM+64ZQ_DxqtIVl&x81GX`(apQmMh=3TF&pNg~Avlhm}cl_)Lo3L}A zX@`&H-wD&$%hZ=O*p$BkDT(6F(0=oDXyoA_%!i_ zICbQ#{jIF{+w*w#;k0M@dG9JX?{*&EQvM`b_LB>6-t%5{ujgmpzbfZN{1q+jUn#yB z>ad$aXL!_mE~2FmM(}&f{gL08{}X=-Y%a66@~5%q!guUrX!-Nl2j%B}eW!QKT&e-P zLuGysIh9L_ca@>+4Z~KggNrI zmy`AUg`Bsr{tXZ`dnQNO&4*<;xE z;M~osY4>u^ZR8o*Q+|_kBTn5d@qw^V{2BFRT{Y!rUe+in{w|&G#bsg>VMki##ZcT6 zuNJ!nXWnQ1wqWT&({H(7JR+X{9SGa9-^cUtX7xQ)D4ubpa&yMshwr1!V`qL<(AMidj_tIx+vA>4>;U;Tv7;X;tct+;f``SNw zt#vNH6aO7`Jz7#cOY`qe^ZZHtmGoY?8JtmWKY(M|`@oZ8PpdCyPYb+&wvhIcdS7Gj z4%Oi1@?viC`tY+}wt}ACeUkj=+51BSvCrjXEYIQBgslC8__+sMBHo6*8t;KmQvO7G z?)a(A4|Pd}f>eiibKT$4YW-dUR(vKPV~VmYV!l(`d#rTmYyPWS>i zRc;-z%)JKUn`m=k0)HCBUN628?^j;@hB^j!G`9We9qAwNyTMVOlXD|;^CYpXmp%D+ zvrnddhqvKhSyH@*J;l%bITWg`b5Fsi!Av;=%AKF^ZK-pY|FqoJ_(Abw@MU6aXlZ}m z`8QfS{4%}^XZ|%}PtLc4oY5u4JuL52DmE8pi&v3*C8T|&{D;NcvTuX4l82kXSg{M? zCieOCLrScDJRP4zdjLNo{t3*XXAWcx2f`5c-MGD+t8p{$n*HM~{ulhtc(V6gz^<$h z>xm7Z)#T^5nUQz~jG^alJ5wxwr!41M`slywrQm(l(II&-4#vVK#_2fxfp{x?4&+WS zQf}IQka{NLCUBYh)0eyBh5YyB&0_CO%lyiI_Xd3&96LddG*vzM^jhLm~5O5$z)J z+7SP!#AulVzrllL#W_^Mevf@MtSE7(m6x)2Knr~^8v2Q)p0xE^yh<#;%{S!do}2s0 z9NIK_*@sS7CUfL+{yy?c*(bB>u%q3DAC)DB5YX$?V$fJlK+ghovyE+!;jMspyf=iS%8F;w1o|2LxnkKLUS%Ii>&K8$?^VJXpW=DYOI=s-vuC|7XK%>< zcrQP5=^1k^|EtJu1^MR7zlGV0eIwig`8ReC;mr>h@BU}8GrtZGmly9>xnuXXSA16L zcVt>$vDCk#XJubq3oXm-!SJlyy>R-!qWoQP_M+Cb&v5$tbGQs9@=v8_f7sBqIF{$| z*YS@M%l?-2G+(<%({p#Z5GK=Z(e5vB&V{^7zHQcub!YDfmnxt47+f&d*`Jl0`=0TL zoIlv7&?|}W#Lj)Bli1a?{JynN&SmPGC6@2UvEpr#3oU4;L0$Gtd=LIf{{3?D=h%Pw zE`LEiHN>(fZ-P=e`Au}Xctd)yKXey+1F{~nXT4aajgZ{U;(ODdDRGYTkHbgM&c}mr z)K>=-X_wvpYX%pFFJ@Yd9V7Qw03v_@I$ou8o znk4^dhqE77>RR0YKc>9^i{Pk| zVmlx3{0;nnO8oZTwct3jH;VOFW4%x?g;&=7q zHtf&kJkIZo&*lG3|4m;Vqf9yNO?Z%hEB^qo>-hOrI6&^@?Cr(w#(zNW0gsk>FS$3s z*X-H$kc{E2?CdjB=vl)@D4#j9EnY$IiZdQLx3kvA%h?Ps!5dJu%)W>>tu4-nfBDBi z_LK&Yy>=nLJ3DjrM1KD4W-)&~jO2fA{$(vrVW+QFmKE=2|FTE1-=aUk9$H@fhFZzb zyJY-t_MYFXFMHH>#yxxBU66hKG|32B4{3ktQo>`^F!^ASCx$8YEmih6Hc-G(%{LJY-kbdqc|7^%NRJ6tH*0_$?sgN@1^C8d;@*ZR0 z3T^+Y%sx%addYk_iM_ShaCY{RaqPor`3<#H?gY34@*8DMInS`K#7`GsZE?IuD3jkY z?vc}k-k;wLkCS%@JL5E7tPOuSo}rGRICsK_;4SDNmUH0)l!x63;ecafKUYPy`nO4zAyHeL=(+0$T++!k`OXEnyz zWAN5<(!TB$ZYk2}!Q-hs;H`)@u!f46usdkXzI{xD^-E{8(aY&G>n@6Dfw z_oe0irm}BVu7!A>`5)SSw6kzsxi#>`{H(t&>@9HS+WycGZF*Q~E8|MLg%KcT0M{nc}k?}~1;j7iq&PI9N=wPI`8 zui-qut-I7BIUn)UmP3_!2+m?ZMSl$5k=Iq3>tRl5@%+g8+`@hHdHjU<-}of4h4@1L zzp%4<9_KgluGN)YN9)1Az&pj?PP{GNNV^)Ava_Z~m1(2cYdHNqP+MMPZ_jQZrz*6B ziO}7?HMXR9HceOdBwF6Pq5Sd6rQNF_^CRmqRA7{b{Yk}DvRB%?(5l?ir;`U4%sso;BJt$SxeqfXesvzTFZj>gzOEOANezfh3wPV zo8kMiVn6(bzl3G#y9tjh7%%mm$xpd+@gzK0tTx?S0vMzDCRZO}&|~ znTwf|qiOBsR z@jI!fnON4vO!$bt6dzIU?j$ysy$d^MM1CimDt?waCgQVcsk;`tF)jN-*6UXIPX3H? z_ci`5{7basSlX>{H|#~rv)003d6`qMK-O*_v7C8{Wv=92w7r}>a{~JY7$?3v9%G)Y z5bMIPBAzv#|BmY3V#DyO;wRIO*6-QVzGA;CwhwHnOk13J`Fy$k2_B<;hsOr&M)>3s za|IF~r~D=OT6MRgE$2V%-4@{s=(&GBM_&P(*qKAQudXk(e#M_vt_wfk5SiDF#b2Pk zC-ysSBQ1TJwXiiU^YS+DZ~}jv_{n%bcpH8y6wjYqm7gqcq5N~`7t%8q(w@8V`C=K1 z-?|jP3GT&n`KRK0a5r2N|ADjjt$~-Ll@$9c{nJ8xO=AMT*=g@X!r~j|uU#pH9w5-LP9a)Dz!!!KsY2(@T@ryY9vMYW;>u{LtIVxIw(AnWE6ygzi2w*fz@o-+#00siy2qnr^?L97`q`_Tpb?5RBx{s8Yy-(K0=HM6$bLH6TC;Xhqg+#@qz3;eP4oGU-6Cu5l3 z1^?z}Uhg6P3BNNw1@@x91-AtSeI#ch9Hs0SobTpV^sK8YC1Uc@j-@#JS>}0locGyUsLFP%JhevoyAu3hr@BON<8D6x!YMz z=4&5V%FkZD4J0RL#k1@o>}}MOKbKm}pGId&oK8yN|(d@SD-oZ)cUNPu&&R)nF6- zRNNF&SDrmie%9N)a_h5mj-5_F39<&Wehc(!{Kw=}ruAme4%m6mO7NY$#-&r{9FT(nsT_?v}^Vo={iDF>|k_*aF&5?6ji+?x_3?IP?4gvCNU1#OAS^(r@5r zp8midDyKRO;-`;Cvzv7>{`h10nN!&pa<(1Lt_R)udl{>b)%mPC_JmrrX}FvAzoLBh z>9%r{|An$0=;@!%{LGEa-L8`#~a03 z<4y8QX)XE3^6$cz!?x_~`D4UB)TUAL7VvxF9%A)j3wZW<}On%?K@GKt>vWe`myWKnz3^xmavmEm^}lohOEPjU=x(8?>E|A z_!<5n+(BLEl^5?gJCqgo%ZyLTx93+EKN)|+zmUEsd zb-B3<=kJ4ki5v6xf`#%ke_O$)^3%6zd&a&2?IgKxt9z`@;;Me(0;Uu8Cw z74NIF_@C1|LS_1s^yTm^y&>d#Kl{rxT0`-y^WFHjLI>I-p7Rp>8F(DJ!4+lNNpFp_ z_hfAhQ7&ipU-bKoX%BvHxvxRmwIxnlvo`WQd7v`a!CLmQWyNPN#y3Lx^$hr&owZX> z`RZ^pEK~kWvFs@&{Or-I}!%8K{a zoE^2zts(sVMjYTw!4Hb(OsvJey4-xn*(0wh zE1m}#lg;AS!esW|Fjkw7lDC*v6JG@dxPzau%eu}!c4C=3Kcp>LcNeh7(yxGO-eoI! zce1mWl;Xx^#r^AH_8Rsn%B5ZZu&dH8mUlhu0af4$*sjE0sIJX;XE}GseNFr3uy2Ou za?Ta2$lexr;WvW?kU5>Tej8-|RDiwNsk2o%SNu7g{+OfAoA~LwcDRw;_Ou23%&#Na zZ_#VX&3w!_W)9`uucXZuOaJzeH{bYtK>v|;G5!N@flpB;<(5G1QRBK8qw?Yzk@Znu z+2}b}{(JwSWuIHd&-nI)L*N1E|Gzq36;C^|riYak-}Ow|0CpES|KHYp+di$_T(RwN z#&e7_^=JMJc}LSeQYQU!jMxmE@oPofl7AeW%Fg>{uR9fIAAdu=nHP_VU4W-R_Tp*f z#r1zY`*Qh@Lk-${;=fws&(W>yX#|?)Utc#4* zj$#?taq8$#-H=wHw-6w7-gp81`oa}RmLY2(Fqlb?CAg1?HN z^M5`+`(fT=PrRD;D5Q;Dluw$A-7HR^2ddD(~S@C*FMAbmVXnY-nl%YGRq@~1%h zY9YNoZ3d+7-;~L|)*Q0$WKF+F&-&U|Zr1#D^gBHFD*O}uxYFW%GkfG#P!lpIpTi%) zGcXrs!UymcEGjGB>+bcOz4$wOPUgx9>^If7TK)OGF!#joXgRM>XJ;?XnDl_@{M;uW z#95nV^t9n@$T|6#^Y(RIsjRpkul4-hl@zoZOFx@w5@PGTL;2rrNimk_~>sMNG#<2&$0w@!Iu*5vXy~I0-U(3&2&pFmX&T_FE z>4(9ekUtw(2U%zTdXLL#SqnLPN+EZ`MR-d{ACAE3^D3}LogbKQJJ9|kZxpQsdv9D# zY>|3%9`26+pgq9ObL+_YLVy3vZq2Uix%K6I%U{FK9+5RYv)o>w%mFZkoxNn8+#&p& z!Q)^D_A-2i{HLJ`e~`8;VCS4jTNgwAdp%iSlM2Oq&O5Z6fqCDo|HtTA4_SZR@hLcC z)tml|vb(Sw(=*;T;HmU|X$|>#$4#_vAp7}X*et#m9^t)Cz&n3BV8Fm)?&b?vRlHz(DE_OVvhrG(PW7*STn%om`&YJ9_*>AGWH^?6dxf6b(%$4*p zkolkQnmKq1f2iEP_(eHcuRZvq`M2YoC0*E=AGdm!Blwl!81?RlbC+L=mzNn+W$$IT z43w$PpG&_AKSb+Co37kz>J$lsUUm)#3G%SoHE4t9pT&j|5Lv^Dijfr?#=bM!Uw2eosBc;??QvE{V3 za5ld+u0yMYvma)E%lyjo^PYX^gK2g7)#c|sj$vrEqt%O>%^Ojjl-AAs{;E$>K!G1q`aE^LP>Glat~Wf-<>v#cBs6Du#3FEIu*Z@ zSBux@p9bmI`f_iCljXnXImgoXgX}H2o2_G~kJD%4@u2eJ?;orAEujU!A-$|n9JA)? zn9SY@-iFKgmy{LzsiwUA?*RVGVo%{mY1gRZMgEoilRa~>G8wP`)=ke`-;MSd&KRDn z%mbbEmG?cD_Avhod2cJb_J8pm#WJTJIVYZAw-bLAX0UIDhqQGdzo|MV%DL4U z)bnBQ$KOl2-gpqcRZjNPj9+b9_Ms)puf_XYf3us{EIIJ{6? z4`(QMI%Kb#Bd;g>XnB9Lr?PXdE8YF~l$?{)m#p|ejuNP9NrX;Z|S>kDM9eBWP*kuBijEu7BibE^funI@+?NxDW3lei8f{UHPo<)?$y~+}TdY zui-iRJ7;9pKsWgrvuDJzX49U2IA_9ab#!MRFK@6pSwkDM#x}`WqK%+u6!j=aq4 zHDc+n?d1L}XCiwF?HRFpkg=*tJHNcR{!{le?7aU^Vwr5yke#)d z@f*P37AnfieA$IolYbP>|6bSjN7dO|Y#r2sG2)r;jo<}-*6T0w4~1&95wyFN*@vEa zKb4>HZbHksc?0_tv2(<7Z~9n#dt4uRgdB@uPjMI3?`m6vQHH`0^RZzQ9HhR~Ga0fMWUNzv_V?4+!*C7!TNnEdJA1}g?99#V*E@*4 zL!S-xY3aLD=}*92o_7QPO*mSt0pu>WmOo7$v+&pa)Sr2>hW|Z&0RCh@j8{S{xKjSB zw4B?Y@_&KUnf44*{vT+?K1^N(bvEVK!O!ydp;zZ0hO>6^TTc4*8pwSj=WG5yVP_l5 zyI_L47uy>?6u(&=1NrmiEyh18yDKbUXC5}D{|+PgsjsoRX0X%dZ~2oU_n9GbHY+!a zeiC~bEas<=d(ifR6XfPO+raDmm-r1Iv6pI7XZFP<#ov*R72lG51RkpHC&lvpxEufZ zb@oR5w(^tYq@Kz+{r(pFJv@QG8|+zL+#@P0U!hBJ|GtNvJ@+i}e6!|V@-0${Hk^Gk z{XLxhrv*%)cf!@_yW;!s1nnJ4&pWl{pG|Mh|580UTmInZ9p>XE{O{Hlf9JY`|8Snw z#dFw`mHoS{IIr?MR@T>4||Y6WWO(#eQg=- z7v*yX9Y$*c`EPxtKZdAp7_J~TO5XPDm)VcW$=vx?Jah6y`2*P3u|LHl<>Y_ak8h-{ zg3OfzmH7{BqSwKv(7t1D%ia#Mu8&uKi!Q}+?afa=<{Zh|%K4o$KHpNeDAxj~59)}o zg7d`kzFpXv4==ILl2?GN!J0VVq}d;C=dWj1W&h5;7mideXGGpJ{~d`(aTCaTtRwGo zIVVEqdmCCmu{U8T{{cKo&Y}2zIXB?UkILoFT+jJJtQqYAyuG^f`_@wN?DxxPEyXT_ zC;2z>GoNmiGXiJ7NIM3KOxkoSZAzEocT63z-}yHvleKw_oQ7~4yIR2*v2%8p zva`nyfaA;U;n0nJ7CuPq0rj23zmUD2Jx!gN%W3o7^vw6m6S+U>ditXdvxmUdhi)T&bjLco|TDf_rt8i18$esk*le(7p4MaV8?d88)?9O?+Ix_E? zi?x9FV7_uQX+!W5e&w>_cpkvco&5xQFUWI0QrGS{W7(FL^SBmHn@?sx$o>R>!JmSw z@u%SnVQ=N$q>o~Mhcow|(VngF2z+!&u|GfbjO=9@=e$Q#xqGlP9_^rSN%4%nf!&>* z{Wy1`+wtwR@%TV|zu2~Lyx5cKU*TPHu4TSP&snxN{<2^^an?o^{EysS%ZuNVSs&Tc z>(QRasefzQ4X|B_=cxM_Xe8cU>?cT{pDg|wJ@e@m$lT7EH4wJre+<{d2>IE+@(p}C ze?Rq1fpS_uxjBQ{%6Uhu8!hKUzMa;f7F+|k9nhk5LK#XiE9iSNwL zdOU)CD{SUJ3TfYua>}6t45D2^TUuWHMmvR;b(wMBl6ET|3ilWM4h%1>D{8I8(!RfF zi}BX*p*G(G{lv2GbizI9$vq9Pp!I@(3%>WYEoV(#e&%UCoVhYa`FZ@j&#gG+_QaRU zT?*~wb>R=EXHM)Pe>y+kWuLLf(&o!M3wDLCnHoxv0aLDY&Pu_ zI0i0;8{sZ>m8f^Ovf?*idw%w*oI5*0J9-VAy=b%jZn!d=AaiPpSmxeFycawOY4622 zXJ+Qq@$50|X87EKTzg`F`tRDb4qp3T-T}1id*{e&>m9Q0U!-N<+sv*ZHo}}AVxHG# zub^Eamc1+M;I)ErQ(q_X%(rvd2ho?Ym%yoVtKmDMDU&lK{g8RGmc1134CjdD-FFZ> zL>*V+o{%wp7w-bo(g+xGE?{i*{#GD@GIjE zv<9?GaMo+ab*$%36|drbvqxr~z2sS0`#tD`Y0Jxsb9VuLo8OwgL_A|$M}Bkqr*H

oT-b?$lTg$5f8^j(`M>Tob|8nl8f9`{(kp1&V+C+H``PpwCf+zVm z%I~Ob`eP7#IG&I1#`nlMSWe!*l(rbJf=y+`Z-D*9b9VF2`mgL$mED6s1g2`k%Q(-SKyPL(H({Si%N&{nU-I(}u!nfo(01$=CB-{k z#(snNKlHTkIQ~xTtn(E(YvBxfo<9e_txu-mimLQ7xeoc)4*lh|ZA&N=|9kKu><`t%k1Z?4v!?QX8@1tc zTIS$b?aE#-6lTI8@%7P_o54^0Eg|hYn*ObYCzwc)*A+^uP$w07)S4n zKTzg;`Ud%z(!S?Ul{-P~5!y4fjgWTcE?rk{awbCh=n;PQ)~@m!;4-Mn-c^3~qO85F z$v>5?Oh1ACDg7$iTkHaD5scRYB{Jqc9*x6qWmQzqR=* z&1L!?hQg2htcS+XTkheIod1rQdLCmRqn_)au~^nXd%30f26);t+tX7=TYm13*;Cr_ zhvO~9@^0n)y!%{QN66mNn4Y$^ft%pwvf^*Y%f)8!cfz;e>}QqO-QXqmQ;@p<#QFCd zec9t7`^)M0IDIgQeZIaqQ7}PrxU@BK2-ie~DNv z{%JVBFK3P?cP;;A^C-XRv{C15+L`Rvl^YEimxINhprsGi({j(t@5i^Yv%hZ#xsU&c zp7(AJbK!RJte1vI&mo5;?y`z^lePk+rkG5JoS_7y*oJw*Q2c$7AD zU}ybgt}cPG^siu{+ zy7J@czd`oLLF%Z^&YF1&=esInm^qYvwFZ9|d^P`5_y}?qCqMiA;4*Vv9eK|OU;u5o zoV?#R{H??u<+ox_hW702E1krD8BCw^wk+S_8Z0{GcEgiez&<2w&w3G7M`oz6!tgv$xZC;;_u+|@SDbBKRFkO*HnHbeHr}7 z&lsd_oA5-qx#0eQ-`AH}dpY~3mGWt=9xxmVK`cJOG!|cEnZ6iobzv&wf_^611hiszlpAeid-{cI#X=Bc})%Ei(@6-DtzuZ?ZF1<8a0_6EaM%u#m+^m=-OBs?g7cgn@-tudV85(f`fw0#K);!O z2xK0tqUUb6A6`hyU8@bA3MbN!!`Vku<`%dgUWIL;m)w&I)|`5?{`O>_N9&F=w#&6A z&&{~+&94i&S2XA6JkOY~5uZ-G6@G#rXzSItq-*iJt+)95FkWmXo{ukfzK)Yu2G{b_ zpL?ipEA@WKt^@;NCA?iIuG@_NQ|i4(nZxCN#h=N4T+WXC7RpY<{pceg`@>MknZ1Ia z^_{zF=2x|n;=Gxm{5+V-UrK)l@{XtRQ_n!i*^$26f}Xu1{ZkC!sQ8n# zUto90yv@7T5<8xsamknsfIm7H%N|Y}Aa(%05;s(D_R|Rk;|1xz+R6`+e>eLp@e^?N z`X1_8i(iuWjdo>U-h-C;aJlh4L78pkHxL^nCu4Yu_z>~5cb$5=uv2eM@mXRSr^=Au zT8H9_<@!zRRCq=`qvaj}H}SK#?#rITz6USEm&;#|tMj|C7qZ{Q_u*I7`7u4usLd|0 z^Um1=vX{JFDE3v(za?@`!s(x^&4Wsd?aVzYdrIbK=1cxRC~ELiSN6B8hxGkk?K{($F|F9>)II)%b1+0Ak8T8Aq$lieD!E^|+LSKd4Pthq_$+DTu-&KT6^pCc!IkoT#md>@>>Y@Ya4 z+LSq)J>XJ)?hsEI-;M0<>qV@L})*y#{@=dLN>s&_*H|`>o@!S-pbwXS=s9b$k`L$r9T_cQ%BB;OG=7&lzaKN z!*lA)nEnK-3&pkaBl{-xwZ)zBTxCX!HRQjFb1(S-PlVHzKR~^gvvVIgjy;7v1o93! zdj`;Qf7>0lfj8t`t6aW+&y6l`V0rQ0Qjebf^cOiR=^4{CaI@SsVj0JrTgS@JUYm9K znRo~B%cm)2PmT{PcvraeB{(Fw`M?$_`rqRBJ zXZhF2IR)o#UP2qi-iq>A7R@PWao7#k@HZ$WE=)AsCN_o_eZEB!x&p7*Xs-<&i@9)iS zO1p@iwSJqNqj1jdl$|EN;(uI~-Co|QIA`uEZJ(r%=8Nylucu7*g=%=XoMF(6p1w`L zzAZKcvUa!doWt<%>idcPPV&X_-#@D)H#tM;sUz*JBX%=n+_w;WfYx6;eV1_=f!`F% z_eU4@X!dn@rr5viH)2E9Zq{A1^5R`!x!9LoisLn2EMq$u+9YpS@_g@AhWz z3YmMIA?v*n|8ueQUpsyYF2F;|W*!~PK8c-iSZ_?T?|cba*O@mBVS#v%y{(d*tiwg{ zG;J%~2QGyM;-Aw_gWNIlJH=}4FXLy-lQ$H0g-L>5PN$&QLxpNjACtkpP#nPYK^3(QqaxyOZ=xMeOZ)$S zjyI!k%|90I>{2}69%N6IH(YEtcK*L!`mr;2R^q#8>7P^e{cm^|`XB7vL4M(12#uhn z`f?{&%wAF`o^co8+VZC0^v59Oa>ks+&pF)|FebRh8Sxa|hBUvcIBTDDOS~bl6_`2Ka3H0(f7W^6&p&WUqkC&!KXD zu`i^Ln$u<}-+`9BCu{IY_!O$B|8e{k&KPIiZw)nR8L!NP#rQpF$9~g0WZzC7yrs_E zv$pm8FZd5b`tE(X)7Wp~m*8k+2H?!wD$r4^3OrBCUNuaa{I{PnXD6_K(C*BG+OVLj z`tUVkwZ#r*XI785u`>KG{s7W9U11G8R95`EbXh|eLiWU+XpQ8r($;(@ z*7Kaqv3H>^Z5*vSoDHqTZou8)0nh$a*=^xY*g<~Ub`(AP^mul1-@qmKTk)#+Z~Up8 z7Ujh=VIsc?Oz^DiVUMu~iB&2yPPhjQmj9V}`r`q0=UuX=_QoeczCT9d{b}{s-?7hO z4^wU)d%iL`lhV$t>%7~}Vwpo9`)xPt?o~O5)7R2=R!2qrIDKEw%x_~E|J>1LLHZ~6 zj*rE=LcUA-Lj(GD^sQ;nIU90@&Q{NQ`3Al}Y2PBsH-&vXW>?dRS^P!^r>^pCZwG+!6{t6C)6UDmH zW|kIz6MKpFRGGVgXMe3<{$^(nIZFH{u~XPt*TeNg*5zA}_vuedKWz!=|2y$^IA=im z^j!8ua(}~Tt1I`l%=7xRj9)L$>Lm9G@#(Zaknyd^??=y_YASnY$a8YHcoedy|BRpP zVjs|^PVz35pFMP%SYKS3)|z%de~P@<;XJwZ#qx}v^z7R?uSe20@pGm=Q7VuBBV-Jh z&@)!a-8pf7&ig*lQBGCMZ2U-K?3|lb`RB{q1%6^@-prt9&F?Aa z8(RK7(pq{u`b63q_679bkoUid-$zd7%DKvAPCYK~Tl(qzoC%fLcXTng#Lk4A*Zt*W zkIMJma2NoYNAJ)w7iP1k!=?IYBtLT~`$+bro#FCw&!n}c<=sD4{}KG`rIl!RLuKE> zQ`C1byMo`ZZ&?{z&L)-`th<0%RWk zN!wpu&XJY;tlu+f_rS^O=|$TIMzIg44T8PooGZUAE$d--oc-w!cJ3B$$;sTQ0!vD~ zhrZn;XAM1j-WdF`Htp9%pRg~aJzH=W!sh<~PC z#eNhrAF?m6qql`EY5Vh!fsFk*{Cco29HULq&%*!yZ!gaG@e#C~!4>dH(KlAsl zF5>*-#Irwt0yn`+wDeOW@w;IwxT;Y6o$@vD_Ut{xYq9q!Dc=9u@*kpSeO(7R7c&1Y zw5Q!J_A7fV+^Vjwh2p!O%Fo)&J#0#e_Yq554&`U8Cg9AQuV6p%@5G;ioS*5B%)#q$ z_TgDDzO?v`ZN>MdXI;*~X>;o72N}aT{H*2OaMo=0=BBun{6E$?`{b+?+n#?Sdq?r_ z#ovK)`Ubo&E&EmW`kV2k^t|t0{OF5e0{?A&y@=hBp0V49eF)_2$(;JAtT@KGb1a08 z^b_z6^kGm{xoc_J-pqS_4j=CB3jmR#$__DB|qoT^Xv*@4|`7bv5w;T zZtuw-C3YA8Tz=N?WPBfOy?R?Gk2Z+^Is7VrK7OB`wqy?Nj{if;Jj+_HNL$2js~@}a zKazVJY(Y;u%GhV9XSmo;p0OkU7WN%D5!d0B7@RSEx773LX(~ioo!XrF$>-n&)UhJcb(X$ zICtXKw3A>G%%JDKdGA_hbHRERAH#2oheF<~Qb}=sea+7KllPt>=O<_bE9e=YiDL8c z1-L3b>v0@^CQN6yhm6sA{In~5lCv%U%Q=^R?SZr3EX3)j?C<@&$M15}jxXg8_m0_b z-d0c6e}1RQxxF9zA;{YQlfQ_aK0Kw&8A8jMvz%55>e0XBS7&D}W$b!D_Ke5G{_&h+ z`4c^Bqnz~Rp8O`t?a!8`pz{w7L8{;MLONzTHB(tm}sA z8w)4MYl$xv&)-AaEH+n89h`p6IGqf;u`j@x-%HAhzt?_*cc5pCZf9=^?F+@b>UQi; z=!@06l%2h5ve<>Rob9!hdlUy9*?Wp*p8W<>__xus#|`5jP-a}=d3N^Kx~0XtL!LWI zPWHanVo!pW>~8W#;mp%%ICtz?@s)iKUjkqIbBhxG)dhW`joHVp zQGOi^WFH6V!>MJyEyO3op6vVd*ZIm-XMYH z|FKy1TN_wUT_sV$^CpYhwen~qwvA2Rwa$gkNi#-$P&x^if z&sFEU`XPVUHRHCGycg)@a;oA3#Y2mDx+gpJcyTC_eu|+?jtK9wBsi!_zK?-xj$^l&;Orn&emPoJCqmq?We`p(dNJj^o^y) zoHj<8y=c4fv+mNr(WZzkgK_NjaEqMx`RTisa!2C1+L`_3V)h;ImNs2OtE7&>^kKAD z3dOVFeP!;VJ%!H`KTds1#Irw?;OwuPU@+v4Q;YUfZ0N==gEN(DgnNl^_MEgK_m+l* z;$Cwqe;KJH9TAW|KaZ9n3%lqXnu#cSPzKM3^XKr-I*#k4zJMy!3 ztI&><_Y%Jb`$7G%J3a5$pMIkLO!@2?zj=>Y^y~+RcwW}Ri|j6N8)Oa~1DW%q3eI!y zku{vPcAESJw2c2xU9hq@@s~pO$QSTQc(!;Oc&p4i%d5`LyzDH#60(nEzULnDj+~6u zG!=(^fJypZaDiw z$exwliu4NXLvi|l4$g0VkK&BMgLq$@^|4Z3-tAvl?tDzWTeGviZ)9)6SK~Ug_zl#v z0-wo0AFkB@`_az#oO1g4a5K~uyGhxb@pyhko(Q+~#{H%x}ipRw|0!>Qu6)iav5 zMwtijk+?B^J$?c9Wq&Cr&q$l9!9>XWW*oDok6`B>l`^g5rawEfHxzsam?K}}{H8Hg zY#(;cnas)TJKynJ;EeG?cIL~A;%U>#>iGn34@)6u))M>^WM1dlJ3(W0Zv*?pSLU2T z@!UHWr>^`rRkCkADE=(%FLvhb>DtyC-)CM9RqjT}KG|Mf)$nQZr$7%myNUh4z5p^m zN7BB5yu&55j78=|?uMBUmDs1rOMRKMi}`KnHQ8C`Z6SMZIW6VVcR4HG)V9Ca`JU;{ z&%L%C|5xZnd(OK|C=|!xYwb?^^X)xNd=;cWa%R-WC*thWx0Dq3zl>Q|`nZDqlASfY zCuADC&0VG@jqnz*gq}Xnd71o-{a=uA%v{T}a_-)XM{f?YL;>)ykfx0sW z6==gDTEouf|HH*^rvugTlX&{#ZLypUFUiZYwjqKz3?cyr9fqtNPCwXVH zH?nhPe2;UEPhy`;-v#Hsf4n-f7Vf8IO=o||@4$=sWiXufQCV@G_b)H@VSi=r7TZGZ z*|gd?^XNl)U%+zZ&!DvyI~sSz$KY4xy2A;u>+kupVFI(FIV=`u>Aa1SRH?g^L}YdE3qS#KZ1P?&V0`v))4=z?1|#v zv*)s#v-gBP{0U{nec@QS*(0<6=Xn{I>__YH!FYytWN++KUhJpt?6g1aY|Va9U60Aj zIyjhL$8$#VtML2b^xyVSi++mSry+g5K`eVhZAjluk^eTnNN&#PpV^r+S<6|kB~Y#4 zxsW~O3Ot#XzUu?od-uk>iruQNFAB~9TGrY|{ucC%)s6hdV&iB%`02w(;56q;=G-4$ z-2Gsxxv)|B&uBSUo0r)K<$sCaqfJo$9Xa`KJ4gQ6a;~OlJ=TN;Fp}0@narQObNcWO z{H$2+>h~!33pAqVy;64*oIO0}#(Vs#IPvc6$#M^5|3XW@q<_|^rz-mem`}eGTGN`8 z6urY!M;>Z{&x>8 z``r(C6J*X`43CO;hS%Y2{vXYmh|M%E7qHXsrEn;1oV?Ds4t*1QBz;GmZ;{O1 z-`P`WH{gq5DLe1p3yu-be7r;MuJ|+0{f~I|t|$3b*mugwI@+I~KU?Z*59!Z8O!-~J zj-l6MUnDnqpYdz)$MgS&(`i|I>7zU=d&5Vbm-8$AqRB0p>Lb-7QoGfx(k=PNt7q<9X^V(0&>Wq0|9h<8j}Y$z@5{sd>8 zeh)+C9w1hio^_VB(35^IKffPOVW<5)_-WS|`8U#rE3+4UTB&x+%bJ;rzxCgiJ5am= zzXm>1nOkty^LKLR;8y%C#Ls5m#XcBsjk70~sQ)Rvr@HGvRTwV*E4?W_{rWoG41e?U zj`P^7*nROX{M_@eW*@0Nr{M9l|DKbcKSSPS;#DE{t5)Lahm6yDe1^Guy!`B|L-C(@ zmU6iRW)9_yN}H-f=FzSt_6~MmIqT$Xz@PCmMrVlUIqA~|{Lj0XKkSEaRk0Ov*U z(-^mvvlL3$OG}FLD)aYtxd+gXr6vDG{&Mzyc&)m!9&;CdNlw#ZFM`p3Z!p)!Epuk7?+LsZ2p|J#WS#vGSg^z-@f$hDJk1sJ?Fz7{B7h^ z#xK$z3DiFkk5OiK_*h+I`R(NWCDxdoeP;xJGrp5{wYnZG*uUhqqOYJ0q?OSg#MR+9 zD5&pUWpdVBh_8dJi}dMGv2*cj{I{Tjdh%T{h_(Ue4w}14H-5%G=gL;{s`IaemN19E z8E=7q(3Zz>=F%lFoqZl2Al^$`?&aUd{;{+7D>q+hnKRWqEA7l&ZHXJvFJ-TxZ^Vxl ziu=*2?9@F@*^J{MxHB#N^sJm$)tl$$&s?(R#?dln`S!R(olE$2#HXvb2m3AfQ9Nt< zXWT$6@0Gr3&Cj@<1DX3@C>hBg70D|00KICxjN+}AVTj~8nSxAPBG<_uciyE^+pdVb3(W9RQw-%CGV z{2Is@rma=PhvE(V9_*j-O!;%!RoKhKuIG34PU)MB%Xy{t0(zdCek_3v>>Y6C!LTy# z%)S{nmzRC9J3LAILD`?+39-u{<9~@*+L>~xBlG4xZM}!qRcs9X8`@FYFqoEorUa(Z zt3o)>tt58`{G;5g$E?}Z+ZeCJ*Q+yq_?5n>$Isb%Kfj#)FwUI1l6`!cZz$XX2GaH` zw}-+6Smn86#lO>*qw$V%j#E$e>DU!uC_DaY`g3ybrTzbU8osvpJH&1&r>-;P9U&)s zN$#b?%8U1)HR^9k`wAM%O}k!ae_be^?QPY)H~TF5NP5Px27jSCjwpAgvvclLgX}N8 zpb6xTaJii1-NZix*TDI+^qd)gsVnoNOHeRb|#uCKee%g8yDb}VFl z>_N}GJ)567)|qxNF5nm73+NAN*N*h>VQ5*=K9+qqeK;Jg?sDVvE4waz_Yz}O)xYA~@cVIt{PlV32F8s7>iF%sz+w=SIKN7!@o&R3qL_7et`9GTO1MbW5eIK|= zJ4I4dNJCRdLgVw4vO=iry@~8S+e<2=fB(PN>v$dK zd7S5U-Pe8X`}6emgBkJ;5PzqnPw=6%vuM@u*~NlRZ% zqK$-=wEX+C zzU;>AtkF5_Nw^zC{}gAuvri0%-Ct7 zH-+4_y6_)|2gSCP`@A;i-u?&w75Xgpru6Rc7k`E_2jE5UyyxBynPUf*wUONzkL7PJ zf26YK^RH0mYq3@AFCk;GulV8cJ$(V*2W}Qy%fB1vE|Gr!M;*iYXH*n>;9c}QV^jKk z_J)Dj3$&})ufxvp|Ie<>GbSj!9v)w!t)?) z$$VX1HdmoN{~G!-ITN6Q{VUEMbQ@0FzAhJQu^}zwAUHIepyx3U=PoyE5Ecc3+5pU7_^mbE#YK3Q(gznsHk z>C5>!6QAVguK9oCV{P`3b3Ei8mN~wzXYqY^mi#-_TN}=xKMOKAF>`Fqg55(5wZt<0w2P` zVn5JwzRpv21ODmCyv}|Wa-NN=EY8Dj?3U0#et!R{%ifiJ5VVlng1?A;HJlCEgKJk7 zb1idZi^P@tTI`*&bDX-Uic%UWHZy?sS-Pan_jq>g?0`?4Q} ztii?jo|5?`|10slPZL_+Z!tS#d9_&X_kHO%;`*?}voem4!-NX+o8L_N2iSS%{0=q7 z_|6yKnx8c?tyGk6$$p!i@0sD^y(`p5%kyu?P5GyZrH#wk*;n&>!z5TIK1tc^=f~qK z#8%*}zx|a@+ZT$p#$)AQ!0y3*t|SLK$%k9YC+FjY=?LpZ%; zo~tkYb2EJg-XAjd>)9j5;<@5|ps#p8JVCv4pcy~Eb!GnDsLWcq>5t6cTj^;}_Pw0L z&xj4hhv>gb`rUH&#!srRx7d}iNSVLsH_)@*8$!>D;>_+wug@Nj8_4|>_k|@emOc_* zu$~UbO)HB%be&krXYFiNF50lASQE%^CqwDmz(6@4i_h@;vh9UKa*To~ZXr+z7rBTLEdyj{2vj zdIm%CQ*WM?dG#?|B|l^HJbv`#>2lKzjn+v3cvIWVEDpWHz{!;i|{o;HL1Ejwpy)?d!4 z2Fh;-Bl!7mt+`IR{c3!b`skbxATl2Y0p*^W3)G}MQ?>OhpXaI;%W0w_)7j@ z@tq-k)SZ4lET-=cX=mDdhk93)i*sQ=el_;nkp4YRUKiyX@pEpy0ZZ9q;Y6{IaQbH) zWKJFeS=Tk-8uq=V;@n(|o6x?&<011WZ6Bw;X82Cp73E^h_vlVEnv0W7hnSY0D3F--EwPa z*RQlq__yQiZ`sGP2J_#noeQVRzYQNCKkr`;ma`ASjp;c@vc5CVo>Kl~IED5O{)x6! zY+LpQmDgoX3ANyLhKr-A>Lfzy{e`ve&(@!OQ8P0sUM=a}Z z2!B3%7W)i#>g&U=rcGBt_Q3SRQn4|x7ke|>&g`81XXA`p4Y-MYC9I`q%}uWG-gqlo zBk?`0zu~kMurag~y9WO&KkH+)yj}6na-UpTeB)=nZ6-c}euH=~{vdX*3U^U{>Rkn! z@qgp*k00RguFU1^^uea|{*~sAIyYdip?w8=iPx65IeQ}91nJxR@eOj*zb7hpJiiA! zeLk8$h@blIHxDw$?_+1}cfpyf>9eORigWcUTvbkUdP7{7|33R?&%GC)#ZPy$aze8^nbkG4K!&74KgxJ`vKd{;f8zWwMO zlsy)Xqz{z)-y`~k-GrUF(G9+l_Y=IL%w1y3Xv6v4Amota=X#5f(D-3nV&uU3h@TwTS4~jKj|5RrtAms!|*S>CvO8e`EOO$ zVIM1gDm=ZixVL>QCuc$S%If$P{^fWM{te!jw@B<-S~q*cOXXr-<^0bWbQa4yKhORI zr>-q%o$(slq3o)jo%1sFUr*Z+vbMXadkO3;XGSmcfSz;j|Nf5eUGx7RWj6nOSSSB% zXu}?fb9P-OKkI!P{voh}{|IE9@4*MF^G^DF$o-%%E%UmyXJ(#dt!M9R0qL`v^52u& zTYP|cZ+h0nZeltA{-wVzKAOFXHV+=*-^6cFS={f_cNxoflgsWXHo?0*E3Yd5Z2Fn_ zdVa=eI?RXjY4_pG-zT7wUBc_r(~leB=gXc0U-l~IUG|l&#nzK|BhLESQ(ndRP@0~%4Xf{hkHTpuvNuV?{WMm#Ku$<`*p@D^D+Ig8P1sGxt-Yg8_Ln} zIsZO-TYLdOYxQLDQ!4c>ts3qxz8%i{9V=e9q8Q8cZPxQBc^SXEm7k3V$XkdH!rzpO z@xFxpyRzwzf$aA9ESxc$2p5QDum6Rf`85D{j)$!GbLk`CIeA&bm2fBhS!J@X?#<5_ zWqrI$+n2V0b~5`x+?Jm8u^YY+*P>;QxE8+VzsH|0H+?!6PNb(@73^8|mtp*W*mY_3 zD(wl7Z;SD?;bP11)wFzz^}{F1YbAajKAOKf)aT#8&wJ0po8eF3OSn-yYh(gFb20t2 zZAI~|mSofaV_ShHnqpxpHxSj+2`;-;sa1I_?rX0XC*D;^)2Z5X+wQB7c4Q2zDcw5Brtu z9XRijF-d=9-5$ozo^&w0DW3aQ=K3slO*w<03jYYXwee>757+?)i*e+ z|21{Yl=~37Hq>QbCq72rulPx2hte{3Gx_P~IkY|He!?D3zYTw@k1u0qoh(=9-QuI= zt>*uwt}F1>{6V;**izhzpS5|E_%wFT(;w;ARTk@dA+5hP(jR9o4;IT>=>V^>Ge3_| zc7Sqg*bQ;kX!hmYEzW?2@*ahwA!B)AWpVf4S?p}~I@(lrE%lrsCwHtZU>I#@@u%4T z8Rri8UfMYAm`)!D*&nutYhhMN`{m`ma{m0JzH8ZaDvI-BLw4rOT-b=6JuSKEnndAPMdr~(!xzpqsJ?QVt+XrUQb6!qT<|6+0{Ljq$ zoNZ0$>GMNrTdE`be&%OgcIMqy>N!(9@Ait=aq7st*qPm(HlM${ycW=iKNA0fpTe8S zYtGJm&hI)`Ky&slVx#rVw(R-rR&wUCx5HP-$$b5P}bdVf@V7F8sal$DVTuUO>NCnI7y_ z?625azdOMM_G7dfVxP0mV5e^f;CiqcQg8OpnKFV5eaZkCfZp5I{C(XV4?&OFE8QtUAHHSElrymx)*s?My? z^e_20$vd>7xW`-}r!%c5r0@2WvoY-!Sf~EHSKcr6rhH5O15hh@>iJAg&b=DZ%fFn%*N0hZe+wP@hO?2h&PG6sYh%&h|WejhGw5hMyrjT>2E1oQG zB4m8im-*ibJQ)_x-mnQfzu8oiyB|Exp95ElcP|xpuzK|K*@r;->{9-gxKXd-oA?d( zWOn)}YbA3&d*W5l69&=p-W9k}+1%2G_xNicF1}InZoOf+*l&0mbWvXoemnN3cpPr8 z{72fFyTaG-5k4-}yo7d<^4IeZ78}Z+;#ot~eF76ex*;}b2^XM{m&Zn&T>`8OgliwHCV;>^k8mBKFhRpws`00!0VpH*E z_ylFHVLz#!=WwfXv8N0X>jqW$%keRl#d$KCpE`2ZuVnwrZY16dM?apPc~=(ufIk^$ zTywu@33bKJ_N-3yfB0?rUD$id`3^sVThj79(j7O(H6Ztq%%NwYIUFdK-+^=IJf8m= zoK#u-+o(tB_w)09N7FJ#w#Vt8`}I%S)4FVLfLR+Z*wCz@Y)4(|$&$Qi}|h`kA1Thc$YX0&zu>M(-;BE2vBQ9Rqb z@2|c!{Nv>PMavkbud2|;{*M>1^Br+p+1~Em(ia)8s*v^c9A00%sXEq{i*KN;fsFet zkay@0Tgf>UzO|p_?+)*>hthY$SF583JNv-5%9JXKJuUNS7xux&!IGdyLqZFBLv=!^K9 zuwTL{H%0zF@CW-Q+B`X#W7+GoKV)94;b&|%$5{)lXz$BQU5|OjyZBhza@yDI#24VI z%1jh*PoKx%+?typma{hN_$bKvbD`J+@TJ^`wEJ!Le6elCHeqMYY>Zo!i{Gc}v-9uq z{*(V0yN38Cy^6cRJGAHJ&4y~q=iRf%U&yZk*=MFh-meza5j%l?0sf5s4SNZ^91Suq zedT3dT!*uN_OK?8rnmE~gXPo^n@XQc+ao?c7fz!;E@v3KCQiRT2@A_bpL~Tkl(RtD z+`qDZmUyr1Ic2f?Wmcx%-`deNJ8z{>^^R zzX$&YZAyOIP}dsevlj2C4W^C6?dccO4ud-q59mvA_U9q$`vqs6=G`0fAHp->S~>ae z3Oq&ITls--2s?8uYbJB}Hg-?kRPNZaxzWqn!+x9nG&F}jg$ru-AsaVGwO zcB|Owu%-OH@M(~>xEtIoFMG)r>?7$}|Np>M{Ec7${HvaAad&86D(-Tx$~jNnFYuc^ zYTwk>tJvGoPmy;C)M95ZZ6fcc3U@T|^!dH|A$Nz}`0eO97jp04MPByRKIP)K^@rI9 z$ZMtU3F52xmqGTy{5z0|?2jOGwhL_=_7Us}IP>TrT5^_JH$SlNV1EF^E6gbvOgjYId$dayXGo>_P+F41H1=p4V7{`(JsNeRGNEuclvR71iv3- zUbLXCq2=FNT+2U_o^!Sz`#1Vp$Q(U|Uz2?c4EFq`xH|nB`WO6B{IlVHZF&p7r*Fic zhiA&mo<7KXW)FHzJnf&$o+2k}bCo)-r7dQsUpk0Y>ZD@FQ(thF3Iaf z&z&#(UB-O|KYK#v{2YFM!}(O1i`e_qu4Lz&>&3qf7DD!+^!sGxK4<4$GEdsTp~?^E z=j?5QpM~sI50{I!+{7LXnSUoMf0wbkiak;OgW6YDUdA|k+|094 za?%Ie<7TwMxDC9;egXgAI+OR2F?o!Crrfd65aK^3Z#i78?tAf-IQO6b;1K09HmAUZ zva!QQz@F@f@HO~m^;`tc^RJM987*g6D|Xh;w)C-{eJftczo4?X8#U`?F4MACY>MB6 zmXL8CM|%UlDHp%}Kd@5YC$8)W+By7>#h0n?7=CxLYuP#HPL*>4Zp+_+{T2HQ=ut8s zl-ZfKKf4_*{W}rvhtJiSvAGn+mE0pBYhwkpRpx8Fi*sj|JMb^;Gvq&9vJTkUm!IUn zA@4X?z@J@Fj8XR0Pvo46XNq;i--_454=dBLSFw*A#?SoBUN{mCgNC$`axx$5sUvH< zm7MBgx3ink{}j*KYmD!pPo<|%4i(G4$H?DfR)}S-PnA~%Z!PZ#xQCv7Y!YpbSYwz> ze_5Huy^492asCAMfjQ-3-5($)`*BSS~s(KBDN*I&WTc#Wkum0Q8S6wVaS zc(<0*Rk?cX&)IG9cz*PkVJ!bdWlzW3(KD~7L2dqG`jt5G;k1m;5`HUrzxOKcp;;fV z!y|IG692^B{tQUv-{yX6~*7rj%SZ#Zz1PK*je6#IR75*H}*mpD<}KFuk07$ zPIfTgbFv09e{x=Af65)@N1U;4iZlO@WiKohd&3y^B3kyzJt2LSy=q5z74GAIs*WGz zZw9N`-{b5{!*Is&tw)MGdm~!*@6kQY3$ZK2vWH~ur_QX^+=p{_*sh|e^K)yZ3i}s1 zOT_!CzX5w5ZY=L(e5Lpp<=Vk!w3W2%Jv-@>mAEPFCZ~tkh}f{F+%f#*ZI09T**7M^ z2JGwQ=bo~m*f4$zb-p2XGW#WbDE$oFM&6&eTC{R;ZXSp4htxF@n!#o8vifF~i*x!B z{(g}5-%h_+eaFHj>g$V7lb80sF7^(4Bs>LAz(!EkhfCyU46^rTuKuR3SK(&y#$qqv zvC3!f8w;zTkNozKe$V{Ly3ZVc95N5jDi`atk@D@?bItja*;!xTD!-?^cI;Q!xksD~ z^Z4E6Yzym&51_qFy9t+IJ6cV#duV$@19mrh#wPcH$7#vWn0KW;3g5xk+LL?Ke6h9s zUV)tLmAReXkH1E&1ugy4ou2ufvtus45}Jx-?OX-J#p}S4^vt=Ok%Rf8^u_qG9>t#7oZnY$By34f+h&??Rh0XZy;j|m=(j-)xmVCv!zcXL z#HV^@6aIQ)r^A=@Ephr|n)u1$J!#peQs-U#nYbbCd|EeH3dhrH(odvkEk0Rt-|SWN z(+c)b+U`&Va&G+%`OeF{I1t9sGbewB*Z7mfavnV-Z)HWXN9;p8p;YW!cZux)Q^XrV zJILQ(-liQS=LDQRd?~wnMKPua(lSO_*JrTLHm9D)b!q)+7qG{`v#=wypruc9H+i6B z?U?JG`RSug#rCCtLqDILeJB09KrDNFHCh*TPg>5Lp^$U+Gn{g@#ZO@$!+xt={H?wg z|5Emu_L>dZH6eXD7BX*Zd%us3`y%{`F@BK#7rt05x!E5Mfy}!u>i7R2djfrRG zT^p*iJAF89G+ZdRH}s)xiZ7&XB=1yQ=^b9BXWyNp+_vgCQBH61eIRpVAO3Odtbq;1 z4^i$@cG^-8J}wpajqD|v?>U#RmU9ix{LVUSs_xW#Hov``_OvTJdq-N%%*^Hd2C})> zM(iQt?dkJyV*A4+&z?tXL@TjJ;mn`3WgGk^zLmBaUI*900sQpiFY-51_6gc;{F7i7 z|495GJPw)vt8w}&zv14+PTm}NwCvna$6sO%lzW~wgq^uQg5QPRU70#^R~(Y*vll9&gXU^;- zejzO4kCOL+yk0o-BK?x_$i1o=-U>1&jwu!OoX9Ujz6(xLF6|i2A1(d~{#v=+Xt(k+ zM`o}mcL#;5Z?mXmd#J{ZEjM?KfEub|BoKN~+oTZmKF{E~fxy#jJasl#tWKSJ3B zw143%=tTROmNj)0v|-P|8H=3rITzz!ATNL4ID>u|{~nk~%lVSEknziV{6)Wzy@PVA zaQ2sn@c_7`Y~PaqD?S{zqvbv@9*=Dn1O=P#mxr6Vf?5F%%{Jq#m;{ouDGWF$u$X*s3 zYL|*LKYQel?Ed&GJW|f)?2fRCoxLyPo_E{=FQ;V;63cx10iRVW_R9Bh#%M!j#?m){ zFZuoPBB;lHOFQ<*r@=CFert8y!p^u{RW80MX0ivvG=BG$=9zf3kQg`SEf6pZ`#P40+)-uM{5Fg`S~`ezg;OL%f;1^y?pxJtU>fbKYDnXMM=G&{TTX@}qF7c&W0uD=g-J&;JBE zva910#NJhIkor^a7VLM~t;O4jb%AyKUGQ-6OX=UjH&B~CgO;-?^LQ=$uU^IZy#fDv z{&ixoSM($h~R`epR0 z^j-NU@c;4b>}PfOli+>%S%2yK^jkf#@5;q}G5cTkh|eH{3=ibGUu}&?1%?L_O)vv_vo|e>9gka8~ADG zi;!o$j+e>Xsa%XtRoZy(vReM1(ZpNO*5{`mzfyh(eNXlcIA>>1u|@2>!=?1xaWcoC)jUJ>+eUn?!@D&@?gnLCR##=bT@H)9={_ zK4JI5`TZb!^9ArVr0?>7o#=j04fJ*9);au)M{+ykBVl^ExCiCT`Hj7b_Op7%;FBTc^Y4M`iY?{m z99sq3vRBgf(B`9+okDw={}nt98TxwfDIj=8NKI>|&{NJ>5 zZ}IBlN8q72{rn(&z|P%gnlgW}SJB4fyWwH>56b6##)vnTHyyX8y}&+&oiR$ii+UIH zzNRuaiDeu<#aXAXuqVlBp&hvoXMN=Eb~enWT_HY!y(c~ATISms{95$1vnBr^bq)WY zpZ&2(srW4_W7C4Ym%dzqJMc5N&x6a##hx>neFS9ReU0`JZl})M@Rjnn^USR6rR@Dz zVpt&WdC2*{7fzq#T$v;1R@@XfpwEKC_-`xoAv^OY&+4tttliAjcf8~A_)q$S^0H^; ze)_jMo`t6cZb^S!TL3K-^{g%o^cIiEV540<~i)K?7(xd-*xP zvUg9!>6cxg4LmJ&JY@e^ix0%T@TvF`xm#8iXVFvMD|7b`+ESeTXMuS$L7nOMj`DU= z-+_2gsW?af63aTx9VmU5_Fbd>m*OXtNnigcXLtF#sedFbXVn(ajrI)WEIGBJSbNvU zhn~vhy}%QC=ljU{jh%nra5a0N_*lqXZUh;Zzu{as zjGytzc%{zQ`Tr_6Ou3v#JJD*2AHW_3x3h1?)A3DEL*5F0=2pfveV%vwLToE!8m-ovZqME!DA^XfDWpf@6rynkUB4mC|z+2I}iA|8xf}QWL!FZKe zGkh+;I&NQ4%*Su!Z7tRppDlL0XDop`#HZ3T#+gTRm3fo>pK;6?pZ}Xe&;9Ex$o`+T zK2O;Sc^k8*$gN3#A3mb>$1~w$_H^i{&U*MrxVlvQJtY4P)bo_xmH(CUEASxkdHO5+ zWeaQObiBkfcU1l$$UN$x%n;Ace*Jo>c)z^oWO;wXwzM_!hRUlgZvgvOc$t14ZY?+c z*IRtH=jM#aemaw%{rhhIIv7l=hBv@p(sLJ|!~YDj_g;$|l#2cyjC=Dlr(4PY8^6mR zhNo8+`)|gj32iF$haLIT<({eT+>tYu>9+^}S9dM^r@qMi$eCIlUZ}MGl+BuH5BrIg z)bStAdY|!Nu@*9KvY$T%!`SO9cfP!D_zNL@dJ=sej8kuI@r~#MVIRnQ?o8Vs55ga( ztiC#tow<;8@t4>k^!FjTBl(+DIB(Q_w47u33)ofhRmS=;JQ?1RKeuEr5c>n}pdHTd z0`G}!$Im|W3O{}D48O!)3%82b;7^0B&-U^v*_k_y*+1i<{O*C=mU7bn&0#pLAAf&# z%55pWIqi^=eq&z)Px7CGd=I|D|4}SsJ65@?=s8Op;*%dSUvTbb?eJmd>Paj00l$H= zt>tHr*--wU^qdiC%enkZA#31eTIOCo$UgBE?KHf1r8$c;uJzg5|IbhVwU^TX_Z5GZ zy|J7n_yc2eujgd_tfO78zeljYp=F#lkbf6$iXSZ*1NP&%zTA1ZIsG4`hGH{2Tz;Yo^ii$o=Mw`p*BP#6G^!d;fM#9&Cbq=IW_S`Q#V|&^s z{9Tou!(RxQXBGU+!BOl>;AuJE@iW(F$w}RBLDt16+8OW#JL~9ZWl!~--}xWO&9`&< zEo*Qjzbej{b&>a{yy3LdXgOnYCQX!^b(?kG3jc^RUiY&zZ!))ALdGWRJa>#dV+Zm4 zKJc^Hc=k@^ArC$*ie0GU+PV6*3W!>o4tFMc&rBKx>58hb6u3b(Q8T`#pMpNIzz6WuF<#AFk}h?BwTOF^}B_?Mf&eb9^3cOIjmkTeB~w|A9YNPwxGh14Chd`WE;N zWwO7WgGYEqO}U4&=fZ<>a?U=duB^kHUE4!vm3R31{7ov0yISrM*ObgXu|sk8x0*1Rp7W$XKA65*nHSin zi)Ef2j!&ld!JFcfXusq0)RVc;6+VIy(6Q{C!yC(;fUC>RcT4uUL*NE>+E|G*&eyPS zfLf5bp87lYD$cfQ$~NU!!B>l4Panz7{7ilCLjJqBxx1Fd_Z8n$9qY-d3A^h1+vH?k z-7WqQ3}QEi4cKXa#^`OxIAkw;T^*g+|ItQ6)^YZZ_pG0)^aItIy`@FT-Ue5SEmAIb zvzOVKn?K;~_$_eO^ttj^t9Jz4&aXpn4|mDQJ*NrI9@mCekAE(I_m#z+A-@A0<(ad^ zmy2HtIoJBBbB3Js+YGUekZ;rDY3ZL{^uyFM4*K&uKz`Ra65f`3E8GRycNTc&TkKt7 zB-E38W4ZX&c@-ZcC-r9@HRQKq=XbNt{B7V_{_pf=xVg4%gvY|&6~+8m%-*JKUD*@b z%b +ADEx17zP5Bp?w&c}z9iu-b&lkv{}(T3fD9b_NKGqRpuz&&x+)DH9;`I)C5 zEAt1v3i}*(=4bk4IlBeU`aS`tT{*Msu(z$$*W&Hq&XvWzU}y2X-vIHPrP)WW!ubx! z81E>a`BVd{c=wFOzsfepLm}^wbyrJn&c@DVdkXDZW0k(YfuA*;cZfbkJ(cqB!sm-; z?LEssR_rJ_({O%|xtjeVdkMZB+Ou>143>WbZUV=ZJzwsJ>=$s(g$3*ma4(#u{!?gQ zvU3jfC|hIldayTv?BCDfoqL&|?Dg1tD6>l2kH)XytgEBNvo>xP&wY9p{0%4ZuZM|Z zxi4j14CZga&VE+O{+jkOT*uCLeIqiRx8_#LrU%D#lt-(B$rP*rR@{^RfxjDknX#qXHwiDiyVQb*>` z7|(c}zXd<%+Ck!Z&zHqErr$)b$*&Lh^N)w@7jy7xXi!nip?lb=dj$T=o#o_`b4J~Z z_)oCUgi3bC>RR?jV#DP>z@AN;1fRf>{LHnFaqhO0AkV2^;T<7+(=)IUy%YV1-rkv? z=X|El%;(->SId3H9`ly=@6Wzoe27?{mpPDin)NVJ&I)$+jKkp$^KG>FlVU%sFZ;kL z%5;@;ENxTB^KXX@X_S`=33_cz3kN9w~#Y2>#WR9zYZxEZSBF%d0pFgLSM0rarW-4(ag`R&*m$Ob=VGPF6LZH-=B>; z^0&f2D4X+Rnf#Y%`?E8*GH!Q?{Yk$TvL9u=WnE{lPhFW`Q*rK|4cUk2?>6eM#?RWV zD=+8Y&v*~my<{Ev&F2`oN65+E_ldcdHP%*M_JCiNJC%MBWQ}cvvyWwNKE_X9XPhe7 z$;p|PcfT3ZA1A`Ou!??`_%X_Efp_FTC8rJC3F+JHonzP=v(x5`(^b%xJ&4v3@5f)E z?7RFMp@&Ncw{sAl1$8IBczIX@r4E(fmm*MT@Or!Nxu0Q{J zcIH6l+q#wRkIMCzvpIhy7%&Brq^O*kWM!SbySI&>Uiup5zHdg*)_!e6J zJDZ=0U&zng$~u|N|3|q^)tA2+r4LSJA0*ZZ_fqZz@tmVg#X2jOzN>(FkondV)|HFj zqmEX`e{dbGt2)v@n~JSgK6ij$*?WlZ$S#ScFCW6+;Ec-_?0PWTvxnmBkN?49$hp-X zpQue4`)G~SlYYtglyT~;@T|1)EBr6c`Id1ws%%cOTgknMe-c0AyAgX&`q%2ciJ$(; z*q(`-h-E#U%g$bt_iW40JZLKaXgmcH&z!y&9+x+PpF3{)=o)tR)~tm*>ksADz;*1$ z#K*xua!!@sMNaO@bM(=0dg}gC>>zlD{R(~w@7lBY_i`QN4pCQKWpmzU?%xC*;p?*T zqt}#k0W=c7fp(wRhB)on4u0ilZDzmQm)%-yI(rXx?pAB?L$npJ3|@xIA^niP8U@|i zo8x`t=Y1!8Zekad{N|vpN?cRU5wzU@|KR7ovYA-kA@gNJ{_)~V*{8s}>=C%Jc=o{E z@V&GfXoJ*$H$ID>wZAbt@6c8q>BDX8+56J>VV{8Sgc)-39$Alh_w>W{a5rrk-WZRn zDEcwadZ34~r)R%9RlS*GlZ;FHVKC$yrKxsgy)T9f=TDJyx^nrByPq}{ zT9k|RSH>Hu?_g!->bFnDGCu9_&p2z~Pj=4y=IqSn-mn2beek++pL<50)rgikw^ThD z@64IZ)dS!hdHa=|88F#9J%EpeoJ-kPa^{W2vtc2;Q!e&{@wk4aH81`L`(d%XYsMq{ z&|G%f`hB_R*EjGYSWf!|_G0&wm%dJ2o$*UJ-!@N)KgeE>y%}s>QGCO-p&v~@LYt3Z zXS|z=XCLV6U0c(t@vGu(;9_|n;_Pd4)iD9GHgjJ*9=9nMb8J3)E&CG48kr)V`MU$` z#s5p)--=~@UV$?Qa|f78&zR=koq7EfWF2k@`K{$${ar&HC-C$8&{}*DtbmE-;?AG( z%lF6<`SaCNPk!3JTKp1a2C;{W*P&O(xnoqpztJb4e?2g(eNvw-b94KQI47xQo5B*kkP0o{_bBEB?(c>-qADChuIG@KF_kB_Z!`3V%Y<;Mt+k!6V?;EkiHOKjho;%zSvx4XX6iHr1&a+N6%WouL^s}|C{zfsrYvRnRnOH$Ef2ie7rJC&G(Gi9LQd~ zCC-|Ajy($JfB!!cZ_K||UFqN2a!P&F#!N0<;v_W_#-h#fbXQxjehWtB; zkLX>*Ux5|;oL>WBH2+aZe;!dT)>w1?4ea^i6X9@n&X6InJ-eZJ7yO)h&SGE8egvm| z8_Ma!&U#ylXV4DEYqclucp^Q|x?8(`qGdcEmvfoe-WA39p1#gry|p$)6-Y$`vy!Oja7cSvUl`!x07=oe-oG`b~S$`&f5N&opm@HUQlm) z+zVbT7iaDqcGlbkTvk^nc!qsEq>Y(FnU_o9HumRmC3}D}BRqewoYx;H`srt}&XD!i zLi{m#`G$N7XYKT1H(`3*Gxzpz))N6|l#d%yB6 zJ?A6wz1cU5B|cI-Yj6x?f5Bkw=j zJup?djrnEqeZ?}rXF?zTiLe-wpZ&dYx%gIl6X)A^Z)L}cpNpH)PKAv7%f_S?J9Bfg zyo_hgug7VhvD5yO;AL2;>{qlt>`72Ze)hTFA>}u%aF*$#huHnp(E#s2I}fi>HtVPv z&RnX+&VF@qxfq|f*|{qm$;rrpOlJ}}8 zzQf;T=dPD;*sXC-+*53__6?GoeIawUs@R{n3eLO#Xe{$iudDw){#oqIfv4HIi!8va zH*KmTow0k_Qn!GpJ8Owv<70+$Rzd+7A=2w0T zO_qJ6m!ZO`vVpAOg3r{Vd^E%&Uv&xic9|4X^~|7DW3xqoFb{@1hX z%FFYrL3g=*aTj<`>@OG?pPsY+XSsQ&E)~9!*xx|j7;ks12n3uS3?% z`}h@@r!C9N?u@k2INt@W|L4yXKMJxoX5oJ7P5*o+el$Dl{3TchuhO=I<>Ft69Sn!V z`74Xx(lhV&hc$9K;7VAa-h23U*)QSr?IzG&nKAs#zpe3b@$8o`ve&R%R9Np~U-Rem zD!!?1qF>J6gWg|m{*Ib`s;}I({JhJt{M*@2D04nP<9Y@AFZS;Av}Kh3_#J;kI}E;6 zU+z|YZ#OvTmV*jvDfc$1M z$#bg6dyAhv<`KLCzYQOo(-(?ozWqhtkCwSIg8i|wtJpaQdaEr?QZx{&feOP z{>!+XB7OqwCe|HJTUq=rlRkKhz83!m7sGI|3zWT&y_veR21c^;&e?Zz_9b^O{H(Qq z6?-9MU&#C&AZJtf^?&8g(qHeYZ;G5B==JGu;@op`Kl@^(xuD#yw0vKruZPk;4gaFPs^O$6NW-H+ST|C`Xl&S z`T17L`drJNES|k^ru$od#~Qb?*!P#=mE!q^Ogqy*sdFIz0cD0j_U3c&&A1k9$N?<5(&MbVXCpZ}D4GY{fVrGE=~*Q~?2?3a~Yf=iHo-2lH`(mwo>yt?en!K1~R z(OOp&?aMlU5YqRT(XwZr$4>vA&(D4R59RWXud_$sl{oW0_n;ru-3d3Q??yWn--u6x zv^9If6n34Ge#NyQb?2Gg^hvaTa~|Wb;28PCaX(nZ{!q?`?EGEp3Y<3Wh}+}OOWKXk zl=Bnq6*>9-$^B?s+)4SYx94#BY>M1k?6YX;|E-lD$G(C66gzFmnYK_)+LWBX z5qnpheRP2MTAZ_JB>NWnp>Q!RzbmEhzK3I>T1j1Li}*r*4e_ke>?e)IF0Rn0{FC^f zi0#6!!JY&C#Phs?%HM;R@eh>SLe6ctH7#dS$I4<&{UE-IwhR6eYSAa)yvJ}jnEy?= z=;y4v>9p*9KjOpS(u!i;wGL;GIt#keUvqDw6D%sf|7T_L`=k3E!pJk2R#?Kzy4ClOfj@E>oxwDhJZ*b;#J(wq6L!EcC z$J2Mf2S5}0ru_EoarkEAmou*pj+S|M2|d5D<{h7;brP>jug(9H|2Aa4+@LLAt1svD zv)``~J^Ka4W;uDlP zn>Isi3gn#{o{ZZH_J!toa&xxjZ>a~fvv>aBc@6P}^dYpgcRNVm)T=1o z`5t;p&&yo+NM1LwmuW|_v&S8Sr@}ngyKH>mWA@Qv)$ufb7hDDZ0S`efe)_VxI%}{G zVqcAC!AIJfF`UfK+-kx8L0vDfv)@j^qxoAv-aX$p*)Mz1Gbi?tbG6uU?C)tKU?Bf! z{-^9?@k@9UdFv~eGU@B)upHXJd+-K~>Q(I7+p~wkmdf0WZ_{tn@G;6~-}??uDi>$u zx$Mm#=iy7ViO|yXKi2QrM;gHa>iJ1dWA)D#&$}KZzk2Ut9CB|-U$2mx_U{a7YyM6C zeeCSXdH%iS;@^zCuI_jEP2g;}gMK+Y1-ZwbgF7m}4tFjY1KMSHAAP-nXU(BEg6tPp z@$&mW54FBg50 zcQ}-m_dbiZ9jwRSvC{kEZRtBIJAj>Y;|j{m_dGL zdV*G6&Kd0G>~rV~VMF$->bi@47v4Zl+WMI`-PyCakG!DH!)ckzx6*c0N5;1s`0gmx$E3WUqC-qUiv9}#h1$cq|Vp;Uf*8M4sv%8zuq&?<^RN=DX&DkPT5`fnYVlK zyF>P>*7Psw$Kmn(7nQBXe}LT#ZeTB=Wna&DW!((Kz3^)DGxyQ#$^Xy>$;+8D1?Qb} zUge%s3;OqxN1Me?+xAc<{r9*!`pG+)pFM51_nAaLj-B)LQueRPY^Y4mx?yl5eRuj( z^e|N-Yi>vun;TveHT-MDs<>DJ9>)}Q7<8QeasW*Gw#qbZLU-AwQ zi2YVpHy(@Am$SvkvnR7(U}q260_Q$)FP@<69<(?4>A$q?BX&FW4ds8veuy@X-PhlN zR`K)9+&vfI%;y$x5PVveN52f`{yqqIh97%ruith4DiwS1EBHbBES!6A=D-;Ce|Tr8 zua5h~JJ7NQ)8>p#;@Ka5flX2*77j1cKinXtjA5v*PKaL@-q(`$$y5uMl5?%Pg?rzPna=0vGqfY~uq}Nc{R7-WEamFZ&lBHO-2=pafR6n9Ms`1Ygq-#8es~Akt?X8w zafEW)^55mx!TZxP#&5vZVzuzW6~%ow^L$@9`HeaIbn5-a+WZ^$ruQxt_l7>W55EWf zCEoyt@=s@Hk6Ma%)~+LIISVrWzj$Ul^;F}3&VIpjzF=P>Hr8`K#s~4cE5B#z5dTZw zRM@w|nJIQ6&ivb%o!=XCzU&P*8sBH!xkihPg7oWMxVF59OU1qDM|_$xO=+({?&J0O zxjUw>J{Nx)e&c7~&c44H&bQqHTtn`r;*;3Z)Rua;17ZKA@glD{jqX! z=I8FR!^+|tXPMaZ^o=X+t2q1q@#6jHyL#S}w9JjfhePIHd+1O93s%T$uCDCO8P~hv z8JJSipK!DOJ`TSF$;H-Ltl*Wem@T*|dA)zlkrFH;A42 z`x*aee(g%*q`lkn^DVPh`K+mTJ$Ej5r*!KkaM$yR!Kv9bGQ&@W;p-42>&_ zb7yPqIg6jZc|?BhChsVlzXv_7>>T>{w7xJ8W{Ks_^ndp%yuWzvA6Kx`#x3OJd{_^1 z*UJ2SNKX3Z2K@5M;`h$1vCPNx!Rt8Vle6?yoPNJXeOIv;ifyb+emC8Lzdwxk{^RgQ z>iz~FP%eIJ${9bFy*?fy|0{KL;vZrU&OW)1dX~{gvtJY&gHzv(a&fP3z#gb<)^Zto zz^`Rz1}v9<9o~etot(7yBB-a%oa@&>U+X#hNo(o$IK_5UnAJPw5M_H?HD|-ri zI-U(}#M{tcV9$Z<16c#FLF)VzpA5ID?+?62A2cZ!^I@oZ=D^A3!XUXF={aM6WKZ?( z-Q>R{T8yXzt4ZhSY&Ngz~=ltOT}K3J?3#`>S_OZ$)opECUfl+ z{#51K$hm<1zIV@a(*JpWzA<0qKP)yD=WID&?q>Ag_>V!(id%49`E|t4frb2x-#L&w zMDE6UZqCdT%LOK?=Oca>`s45y{SCZ3J$vVJ{^{y`91pFAlbv}# zO5Z-sK9OC*=PI+4_#}4ri>L5~k9bdGm$NeWhxE;U>=t;i_(6CTT*Gsc(^pRFJPfzPgK1f3@3XU~Uk4N5HTV#YTxl;X7kftLOb2$_(o*atn9p91 z-V~>u>Fa-#8OlzbGuau7X6($5t#S6&e`!nMRq#p9xNBXqxHdi(e{DS%0Gsu$sa_| z`&`DKij$W)w@gk2o~iE4sUGa7JS+1jdwR~(E+uODE;q#TUz1&y}|x zT+7csmYkvNk+iqeKPNWSr04g_jdA)oXUA+g1J#%B-4*QfVFt{D?(}+cU&2S?cC`M^ zv*Y;JK@(+mgiTAu{iPm0jGwufZ<_2uTPc(0XKgj)Z^)ib`w8}=?ZJOo?&th#`3K=! zX<2s-t)1M%zv5pYXCi)vzd6p{x_-I1m#o7<&Z;BCX0m7CYQ8zIlz%S!AH28ckM{lx z*m>6{;SgH-Hvc`Ft<_l@&ZFm>b&_&>!v^9DXz81#{Oe%{_N(|{`MDc5#Cgx;R)xRC zvkqE`or$-BjK`~<-&O9r^xya=;STcOV~^@pjA?VZZTQ)XzYveUyPP5PkKoSU-ktv= zyHeh!aG%_Z<3r|H4P_ps@5=riXHHf3+}i5f6|bf51V7XN!%yHpVdK<`Kcn@)9r>H! zmg+l{{VlyIz6`$rcZ+=}ma}p^KYM3S+U@M@ms!)7(C(#O#qO-^Uh*GgPl4>48;S3$ z{IB$k^gpM^)!?!+^cU&YSYntb>%mKj_m9cIs4ndzh!mscNNQeYss!o8x9vh&ashVwd9@z zyYml#eIa!op#GoPW3;0Ve^FT*Xqo$6`1SdzH~)=DXwsegCSvyWfElh}5I>31C4ljP0eP;%@8_Cz4VPn{{2C3uqVQ+HQ^+CQ?kUE$Cwq`pYx34Vr(?;~JKo*YDfl1#K(Woq-$P&h z{3d=&yji*UZt6hZh;JJ>kld90HMk>vu}$!te(ag&ny_}-^M7bArbXw=T<%+0e8b$w zb^&DH=dP1=$$j^GoOx}>-h!TQk=%1r`|=y$9(WhXTiR96^4;vkv7uTikY#x$Qt- z4D-m@-+$tz^i!cWWE?dizn5LEkMpXHlinTIz&}Fr@?v;^+)!L2@)-EsbKhrsRJ#r6 zP57RnuY{bSMO--`?6zV9(fQkyqn=^nDY5 zNBz`=P3eu<-x9ZiJ?%DQ{}}J3&CYmZG0Bgw*`vSfqdi-`Yt~_pFMrQ! zvVNM7E7@-sGf8ZH^0jO`@V#0p*1rzh;cTgE`)ZfmY)L;44uoIn?~8vyAJ@_A&~rXy z@8L26JJ{^Vuupjp?P#Yf`YA0B1@)mZZ1 z3tdCLz;j~D9-k)WW_m;X6k8cu=;K}J$iEkRzUzk28$l=XWHIBbi*sZfdh-8D$eyiV zvR82KUD+#5=|_;0=M(9-!(_JC^|cI7q30X%Omgl{$+7&eA9(;sz0A}7ka_+eybt^Fmth9HSSr48I_oFDZH(f-74kh5 zEav}~K0xdsa>jRn*tX;+$!AsBTlf_i&Nc#9@HfiOfo8ue$74+2Z=Hwe7|E-_D zaDK}=OiW+x4;S+_+a`D+?u2va+nPKd=NYra&ZlRezRy1jzJ@kz)8HexgMJLGfl4^s zIQG;&YjLJ_r#?m#eNR?Ao4ln zh4?l4-qpo@ZdO%sPsrRiX3KjwBF`+@gRnUq4{O9N#dBc?7__wTcPHNnx9WGem~nW4 zxq7**uEKTn``{`udGD;z-Qt?iGY8+|Px$lPP020z8hifT^xV_#!3RO^tliv0P872U zuJDZPk({w3wXer^F59+vf5;ioqFj7SCD$&e|KB$Q?#ez&Y--$wp79{tkNU_w=2=_N zOZxqpJb^rruNi#?J$36<{XGn+MOo_y#GDI<(97|W55S2ZNX~l=6}OpZ-d0iUm5cb^ zVZRmLAb)~if$WVzmFhlZUv+x2WKU^DvOp7k;LCBHEx$1<*y=*K|j zaVz=~Jf&3B>FeoTAbYLMR;u^^5SQFbPCifnl`rG(M!p!|0{O=8hOfZ6n1Q@HrC0K` z#L55UZt`_|de(OvNUr2AIg$JZBqt`})n#_RzsP-|H~Cez)XZPCuT4LKoN;Cymy)M= zr!M5o`G(|8@Imxv#P5kO5mODBeMZi2X}8kH zkn@apVL!eTpdAdQ4}sUo^{iED+RPGY*RR?g$6R#euLUna54L%HS-TZ-aTBr6iP?eg zE4UYS&{qpcuI~z&i`+%lBCmtwB>HccBB@>DoNTzLU9ml6*K@XM8)1 zWzSqU5&IUs3(SS4uw7+wo~|I@P%6&dOUY}qe~i=T7qGzd^1DdhExzRUXh=QHoqizs z0`gEz^I?n&>^v)4B*7yIy6yboJFw(N_~#3f%d2bWaY1CX<+g6(|1 zjl?CVaz0F)I?Im%?;WuF#B;N*?if^4C_%dftLDutiSjoQtTEQl;l5ca* z*aWUD$tV41eX{PK;%DG0cthMR6>?Ngw#Iw#=gyrR>rZb){+j%ixXf!Ed@Fqn&N#EC zeIWbwGwx>CcN{U7*1KUu?V zAn$TEUvtk`L(cks!oChU_m8y6eDuQ8A@`{L_?ALD_TJv58F_oS9@0ndW{;JN@5=Po zgzX-Dytduh2SG>j>G(o;w^Y>PcgPdT`-uG+w}(I3TGC(g4zKtvyEFL%w$!eRJ?9$y zlE0&tu{=V)Qr!NPa*eMmy*>F^d=NA*$p!wc#3pw>B(i*(NmxDj;UXF)34?02dPKic~$cyE?NykgpS;g}@}#QbOgfgFeN{uhHXWkvGJn}4$)U9U29lqLLUMMP zJ}dD$;_a{0N1Qo--#o0X&mSS@*A6)Mvzg{Q|80$|+0ATI@$UM$1ZQuq1KrBT zB6b7vR56Fslh3a}&Y2hS`K6)`ZbnYM$#+%8dxv*Cl71^acb%+l>P8#9F58aUm+)}5 ztXuZ`eSE8{the|^Y&Y>w!mZg8mv8&5%}DZB#@?0u1Dp$uv^hW@-T8WYw>tQBV@R&< ztX<|O&p%j9Q*!PiALBe{Q*BScslQpnoKIuvxx<`5?p{@#8Lil|F8|<;u!L`a`j?Pg z+5{i0O%;3YliwQ058AF|e_lN} zjI#HDndA-ma-ZH-o455pjXmF>xBb`mPW%t@-_M>o+oDvQvDq6(v3-Q|ZE-U>^ZBj# zlh}5K=g8T&nTuii9?W(++ak8yW8R`~1If8hJTrCVVtOsOoj>(3=TN>qZz>mi{BFL+ z0B!A-&uoK$h*OVaKCv;-Mo~&p>=--pH^+JlQ(3q>v^l$ zQs?d{6?f4yy#KZ2o8SojKg!o0l1npTHe~&h6JNur^dsRKasyaj|2yb6e_op0zlJa8 z@Sl+9uYgC$6|g0I4wd5b{G3ZU%aZ5klndK|^wgB2`4_?<$i3ldoIC!fo{>Cy1L}(X z4d>iSZq$JEpY=Zhw)Ad$uuUW<-&4C@(|#%WW&Ar1mX(V+ZpYt*J`b9bYqO_@9*t9z zjwN5E&wBhhcka>0wtNHW$HF9X^0)%`=ev%rc6E`zW6V$P)Om-j!IhBu)e-KD4C|MQ zHmM^$=~MMJ4cF8r-*MCQ`8r$v&Bbrz8`)DYQ@`$oiEIl>-ji)tocffr;}UHyD7(8u zJ9_f<7yOQXGMDwqy|sB0pUBsRtsOo4s~1ikzm$9iY(SpEzW^GUi)L&)!G-kx@Hu(= zN_AA+ILKY;a(IF-cdkxskBRw}o^_9WJX_W;?@>+PmwpysYFzwh!Uh$^SusG&m*kgl zt302adCa+YN>%Y4dof?ebPn``w0#VQkb8UQ)V7Dp@(ng;8%IBnyb^lSlSg^SA7C8p zr|rKucaQlvHL(NRqEd0^*b)EV^Z9mn?sTUg07r}46FwqeC_d}o+&PoGLdKDA_T+Z< z$)#+sh{;~h8ePtR82@4X_1N0*_o1i0uZFDCHf*_Xtqq&9mDt`9o4tNL&N+MpIrpt+ z`6jUSA@2=Gia(Ao>vAT2S#)yF!1u}7=UIzCv|EmIHe_6*;l^^YM@}Q}qU{T~rnsHN zq~49?Zi#(E)NehbLo8M{|(^8R@C zvGfn==ZV{nK8C!T*zI5(Ik}a5TbtZa`*uqTduQ@nVg|xle7E4-qmoCPbuWGgIF>w% zFYEswgA&?$XUX=MV8m)3Z(^_%auVv)#{@vA3e%jDKURV;z>0yRp@Q9ibsT zbs=Y3=5tfF%h}$*P1zp6pJ;m=Zc9&1NUrvP4&-^-bR?(l$fcrkoYReXEB zTQ2V9dDqL~MaX)cuie_@+*yt{FRzlbw_28+m;86o?}X%3{;knq_TTjRE&X}^`{=2~ zx05sX*&E;R9l-vC_|$>ytK2JdU&*|#gnS!}#wYR(BELq?GoQsv@$YaT-=FyZelKJ1 z?73^<<+v~V*4iBo6Uh(4o$Q0P*^ZpJtU>B)4bRO!uArx;_lD1*PK7&w_Is9!GrAp) zJelt@$h+KxEAhR!6C{u8c}8;ODDoa9a{zaeJBzzdpSd3_$Ejm);KS%2Km&3e?RH>a zRaNwLK6}ot+?VoQm^yU_Th7Fn+19G?eriJM|GC;d!#)qjz}j#$q!!-;Uyzd%`+COJ z-Z}k!M88NsyW#q!VjZ8+=T2g>7JZhe5&UP6>x=(Ny8&XKq+gEzrq{=>Ym<3-&shG( zgV-CCiZ=O;qOLw_!B^y=>_?H4A2;LM>D{%-e3Wp$BOWJr!P)CSvLzp`WNU}tV%wSA zoqsOQ-fadu=_~vE2Any~{u&v0hnixSkWYpSN@_FgDE5B5K!5wOXYOC2??rzPGM8D$ zK{&tfj>Si4Gl~2q`JqyAKJ*~3EB<=;ALLx=is$3(#hj)2dp$X;CYYCrY{|7e^CWTG zc*geRp5#9G3Y>3(%wL{&E88E_vygM9jhn(5dt?Mds*5G-#pPW6?TH7z^ z#~Rmp_zJO4>NjWR#q``0dgC7WbFnXz*A}}+x%lQAAuhEo{!hGLa_l+2!R)7z-^bVE z{GEw@+Sbs=_4pPsITwaP@+Y|oU931yMH0yuSe5A)h2gc-}5C(^SwUef+o`iAfeq%LeN=6>@2p3|Mb zruMthe9%qgOa4y@iY^jfT!dm9ATXiw+)c-BY?iK8F@i2X?!TI-C z+pwJtv*=^kr;?8)XHME`*9MZmjp_OQH0MO#sXzS`ay#fLra3;EKkuLW-k0>$u#?HR zlJncg{^g=R-b;Uvd>v#xkL25c?RK&Mkh9LYvv2vIeNBb2@sAbzu0D6g?Q!xfb?Q&P z4)|j2Gmh-v)Y46iry2Qs^0s(Lr8y&?B&IRG2_K^Gq2%MBy_gnk_rpL~Mv>dl$dY--etyJsCRFbGDvQD(ZS~oZ6ms zOwJ!rF6!l8ID6~{$iK77H$vvBH~l5t32%h6hHt{%^!IW0z&Ly>{>iiA>nkpEJjB?R zu_bRx?B~%pA|J$-``$)ue~C-X$>gkkTmC+L19A4#>uj0#mTX&z-B0W+@?GptLFT#x zJj}KsK8ab1mVg?qt4Y{}QuwTwBxfo#Y2 zEq_C{SJ~Fbjq&l~_QQLK$-8E~4x?ur`y1yn@<-b1b@xPk4PWZ@8uE1b946A!NBBqjIJnW+CTf#&C3liu z^lE)wC#DW=g0mMAdx5w|$T?F-(>vnK)$Zi6u)g;D>ZduR&ZUp-$mjB1&X>Gx0Ee*c z06%!vvwROha=anT_RMxTwf!QzqEvh%@2ioeFdHmHD2l{W*$NzygA87xuep7Q&kGjJ7V%~rw_Dc zavkmak@LKoFokUc?RR0D1ZUBc=`j3;B+2?kUbcj+tmYKeOg_7k%` z-xuWb*xQTw$~V|IOEB)j-yYeUklIB zvzOPcF7C{O={M4Ir|3^^;CX$FX@CA@=m)}hdd{}h zoc#~~_eh+5PIl37>1Ya(uHhvgCEbbQe+@;PZ=iGPzGC!j#if@qo_SiwpS?u>i zXZj%ev9L-^-%7bA{#CxN^u>5jwk7_2ql%mwn6=KFOrU38o?<&0zsdG5pfOXYI*+M)nAbPx3;*B+O&oe+XTEd+c(;#R=-Qn-k*!7uus%?wLUXf7sGhI zdS!Wx$J0|Y?}Acwac>!Hee2P)*K%hqiFpyP%XYeU$+y(R+_k=;uYzm!zYqO?d@sOn zkbKy;hnl193i0=_-6!q?_T}U;@GRWQpXcOzttI4pqK=q+GhW8ODS1DA=lnbvD%g%- zdmLvUuA}W{>~(SWUIX&;{CTge+r#=inmmo|OUSv`2kNU46FlQ-@>2FTa4-2ISRc;N zrXAbYTjXi_oJRi!r*@@IFCbqpHhDG#-wtoUKJ?-EUj8ZC$g zCTHIAZPo#ofR_N-Cz;Apm=@wfD!prhCh^g|)PH_yTAz&PzThyEqs zTd=M7$UeD^FZ1#hWG%N4b2 zomw*2CSp(4F7LHIZUrOBJ5(0y^_ceemaI2qO_NKN{M+*P zB)7pe*tcU_4<@m7v43ACZ_RfjUc~keB!`cJ&hQNkE*JN=?&2Q$?>X(+=dw>Su9<8H zkyG#XV9%Lw4qN8#SNhTPrQ!}_tI5`$em_0+a({Xq_P%WSUH3Zi8?d$J%RbHTBgf(Q z;XBBm^Cn;N??wMAnM-nzTABGpwlUq95Cz^!>^8a654iYLobW`09$HPTU}_7fca%Ghcp-_ysSO|Igx;+Md9l_e!2- zJ+39Ec4vN;<6R+pWTCiwweMIi&fMeiT)y7$3*>y>UEBfe1MogV;X!hJwgd4;Z0G53OLBAayYQvwAHuer+={&xId`1w z{j62$&rWQwkhg@q|Fv>u8hg(CbLh2TC;E-__sGfdto<+geh_DllK%rr_9niHy(xLT zHd)Ips?<^XnS6&VEq)s~lfDToDi`;RYVX-Z+#}-m(XIoY!(TR*+$AoDiV zD0VIKEb?UUe0gjx~xAy7&{I(ZcNB-=$ z=W*(J)5sOpoWDzTah5L7UusqMQ$4(wc6Xc0@#LoD=bV0kJ?ropySA;qT1fl|JC%#`7@-Zo_88s_ICQ-8F5soBlgq9|l48=grUv4x+Ep_XvDIWwG`-GtMDrt!m=^v_J5% zVsH0hdqM2ZumrAP&l=`E8i>ohWL$Zt{rR(BnpG6PseVa6l|EVjQ}Gt;$*CDV)kU`C z+<|P}@HyslDtQI@ADsNo9!M?w+T68eFR}Nb&&Cy!n}Z26+?mCj0})vtV8JDUk1ofzXbvD?Sb9Jgb7o6O(5d=bOgUpKWSYao2A{ z&U@B`)al-(;*Qo3|3&`|KGSwGB%gn;30 zn94Q}vR6{8U&WRBe}p`P{4dTJl4p0(*M}3K1=K9rAAGCG`;ha!mi|(6))W6Rej2jZ zZh%9`!^CVyep$aih?&W@9(19%DC}x3vbMRSjltXGdH7d)-hVNCOkPs9 z_w{!_J^Oe=@*JGCs>61&n3?zssL;pG?8k^nu1>5d>PvI>Dg5Kua;`PzUr5h;?+HE0 zL&ao{p3>iQ^vrYC=3E#By|l@__yx$GnP3lPKhE&~ZL^52OwN3l$wT?mN6?J?D_iaf zdEQ!V^YBOZ&%5L{?3+{-XY)hiwq(nizb@ue`rdr`&GsAmM83zMCR-yhjmbHqGoQ)9 z^YoQHvktD08;g68{Wf?BK7iyy&W!n`qBacG_jvNbQ1(ulgR|ge`bu2J=iu(*M&WvR zx!gQMA2U6tC*({|&W{j#IQ<842hpFQ=h<^1IgoxTs*1bYwfabnT1VVh;$GnEK>q|^ z2gkDAVLV5QuR+dSZ;fk+{~YJNHY4w-%{t_);Q*ZTYoXjq4XMr6gghGW!k7De1^emb z7unKZa(o(_P51u?0K2zBlMGfJC-fy zM)qPQc`)?gI}+#bTc?)aPEO6snVou^yc$z3-X~+u{>WacBG)Ic&~NJT4dm>P;~=#$ zbFhdlYoBqY{xvKWweD`5yX_Ql`|=Nk9oVu48TUA`xnB(+?+@AgzvAqz8thraW8o*r z-Z)yj{O-D#{8`C4f$z|F#u~XR9AB~)Y#DpT^aJ@*d^3B-p7-4gFN4?MJN-Q?rV;xN zkovM;_oD8#DcRp*k}K0Iif{F-^}dzGZujIc|f4|}>(3!2j{_Z4?VqZZYV=VdI;$Cv{A$Qk9^)m~nUQA}I0lSFFShBZL z4^w9+(_i4rSf7F?*uIABm-_m@2ItRo=i{@N6u*<@-8R?9bMBRG$=^cu#F|nePt<4D z`5nlA3pvke4fWUuh za*olb(Yx@UL2d?1>G}5nsaMyLGoJMIk$zGaa>gVF6FY?cMx1e+3=hzc<(q;l*>iWw zT`%+TCHZw+m;HIq|DOCD%!VV&#W}bSd4zrrpx+Kn=($7pBj+8mM#<4h=5hpI>d&z- zot(PWn*5dLH5KzY+oRrpu4mQ4IZIcw9l@8m?<#g%a&qfSzAe~R@xMUd3ujI4gHxcR zK5L6VQQT?xB>W~@p0^c$E1a`sLAj_qf3fu>e~K&lUL)5Qe=)rmeL1}&c`!NOVhz0G z8T8ywzvFuZj->CS&*V&R_DO6kY{I%*(+&7QrS|DVjvQF1JI-uC&0P-${5aq zx_9vars?s5PNKy?}y3V^p&w>eqMyh@E%)#ye@m@Bzb=+ewS@5 zJ_yz&Czt2)^&!83AH}QSIb+z4FXLUGp7U;N{@d7lR~FyQmF(l$Dl=a4zVYL{bI!E& z@of6h;=jfx;kDT}g}unBO?Q#=4Y&`v8ZXi&_vrj?o_ez*`AfFs@xkOg|3iIEB~KOq z1^ExY_woATGkn5_|UT4){!*In147SaosF%ANjmamlUJ zhtpwWF$?vT+Axd!CEtDG@?AcfeK31(Xk(3Ddbmfi7O7FS zw5^BN7MJsP9Wfti`;_<-_}Y?_3$O6yzpb(zId!kUHg}Ov6+Z;GhE2$q!)VA|Ie+IM z`8!iT*{hdnm)|4e+kk#AeJ^~DzTY9AqmMjyfByUMKd_W<2E0zLTPoHgIq^4rnz%dp zHl`1TPhnYA@f~&s`&Rt(=$Xr%$@!+3N={zfiYKr=i1)JJ#c<9<`nf$@^5R-}pp?!WMN$uK_{~r38^z6sR^rvBoxaI-dD0=jPY&)1n`(E1J{plTo_s$huTCPb zW^aRQh|jzXXHQ+vH*`O8-lK|rKgd3L5ArR%R7`#SZjFznU(44LR?@rC@571T zOkdq`-YGdc7EY}$kXqcFKB-*X4R#ijv*b6njQ=zGF!Jgu>u=1-jVf{n@nxt%Pi<^S zF5y$eFOUO|h*_;&*7RBU2PTw@dXT--4qjsW1kYhVztXvZOOHA8J?D7xGB~SL{1$Vm zzEcNs_MBQ(oY`ye`}F$kIj>T$b7t*JzeBrmIA>sL##7`c*?L3v{Mq!QJm+ot-f#P7yZ`G(?`uqB^5vwwL*LMz`&Y~{Y`I%x{;o3@neVL6 zb9gp>1%HEw@h>H3{#tpL%>6a=W9YG+M$S4MN$yp)|8dr)AzNi-@jFLyEw#I|w$sGq z`E7ASadpVax$L{-?;>%V;oXcW=iXBGwb>rWvtWca`@->T{qQh-CNI~c&!MN^#`M&z z?7@0CwR0wW=Hf~5{m9?ox%@TRhVysCZ?UI+&Yry6e4KeXNbE9v4Q$N*UD+Ly{1baO zZ7-zPfa};Qs^qD$=lgxz-K>fDlf^vGpIo|~o;j{b&)GZzH^i;k*41BX=AW<-A|I%) z%=>=i?3tIv^w!76Y{`jp$OpjgFt|ub-TaTZ(<1ZCn22&4*?%BcL-KSUuB)H7@zaod zP#2i2zvRnMwx97PzKJrYS-<}Lt>6LrEO9rJFEh5WmF_wGQ`lFShwP2y&gOVs80tC0 z`0j-*s*C;du=X#KzhZlff3N@i$%hL{#dl5C>;rnnHxe4cB<=dbPmp!bS-G`-Qs0g& z6?6MN&U;N$S8DSQoOL&-gCZ_aJ;ItSYN3-m@25 zeluN3&+mVq;hg9BHb|Z|BxkSm^XwVc>ruARY&k3Uhidxid<(>fl4zJG0fM=f3osdCuNwDfWErlfTK!ReV)&89BA< z9x?UBOlJF++#J59_k-lnMC~%x6X@B;+rX`2^Se{d;6wRRGxq1te#_dPNWLCslDEMh z!7b1jMr!+Txwx+%iuZ`B41bVm@a79_P1{ z(fq%WUl;Q+WIrXxRzq?)btY>&k*`0!86cak^jmaMtwWo{pU ztihxBf7<8#X+wTZ%nsU(7XJoZM4yM(V;hgh(N7`IhY!f@@mM?tQd`GWdk?lx$;q#* z+kLnm+f(}Aot{0AJ45Qs7VuKJ$j2&i7x4XIT)9gnuimLDejnUK+q9|0KZ*S%`g!;b z$p7b9_Hf$XC4N_Ne=I4!_j5lygr4>NOYC;qT}EEbpZb-(Fp~c__F??nlP9oEC+BRs zQOq&;9LTx$KYA0H;!oMz@;8My z$+zQ*3h(9J%lyf|+=WiA6i2@ueuSI&v-bwDr@rOy7mURDO{g2rJ1oX0;;irbo;w+T zs$JG|T)C)O8&no`JvBe~+1#~S@IT3a4Njd-Ene==S9a8Q=JANiBFA^aS(nVgM*2^^ zx}NPleQeA24eUZsuBD%Kapvl{XI%{Ew@ z%>ABtDvsQpy)&Fl-lweAz}5UY1OH~bkbW}cT^?g=ufL68RF#^_wzO2-o8P9_Wlw$k zgRL3<3tHs z^XHNII}&%5i+cDc?#Z6oc#r;W!2{?$#bl2jr(MpMoA{FRCsdkCzU1!x^d@|D*lNH! z+cOcj;Bn8nne8&R_`Vj`5Hi2r;Cu2{ z?052KZC0AIoYh_VQUmj6#AnhEAlLjaZYQx4-_l2m zyI1@C{}HB-*5q~A$Ft|(b*09g%9e9xuD1EzU>5l$IA45yz8A^aX9IESc;@WOQjyDf zx14KF;~V%!;>~a`ye|9eIQ4H=+#C9`{bGD?l#999fxbTbJz{FH=iJzvFZ28%9s=ix znIYyfJOn?$c8K@M-Wwu5e}^Z1j?tz&{|%5eyPK^J&fYpi+okRh8{=!)x@y;u{8qWR z_h(LfiNBG)8T1vCTu%MZ_*1vGfLYL%{#d!l+gs>cYWo{KHGe2Qxs-cZ8*=tz_Du5j zK5{pFsQ8P>+v?{G{nw#qKI`*+2m6yVhgrM4Z>5;$A$zd{`%~=CS*Ln9c{D)G$L8R2 z{3yi!y4Yd(OZ*T16W@+AzNYwQ?Ni6jB5&>a$+fxqxQneF+r97^G_%i0~oo(r3$aj%5kF6kUk{YuXegyLS#t3b44@+EfjbNy)`u8Pak`;9ZKI=+XZqgIq?L25j|sG zANQocSXIo)h1zYywyC(Q+1in(@MnE~q9-TkxL>~}?jrIL`WS!*mWscNIh1_@{YLuT z_!4clW}8Ru$2J_-*Jo-4KACMXZlVADJ&f_}CGx)HjO%`FpVc;XVIVnYWCdj2+C%E(MDcAP zeP-`0Eji=qGsriS)Al}kY!lhK(vKJ02{OOQtG?E2Iey)|{ZC9i_S5Ov6KBB5mBm>y zi|=jv#`r@xjQu)e&DgGG?@r$XcYw9XO{=|w@#jqcM9e_8v%S|`{$@C5-Tma(q0ApW zd*&J#!Zw*LXUS1`Eqp;`ah|5$z5sdGRj?c1pY+ej*<;V+vBtHC?KST<1W#n!*)zwH zUxb_V^$YF_ud%<1e<}G6(EciN>QvT$6SlO?yB)@!93Bj(ip%*l81nZMuI5{p?PdH7 zq^_lQPA8|Y?8nsGo5{PEitp`~IOj~2n5^OMVlw~hu=Ry4VL!? zKAOK1Y|Hj3-U>c3j(pS1*KgkEBJx79IoppGmv`=<|E<`!(06@&I$jC67hIrC&aEBE zL&SE%BjHAJ@?#fr=ImPW^>Er>%=RGv42W$7-&*9{%afzY>)zz#W_!5PcygDyyK9DudyWWA1fz16vZNfX?#dwwYLrczHaRZEZD*0!&{P*S4K6~k0xEAJ@ioBeQ z8;Z$3yp8>G_l4wVYSYbZC$fzYGY>znX4L*qK2Dp|)ic>E#9dxx57GZ&KO0YiUD%U% z7eW*LJs>9U*9KOU)l6r={^ZRd^Sd2?a{N8x&3(S9nA}-2Pcvadwp(%L;XS?^AZzt5 zY(&o8<_U7niUl~ilRMXTFo#?X`K^8zzHRhTmp&cpz)AG1(F>5=&pmx9c@?=H-#_Fn z$%EP3lP5r)Jq~7(cVMr=?-)bs!9MIcV=re*PTfM!Gt+Jvq&CcEyO(??9;rf@8d3&#h-%>XKzJMZT}6L zl562=cm!JV*N{7Rv!6wN8HU1X^xGl*_kma0y3vQhG-#^-uI$O#tkVV+#XagUwu8&o z0dfz#jy<_CKzwpyCwlrE!=E}givAQ%KFlkL!FRzUH_Cg?^Tc$(?f{sN9p4>zU|2yuy2QZLF#6MO6Ovg z^M-y8-xRioA$xKO{S&_Ad>5Q?e6OD&^wwf_qYuQn!>?kykDR|_wX?SUjdcKfEm()I zIp2$rwa@P<$-m9mD_~#pRQ!j&$5`L5w7tH%`1Z;3I+JU_WPQ&Qn{~=FuE6iX3TR8e z5Hc@q@R|DPTq^4OF}Mnnr;Yhf!*9}aPFzc`r{8DTl1uHiP3#@y^~nFj@6b;t|A;Sz zG`^5c%voGt2`3JX!x@tu7FeQ{@B#T) zalewgcuvmSqu5e2vzA$_`FwlOv;O1wH^%Gmtqo_xH~gz0@BJ5F=HW&>Lfh1hIpjW& zG42YRdQMOLC1kzM#d-eraHxGS8PC;jqe}CI^JmdFu&q!36wiZ)AZPjW<>L3N^Td1z zSJ6kY-w3V9i8;5zv&r#|hU}S~1G(R&&!5S4p+5OUJiJ`wefB`+IcuFWF8P`}_(*)K zxT&6(TzeEU*LyXR#;GA0V%VQ%iFGJS3lzC(HP!;L*@i+vM2(IPZQ-Me%!8Gx3e+SL4*M?ZxDG z%j8bxaAW#B@=W+szum<>$#y>dCH%a8lK)w|^&o$~Fhskr`SNbb&&){+^56J0cmi_o z-A&xH89G$A+IJ+ zgC3P?F?~1k&Dx%W^FBT8->lz`;yaeqjBdsK>jZMn%t!D~FcdO3srTbNb6fFU>HCmV zUw^^L-{f=JeomfhEp8RRiGH$QRvVWh($@@9%&EO>ZUF_M%C*kb(R^*%yXF?k>$-&?7Dx9-wJ)C{>7PQmm^pZTp z_vp7oz5#N_?0`?j6RV3edFyg<2Vc#e`n|vSZ^^f_x2CVa6LE5LAzyyOdx5P?U+x`# zrf&wrD?CSBYDnJcEOS^#%oMRbBC{U_YqPB<{we%2jM2v;JiT1pajS9O?^XSMLw+$b zP7V)*n)JvEA!|94o;CP~o-=))O6L-s&6d3~P5=AioC6j7`8T5b;+xpMggmdF_z^h! zK6l2xxE+jSdjzg8nKvs*hmoiXS8^aVVbp1+5jeY{AYxofLk()nU5>QVY-5z85*?+C_Z1yZl7|65mX(BHsh;^>+{b zf8uV1t=R4r^SeH-hD*s=tM|oZPiOykWV@5?8MYhoM&btJ?8n7yFN--0ekIq1b;!GD z)0%%dtSvt8_c{4_{^ZfYICGaZ>Pt<(@$o<4d*_aTu9E}0{yg~%r}CZ zwVDev;7-r^-E&^zZ&h97?HeU`IKIzt?j6rS)~l|zwZ)7Vo9~(i&=Hb58^TODfv+V# zt6bEl-N;#^+=Wxq-Xos`nd`Le%a%J&XL=j*MA-H}eG$%?y`Q$t$(!Mu#kGaT;$DEA z*wW7(_*5~&_-e850-wO1Y{&6e!e-=l{CBaxfiusOwHt@C_fKPQ0*{hY-@35pUjGPQ z&VDetA9(>}A1*O(>9b8`ksqhB=Wcc|{ULJleL9@y8S#yUJ)t#Q+V#}_R=M9^{Q2;{ zwzI^3!QT(E4tJI1Kc2u}Ngjtcr_U!}hqG52X)_hq^v!t_TksJBi8vw|&m8z1g;BYb&NJ{t@Q&ENa>RorCn$ zhF*9Ltkm}Ya*=Pb-OSgBp79QY-0>%w=Z(mXpe`gIGGFU@MnlNFT)-Z#hQGCGN>5$c zQEdKx*O~OZSEH)p4A1W)*-O{5H`69_GXS24+t_=sEhS%uAJ*n2^5f*+$hG)W6CTpX z*Xrt6@18#cf01ppm{np@7uTY%p$`|IXLN!ZumL@1V&?OEaWhLruI7x)+)ai2UHjXr zi@MfH>_uYFgT`mT|QF(vKi zh&h;^@tnn1hwWDSLF}2U#8qUjg5V`;7fk{2?_9RL zOa9y{XGm()`tXGKtN2oe>hu!HPdd`pfxxuq%(03;v%r^p`0f(^lgU{L0 z*G1%iaB^fI{t%Wy+TFIsOkLWH?+5di+#3R8Ao*Sc&la2UolYN0PA$qA zkoTyiZQglPdj72Ec=Bj+Gn{k&fvg342l@u(;=VGN+={$2G==)~EyT9g-w+tVRuiAY zHi~bOIjcp^zyGhI_vf#z&yCq$^sF)D7uajiC-UXIcoR2-f7vI>%a8FH^xUDc=a=g< zXK(J9$(xh-meRL{4cOkJSHNic9XL5wiVR!RU+~;cp8K5l%icIbo8|aZSPXR_IdTJ@ zRBW}2%ryEGdj2;hG34y6CFC(M(U^ye%|1x(UI)LkXC8m# z8%Cc*&a?i8tj|sOD)yiB|07>=vaLQwvd^gSdjQ)O_xv^yJxh^nROyL ze-^iw_8Za<(e_u!nhs_AnY_?9$EM^rwavOtVas~uOnMq$p|1njv(7ndMv{+U&)qM1 z{Tt-|P~V)UMn11y-l6w@F@wqZ{>c1|#xv-nJmW1miF_V3VNWfX!> z)#XoaUk;myd#9@Su6adl^7MGNf6326NAg$v>xumZ=Caj=cgS-fwO|oWZtlkZA{@ zsF?d>hds1g&7a?BGWOf}7qKrEb0>Zo-@|_cw1#ilt`w8{^E^FynDvdm9URJ*Ju*XF z4>+4`Y=!;6z9YFe?xTI}CB^R;1H^O|{}222?5WQK==D7JOUUnb53yaVZEa{NZnd~q z?aAB)vcC7hCHkM@29kS`ufsXx8pMDZ`O-^%_P9uL#_564gQoy2~YzLF!2$vd(C28+dvg4CbA z*{>i!Mt)!HO!NL2zLowY`xQ8I^C7N+W8Cnb>!5i=fw5F_pl}ZFCf=6$Jc0=JLYiuu!dqzugTbx?{ z9Nt^p2ISPy>tQfGXGP>CRmGiYKW&zg@8)Z*pUL=Iw(QwY$u-D%-=pDnan0DfvH#$G z>aZP2&;EOzoIQFCc^W(i+iBZ@ZK`(tao+3o3VVz_|Bc|R!yYAjhA;KM79@{y$4!2` z4%s8gvp*pD*&fd>7ykzLH1WM)8}`rG=J2JS)n)q-Z^Zry&cFX(QZDXLt35ySxQd=T zSx>R&(#!NYkmu|PTZmhNzk)$9*m!4(PmRd4YUukEG0Vtru_vDfu%+gwHsq{IU&-Tq zzh)giz&r3w6LTRw^OJpcw3u_rscDnQPl~${@?D#}$eLX#Pf~LilQ$IGn|xzcu^%(< zjl5&l>Mrr))su($8j0z}m$geC<-O;#5G%v~!yO3Xf$#eHCJX{ULoMpEIuK zAi0-0%XrR)FUZj!6gQuqJ6^_<`RhaezqKJJ2eX&&CFe}ee)y970$XQtzJ;>>f0J{b z<=%0H_>HwoZES~guSwpvpyv*on!B=Gtkp*3 zr=c5E{KxN$$r{zx=4SFCcoMyCmAs?xK%SuA_D)OoiSRr7c>Z6> zm7bY=TH{%-(g*VGgI~a}!U%0Mhlk38VIo;a`})Fy}HPg@5EijzGdRr>cLnsYuImqhU_cEPhs01)+MK&FT)GrPUu2^5~r4| zS2o9x?~6xkpHx^zl+4p=|zsKl( zapr3j`3-S7L)RlW#_M>u*7#WdJU8pr9CoQH=3z(jX4>ce`9;ZlLC(S1I5qOv%3>_{ z(NpIShvZ7`FB#{Z3n$BpX<@)l}#?iicu zW2%_mVzNj3;=D)B$}RM}9$RuT`Mxh-*5W3dbMZEJ%jD*D@Pd9H#J59rSv$|_f-k1$ zduBxJY=5$iBEPHuUt#lV(-vd>}emjA^7Cn08Pg zi|I%oM$S31IeCDXCty=J0aEj~5|@2Fo!o_-Junbg(O;@6?!xzz+d$8ysFL_T86k%jomyL&disUjt+4r$OGYJ)Vf4!Z*W)+PsM~*1T8t+Ei!- z8N+<`Q%de7)}sMi6}chYNKUQ~fivJO`pa+<`6jVP;`}DHq4tx>8YyrwLl=;J*j z^EZpF7kwQ2`p|-HK3ne5{n;~T$+h!gclu}a)Q_Iz#`qh^yG)_C!e#njkiPe^77NJr z`7*~9_#C_*&e{Jx`2+sc=y&mF^whQ2$z%Q-?`V1-`n5Rq@M7|-;ugW$Y?CUD-B^EN z|B?S=_UxlcIBS{Q>q^d@?qIwzJ+(g1{U3b^Zi7>M>O$)B*KC>hpV^1u?(Fqooc^z6 zYYUl=;Z5>)IBU_B{3!lMzu9YNLF(Zy@C*Aj z^wfu0{7v*zmo0mty>GPq+kAmQ`hO(~%$)grH zd*vE!?<42gk3nm;R`^b~AK*35Nxd4!wr*wdJ$4W|&w3v3$G47{Q|Nn%U&%HT9wa}3 zr{K-R?5p;VC(lTKc$(+tT_(~W5SLosTK`wDr)}z9&gYh5wx`cyKh-npvt2L#DYii{ zmwZ0o2yw&7C+lxHB|%*j3f`HE_B_K~OA2+8_i(FyI_iLR9Q-vAf%=(j6)dN|}IwjUwm8Zo%%> zYhSzT`;Gth^LfTI<{Wdbwbx!T_de$Uy3QK7kX8%l?B?G)-Hd;(oaJ(=*i&75J-m#c z@ehE-@=wIM&jWB+dm}mcn5tsE*2lZkl0Sx> zH8~ot*JcDBhL4Bl?Em2za`Vi1kKaX`JYx={H)Q8owF7^Jc;1h*mz(!4-kGMzA4AW5 z&w}^4F&?frDtr(^l#4dwLsrE!ENP^rva>$@!L@b!aSh z6FYk^&&_l3M|e!dSlEs55_7t@+!5^T$5wb1^wTzVXDRJV+=2f*eHs4-s8ufBFTS9) zgNC%NX-~4#PtNKt+P%e}#NSCQ>z(%FXbr^Agq`^f#OmUMaeX{QysOv)IO~z;WS&*S zjPrh+{WgGIo1K39@N=J8!}B0DY6|@WdJU+}&ipiTub1J>XXf&8vJb zU#PB44Ok>T9j1z}hNW`H!ZqxJ?ElT#W%e7;sH%9DWo-`Eb{$%Km{=*+`#9Pe?A-5n zc(T}8+T_`l``!?*rT?zaHT*jAI^)!+anM%$5q>S)T3&m69REXh=DSCOZb|0ioWhd|M{VV*nSUt!-%QNm8ZT=EFN8bIk%v1JWo`bXG{VBd&UoG)x^qlE@t{Eb> zAEeDB+H;V3&tAM2=Uip4_QxON{F_|SPhnSs3hb}VLojlc`U}a+nDP#g`FfXMotg#FvRr%S=zwk50+Hz)>is#5-cvD(0d_8>+cHRZDZrP{nRu$jbX3euspU^IV|2xm} zo@PgDSFz^e>Ax>dO|DPN9?9IE0y$e*rv{Mqt!_Lw(%v_|tk-gW*5g@P)+g^*nWsP4 zQ`Lkz^l|27iniI)JJOf&Q!{RtmwTSWuY+^#e|i;blY9J?|0*7Zx5jtLe*?eFf6O&g z!!Cf5=gw-{R^rEq-wGMWSNJsAI;)HK=|=o3=(UqmDc)H#KW*6;(q_W>?6o-OW4>{n ziw|{;duWg1?6a)hqx?Pfy*ti5jjUJ$+)I3S@mBZ<+F@{lc5lN_`Fn{S5>31#e-=CS zvXT4#2d6$hjkEtA*JdWJ^o{O7pN{LYn1ov=_NHryU{rB9(8c;?Rfb)vu)X(@G|;t?9A<@ zV!Ks}?<9UTzAp4}^qk!%=||IU>+P9@YtiyKyA3^i@k+V>;G6M5w9IkdJqEBJb3fz7 z@_c>AHLjy){VsfU^Gv6~H z&&kekt~Mw0AA~I+we)&go`umC!EV~tle-yhC42;Xzyrn8`sWETZzf0MV%FDAY?~Vt_ z?+8b;*C`kKeTrB$em9(YzYyn|r$RIK6>uW^_4wMPmfXNjok}gq{@+Ra8*uJ-V>}hI z{~p2v#j>xjmH#Zi#P7=9h8;ia^e6rgtTE=Pu5qNCCH(B2BjoNVX9rjfIZHVgtsv+A zeskZvQq-->XeYoR{*ll_ydghxlJAHo;JkiMrhlg-k!C+4)R38LyJ_qMTQ7?tdI)ZMW`Sd_Kq? zo#)-{Y`K5!SiD2#w~^iT(~dSmJoVxp_B{3#@Hu~>ob&W^r+z*c%Q@PgKA1fa)`4$f z6Y<*m%Dci{{7JAbY)*~7W#6;PZ0 zv7FPyQdb&?<^3Y#X&}}RXU)EVL3a<)dYCsvBOx&r4Jl)878c9-+J@kg^~()QrDX1|B?IVA5F_tXE&-;`gA zKZu_`7ixDaKhK(6YlfV*?APQSE`Bh-5&s`{iQQe!KH7BVPm+`S&hzRkSOYo3O(1Kr zHO{*4UoOu1EOu>L-jPO>ig9d%Q@c~&Qm0P9*V5j?dG=@T98Pb}?vL}#`Ui$qig)wN z#GkcCzTxM+tsk5OOX;WeDZYS zzsr6`ybJpx_Brg_>zm>k-tc=A)$k-3%)VOA z0-X7I91iU5yp`>L*j<}<@$2-yFxfoah5yke^(5EKnV1i$6Fb3%v~KWU{bcRN(cYGu zJ(W7r268@HiRbgymi!;&{VtvwauDtTcWZZ(yquZN?qw}4`{!fYpYjLxDZcL>PH#&; ziC&9#J3r5kaoP>R*QCApS?tuvuI%ISY{+``FKGvP&JP!x9I!`-Wsa8O?e&|vcokmc zKZ|p(n}~P7RrojBOm>BxdCa>*4ebtXw3WLBKAPSKE)x3yFNB{w$C^4DW$|f{IXw#U_hc?E6`u(=t17-zn}YvxjTi7k zax%|%L-u>tb}Y`^{#NDP%{8y%-wM@e-_W|yI`Q-F@hrZOmUA;k{8rj7>}l))I5mHx za+@%7lVXsNmVRu`WoXVZ>@+vMy= z&pq~+d#`@;@7$iPpVYduX_>>+h1ALW#8fPsdso`^?C48z*7_Pa8G6Y(%J^2YH+4?~%f;{KhxIPj zc?Cb~pE>(X`_u5XRmI+!gfrHCX+Ps;)^h)&xEkf+`rFdyTEDDK_CWS*o*}7q7r(H`?Tk+4s!}Z^n{%Y)!d%>v{V{lXcS5@BaAoJW@n{Qz`dwsl-_(+_2%D5ZR z6RSnPlAnDs68}S62Xem+aPGZ{wK&>!$MLhzQakgx_E`4bv}@TLvCq+F1V8JP`+XX3 zi?c`m#(&`1C1)T12^Dj>r<|SH@8BMIBU4LPhSS3VNY7FF$L}{`8*sC`v)~O3WbNHA(Tkt!;T6PWEYB&QX!h^J1 zU|h+WwP>kfm-17C8ddB={y?1j%=6@0d^28YE(f!}DY;L6=0APT!yD7e(2Je3cN~3< zoVPnN`JSihbA;PQ_hI#Tm-?LRYf$jNHqDEnS^RdVn&e2Lsw zaQ5Q+;&b6qb~x`(liAO)_ksK5o^KBG+t=Ux88EtRf6{ZFQg6S6AKhbO+l!5aq0o{4 zAg+Zsl#^?YXKw+sp-b62A}wd-QSrs@FZ(L(cj7O^*~KVJD+Pe;~!or_Das& zqi_@qF|O3FoR=F)#ae%|(%RE=%_a1l-vL#{e#rc!-i)S=w7>K0m?7_F*qqjte=0rz zpD(X2-dXH?ya%N1bNr3i8?d*5j5*`aT72fZFY!CFcY%N8_2ds@e3a&hv&cq=v!61%T;49a_?R7SG4Sr+WL5(pZeXOmU_Mlrtqgh_CaU!_;%SERg6*l z)SMywqu^WG2l%3@;=E>0W^8lij1kMdwxxZ=&b!Qbv0L5K9WaNTI&l#E&VE+Tmh8pa zyvEO(@6E3X+tNCT=ZrnY{)+Y|WFE6;vbWEmpHCZQjJ5f>_wOL*XS!I%bi4fLA)l=> zcTL1|wqIvYFuu%9&cq)4d}ner?P`A3^Dx>@+U@`?p%*>5XVB~6ZE@=5bFSZ=-(OB@ z_0F_kaQ1QXe}m)1Ci6F7UyYl=`*J30yAitsyEVR{Om{vS;|9=*HWji@e%HtQ&`s{e zVz*9I*&nngtZ=_9MXel<9-yL2MI|#m} zZRh@f7QdIB@g=^RKbAcVPN=x1y}pLt%Dtv8-3t9+5B&EgzZPeY<{Tf%&wBNTo3yX*Ue>agR~6@Xp4{Q=?906S6g>a!QY7Inz_f_X~*Hr$t&`%=jV5fC1H8l1C#I~xSf_W^bkIV-dFxQ?DN>k z-whrWKarpPoH^;lK7jrU_)WF70)Jfc^&Nue&#jb z6>aJMPnDCiz60J*tPlHIJc6I!h}*J1fXVdXFaov^%l*uUT=O>gf_(+#9BqN0#@khj z&$jjWFYD`E*PK)3IRdNU^WHuy%F8orPyQ(R^PvI%3CL&HJ<7#*UL*NcVjI$WvQs0n zrjN0+*460gYnhyrRy$v`+WJqOTmbuv^@ERLM>(}=Is2RAH~94+zoWI+_6qieRj$Dv zk2}N1w3A_P`By}fdnkJ{dl|kJe*2ldjb+y}riSh@b6CS( zIv8&Vc`w|wQq-x_@F{RwVvEM+I1GYmR*Jc?9K24 zcpA?69>m_R%KGEEw7uEcb8q&twb`TL9{O-v`e`k1BU<+H?~t)i<7cfe#Yf3&PRpAA zBzFVa(_;TXHCp~Hy3FTCV!6i~@lA3L6wloC#i=#7y07K@-`JV+ognM~d)l$j3iwy> zPsDj=YKT|C0QjHw7grUZUmg`}OuGhmsua(ZN5$&X4x*iJ-m{;V(|X|i2Dg;GtK47t z^Vr|9r?IC(&c!$QzuL{F-^lL@eW46B+*j7Hf4MmK*WlEU+qB7k9tK0Jir+te)<>Sp znWyHkk(^s`3)n(GBg@X0ocvo6zv?5;!=ChY*y};Q*BVTlA^$1-Ha-kL-n%$gsV^&O z_v6%%rln$?GuPGWU&z~o-e3H2ef4JF3U~AKSt`$o+)J<>y+1w#zeqnCp3-JIZ3(*( zeGu+|e<;}-cpv^V>>2X%EY2Kv*YEcDB=Mb~B|USz0B(_+=UVo}40aRuns>OoSM5tH zn}>C@|5;APn0u|ke@SddoZlF~re7=nao7yL(dH6bXSr?U)qpyyJ^%Rmv!}zvUX=F` zKQ(Cw{amqg@qC&@+md#>oMW}izPp;8^VJd(A4kjo%V~4m4^mU}oXuQLV7FkmVrM>b zo)+=f;w3OpUsvF(XoutG{HD-~-K(rN({n!ae4E1m1CN5{?6=_&_GEmv`?xien!LTp8z1R|*|6UePP1p}IF9+Z{eTp;Rg?|)(OWZ`>fYs(-+r9Yb z;^V}=uN2SboVzF4o5^`n>_a&tdi$=E{-HTuDRu|_B6=;DPrndyjfObS#CKo-`%$^s zAB|wNKIYH{vUkS$+_DyDtZBCn&gYFkwOOG3FVKdbXGr>L3^&nkz+bwD?sD%Ed)GdB zm-d$aaxc5F*SOY!{B`kemEzfv_p*HM%kLBU-fw}tm*h{?$9VRAxDOsnYmL9;Z>i1W z?EKqQr_t-Mb4F&;JF(lXw%+Vb*x5TZ*{S>Il#0)0JJ8RBrRFXBtd5*Tkb8Yg&KLOI zfAdp+UgPHspNdn5M;d>7e4ue(N?Q$?tL}KZ_U+iWony zYFl%Z`${cJz3#$ZBlmJ>#{UZEU8XaAThH9g)4}Xd@KSBI7kiT5432>8{;~?Mb-9f*I-JnDldkvT1DRvwDze;fqPv;MjpFQ-9^_!qg{@Y5<`fxOVir9MW zXm{`zYcml4;+|GQt}~3b5bCb>Y{bjy_t9^LAuw9s)9`<)if7q8cFy1W_*}@Et=~sY zVQ(z=YTOZ0FER&*>9-m`_tT005PNU@Gh|+RR@@`xJC3*bP3d_q4293wTjJLGI7Rz@ z?EGe&b)RSMn$kwfIfH*NRO#nIe)inE&crwR%=w%smS;=mFZa^Jc(2q?L;fxNJg;YH zvkG2kFQ?rIRq$Y?=yxGIwed`R9^^f&9)Gy&HOif0_23A=kT$z8UUV zF5aUa*4K&r?6>T@&$P{2&4SJNnUnaKr-E}2Khkaz`wu%~ohN@koaf{gIOA>2z6$>-cRuY0_E0<) zzh{2a{v>|(#j)DwY-hdd@E5>9c0FysgjV{x3Fo=?^8=nMcwO<$wcFgB9m_vTn>+9@ zm{>6neVlt*&O)Af(_L=}y9aF<&N`+BWzJ5JpBiuyY!aQvX?(9kfl}<@}6gw%BZb_CtPWXuv*(J;B^I#&u{}*Tj>Td;YCb)YBn2XW%h? z&C}O;VwcGIRiEz|(;KwZ&fM#{>>b6=hF*|$>LS(v*De>|(dXIuti0;9`^9pe-eYgj z$J((|b8p3a%jqSz5oA0M(+4`Ey_++7f*Z!mH)xU2g!)=N~109i9W#jWy4ke0O{+PEG2GGtSFl zk~Voy&To$G_)XwqT0RHl9DnDUe~3R$zlHue)GX@<28*4FlQSNFDX%-udqw8+0d~f* zF8nCADdfBHuKfGlZxep4Qt|oy7uqdV=7OL7oq5SvMzfEA9_%?{A8Jz_He$b_uY3>q z5Pu~6C@=4Mm(jB?hs$Y>o8bj`c9pqtPid z9?2MP)X!b=NAf%HQ+J*S=*Qu=^ivmS-@OLY=_lX`^z$Ll|B3LGob2U;AnW(FSk~wQ zJO=*+*U(e*rqQ~?{rmxNC)9&?p@#8YhEo%2!C$4~^VwQw)}b*99|c{9_Gl&nC`;S%KAxs zQ^@mRds=&(=f>f1EgV6+3x9}n-JH9bWi^|h@40%fw%043Q?MStmiXJ^6Y>7|FL*-i zO`J7K{WzALy_B`ebA1BzVc!oYl#A=0jI#%B7E7Jlo4vVfWlw&@o)RC1!CU6589uaJ zd>$SvC+m`Twv{+_YpB>^(7&P{x{u%4S*t_DMzT}0vVO@ML%$DS#D7QM&d6Zv_* z<-YFoxUeR&TDGtt5Bc+slM_ay$$^qe3pC3Sg(bwRTIcPWuFbhdB^z& zckWa4mG_ei`EMC(zpA3HXOGo~;riMQZ(H_$4HwIO2S1?g>GX^z`)`oEY0!;+75yIe z@%pR5-bMTgJc5?*!%pWP$?lHxXV0?dyNlB~GpR0Xne{7SCCDx_5Clx5A&7)i+x7oY{=Aw)}N)+p^C{ zw62h`)rND$cII!vZYsVde+Qgr)s>JoS(~@@eGGoXIO;k78E0#FQ~n-!Pn@$_U3?qZ zyIg$#oOg`u&7Ad{Xzk%5x&2q$Z@3XX=QR7^2-u1?*0p|Trw0C2W!<$Ohv$m*fXsDm z*Z7WpD*Yt>d}B(D$g|=a`gr<<_-?W1*txgi;%o7pkav%J>AOH}V@$hGXfMmz6_&`! zJa2;cmy1CYtn3{HPlj3Yaz?gi zUks@!zm%;JuFmgd9DkI|IX~;O4ebo~w+U@1{|ELm_r8~SCw_JFe6`s1?Bv&BufccW zQ|0U^H{;Hj@}2ih_!0h3_&)K>$@=WxIO{!`mil!xEU79!6Fp3;!#;%eu0FPf>a=h4 z{fO}#S}JPzZ?sWzhVt|7@&&sZ?t<&f{i3QkXPL8gX$QzVjeRjY=OO1Z>y^Fm8EqDS zlJ*_>|d=}2yTuMIzZlfQ^zXa#}FD; z$Ikw02ls394DC(1SO2SRgr9h@IES&P%E_J|UR6Br9^>Dp-*x$^uVY{?djjOo2`=X^ zV3+00kozrM;682@TS}kL{~VH=x$0s~bH96wy$PqV4~Jb=7hF{;)@-OYsl^ZCymw8M z(~-SBPQSZ|WzTE@IS(O%l#ZUS`xS14Zu~oN-t7*?snc2W>TrkH8ttMlW9JNabiLNN zJ!CzzFY4gK@K~`ogYU zcz{^0kcxUUp%5`a} zVV$(u6z^`VHP{QFnl?T3llP@(@RjoG%c%u*Xp7~{lyjQco32xno_VSXc`iJH$I!CI zPsx7=?*lu+8u8@5g%|PPV(;qOTdywWXE<$JS`F=XbKRZTN7F{(-MtrQFT4lcXsI)A zKo4l7zvrQ!cJII(TI$(a7^m+8_`}#Ql#BMY@wV&kzbUk@H)ca)zR z{Ifl>KR%ZSc42oUYA_a_^xZkC)I_;OqD!pmy2!a&r2xGnbS2 z%j7%>c@F%Keg=II?9FaZYskJ6w&K^pqaptu&nx!llNHcWd#H{Cy$cp*|!xXYzagApE7goU_yU z)9`fu1c<$oRzuD+?Em1rA3UXxhxy0BC2%h7w^Ffxo8zq0cDoe&J7+4_Tc@h1X%(D3 zpR+QrT>K`yBYP}+iGH^;-o1JkH6h>GEEaoDfBT7_4%xRi@lV3%x%WHqcKn===GrHJ zHhVmtjHluJzl=J_s}HFe57RRCw)|7%L%)=rHQ5h7r)`hx(fZ)jqSTQu+`|Q8JF{0nKlTiw z)A^0rTjImZ`hm~r*Spqb^dtEz;dOS#nEN`9pD`UpyBTKFF5#~aOZdsnUdWuxtMd5- zs+X)4Kl&8$`|u{Kiswz{c8Tk~B4l;U*n6!Kh^$Xd^?O4 z&sp1 z4Vg#VKu+FkYTy?jZR_giN}SrfGww}4h@E@-%)4YY+A-{1@$v3)3w$vD2|4@XhWzZ` zp^!O>p4!!w9ZVEogGb;K-~-po`LC_7sr(PwwO}QCh(4ZUXYM<&kHbIU>5$sm6mp#( zO2soiXL)F!;=7;z?9`c>{7LK`;2g-mt1^UsYixWD>iMH z`I?sX=nhjMwdhPdojw^l!d|f4JSF~#dsxr)-eae(vX{Yqurohx=Rs%V zXfNjt+CqMJoZ9y$)PWl2Ip^VfcIxjc*sWZ=Pt3zD`Ri5eVf!J^lkv2TV0b=sW29I(9?tS3nK+pY+7?-D(G%b=(#fvK!$Oan^BbcH*n__msB1 z`PE$K6WoBdy)jXYn>!^AbUFG-0v*!WZH}D0kkvuOQ9OO zr2PxpmhsE>{>Jk5k=I1*NLu!L_VJ%^13Urwy>BbHTf3aObIpHie#Ta(R6Mg%t2@H_ zv@Q>9ZjpkOK|qXx$IeDbNE;BYvNB_CwuWU{nntJCq9sUgWL}6t!SC^Mf`DUNj`JD zK+E?u_p;xIyazUe8E_r_I`IqfyU>)j34Eu|{2RhU#Qx!b#cxvOeBkE#7)I|Zf3#TM zNA7^+J|$-X%!iCG-&@zgpULgP&e(FsvPN0kR`_E6FWS$9_UuLUTrcP5RLDHvB6b1( z6pyA|q_2-~EB-}zAF*+G?<&tjS}lIo=nz;V_a}IsmfyK2ZKKm>K_6{u(6@%X?;QlG856})pIXv4;rGM+ z_zmGT=pyd~SWe#^kCd|szJvdOcpsdalXcmUeT{2A6Q<3>ukf?KhVysE|B<(XeJRe~ zE6KSIkLX=o<9&Yiz(Db9VcXcWQF30wukaV^GtZJ^A$_dNzX>vbga1X-a`C%DYDL}~ zQ_mL4&%YCJl33RC1+nS$r|_-#L3(Od)^RWXwQ{z^Q*m=S_i3BC8Y2EMjAOsN%D8$J z@4wjxS?{ayGN`5P{rrss{uusCxE-@J_gs+^pj%_!YcHU8siZ^AD~R-(%g(|DL@79%3J-kKyH_{^fH(p1W^~^{1`E zUyEN%%ihWP?+nZ3<=x`{>a$B6T5*1958|J3&ihZ0vz7CidCt9Mo^yXU%Xt_7|L5+3 z^d3-K&N=$+2KB{nuztgE_Dj|#>zgy$*}GKER_tk|;(O%m(v2Kc&C$FVDsM!2!BX>){dn$Ke_A)RzNjr}A?T zjbW^L$!F3BOP*)FeYOxk4o;z^c2Cx(ExsD6*pJ9rDz+Wuz6Y0$9e2f#>f>%XA3#0Y zB+vfp^Z}549i)zI1AEBX03WZv8n{nYQClvFEw*>b9O75;UB*~X-XG9TUY;e3#V(M0 zFTNhWlXoP4COd1nFTa+&-R1Y-r{78Z@+#+@-$#6FxJlpB`Kd#B$ICvR1?S1ne55ut zV4o~!46LiaU0@nJYtUVrviJwR)Kzv)Se~Od@K^C$Ti-nM`|)p=dp7iE z=h_b&f6jHDljq}yddp|`((iD4_+|d_?A+to_)Bd+!ezx44 zt?bLx_33bHskn!X@i%_zMy~fUUISm3JWp2@pHbTJkCS)azp;#AG=GMibzuPgL%HL% zui{S;YYtzy&xiRtvUA_rb1(DT8|S{Xv_H07?9H6z8zA%kGu+2cZTc3U$zb+Orwn_+U+m9radoV*rzN8E#c3w$rEzkP@uruzg^*W{E`?mkG8_6AqKcLOT>+z?_ z%X%N>dJ}1Bvpqleyq(zZ?DfR5@ADf;_VZ(6Gx7fT3VLeA5jgjgbH9?`5x3WVcJj5m zmY>>jhS<~eY5W5qf4}(++8}w&@ZEAVXD_e^;(g_`5zF)SWVxxm$FRqU?FYU1jqs_o z@$8JRKK#Sa*uUvrJi7<;dyDs=rRG$lH)Cfnr2i7splAQ~qrE9V`~Om?#&3%ck<+u{ zJxJRXFp_^K|9A0A#M|M_OJm4fPp6%Rx06$Sb+ONOr)7Qz)B1YGAIQ#mxR;*)fBA*< z+(*7&*hlOwddAjU&Y`#`G-hXvdH>8F%lOLLoX*bPXpQq+I-PzU{a}8RRmKT*`6YgP z@lpKjfz16_+6T_YB>aWg2(c~LBP+${?;U8_J9Ff&!2cD?`Ktk0za7QbcfI=T8FF&I zQv*hc-=VKs?411_A^p6HTgm$YXIxj|wAl%U!HN9b+v62yUf)Y;+rsO#oTt>1`uz4d zH8J^?f^|`AL(N=TI%FVZE}v^!-w_T~EcQQ9`*W3^qH807D`-bz24p3jBp<0bq*ee|j-zPD}Y zn%Qq#-Vs~!vmf`>?puD|(K2tTHAms6@y7HH z?2clC^pQDl0r}1$^)I+fUiQ}&yGZUP{G7!aw0mh= z^RwSuurJfkD{^w4f8n1Srj=j-e*nKLWZ&H_?+y2pd7a5W0P_C09&EtwA#a_kV!!Om zAHzOLEPL%l*qSzumfDg%bt1H1h55Pv7tQZY{M3|c;0mNpPS+;=%)+UGt;F+ed!9YFckynTdh;gxcF25gBR6}aCgf~yhX=t` z^yxTfew(UdZqCGW^qKj4LvA&GZ*~Q?V&6r(Q@{B;KNb8B{{^uR;8*=+oigY9$<6&A zfgj;N01xul=U@8ooMc}l{yKdh_U^bY&KWoj=WJxZY(sw*MpYH>+6&k_z>TmL>hvm} z&*^7{G1QTl`MAaS=F!Uf$QT|Jn?U=Mb^vVx&NF-$`5)n&iJ7$Ymuq!^m$d0d%RZgL zKT-RKXjA!(pb2D7t}lCLRJ>D*mseU-+*1BIVl(LP;M9fe_5ac*u(S7Snv2G2-+lUA zF6U=w_7Xfz>@azG?qv;9ic^nH3#zV9X4v+wUzC^MF>+?w!ZBjA@OV72~<>wve3D{H4t72LIJl8t$x7Oxoe(vieyl=(65bIArnVt8db;O?GugmVd zs;CD`<)#j0zh+JU=I30G#yOK4L2AR}{PXCkOFQz{@aMsGRlc`yPyalq9pt?1Pv6^h zGH0FO5$!&yxHq{A=>M=&W2Qm&&AZS~yZP{;_(8Z1UVzug=_#IfiRfA59{5gu#$Uwm zUR69}Kg4aJpV&HhmRKK1PM#UFE7r4Y&$H{vxf$mfk$KO#Nlkm7-Gse2{cZMC_8#=7 z**8EH|5Er``>*gc+NSQmCcD4;AB(dGj;3$VPMg%%?2~u-HDDHdJ2?~C$-P|rto41p z^@AJIH-M31P2o^_iz@R+OCO6}dj)+5=uj#?FScO+&CYW$_5BsN6)rZWf9 zAam9l{(wAVjuOwFI-kG4yxd3TyfHty-?Hc7A!W}{_H1oZ=N7Z?f$WdYC3_cg-F(lo z9(_Da*Diai7XCu4D|=7K`P!0}xf!m%yd(DJXYbX6hWfaab|-rfyDPj?DxRUY;1zJ7 z{D!cY{iix%jfJ!tER=Zw6=-w@A%)QaI%>ZNO>E*uIS<)-b^ z?9}POkUF0IeL7wS-`g7(($8W41w$&u_jWm#_w(O^oXxEL!ir}Gz8qhz&5n?H?uKuq zkAN+-OD(TSe+y3HKY_=H-D+%0#Lnk$37Ojm;0?(43*%s4I7!ZZv?tkl9%tUO&&NS^ zcE&VNY*=#GkK)Y#0b)(@I<%3rO<|h6@A%Kdwc`1=S$|@8qn|GKG4?;~OT=>KX7RKB z6WF=-C+r{L1GPElId!scrTA{|I)28QoJIIK`btQ<)P)1tc^1CRz6>(w1OCk)LjS@w z=F+;Ci)U2kBIhe>bqhOtxoPiWZtv!2zr89yW6S&U8|*=}{n*=z9|U=KnorO3elM|i z_(gnsgQ%5B>DF8mA^rtrYJ_ z+u(ulkXS4I9Ayl@h^Kx$LtjMyfqx}CHSj5Xpu8RNH~gW|p;pN}8cQ4Y-!O;&FfPey zhx1(858f*kdp|XxC4Wnp=o&dcFXEQv;=S=X_Uctd4ZoNDlf1oP6Y=YC^0UwKJjuOf z|Lu$O|AhL2p69^Ea(C9p>-3uVNSt$y^HUyJBw#LPeW?zmEtGUT4?hKZKX5!CwnYyOL&Lf6YercZ;R&{bPfKUc5lXj zb9S#Z{(9^O**Wh!;~M;|SI4U2Gt%Dt(PEkN+v(Z6**`tCU4hq=m%W}jUj_B}Ial*& z^Y~e_%lV~paepn?sp&hH)D&9w@Ad4|lRTqR8&ZP~g{xpTD2UHb2Y zN6-(Kle3fG0B$R}7o0s&9sW-jn<1wmPTk07^4IXEIA>xOeFbbPC+A>G_{IMI4{b4w z=RdQ`I|$BtRoDMtCAE<@PCRGoEV0y$G07MI4)$l~eXyl|H<$k_e+0X(zEfk55zBer z9Inv*Fn-3rg?_%qdDnZHeICyGWSpBo=4TE*2zP}|V4ZUDES@6&Y;Ajrzd~yRqiETm zxu0)vo*8}3^ciGd}U%HPxtFyPRrgwon566lhjZ>3G!)EX# z?FW6mEhl@wKTbU_{mZTJIR3f%%sWxeWA=S&++un^{@yUBS8+cl(0kLf7dBvTfNz$6 zEp+1NyuTuUO4`8Ika^n>hCvhY?O+qRpNnPgTfq%5%{5YYaxVW8yO`Y{er8Y9wjcd& z*pq((U>NpALd=I8yA znghGi*WquCyFlvCT&TwWQ+z+MOW{fOU3jKgAKU^@G{#-Z*4O9(#i6- zgw(s|`JdsOo8#r)&CWUM?%sOg{F_GEll#$A^RiB_v-4+v^8Rrvw1y#a|G*E6e_kr~ z_{Fpt>{hg=#17zp;u=}UL->1$y~Q3ZeiDBh@zn6@a%PF;9@Fke{%QEB-o-m$e$z;; z>m{DEU1r~fJ8J)gyf@)XXw5%a?n<$IF8!U>MIQs%o6*|wm%#n()TNW~(J&Sckh22U zrN4nU;@_$5QT%!Q?bx*{#oRvP8d>+9#WJ5+-v;cQnQ1VCpL3P^nmM_be{5CpPWYVo z_4NH{r|L7cCuchQC!b|M#2L@;(40O8UqjCsJfmX&!z6uX@8o^yF#g^8xkr2!tuy>X zOAX5z{ha+V_ixMnTz}{C_ksJx=2jK+pL2f$Oy_S0r?LCW$+@jjDfZH>{1z~b zpZU-JN{yUUD$Y&z-c#&%?Z2#ViTx~W&fd4G*t3W7zks!ndh-DO+PL;JuJ-c2!X0Vj zDn%VVP0si1<+SY8%vJ9FUHmnDocPW73Vv_A3`Xc9^Uwee(f4H~&yWX;&km_MFN+`V z&laZ!ylq}?koN{$#6Jo*hSaI^O2zMoljJ|jewCJU_!zr$+4C1x=|A(6`tdZ*_cU3% zW7)%DPiW2mQ(kKS-zDe2T)bCqBK8Eo7d`jX9~#3ewE1FrPUPI(NPCIa9A8b_lf5pa zHasG}j=ryjYoHcw2RI(qsVbfY=_6-n3|@~mMcx?kzi1iTAol4{pWT_(o86vW4|avj z(*$jw7R$W!hwS$|%f)`&hkdr3%uDLxCpc%jAA3B`THa2(7FXA1FPwEw`Y{zd?F3#UI^atoq(9dU2VdqRtw}-~lZsKQucZ2MWmSTD4f7iP>i`VLF z0ZibhMx7?MZD@M{|#vqugKiamIQ;MNP%K;`Eccz82^EoT+m5hq5-0(sps(tXb++BYByF z2Vra4L*kv-W7xa1|FRA%TsQACnZGUJN_Iy%IZF-Yy+b=04yUJ{-3Ynn&A1_~%g!FH z1~njiW0Cw1j3fKLR!Mu>e0DeMvME3Fna{;1 z9cK*d({gW%@Cvwu{*-&j?-z%#XVdo5KI@*dQjg!wZ^D_MvVL-|bCxom_ArNbi`cd> zR_<`xdN}oNBX+*e-U6o%Yz61bTY?9diZh=%=m{Ci1a00jju-H2{9%xq6+TDK6nqiR zJZ~i@XJZm=sxjo+tF)aDPt#M^C(~1tCbNI0y^V8ES(6{20@u^}(*DO@ow2gFukwDT zpX03WO7WdwLweR{4|%QFGhi|Q7x+!QKdeK`b*jU${5t%6*LFC&32j&7$?xfztJC;5 z((f1lzqQp*YR{o!>%nE>(_H@q`kTg;J}-xFptkshC2hqruS@j%J3sT1y}Q(UHj#58 zyE`<6pIxULz7HnK%lSJUXU+4S(@_5Ja&iwnV7-Tm-@LXg6>FBc|442X?kKklJNNe) z{{;8+u=98%Jv>R%khR{>j#=;2kZ|M0qmR8E^%1+(u$lo95tmPT{3_bU{ zM*bcAh2q%@$3Yu88UKZ@aTeSro_)KI*yHp(QwG4H>?80fbGV~6qr`vVuLlFz9bq*6 zW!EU_rzZPeZC3Cb%gG#OUB=+q{6)qvUM%PMXL$#~RC?~Knf9q6*<+i-9db68e;~UF zyaq$yhf*=l#dv@DkI?e2e;PmMI@dgsoqd#g)*N29XRfUB9HeKivS;^#{a~=`)|GRB z_)>U=Jy=d_Xbbz$$~gNs|BhVVlk!};-+g?~UJUsiFcuSnQxSCx{o1CAlU*;)umgmmXmEu|Tb5&6fwx_M8JqJ~=0spp=+OB;I zelvM(X<7Su_z?ak*5OY%$J3_B$-8w+dj9J~&$BRlbT9cc*;%_g=$WIoa2IW^HdnG= zVps4Jy^6V8pPl!p@OhPDf8DUkyP5pVSp@h91x@eIg0Y{kzxtf!AWf6g)1 zGwECMN6^>RZW+G;&itn?Z-&R?7vyIT=3YC?ZzX39J$4;DYcO{^g-qwV}}-tF!qb$wlawV-VO^HYaT*XB}oYE=GAaGs}s@VDpx zQz`n&GbHat`JVMwd@5Wl)(=m|FW|GZ`yHny=6QFZHdo+-E5$QwYdnYlJnbVA~!pd{iangYyM*4c%6bRxYcBtO`gQJmJ}ql-41c!Rp8VALjo@+mV>or=e8_v%EAkGP zGZs&xT@G2BarzpGFQlIbS?{IBkve#={Ac*NmnLGFqc7R1Wp}bu%f8{K{_Mg&4o;DO zqdrqZa-PQdGpFy$$vWpe@2uYu?DUmq*+sZia{l03eu;mcoI(61?7L~HRdr#lesfXja*NggeE5hDqW%2RWx_@E7p2W~ocrzi0Dz$9Wg|nw|N|zv)tz^E~@HoH@_$ z=JWYm%dHLzTze2s-ya5Io8dff^NunU-&0a+%I+N+u(QXz;mrH@o?8ppqv2D1_mh)% zuB&lvc?ZeaRIC-hBR}WpJ6LAVW=`rt_I?-f)V;mnEAb(;?2DH4TC_fRLVVXak=+;e zDVYnr1*}(9d~dRYoLbg-32id1Bff<82p*0Pr9FXj4j(GnzwprN;=7(2?7{SNIXE>f9H}&`p@rCC0z~064{$~6R{XEFr`~v4!>{~pWwr$zFAUkW5`^p|$m;EVw zL&!C=r?T!X%>6N9d2Td=o@t9yQ(lHH?95|3eH_5fp30uM8q%h#*i6`#zj3)(*Dmag zVJUqE`&(nF#?Kze-=*G&y^K9vY$!W>wHy1BQt@3(-c7c0oxFQp0I%^|h_{5-^tGeB z8zJ+0hWG?__F8|)XWVDy=UUIpeL{S>ygZvz*H^JmcF&9H{n$+*&x#e0^P2WGjWOf@ z)10@_Pc!y^wf#k#C34=Tr7pd$pPa4U_-HwovX{UD_K)KE-EK$uM?v=Jb+qh(sqj5} zr0Zl}Qa4gx+QLA3u8}?5g5L=;#)Dns5wXQsv7$9L{& zEPqpe&g=^~dwDbZ-}J0m_URnx>s}wFx0jdqjU8!+@eje@l!`gXetQEphUIedc_QDP zY^tBz*xCEl*gsUv5AK8)>F;oQ=Kmx9*7SGfe$F06%bH|87xM>-O=b_q=Ri+d=54@^ zMIHXlwO*r*6q`-IA6NVL`q6HHZz1RFBK`b=TghL62bYUI+nRj|WK6k!p0lUnx#9;v zZ?R4I7gUPxmY>8&%IQv9M?BA=jA0zKwGPX1Ep6@>-xvQX{v&i{KQA^xn_I*#$M^C# z#czop%5DlvR~0q;BRMs-d6<@SJBFXV@|gUt?2YBE(JtfJoR()&K2v{A?;`JV*g@X6 z&=j(7j>ex=iW;9Yn|0hD?~PA|4Yj`jH>QoLs1xkeiXLVAL%%Kg+tQoCVRE12=QqW+ zIM4m{==VWedoySHQLzL0&xmL2$HNHvv9zILli4|6S3$=r`+HS!cCTZPmy>7EEXe%6 z4_DKV*6t;FbxXzPuIVf1|uSBY%Yf^r8Hm_3fb!oLuF-LToqoqw=$cd+<{mQxEQh zoYC80LwK7%)cZm^@jqN^pgnYM=AEElsW{s`t9+K^ z|H03D#>Uzm3WN0V33R6)FTS%_YxbM$)$lw!d+AF&6Q0zjHGiDEtVtOkgZGzPpPhTE z3(Mr@v(i8G)RYsUro3DDv+2w5e)94RKMuEnOZ1&~8$oL0wPIQ4^Wa%pEA8`+cBVR) zzrVh@oXk(2K_kVpe>0zb_}K?t*}se5$sS0%mpz9)6~^$@=qa^S=l33R91$N$z!qcHOE&DL@u=}dwx4CXrzB}V*kJiT@$jf{-gY4sVX$NRm zhn<|PPrj!Zi*s)CKJh7T23f;1@avHAJ;~4c9L>+ZxE&6HtF-ASzGPML{O``cfxZ!K zvyyYh&e{IfeO`w*prvizsa|B~o=?&CF4`@4{i@<#@{YJjPVOP!mA$O}a@x9ZKIDwP zEp`js%3cDQ+uX}NxGVi1$g|=FJRbU1iqA3GcN3vXza8<^mBk+4Th5j6mvLN4KUsb) zcE&iGza>BGKa_S1`wi{y#_x*1L2FTEFYz;1AB*K4UbIg>VlUV3Zl4KurOkn?Q_jy| zc3s>8HmVfgo#$D<6FYnEF#3Plf8qZ@8`@d?t=JE62BAsefHDr1^5r~`Ru9X;xpfov^-ZI!QaAeVmY4&YL|Qeo^}oGG&~YMft{f> zG?I6YyfLNXyN4b)YdTH473`LH7de~IuIAVLe?;90xRz7Q-{)XnIr6PtE$q{MBbq?Ha~67ZQ`T$Qosidhn&5Wu0vZ-KJ^kK+ zGvEI;7p?iv%&0yFZhSE<*dq` zJo`VsJ?Rhd@5$B!=X~x)&iGTe8)}!ov)Y|~Eq`(^dC-gg63#t0^}2@Vp03?8Tusj$ z-)$dHB&QY*E_rYAZ}2kxEbU%kn+>-^sZuU!o4wnU|5|bd+n4m=WwC5~ky7VYJHPB6>wBHU>1UF&m$Mh|#1E8;^*Imk&7c2= zNSXaY$XblYxxXEQSHN7)N*)f?ehtof*ySJ2{cle(o8tAwZLhBm^iyD4$o_bvBp306 zxCTC=(sRj;VFi5`*u*@JC|g&29X^^ZYnxgXd)EFre73e(hhID^=jK6dU(p-m)W&&m zG~6yO_5IS4a~B^dHtUmGm%Gfp_}<>dXVQO@Ym5I2XRH(Xd*OvJ5XNd-$9VEiXNvm_ zFMyel{geMc%n|&_sSWW3rJ{y)!s}_9I+wg(DRw6R3Ul-~eGJ?MFTg>39r&_Wa<93m zRLs|le03q`#g*E9&%P@?bNOF99A_=oky~kd5}Z!H);>8wKi$cxSKG3u|N7)pA!lvs zM9orh-dsk`GrRGnpI6AaO9gHCj)Ylo0DJCKqu~KqS0(4zUt`~2yP5Rtt4`#)kTdig zz76z~+T0i~$4`sNI&3axG<^@8xt}GMx5b&eTVPj6jc*5klC$?0ldpp8wcE{oHGBS_ z!duecB_GC@`g*MO%^mAWdR^!TFL`F_-~AB-AGw4aR| zh{>6{v-o%E+u@h-5p4Oqb}&7kQFH$61gBS%1CRHin+?Df{O*bH5dop$0vl#ronwkh7ytY_Sq2FjXZ+wbvOoYguBJ%Zg_dQ_^v45c|AfNR9(!=Q+yvm>PZdQ z`k%OV>@3V*a)aN^J+N6fw$9FXS3VbKq?KpG#E#8m5Te-Mb53aJm z#pN!rDgU1EC>&!>JFq{;z7CJn<_>Mr?;LzR+yYO~lk;zjSp(hJp01LQY{}=%wEGmV zgN^xfhseEW9%Stg5O*ll;ro`}jlU_JNY2k>Ucp(@r8xbh7Cl5>2yNM0u+<`$#ZJ@D za<fFv@#oIijhwYfo7431Z!v4(M7CXV&he?__sN~0EBs0y zhTB#ad#;54q$lsQ?qlFj{bwC&RsFNK**2nok7x6(^Q^2z_E654Bgt8p+|@rI&li)r zca=6>;RJE_v%SSOR$K$N^Vr^@zfZ5nmmFM=?K*wrUYGp8lRtZBD9-&ObDX(f4f%J` z#<1o6GT%A3=90&fvkxcYo!GL+TjNvs=0IQa4LE!1n=&6BA*L2gAveUST?_GC{=Mzr zoXy7>M&Ldipqf_G`|cE6G{IL6yZlb_eo2-=S^C_qF(6@J-&K zgR#c8mVaw#z_$X=fEDH9XN`??>{-PE2P!1F|>v#hujkKdtF*;+L`|w;wF2&urHi-?ePR*$!m8j{F0D5^{fijQ+9r z*hc>kv#qDiTDAf7?EBpJE-V*k?~Zmr!XeEvEGus}I zt8MIg_El_;^W~X2i&J0kqc_rTC$^UK%*9jWmm%vrAHQp@u9Q0)i(5obp3lYkUDE7_ z?)2Rt4BM?xPrHF)GyWd*n_-ODak!H23HJW@1DySR8ei5RW89tX zZ1QAS1aqscMTLBVVSF3V+lXsHZil}x|G9_UM&F2@m|x1)1u_p=%Zw-Mwt+s*6Px|` z3;RjreaZh4HyidQ560=|BKvL)`4IA-+AW67>AOJZQn41F;UDOK=<7W^5T~ww52K+E zTUkGOmkHYcU%rSLrq6s<9>dm>{-D?$#rzE!`xTHmNIh6tQk%v6jgQmjQ~a@Z1NhS4 zD}2M~Jwne(jU zedO%XRgju8onA*=7d)SB8RYrRjHNk$_F?9E7ctrY!^vAi`dpv>N7?&8GuRluaK0ag zzbv^cc+b?4cFl%y}4RFQhg;f|GMY@acS;;^gmf@K&k#ImS;opO??z{|VOd zO=Zj9gyh^@3v<}p>gN`|?L9AJ-I?5rExz2J_ao=AtX@O5L$e6G*d4?o40wSLm`kMf>>k)ML&w9CHh&wd!rXR4jW_JlkudgE&K2!BM+ zTHa7D?(iFGJBqC#{bJ93j6RmUub8W}`I4Odk$SW*J{pog72-aJ?78ffbH!#a-A-N! zt?AiQ)7cW+So<4s?mmaH?+D%LS<@}qvgc;f$CIBX2fek)8fCl_Abr)sUHNVkcPTyV zS%veyCqnL--Pw*KXRfktskuYoJGR^*?q&OsZy3;k5`hENys zj-4Y{7C%Gn!G9T4YBRj5I5+;pi`b^Y%ld7PC%`cFhj7lj8Rg=8gFfVs?Vo=7d!9de zvkHEMd`5YU{WP&9zKz)?!Ft+c3|q0^%iaP%39rM!rQ)8jKlue%4kegXl3VaCxq-Ix zaB5)s%h{Q8<88Lh@Q%>fvolZ0tG!`&w)gNs&+_|!^Qd9H1gK>NB-iX|R zdAsjfdLe8|Po1~~SM$##|L9$QB#+}yJ;=J{p7x^tPM~jz zGyji3&VdK`a}FgBU*Nk3p8H zyeU4lcQ)U@VFi$FR!Kl;yheP{+XOyxf`zW%{gslrc-(l!y~sYF{x^Ipt|Qy8VtUH8dTd$CqhOZ4 zYV!>ym$Z2TJ`tZB|C*e?EtpJhB(8s1PLMnB=W|kL`Wkxb*n@2O%$d1M&3S@p6}nz zuaevJxA~HDlju3uP9V=?FOgFxx{>c>`&|36VxA#qoszSuUt5Y9j!0Bf)x~;jM$Yfrr3QQ@F7t6< z+VS5>A5ki5X2v-T_Jh-5oi<;WiqFD%_Y<_s{NIn?WFLuBFY?Y=yWIc&#g=@o%bwVb zd29CMP4@d=^lEZ)YG-;qdYN3(<~#DW@Ut;3sVLSacjB}OcGP$F%>PP7z0H1o0_yT5 zzg9v%H`IKh*hA~+AL(ZgeUBjDz}Fs<+sXM>Y!#6C&%7>!S=rEoIA|cprboHUnNEf6JD&Z3?T%4^$O*l;_g7k46-%!XJ=ijDoSZ%ywe&@TF?OZi*HTgL9oDcnR=6VZqzJEzh zm*_Xr&%)UodEcSrHu!q${TBHQ?XrKi#i?dZQ4OU8c% z+adhfH$TE0s1K9GpM_K3l3yoiHxeI$b4F($X6`Q3_C0deYXIbZGQQN9^q>1xN8AZl< zik|t)-Lsvz?8`gYjwd&U>$I7G{{y*mZ;tcKC-gBKuPhbc@f;-XWO{0KQyAm<74+N> zHl}Yyo{YzeJ4zon!Dia#PV^V~6ZX`v)lgsDpY~hkAa{hf<#756()Tp+o8q!Ix$AzZ z&5vRqWN*&C7sa~`P?K*Td?5R_ zcu%|~+jpLMFL`tFDmnr?4-?XOy+oUpxNPtG(&p)B9>O4(Idg zVr@pjL~RD(^QwyPJF-TW0NJ@Z>7&w2y0r#Hfnvpp-m2md_0q^zE^XU&_j&4Il4F!t2EW|hU~ zt&TYLdt1C1HxZM1aT&cExwHP)mWuQIT>8CyZ_%H{3vpw%I%3nV72BU|4L$cNayN2n zbY1?u|4{a&?8C@A^JPyQ$@deU&Yt%gLeDy-P4-UqNG1J2#%M7}Qi zt-^EQPPXj5BgEXsz5-q?sV#CLHE^Xc? z2M21q4LRfQ0@sUuzoedspNo@!kK*s)`f^dbCV5u&(XaHcaMpel>3%XyYOSipB9 zWZbEj`OKd^dbhYI*awi4=au9I+EtVD&WF=;2l(AsuM|@cXC1Q-bIz8%Q|@}nsnm`C zm7OWtrS9xSUJp{c9)#q?AiloZoJB6f-SolwJg`)p6?tyPIUE|mU7qoua&d1s@@B=bpoUB_tQKhd#kMZ}NHa z9&$}``o2bA$?30QFZu)c3cmSplm6D{&wf1I7~12#=y~=N;--`Hy>VllyFv2r2snZ7 z9x)@xtH>Ec*0?qPR{V})FQm7EywhyRcYqh;YWSaLZCftxUHj7W+?Dix1emT+~cxZCZ4^SsxsQ|5jTz9Z>9@D|XH zKeacxo1cm9MsHT>ogjVfgHQB~_t-m-yRr4Iws!RVT(%Q^4rHxzcg(rf8gd?N;yH=$ z52tC9y}n3aU-N&<_67bMx+_72>!l^qu;naXn#2v`KJ^V~> zPrn(m23w0+V|>|5$MPKqE7)^~NI$8w6X9w0CiJvvL%#yoV9VZKZ9GqsQx}^1Of+AA z*RVxT{mwpbt8H!VGCzaKS@$R40rT)qxybjf>@~?9#HFU4!M~Kg39ir9nST*I`?jq< z=i`pz?(jDXx0BB;xrf7l*uKMuz$9&QFBz(R^6let@jdbXjg{>hzNRonY`*tBS}trr z?#Y*SeaP+b1$?RPzw2W!wwxym@SmRDnLYXT3g21e$>h;x>rZcibI-}|&fH(+Ij}kZ zMD~$*Puvf$f^GCUiJo&TYkd+c+SKGbn!Gpe z!=5v12>uj~XU`tZ-R&y;U-}O?`#bBCbLZ^JVorvU&*hsbPqPMFKx)Nw$eEkF(KnE1 z&cR>e%*j+}XdYYZCwY-v8O^sX`E*Fmtz&x%YLWNB@3S|@gYk>@Z{B4*{Uy(7sBK$% z{$}A`^U)I8vER)0pqTyiaWg)jzZxfRlh1F8%UTSAo@^6wYyPZDU-C+QtR=tAb{9SQ zm2+Wzaus{(*@lojogucHajg=YJ8>m^#{MIHdprWS5|f{eWX)1*pJJ~E`Cg%elQ0O(69tdt)L0kDisbsVkSUx2!B`&hcz3+1kOT?8%Gd{3+&V zEIEB%C1x^Y{@>S6&erDapKCK2?+Gun*X19;cQ*T6{0-Y<-eUo7%JwJJXaA{6zOY?D z&%c{kQ~Nu}dCvWi{kb15L+md>&Xot~X_wlNx-!i3Qnw%GI|N$rb!Fd!{bKeuY?rQ#BV5e4|2|$K6o3*Ixm5i@Fl!dF8Vn@?56a0>A5rJ9bd(tKpnnG z`r8Y4WIvOwHqKr+0xpMLjpI7DW{|tkm2BOsi#yGM^b6US8ehKCoXs~A5}&)%p8UzJ zz1WT?k7Uo@%vvNjFXGGmwqzSce-L(n24ctI8{v|Q;&)*7B=-{k9li`cW#3&~_Q4X* zKZfm2IH**7UK+)IWo+Vd7w;yAr_k@`|H=B@O-?;nPuxR%$(4@eukhu#k2X8wx5X@B zOHOTt+f)^6IUFybH>@mbY-(e2Dfv5%Z;JiclAOEZzRsQHrD7ds;Zwv7!Q1h7!He** zP$qZcOD=WCyR&^-E z$@3F%#yJ?K(RaXi;?(WGDvGo8Z+s!!rTPm006(!kQnru8B}abcOMcysGmo#ab>Uwk zZUVlSej|VOL2~;Rc$uv)>_ncRU25w@@-n=;b;;ehj`*z2{p{-?Ydh5%r#9!Vk@a{) z+)#W6yy|&*@AUIu_RLMz^HzA5oVC6h=l@}Qx!C3U$(+2;z8G@>#&;>b7W*2UJ4)vBwu<5|vYc%L?~-w6%x~bQO2ucW%uj!D|H7NF zorcd6vzhkU$JK0u;6kX!eulp1kmuv9Rrb*5cs+bJTk zxfx$WIEL?6^IO~al4rH~cgLfln(f3=aW4E9kAMc^^IgFw_#Lq~h)Jy&%l8iXH#`h# zv(**zfSAe(cZy2;kS*W0jKa6WUyyn=Rb28b`=$(i=v%7PY;Yan`SuI>$ggNoS*yHDQ|(5H&9ibE`5xqZ z9}Zzl9V@Zz!k2omT>NJEWcpxh@F-t$;BF6p}0Hsk-3}8e?3h1y!bkkH-`#sda$JqKOY?@chg^L)Ij>jaF>{G@pAU* zkUDoc&cDBNIKGaa{nZ$r7MuR3mdzu1YyI@Z2ho?trvKBRnw&Q8;(qiE#I+$m!qy+> z+(|9ZeKYHIE#8QJBztZ8uHJ1j+dhzd%w6LRXd~uo{+jyB=aGziCVV6&cgCy5Yz=pl zH|Ot4PVLNoxI|3myJn?v;+$FAv1e?vs?>P=t>@-U8BwV%UpvShB6ash?T5qtVkY2i zVH5VOXA|-7dgdhhdGufP9ormoa^f}nFSYSi@|Cd2^O6Hu();?0y^Y;9G>50D|-lTs8`CR-FeHyu~w%OlbvE2;qs^l;3z*mF4 zg>lq^++)T;SD4HGAGW*5owT1#ABd;p)RaB>8o@dA521Fcxc}yv_vrgzdg{*w(90a2 zN$)Fu9{px=?j6~eyO3Mq*0=}k&;AO&Ngu1olgO!0b>MvZp5>xm?o(ZSUiw5X{K=Mi zdKl-wIk+2rDKr=NFdm?7@?vGVc&DsS#@(KOf@fXDw;tPY$j>}G(%*njwfh?<|JUOE zAayMJBA?&>P46OR3fr!bzq^^nm;CrIzE({7J{u?Zaxc$kqG{w2?0q4*c^PiPH%GhF z=nv@IkQd^g%hq4aZqS-M0gv^ZL-E%1+%;~fDC$yjWC8raUyHpy`36Y-wTEM?i~B+~ zIqx?Ijv!wywj)lBnaTF9F{Y-iqtAf*^q;<8gS$&=1ilj|*YY`RJ3Lk&`|AH0yo7#7 zx%e6T_3=UO3$r12v(M$k^K4tg+pq>!K+e3yPy>=H@qdo%z){9@bM_;?pFQKqIkm5M zy_Ww=SYQ9E*;{M>7X4Rp#Rd3X3p~6NbD7C zIS{6k3-#HBEw%VUG1J6+g|ja*--G$a^KHhq489P5 z9zE~Zl%5>;4CnrF6lC5`hHLawuhKffZ(>g3%fH2yT9SWnsYMVr*;AO2~(gP5Dxw-7&z{Jr>Z@L6I;%C1!mbq=rm4{sT+e>*GweZ7Pd<$r)_Pi|mJy z`g}@k2~tCb;@myzv)32*r9NxZ^W1sxER@T|=g2vHXYt)aAC51j&)`p;Sjjd|AE}?I zGr32vfc|Xx?3wlaTg)8jOy7ZTg1F>f2Xb=mb^WC-*I`R;&c_$Rx!x;dOAWab8bEz< z$%%KhIRP?`)VaLV9`sM?o00pAeUSV#yg@!gzmLJ&un$`+G1tM)eh!q+tJ}e8;!fi0 z_K%#spD|{RH^sN%ANYshUiAMxUi9%D+X-xas@?mo!2OTJb&t7&cyfFv-h$Fi^*L} z-jzM)L3{RT^zP*JJpr=T7vopNW?r+0Ph`uwrq-k;pFpq0e{n^j@6PrPTRSmn(~6vV z>w;I{iPgov|B(Dt@8Z1A8JTmxlC2H=0{Q%KCfjE4lD^Y+CY(*L(8t%Fnc8y*{Vn$W z+4_sShJFuyH{5{#SaJ2~_4s?!cf&brQ|prVo6|dsJ&F7(eu#Y(UgdX+ZpQgusX1HL zbA&ZpP2SXV+S0EipC)EkeY}k?hlS+KX+Dd5#lM6t@An!xIeaE0R~}+(B5oh!+nt=V zZ46ANUx+7Fy4!eO#&Q?rUN{mSCFc&ctX$mble2q~SK!kj@4c|9aQqU36K<88?HGrXW(`g^QYoVPXk z>yr=Q+nld1-(vbj?0MER?AcH6;RSd!J_+WKUlDUL&fVfV&nuA!LSOn>^qgsVex7+F z>|E&_C>Li$-aG3$ioFNhspJ>6P5dM|b37izRtvh)pW(}1%p6T2?*|{!AEY0ukKB8{ zsBkA@ufeukn7)OW5wJgG-m-t|vwug<9+{-wZ)`cg$HG1CL6i8dhr?hI+$z2%-d$`* z=&N1!;3eWylagzh_XF5k;*a45a_UFEBg&lp0e$JGYuf}y>!&9k&z`!R`aO~VQ}Gk5 zS99_S?71iIBqrxf8(3g053_%S=b4x9@K~{1@~zc2XIb)jB;USleZ=mHv$y}lp8b{g zdm3-S_XBw=I2Q(s$-Z9W`6u)JOwKwVMb19VxZBe=hulAAY4a8NUA)FPPA0!*442Yt zRu}Jg0Nzo5HTkzAA0s9_QQSPdh@QLV3%H}06_8q*JLyceZSeW@L&^JA7Hgk7Rr=^@ z|8~Gfm5Vbcce?fATx<9g+eNUUXHUVK=p(+=!~B;zpZnviQt|!9d^}fN?s!XZJ9??I zxT~K-&*y+gtBT))%blk^q`!OFHzdDYQXe3n58lRk{%HO`$wQ6dNO3**avqH0TaW!T zs6pO=?H2M)m1-CHG4i(L=-bh2K-O-MK4!uR^!IVvH-Vq@yA)>(f3YVw?qEBR{s*)u z7i;qc`9=2q@ri5;;W+4`zfP5&t=-*xt&R6|_Mn|3*_W|?5Xc(z;N-YrP+Hqcdy2+*azd| zVFR`a_!Hy$9hdd9pj71h+w`7rBjnvqEfw=MQvE%xy3n`MzJh!kUqf7pQ+FncA4c9% zf2(mPwgGsv%A&v2^Q_%l(3ve~Wa?g9yp(N(*p`s_UqC;Fy+3XsHgndGFS#=Y8j-ie zqs4rK2Z%`>=ugl2lb@$#zSe^WOZJ#CJ>-24;akggnrCEu7qCy|pGcpHALBddar?Nk zI3x3&)@(5k^G$%%si{zx{22Rqe4T!N#Z9%@l-$pAw#KKk-2|(}je}>&8RI$lv~tmZ zO`P+%kv6Z2>8jnK&T6gyAcSvc!>9R3OK58cJ(oLf#FqU}n)?6=HW#+X{Y3Atst zxc_$3-y-oBs8x@$uTRe%BlD1FKE?LE*sSBGkk1T#*;30tVqXFe&~qQ#h<_M+p4XT? zca(R@SMj~+87q4^AK4FOdj_5$r~a)YXARaDv!^zjK+ch4*`sIepW&Ygd5=9I{l5cfV?$(a$-Kz zWgm|7z3KvEU&J?({%w`|kGJCA0ZxF9^cVT_y?kT5XJv6F-KlLpb8O@8o!XJHwS~vv zL)b@L>c$9i_F(q$SZ(UkJMpcvXIpsQPUI)~Q&;c7S@%JZdU+3S0PQN}4d0RY8-3iS z-FL>5HowqQ4|eC9gd6kad*h6w58G|VxK7M;a@zlgd=ySSt&3A9x#XD!*56~}yd>&f>dB==%GFzURcfFx^aR)n(oV89LGw`Fh8@@~2R(v@(U%*+n ztW)a$IJknIxz3oXAoZ*nTRu}?0D1N(n8eo!((f1a{Fi%L1Gc;MJqCB?`v&jF-UXJB z@8SCm?+t%L^0G&DaW~q6zLjS$EgP5THW51-uSd_Bu$2D>dLw$;SK|EaHsf7J&st?{ zyReTY|4ZzR?5ELFyH3KtKx$>)h8#z%>1!~V3mv21;+iaWqE{3m@1Ji@mEGM8JzBH!2c zqMt4%btm)m0QA+@_N8JkTwv@S$oIm>eAzqwJm*ZD_{`xrJdUk5c|-Q*^eT1sF8v(K zzJNV{Ygm_W6TZ~c)Y+V4!ytXt6mu2yATRdJ2gU9E&o~-at1<8@TPrb_>-#eDSjhJj z6U3dQ-NA4bQ%C;Z=pk#g7QcuUzPk}we4P?t_n#SUe#s&e>wYDG5sOyoZ1}U-{MjW zQcLog=_~T~q_4NKPPr~_V4hqY*~w*Vg_j2hCB^7;%mZoDbDwOkFy_#C*ziQHS`m= zn7&rbTypm8?N#!O{2*H+?O(>pvB&W)&=#_0KWV=c{vB_N=dkCw^T}D4%+;6Vh3uE( zoU1pG=ipiRYR@@9%#J z1F<#3sdZh-a!%as;vd32*^_r`_%a{U;Um}-wh(hF?hiSOx|E8$_51XTv^|`j{c#;R z`T3^jWxjr8+ZwXZf1x+z%iZ%;NG%;!A+P!Fq%Y??k!=xdL~a4ei}mp${;{}aWwGA* zjF^4=C4chZF6hqolK##Rm;IbNbti7az6rb`wt?~GE*d#&dK_L%KUTXJ$yu8=_zO6+ z>^{Lh51&lWdu8A3fOmrA@>+EwxjI+O`;Qm*)s5*NX*ZH>HT>b3Cx~w;KDF;toS2*g zzwylzd#$+E^!T%`o6t8jhjqvYi7(--*#j_{d{L!ypL}Jx_}!c>>6zcum8W5Ua!s86 zSCR8QP+j^Ya?Y&&rDDzU{=2|luwl8VYqRlV@D#iZeT?tfQqk^PygohWZqBE@A!qtz zIDwoRo&BEs>){qfuQA@~l4!qMWlWIqWWp#N@;Gq#iU zmwV9&SY9f=zAIk*>_pD1Ifv;!}R$!c@1nt{v7^qz32y* zi|?fGz+EefGo>ke_T2zDT>F31vxbS?SKH;{{$iU-{{S-f0sLw6Iz9`&C(o`dYRho> zvzwTG+0JF#m;9rCP9fJ9dnS1%Tl&lXKL$5rA4fijoUxA;o0@YmWS(}R5981N{S}h? z`FAh6^S6PaVv;xe_0nEk-hB|91~b@?#2v)#M9w?q9@&(hdB|DRg8%0V`N)=corSYD zkFs4$&UaeV;2E|_{Kv!p_zuBKVHtU}ep-rsi9SHwx#WRt*{{jZ4)_T8nr|=m)Si=Y z#y5vOck?r0FxwLP3TJDaVJ zK8K2*%$I$2FFijC%$|+^R<>)ze#dqJ`6}p1&pfYzE68K<)#m#M+*zB{^3?RUPy^m) zKT>}ikO$+;Yxd`!^!uTbZ9vcByX8fEr}8z&W8h!>58|`oKv)Th{}~?#J&kK2qz>MK zm$7|~XXzt*x*mNB%xAlqtsA)sz62-7+L0HRiZfs%YxtFWXZGco>f(O%l9-Wj7ThQ9 za{bigPp+f2qCFT%%?!Bpn zzlmGO_9EXe%RC=XWPefH3(1FwE8)8!`*0nkhAk+`SxC)ai~rP5YEnOX_SdK6tW(ax z!(b%)80-5jTjntLm?3y0eWmWb2%C#Zt|fo-*(GzCIZm#PXRpFP=zAx4jlPme z5_<$WwWv8<78SNzA|PL=K%vZqOzO|O(1x$6xXe!j^VCH9|MBO}k(~P2+;2u-kKS1P z32dvy9z>qOmU$`jXZ(pt9o-h@i#eR{7WNJ3PvEWaiO#%TJukKXB6@3_vpesU`_u#C zrr?vRi@R4tyffc4_6NmRLvph}y(T;j&(Kq|mT9-Js;IFiYnMF8n6g*S6mzDSZS02~ za0|Zg*$##5=P@|z--mn$c^0J2b^pjm@y#2U0sTO+3(1K| zt^5miAt#@Xf%$Kb?|L$6LCk} z4W~A3Bz_RN0Xexi$XqV3G8S_7!4~j4|8V-pVg}%S*&5;dVY5m(A$~PEIg!tAqu^t5 zOTLU@I6d=MCfBVl)}X2Q1L*DP*)MtCuVTK1%wO_&EZ?1YXSR&@JecTN_p?8XpWs^p zPeRuDduw?Ixt8{+y+`9!IC+-)?OOiatF!+u62AvjlUJ~1Ts6qsz&@p7%mG=+O~r%x9%EaEyTGSaY9jkXY#D!Y=qy;r_5s^m^6Pji&Yth9pRM5q z{>*0$w$+e#_zGsTZC%z6{}#9oeF1ycd@+0ing6N!sDqp09q?)JKCG!K>iuV>;%A*5 z*giBDsafl@+&P8* zlbVygpB&#BufW5rtr7VP?c0)fha0qO$d-KD6W>kGzh8f`_-*+!f7!3&U=Fk{%V%-h zi@%e9V>p1EIeXq+U>*5qamUkl;QLE~AbExkKmbMlw4guW?#G5>gd-oZASJhxPQA8{DpVQjy^YiyUnyW$$-J=r#gCUB27 z+lWog>A*J>e*ziT)ASj9yU~}@XH^xS(HoFkRTuNxgMXR0^!EasA|{`M?j^s;p3e!% z{hUo%yB0Wmc?kb7dUEPB@(=iTd^hf(&2&7kqSz}nv_Cv{c%@v_sZ(%j`x*MWjC>&Z zJMH!rlfG)x^Y_0yli%0Rx3GqMBs|17oBl|-I7@HF{plO=b<$r4KNHR#OfF5qC$T?F z&NKVLwe&Y&GWiWy34_G-VSAjsFXaD?kvsH@#?TsH$o4I?sgy%FcY=4=v(J-X*}uu- zElb7kwS0|d@;!rl8C&jn%gMLGkC1yq)^uRG_?gis_!qWmY#IBB^eN=_IP3f(bZ2kM zR#RWOTTK@~keu^1`{+TOGdcVAtEwWGlJ5(|{RjVFACJ3x&|ejk_uCAv)%Gg>MPrzHz&xReenSh(HU52w}M``l{9Kx3U@x1<);OxcxZgAdz6#N0Hv6thP zICHfTei<@1qm5~%KE~49lTReif!p=BFsDF=pNS45-vtNr zZ6%b`1cQ~rEz=uAJEFL{>S zNxO_G`8SL2Nqr9`KgpgmaVlFG?qg3Kcp4wU_is3yo_;DSJqNPiK857Mcyamp^Q?+u zpPYv0u_xvmocy>1j?nHowk`Gb79MC`rn8+Qz870^;3WNQz;>zleD?l}Z@$>a==ttB zpR+PQzp~v9$>Z(mbKxn-+N9>@eICH6k&m(64?oiP)prkaC4bJXn&iCGfBD*06rXdi zH?I2pHA_Xk$afok=m*jl;H$NHkKC610ByI2~U zi}d96;q*b|$Jn}h-)U?)tLov^;*Wu4@ChW>+LnvYEN%Jck++09`I2v`H`!C`L*6Ur zMCLoWb1L6qJ{1;2<|;LB4!KnAjtSS$^RAr0=EwCKKkq} zwi$g1e}3mMcf;Y26*c}@^34$668#(|wkxm%;z7 z2YtyuwiP(B$LMzzKFYhji1(u>r;;bnde8qD(>COv;q7v9$6ZSv3=<*$Mn~=f z)OLcHWBUeQ zppTo_N076JGH1u(_X?*vQv)}58{Z35w+%R%Aq#h0DOI~)S z?@IqKo&$B*zJ|Kwt?&l;P_e_R{#ie<7qI1?kh+n%%zI}aG{aYjTc+Kk_;q~0xSQ}0 z{HQVJ&YT*XeXyME5AT`$y`G$T%vt|__A)*iP$N+&7PB%NbpR zE%~@9G=?sf#ohWka%xKUbIzD1^lkZmFfZ-#_r|uGJ#+BqKhJnVj&`CS#Fu>NugydB z`=AGXBYO7N2lTbt98@myR;HW%KYcjC)>4kwSmzv1=xc7?`pF;qcn&g*y{JXukE#y+^RsQriYrG^dX>qPDd zyTWJUE`ij6edxQ1$?wSCO&$yL;SG3NOzOkERo?A_ntZ#Hf5i_$_TFZY9C*(< z4bvuj?HBqllrBnx5M9Xi0q_k7Uc8{3PvISJ~C%ijKp|9Sc$aE+MJ_+YkGe5nnq=sD-+lQZs3>1DWr?N)QVuu?nn zdTH=P-P3{D#iMO!bj<1F3Y()P z689NgPX7v@#x{?fHNQ#xgYXi(&X@TZO)il;9EFMkfFFC+8eI znQxWcoJdZe$^Yr_27C;4`C93tvaFUtQ}U1IVZ} zo)eq<@~`|kN1nsqYxgV zl5gjVdxf6;ko|H7eJ0<&^z+EukTc$$>3P4?tIa)oNAmF{HC~(ShpbETF!OsVZpxRs z^CVQm8R}Zr_D1%r`I8r$YqM5gOUXAw&bG`~9esY}>{-i}ynljT+k57-@)&(|gb&EM zlP5QFM<0Y=@_x6H^PG(N`-T{JB<)`9zEmFd2ye(8}Yf~D%r9Jw-LJ!WPW$TH{lU{>H9)F z&=`_ynY*Jai}$^m?^0;Np1skCd=EbUpMHLzXWTz}-bQS-wd=xvAltS0Hh7b7fwg^# zTn*Xx4aH6pTfbDy|K9Xd*eAghws!O_$bI2W7$Ej1+#l!c+?qU|{0g}Z`4sXluot-# z9>YEYvIq0GeLGaUgYcb?JMdqMd(yk&{l#S6jv@bUtTWkLu;slw%7yHw_t|=inT#|3 z)Z)SXOW{v=k1hGKXJzr6?4SL#JBjUgv8UkPY=@G2k=IlecZhrFjkWoRE$eU<`K{fH z?=VMcpEX_#nUl_ZkKx>fkHItGin4Q~ckxcCuMa~&jQ(viPxjSawxa5ec9v00zZU>W58Ivi(jb;O6_ zkM*$)9#k@a(3YG#QP$^UcoHsxMeO;#$M%r(t0No?nahl;JO8GT{5%L6d)8JGFYKM!+IYqqdTVk%!>%SLC%1&uy^Jk=2Tzo|GrjFeK;(sF22(_)3{%>c5Su!ia&GS6+YBgT`_amI`CzUZeh>f-;eJ&_KfQhde$}b z(oA0&!|Cj=;Dz`@_M>21eXU+re5Tu$J#{zpeI`zhUB#cecM;pA`p)^6ci9N?&fVzQ zGq3VxEdSPj_Coq`_g*!B2w27YclJ@|5-?aZFK za2gyft`Yx+o^b)a75SjDclCa)#ih<>Egt3@NWN2CJ=|1G`hFFsX8r|LkQ$Y_n-4i3 zcd0T@}_ygFD+avQ#^?Gm^FGM{zWQcE(wml?-R z`rL=y3eM!ai!Enu&bA@&3rv8&;XG&n-Jr3)f?C>KUoPtWr`l~ru7bR4&d<60*T8*j z4RQMJN}kQ1b296aTAew%o2|Q_16_>gYWpeMNHN3c$$|WQJ?Bm@NIs2Z`-^V`IcLKk zj~Dl>oyZkpvX1+aJK^Nv`=w$GO>tL9Zhz`IduyLuxSB2dxHDc3`CPm$UcuH|+I+^PL})ZV$WGYPfq9E#^WP!az413cE(#m`h5qkWZPd%?)PVtGe_Hz zlRLE_`{o^dHv24S#+ST*UF-+==dzkd&OGMat1G@WeSLA+)92w)RmFERPm7rg8Ed{n zNM5WX=e!tWjMw7a-8$i{=R^3fviA5?NPgtbpFMI0zL}nSHHdF7+)f`~v!$LL$d;Vj zkbWg(uWX^sGPXVO+2Xgum%$M6!`a^za|b#5;0^jB$lw1jX1^T%rsv+!R@+DE$*C{d zvQ{(M?;`I4sac=#B{!GDh58%Fp7}jY+tK9A%jbLzaq7h8d>fN@g6yBX&kXjlA*B%Ayusj^E>ZQ%v?*nVkK92~OM8$)4ovAvNkY&r0rA>pS0=T+KH;pnk}tN6cL?3C7Ut;(ehz+~plpuWm0D_mZ>ljr76d zjwLT6A5Km#CGYmYX`33<3|4tYe|p|CH81nKD{cqzT|(cm(lhWzWwjj7$1`Ck_Kn4D z%~pec8J>Yh;OzZ=eA!c}9q-bQ!x>Xo*cN707T?EbZYDvWGB&qE${+uN=qMdZ|vL1{xiR-3HPE0FvjKtGr7W%B>nk_!Xz#rpY% z?JfQMO23tUDg9V-#&cP8awAv;v*;J&Zl3uKP9181_bL@@*HfFX^_BhbG+Xv)=KBep zoI05Oe)iUQIv&lQS`c6MS@z5*_T%AV$a}2T-)?-(#AW~HJNm;P_=}SDCf5{y9({W}m7eh>N58|lcQu8p z$(jG$39`o?qCa9>$(w1ogZSK;ce8&tOP33GT*!A`I7lCBBC( z`{_uWev>!fLGHfa;!`S%Gph&R49L4asL$-jn{XR3dH2lu$*Q;4S(j&wrS&k+`$@a!yy_YaqGOJj1LcUuX&vqwH4IaYRQ-59Adg0W9)Y`4!Wd59Oo!EPc z`xftmPvQHCT#vk@!Z`%9#ZL6xd`J8P-yv|Jw!g6t6QBG_K6S_Mvz^11eb9vMDdZ6?w)e;3=gIeQ1OHPz-oa%XYbgURxsIOmYl-KT@U!$gDvLRIUtR5}zpTsC>>IPcDDG7H z@AQu1GrybAvp$W=KoY5L)g!vH-IwvC;gn{?ws6j07q2IN8AZ77Lzg8 z5SMdfW40^k7vSV@?u&cFPU23*RpM&nQS{TvEzR4@G>TA~ma{k{>-w}_;hn9+aUJbtI^q;&-oo-J*3x5r@$?LJ_8GF#Tr|+Zx)P(aP z`(YK|LcYxZX5_5>x40UHL0$T_e9=FHPV}40entj6v$xb=z8g4HBVWaynw)cBXYxnAihI!` z^sIaKWY#kIdo|l|m{6_u;dN~Fp(k{sw_(2*Ujau#pXlZ{_tt%31mw)j8thD7(%XBK z=M2_*3|?T;%EL#`g^Q3A~H`lZRdS zGfzv|@|-teHf&Q}+>_^$=fSPeimkbJFXCUy#rgXvd+rVw;D50VG``GDJ{M*FZa0^C zr`&h)PN{2E;#S(DnVWq6Utg|dPiLR@fPJ;STTJrdXz~cLo%r8}+WP5HEl1e)r|&3k zHU0qd{aW%cdubo`*5t8lIm zr*3Agch&v~_=3I=cNU+qFNf^qyz@Br>$I;&-m|(me+SanqmP6Z+Wr)V+sf9&oaJ-H zP4v8Da-})@$J$K9`7h_)m*nSRB{btZwN!itdl=XA%o*%!^pk#bH%ac5;qYbhj4yTe z+2~@Q^33E}3s|PF&wCejH+T25V4}FSkR05az7uX(UEFD}C1-99q2KR$c~0tV=IL1& z!q%96Ae_wiC|l~*o#cVA0edt20{eEb2l;Q@7w^KC9DdZ8_SPo9f4PSIJ>Pki>I3;C z@;LU~ss0qZH9h<0dU|r}4tfvv={V<6FY>+Q?Q!bIkuX$0nUBxlj{lFQ`+)m$eBTFN zR8lIGQIRB-C}|kg^LZkaWMn0K&9MK;-c?~&F2eSGiV z-|Ka}j`KXu^SbWqzV`ho$VoiqZ{p{?$oZIe$usV*DDHKcI}eIIOK--`p4fz+Hhc=X z3toVarDy(b&2GZ3V7Fv9g6q_k@$FB~_j?n#jXsuk2s?XL53zajs@PW>r+(^PDW?}d z+E{+t@-=N3Kj%~CWyUS-$lloox8NU&vwrWAw?6%F{y6zZveVAS{E_%<@eVlO2Jg^j zvzxP9m^thPE{uF@Ta`oMbgh#$S=`zXUqAtj`%I~diXY+ zaaq7_iL{pQlxb=N&Ts-_YKMz1U^BdG0gn7=ydZn~5ht?mw^M^zRw?X1qCW zuFP3tS&!?}TEQpuX7s_hCx5MZ`;}74QN7TJcrvkHx>YPxPeaUGx7z&$I3kPygocsSTAkABMwN$exyUocB*(r_O)1 zsS92ub_`wv&E;hMokYw2p8G&ucD{x4cd(veXYSX~hFOq(ePcQILw`A&!f)*KRnGGk zrDATSzSG4P(9>US+2?uIYjSQB`-T27zZQEHZ5{FSb>`C&m>ojvn+ef+*y zKV=WVSK@i}%;idU8L~c~z)k5J;#P1sr0&B@+9jTQXU_DC_}RxYPP_1XL*{ta$X4{H z*_lJ@d584d*6e{eYxFDRzzv}gHgL-x2w*nhIGhs>j&)pInyw9@*; zuat^*r~kXLvp+O~>)2n4PlFz?4gV3Gb&xjX9qPeFo-qf1PrC9cX7WztZvbQY zHO%|2-su4PrlsQkoVvHyo_m$4K_5!LnU?jPHQkuj8fWe1Y~GK31AC-0o%mTFKk{p_ zSF;yGFUb5}4w;ja%k#={WUn8Dp@09kiyNIRCa-6xA{yd9) zCF~57Ap7orpkK9ic*iBQjOF&QAMMGq`3>1~UWD7(PiV_@c5-)D_A9&sGPk-bvpsC# zS)1eB_d1o$A7u`dQxEUY@6W!RJxWenIg{{}IAij$Id;UdVn3TLx0k$0_!a&CwfHUk z!{ufVAEDfj{8{u~w8iYFKXLaY}a1(}a~Lgw_5>fTDM z6MJKLn7#pj0L~e*gLuZev3lCjGM+h0R?3|O??Fxed17hn&2SXIBV^v?T-jUBNXXvt zEUc!j;-7@mUnO~G;Q@uoQQ`aAUv6W^9SRhis7f2SR%p4?f|o;>?yc+Z}@K>P(-{_O9*vhM)c2Qmj& zDl@3ESY!EnR+rPVkG+nw{+`AYp{bng1KG>xm8=nC@*X>Pfy}46IP2myj1K(!}*V?qYW&f?SuEh z*$e8k%lMMA^V0L?RTlUEob|ciq;f_)5BtTTM?N-AwBQzd+i*5#+b+2bIq||3`gW(%yj^ zs)|1^slgr&$CrzHMtAn>&_KD&)!g4^${j?T2)EF$Nm+G%EuOWPHfR03iU-nO!B=`# z_KP~oPrwJ``Z#C(Qt_?XXQ?Y|XIuOueiH6r9}O+#52^ADXiVQv{n;<_yqC(w?~geH zvTxOa`Sh#kiDli@!*$iuAK%aKj^DuF;;iHBV;3k_t^NbpC%{YMzlk4A|C*is=tS|h z?1R~l!O3tEJP27sdzZ{-IfIPJvFwYK-RJ+x-y(jqSe`detS9^exf5hh&brKbpFdOE z1b?cIJ~;36A}woiTX>1Sfx5O)*MBSQ%g~2DOkMrx$zRDn8Ztj_h2xc<&Hqzw)@a(B z^;N>7`Pug~o_kk2qjAQ(q4+YfPB`)W9lr_%c-^RWOuf~~I|Kbt+q2lMV+taR6 z_AAlSUiO1LKl?~6{#*L6r<}v-S*v5|hwwMY{qdc&&E$QAvoHTj z-x#Nzm%)z}#?7`!JmZlXDKUat8fn(A$Ps%Vvl=G z&hG4sah`pzysVQ}?A#mLveW+bQ+rzKO5Z*WQy}x_?7p9&(4^>T~XXgUephJ(SH-qKAm_={sNr$$(^_{Tq1tB_#Dsp zoSwb26+Qn=xq2`_d;(kx`|z`O&yZ8a&$!3VI+~?S`sgBlGn{kv7JQ;uTiQd~l`+oz z&N)~OnM1$H8zP=Ib>pwa8LRBA*Rb>7scFf6f_+fQ9Iv*2u-|5<%~{JC--mHm=*Rxd zv$9v*z^|ul{{8jm-cT<0xU-dMLQj3`%6Wj+6kiKjd)Xgy zz8%Zofqe@*eb^gsFJ4pr&FrO~HIrXoEbH}oyfyzRvC*(QJ7;ho`5#mj`)1aAopNz* z=T5kcpSe0zU7M<760H}`9KVS@O1VwNC#q{lye>RL8-y>Ta~z(gW0>mjj(O0 z_-*1OdSBR&C;v|64uZ6O9XSoe(kCyAFT`12XT!hjGjR>^ z%+t%DBR}J~k=*m}%d|t;*RuzzEA4K^?gtseBk`_yGw3On@AI+pX5-A`A?$C&QvQtO z;GD1f!Y=GjXxX>=va`0*=ey$6m3MuPp80(dzZUy%$XpxnD?tB%*Lxu)!4a40|hbSz#` z(ogK4l+8QU^^B49th@B*>97Or1oJDzaNaBZ($~53IX~yXSmo~)&wg|h;y&&r%z9}c8{4I98k{4K=t{*UrA4rz1VZ-4$4?1{8F z?AGkWPsCHjvu1xZCgEb`( z^m8MevCaN=1^+|O*p$5oyBnn6-zXRN_?!iI%Ke3QiaJiF-;ZB}{JR~m>5mWD3+PL# zit}%fJ|?kv{V^R|b%C1;$R?3=rZcc(Rk zRrE%*aj-Ex^KCl46@5I;TFzS7lApDl`Bsy?8BTwV@UH9f(=RW3X4?G?ylTJC-jy>Z zbNL3j)7V#QL)Pu5a=YWK_s-&bt1n}E6J)Pnpx$%wCgN-G1~~hD&V!s!`Fl^}Dm+*0 zJK7@satmHhY<>D`=5+1|yC~NHkD=F6_m=F(;BNjxvCO^6?4xi8ycayrei_pK%&n#s z<~w_)@{4ia>9Ml9`Co~B$iG&5Hld||8_=70#!~uiFo<3kXHLAz-jRJ3d!d}{$=QSE zsOKHXyc#1X{rrKv-}wFI{>uJ{J_NRajPqo;7t(LX;}_v_`8&uT44Gqjhmqp@>7V7| zSx;G)RqS4JvQ8`UoBZiwKjO^0d)e`K7SBF%JEY#){Kx61dCvKAABVy4C2bw$PJrAc z&xXwH-f*Zg)9|gd{7#sA@s0dT=wHy*=TB1KYIeqHZ}Pg~%9_-*Cf!mh>sg+78`Pnm<`(|h6E4>zXQ$8FSmKdmo5M}B`f z$?H!0!gE*R-}uYmOa2AQWz4e=WG+8}Z&Pkvb)6-aF&d3e6-)d$u`g-cvMq zMvGvlUiZPYP>oi_d}=YjJ7-DBxj@$+|b@|}7+J$vjQ@*3c;abL0h8CGyg~{N}Tt*dTsC{xq?Y_MgqZmHh+Go^UhH{Hcb|pscPtX&=FB zFhfo~oIND_RVY^DB17;S-n*qf0gZ_jg(T!O@T6fWK)AAZvOJuB4vucg(i z&~943lQXAF?9`KYYR#X=&e-)5pXxmALz@Bb(f^AVt7m}NXZ+XM>C+$R2l2OK&#Er& zX6^Y~s&g*RoL>ejO8QBeM)ET5m(uQm8uEtWDYPxUM>l?L>-TrL*V3P&|1VG75AtV1 zL%2rX#rWNldlI|`U6jwiU$>Io!gD5-i+kcW%C@Hsz}W|`70WXRv1i~4oV!X5_KBYR z#Zvt#Cu=YHjg-r|`6RnGZ42CmwuLhF{BQ58{#SWX}ATJ ziuKVa$0^%{pLNsL`_;zPCFh!W+B1ZG8SaD|!|n7ZDvICMTF^6Zu26USxjEz;>mJ;b zHif+b-dWBV+*P@6Xup|5t@wx2GoJ6r&zXHMKlAu+c|Bo>oUg<#Wp|@z-_2U;FE@M8 zhwRMnztlGiZ$TSN`v=}r=4)Ek+*9oQIZ@8n_r=oIpeg@6IoapZ=KI*WKYxYm@iSgo z&lC6`vwwq*up@0ZW!|Cx%6}cU<#$$g3awJS6TXzb2Q7b&vMIYiE&Y*w;Sc%+a2P%N z?P-uY#^T*^{yy}PV(aqj!Yk}eaLo$$lwQR?wGr)K+I0LUWE@ti^C))K$20z1E^D{} zy-T@R`#a#&eY1GhYkm0-;LNvwX}3c5k%^G!-Xne=Eo(F9`5|yK{W%y2|51NmvEk57 z>#if7nkzU3H~R)7V?VG_fuvb*n3F%Q(!SRg2}DIdePj zdm*h6`*LNo*UpxABR$W~`dTfXvCDtMX(qm2Jac!roNd@o(ndq(P$j<+#`G&c+q|K0ix)5%JgW*Sc=asaR zeF+@MKOA0EW-nzA5o<$VinEV5Re#2;KR;*W^Xw-08nM1&$;;edj)#ejp=ZsEU_S#x zVOM$2;d*$e*lz4;C3bxF!8p$u&JN!vXA*mWockeq-Xr}n5Kp4rX3n;e+ZpES>&(%t z+aoKytGvvW?);o_Q(#B-o^l%Dhxt`7i+`lNX0*KH!>|I<4;Pe*KAeXSgm2Y(pxiIS zd+__>*F5V`e5!r30>22CmWsL-$R7?DDpN~+d*JLx8N=K=E>QM8c0c*^aoYHt_gJg0 zoJl*5W1%T6`^c^Ak;=^WjO;aA((`w$ z7Rmizo}4jad(*Q&PKMu0?rShne&*PEavVa!*e713W$ylhTj1N||DsK^ z@K}D%>dSHNBB?WLw-0ST{#8zE_Du22tNeDC=R5;P(LbkmWjDlmm+YJQZzw&;ZvvS! z4RGdto}Y2g98TX|1sBoJ#j8E1s%)==^ncdu6Sx}MK+f8?X}7~<_7eN`SM<63Z}0@X zk$SUE=I~p>674t%@1<`C<32FFT%4V?HRYtfN9Y%d-vU|dxtl+LZ-Q&ZbB5=; ztde~(?MirqpZCdmmN~eLmiE2EZYA%*3iB0z%)gKRE95RUSGg6?o}RnS6xwHEBiY}v z=ZM`UmV5g~^sJkV-{FkW*@~q-}9&P$Nyh@j>E}cgqzX-!#+jsZ^}25 zo4I%`e+CSr<&JS3djY$>_!9A~ljCTI;cMU%vGn~E<9o9B%H-qhTidapgkDfZdk<32 zT-+DZwmQ&-wy>-`&T~&zb~=9_*jxNh{24uU9N;`3Tx~yxcJ$o;8hh7i${Z(t0zd7V z37Mq&cuy&-K4KYiM&Y+cZr({eXEi9eD5DPD}f#arNe<>xoIqxq?OUr0{o z!MTvOWG!zA*|R%9`n4nE_s*O@KZ#|ZUrzf`eIMXAp^;eXc?5C??@4mf)<(&>-E$kk;Rm(aeQU*eekY$ZCVLu?23>6`QqfO;c$YWWIm;h_31#h*_bq!T_B)U=oAdMSnY(6F z_5@f`QS2XMX^Z5fKW5NdLGDTU_X5V#*I{>7XXgJ6a`&RWCMS1+>B?-zzY%ugXK&5; z_hKJS%N;Vm#~jQ54b~CQ*gg(x=;_a|)v+6YfOpwT{*UY}yRVOML%iS*v`|B+Ki z>>05+V%yMib`0RRq4(gws=lwphS73&+Fv~5_c^~ed@BBryou}sU@hca|CKhTRP5V# z(;kP6>s_#uy8o1OHSGo5N1Gm{XJ6|j_7KjxN&O8xFZY9LcJ8w2yL|KYWsjGa`CA2< z4_#>&TXXBm$$D*CRs8u@#{bq*ai`6k%9)xqGmw5ZKWjF1R=IuY$?^aq`ZvZnH)ohLg+2Lo9QDk#=px&KM;(eb^ZeQs#{c z`w?s>mODwAo;GE_*_yVN-xl5x`vKnr$9eX+vUvpgeIn;qPj=4ttohOG!R$evJCvQi z{{&XUS@3jOJ&^a!{HlO)a;xy$RYe_j#Qs%BP5qIzIa};f$oP$tlkwe3&SLyp*<7M_0>>Xexdqa2*o`8C^R+YtFzDB9Av(KK&e#1Q3z;oNm zn*`YpE1;MBi~0Sbn|Pj;HL^bJ!~O#DP4|!YOMjlI%pYQxL*~dUw42zU!@IBwa)-^H z)C%W}`;_(zzZ0AWd-7Y;vlo2CudV)#`N_@xocWh`+D4nNVQ1X3jb>gSath?>tVpv`-{#yrFxp-{2j5n^ta^pm-9V6_n(3IRk5$c7W1>#2f^-ewY<+OioN&A zlCf3iQu?Os?Ui|5>{5Q_)>`)8@G*Qsdv9s6ujF0QUs+dqMshO8G7l%x)>yMyUzN0H zXqiX(ZzQg*^3BLjAK!_W!BF-Zyi^^%+2^vK!Ohj5J4*guOf7cy$=~VwT00rD-NjB8 z%Y4~R>{xh|){vh)VH2Ej&O1JV=i)iI1cT&Ft|-23*5|Ka?=Jo$JNJj)>N<#hB`y7s zz4c6(*UPiK$G7xvU=rj`Qze$~?e21KVqZl&Umbt@_e;KEXI^|Pwx{E+wxWNop4ag#^>)Qi)7yyG zVK0E=#UI4aR~38jhRKsNhBg8(hc>Vb^3JD*g)FZcr0|KDLqSIm+~8Z=w9tcs_qG zSdX7~Z3s0hiazQl{v~X$uB_?H*yF{2hAm-sxLnN3rE<>TXZ*95+>J-UrpookYhgYd zB`@us%>D^-56#-z4d3fM4q|7I&3?Ns?U=GP2a8|@yr6BV>q|TwH&Es|`oDO8e(a%e ztaw|`xtVq&yvS~;-Y#NU8-Kyi*3TxMlYW`&?6^QY?Wql8=@aD+t0=zv^UmMO$)3KF zy^@}NurvEldD(OJ=6}WCk)QRk3g;b0!intbJaaIffxoAnho8hXIiRN85})3+h0RO7+q6UTJsM`$9Zk9TnyOTSqu|;~kXE`n`w0RQy){RgiX# z#kG1C`$QZ5y>LsZXwUKZFBqrp@K(4F&OPG~_2l=}?aRfpR?#N0tBl14^7=#O-g

by_3)mOK?)(X2UwB6TENzhd>@)ejXJ7G}a2RBruTQT- zAHhEhT0_>#DCNrVcG+0AvYXS}^|ZIj&l=8JpF|r~ zVc%tE+<%bwI&8~NyK=4#$LZsr=>z55MZ1!{EBi=#4e={*_JJetsoIvk=_>wdkah6C z{Rod%b|bO8&&TWoE3}QBztc4s-zS!P!`# zPQOInMtDC+zh&O#%*_}t!~ZMG&iBY1e)fj`>K%cnl)S$)FL&`X#iG5>zDN9hv6{-f zK|h6__s+ifsQ4q0cfE;r9y{xFYi;g@N0y5F!2~>>KLls~yvoj8t>X8EBlzvr_av`I>}URHJlo-U`X%zIFq z_OUwhzS$>6ir>r6|0BF1?FQ%x`Sv}7eg)(^=^b_dmtLEn-@d!DKV&b#nYWdYd7XRr z5BPM?oXKvhp4_oA_ZQG}f36L6`PJf&h;0s;BL~7>a&Cv|FaSE0%u#u}$Cj79ua>;U z^h3RK_KWNj&&$~!Kg}N?=Od_A=4sqT>_&DYvHRe2ety&3fL*4aQCaLgN6Q_-&%CcE zmgg*#_ar;}*q7pu;lCmK#UQc0#M1t|X{%rq|7v>d$MsVKzc;jGFO>78*kanH{04BO z_Me2O()0IQTjJaouEu%)!#(3r_AKq0sr*zqIg1bDHx0A$UBJr8?LQQCy0%yF6PNr%Df`hke_jTLoDw;M(&Py zKL0d1t>A6;eK>2fi880K=dr8RSzj!Be?!`n@V5AO_!{T{W5wT~)u}A*5@z@R_Oc&n z&%rA1IYwSfb^L@=rairz`ZI@4#5eL+LjJDAFF5;K{$1UC2fW43-FBMtkE$#Cbnc6_ z&HJ6p#kXJf^T*k5ioeO;T-hD@nbT=&_KZ37t39ugvd__Tt{+9Kpk*y&?jA?W-7e!Y z73R`g@zeL$uph?z^S@AjnrD2?{~dDf_pdC@yNub#aDu$iwASp*z5ktma_?tn|9Ba4 z7a7XWUcR2(y6_M^^JbT7ed--|;Gg8Z@_XrWcHSfBbNcB6cvaag%f()hes~v-sVKfL zvNt7nC)iKUO25JPz%%9Cf}4mh#>3$v+8J;!dm27pnOfrO^E=`TmKFDpPgA#8{!Ux^ zZItJ?m2)P{hMepD#8aj@OxNy>`8kiWma;Z0AZ6CT9i^ggyRkDKnM)UlH{-t`|2sI0 zo!{NYiygq80lC{>&d+}Q8NVI-c*y-b(62aWk_a2;SjX+Q~b*tS$1= z#*^Ry{*(G;GM=w&=6BBOvy>Uf?<40SvD~}1!MDR`dd7Ee^`tNJTivVt=h(StO<}(V zXF>Mx%=0J8#Tt7S-%Z~TpGZHMy+FBt_*cL_{44Qr<(i7utM0-_?i2iapuI3N_}2cjKxfIV|#gziPeQpa2e!IvW~ni z^5%#)WY>VzV*TM|$Q>zjv`TKCcNIJD^oZCl%4M%RoL!GLS8M|NczFxi4dMJ~{B`7J z4|$E>ihT!eLLZA4mz@3hA@O7IHT-_++Z{g*xlf&gpOL>x*~|D#%if)K75m7_V$JP= zua}ea^9ix(ka66ao_UqIzBw)HXh-%{xHfcW?~nIT?*D&(zgI5moGg|#o$-AMuglLk zZeH!|)|LbCN%Y>hA-xAaud=u|X8mm-mV3bCcqIRNocl?4cK-bR5&rA^G#3@4m_(j?c$4a2xtcWykQl^0O!3r~G!fEOsZ({*wQ$PU?Svf3e)0O}i_%8~-|b z>dK#Ap3KhJcMv;U&N5ooQSNjb(5vNTj@8Fg@PqVqa7W0v-pPN*v#wS)^J6-6fTQf^ z+bN&<^di5%_>J;kU=L<@US`hoZ{z2_-cw#J_Jy#i_znDqIP)s=zB5iA9#v^yfShmH z2gXAq&mS(nNIh%tFL*QY-t33j!7_Ox@OzN6PlHXBe?|G+J$A)e&l$&V(2m_mJp0t@vb_+#^6kBu*ebbG@#fcnnt2vNtwW*J1qOFc{X6^Ml;noloVb zJ#Vv5C>MKN#_dVkevp2zDYiK+ZFrlOemTf9^V>@90K>&Ur43-O!s);0kFv8bXU^S= zM;YsHaXZL6ZUW27){F9apAMBpA8*S4gnkh2NNZ71^z;6-&DhV&-IM+-|2WUNg8z!# z{rK6VtMH>Rf&P^IDLDU~v9w_t{c<^9u*b2#Q7-HFB7P0X-&wmGe;`((xAvTT`%L7g z{*kb?@*9`bPk#;9<#&g#mlpT^ZRKo1?^FVA__bp3va-zRfEUBhoo-xL2U zFKefZXWp*-PjCZlOWQ##^W#38alD_M++OTncp|QX#IAx()Yql5m?yp2PnC&w5BU6;KEvfqxDw}mnl z?6fy~(_DT(`oa7$@?OFHVO{0R{7#VHE}HPi&^zMH(?6h>@~0|)AUn@!FP8quT;CFB zTsq;UVp#{-6X!wJT;|AJIE#OU{3qD6aQ3Z#*o*0B@!w--UcUg@lSbejeWMLlZ+rFu zus3`Uhm?wO?Wg{(>bY1Q_s}MYJ-|90d^&f8(7;@f^JcGg_x^1I5sDxPzp61ww`fXbBT_kr~1P|s~$F22uF_tot5 z*BpF_cztzlfZwceu4&gc{Ok$&e*R`j@jhwa%i{0DM7ee4WKSAs@6Vsh>?fWv+7a^p z7m2?hp1#c7+X}BICud9cr_u5rf&a~AdD)9MgiXrUC5)1nG0Pgu_$^>R&z_98#xJWY z<1?S#9WwUUsVBde4WzH29R)A2XXA$S3*krQF3Ot29r#&qU&zTi&fLj+98zhld=u@) zeyO532eLl0#@dT*gR_UMfQw)w+UxpZ2l{UOynpthv}+=?Q+71Y{+4%d!QP9O`Q4<- zd{u5s`bvJ*Yu+XM<=^^sJb!a`H`);Pe)v_K{U~$ml*HM6>5cd=@-q+8?v42Qee+9N ze_Hm)%!85q>?y19573JK0KYwZC;4aK+|{z5JhQZz>;1$_{97Swe=@WO&uu6P8+E8}JB6o+ga6LIkdv5mm zlzpH55k38P5B!HcOu5`&4};xlBWauBwPCUA)$uO-ww}ee+S!%fi~kUP2X-rV&hSle zXINhHuIh?i1;4;V{^$4(WwIxv%)jhQOT{-+#y?}5wr4)>%3sK?0rlZ0{)hN=+yj1P zXT9&Qj)(Y7Am!8c&DmYxI!M1??b(^9wZ&gAyXUFDIsY-Jp?p(%?yNK9H5E_are6-g z`3B!o-f?iPoQ(e%_8&O?w;3#8zpZRD@y6`&?CbHJVomf}`tJmG?u0MVTeAOT_l2Kf z6fI+ub^k<#wW!QG{N4HOA^muuww#Ca?3B$N>5|Igccb)WNBR(GOZ*P(N$;wz?6;Hn zCH|wZFMDUa4dmVOeVF~VhqCk3S&Mx-?Huueuvkuh4`0h(2c8wLmUl7zR{p#AeVlKg zE7|!r%sNSS8CvE=2l}(@*Kt?e4pLv*S5F-s z>9??R=3Rq-f+s7S#h$Tox!5POhBuMB2ObXl{ok{%E!iu)OZM$m>QM z!P`OpoyxrXQ(|ZFx57{Jx34VzOr#Eb9JEwszW9yom*k|4OZ_``AJguD59s&eZ=fqY z#~-bZA?#Dxm5@H0qHJT@_jnE7!@2W>@2w-)b75sgvHv_`Z)+guH_yo#+NLaDTW_G9 z&fZPlO*r2&`F@_EY{vU!co#mDw-9H~&%Dbz&V79`JMTUXj!>?n@+Z;qyUSWws9bXU zz=iC(IP?1(c3b(w;8Aw^2vtY_&eiquun9xS?qu4`Fs62+eh&~g|*Oy zeuwuuU*1Z7`tK*&6L=$7&d%BxkLN(<-hCCtd*<2q!1Mh2)y0{6H9vdgIQ~?*KfoyQ z>^bkVQ+5RVEYEDgp3i=*s@OZzA1}+vI5b-7%!Kqq{tnuS@@}W+&t&Q=mowo_{?(8< zpZ2D&Uxc>h;?8mza5o?U*a}6~8-Vye9BZleyL2gHOJ)HfdOv`%Pj(-Wx8vix- z6n3X_aUS+mclvf+vDL~v&Hk9(3YPFY$jyF{-^$V#`IgH%{g|G5CPO{3*Px$R{39TD zpIYT&&ZgfxYQs>d#hy;T$^LMwyc+E60Y51}3;zun$M&V7Z?m^OMavi*pw5FKIakT= zim!m?@-w&dcZFUn6?f@FX_K^jD*YGQAUIK(!|{HQGhk`CxPN2~W?WkGvyTmftHc|s z<6zo2e$Js`?3!@9Hat#yi`_U78_pl5{UNW$yHYX0*3)_Png~v~Mau-&9@UglhAYzmYO! zvCPlh&vT#2+#M%Yn|_~o1)hs@zQ2Q4@H1Z;@Us^szY{-w_^;S#e#Yj%koDTCvY1n& z`OW2Zpk2y-SAH}8)AqOAGxNJ<=IS5bu`hoNdq?Bey6g<#uZOq7HRP?2FXNx4j)U;v zQZXN|!DsUq$SI4p!+ zQpb8?S<`)#-IPBFUxq&rZ&3D|7P~K=!k-DtAoD$IJ#}n{@4!9q5XipsBmWcj8aTPa zo`=7u?d&I*+2WUhq3b=-(9Q$KkGbqpsDn;;A46jUrtMZ zXYM`1PTn(OgJ_A(hyE45{mt*L>@Lt=&QY`%l=%eyBmZbTTkJLOuz(%^R{AK&-KdAy z?vTChB(VXwg=eInexUD5%iV3i3hiazqD+2^>y2;bXHMr1)(F2PC-d}G_M0%7|2+R| zcJ7B);j5Ls4ZlI3kAJ0K$WGop{Mm3YT+iPSp9cB!_m}NI$7@H{+FaVp;yG9L_lzgl z-RV7H7f2rurq$BVd*F=s>3E6Q9r!AkQ!38#mFgbJA6M-hgu3+0arT_Y@g)90e3R$& z!FTYdRu*S)FI*pTR%CzA9`UJi8N;D40J7h_@4m4vZVQK07yC-arWuU&{G3m@TTT

knZ#{Y^a_ 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

t)^I;|=3Om*_O~W-{(!q- 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≺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Ξ=5h=<-+i~y z^KW8C;tkl|$7@Q(Z<8(PeaNxzB=&2*S@3}N({Vj~F;4Ez_KcU{F>>n4clayy!xOwKpn1>|P@ zx3VXXj`_FU+2kA9v$u2CSqnc$?|`!xHYC@F?5*eMdC$B@>SFGw4fHdd?OwK--XZ^O z`sLo?R?n^JF7Y$_EpV=wiTFtP6-J2JS#C^5KxPC4Ek1e-@_>c3_)AzYNcz?}3xk*RthriP7LleNh@?*n<~<@(;o^D?K4`RlQt0(tL|Pz##!--cg+V?4LM{!fp+>>aduguE>r zsgLZ_{BC}RHoNnuZf32<@!ttoko&M_EvJjW4br|9+jRT^&iuE~?h$f($bP*;yL=

07Y#dELbo6kN=>_vDhzS^atp7!ROM}B}k`zXIhJg!|`n8SW3Th58B z)3>lCeFvY7*9{1 zli+c-?;Y+29L@O#;o8tVX@c9-GbVLN>tfHNOc*w?kU zGv?OtEqyYy6EhV57p~UV#rUaGF{T;hoyn;!8|tG2|H0(z@I&lv$=SEJ;@r{FHfL;V zO!j)_^#=VX-_GOPqO!OLCuh!BN@vUb{2(Ulx&y9*yRzp_Foygx`8_;MU+cnfG55;Z zW^DVz$K;LJ?&2$vduf;N=#li>wcS)qLpY9{dhwvx4(hEqSjV1E}+ z75@g#d6#*)k^LgLp6w`{JMT*W#wvH4+3+ZP_Rx9k9r2ft{5`i^oHxm@MB{^z+H} z_)>%SBTw+YqsgfQlW`-qd&zr|)BkUfx>kW7D;M7wQ^{|U^FI08(F8n`{taaA6PMbR z^E6{FiAmlJ^Q_Iu^VpK}6LAClCVm=@gY=Q}?^WZ?J?gJgQFrIYM;;8*yl3iRmHuvF zKaMST+`QKma@OTYa^81pS$qBbgePj#nf)>Lyz4^G7=fFxkAz75U(DvF;gr?n<7cA@v|?($%zN? zJK{Ic$Fb}k*?z-$&(GQK=KrpToTF#%y1*sm#rjQdm*}^WGq;(`DV~*gsfN7EC%6`5 z&DugAcu&7=#2-!mRomRZr;@uF!&Lf7>E>;2p3BKnK^ z&G%|c_Lq$*doOc$J^8e1wV!-~nAg~kqt~a8BWK<^;hdj~@hs@4&8_S?s~^Lku^oWR zp8qPjhS(4A$&kEz8hXKa?YiT6Fhbh{#cfVb&b|#b;P`TJMrR#o=x-U@F_qq@N< zC$F>a-x$Ls^l!*H@7w9`dhL49JIb+b@&DTMtrC|txQ_p0{cOm0KK*+LcVL?%?gID^ zeN5SXSbxvZwUye_6Zim*hd#dpEcb^1ENgcq{pN$XzAt_M2Mv z3j5vgMpbb(jNtFfm-8ggo<+_+ScuE?{1*Hp?gLwE_ab{!a%x4+lhm>GAn&pldpB)w zz_~{(V9WcyE^a@Zab&L?LhdWB1YO14sNV;o0z+>I@pWAV7oH6a<8*MFpJVkzieGWc{?-gyb{=J~1zJAB4 z7k5H(D$k2=2z(E{VMC}3%}T|bwAJq(Q?BhGh79l5k8 z&V1%~qVZxL;yaN1Hu*T*6I$r&S>sD?rUs=ZWIUV06nYEXSs$;GN5M?`44fME5gsA_ z7d#H~+ikhRILPa3^DJHiUzE+A+CR**hLgX8+|jz=yx$p+@A{6$vk}|-`Z$6=&&e5| zZ-Q^3201ypISl7}#`Es-{NMQU_mqt9EA1!Y>*ZL(YV#^K?=z481ordk{phI)^~tZ2 z^NdLq#r-jJbF)4(j^uG_-ZiD-??GA5y7c4u`ok{J!Z*=l?78peUC&{gh?8HtlXI^3 z#HoedVU>A!f^D*~_oMGhK1=*WF=HX`bR7LY@=owA|6qFZbjxb*&-Mr8IrZ48`7UOE zSKPVew)l6P^{&a^ov%545dAgShrTyXJ$VCXUbY}_35lBxcj}|W_Y=7hxe?Bo!kM4h zaH=`l1lM940@snNAoro58~;f5!$LdA$kDDHq?luai&X z&%RBqI-cG{Y;$_vGtX-W3;BM4lgR79rSK#D8gX?X>yY#4U~MyRiCY`ezBWB|Dc@4H zjA2W0o3WkBw-V|=ZNA(A59dq1XMI!mg0)3V zb+Ci@ZepJ0PrYo;HidpvMRAw7n0;R{KeHVI*$?l)j_@7a2>H!szf$oY*;A{br}6b< ze}?^M`YOC!pIylZc~0^pZ9n(CHO88CPwm)Bn|0W;r>D>Z)-s2;`37Rj(K+SCAFlP*in4HkWYntV=jlC*e-x3kUlfd z$>&QUdGIT}C9I?;KkAXQA5J50$G@3S#JSATy)>gyQeU(Gg-e1O=z=X_{ZS^VAa zhKl0ezUp6F?pLpqC*!gBeHf>&tYHtfjO`-0R=eC&vi^6n-vq1Kj>EIX=Wizm@?{PN zvpLYiZzWi^Ni@Vk!wiWb7Vn_02pFax8ueWH2Q{=g8K>sW$N9E+sHCo$i;m>|*pA1^_irHe zVKCor?8%FqKaVG8t2G30`c+#`prmw*t;MDc3`NDGX?2E-T=F8kXs%`G2iAm13 zXV3d*|D_gXKRrjDMb0~#xJ%H#LAF_5Qu-~)9+|qNeyNiCgXPnQsr@jxRXIvNY^`Nho(+~1(DP}t4Zuf+?Irp~4 zW5qrpE_E{V*OLDo{Xfn=fqgh^%6=dHGX3Npl3JMW`^Stgc|Ac(s(eM;`6UcfeSKrn4P`sHj?u++=6SZGqjFZde%J`bGEg-kVcd#d) z^UkSpN7F}^pz}W*0J?~_`+QwL$ zobxf~#>RaAK=j;YSBp81eF}X8_TN2Y207>4jr7KR)5^ZhJ+CeK4z|zvvY!{RZ$v*E z{vdC~-;lfyJS#_%KP|-F&3BeIN6=Hl*3cJ`|Kj@@Kg^ds->*yC5_Y(f>>(s5>fi{2^?CsdrO)=v|>6jwv;CK*@m z+?nII;3mj-Rq9pdCF{5&a*a9-18Th$U zQBS_dE9uGa1;?|35HY&e-czhmTXmpV9~E&H~IxQih7zT`t{ zL{~VKegpeGY?;SfjQ1*XP1s)h>DDQ=I<@P+xDQ)raRbeB-n$MQC$ac>-kbU0!X!v*p>B=s)}Q7g&e=PqyUOMUXM(8Kc=UKk2U%-WK-LZm##q z9bq{=d-Q93p*E>$4|wk4vOSC!;Q4$V@Cfm(`4`|zwSS5IJobfmzcCl`CM zErap&-eMj%u13|xnY0f3?qc?!k0z&PTnh`uHh|>DOZrZYZ^eH++s|+tc@&(Yzd`&{ z@OJdmVLUzer5(v*J!>ScuYK)OF-JL9{uYxulyRH{-;wJ<_ET%|-$3RpIkX*r&aW4= z$$cpMV|{upzUQHfxO#XQY$@hIb9f8eD!7WiF&@Ep4rH%njHwe>lWXXwFMg42C{B*N zhmYjX_zyDvzHFJ3gV=|$-HCUGugOc`zhXY7_w}rgjO`(udVd-Ddj2YC37gSB5x2zn z>e&Ce(AK4pV)&T z`{5s0AC7|jJE+}D#lMfpeygipPxkiu*_pgZ--qD?#N^z5iSGq%`r$RO4PWZxaB_b< zt3=jz2)@(%q^6w!^U7+g8rn&JS=Wp;XFY_O_1`u-R~C7E6h53S zbCf-k{k1QAuFYSbw*@{KM#FdPr2o;@@Zy0fh|p49cM+hTIoX>&LN9%jpXz32U= z(({|Z{q)<#)qxM$-i6ea$?TuQuaF%4LZ6R`IgGq9+YmAFkJHBr^K*{*y^t??@;*Ez zKJ~g8f97Tb@g4c|+~mjJI60m@)5Cb$iO)K8p)Y2y56Rhvc;9mIjeM>?j_|DP{p;9H zrnje$BX6bMq4fA_(DS!})^Of~F6#s#1!P#O)^SxG4e2eVKmS^6A?}2<1=iNI&@}vdMx{ks<_%n~$x0}F| z&`?Zrd>8UwFauVUy@R+t`M#!~rR~*X4kvF7P5Ji2FGHT!508Tb%f|`p&!3Q|Kf7iq+UAeuFLRmiat|o;~tAeIlgqzVMXTR+aLV z?SFsYWNZ0v+e)^n{EtH7AEIYZ+(Z7{b2FaHv>jm%Hes74F26+`gV(CC&+r)Wb>J1K zN#8`9rF^}_e}t0@N5YQm+1t&u&wu|vQEck}0b(*YtI6lEW#1+@b57nVekA`EY^lrV z2N7Mxsy8h zC*)o7U2vm5x{(*a4&-C;1oqU>$33Slw4k5GK9QUnnOe3B>?=NJP|o!{J2|?s*nH1y zK%T(=D?XiVWx1#k*@N$rvzOM8!za_9(B?6`0Fs-%yhkm*i?ltGJ{`V>%;h5dNY7%O zHWTxe*kx=x^9}RNf5_Q`>30s)f$ivZ#AaP*de(9DJg=rY@f=S90qFr3DB)?)dnzmD{~;Uuwhan9>|a3B4TDHruFf8)4J`~CUuU|Y(6 zAMSfRH=>Rf#Q=l9q1Q}dy<^E5wOy`yghKeUq)YE(w~@9+4ia~ z?xX9g5!vqtu|19Rj19<-RM|K5tijoEIQ!M~lVK=-p1nQ1!q>XmGuU^*P2ffT1L!wH z72HJ69G?r>Q@0t*JM0aillc7hQd2*3tBTw`jeRtGZM={8*VyxpS)b|T`|)Owx_kov z)?)jYit{DU{*?W8`ar(a?A^$<*z?T2`L2L1$y>3l#&;Ue%j7-qk>c03XKsrfQhVF5 zrM5p!&i@Vdyqn4AvmZj9E$(Y_zW;l(AL4!PtSY`evyNBrr52>delGTX*a&j37yy^T z2mI^mEA#djd9K(q$@w<;08e3i1D+s11EZmazLPuq;^WKa66QkA+cVjE@ujBhf>*KU zos$1k$@f6!u@T#F=t>??Z7ldJ7$@#$yr5M48{`Ijdy{kTew)1=Is5QgydR{`wdi}| z&XxAQ_Q~;#djgDt*;U>h?iY7#sW=C-{*Cxj(=#`z2{qv{@}AI^oV~RRPR%+({BV6I zH};2XAn(x!U##!6&78NSPlrS4M{4sWepS11IRDl!&pS$7p3w+zNxz@{QF`K@Bj^5F zd#Sz0ms~oTzAgVCdTQ=Rfa3FV{Z%FyB1Mg*Ua?!~Px0{)pI(_zvUyiaZt{B_{Je zk)C~hs`wk|-@wgmnY+}a!|03jk@Xsczs57-!y9RnxvUABLi)?QW@6A^& z=2Z3_*q(vB|Bmn-IrINLd6vEtpTC>jOrAy_!@mdQ3>!)hufpqTbB>tVe7`{QsukNY zH{4{3iMJvAZuU6;NoIq#f4 zccX6uHORGLo_@d5*E{;jH~r4yzGvS?n-B3SIGn99B)9*-Z;M?4n}}(`wi|gNdpr6W z72cJcTCxdW?y3(b1|Pwev!*Fz&JSY$9d}~S9GyrWXx|>Mj}m=Xd@f94Kc4<5w1(^H zyWxKzXVUH7J$q+5d<2F{_HG@voQ=6hHz6mtGw%)Ht*YYu|5AJp_Umy8m+=XBoHpO; zZw)=QD|0Xcu7d}mOUZq+s<>}1A!pCuia%pZ{k=fU`tYkhdf?pk>*G^l|B^fvw--5e zGX3_zjl^EXp0Omy=Zkv+QX?`?JBhiUFEy_pxfR=h9z`D4sWt|-7xi%$`!{ePe6PQb zW$&)d<@7tr&ymOBtnI7#9BroJoE52`she$}3=QdF*EUlN0x>Z1j`OwV`KD*n`iPLO_XV_%53v}Svflh>W0x7g$9%b})cPh(pf5__jU zhVbWkd%_0B*hEbBQoEA*#W#k9t2(>LRPjl6BSc>gtgZP>C_*>m|VB!Bz=6(=|H z_qCJ7A56}^9YSs`HgkBRJ|4xJ(;vsVGquDovgclv?~B|s8}L8Fwm#pv^mq9N(;LIK zeD~l>;AlwxCs%ea6~8n7gdbo_ZcHRk=et-RkLhDiZ8N45`R;|9{L9!ovu_4#(XW6i zzV&=_o{js{bI$CFkDME)q%FCEy`%oN!>w@k zM}9}h?{*uob-;Z*`-Yyydgojn!uBj*-Y2>8j&{xH$=SC2-_sXr^E&xP_T8X~*oWy; z$oE#+yX5?>IBU2XPsI=MorAx?ZNxt=?p^wc^t<$#dC0r$#n*!EI=FznHTyTP38Me;5!3S?+4K@6t_Jd?tPNqZN%q%8^ZP( z+~?hPWXl@9k8>tu+*gqg5I3|WSE`ENcaqCNa$+QG$k&Q~JkGvJ9e9}BoNY9>)c5 zT@)Gee+QL|9J+>{v$&aYJwra7y*IuM_kxL#99`G5$K#_RcckHvHF$?!#`$hvNxlgV zra$JH8Gq{6CwL_L8rai%*=mbPn}8J1oLqXj3DPsJWkB(Vk_y_ zvJWS>ht%ebAvyaov=BGEvZzB@-|zV!(f&ql&gS0(x>gmj-;>*T@4xx8*OE_n!BW_b z?+bBflh=XVlNRBZU=~`W%0YgA$&X0Gse|b`lp`{HTa*#Pr`(y(!K;78hj8xEFW{As@h>eEzx!OwURU1_v(3hp;)n4~ zf~?=0(7$XPcx$m2buac?Ckeu4jI9P+zCX7le@)K4t37@Ds^YiADQrvGCh$KFO|)&#m$k{gYX`9zPiMYw=~?SC zc|G<4)#?pC49+Gm;Lp3K2E?9sS`QXf6u(j2DJJ#j1pGdJvt0ZZl)K%LY{{F{<=m;0 z1J(4JeED7MO!9H~M|I*K^19^QCDzgI4zW*LgVpR=^Xuu0wM%`S&-NAl3rJkX{5Ri7 zT&Gff4>|SVF3&D$`*uZf=C2TUGYsIr1^>z3OdmD;-q4z!nvs3G zl{U}g*I|@C9uei~S+;OlzAQy;^MZYV`wl=Fhpk%zqD@bADg`Q>%Orl#1UE7LoTO z=R0S#*zA+9=^x?DQSL0y<7sT&@x9Q4?X@MvyRFN9ihdhG>Og1xZ)F^r@0H{s;(vot z^dayaTkb~n$kVh*T=Jl@TfmaWuJ);6_fioWaP)THm&N_@$&)VsI5(~!#I z3`$LUK!1sqU$Rc{5u8Y$1=o|0*Jcg*5%NiR2kpC&XF~FMKe(6t2j0;$ z^BwXbeU!Mx_)540J~IB)rTOH{;X3wq=IEz#@$GaCJ_N$4k)7cIZCcP%i(bNwA$$MX zlKKPF$@vZ)DK5W1{LEH|Z9VeaJ&M{oirkxRDPOfQH5QxNGtO8iz!Dg2zVqB^kl&Qn z7nk4OQx~#t@?DX4e@1M^xHfwewl0t{qOrIX3*1LYV@=8 z!{9W1h12)WO>GYkG3vFPI9e#C!_%E9GyM+$1kH2My@Qi`^5yFD}o>-=$vR|C2tR zekMGk&58QWx6mvY$JPScR~O%qsb9yD$FOxG55|@H4>u|ob2^%RB3t(VQnrmfC;N8+ zc^!SeN?w77irb$&T3q&LN8CZoNOFFsIfFb0w$^?!TW_4_W4&ZFp3hMmXbG0`Kr8$FIiY;WJ1+d|iXr^8dF~hJyLEBn8{xOX9E zZO)JU4tAipe3OpV_GEJ2|5M1hl(`usZxY*;E$jO3zeruV1P&+{btU(=73A9>-!=8{ z1pViX9#SfNsZ&otYHZHv+I(Hard@I^_l+lv?OOZ`WDL#uGroQJ8^Ocu7qb0`lb3zi zvIj0AHzhxYvv+I3PxSBgw<-)5!u93i{3+=(XHxD-$@%1P<~r-0nt!CYtmAju_oqM1 zmppnIQaduom*Xwio0Ic5fmwJfwya^J%Hll#O8o0$Ci9&@J_@qmZl_;Ho~vDba?Ypb z+SFqEP<%Vw4EA9!!4l}8-5`8m7=ALpna7;DPwQtQ-^ch9`aHh1pfOwKcl|)@_59cH zA51@_RIE>)F_F9nU%u(`?YKc@v2XLfukg=?!`Yw4J+%LWy)ERv^9Eb)i9^W0KqLA( z+GoBh$;sKy@TQov*!mk+51f1SK=!%tA=?zT?5FIR>0+n!D85~@HjnA|FaAGapmw*2 zNv+74mNVi`dg^A*fviDy`i5$D=Iagi?9Io?)AX}BZbnc3)gkYT-x9MWPCb8)d=-o* zza+jk`9J02_lgJb#gH|=98WXGg(Y*2tHfq)hg21{uqS)Y(wTUdN^M{QxfAR~ZVVff z^EZaOacWlTWd&a89kXs1vuDopJ(C(#!*f>Rx5W%&pM-O_ewS@A*smqe6qh-C zo-KPebM^{b{ysY1^J<8Do-K2=7ke);`|@o}?gFpT=ke#6=`U^X$EQQisPvn>+myUb zMe&=!6MTo0Pi8xUoP1k@vu|?FZLg2qA@;|o;eXh_hSZH~wE5Y)A5K4po*J+{+gdP| zt-dx#u{VI+QJb)5YU{ z@@`ed{5K}Q#eW@q%Qv5X2RuW+jouGuzTSgp$!BP{hkcO!e?0kUoc;WW*yLC4F?p}l zjjM6)uBY*5+^^9e6_fAS%=ZV7HtpDN#_2!x@_PPr$y-4cdvECX z+4GyypJ#7PUYneKc^l+^Q*D%XOW5-4mvKj&HOzP}7vGuQk^L#?>KiRJV|9hR5%U0B z^5HeOmo4))mj4)te@8eC9;D~om{uyj(Y9y%)4mv~%`CR8SN2ZwwL5;A?L^3$HS z8v5tt&8qz!<=?oG^aJ#nT9(|(cxv*m4OzR?nXJ#ceD~3JrssUmShn&_)Q7Dn`79Ww zpH0Q>i1Tf*4P3)M8?J(nmlpX{rG55t*7rzwu3X&d`--p0K9GJi`vUy6Iav>l}`*rd* z(evt8z6Ic4qMhWYu?=R+TmIBS8(!YKHeS0UFcWii>r$+l%ik{j}8XIksB#4&<8nGHr6_oTyC&TXHaY{v=-=dI`R#pUa*xoC*2P%Dp3f z=gxWr{S`4eYj)?~g?)`aXYjS;+k)*7@)ct8Z+@~5$3i=Leb`QXOL_(Qd3)|Iv6J+b z`A!Xa4%ZWt^R@~79scC&9Nd*H&zee4O+JjA9N!#vE1Pe6?j1RwkD}iVW5}KPk0ED{ z=aR1$ld%kjzHIyAr;Ih{Qtqp{k7V9kuwTQU`TK#qElzIa+q`3Cah@J5Hu;+Oew^<< zT#v1xnCz|H;8yQ>1pAayvEHe#xv#B&jB`4B^0Os-*5tqb%~5N%_1XGE&XZx)?uz0s zp=X|^le1P^@#V~^z(d)yj>-Gl^v%hAoIA;>MQm4ze*=CaXTLv0?!DCe;w`=Zb!_#t zd6}MCllNT1mizV$`t$HEeK@@qWIZx3o58{CuUAOXHD}Bp0-o? z^Zahwe@94t%p9Z^983P2FYEn?cU`H?(e%`|Rr>A7J{Kon$MAI$pR;^7 z`bc`lp1t(7wnwnt#`h2VDz?1yhUC=EoNG7ok77TMo?Pxk9*i&I%YGhBpC{&Xe7|<7 z2{qW0Ph;2?!z1Kf@qyLm(K9=VnMI$EuZ4GwXEg2wbs%?$Jo}(>afeK;-Gsdddw!2v zu8(i%kHY4#9{)4?XhY84Xtt!dhp*4}LAkh3Ei<;v_wn>!_@`>~EPu|-@8C#%cY@4g zU%mzSBRQSAbSL?1Nd5Q;&ehi+_(tuY(dHJuNwAIB=J;>Ozn?vuZy5Pramjfq%$#dW@@;`iAz|Z_|N z`mL{R-YMts5!z;)2N_>Y+!4C5pMkqT_*cI7U@-Xw*iIiWmb@GJJ;<5WOrLMyGGA)` zc=B2LK7@S{J^7huHi4|+q0q;;o}j0;{h{48)Ia`w>1KE#ZU&VgOzi~|}i+?k)8~bnkz0{1>+${DEwhth4G7K^odG;jyZMnE7?ub(#wt@lrt5u<-Sgnsu#D1)%^ugcroh2^0 zlU#p*{{Zp`@)qn*>o4CrFR`D@Hb$H4_;a61&F(6u1N&;Woy6sAxsALWD*1A!&zj}D zS|ToIVcu(B7zi21-fY{GPxVfjuYvj*MbG-2NzdBly*G!$pagwOMNY23xpNNin-t?y0egOG>J>QgB=Tq@; zadmK0F}Y8@YAo5`Ift{RS);7MpM2xk_7i(L`=+oVTTix~_#Yro&^C4AF1DKdZM1De zU+$S_;U{7Hk{XN;fsf!KNRFpgeggScSz?_!>0>{!aScitQG- z96n{c55J2qhmqRkY|n42t=K!p2eb5@d{_W?>i;&jXUp~{IlmEp2anPpw0B?P>sC?J z#-rE{gG2c*WFJaykFSQW;Y#`nzAEuQ(`UhIw%*3D!dO@GEsqTn^D%yyZ5V7q&iduQ z->6wG=J03sR9^bCq{kn_P#z8T5da{BzjX z<fW2|sf*da-Pt$MW-{MgvANTRC-WT+2k}qE59#j^_ASZT&$Y$RCYQ)#`MS`PyJhl@ z`p7<=PJbRwA@8K0-Pw~%gVR^$|39$CJ0wpY zV$1KT6Y*U3^^9SCaxchSr7oQ!F8yX-CTDJf-Ra5U%+E6ZmV9}yQQB{eH=_TD2WfvV z|8eAR_y&2_0`mDK@11u1d(d;Ayn?TJe9)i&Za5s?gyc{I@!N|}Ey{Pz?@NmDugmve zdhTJVoqNMmklH?!{S*Gw#?;?iJ!2sKJO0#|-Eb$o4ck4Cb?T0D7H+KVrMNl0v6zYU z?49h&f@9?+Hi4ru0{EEy(y%bK67u$^Rx`>d*0& z=G${pTjrFCb89U5UUKe;`QLC&{XG_PhsyoC00WBc;e`~qOgA2)7qi-R(82wIYPoE7r=Q`7OgZJoJyHhHQ z{q!Gel(~F>Z&%owJ>NRX_bbGm2HUdzz_vbrM}1{(hmm*pcc9P6jmSIUPjJ?COEI6) z=UcCw?MuwpUhIqXu@KM0wf(Nsk35sSg}D6Qu(>vSRjRdBMSaS9HiWjY8akA%zt~=4 zZ=^4PI@)i;-cFlK$%n%$^!9ih-a<@$oI3Ue&Uvu2_M71jRn8$8z+MyTmfZR2S;OS| z3bw(cE@qhePspkl4)i3i{pR&a8AF!?WlovS+Qc4r^<7DS2D{ zrk>PcyN2GKeHZ+8$^5Y|RY6g__FV=^Sl}IG0$pfT=$5(k9-b$UHUKZ8odJy zCilWw$HU20vP`0ddzF(H%&)}(2QRm)dYp1{Lm0E1gD~r2Sa&QSm@5ufLc|J}pOs?Hb{t~x> zk^28dKRF}Ls4l*XW6S+-S95fLnA|aH@L$5VMBCKcR$}&LU!Q+5`(j*EeD+uB>qP!0 z{2BWr^mW9XgRi%~)53g1@L}cRzmv;5d`KTpKT7NX@|MsIhU+We4U701KxeodZfE-$ zKLx#Ey;7m~C;zUGN$?jrXVwtBq4?+7@(rEe12QMcv(4Z^{*CYt_+<7^tjm4mUEu}o zc7&SHi+;a8`ijY(&bLGMQ`(NAf6V_iG*Tm`i~EhdOiX@@y+vG0dhXC0;saql_GRK` zmWuD6UCGtt^C9zoF+LUFUM~7?N^Zy2PhZKq=h$no?Zx&jeLMVsK9Uzz^uPUE-OasA z-OA!T`cQmxcvsvjaE91R>7PQz^)#LgM~QzOSCoqH+|1Qfdt-(8|L~nizlr|?$Ud!t zh2#sMeW|#skHB})`&4;vDADJ^w~#x)X62&RAC6O_#(I9%X;8_1LVqLR1kXwx`<(4c zI0J5>PsQu}%Q+7k)4zd?FYovb zriA*Q5Hh4BWS)uFJkS20`?vjF*L~g3wAS8xt!F)J?S0M(+yh(iKLL-FiuwMCoPBgp z`fKoiby8gBCH1&I<53LsNR*bAynO+ZlKh+>rfFay5OQkMquQ z5_zR{H-_F{%rCGldncI6)|dVaTtM%PKVy4cKY3p{jQ$u@z>Cg1JMf)OzK6dv`N1W{ z8E78)MD`K*amYFPL1U16JO)zx@6~1toO<Emx_9vd%s>>&Ks-wtLSs!7V=AwGvZ{}iN7&Ex0|(xFV}vC zxQUQ@u{FJxezF#xAz#P7g8grD@?aGGZ19t@Ty%{_yF4stoCtrWowmROD zy*Yoz?mlu`ZBmb}*VlIBYmG^A=t8kw*;CsZl3S9G#cT0o{?+X!7-jGMik>>3=gK%9#FsswH=Zm0e>mU8zTnHe<@}d9 zy^1eqfn2LKTi*XZ65E?CIr{}12evkD=xaYnfKP+aX{!ZmfZr5ge z03IQ?VxNl7fM3X8vM0y(pm(NEChr7SFYydGIdTVn1b!rcFDALvivB(%cWTnJM$U#C z;49eK{XbML`na1u5;lW1;s>*TgFmHbtq$~zc_*E)L|(EFVapulo)6{Q6YA^lV|sF6 zcDYz13vudwGrS76f;Z_OtUa`;s-wV)n(eeF+YZ$qBTmYVfG+aSn0)$AqiMGm$T^S9?4sjtV#U7&~9 z%x7b`l%8|bd~)(3?|H-7F4xC{Y@^7ji~G|L6W5+Tfc_x;GV&XcbLF>X?{<$D-x;&^ z8bR`RH+-mekBMIg$I^FGd%k4rqg{QRF}fXReI?&^fT!uH?OC%Qx%Y15eigpovcEx} zz&?R3<8g|1N3rj~e+oVIE@PGR@O)#irc``moQ*U8LtqNzyT@cP6WJR;6aD;#vlsl0 zbLP!E+?sN+@3$7eJ^SJ%>LB?5@*8}~)2xm6=&65+dysq+yv>(;?8AQ+`(NY}$Qk45 z70wlq-^u-%Ej1)Lk~-H$KUpV>VI#=+XWiBj7h6;MMdYm8JX2rsbNCm)Y3#@2vFw?H z6}UUwNiYl=c;{@+c4tLVmr{FjMp;V#5&xp?O#al&tlRzF-)U^$k?$vG{Xa|YAa;k! z;+!~)o_tK(5#;^td5O<+-mTx4$a&X#i+&PxClAHR&$_T9{Q+@#{=P5>+OoI9N8;~X z_Ygb<+KWBgwKK+#vA=+S)%LdTMSV%VYAfbR_D)sBe5@regl{1G>Me_lb87s(*fteE ziGAm?HjrE>yT;q%s_3i8EpYa{Uiki!H78~{{~W&a=sn<1_mi`K#`H$|36MFs0l!r) z>e+?jbKOpC7qfK{e?89gX8mqOP9Lc|sV_D3nX!2k9v8DQdn5K!t+!iUKhK+-sp8um z-q-&|Vp4w}hh}U?;ET0?mi&}9$*Bv;Sp#2@^DJrK6MvVm8%J z_KB;>li(71&ijMO{UG^RQ`_(LvzWdSoX7U6m`z|gyv=qjr0(SWl{NbreTI8$r0=?L zF#Ci2=ff`inV0;YYz29c*h(?yktcL3_M)NWR<8T*l46g}UeFMx!g}SRj^2iQ^ZyA~ zuqTH*>GOB?b;h$Z-e14-*v_gb#y|J`ptw9=3-;Yyqc85kelyz$ocXWAo@-@Z^9=Xk ze;~Cl=ar|*)&l(*`bv0>?XGV2Y1~45#yGinAKL={0q}%2sV`5mpBWkVgc{_o+Wmkt z)_3EqgKc4sds|Fj0=qy>NPW$@U~Q>*SNMf(8aeaUz_l}$-&;SQvTX|CS>+;Ty#6*! zdoihpsn36EcPJhT&+$D}Qs*FhQ`XKD@^gGQz;ZZ_-XHdXKJ?5}^5_Zr>&ATo9>|_= z?yu@EedOO`IE!sF@?HGZ$@8?yGp&J9FwMQ?9JIA|8UO9Xjw`7Jd_Uj`{6CVnfSlX% zyC(;-->9F(&=K;SQ*p*H<2{T2bKDeuCXd6ZN4t{qocBU^dTVXw;jGOzY~PaqaUc1v z`!HK_?lBkuo3M3f%bc7?J{d1v!Vj63hv*rnN%XJ8WgHJF72}+-ITB{`kHopoW#TgL z_32~Ccd)G{KTgir5A0g}9owhwC%G^j?qp9+b|e2LZV0&_Or<9WJ|+)@zHl?!@A|)q zJdwN{pMXf(E~&$wM#Vchu^(sPZs$k(x@e#E}F`2OrixR1NU^fdlE(=!)4@;^_$ z6nB8n#XOkDMxS}-AIP5E%)Xs9d;vNAy^8a^+1F2}XMArG-$i_T*SwIPdc7y!K5U#hGe|7fG?DyhH@ESSs z!F>JiCnjTaJshG<*3MXRb??GQ;_MUkoh9<#knw7&&1dX$$z@y@O8V%Dn?rIuHDnzg z0qx1v^!q&Dd!DN=+ri{ExUD`jck}6~sU69w!P!&WLY;E)GuejjX)nI%^q!D+$y@j@ z`&U0xo0;_8AUT$tn*x7B^7GWPe8JD-548Oef2+-6{1zUMlOvzSYh6dr zS>$P)JZ;6+h&}-?67v<#ePvB9#Cw;Dygl0ezD>?PmGi`>g2)Y;+fmyj7dGV>{rXkgV~Z_Pw9Ijde&hp z*LaCN>$o31H9BL`0_u*-w<@-*(Rnz1t%Bple1LPm zdyCEA|0jorl#4U*(dt0*d>j5##U4+t1sA}(WoybaF2K3>_t-Kv-x}x7#kR)1-Rp+h z4uZYN`@orE{v&>8oUyq_>}L24`kOd)Dmi_<`+1JR8+BaMt&8_-eQV?k*SSi8I~z%Y2*A_hml>l0PTV zbB!zbvW{}L*h{}T2fxl<;oTs8Rwp;&&)$>widX2J;d#gzEbAcG{S+=U-g(CD%fXhO5dbXO@^$QWsrTW4g1CV zdWPPWyn{Y7hGlx@_b#?y;97buZ8wMU^yV;wE!S!&ZWH?Nuo4bp%dI~?*gsp=kxzT?nJ(i9DM*}opdf2IaQy3 z27O;Khv1vovp+P1Of*vmFcV$cY~VZ)%%<4kn*P?#9R0Tii>YTx!41<-zE3Hqo-D1hbtiG!H4O!$a(H}aWi}Z`w`?Rkg>_T+g`Yl zy7y zJQM$@{SUYvy_R-&x<u#gXCTz9I zyTQZctMPPb>pB^mTgY|w@n7>kK-)6po%2qIfj5a%6WGM>S{CC<5PA-ONPT4m8k`o9gD$EWYiU4O`Tj6>OTpKZgi58Ob{ z9&il#7uZ9aylalo?_Km``Fe?2<=R)%Gw=CMa~k;;`~mxMjZ?XF`3*m}Y)6{@0UVfH&!l@h7;O_`k?`{$z;$B(&yk#(3jk@%Js?l{`)M>xTFTm<4b)TNzQmT z)xIarvy6dU`%cK5W`Ar9Zu<=elpi5knhH4;jF3TLguS6+oAN>YFAok zkmuZmuL=***RT5OBmQW%th4##*ZC&Uvj$Q-4#x}eW^f*qw8>gYzo*i(z7qcm&OUc6 zY|7V+p7Fc(U!JV*HLjWOo*#?J_%6o}@L%a!o+CGc>=$3S#t`vKA@`O#^)!3t`Ud(N z_Tse5dfp6%v87HWzwd+&dmP!}*zW*7xm+!>_UT z6IYkK59B>_y-ITmdFHH}oa0)E%k?Hf_LS=Qy0YBFpW~cWbB|X-Q~E@lb4}io;SNTCf+~4HMX2!Y|?zwEGFKUE-eD@*eYBsW=n;kKB#zYZyU(oc(va15AOppoeQc zQc?W;ev-IP#5@7XpXA@w#<45CKm7u=zn%Eu7PL_zQkX{et)T`lc^gwkzW<}DNc?1 zj9gdD0?2b#(`K1#WX&h`0Q?M|Q!dWXgYgFXxyv<&u|G^-rft^6-(tUF%eqZ|ZHQa4 zAH?=1oI<`GkAsuR$%F3Ne2L$o@2KD8;XF78@(m+%nsZI+&~fhTMEnuoFOZzLm#wjD zrEa`{hp?p{yj?DG>{#|i^wgPian5{sZ+!jVzOT`D&f)p4kZ%vS@n!taC!Zy@2|TQA z=5e$(E$KbUjl_2rlN|33oA6D=H{sOQyU4jlmr`-I{hoZhzBhuC$p7H%liADeq-XrU zAdeD19%p_pBkzUp#Y1sfOvW!`S)HEWJ;QfFEZ>Jw z`q{&^*J_g-KDJbxIZ|^rWPcB5?6>7RTI>;cbGQelK*lg39tn@ z?*UKiV{dV}_ttn%-vO49cO;)CCh^?i zZzwkP;!*b8|CaRXxSjaJ$bUn3`o=I^o2|4-?VLu>8eipl_lX@vz6G*YUZUsTlKZ*l zN}M`&H_rL|T5*p;>SeB1Q7XW^CixW|Qk_cQd(7Wl>+UzWejf#Hp{@k2}%_ z)1QW!$fi zz^gcWL34a0)aN^;!u>3ks@0^|KBk1$#IcppOL$w*ZZ0Yy27UGuiSjq)gAx z?Q^fmzm}E8i+&@2H*q7`A}@oy)6IkA#BcQF{O2z=?)2`EHM|LXU1$g``1^>zmHjRH zUGSj3-=*I~ZVy*$cYj4ub2rDS0VlFm)9z{Qx2AtxF3#r*aSgtHe7SxfI2TS~KUVBK za@swhpUhoNJeGa{d+NvfcnSPjF20TKOU`+63a*2H!K3gTyk5C@-pobDJ#}PPeXhkN z_>8R?)Q79t|ATYRKhZUhCg)xj?^)!|QRKd2uH&l?yHpnW{vP{P#xMIp_Uy^V<`uSW z$iK8B|<*DSLq?_1D^z(IUx(6b&}@}~~vU2HkubM*e&cdb%O+*j(|vuq!eGbcC0SaRA; zCT9-+j!l~}?5~hVvh9EyLB?cd-GF>7dw2FWxFP$Yp8GiVP1x?oud?OcqdLA3k`oKd#rsS@ycm*)b>LapnVxaF z1uxKNo;kUfv&qK#OpTt)o_WaLaD2JgpTDMGN!}NJCb!``7AG%OE-A)uIe7tF*_`Ct zS}Xb&a3f^y=IeJ+$)4G@csHx5U2=af$QsUB@(D5jqn}ML(btLZ!gdr})=lckaQ0fR z--^D7J-IOuFXUSS55fn~PE4LX`BEwu&y+Qhn6$}zV}Ek?!Fo8k&>S`rKVSUQY}5Fb z<8kmG_U$}dp5moYmh&3l-jxr{{{57;byk6^yEeMhZ>OIv3Qa1E%I}?HRIt5=RHhb1fcnh|x_@5w;ghR;LcL%cP9Hn|^p zALE-`nFZbH2YQ}9+D)ae!mIJG|LQZy`@>_}99oh`NLSNPBId5Fcc9i?tk-ZK3 zDEfo23;(I~@wgt}Ua*RsT)bNUsaLl_)=>+#F8ujkkoBK;%3a}maYw*kkahR0*uMOm zic8Jjh&+sK9BczG!%KX*#v9t?EI$}hBR?;B7hua7<7aVW*|J9dCV%ZY&Sg)%Ob&Nq zzY0GIS3~BZHOwv*-(lVgHi-1Tt31ljK6qaM`23r6-R%yVf1_JkNI!PTt*y`?2R;psMVQ2HERtLtVZj zA#-{O{#)Gk?7Mr$5%fzdigtUGUlY@|vN&^|%${|1mDsGOg?uaA)5GGjS0xu(vt0<+ z=__j{`I&P^Oa5xw<(#@FG+`fH5>HNjzMp;xPJX2}&KsG%^Bfl7(%Y8pSs=EJn~niVlHOjui<01%ug9-4?UE;J$WXc zqu(Al>!gXk9%f$!IoEuMlS_m6lWU7$65BdR4cOQf(5B2t#iGT=|7h2DKLuvTzbZT3SU3+0r)^XPWyA<3vws8wgm2BqBi~U!}RUo z5&Ay(`LdjW)YFlWdB|KQmxi$~##@Hn%hlwZ4FePnu`4a*Tt#1*(d%i7kl17 z@)3NS;?$a&e{EK4uaH$zwmXi8~aB11GbLhwl!`!_q1mpN*~JCnD0p#PQC>mAtx_WpRN*{ z8j|PQi)|}yF03fd=ijoGVHkh<`2hCe|56Q{M4kfSTt9U=b?Y$lF#WVDc^4zUsjnN! z!{J@H3A%}GS1!ixFZyyZ2jDjBsf~Tfd-yp}`o4pl>kPv$L!Ke?bv-l@yFbqQ&6sCT zDv|5>uG7Z797lde-`Nk6n*;c#vAqe`Kyu&>NKI+2uOrIEdd~UtL*qD=z72E~a|LWn z&RWR6p0)Cwu^Ws#Yd?eUH1aV1#BRxU3R@Gnh@S6XcaVF-MC~GHJw1(E!TNBjwlCop zk=?_nl3ZlFhMs5c$u=8L!>9Ajb&aftjCBLt!9C|2NA`!hY&rARfKS+4;!D}K=~5|6MBP`~>oIt}$HO?fILq-^Z4HID5qh;=7Y;^8dq^cYvSi zL&&pmPn>n~7~T>>T`i|f$8gj(cE_nGS-?wmi4tv+lGGubn5M``~DZU|qq zy$3sxa}LWs*3dm2g71cX*=y5lkh4xR-Wj{Q-SaG1Mo!&WhgZVfl4rm}p$_{A^vv5j z*voZOe{Uw|?Dq}MdQJVPL%*BemOdVD3CZ=?MwRU~VsZw!n!FRfmEMn>XUG{m=da;# zB3mQ+?KpFnZ|5D@yU;7h$-(=`J5&_=*J`#}^yF;j{A~Q5`2XQC=J)!Nvm5(Ld>z{@ ze96(|?L2*qC!b0_hkYeExjh!%WxE6hkjHc@_Q7+=8NYh$hvBE8kv?{IzvpW|9m;IG z!?IHGy{Ic+@+RLQwr9(pnq18H(&y+SwE33rzuF}KUZLmTq{*6ihuj%95T81AD|=%x zUC5vDKTqDDuaO!tnErojq@q~wkMNxb)A+B&@x3S}bFx%FBiWkjXG8it*J#SW3ETBJ z&(?-Lb?6v;BJ2n?AnT<*v?kYq(J+{vdb&_Q>94MSvvzXd{n>kwYr!k9scV19_8W|H zuQ%h2#ix85K*nn>9>%@_c_=KRpGkibe%9u8*Qr`u>|qVqUgiG*nv+N3*CA`|Tc{0p zK+YT4W4WtZOwWFw`gFMK)hx?9w(amQ{H^p8 z-#hd*`g+4SK1S{;b|F4q%;q@va6in6EM^qWIA(4KXg36p#;x(8rQ%)dEb@V3rf8RM zC^-X-g-P%fU)JWMczwQ=cx(1{o?Kn3%<0^{9eL#ALRPU*U#B_@SUGB!VuFDAd@o>!4ShMypHd`rk!x5BgVQg{ejl!~0~gKw+w zT>PW;o%s9NQU@;*m-`rw&thB3KbXIgExGbCJ$dyG-)Yd(`aKY*4*dr{*Y805v6ww^ z?xziY3O<0{%f%U}E;;rMp%&X;cmrJDvz?~R67t5bwLkldxEb5QLY6p6zn-sbVtzC(%!?DxQ5G@?e94>fNUc7OZ6vfaq~$)yv-o=Bcqb}r)Ih}@8^3ZKqD27k$y`zQ?QC&Z z(Az<-d-4)_$(Qk;1Y3%^kZpbaAIyJ%vCCS|eB~Nnzyx}GXeH)OJm+6qa(;rohOj*& z{ssIfWNjQK_7k>``OnbjQ1<7cKK+j}o0zPNjMK5?)TjC6tm%pTKahVTC!dev8_zZ# zXFcXV^WFIW>IwOLczubp2;QZn_Eebcirq{p0uzww$>KI2RtK-%<`L&1Gp?I>`>u}cp^N@FynmBVgu2jg?aAR?)PqW~7 zzKs3ZaF6Re$hR5vqd&Ps{^L%#Gfu7_BjykON_zg?qHX9o({vM)YySdy{>#Mn#L18N zc42Q$&srJImgk+OpPb)QyHd|ShTf3;s3kW0VgvFPZ21oJwD?WwsRujoKgr&RtqFY> zePlid!y5L+{P~&Wk*NM7Z7_u@|CvUbPlrwLy*_UiNn z(4xZTEnGUROgHCyIlJ^t;)CeKp`|FpNKHXlhopj_0fe9N0l&+pMs z!8y~MNWQ>*WxYPYmg{YT7wYeFzG>Z^X~lgAi`dg=-t{xjC)0E98Rx6?-Mnnw>MLjJ zFYprfjo7C0{ecf*Zv=PH!+HMX?i$<{=fBMNQ{)=_qsjZgBzn$Gd$A=~CeyEF&w3d~ z&U(uIU&)^}Kanr`BJz5W|57{tW`8TXu{n@D%rkzBC&78z=GiLA3+ynO)bFP@=LLGW1IEnQkuo_yI7 zcNF^y&b^JnpX1Nm+X47oF2Ht=8Ty! z&%U~(T)f}iNbl|$E~Ed+o^`xH`_z250 z|Ju%CyOq2p^kKgrw$i>a&U0SF{uZPj=jR$3kIdmT`my|9bT7^WbzSc+_OIEqkFOT{ zFxx~in?vUKd;YA^N5pg?KUSeVa6_)7K1qG<_`m0%w(ry=Vk`P5oa)|A>4K zU)I_)^j&byHjlz&diH{x$w!#KAL-dQz9(m$KMgqtYy@YBPp)LFkI^Q7AN3@;CwVjW zVdNumNA{c#uYuIWU$q;+_YiK+pLd15w0V+z4toibYaL)WZJzFCti>kZcEw-9Y%v$Z zXvo^kGo;UqN5(Dou6QimsgKv-U~$RKWqb|TyHyrv%+J~0gDTIspSb0ZsqwC{ifwms zIb)=L-T-sTMV)P`ZRY$;zJcuTkn_B`{wKJ;ep0XVz3_Q4ce6F}99jFhuV#E1v-@z` zeooGs`i%T0TWx#wdP>r0lDCAIRLaVc!7n&VGRQ z*@OC$Z)fX*^Sya6oX$R+ekIJ|YpmaV(|MVGFeE3Q(LVo%(r`mbp z#jFwk0DJP|5qj2t_MMx_P091hpzWFP1o=6(oZ}YJCz8v!mH1T^#d$3Aatho;Z-G0D z&D`a84hPYD#a}9F*iqWGCASxoccuOLQr9-6p93SwZ?dg~J@l8e()DcXJnQB3thayY zIj1BC`jE3mzlEGbM&-KX)bZr+Oma2v!l_}&zYUxvYUwL=W)}Tec)YBp;D5ATM?R5! zGo=5V2`_~Iv1e|&vZqFNb1$g}YhWY3SMUb>d-5O6cLUq&khO9reK$Oro-v+)kB8jD zJKAlpzwPN`U_JPuRJ;S^nM!aJoCFKaZSwd)XszutY^k@&_2kQPYcJ=G)TIud@m1G4 zv|L{}H})QJ z10iEPPs}cmT+aP1B;QE>33tG2-NWwg;Ty<0%363yKfU?uK^ykG(`9U$K+f-}SvQl% ziOX{i#5>cQR~C7HG9Jpd1KX#Nc{>arg#U!hPx{&l>g%sVg=>=^{6 zOorrQ?%@FZ9{$z2DD!gxY)~$~>7A(k9{f+Z@9D51->%S9U!z?!Ih-8*i)}gEV0y+i zeP3ExthX!42l3_XxLkj=#GEZ=9&X5X3;A^zLVpfMk++4D$8lHMXeoQ zvL4yDb6@=+`@nm+IotX2Xn%Tj@;KO$y(j%I&-fAkk?l^L_lH9&^heKl4S;{x>#`-E zGlyH^)UwP$YE`b^9}nQ`hdZ!m&YmRqV~gAmHq}SwY;(4(y`lV%8ne`>yxX1YUOr@N z2_N&nkDrB{5hl>jg4XoR>jQ8$WGwb86*>3fl46}6&z72+nx6G|A$gYPzYQKGp9;x~ zti!EcKhK@}OYW|M-0w=ZWsusk2LBgQcQO`xcC)U?U-LaqPhT5V7VnjlD~g}BjOJ_2 z_A1|-^>(8+SvOfjjq!;%&z)NFr+ePG(z;+j*f+I1 z*uI3_=tn^XUoU(!WS!>h`%k&pzqX_2_r~v}Zz(SQR6_pSm9M)t6Zo>f?Mt3RUWlvo z@e#Qedpq0$=lSZB@8SQ2zX4nYo#>O;Ps3N>?9E59*A;&QY)x;?KCY^GcfOzRR<=HD zX}1kIwfP(Je7>8=Yst4m*62nR#lA6){WwTXpUd}ue@Bdu5c50xa+n7z^w9-pEK)l& z&v~wkVFmj(c$!@7rCn-YD|%mYav(Xlfc_+OqpzbMR%Kr4w;RW6+{;te;d1h;Y*+AS zj8ogcH%~XvAAuM79)q0qo@dWIO(5r-o0_-=K5-Ap>z(!aFn=}jEcR>Nf95{Vd%yY4 z{bn5xf>~@^@^4mI!B~41RHAiH+$B3SM4%py~(*&z8&QoUe3%r@YQDfkpEuo;yVmyUiW9uIe1IHJkx2A zy)SJ~6!$qj`SCaZyl%z$;Zwf0?3371Z<^yeVm>C%hgsTXUfV$xJ@+^jFT?ALTdSYX zT%$z(5ZC7$s?Ev#*RmyNK49;~mi9S!v{>p~%71&QSobU0^L~31&O1!jTpxN{?e8If z4#~f8&Vs|q%gCGktJf5F0oz)>R&4jd7VsO~Qnp{|EAM-+GPeE9H=V3eg8@R74G1#1If3O=*wJhiuT`;d%-HG z4>=1am$DWvrtg3kmy6#c%3A6NZ_cUT55xy~Z@w?F$9u5%3A##P0> zmbq`hehPo~;)lrhvyI338qoqh?{%mROCasHE*16oUDv5ezm?t>J|n*e zvs^QKec5{2o&8=&o1NKi#N~2v22MWT>6)qMBiQfJ--hJl;2H8Ou79+cec(j?)TS}) zSsQN$Y#EE>dCq~4(NjZ5;`Z!2lIvF%?;n%d&UUSD^tt-qK2pb~vA1xaW5i#?HxzH8 zP2NAZC->p+NS=gqrpP(%2=+_$mH+R^KIH!FeeterPl?O=|C;_P?xpV@IJLT;xMRpa zYj+JkhMqb4pj`a?D(Ao-^q;vsoSZQkjWd6nYM*_gF?p4CsTnzQcN5c`EpzY^WQ>y! zdH(+5XNbLyd<|R{ywzj+`3 zQ7+zLat7Mb9KA+Ahwpp7y#spA$EgLW=UJPVl2Zrs4X?T9Su3s$TlT2r{uDTsp8cke zemi>6wA!oPOaf5R4u6vSeZcgrr=R)@CPsCn?k1Q2q@E6{YJ`Dc?ef4p; zd&pd@W^YGcPn(?MI+82-uhQ2{{(Kj#FQx-zo*J_E=iiY$y<}eDC+#l4SF@cAdEU%< z?r}`1nA3a6)wDf}ub23&-L-7l2U0s{lJ{d9K(2!I#ja1!9=|mls@*l^Vo$0|KT7Ri zfj`y0EOvYT_4zYT2ePNnuL4!T@oZpN#clzCQF- z`d`9_gbhc@S5p|AfgDfXG<_?_?#UwyWBj6)l8*6pSG{fKR2a%$=bd;=u5 z56pCp^xXrFpuZ>f1?c7a_p`mhwg`^WHnq17&RKB_{bufxM?aT~cZuZxTYN3>@o<@# zjLT=xkeoI8s5aB!Px1pL>mOf%yWu(7UP>Np?WAV3bFFV4_wzq`awF%8Mr`f)mWbN~ zF4EVf^c(ODzRX!$@(KF-hCTJ=19HwCSxf!c=HhvfXIY4^f`4FX*;*8rXG^_(KtJ8I z{hmEFxHWkgxvqA(*Spvr5z~n6I-GgTy`^nEzHh~R1J{ag#r7QE!8m(L?qw&)*wm!& zC9X|X@tv z@4%0U*;Z_R|Ls@$*KjXip5r`n*7l>%SGfK-ctOnX?3v?D=~v;@ji=dX zkw1fp{9DoI;4An}A~$ort>75`Awt_#{uHrkIoOhFp@JhDq1!MJ>=N$*#*lyG= z&yo4v9lBOJ`#r_p#-ID%jD8zEKU>It_Yir3zHjCmMNh5iLH?LM@4nBmy;wHC zcmm#yzdoD`x#uTgH@JfDF`RkIJ4ILeP1;;YzlnSg&NU8V%Qt}3rG4>-?xQ>VUG(X& zCHZ;w5`GwV*H`x5LF6iJbN$#;KQo5^fj{)|oi;uB=HZ{%{=^mdzolXg?v{8MB6fTB zd-%V_HQ3IrD9(tdkuPS;{+67|{cHr=rERGgzrXPP@U`}T;+^pS#6LrRy=?xC<-7Fn z$o(O;??mluR~7Yj2wVQ0&W%g*oUI;Yub7Ctur(p)xt8g-jlQxEr4}B}mpy$F&fc23 zoi#EW4}g4sNIgjnXohRitKlcg))6^-_^o_X>Bo@U;H;DMn?Cm>_qOkRM}C-`ce~W7 zF;(_-?K3tz*=M%lzqg{;tG493RBX;`U*Q#epF+-9Uy2z?K7l-%Zx}v^{u*TM=4|>7 zURN&my!*uG`7ia%xxZ`47w~272axl-4FABZORP;X^VlZ9z^Wprw;&%UHf!KbF(dE? za5TBPI+J~QFIZhJa&$5I2>pEn8SfF=9s*O@S89_QbsKpJEGJjNt>U_gX#=2T%N%E3H-ZuJs6(Z90&UL3jl{hvZVF5$FMyZHqxsi}PmbQsmObKspS3EBpXoHg*^^69lYgQ3@nQx-A7_H>L8)Qw`9`?km*F4R-x*$HZ<07@ zt)Jv`?r8QxoMLwFG*yQKnaxo`k=?kE(eulbMQ@+gosr1yd z^~h(E_YvPiAFudUwIdz}4cKdlSx3$@{)c=J&iGU(C!b#?kArrQwY?nQg0p|*_X!T8 zzwf#e=o`6K3$}7aF^|cq^C90g^6!%6-Ti%fYGR&u9_-GyExogQ$k`!l{1eFgR$acl z*GA8~XdnKw$Y1hxz{i%o`>o39bL5qrkCGha7yZF1^cV>pt&bye|A zr7n4kisBnp_SNs{J&gN~{F%#d^z}liI16U4*+2pFy4?_Dr1IX@wiIAIWyRd&s}j(3QOzbT1e0Icr?&ZgIot zuRwFY)TrcW`reTK6pW-lOdkb%@Lvtdk$%vw(sR*&fm`7fwm&P1?@3Ea@)yR6$#s&u z*Yj;uF3!Xk;;gAou91B{>nZV{v!BeCH8F$ST$_jBKKjn|)W?Z9xtBfRH*Igl_p#-? zm2bMu=#A)opriQhaK`0#d>*|49z@SPJXrGF_&m1kLl0|T#kS78XaL7qM9{8V@gvd3InF22Q_NN+{Y7-f7ms4Qy4NBnz}^G&0l_Gxz;g2lI{8?lefMpxN0 z$;p*QVy2dhIZyrX1as**AEX{Hz|&lNnz(!u`JSzwb#aP5&fsfH{}`w5<~VC&FuX+X zEapXWZ}`93%+`dQ`kuV+QR)4cZ7g{^@?);knw+uC&o>(I{lb<#>pl9V?DN?gi`_}w zS8zWxVo%Pb=3QPY-a+#2xE(p`X$;>o{0_dkT-3_EyX>OfEc%b^eenTHik~Z9OHZ!c zpv^EC$Jb75ceYmaeEa-KOvZU6{+quUY^42Ys4w<#_JwT2VZD-b6@D1TkoUt|i8&g7 zCqDCfp7_L0*Jo->&M?XU)XAUNvtMV7HioA3KVfrij>kX1cjVh}{<|0-Un;&^9m{?T zeFRL@_Fj5wTIO*r+YU zF}G@)^|c9K&MGHE_L3&_!{H|WC-}F4H{nI|u)TKM@_o-Yk?jZaLGT!T1^qhmXtB**lZBAuq<8)00bimM`eP zk^h1H_^0BA-HK;CmHq_(B*Y<&y1D$?le{bBz2)h0adz*(H(x(5vj3#t)RQmx>br;J@n&LEQH>LVRs~T!b_3Uub(exlh^IO>EZx%WVDm z{=zlrshutHschMU`{3kJ8~B0!H2Qjw=N^F9=_hA^L-7e!_A9oG-(9#7WPPuE%y~)7 zbL`2Z%vTq@318~vcI>msS@-?*leP9FeF44&uY)0w=g+?SuKQ|CKLJL;zVwsWhmm)H z*XT{frq*>Ke}tbjXC3iz#-%6UF|aLL#wG8HHMLJ(-!A5=ilS}|)Al|7th1|d9b8kp z>;uQ~U4-YeWe<7=f1#i3!)+c{YKAZn`Tn&=nn?jZNT(d&n-9gWMzRjOKxsLBTIfD)+2RoO& z-_nmHuf@sr++&`(lAhl&%RR2BD9#dZbHkvAsion}uMNtzMK&< z78&!;@v(eu@m+iy=;v1U-sCD+1sj!$Hf_iq^)~@OB<@D`2f7zIGn>2}Y^&`&T)$k@ zlbj15C11~$96FZ2tLwc%pT?e=lru=`_XG4x;2{3}{|jc)o8fK5 z-9XNoO}@{^XW)AvbtUgMd*kefFW}nnOG)0b-%q{<+Eo_oA>Tds7WX~dNHI@CE532` zgZNsI2f&u}65pA|B-c)@%2~57Iq{$JC+E&(TP$a1YP*(iCBELXUB`bWf1ZDNMNvOr zBA+Kd-^g;dc^b0zHW2rOm^}C0koB}Jy)QZIZM^w?UF?zUhtpTG9}iid%gxb!%Q_F5A$d2UUuD@koH;kFG9W}G*u&J zvwtY=1h&k@Nb>sP@~pFr^LF&q%UW!ki#d<}2syFY&yvp@;;qGhNgm0?*6`@{{# z2SLU;_mbQ`28P3Uw$<U^z&%RGm-DWw^bB%Y%llJ8ot$Ea%rTvt@!VOgFWXWwteAI?H0-3 zZOQp&_buL{RJ?;cL~cy}lD!G}Rk)n~hW0~n=I=6`dra*f<=Q=5Cwugr^uyU(YrB;E z3SUq6lU#qv{ilYdcHawA$oURdN4u=~<4fuT^dvtFlOVNnwCm0mpSk}JTh0|@pc~(z zY`?R$fj^+Dxa_OR(E+faqFDbm$XjUBR?Mr=TH7n}pZdO)uM4>g+lhEfoHOKob$hloa3FnG3G!d!+Ot2wmU;b)d@lKH*qVGEbR*~A>)hM=Jx+XQn80=g-xs*Cc0b^$ zkZ+W=^__R9fB3UkW#6jOrUibRZKRkH_(Pm?$*<(RryYT>h7!D4D$X3acIF`Yo-t3| zUqT*=^E2wk))Vz#U+p6N5^c=p-Lvnj7wyf7n;6iOb5__QBsK%Z%cHR*( zev8<@F3B~%)Wes_gK>TbZ6V+L^gUoTJ+*0XzBc%=vRcQM_k`4!&Dk>6z4%kFj&O|% z_WJl@wol2ask=aGTgKuM@^JoUrQ*F~in#2LYx!^B8^hNa7Ljv}N#tX2*6p&zMV_Xf z4`iDUy2|s4*KuOyuY7j-F{EcezAtH65ga|?0}B?TIAc9P zn-k$L7!X^*r|x(2XtME?$HC=&5UyaVz-E^{z=Q zub}zfS{+s0U$zAl>n7`7fNG-8__QkyDE<728&u!QzL}tCN3W zYolHE_3V{-#?1L;u!FdKE82wos_V_f>%$u*Ig1a#6}XF-mAHfWZ}nHrHB*bHlHbI? z!LE=w%sM*>>Uif|&vjFK$CZkoBRogng8mxh{qza^Gn`f`&P!kOze=Bu55o0a?@m0h zqWG?Kl6$PJ-9qszVMFq1{MlmJfCZ1 zkMDx-agA+oJN{ewQXBI#h=1rgH{IO5_-2;en?ZjM&L9sFcLq88@SZsPV&?TbZL@xN zApgW(ot(3H?!7i{&(_Gj_Tv8(^7EYkv0XvF7Vn6YBkzj2iJlts8C&jaEk0KNH?!qg z`_nTQ=fX_3f!ZC0zj9AmFVC~@iaX)yP)*z1&!6N6#I6wcJXC`X*~f}KR6V#FzbNJ& zNS>$O9R)4LWFE4AHo}9;#oA3xyNbRg`&RgHSdX0dza`{e_*iTI7T39|QqC+LVg3|7UnAy{10T#L1i6#O_5OPEP)B0B?ET-X&uSZ^Elf zT${ZCPVQ&@t|jMs$?@0VPZ(R_z84qIoc(Mx|HW{QHZQ}Ia1niwn0e&$6$-7|Q8*ZnMhC%d8%f-*Iv(IHr4%GGn_L}7N$eV~8Oa7j15jo>L41bC@ z#{a;7w9Pu2CMIj|KFBwj8_UI@e5uIk2gnn}e~o`<`%>Iua_;+i`ZahStYXXhd7Zp3 zPW#MVGo1I3)5=B8rJk%Hw};fWFLA~rIh3(azWrJ%&JdZ8Z{Tuqzu?!)#rMxqVzRGX z$5z2M9mcW^>*jgbzL4vwnFYr7*HMX#kzYR1oS0{vXrTugKNgJK@^{6CWO^SIOT zV{9F@NuM>!a+1Cp=b9OVeT$NM4!4kV{;FPC%*S)C^&j@%#aGAY zLdHD#p7C4fzH^3b&pwg16YHz*TaSNM^hxw(OyGkW&6 zhA@%sX8IcPB7J=%ZVo;L)@RS$?FFx}=R7}voO?;0HqfSPx!BXw<~wo=ocgyT^e6AG zkGw0!{v6xq+GVbfz*(cy$S32kJ!5_H*<$nGGyLzv3)<9$?qWvZ?5mlp>=(z=&v0Mk z$YnT7+Xva6<U*3v(L9+3Bn z-^9=3&v&kT=X{D@hc9`W8kzCfuBynNJLyYcaj7_iHzRLP{@Xo0h98Gfkbm22M%kRf zH<0)D^#2)O{+<84f4uJ*N6@oR9gc_cUrAo5uiEtEp*Pz)_w#bOSOYmvj1==5-)oE2 zTl^D$=5Gq*e)GJWv7Ln1(Z9uMmvwMABo;=K1coyF`@Q&wt)jb^|ZWVn`T!;UZ3hn6k&~rc2$sNd(@h0?h zTrasaO5Bh1M(m%%8T!f^&K%~fpL}fyJJ1KY)`#RH;W&EgVl%P5={Y;h!KpDj!6g0G zg`VW4uJtVL@A@xA$2)0T1M=NzX34xj_NnA#YESmKH|Q_JpPu<;ww%>26PNaTid#X> z`0UI7xwxAA>l@=!J;SB!zp;G)T`J5CP7das{RHw+d)nq6PO2z!sv3C#+W|1p{UzTziO>G@gxp-iezusL;qwlg^)i*d z75S^k`pMb)XZJsjJ$uIs>{(+i=~=ruzum^4=UqSdr_ISQm!5i^XBkr}&Lq43%N_Lj zhS;3<*Mlp?9FFJn=X#6iImaaK4Cuptx@(+=vlq?7A3^ds>ufIDp5p6?{kUw8()W2w zobCCN6VGVdgWiX|p4dwEyTtuQ{(Wij{iiG2;cVx_!(y}V=WpGwAZKkp&p)307S0~M zR9w!NHR#(`m`ivCPG`$A<$FPWa&oppOxE)a`fJOcx>19kvD;QZBVjOGXTGi2Qzv%A z$*W;F*GWDPgbh8zVDVW?t?_^Pdb00B9s>7?e+$=wLrX<%&3i)bWgJ_04BnmZO`Lh! zoSZ#ljJC8s%_qDe<80RFT?fA#a=%|-1ThLDy?HU zn6HxW){0{PeU`pE`9ikKA?y2WW0C%EA{)U!{@6jbZRoioM#(Fq@w_H5mhT6Xa7t=3={}alDWBdA`r{x<1$E+V|(nqb+=?j*)nss^SdEI3FOsAHTW1b%in+ ztIW0BD<|T1{LHWPea8AZ+!t>E=PTD*9i7>9h9y1~~iDf$aQNn#RuiS84N4VoN=%F?(Nn-YI*`LYOA@8Dw7dgq+8B$;~se zf4m2I?>^PV?~6R+0{HLwfX@_f1>ZvEz!tQv+4;V6BW_+Uet*60nX|;RmN%d+Qs-9q z9e&1nkc@4+kd$GL}*OJ>9=ihs6smw&!qT0DuDdt=~u}jo{E!;}~#Ipyp)5m}C zb60Jl%mDdK#4_LJiRG@DeSDOh3*;~6&u4!Ghl~FXU11SyP+j!T)#|u|ox4`<47nR! zjVIC4*DtZtZ#VLDFN&S<$^P6LkD_H=#2+aq>tu8KP`C*)?!&Y%ZFrr%8;+KJs1-Z? z{|L?=_^LI!8$Wli@A*R@_vDGVqK?e-XtTWAQtkX$Udn!?ZMUdG`I~OgQm3gJU3&S_sp5{p>mHx>|V4x;Zw*w%RPE7dmH%&@N+jv z-)Fx4&3~LfnSYTw(+_(?^y8|%58MkmOZ#eH9q*NW@>E&}To>N}X~%o8aY-M^`%ayk zDRY5mze!()vd zIbX^Dh+Uhu1v__^)p%!GzC-oLFYs3>f4TC>N&B*AZ|<2b<; zHF@>eyRo-}rqB&`tS)|Ee}jjp|5kY|adPhwU&>yHb9N7c`^DGe?}3NHUY^;Re~j4H z_%Zok!FBw<`6;^+9^&`&PDikFCv1gB$V=a)pE8~)lQI4TXFlGIZ>%cjd?)R>+CH)g zUP@2D<=&sQRg0FfXeu{zC+Fxo;%7q}Ws*Bj{4Dv$ioGu{`H#bKknvesD(-n*+4pE) z+HxPQiTu2C1AIRJQWzn2I?l6S!?|aszB}Y(?hL2xgKNOC>dL-+lGv~OW%}zk_N{UT z@YjZ%>7PNq8yrw7zK{G`W$waI{wA<#rML&=Uf%=qt>h%}wEb(?A2Q$5mzmpX!*INt zoLSn?u569aZiDpKWU)2GzU5~Rn~(GT;8j|_Z~pgOmGg4)^D>8aqK}e0o0i|k6?RSbr}CQe zZ@`oIPvP8S2g%=ETgE8AnwD|shBLQ%$xHudEj7Z~N9xd8KpTGQ%J-of?DN@u==bAw z<>!8~F}o{H-wzV&iL+-uXpUyhf634Ol0CT}JNNb9<*muySevp}y~3VH%lM`*@5Y4WS)7mLmF?3{BMgMVq8!Zt8fY^vB; zcB4|UZ|yCw7Jp;0UuZY84}*j0IbU**-iUvrSQCEkRWE5%YxT7An`od|nf4aHz4#dR z)9lRa@5M%IS3Px1tun9lL*8#+`UqM_THfPy{&aW?j^U?&AAyYdwQ}~;uPgbfuMXrl z;-fI4)zuB3$=hI)tb!f5opwGp3 zLHGb@Q(c^SGx2KJNWF)9em%HId>E}atq;!ow}ae+n(*_?*C6#}ugtt(2=7!EfA84@ zcjMoV-^c0iY3#%B(fCDLGyLCkU0>#|yFT=%tyM8k#TT)!!yRze|Iwa(BYxi;u8;qu z*TC1%cT)Ca{)TXe@-wQ6--AccpOCW`UKgJ$-hsUqJ_5F5&y@QiJ9pT3)b$L%J->CS z*gIRX>(EExw`jv4d79O|Npz2_(S<` z!XDlAGk;A;znq}Xv?Xn<<6ZZptpn$&tDV?>%GVapzCDGW{U>YhNA=`)+FJNQr zXXkhPV)adCzfF5l`v$QeR6g@KecJ^O6l;aQ<2S%>;0bU%eI))~9T~&H{IkVd;Jx^{ z-|x)NT1>w_M9(+Uc4BQ|31ojS%lWuuo_Y5!xGVo}d^l{b>`eR-d|h4a8+(fPX8#D; z*Pg_g*T>L5D;4Aa6n`Nc4sB^c_W5c0b`AWWYWMT9Iiro8*w4_WL-x6T+L66BV^9Mc zRM~GmXA`-r<;0!j^EF{1ZOkBZCH>G-xxeX6@m{6k{+;!3ly~@6Jo~{1o|W}-mzTF>E2@9703(-_Y}pfxp-* z#Gb=n!A_9hwK=nwX-{|lE$nwn?pZkJ?H%mb-~r=sIsXPZecelPRwR~ne<*z>dl35v zyq|e-1MUlX#wYkD<-f!K@Y`03-`=|`w~{tX+ZN%>!+gK`N!?j@Un*0lx;Sfk(eu0y zAbaR+Wz+5>_}QnvS7r&${LXjT*44%D>c7}E#Fp~MXxBe-zF_}A`<{IYeu)1J>;e;M zzvKLd&)-nD!;kQ{@ci z{9hfr@sIWV%%iNahRUyE=WHIq{uu8kC%;V_sJ9a>XYDF}-s2mwUbM{P&)M0_=j*d; z+4=m7G zQ{}S0I`P|xZNUG-oW7oYvwEHr%RMgNfAX%|g`8Utz(KU^FJIBJjz6g~ z?%Ht=|D2Ng#j>YlPGoK8|8FOEwcH`IeskBz_-#r{zvXP%hyT1fvfr+$OzzUz(=sP7 zgxBTHly{$;DeP((rJmY&7x6>HCg5#ge6_X0zn{G^`&wnP{<3y5?hWwmurK>edFj)> zIAc8)^8W3_YqLM9a)z>BV|RsV&b}7>1K2gi`m<}YGu~-u-uVGImjB2j#XX{C+4JS@ zg7fYf%lYE(Y1eh^(d>WmO#ZF-Rc&cUd$U_{HoZ)11+x;PXRXXp-+SVn z7vo1@HjJt+>=)V3vky>aJGsZf*K#+f-$k!kDb`ok>nHqSIFzD&X?F_cpOeD=^y1XzZURo;9c<*^j+z9u(xBcqg>9>yvJ#@lzj?vX8nch zmF$b~uXyg=O=u1IqhJv`>pACset+Ia%NT7qBu2h_ZdDbEH%W)T&#IE2rupK+?*+Sb!u`g2p3owVhFMexhXOLLN z>NkG+{xi?d-tr`^8_ql$jx*OgK>9xOsaE%5{401hWUbr;B{>7cGv+(+r}G!_Ux4OS z_GFks&)iwS&KYwGdno(@OH0MK|H(LKW%}#{oV#>?I9r*v^7=#i;xIVQGdjrIlif`` z{h4nJ<7h9kC(>5ipO5A*V-Hi`DDjQq2HG)lGCp;!o9oz__XEZ9-Z|s;l|Nm%;6-QQ zXWfgxJ7qqs3)Q9K?rm{bs zPG1Ix(KCma%l}>MJ$BB)R{U1%3iRq$>_cmbm!Y5MTqiyh&SAHgbAU33@LRL@Re$D0 z{s!=;{Lk2f<*j7rU5}SL8PDhEx8B)!jdBq?iQiQ0M){fhSK;^6J6O(UVmsq6=+p7{ za25MyJXTKnWq_Rf**oBo^mny=1>S+SEj+>gQC`-|0o{vl4}b7G($|Bm*R&=xX`^UCz>H|gK(7uh#&#XV^Y zXuHeFo^}sA^Qju&pf7I2AIi(TeHI=P+k@6exmwC>%g^|~B=!{K86)_2ve$-#pqIRD zAot?T_kF61vcqx4KKJCjPwuJfMDyGc$~9x>p3)=bMWlp5M-T8mv>9mC< z=N!xnN!R%;h+3Vw=~e(aTZ6`Nmn zZov((1aj9qxT0U|bE}m(AF}W4O3V7nGoDsG%=;5A;|0W7$L0brC!7JRfG!CYFnD6`SE>y~pox%o5`%el=|u{34Xpa}57VSjDcL z`snZQ&%p!!r`1sQXLY-y{c#;V zce+iD%kCBPL@e|Fb(}Sm{p^)${mbvgf0e&I`!mna+HIgMjp>8nGRW`oU*y~>_N{Ui zobi5v_AqWzvDP5#ZWTZO9C8zNJjJd{&v`yhY&M=v%e+pTbLPIzZsNIP*(+f%j1D{} z`$2p49Kb)syWEdE(!Z^kKkzhrC)&C=|I2zESW>Up0m?qaKT-UA{3q_NT`R?I<`2M| z^eBE8XKmM7QmpTtl*?S7F8|rZ_62#FH#KM@Xg`#S{r)FfV%bB^!>{p=#aRQ-;(Cxd zl6n0X`(iv>?g5ay!%O%HsIIo>itUTHme-Vhvv@ytOKtiYzpUMvqmRk?fj>jeqd4p7 zM)o)OLs%}pH-Dg5fBYpLiQB+6klzdKP8M0>Td9T&%&*3kz{61R7Za{D0`K>C& z9=9654Of(nyLz&wujOa#Z)9(bm)M6|DD!67SwhR&p1@uOzbf-I&Toi2@qHC*Pwoi* z4X``(k+Y~|-mr(__vK`cZouCIXAWilHsQ~L(d?WL+3&JO9>8PD?(XWzKeMnWdldU- zIY&a17+k z`j~dR`VT7=-$Xa0Ph|g&bKkiE?+Y#13+UNPra<2N)9PXkKE{85KMfC4S55i1^B=}9 z!3L0hY!>YycJnG{40{H9C%7K6ZzR8#Hm41*!6ovRXyY2R%+blT&pcx?d81>oxPs+ zXPspnE`_YGYhhRMVR)kY#^HM_)+;^|f0bvbuN6#|_ag32&%Dnbo<4k&){*}J&O7GW z`CYXP_7YEDr=P~@(>yzKwzqbF&CYvgOy1zHqMyV6+;e_a_DTMIIBl6r|B#>8msC4LX_cyC|383!gtA|XrLLRAPhq#l+rn!0$>O)MpJ7i|_F#Djv(xW`*z40) z!)12{{+ak_e)f!y*x7Tw!k@w$uyw^eUs9amxAQmB_N!@EvnN9f zSYMgCFr1z>xk`N>mCQe}>`5CabC=jmcvJjn{1*Q2z9im>-;4hRdj{?%Kfmv?PwXd_ z_1y=T_?^Y_9byGPb8Tqiv>IX?vd6=nFstNjqz%D)^FM_b*lR2)zD0cO+_;Q?n4J1z zSsU~4Db>aIiw^7~JSXQ-XZBS3XE^IPd-~S=oEPb%38muRobNH2Gb3m}%E>%`fqf)2 zl=~`vRs3nOi7=hLn6{qSkihevpxw`Y*BP-c{#ks2-1Kwaxv}`;@>;S-Lds{q>PYX% z&RsIGc5p0YFS!ABhH>SBGe4W_hu`^GGwHLuW5#okx-&mNQD5fuC$wd9wh_Nc-Syaq z(ciH~FB0nunRgf9%+V*=P1T*fbh5lplo>#OfZrdo?>>tM%KI3p=c4pU7OZOEORaEDY-r6WUi)P(!WjBmoZA8&fpG<#PJp1Uu z?2J*?>|Wwm;LUKJoBiV&v7ccweKchMn1avrj0foZz;3jDmEwMSBtC|JDD;BNr{$iX z`qQR0Xa`mm`^0c|#xwhG%5)Upv*I@pjDaU$8q|PJ>Nr6Br&6)^??cOe{$w=x1m1<| z^pyP=a(@|IcBa59?A-S{u}9%o#AmZ##0QC;#Lk|)w%98EMDN##pEP+i=KSKy76X^)@53&hXyoH?+XJ&XPYoWsr> zc}>o*Fxh_5gMSBm9$sE4{uY<{yuIgS4vtgD`{LQZE~GD%mo@%5J@_OJ9f#$<=4*JEFfpWtVnywA?MIznFNNzU<6wE2*=bPqpg(ngT}>k8Mi*U-l4 z?A%Qr=STa4y`{Rkz&q+5328^hb|_q&c-da+U9Z6VzrC`TKU=L&#a7bm!h`ZB@N=Gaz?~t_ z&K>P{_>w)X;vURS{yHUXV*dyk_uL5&)0VmT61fxQ^np9snG0XzsrWeD62B^Ms`Yfg z=RVH9nx5Z>V@kz&v96pw;lJ|>pGv;~|KGoNa3i}#**$>1j`yj8wQwTN zGj@SL`Hk>WoVhj(=l(Ss($)tcb3Jol3T!6lT3Q2moj(uK_A}rFu?unT2@AC4WOj{8 zaqnHq&)nZuJFjHt`5Bj-1DVI0!%nn}dHS`UyqXX#YbSeH6}-(q4RV$}q|D~*td&OP z;vTtJ&eQO`c-|>(zK4IE++F$UzaH$5;3xVJd;x85_>TPmtW&Y?LEimVI2SUZY4mrBK29nQ~vy0LO6u_xhgceOZETCjLj!n}zZV`Qe*<}$?`g-D^c`tAAG*>u z#Dn?!(YJ>$Y5&U0*iM5z;BkKX?Ow>7%3RMJnj$y-o9|r1*n=v?+)desAZ1U1%*U*y z{oxoG25IjOIPJ+Ea1hMrAFG}gxZ)XEM?>k^GjGDv^x1c`_i*OJ8?=S|SNWaUot0e& zAId+kyLLkl{+sl(<=u`i$6aW@iPgjzvyAs`{CmWst zO2ydaUfqzkKYT(@zo*SjA>+1559cesi$4Zm1=)iR#rfT{8BAv%Q7+#5T=qlaKg0I4 ztoKIM|NmS_`OM>u;7|6>FbZnI#By=AAA%=~Us}@ks$#7lFP`-|mzFj22zwt~$MY}2 zTl2FgPsOb&<{xb%_7UuSza7oaJWoGw*26sr&yzC(-vF(hBLi`r=<4|iPN)=PU4uQA z{ZXk{YqOPWz)qWo$v;7TnJ3@y)8^aRb6_m{47pkVZSYuH)>FP6<(u$O`o`>PdD#OS zX~Ui38T0(FKP_u+JpNAJXZT8d9{#XY%)MQD6yGd5sw?&7Jh(=gd#aR^vxs&Ctr0E# z+JWC6Hy6u#xI^q==mwb=H6Z`~=u4inCG=O%4sz$yZ<0SAKGDwf+dO<7eYKpk~W#-I$$VmG%yLiOo}GSJfv*+c5jVsi!zSz+ zV)@;gIh6Tx9%R0rN54wWHvDIC=HC_S>%)JLy|XeK$UB|A6|ENg&?Uv4J^R=v{HN&o z`|ZQHTHl<7Yr{B5Uw;S>LhQ8pALyuT3uR__R;my;AHEd0+DLJNYrZhxh?F`(5VoAMA|(lk{2mar!Epwb+2Y z(7egmPJ>bM)4!Q_gJ2AQiuY{Gev^I>42GOBY1e-2SJ@e}eee}nNw z55Pm{Z{V?V_7m&HzaQ7uKfAFrUKc>_-3>kWS-F$Vopr^sUoOGFLdM}1$ZvrBJtytk zi~gYeOWEVtW0hM&{3QPM{25{++1=>F)IE?P2|tx&I1(Be>qPE~fulZ7+hx>_6z2;gxtEWPTpS z-x$V1z76Et*lXqDZu~lJ2K;v%2)8m)Gv7~5L>KTAb_JdX3voVE}GHv7voTi z-cH~5RmXLYd!MS}9+dm}?_$;b;rI>lLCWurw^IIkTu&X*ZV}Jjun}zpdtcmAY#KXf zPTKk;?E!upWp8J1MC+++L)r{>)?oIFG58yLcn$tVupex_SpR5C?m3AMQE$e2l<~WT zem(n9vCQS{4>!Ye@G9I(+Ztz1W=(9vu8sG=tCd*;FHv8WdOl)zhF#0WxBKiLTk(g= zn}v6P{GR!i|Bia|`#W>{j~|xS@W-F+{C_LxtG&Ux;-4s7A=TrEJo-ug~CWtMm zD)x(v-BNbe{~_#k)qM%RUVi%UR`%3q6n}5MLeAB+>?`dn#r+^_`9$??C+|;o9o$>|0el)LRkc5c8PSSh}<{U~=R{Y~0b^*jW_;85`iv?=C!+OY*K@1DN84!&S#{C2_X zE8l~?r*}Q6WFP0Bj3+|IVG;gSUgq?7ctiY6b@9yXwf~WSFwUBLO#FWKB>XuZ3C-EH zp#-D&Y1?$@%jHX9Fxw%St%GS4@l z=lwRqzwndSpTCN|)V^{i&e-**-&$R)&358nv44S#!w=$5;$hIYV%@-K_L{h(Sbn4I ziSOj+EF1@!+vDYAZoem%zRoi*q-8$*_kC~|xDJ-fZ>Hbg!|ltSN6R<1o0ZAfW0W%(`BZEh`3uzd ztege#47^4=NN)DQec4|^OLjXv7B3V_-!^79XZN9}kI!Rg?dGgHo}cgGHSmRUYk8Nx z;;lS8V?IMp-eWFKJsFLHKal;8GV|HH(Kb`x zLiS?zQ_2p<>&nU9bvRCaBgA{M?`KaI|BIi!B75aj`V)8wo(CIsGpFTk%YFyX@~r#B z?}m+`rg&dGwp^U=*I6Fnbz3dq631oVAwU_PHaj#cx30M4S71-)eTwpsdxb zzuWn3*mu#k!}r2*w6FLX+k;^z_QA9cFq3^I?Ol2KTj}ZS2JEcY9mG>^hJLu5pY~^* z9@CC|d%RI>e>t_3+k-xf{i5=%#nONCyw9`B<~w1FM~m}5_l$q!j8(RdoEu?brTDJW z9oK z4N=bxIPE_hKY}wR&y|ZaG5bgK+zHD#`}zXNo|pNQvpM~lXHUj?-ka)JM(@JkRJ&Ty zy0K5jC&I4m&GBY(4uuWb_24&lz6+ki&RC7|%v!iauU#qT%Ps7#>`!4(b+PtO#`%7* zw|9GzowlBaTj1;+SvxauU7UV<77r6o|4oDH#l`w=TrP6!({7fZ-*ioBdABy|%5S}O z*r{_(d>7m*Hy8(>@TbBgn8P2h-rDRT6?0o%Sv&i}t^BoMnAjKm3GDQ1_T_eX6)b0` zPpa5?uO4^{IH+XqDAP^sQFb*u`{@7}LTe=FE&M%xA5Qk%7vyDZ7U2EFj%5#FXWf1y zehuu&?p9swk6A09!QFDxzGK+)*xPn5>a0V{J!lPn{+={X{4joV_TH7^K6Ns_ftG!0 zs{FOsdC&CU7dZW!_HL@onYc+0>xO+heLCd*bB5)vG={%ET+JRR=T!Dxkoq$=HQArR zK=$4E0z6sUR^VRhO@788zmYb?W7NAX&fPw9t%0256KC&^pTq~?BR%gG_70GKeH!lp zX?JU7`iQrs=UmHJjbP7acf>vTk5`$;?DXe+d^s)c%sbCAUt6l90c{sr)?i0=+PVnW z#XXh#mc0t+8}uOd&yeSRU0wL|zQDQL z%oY0-PF8*c@&C{==C?pU&&-@iTZX|0?16YVbS@R^cq9BBKX;R@NH^H~!cOY{k`$+1}y?0~$I{#JmJb<&_&zG|=epR^z3^yROk zj{I|gkI319pLv(xV;}L)U~i7UmHT(uUI7hZJzC~S-luC~^6Sez5UyfxA!i%dRlP06 zvxnE@@5f$oym32vlci`zo@7=Cv-nMYX*i?#hr{-5l(N@CE4 z-OBrwaUIX>UAFG{>+&bV{ZN$MfFLL%!9G!7r;XIddTUP}-ce{GsP(?^%E|N0*2-!+S#dCworD{d(oQ$gc~d z`NQE?c4vHK*|@PMv0s6spgnDiQt|sRTE-^p@G(3}?kn&i|9tpU-nn>9$X(%e{%rOV zJh5D~XJ7uNVw>>SV&5kADnDa#uy;;9S>xNnH)5UmnU_!SXTn4L>!73lcujm5?K{Z( zJ-|N$W{9`t4}^R_&fbx^+X!d<48jN4FRE!@$$1Tbj$fdsKL)^+koN6K%YN5b8?sKH zgm>s)K-xOIY`>867oG&8wd-!<_8+kU^o``#)P~k#8Mp1}??MB5_NPubeV%*T2K20r zLHIIQNc+)v_F{KX@8RX*em(=Y6uS*S1tn!h%gdf|XGMD<^|j;Q2rIm6%JlXQS$~;3 z*H?=3Y%zWhhO6TWWpe-6zFe&1;p%L}&U^Qxr;qdd>J0wZax-UU;F|ay+63>pBRgYX zNA9W6klzh1hsFHG>g*%FOu3BJKzIT^py&S5T^;YpYhGQPWh<0>IOX_5*>%}_b}!C_ zGJ657AAZ(5PFF|#r`3~wIoLQ<_}TwjJ}kAGy8S?ZpnR& z-B7=-V80-Kn))}#ZzT^ufRBQ~?16Hhl(!i>XLsge^y_e*|Bcwa{D1J)>MY@WPalJu z)AJqRBYD|Jn(}K^7vEso(^jw#!@cNdD6<9oMOxOyeUSa)1^#>DdGBxWF))d?lHXiT z`ZIUoz451T6YL}Y7%o8vTs01xCOTmEvA_9y|42ig(A)c<#e+Aioj+{;J|UU&fwJ&)k@TThs57lkr-cowYrY zzAJ7GS>KyLmvZs_I&-LvzRkW@f|P3tZGZl$=F=8kl( zI&w!&A7)P+3mJp)V!1oyIZfnG^6t0GYb9?eza`#F&PI5VoGoD~91AIPo1ElTiKqMs zT1}ks-w@vbdFBQ5>@E3Q{B&Fg7MF^9`rX}&e}?3KdD$;UiS5?YzJ)vS%XqTf_tmkf z^40u4?ABs4w0AW>bNE9!nGdKesS$+o|?Rok4P3!Sft~NjM<~V0V zZ}l82b|md1cGk|>?DNEbud;Ws^BejQdL#Ky;F)6M@i~w?rrpV&2tUZ{ zsO`J5)6V_G?!eQ_#XRmWcAHou7$f!teTaDW-s@=>@psq0wzxKIAohd2J;dfi+F0co zFTf7sC(~BptkKMiarha0nld^2vp?46r@p(Cod9Xm^-!U`g*U{P!<%APSBif&?=^nT z^riegwPzlC3wVj2F&==|-I7%g6d{XTsN zJI~BL;5F!`?jihLa20*~W>E8%|1Wlfw#--$m0zN9@udlg11GexWfIS(#_t=JpV*JM8cnX6~<*P(age+s9w zgAw%0)VF}vlK(FIR$AKMxLllNqxgUEe^yU#TJnD7cjZqL8_XYU-erzwJ=Bnsd(lm_ z&GEZ(o6yb?|BBt7mOFFiXV%O$?1yRT)8mzyq252mKA>gJ{K(#k)`z`7&Mf|pxQags zMszRg-j`m1|GuOAYt(U!}dR4%>)u7%TonQN`pliyewk6QFS@Gm9%wE1+F zHVt5JOv~@9e)4i2U)-&@*9=i_TYhucQO-xoPnCB=srZJuH$VT4^WDWVmk*P-DQzWQ z#LqiCp^hHPq+kBUJ@F*5%- z8(~vOKdgnv<2iD-_+NX^q1A@{y~F3qo+{ql^Y+2H{~Swih_AsPiw}_70O$R`$M^78 z<9A?t+GPA@b@9yH6SB58P~RZ-UhKi_4ROBr)}&?rWlkRt8UL)8_R2pCxewMhpR)e5 z&TkZ}%U%Z`1M}Ir7yPVWJMoW~e>xrmnR7RK)+;cXT@&9c{vaG^UpZX4#@cX{+)uw?o4~^JYs(S%Dce(-mm+@)*{h?i{n6nSb=_i)E*BWpSy`AT- zK_3atXc?cgV11aS&4c6|N#79v0qNiSAZ>n5&Sf}zNJD%HKVx&Qcn7>M?J4y);cv&j zjFvO7M#{6(S3Q+oz-|niL+0E^v`^q>ZF&i3zu6B?R99CynFm>4oO_gc z4wmxM?tN*OvoDu>5kGakZ=Fx`tPA-0o96knr{!b}-cUC4JbTM%?fnPpvriJg4KESj z1ZRKAe*JVso7uhT_t7(k{c)bXKRxUDMzP#qEBy59l`!vr^_(j=``%RAD%yYK-_D*8 z-c`Se4S~C8ndi6C-@<9bay(a=UGN3a5zdvDIvPU$nXcXCwSyD9N5*|GSS_!iSXZ32 zF+%QbmEx?rlYhN6*@Tw${{sIkJQt=y-s>OQ#-5wMQ*~G0Q}h$zVmMIjH24OxHnLCk zXCKM#TPo(xH8^*&+(EJi^P6TDcKSEtFhYK3d>76g@IS$ zCr`$C|9fap!J3f0HS2dbdfNU2{~>%)sp!ki#n#~WhZ(df{Oe$F*>5gd*5?n9y(x93 zzgn{Q##sZGvgeeFyYza`Wgj^+WF8 zZN!$-v-XFGUB=#*o^`P{&Ug)zpS5}l)L~~1W^A_xgBK$b1=uPv!52_cy*d zv)19KuQFDnAm?@7H+#wjCEpn2r2c2R7yI^2Vqd~2`Xrbxb};`oxksC;jq&F8qRh#g z#I{w>oA_8cS(mlN^X_NTKT`fMd>3R*JSWyRKF*z@F8d_*KkUq{Cj6{}Ephg~wP}O+ zTeF|UD;5{`!JPBmE5*HIh4=iH-<$sep5~d^izk-cxp7~7ALMLUE!L*&o!EJ|+U$(^ zW8%BW%{C~ZR7S;)@V^=7A!YC!7Gxa{sdveutd zZcUu^xkx=-mB|?AeP_^K;BUdsckt2ja@Xhq*Nf$z*+D%0bsB#hw1Drbtu^5k^Lz9C!D=-P5iKWj>M_|FR@QyU)m|+zdA#=po!N77>Yh?5zA-moKTn$h>(VCSmhg`E+>O6asklFGPFqWE z-n|6_OVt!Q__GyHQs z>wESk$~A?5#2@6Ru4{1meF=M-+&AD*e$J7f*z3_V5BfmPnxSIbv(xV%D!UGDqb*C= zuNdbYcz5PSqI z5&xN9mpzjGxcAA~Fb3a@yV5e}vu0Wc*{G5|N@GphB`fNA; zp2}~_p8$uk+rxMCobMaKI`CxKS?5{T($gmy$F1@1cz5ya-JMFs-6r#RDLebv6v%#? zI_tCZ&&;Hb$@06v_3Y2+o3R(Ge?NJ-3pR(_=!fHWut;9!O~!0L{$B8~_#^xo>{H-k z_E_8r5B1)C*zI9h#T`NJF!f)8*W_n?+#;_Ze<^;CcBJ?}{8jwCe;e9rep9*Evxmb? z{OpVU#q;f;KY#y#b}#KmI3c-WbMcQjecl=>uq*8+$ocUf{)6S>4)83yhTI--E_*xi zthq5zl2c93{$Ee*p{n8x>dRjXUXXjGoQzciIF&tJ-d*fo?Bvwe?;qfdOX|#?HAl`l zIDKD-mU*6M=D(Ym=j3^_JZt;1dimM2GtMu=a`k*kYf{ml{EXKXw9d4ywB`Jc&;L`5EtF?EbI_-lFY8TVMVP=%${-=$lrw zo8OFm7QT|7b@45GU-r8=d&v6Yxq}?f{+0H0b#VvEnVNkn=U53Zhk9~n@wb)#5ql$A z_KNiD=hjX7an0_L6Y_tBEj;6FdfNFpf1gV6TQ&6@$RC26 zi1(q-fi2)YdSm)S>_f}cm3?L=G_CyqzjfrDGhTV8_1NjJjLUd_)@t^;%-h>wMzy;I zzSKB0!#lwmw0GrSQ!dV>8p#x`_TSk|68&Ti)F9b6LNpL z2VM~GgI|H2#cvfmfVQzRuX%ni`ho1zXzj!|;b)G2-o4m2USmJN9;jS*cGhRce0P2w ze%8}1a1i}j`iVGeYb~*yvH8t#K7N;93uiCs0*mBj58hST4)C4WRycd(%k%>(;;N0eS)febC;hwaQZHw_k9`rZ+d-skBS`@mirfE9;~OHU2qwWk{ccl4~YNH->9s=l+XW{QA>VJ zv5aqT`Y`_Y-L;SZ1AAZEvTFAgc3pWpv;Tqj`I$Gz$^C-gRcu@S0QyM&Kz`okY{=Y5 z|76@Vcc=0@iSNLE9ggL%3)xq$<_}|ECEgHnJ}mN#4ROXH^X++fQf@o<&^q)7_=#n$ z{|Vir@mI_Fjh($=GdZob;dok}{WNY!uSFZH%%1GqpoiFD%HAM;D1SD)O8j>I)?&3G zd&3d_m_Jty(@I1WgmVFKBTvzom4Kq$^Xpm4wJ+_ z#iOdtb7;>0LVX$ItcAOA)^X0wqt#!(T%7ZN@Lv|ooX=d&n!2Oq$-ECnk+~+s--=$)%Z7ugw+GFPa`n1foAFb(}SAWoNf%%^CruwpO zX32RL*TkEO?Z>{EoqfDl+1Zaf;S=OPicb_9M|)fDAKoX=Z!Wf`*bejt_$}Ht_&B+F z$B}Tq*aCP$tQll&We$F+>^%Mh{002%e79_VS_qj-oAdX> zcS82yefURJiu*&(ga5Gm)3WbAsqarx*SFQh{j;Zd8M0=pyz}j1r|5&%`Qww1(~j_= zFtJqZcOT*B`J3T`#m~X_L$69Pc7L)TtF|T}d(_D|b>tqBd79tZ8{u6_#eR}KG-Eo1 zKU&@%+AxJyDi`x2&&q!XU}N#D-|TPivS%o>3;P-Nx%BkaLggy>X#S^={p&i&-}fH# z+%1&Jxsq7^+qpAJ){ioIUiRaUmdMplnXfnVPf_+X_*Y&NWiwy0mJa7XB&Rp+1o2~N zQ*iqJH?hX}eX(Wi&DdK*_TL+z13P=cGx%EE1iw$uK9%{MewhYedFQO z&f_`qN71g6*9qr+avo>y*WhmnStEJ3>;voI%=w++Dew3~Nq?4$-<%_9ng5r|3kKYi>yt-9h%3V%N-`1k#8F%qNrRV=|;e7sq{06v(_%e7$ ztTSZHzE<~5_!D{yJd=J4z71zi{A909-zRS@znZ?ZyZZRs&~Jy`=(z`-r(9ot)$d1@3KDqMbAiIl;BbRKTxM^Y`otT zv1#mU#WJ@BD3i7PB+fcMSngx^TKpa!BDWvB!Owdh#@<=}iTv8^iL|r$TjD&k8ND0y z5<3%TKhAq?Cw>jT1MJFgL4S$%t!M1Uev@67o;z{Y)G_Sy;4S_LoH_lDoW=Nc=v^uH ztn5qE;SpNOYzlj@+lqgIhl}-wv}Z@!YJG4gdoBCV(fq9OtnbmD^QpGH&VG@W``LB> z!w3BAW#1aB``BHfp;&LcA-+v4dqLWn{pTTeZT9YBP4H~~0(QkSz7oF*hC}9ZGx2@d z*RXq47yE90OCQc&q>bt4e3zPzho~cSw}p7>xqv-N`58ES*6+BbSbwoKlkwm4&vdHke02jO$z zLt38MhdrTO%+1WuHK#nawn+1D!9PW)E( zD{{WV$B3=L{v3CP7uE4HeT0CoUviRnZ z_d5(GRl5^7i@ugKSWW|&O`pq8o9c;gq`uAQkJHBRuh-7Zh5vg_Blq|SIW!IxOfi@zKv??C!v^mp;i@HR}XvTuuLT^`5(+jH`buM5A0vi0$1^rvy!HkI}pe_y-+ zA4z*h`FiSq37<{h0lzGkHIw=9ikvg(nSZ%6rrzt>d*Y|WYRlW1e=&P7UJtgRt zm=D>DGEaNZcYzKtf<6Fhu=fz}ieJS?Lz}XDuD1LoK1Um~KTe~a!(R_F*30o_a?;0R z*%{aMS)7$1gFj4(B6kJ@CrZkU?;J5_*>fF-HQ2|b0+VW{>&Vi z4{hbuRAxhYE$P{J-j$z!zwcJ@y5jZ3hR`SSYs+a0spo5Y+L1aI@eiTDhqE@*2f2%6 zUS~e_lCzn=2`?=bXXO#fWgJG~p7@rJF$PH9ga7~xf}iAOlXYX;5U)?mNu>> zz9aiPb+3y@!fuc~XAOB5SDgQ17b|xa|1f&?tAmu?SS<5D`{kzaJA0XU54e*5xH3!F z!<65wT>Q@KNI#XGbKz3<-nb*2#O{XYs-rQ!Lz(-PJq<@Y6Xvn&iDeD`F1DpIxz}6; z6WP7&%eS(l9Sz&lhT&22s~}_aA!NShH(lmgm3p=in*(?AN7E;j^&>rV{yX}Wcx|!F z-HdKyr}Hzm7pNoSm2;;Fe=m0WW-U1x zzkO-v;7i3fVBgLjfj{A=o(|DzT77=Li?+g(`Rl=8NZYgLbd#4oZnuj0L7PKc3$BK?kaOq+IX&6wtM2T~g~Op1 z{{}p)x>(1bRu#V^50g7corluDa0Wlf&;9ZkJP_(Y_MF-k>zlp}KmXsMOK}TYZ~=R) zoayksddBiUR#)ErdKe&QFP!~qBX~zW3*}@UyvBbQo`>uB57YBKBYpEKZEw7oJ`Q(+ zxsd(rdD{50b%3+p&%k-d%-gH0-NWQ;#?Jd*Pyby!^C)Yjx3xBicDvZG{3h&$(5_tE z4ep?2US*A)&fY`p8~z9UW^%^R+R)zhjHBr_*u81H!ExfNY0+ELvp08;mvy@l|2}^6 z|J9qZ+Y_?)j;Ck;UM(+o>xbB%$?J_9({`xXpXA&{`-h+Ycn)WuSV*tQe}td+e^yS~ zur>b=7{|_9TVKvC;+NpVaR>N;z7b^Z=AT&@sqAh1$<@X8v6gav;paTqncWF);_ol_ z3HBWJhwNrJcZs@qBAh06FPuTkJEY$yn>U#+o!ITGi+%P;_A=;5?}c~4nIpU7t;~QeB2|&zVeE^hP16c|2^~VF?Lm@xTnmfH4?u9ni%g##ilB|ia$rre0-Fg z!L+n#eerqxmGrUvI`p*p8E7kZQ&sVsw<&{70Qb z#nxs&s*Zu;*RV4W=7@c+t}bQo&rg2FEc<-c@+|SpyMeU)FZ=B#a5?=5@m1{1+w2pY z;K%V7o-s#%Rm5832l35f+lk#;UF^HL|4(6mubv0#X-EEc{xF=nAZ<^79R^vKcfdb#Ta}CbE6>jwsiqH>`#$~< z>eKSuA?rNjmp1-Qe^Aa;v8$EK7-gSoMVl!%>-k_>S6cdLE&K>fr|$ zXt4+J8pb&5tp!YwbDo@r^da(2fwh+?Prrbbz2v;WzMEYeHmfT7|47>Y^19Hjhs=>J@EZ?3n(d# zZ@dRBZAd?VE$1orj{*B_<1mJPGkcD5XVWiIE_Z`B*^BX7;?J`$v9|W%FN48y=F%6y z(f^O8JAv17>fQ&QqEaM7ib8`1nv~3)^NJ-ielXmWjX{qWCbZ%x~ky%yK!=ic(&;mPGnzY&&_W$?OXmqntAvYM0OB z?eO3H581oQ$=cbqLLENjzVRZlYvp{wP9L!|Pba`oZJI;&#(wa=+%H}GRCd;G4SrYo z+wx~X{(m_OY4d55_}9VC?DO=!SU;(~?0qvUeLlgj;zwZ^JNtXy_fFwI2-*9OgYGaA zvPMqU#|WIg^>g{DsXz2Rk(PC}8$V-wAT;6U92zI~Iqrtf!?W?1xCD8g>)}}K&ZO^! zui$Tuvo@DO)=k#yb+8wI4!pu{4XfPGJmZ+P-btIsjLi??IS-ScT7HOq2`$%s6<-ZM zi#5hO_h1rZ$43H~nZJkvt{Abd0aUSA#L4Teo1ds-veAY4w@{xa z;frbQ#rxsxAx$B3elo1%I*0J{%(+(n7SBZb{*XDycxOM(+%?lj&ZxHZj<6g5Zrn*M z&z$$Y*YV@9F??7q_W1w%-6wv==1;gxyG_Mv^*d;l~;0~}5F0Tf;Id^wu=d;GGa@x>R7wPAuisCz;OU0jKe@vSqr?WQMbFSpq zW4D4juo#Z6wr|n{H$H~E>{!D8pZP{??7hfmqv;s5U&2Uj|eT<-!}YWo|U zwU#=__?*kn@47$Sy?AfVTB{}Y9Idtf((VMj7G|@%R2F;K;qGIlysP*}@;aRr>jami}(gZYphS{<=8t zM?28pQnSY-AbMS5OSf!f4U&)^&p0PSr z{uS(DxDT97%l)1NgXnj|FJag3TQ2G(bMcaQj6?Z#;Ds=4n^N)pKuh-3kh#kKxdXjc zW${jbGwlO*_KoT6AK1xn$$!cRMIVJz>zSLQ=o_==;9VeV z@H5;IhKlF&?GZS8ao!7)`<|S;`KPhp$C=-Z>swXDyG720_1y2iVmX6Gn5Q+6J#Cm+ zZ+d<9B`}o#nYORdR^o?YKX&}Qr?lbslXpHoP;S=YG4y(_wYh7Y%Rc~Kfv@O)%Ig5B z^O-Q1{jl8a*?G3l;RyDB;0JcMk~0Gj#+&G4JpELhb1Tn!06*7F&E3b|5NBMjV5jZB zmBo8wYHkiY`Uarg?_X7D%r9%vrcMjuy;KjxlZbdSAh|F9cX7w=#>LoQF2U@p3P^kD|Y~HC3h~nmYfd!3b>x1XX=NW&~q;49lXBWNBBQN=4G0k z4*Gi!&XJRPKa0K>{SA1H{s&x1+s*jpz6Zz~B6g!bHpOq@oNbdK&whpc-|*IAsgIlo zsqvO&YX;v8wZ)F2?+CN_kK)uvZCF+EnShpiZ_gf3_Ri$m={M&{eg2kk8GlFd%h@Ma z7JJ>QisJqIaCz5?pC(>cJZDtu?E;v@&$?KbHWk+4e+w7VQk(1IJa0X`zdmlqsf$oX=bTGTFWR%Hp^Wnp z^vvfj>?`2_ZMLDM#&hpq($?W;9&5lvdd{p)73v!=5o;uG1Z2&&7N3p}f{a6R@%z|W z2dSAA;!SBS`Pnz#;rGCqzxC*U$W1?o!!pQzb5z+LinH(3p*o}Dx7OgY(qHWSa!7*c6z#A;VM|9&(y+m`tKm`e*ClN zTp*UUzM#_BLFV=;I7!Yi^hapJ;j@b3Ge>{CEB`P#JLqp3&i=nE{zd*ocJ}zM`1e#6 z`@&#tR`YL>-$u@j?)5scE#%#eH-`)9UmBO~TPDJ*r)R|!;Du8{vg<2d}7)9g1hLMqx9F<^?%{lVqXI7uoKb_k>0G9$MB>&Zn%Atbx>2_P{#04eeYt zu(!Ovw3+VXGxi$XL;MB(4Z(T#>*@V*4{aL2VEWBC_mh3+PVpP$@4-&JeZYUWs`xDM zJx(8a7f9RGXKJ`YUJLs1@De;p8;Mf~lVJ$|bI83;(k6TTum|;pFQ-j~BiMQOxVo%= zNZ*IhZe!PFZ-{roC*nN+I&%N6v<7JZ#p}{0(sJKf>%Y;OLC%uQd(M`8cd_p3;LYE^PmPKqw+sIzwbDbpn=?LRl(A|h{w9oL=YFcn#on6t z|IgTGz|HLJsjs_!-l?X^e?WeI9x+S2mUboae*7=s6ZbWnpFQ#e{;uNNL;l`$<}+*J zc*ryTAZH2u#qY$g#Hstvv~wYQ+b`wfTpY(vKds?yIb&fUZC;f<*>(EJ8za_|{uKO9 zJ5qcCJNY|^<-6vO%SFv!j?xV-Y2xr^_g0Hk-d@FPujJ@uk%-kWxaN0 zcgLsD-xKeMub~}VZ9kNAnOGh6x$LsoRQ7gw3O&!-hg}zy=U&oxTyp03dmkJ4s__ZwS_m>*&DtTdsn-?*}KDT)#j0wn(2gJhTPLg=*7+$ zo&oJ3dw9lYSjp#MA%vmhph55^at3-$vNA7 z|Bf4#ias8#QfqSZ?@*r7uDRGJwDX(`7t0yNPaVB0HW%*&`^#SjWAwQ>)D+7zRpXmr zl6F_Z2zFI+ajud5A!9QH{?;x(Td#Djr|GHvoZC6`8nV-VBW;0`I*Q3 zJmflBC)%}TtKYO{atgS zYgLqsv22g?c`a=+=1+(kn%jcda*LC05XZ!}*Ud29){T=ZjbRZS1s66+4I@g z%bPFfJ;;5HR-ZeG=ee5j^PSAG@@}WK;-^+Kw>5DaoN=3tm&obP&U0q2Tk{{q|96&( zk7xe~S>qdsJ>q)D%b(+{Jdq!LSh+aQ7UOyRL2`1ojgq$vXD>^=p9+tO--6f3&3upH zZ_E#dm()6J$=+7%bFud9jQ94@pac7>Qt{d2LjFWKiCsjm!_ISU4yV%|pry^j^s8V) z{+I4&BF=ZRn~HUmdoUbB%jb;D>o>3zvWI6}viFtP3*ck+b2#;RJ3HfZsCL=Ea=q-K zchhFUM^(N@=0DEPUL0+qyzcA<(3k%pq_4;1E}#vEoz3Mi+)iv)oV~19xi~lbmx|9h zgZb}hvmXA?HExiTamw81oc)@8j@;}S=fZU8z#k{Kww%=99R3J;?jiO2YPtA**cI&T zXOHkxFZm99Ab+N|^WaSSNA&b}1U=8wlf6nTpJhK0+nK)@pC~uuv|LV}BXzurmiLEM z{9L!pe;u-J&KLiSHVW6n`FuI1RGb@o;`inBaX)wBZ^e_F_0d#*V_1*hpWhLu@4vJ^ zi9Qh~(Z8cjV*dxHefHKb;NEiaIdWt9N0*9w+6Z4w-wCn@uEiOn1GP_^FW8y0>+x;c zO{U!nOXW_(qafqEaaBiWf75dg>_V?H;=pWC`J4-)wzx~+D=@&w3@dL=;{j4QF>$yIDxLoYN zb8#nH@|xjq<)!b@^!)C_6xvo|YoJ!{L)*-8;$_m2&1( z7uP(RKT~`^sD?-3HCpz}{LaU(mBl+luJeF)*=zq5uM0VwI=~<~_rg}RyX0jY^WS{; zbuNDhq!yQouj{_DHd3ck@Da56xVCt2TF#9|IDKbcJBv-CJ;weR--NU8CO_|bm*B_o z;qtZ>&+pNs2Ctxxf^qCtv@OI>cK-+CA8A>$>z3>r?3M7m``V75{q8cchP2k&ZBSXf zvurB1h?aFS9p{?81zw?QnJ zAzaUYWzXWh_$vB8tJQ>SpDK4t@eT20{3Vp+rr!3#dF~_G2e309`|-chZtqgzXPh1t z%Qf?klf82#KR^G^Ge4ruk@BCV?_MgtyRV`*rKkQ=d)?`qn5*~c4cL#-9*67LE#Vrt z2NK&_U$yZB@fJ8^|3GDN#^il|D|nCpD19vZRGhu2C(gTQJ?(F;DBhPJ=3faHLEde* zDHrdsGjK~dMEkB#i=E%O&Ap_)#=`;72u>2ud(Y8udZn5*#&5dDT6S`WLTl}^uV#H_ z-_Cwjk3XD$2DDScd`;_IF1~*|%zfvqTu*Eb?O%4*#esOb+>HCD z^g8mI@o!|0a^C~^3)tI;p9ERY&xmJk?&N2UZw#-?dw@2C_9RaGrSc}yAEeEflYKUO z!x1=RJBya}lzri9T54o6yD1)8Rjh}*+4r+I(e5K>%Ax%0iIMG@6!9we^&Pc=r7Rn z-jMNrx}unm%J^rGBoFzX47?9|=n!>oVK=v~Er}D=` z_SXIQsr&69*ZRsco=ATk{t0i_G87ymXAO1LK!k$h) zU0%+tdHhN2BiJX3w`S+PbU8cc%vJo>DXxUKx0Ym>$HVQ7*)U%upF{~_o!4CuHBHGJwMmG z5;w#f%DIM}J^4WXZ|qU>#=&a#ba=L+_}#f{6-&)$f80mx4)%Rj#k_pQ&e_?QzLR(-sL9Vd&b8zg#+HJ#b z4tZxey4rKnH={p=*P&%DFNN1=Yaq`vRsS>CId@Wjr@+CGJ@7OAzg~8IShr+9!MDoG zbyDNq+~3D?_R;@$_;g6E&Bcdm|EB!$`hSPthCUv4s8R!B-)Nur(gDy0`j?8jx=)*Y zCc0VPa&~KZxzC?*&Ymamuka&eedW8bJ?M||H?8OyqT?`>IdZ_^gxXJ9k^Wl!6Vb__dpQV(`16?HyEo2BBT#Fx_YIr4mb z80|=0g)=_M|6T5>?D}#qt9IY)H>{yN^P|vM?pE}yvFv+wv{{GUMSjkYk6?ZJMR)~H zy=I>~iM>GX1oj|YiIe|0Ofm;~@7kfV*bi13lU4MbH>c94(=u{vRfH;hp)p_smt++pFxg^3SJ_=4ZTezw^Y??hCv-{|a{I z?_uavUF?Mo=+A1Kvw4lYh4j=?*2r)4?6o_{pHJJEJrBQUZ1RrqEW4N3bK0Gc$Kbvd z#rx?o?Cc$Rm(4Th_k0@Ttc}icvt~Dj=JKxO4;I@8vX9?Se^I;4=OX@Y{5oQ%^D{qB zi>;4yc8q|Rp{@8BHPj!!DsPVKJu0UXGQJ1WC(=Hb`x#7y)YK;WO6_KUNY3H%Kasyb z9u5oTb)ol$`H+3H7QS6h?qP^{YHmD#G5aa}UpfE59qBvD{hFS&u?4>-{~`YO{Bt4K z&zY1yb57=(=_k+A2vW<((X!8_o-==0L-*nvA)n!M&V7q>kLf#g{&Zz=Ps#5=&lqH1 z+Yx7vdl(*KH`6w2XiG@l&n_41p>e7B&ToQvH@R2xv+moAZ;flo9UyiI{|VQA7_uKM zk@qk?dt&A#=lX7tGyYid8^x~R-@v~`Jnwax_pH71@M&TzaoS}MJ_sg354pe5nn3oo zd$oIuo%zc8-4^zcH$^OG-=GJJ&-UGEEyP+vf3Y!QU1>G=>3ci8Kc0p6f_!J#mw$k~ zclr6wF4sSUJx%-d#6QMk#Z$}2vp0r}+x4`3HcVZv#v9_SwP&rDTeW)~GXHgGmqWwK zVy(W!&iD^_G+(B$xye+?p{I|-^8}@hhosIsN zw+v_g&!EqRu5up3>8~|C``+oa<&b$v-7G2qEeeX!I?6LXG|2ut4KND^vCu{FX{xxEi za;^|xjxWa3XnBu)qrzT@f9CHj_e#73?qKgwDn5s0zk3uul6xL)toZNjYiRfX51EJF z@Pxj){FZ0qou7{9pakPGo2OcNELMk-WoU zC)!i+x9fc)mi3dhpZ)s>{!n$>TzmlSV_Hx4M{+9d{Ta`_*>}m?OgwXQU4?atXV71P ze7E_Sxw@Twhx~j$mvNX;X|G~GNjrl5q`b-IaeMx!?0um#d#`Grlf+ZQzu;GBf3R<1 z=XapL=07brXW6Gvs#3?=Ok$6h^DYeMkLIV&y0Nogyzg4~&?eEwYhMrlO55;Z>qtEP z=X1_Ra&mSJfP7xgdP#lFfm7L!7>7D?dx`Bz+lH3+fOYv(=;u}y?;6W!6U1}v2iF6-SZjg1{l>d%4*~jaOe~8D?pDTIi&_`Exe&6n8YiwQi zWVr+691VGID9P)f|9$Z}VzVmM44elU!}@a8({2xZwRi}|E+MD|HyGpn?(EZ)x^pdU^f39r-MU z!!o=a+Kc^NF21+gn|-3Z`>M62oy$KP{wr@DTt}ZsKbV$x^Vj}o*JEe>rUtTC-7h!m zHGBT;#%Hxy16ota^_RJi?9(qo*63~8rJg5L7waPPc^EuPUkCpTTUL8sZTH4A`1$O# z0e*mXr~cQ$H$YqQG4%0}cbFQqD`;&XHP(T?6>cGRRoVFR>x<2S%i$F0BtE#LuA#A< z8hB?Iug#Ga#X9Rr&pY;0IQN$O&Abo94}0c4Y5B~yikANF;HO?+q4nqYKXieh=bjYzMG24sYRKTw|_y zFaF*9%+XBtX0RbY^|oud*sD`VU1_Uu-h)ynf4Hxmw8k!>Drc{X{MH683a?nal0?e?#`GN9b7(N8!vt#_wbHOL!ap_SMDv ze4gt#{$&1B>{nqdTwC@WcskDYJF{oYdj&rSjp&KptzG;0IPaquvPZ~k4B78mz;4yv z2Vk^Vp7SQ!C2~{8@4^uFLfDf24gW+K0cn%5n91%W)}&nYk!y{HbKz~V-tI4JwFjhr zcdfENv1^D|X!8ryp=ZsnpuZ;Gk(R$9*;&q&^m+P7jW3`-kF#zUi;ahj=WuNXX#1PI zy~MKiX3+Bc{8i#Zal}H`7Op z@5KMF`>&+UXJ@>(gYo>-{T%oQ9)u>P;(a9bShsuDkp3>`H)1~wd&|q*3}>HED%R=oa(0oEb#hr% zF~{$*52L*-b|CvV+yzf|y^Q<4@>0Xu=dRM~6HVVp$6r-`v{-n5|vT`i7Oo`7(q38+%`z=h;^NE%N{7rg>Py1@CUK0*MsZ z7aRoNc$a!yUUF87-_E`Weueef zLFyuV!Z)@{bIrT#X9-`PL7cGgJ?SfAaPb~JnlpPHZbDvQqm*?V&S?13M~>*74?2DmMxR`T7) z4(u89)JDU>tvAdiu`ZHVN`>`j=R0D(j+_w)55FCphoEH`DU2(i#qhhHyUZ`Es#W z&&7*q7s=lR-yn7!ZIo-?$Un2XI1_VUm-82jW$fO?8TXtGkMQ%|Of&Wu+U?BGJ#Gzm z%1ita@mA~_a2o%7d08ua(NnjpVLI(D+FNoy!|iFQ_Z~09R(_A+>>ve+x~UU@3KNb9J5*583R?*%RKxBQpjD9E{)`Myi;s>EsoLIOX{}pSa7j6pKZXURy{FXWe9suac8{ zeXP1TOEO-~#jcf?pS^tx=drUMmdcqbo^|@Twz=jsxJfMac>%8JT4|g6Ox@<}{gIZr zZ$y6$Ueb1sc-my$9ZP#3zJq(ArQD~>t|hhtXKiN=Tj96F&!!y%WBC*1T*Dp-iy_x( zS7|QT_3%58b$O}0E#Vl*eVi}vWXO1D&&qkdg}gC}}XB?Z*#^LeR#af=jPn)i^*r~^B%jR95HTk#m>&vZ=&xHfUl0O{Rq2EV401l(= zfKR36XCWK0hse2{p7lNh{&Y{3_y9S-uwN(_>o4nRKD~i9EyS`P{PmFe!UM!^5X<^* z38~NQiC4o0^0RlP*0#mny|?B&yu5Rz=Ep+r;kt_AJHd=;BY7Wb`w$$+9x9%BNbT2! z6Cib#`Ch1>Atie&Bu})|4d-BI>dkOnkdgk;)T4(lH+9>Eqo6LWPJ{{_@v$lVSzblLH zNn5cWhkQT#4t|B+N4t#OyZkHp2a6r)_uzi!&*NVPncIVnXS7cI5AZ4S_SP=r@D4lo zSPx$%ektz5&w3vWc~{(xeh$t)S^=-HD@)dZ=gyc672lITPHZ65qCH=wp6U0(@vw-N z+CG8Z5MGCV{1x)P5<8pUxKw(k_r3R9}7rixm zJF%?AX6)?y-_p;-_v4B10DYBvf6IO~joqhO9nw=DsrL)`skeM*(uKV}yDwf>Y$Tq> zPu*t!zXn$E^Ip&&KTMwkpK6=E`ySd*Im6gz(^4-t)ABuM_NlS#-qmVMJaw3Je}DRF zIElV7yhO{~`~o@SvQDm+dl)-olX2dVwh22moHdjBItBLT&&AuppYl@Q@6l?*OZ>XH z7XA)@fissmL%WoWKiolYjXTl?sIBvH-q~_*%f$YLTx)`M_p=A#jNR9swMVJQOI`fy zx{rvZ{!>TQ{5$yz*x4uA;DKW6{s zSBqsl@~k7n#yR7X^`G$`FSi?HZSP;UrfBu~xreN$E%8{~1hPkT66+*)4}NNV5B^v5 zD{1-r0w3V-Tw`-~iJkY=x_B>n*&jYqoDYrI1LRcjA7SUd#a+ul8F z$6g<@=bta$ot|~O7O#TT@FICzm%Ted#^_Akwz?Rr#<&ioo`0u*AUE|}15y{eLgUKf z{L7h=n(xQY_iHWXWKVjLpYhJWO}W+{(w?97cZK{Z?A+tu?CfV5>%sU)+M76Sx2i7Q zh3e53(1M(?r|{?Fte0`@d|unjbsyztpGb`yTv>d7d_BEuwQJH^L276!PR*UlUdBE{ z`zm?E`G=Q_&rXx*P2^00ZNzHw=ZW8JuCfNE(MRzIvbPtj(k^T3LA)Kk8fV@b(o##Q z-;K1%*k1?j{2b_Ld<~?{q^jb*>_GPB+B`*1ZLHAlUfP!Iop3e&X0_+Rqxs*!Jo)$F zZnT_L{j}*z%lgh4o1ASRHTbHzNPbhuyj?B#KVrF$1<(ZA^DhfSvuame>yPD1R3JCOn3}C+x#M6ZS6^Yja?^_`H$t#j+PIlJluH zx!&dc%+(lZ%FkS{a{te>@Anz!L2Wu!7j<|8?hi|}y<^W}46|3hFZWW&xW6jD1O6{R z`%5!+3-h@U=R1`f^*N3HB+KwUX8kVI*YW(VU@iY3_pu?Zsd#_<5&dL(>O1u| zi2s6qvX@^*t4-UTmO9)92Gjp?&35d4#M z`accM<$sQ|zkMN|J*E$S0|3 z*P%6liz|wE(BAC1^y~+Bvhz%5mECW3QO^gl-*JuHd)`lPWM>U@mV3S2L)k0k?q~e7 zU;fv4ZY1||dVY`ocJ>M4sh#iuIoa>-;9qEd&lF$5&p!A*Z410N|2F+p@!Rkl@JGV= z`n*%0GiWvW8S9VaWR0eV?h{X~e?#j8ztZ={pTP_CKJs6q&EogLGvr^$ZpS`KteKqm z62p!7_pVrPH43Tm$y$Of7xJ5kJJ=q7+H{!nzyYX``yW^YaWmpKOiJd}E>>76Z z+zHo$XUfIrxyxwHOZHQI1Ww&PAU2yFzpvaU-N!?;RqU(8&!=bq=tbL=Ka-yEIi8&w z%|5>ce?4~A?QU>9Z6g0c`l0x0{@M5jvF_|^aqh1!`vREjnH#MxJ}-Tu%}%r*@Rjm9 zu=9K$8mH{z$5gvK9Lqjg>}PGa$64d|(%!{ScDJX?$>;Ni`B}4h zA56QPElsr99lroC(Z}NK>8)`7{r}OGYS4X5q-`qq4}Qk`4DnNO&i9tMIi4kURH@iI zb`-CIJm*aJ)?R!|d6}o&Q{HcKcHSy~C_K;KUHof#`S<_7;H||EgZ}hcat?y8p6R4?*CZ!DYTv0t>xT|vwjD|T6V@FXWU5s=aAaC17{5n5X*hOTUGRz z*lzkbtz69IwQ^G1d7hkUOZaEtZ)w|>tZDpJY+BB><>J}z=HZ`>!{z!--E1l6UC4Z< ze)8{LQ=23CS!n|Y^uL;M^173};wxr_N{@fXX< z96!!J6B@bCybtZ=I(5Wq%g^t3Y$$&NvAhc%4LP53oy`4_^149kznA=T*tu5bC*!vt zWX>PrXTP`yujZf2POaw}Tha1Pcp5uvAoV&)&Wr5y^}c=bM_Tsj8!FV8yjlF#IJK}L zJNy3+I5kj)%;~Fg7l=1w57PEr>o)HMZTQ)vvmdPo&#}M6Sx?)*T*%*8c?sux`$sB^ zb$2Gdi~c-)3On2rX1HGJxgYH)7)M*fA48u9o9ZKbV+)uB8_4;DR#z&%wg)jE#8p-I(rLkvPWHyui|G<{S{{~87rp)r2hJe_k!#R2hc|0 zoUsRqC3jEQpWYU>pl2Mj4i>?k|BK~bx_GwjX?e%Fot}Eiv-W2HMLSK->FnL*q%PWu zr}i7skHGo7(VSLKyd6$0?1l5}_sYrKq(-iWd)ayKJ(ztzG-h9fYsu|_=a-B3mKpdO z{Z61Cs7)I@Ry^~zHG72kAY6^lkoT2Z+#@+9>%$liq1V@U_SNel&-FXhW^W~@x!42p zH{-tz*|#6Z*+*vMari0xRJm9K-{MpFwY2NP{sLyp$sF9wZiydsA064bcIM+t{%qV= zEN9ea?96L^#++K{C0>L73^W(J4fm0k@!6c8cjCNry@5yZ|FCAWzh_)hGabYx@F%cO z#&hLViM3?!K`Y~pX&2Bg;1AeXPS(rEw5<0-%*$c&a^|gsW^f497t8w0I4#+;nA1b6 zioGdoGO_HLzvA?r{kjKi0CVJ}&W5-~Cp^|QR>;e}JSCPoPj1%DAM8BeX#TbAf5n^Q zhT8276X{v=nd^-03i0eGpWrY4r)?weS@v|#ooDGUerSa?1ii}cnU=MZdCYU?*>2+R zATKr0pT8#_!%zKo!6%7Np}#`U7_?{ST)9L1M1Jlmb=aLf1MdRYR~6sMjFS5f|0Jz^EoFqJq?{zFgAWe?9fz6k$~=a!9wzH;5sv|adbY4-!ZM|>UHRs5{6 z^C8ze8FIf{;GQ^p?}Ku7VIK?IXq$C@llB8)oc`yDXT4U6uMx}swTj+f+q(Rja2c(; zyhds={a+>C2xlFp?gz8?XKxB4*f(f%zx?Ru!!7Km|JSCQSoYNZ+HH$dgLQE|JY8;I zcJ`FA^^+QD2;Y>|lh_zsORN*^bN&4R@rUypy0Ro z(6+#TL3O#-sF;_Q8~de3{s&iFBY=K3LhF2^~0s?|c)=>xQ>?ETsIK;B^%m5P1u zHtoyW_AD3gX1(YO<*diQN$gwi(s#+v`aFqt0{pA5gYbO0zu?9=@0h9Qaq?2{TjI9# z*#C&7&aUS7gq`K(U3(-g=g(LaN7M0=H=F&WSQL7&;Hw<#5E ztHeGI#3tkKUQ__m%t47qW*^9-Ju<%esfN4E2om*xVrdG zY%I?In=#{*I@(^lR{W={D?EpnZJSbbNQ*Ui*fGj zE_UWSeO|y{1|RWr*0gochl=ma-$#5W+9F!^_pRyK(^A`8;_Pi3h(9Uz67I-9QJapi z72HNk+wbu4{Pm!9si>)ywDjKuvbRj;=Nwo^%{*XideQR!bi2Mc#?#8~4?n5D<+OYF z(;?^aRM=50^IcP{KKn(ntf95$E6HBwBtj!0sXCO6s4Zg-b=AGqV@g@42 zLMw~i!T*zEjV>Dl9n;shChQn3U9_w`xE$o!AN!;xSst`uET!Ub7lW| zo3@Dd1+81D`0lv}y%oEk`$-*d3mMz&GudChk^dI|bMYJT;W+E|6VI4&$=R5GYtydL z<|*2K_;|b%?Oe$3N$2;bZiGH^vp#ahR6zE%Qy_cR0DUikybEpsN0#g@cpX@y%^^6? zo%tWke;7W8QSRkq`atnfrQ)1FkzYez_LN=Z%!N~Fqhpb>Lz>pjkLs4 zC(H1akpJGnb!d|zbJIb)tMT{r<+RlAM(j51K{#t}2mA?tXPkRoOY5sm>h*eld-hfA z-`wMo?9|U_oa+?ePM%rW!PGMhVjuxgL_U+>T$U8^ud4ArVzK86cIqw@le$G-$>>k=X zVsqHJukrYEIEtO`uXkj>Q}*5_X9=FoPtCqiRov4&cINF~v2EZy_wlEkY1&`H&-t2X zj&_7LvuG>vb^N>oW~_4Utc!dOx>!7C%VM1Kt?}x@ew?57si9%=XP4|}wA6WR$a~r5 zcwMoqx9n}5@hlhz8QWU475cb?{~z`*v~9GT#J&L!7yFf+eK7l7&Wp`y6HCSVAB20u zA>!Fb#)#cpX-th%m+E3)JWA|PT4(;l?49H-;GfISe4YRu_y@yKTGrx&>|bHB{<7z_ zEC26|;vd1DLOTl@v(JKOjPdq(yOQ+>izh=;(vz+<5qIsWH-U@L(bV<;FgNw zo)*iQ#-Cj6{gHituE*|Wzxk4+cC6Ta+P^IS8~!t}D>RYU zAMan*2YU+6y4{8SI6dQ#THLLoIAaF#595!9%*RB2SzbH1ieD{n5v`{9`miH?BJCC0 zFc=4QA^S-Q*Mz)hX71O*o{;k?Z9Bp+IJ#V%&6`&j-^G3?p0h8{(NAmzz8n5^ovg)$ zFpXVBn+vP^tKtOH11Ga1sAl+!?1XwwLp%+`sX}%Hmxo@z-G}ba0JP zxWD|9;d00xk-f0JHYf4(?vy>~8QNlVvO#rGznQ13(Mj~5)Z-@f>$Lq{e%Ac`a4_vc z_?q5U{#)!8xD#H2|L=WA+h@g_=_`Hj5Bu>mmRCaqet)QMT{bLRf9(0NPO0eYBG)?x zcBAb;%XLyG`#@@ZN1V?&71hP>ldO`P^JfHYjcYDu-@QjMr#XM;&@aGW!UR}VsV3k% z_MKvfv9E*&*jX!C+y8gIv8&)BectVvavuxCwxw-@vkr637sOLTo3OJ-WX)eLp8IR< z-jb7g&l#IDG|%z0{1y64tO@K|?VT4!(re@FEvM=u<6et@FMCj_co)5kotpbYUw?`B zU|-{TPr;9fZA?EMHs)tN?IkCB+`H_}*e&U8;TUL6t53TfH<0rj`x;oxuf|;=XV4j9 zspZf4&+>PHOZX?~u?$XGtb&-y^~LmDK*_rUe;XB)bF+3LQb@-?ft~(;oBhV^LDZ9mmTiOc-Y*{SiI#i^b9<>k4vxBpV%9VI?2=6}q8i9G}=N<}T*%FaHVGiwU1 z4$j`&iuMG1gljZn&xSnj4q{*8>@%sgE2`a_SQ}dQi`2#i^z5yvgIxau{u*4@bEMWj zV;>9Aj?#WJKNHTH?NwRSMPE79^dH2B;}hup*{Qvc*q7rexCVX}r^Y^Utp&I?E%mgn zJ~Hp6Qt^Jh5x!nLb8;JfF+K%0h8y7o@ptqwkv$nw$9cEUeZEs!%=<9yma_9Mza2Yc zUsK-QWosAOX`i}VLeHA0ZLPh{&YH^gN6MLmb9Uw(bP~?HK+cbC**`(%Eqlt9^f$}S zBYlixkCk7apWh8XAJ2rEmEK|S^%ceY+Namacf#zyso?r*~j9!YWETSOyXzVcffbzj6q}Z zwArmxe5TFWntR&p;bNTkV1Er`%SEk^mRG@lm;VaRzA#w(r;S5b{FrNH&2?knj~{d| zmF1!yp5`a#R(v(>ao5VYf6Jc7uElQ8UL>B++Q-V>m_40-7<&WgE8d@ zsrcF281^&thj%OH@=5o8kXQp+o;&ju`v80c?u=j3$9iIy@EhRl3IDMFfV(P--{DFB zbLdCI&-xt7PYqteAC2>z^Wh$L!*a3TtuK~)??S(gKArv*452O6M`zmY{9E83cGg2b zc71jy_7FKaKQcG#@pDf8$?m~kpPl(z#@>~kciF7R!}zI}JoAHcKc-EF@CL-v}i!MbuY?*~Gzm-)(Aoy`6UUxf!h zRP1}H^iRd=8ZT!7&f30B|IHxJbU6P2cvWuBfvl-hV68sB;OGCZu|hoi>DBy7SKx1)BW z*MS@OJ;ayje;~gm`_ppqoncq`_pw{zr^H@hH)VgpUd!Hto&9K+>f*hx6~2vs8uYF# zzK6ULALSaUgY)HYjC;yEf?ZW%{L968I{+_+jAga_i{(^xFZP|R$rkb_i06K?zPicz zoSivI->Ji!ps&~(d^Y}5&MRg2?>c|e4%NqI_)7XiICGP;>$!6AzLa_xC#MhI1!pW? z#5?mdx1Y%ELz~VY!SBufN?)nz`)HH+4c+q@{L}aw@!Q}#<=mm|hwOP`?eW}l@tx8j zv3By3-xOzW`W$}ZXYPiwzma>r`S~x-zIG}ssx0bhZ}z|J!FWHYlz#%h8{|9D)NyzA zw)DMV3GGdOKle2ha^XzBD zhv@q_m`pnWo`tpa+}rAk;@!L_J9XZ@x|qK&^tp(gpQkjatYN@i3uo{8i#-)CVm}92 z6Z>kPwYHd@I@p|D(*8mAJ1_(;<@YKV--lm;^EqHp*u7-my^@`Kz5^$BIpqCi6)pQ^ zSD3(mkw2F`1wSu$0^Wk2TFBVU^-S-HAH)9%da=jC&g{wJ&&$pJa-6)Z!)Iuj+xh%Y za3_2hUgOzsD6^}IwV3(5kAAQ=Khtlg--J`+U$Qgre~RbKnWT^2{B_}K_)1>R-;CMa zFpK{!T*kf=c43c)2iTj)uP?Vbza>BKv#s^lm!0cAC4N5s3@Yh0@y7CVo_B+T#2esR zwBOv{was*U!p7kDcev-Z>k_Xp_CEb8N9Y;IeY@?!LI%eL*As zEznqQK5ImOkDon%COp7jqo%Vq+e7xvT}uVGmiGn!ZM;J4K>VdX?#K1;BVuWnc^(J@ zAZKsh2ebZ<z)*QP^EQ(=oS*r=1oHi6*5o|#9mW2_-_cU9XW+c^=CjUJc#@rSxT;)y z#;?JzQElzfvfs>xrL>W*pE1~qpSrz~Hi!Q?-iAN9WNpLov$(%^ zxvw|mWUhB)Un!@dcyE5javaV+G*?Zmk7q*O-SWGX{l(U0f5G0lRGcqMjomu5b@Y?_ z&i&RF+Z@ku?Tq#2V&7;x3o>rC__^mF@#B!|WZ#=Ce;0nuJ&W%#K4oVP2f4Z^K&n6 zvU@;k=0tw#YYBfC-dew#vvcl`cR#Pveudp>{}DTseHA_SCE6~hkHB5=W3=^gu6>R9 z`GkF#er|%yeclf;$8EH)zzy(Z_dcDTedi+imqOE0F;{ijebrUg-Zb&U@o-w+0hY>n zik*8tN?#lEqtBqd4o}hs@*l;m#CC7RCo{K9h zi|-2Gp=Yi9s^7b5ts$Q`UdMUQ>cr0X1~2k6PhUg!&g^gbPBXRDpZzl}XHC@}#eD3^ z-dWpL;-lF;D~qu@UYi~HIlB)KYcJ54lzw z_OEg_W%m&K5a&Lc(Kmt%*vVU8{&?|R<5vEbVyXMF;vM-z@k{)@^Qj6=sKz8c<2l`#?tlNgT6FwW(H(tky zeN-ye^f{ zrWYV{+rF~c$KJ7D>>=+OejobtIQ5^gtuHtIH`B)`T57uo%n^Hztx^mYk6?@mdRmIw?*2hEaj_{6l8_C-gaxX6!hs@gu_Bmqf z;M$P&aJu~LFF9xa#9zUs(3d}@()YG<{*g1Eb{-rFqv(C$nhMvFw=VRh=e_rO`UU*n za2Bm&sd#UAl-=2V?4*UHNt zl(AXG-Wv{k$Qii^a-;$H(okhEleG2{^KLn|%-JlhFB4l39=Fcn_ z=gkt@2KvmJ977upIiJpT&DYIcekXWmu}<{+<#yHnYw^kKo9IpWsjCBUe%6%F>wok2 zFPlSsw3ag%vR;PBNzLplmbw~6zaDpknQ&xGZbSY!_FlLq-b$Zq_5U$$ zL(4f?n}5ID+i>3V_M`0rS3n!Ming2B2gTr;V-ePVCKLU-!N#?I`%owVsEMVZMH{FHB@- z&7@Y>LhAf2?T*B)#n)w*=^3NTVPAUc((`VzX-5=Nw!fpD$U%V5ot9!`0 zc+It1@mJ#4bpM5Mnwa}lwK2E>$#Fj$d zi?bI#;9lPl>%z~zb0BSN_j>^UXq@+@tf4LF&1wB%XZ{6vNOe(<$Fg^%ErJJNigp=~ zP3Ujn?7LY{U&4v})We%_96fcvJ-&@UQY`1lsB*D)55xOb6np3x`t7iV-2D8arC0|) z2YOSRBlK|{e_Q%Ta^8d9(2F)4>au6c+n(J*POkOS>f(KHl>V-!r`9sQw~9T4U&nvT zO+8))*{}0Xk-rPpfSrA;1AJSyj`VRaPQRJA$JkwQ9r~B-o7vM~ruGA9)1ebT&%GJG z+I4dOm-92$dA5Do->^@D4cVzU+FX7E#>*Pp=E#0eSbsC zyWb4Cx%V~3DDRSuVSt>!@It6rF6OQwJ9}+QvBlay;hH`9yYL78FIK^yEf!p}YO7_3>%U+K?kDfKY4!gG8r=T`%to9$XvsYftuh1s(L*R(AJ&`|4 z&Ro17KWFr*&>QmnGpmcwRQt)#99)4{LiUuN;@!3Tlm9lqpLhq@m!5rVUECRVq+P>b zBj-0fmH#6?oqtb!^Sh6GSVphKUjzHF8;XAeIX8}RoksN3SbbXdy;=5)?3=lt^mU=R zzfj&e{2TdSK%VCfxoJ~_owNOA@z_T~_NYGi0$OVH4R{A~9!;Tr#?G~}$D~$%;^!SL z_y2$I;_TM+h5E@{TwGo3FYAc+qHl?FZyVtJ`~NHPE&Pl_-evD)XKkjgw!{_mx8Y}4 z?fF*IGnTV(P5m9?dW~Ew?>D3AleOten<0Lsc=of=wD0+cvwu?mz1eTUH2O_)bI#8Z zJD*mMmh0!c$s_Q!kh$mp8K>0HwlIR7eIxgfb?^=}fb1zJ$UTyMj9Av$T7BI{n<|zy zm9_K_`xp1J7v6xs8}wkGR%JhwleL}N+JTn!KOQ>suZDx=cH?KAHo~d(Dca3-jqFD= zX(vIRskduyMte^@eSQvKi5*=k&gr?hGyhol8nQoCS2~knBD)UG+C98nyhAU-SHfvx zIdd}?Lm~Tm*86$-d%aw&lUyU~rtI3a@G!V<5Br{YpX#EPx20{O-7-0^(X(FinJDj8 zC(!<&4T2W(y20c0ljUa&`|B&UIvx(DrMB}7iy`0VPp1VhR}}kJ7j4=>bEuM&v1re} z7YBKl?7<(a?Ob;5ZA4k^Y5zV>EewL~#j;*9U(Z0s`Aw)4`wzdPSOd>6fWI9-`(0Ps zE&OlwH4N79Q{O8+!?kiovO9G*5A^Z+TB^;~tIY#_KiU{M{b?E7jO7%(15QolzK+E2 z>9-Z_DE$A3x(leSiuG;aNDC4Yil~T`fC4Hu@VtZpCSsr{CU$oRodN<10tzT9HY!;7 zKCfUS7Pi>kt;a&Y-*xu+ukWmNt?RCtJ$vS!duBg?XY*1~>+0c$p%-M%o0R`|FBNwN zTV33eEqfrjTAS@J?Q?c~3`axetAzh36?g5Q`Tr(wlQSNt4y>pw?!52wWe?oNp7*;M zCw70fjC)`59gzQyN!BCldj&mnejGWqF7aRMr!iX{ZF0w|fUHdic$J)W-&5Ru+U&&E znm!R8WNS#D%bq&ak)AOoPrri^WoHWeNO+#y08;06fTr3_ELm&MJsM{%c7>ABorHyAWq@Zw#4>4`F!Oxdgdib;I}Y-Qs7@Ta=5l z?;ZMZxE(Uj$@_QsXOdH!FEjQbY>gpjQu1gqc>>>UIQ2ZaaDK@h4GyFq40G91JCClC z%ldeUoWASh%ysfQ?UTE2R~CCUc{PVU_uLWq1^T);_4oo~>WbULmu$mu*8T=D*^eLU z<1F$vcqN_%Elb`NU!m`sz*3934X56z(P8tdAM&2RvGQKRinQ zm_3|5@Q~;3Cnn>3lf5pv1y1cqy*d)#K);tichB$j+k<^OXa}R9JKqjs-oQWML!fz8 z@$J@*Eq!0_`Oo3(T9u0WG?!e< zSX#q*%_z1>IPbRzCubV*572%*Jvq@DQVaIPsgaMdFA|e;p+9{z z{ZjI|_yFy?^W8;1o&F*{{~f1m<73Z$+)bMf3 z1zXbBXa5gd#+);8d)SG+2E8A74&QR}U-~+gyuGn4rS~N-#9y#KPkz~ZABSWAg1wD7 zoQzY`GN(Dy{-F<{_klIyx7Gjke3`e?_FOe6`q&d$zXM(KE)}%l08(&z_o|=VX8E1h=tgU-uwqjneitXbGFr zpDh*lf_CIQXJUn#N`-|zQuRJUH+?j7KB-gVJ-Pn^4i^vb@`*`-*{CDH5*?N$D z_9uN4$XdKwQt!m3E?!0c2U2$?vu#FRNBo6+10egoEo@{Alf*3MKcUL9l8?co;AV32 zx^G2s&Yne1u4jK_t|M2Gd+}$Db5FR0zAGNgm%Cp-a$UAYRmI&S?=u*-fLDC`wj0_;C8R zxHn#ivli!yz1W!csWMi!{O+Fh{*wIx{F6MXByYi&^X`4NtnY^8+5hjuZJ_;kyQJ4!c5Q`UUt{$k;y6=jG)6)0SLc+i&POmvUAn zUmBBd!l}hoY-8xz1DU%|pe9>&dcNDIkw>v@FD7TqO8R)dF1Rk+`F!=rr{L3Zm-yJv zz5l0q<*@=jIN! zXIP!c^Iqbs>F*^j((ZN0-K-&g33d^i{K}rsy!9fdw#~=;vMm&odANoCI{V(*Ua8Hi zYz<)+`w}tNO+sxSTe0r6>e}WCkTe0mb z_C|PH%p9Eac{je{e7lg(fU)G;%t!XqG8kr@-zc`FkNwj?{sh*#CRR zvNs^-jxiUWfmii672m+u3Z5jl)n+_7`}m-eIi-JYj*lmw4auu*;2`$gJsXMJfW9}* zGoKWbT0EVcT&qd%0-uPzN=$$D+zoyrw`R{B{aO3z1n;vYeOFkqN z^qYI}DzSOboXfw_zo8#Rp9p=lPu;wkZ7}&8wgu!x;ts`i*iw%kCg+Z~0smZj_W9@J zuW){YnCKmEAm^RC<3n)%n+h9}Z!Z-!yQ1R%=T7o|UopvnCghtTwYLLXHCWF6w0^gS zrI2xa=p8Q;a}nQ(@ZUPcSBow6s-u|Hznm4xwF%^YkojoJpMCWgK`SjLx%HCV)!bL%m&f51R`>SpHkTJw@Q%XnMT zcQdZ!cJr#@oA2Jp?Adp3lUu_e_{=kEv)@M^qD|&7`*Y`Vaqi|!YznCz_uyqP7IMC2 zT~j*;Y5xVThR@T-d~#p%8veZh#`t*8`I7wwbMhlScZJ{R2f*WE`_sP^lYU?KE^Wy} z;Obt^HU7NoCXjX~i&@T|^E>&zBdkLX_Hy5Nzhq9x{n%3nromM4sUN3npSr#qU*_R^ zw$JJL^Mo^u?XJq=Tu5$QDW<=eNqjjMKjGUErn9xx?l=55PCn%QGmjVGtLa(m8u&pt zn0#kRj%xcN&iW*eviHt{VURvw^PCNj+O_a|vII(KIEJ^*%#UKyVBRkxhwrDcA}W%@glbD#b?O*&GrwzjjTm-c`*BBCHVq9=&kv( zR+;B=^Q5z8|5HvE~^w`C8#maQfRCmi$lez;-A34E_ED)#Q$k0Iy(bL|doZxKI~EqAJ({F(C| z;CaX%Zdi6^v**9*d;_^PxgGo|K6kU+n^%ba3ihCfYv8QSMDaJ{TF|7bn5(VXYLTak z%N~55zCC`Hz9UZj>1@g0JgW|UDc`5&MQ517oKZ@YW=D&Yd-ITGK`H;BDCtWW=l-m0RwJLLZTCtIHV1b_B& za{Lo(b~yP4W52Xi%>Cc&`-w>{Xu_6p9|&FOwQxOsZ3!ofNxs%)>rL+oJCUIZ$_SC{FADRd)f^AAHL+xW{?~_!8i`BD83Im&~N6? zJj}u;;*+h(+3czBnfnIheyx@ZBPBl2eEC9i#ux$Sv9b(8o*Ufsi@6uThKtGF;YBDz^7m-5N8-=) zl|A}^`P@^Rd~YPjF2c3xZ;1c6SMhh?vUihbbF`a5E|K%>)XL;la;-l3H1@va-*L{r zP8=Umi~xm96|m8uke10#DBs53(hy+j^yV2xuay=^39Yyokku*?rjWR#I$y1 z-7F@xC3mgeAh}srOmaGV;dF7M;ahqK_PY2OoV7br{G0S@c&ga^`~UQR0(qflJc~0Y z8}cWQI`Or}$+45kPnw5m;;%QYS0HM$*hwz_vamu;0c$;G@!8@zzOFK$;Q zKXEJkKHE(4uXvhvy_Ts*#`hpO`z3!)^`iC@`BM*?Ycmb1LFQ-*+!y=*aEZ9&@HYH; z@0H}tTh?K3`dadQvG?Gt*D$s&@Tl=tV^5oV;C1n>@ioTL1nPVUE^ckNEzN}JU9)VE#9sShXMer(OzhCxmGTK3HQ=WrYB&Oe*I0p7H1-q`+v z)?IpdG28G z*{7SZ_1C67K8=1AWbS(DV>LN>pFBT?Eq(38b_!qq4cV>aC6M~JFV4QIi&KZklG9iA zV&)@vtTyFhEl$BR^pQ0l1pAXmifKyTqslpq_kxeuFTlg#asJei)WOun+}pD5mGtEA z`(@)Kk0Uq1{rOTSn$feL&hh+NY!8szmJ6?pp2nAZ3Oob#xaxH+(e z@tiFte}1|%t|{hH_OIbRD3ywDr}N3li7NU0964uya%>=5H~b6Rt&skw>Ek~AUP|r- zSY@-28Uv}4P-&iA~m=SVoEqOg6S{rAvST=HNs z`x*4w^(tP$E#AM!c_8jn-`QmF$zn?vO{h)F&*LmlTFoG}n zGtE3*#n(>EZFm%W=Ji{=F+JnRdR=6^Z?h#=a^^n`htjiG#_(m_2a7$L|3sYn{4n{^ za3OgFp}M#|*pr*-Ke@0u987=8vtP$e z*iR*oBEJZ!CpU@7eBVxp;SJ?k{&ioJq< z8h>&)ePo`FB=60>yEZ4|m)Yvz!Thh0uhaH%JejQy+pFZ{?g*THk{n#kcM`6ED_|Zx z0!`GQq4*8{b0Bppb2tJge=ErOv%#nIlm44vex?A|!mHhXKTgBIy{)o8DMSi#5mM`s>u;u;VXZw(x zeYg-mXZ@;?Q`=Lov$j*MRq{D!&;@MiZvcPxdFC>I-qx4ih(GiC1bsSt^8aNhlgF`7 zg{AsB6IbEP$xQM9?cT$q@zZz-Zlv9G^4Xr>ksjN|Y;)KqmVG}M=Qz*F+@?0PAm<#I zB5oOfp0^14@V(?&{n`IzUnwqg_dNMq$Xa}lH-fvMHebEoMa|xTuZk@>*%pq7zhoca z(fYZ8tvaM;|p~Y`yriXO8ghE7&s^sYx%A^WT}xc()O=g|?6JwP#PhpH}t^zOA4?c`3XK zZRmI6xv&z>rDx5qB5$gn%*g=#zJ%9h|44r$E9_;@&pDC%UiQqd{QKan=iOq)ldpo? z=v#_uPi{|s2bM$TY_7P;`p(*?b{x!}`FYlUN)EJQ&)NJYd5E#3UCxmi_%pU$wD}Q# z4ZYd-rcZ_o_1RzFsWS`3J^&->Q}B`E9wO(@942TtgZ?hPi`XXQ%)>M62SW|=QS85X z*FPcow1=2lY+2Xvb!?fBTlJUxe~o=Oy$;?C_7l?|&LfX56?tV>K<-&- z+ZC7SS>v2tAK(Mozr@=?a`08p`jLDcdmnr=TXN$K$T^yr!{B%NgLpCi$oOkj$S3kl z`0qKuH=aD&e9t1!fvnFHkaKFL=VV?Rm5TiNgMBp|K<~r81AIz83&vCxXF>A$A9{WZ zyWKN;(myX3`IfmjoiD#9zs$cqy;DVTXZ*&qZV-1Ludy{*5R_ew<+RTFN*r3s^xX!U!n!4X*c?(rvt}=ri#>Wi&K>Ft&pZQPBEBv8QT8XqwT2zZqu4X1G33*x8uFEOO6gC57jn(SBLzjI2e!RPaQ~`jN^Lxa^ud~avc8+IC=99Un}|xIRAee zsR4UJd$wcQ23EN9#4kSeDtl!njN?nLzeb(`?Z~rnOS~yxJ+TeRT|9FDJ^OflSOuNR z_7vYSa3y^l{bF22PaR)FA4;EqlP7tv?CERyJ|)-1ji7slby!y1C9>Afm*fHe3^{Q# zc`134cBw)0>Ax6D_QX8)UFiSdlOTKQdoiowL~`oMh+bmJr@~I~Ub*;Y$bP zsknEaj{C7CM^o?4rl(eBPO_&TGme#fmFziV_ki5bJ`eK>pO_EhqIIC(S? zKd#Ta@uu|LMONb~dIRnL!q3zD!a}~x-I>NafZUOseY7d027GDUu z%tgMVGFO)y%QraBn2Vd2?S1F|X!1+2LYvg1y~Q5@*^5WgN8z*ho+7^t$%z+mXZ@re zHl~ki07^^(KEOqOG{>wfg+o zD_QI9{W;KZoZPstY<|?a2guFHLvV6B?~`x!XX$0Unm&=dj^A~v>!+q?|3%N*eh87X zkCOXK;cL%)h~5!irEjIpFu01m?0@|w|C7@z!+huBpZRw)7gw{7rDx4E_967_o3!~1 z?$iI4+DsrP*Dn!M3%Zj(!l_kR)4b=0|C95(!;9n#v`dbst{!PDf5B+B*YO1LB{4ne zD31AQ<(`SB}%AFo=KxC1ifz#Jlljea6zaEEV^r?4Rt7)Rp_$F7b@ijTglo4y|E*_Vf5_!63ek>79)+ z`*k)J}sFR?Aet6+KT@Ear-v(7*8KTrM`vcI!Gat>@ru4BHF z-#6o4+RUu7_Vh_&kH@2|Z^n>wE_rbjd3!Otkv}jGz3_FAwN0O+;W_fx_#koF|Bv%0 zHore)pJeW{zw1$GhygGrvb~0Ez8UD$e+> z`kzIv&OZ$|;aiWbfp$B1&*VZ|eV)kwAh|YvTwLy$dyuoYH=w6(^d{F3vmxFRzR<^e z?43P-SN3T*@BR=u^Zo<*v0myPJ^S%%d@|fzD!zyMRuuQWN9p_VPsUk~o!DT*TI#zEG`94)uquQTD}go}V$S67wJWj*$H^5pMwRu^x%6)9($s3ZdCRfw{LHK93>!7~a%u)7Dax{B4_2xHnE2zi*bL3KyPuJq-#Z-?^ z++b+RHbGqSW18nQC%?run!YLjXmZx=bvz03d-;&q%JPCg?|cKjlbCOCGxm%jdnoHT zv$D7oE}^H7?&5M+yO%tYt+)Qt|4~qbEpkt7k_*Y9)TN!PikiP4IrID!`(k-=9zL2a z>yx}%0uMm$#UJBW;a4b^i*w*z`~+lu^KX?pYTL^5pJwY+D((P}k#mk`4<|=Y6T6b0 z`_M}IaQee|uy)soNzFTjK9;{5`BAYI^r`&mC;$FG_53!rKg7&sOU;@=zf;UbxDi|O zy9s3NcBm-MisSgc^Pach&hQ1_x?=9d3*mBcMtrGB=aTdNop0>C|4!mQV6Q{Zc~BF#g1UU`u(yF@#AJ+j z;Z5)xa2aI3%@mv5%U=J^ysu?jO}@za_^S9#>Bs16U$*pnme{}8l7B7fQ|X!0oE?9# z=X_s8u7KoV);Bp<4c`SV*i!>8hs{pAahcnKD z*gi0ph}eDE693heBIk^I#@~ggp-l_+ zzVxj3V%!|(97t`>J2a~(*782~O7Gc?zB_yTe?z`Yp5mL1Z>7Ja?Of0OmYiC#HQ$!> zMV^&sME{&Gb$%jF{Y$Q%N*3dM_WyCXdPOnM>)_1$1bD4foEdMDvo~)R zdn=?Koc&?4=;xA#P!CBUtTWqZmF2YVs>O7uiXdagREPAm$`wwIb{EB%s;bi zPUv~(C&|s&&&M6{Ci=XPTtQC$&WEe$!I)C*>=ij$Z5zPwg{_m;gYc?J91@F~2`wxxEtcV~^0W7*$z$iw;W zfV1HSSZRz|&kMyqY3$pH`@2+p!_*~T%9lOx4PL|E0565arT(u2si(8qE+(Hy&bcy# ze-zHXst5f`MP2%eoH;y2?4@FIhSwsWj2~vtymp|sgVe8kwfl(uBKnKQ@d&voxe@yt zLgr=_J{{+LPhO0muQaB& z;0t>4tta0J3K;c?pVjca_^F}FEy+qUu!W}K);IO_lK9ri(#c_e~B~Ji}+^ahhSr9 z3i%&fG=4i|^IkVOwjnq4?xf z_FFCTZep^Zli#;%Kc8=VsDNw4B-h535`~%53FS4eev#o*5 z(L?ez>z)4ppl5Gg3p2@EE|XK{Dmi+qcgZ|lAZ`bGayIq-IpfZGGnp^X>I#RFlWW<> zXVH^?se8ARpJhA7IJRO-?xerkxV>j3kG|zk?zD%uprhFAw{65d1G|b%ZeLt3_RAu^ z^m8*F0;~Bxr+0!^=!fIfshsusUvjU)JLS2XvyBm-y*m~5F@~(krcYm z={WQIJbUskxjjK#=KElBWBe1}D0oL~>R{#}H7fa^GoT0LnUld8esxKigPi>!Y7(IBU6y_|)j^yW8MK*iLNLdnDhg z-ehri)8DH!zh!p%AN0rIW%@yIKRI0^A1fOJc~5;Mzw+%7^k@GH|3^&b|6RD8d^&X1-*d1X zTlUPwcrD(8?LKW%r_)Ey%%3`_!OMJb4J`&S~oH5l*zvP?9W7x-v%WnyplM(dU__s>uKfW99&DMjQaU?IE zD;M{M7q!Wp|BRDE$BEe-YKi$tyVqc)HR?y-6?TU;;*tlc6M5F&*7k9>R@#lm$>&#% zX9j*>+vIifYaq^^cv{EJ)5mlbW{)oP+lV`Kfhv|?#aY?Dz=SS*yL$=Jr zweXexx6s!yVlwx+^VO$sK+hSR+^m;)XbsEKo_)7cv9Gc|@3ZfK8?t8&CeruB8Q;xt z84Ro_?qf~FTuFY5?OgJGI5qr%vN?g;{mizRW|`v_mgr z$M>?IS>+wIX$|?^J8Pdi#p~wn70CNu!`?^RCgfp!$)bthiD|2^`D}Z`O~$bmeyUX5h0i08 z(&vLX-%}Y!558OB812X4tV{CZ3w^$hv;Pj|%enFs{Xl${n77F@VGDB3hRoSkQHIB{FhSNQ++TSU%$dz_*8lW{><5KhqrHd{;p7DLL0sf4AYc#V79{FL@XI7KC$W`hqR_|C!jVUuxB3|BKK5 zxk%rS!!ow4&Cl9R6_c8@nB0*qwRK~*@4UkVyo$Y2d?W2sWBRiF^r#$RJB}~+)!d(Q zj?U*li|sctH{+~pZ?@F!xojKJ^FFJzZ$-b$SY~TiVo#pmL2pSu5w_?5fUOUCFLLsK zAuNUDcumNd-l5MBv!bMC=xaQABsn=T3U{YpEhc?6t1Q052I5WGfAo&o)9;A;k}bcr zK22VU+pv$;<~hhS_GO>I{tE2Iw;_DMmb}aOwh=R~s`&dXS=XWD5jeSjJh_8+JDmIt z+X+Fb_#VhPkvt!5EW^o-U~6%yAs3UY_%@=iu2d)RW%OaNPf6dfg4_%Lix1T1P%)_u z8P7)S4e4ckI~)up`uwtc9{DevG5!FlfBo^`s^WKrg{7kHM7Hdg7S=55+LeDMzJ$IG zIsL6Pw%vNr$#abPYX0<_Ju)0;kL0~Fwyfb#?6u(&wi;}M*pmA{u#KX(g3k2c%~|pw zW5~X}lRQY9^qt=-=Hk@c7JT2c=Xcbs=V)=+2g$p<_e8ebr`Ms6B%h1Zz8CpXoONkq z{2ALn;-B_zlkrV(uy*-3!O5Q;^}hgTKkvc*6nQszhP(_uAulh<6XQ<)O@?>0>5i|4 z#_%`)5^=e=4yP~1_g2|^Y+FMI{z+`P`+Y^8%{G(&JA6I8DSSo#8E4Ig8h7S<0Xh3Q zxiyrWJAUpjI~#jfdd}tE;yObkG0)-E_%*)l%U9@|;=ET)_T=RQKGS2VM;WwK){ug;V3_@y&ws#rMT`=__lJe7qJn$Eio}h^S_q_7ao(;w@~G$S0H6;N^0l zi}(fNGIu#AdXk5FPVTMO(8tpA+rY{6=gEB@F3#?w+0NBAcf{Olx8OetI*EA!*De=( zG&Sv2=+2&T<^8h9hT^lVVK-b|Ut7Tw`l-p6|9)5Ya`J1wcKL4#9U`tiJP#B2Rt%&koE2cN3hKlSGS_5t9jmVwte+A6#s;G<6BS6Nbh|P|Apjd;8gxDkUig5|H=8} zdp$8d%Efurlx-o~Ta`r)W$)C1UB$d5W;OpskXqUfhOnOvIU|N?lkd9o=|A)3yr@?$ z&fq)nw`}v_QvPT0$6|lQtF+shtsD7e7*ci~v8~0Y(^GSDW^RYyh4smo@+Zfh(8oh! z{zK1xO>PX+E_L)&IEOE5nb=YI266jB_Ra_2KhjV-xm;2e@>XVxBUVBcMN&**C=pX#XGl zvi4u%)cd#i)+Og&vA2GbBW-bV<2=6I$rJFk^p7g#I!X8>d~Qawz#bIp6t0Z zjVxOe`bRj=ZOy+iP7OYre^$vp@Mpp~KPS`svG;`)lxo0r}r%?8(t< z#oPm*K;B`txZGb;Gmc`*xbx>)ZP;e=Po$qj{#smXNZqK_yEyxHV((_Jq|a9DdAIDb ztXngB&bpi*IcKtWv*s-!bKM9p?^V>Bjr`t`=hv(#{+yyK``u!mhJTDD?Puey_~(-I z|6w~t-=}+aXSPk*z8AX#+X$RG_A2{T_s0hG*6gpaeF)j}%RMXi#`%01?*dp#zL~!-ybUKnPxdvXV%}$p zKLtkV^9O4*nSV>qFO#Q<$$63b@gpRM&w&}RA^fgQ?~-*S=NllkAm2?x@G$zD_z3zY zkUK~A-BIkL@vijl6gdNIqn5eZ-d9lDc0>&U-w^ zHypNM%N)*x6JauatCF18cLVFQh%JAX{1sbre1AN+qL}ZT<%f{Rv*pfMoxUAC@0RgA zqwP>~>g@KoG21k@3)2p=f9H5c{=8xpIeGB9xDUyn^XK>G`^Z0#^WTU_z1fv*D=~{J zi<-D2`8xhz#XY2bo_8yG8#q>c#+aNq4RWrv!I{T1wSTs3%wqCBcZ#cq-)G;H{ZVmS zlC!q!iEqNU5sWw1w)j0US>NaQYvKxee|?;UKY=UBovMm&~%Qcn|Vu&zZuW zv8N{d0@;sG(@(??;N(?%v5!D6wy*I|;+9m(Y5K?Hb?}q;705h42E!_fJ@<&v(AS8$0B2s$7t@db1$+Zr_UCu}6X?13 zJc)kgf9^GD*U#K76L+JSh1#_zABaz* zC$}yncNCZTn1z3)-w6HaSZ!y%RPlwal+VQ9EB$xWqk0}**uY7}k?dzx$FqkbltbH~J!a^7R`zogCi_PWR6HRG33(O5X+N zOnU)t(B>a)lPiCc@6~=J`xwZ6{Q}0Zy&}dKZybZow+d>rt4>aNKMIjf8v`AbD=A}N2xfQ_N3P#=ZtI!XX`KHnBw_)ZqD>>@E+UN zxHDVoRnC>Ip*y@np9op&iJrYKc`A9nzMAkKUg6pFwNT051Ruthe_N6BpqZG=SKD%N z7d@B$w76U78?vX4oC9~4itq1M=6QSeXW5p(wPIKCox-+&-T|-G_fO*TZkemB=cnTG z|COA5l6rp>zL-7!-t;Zu`kvlL+*fQ%>09;k9Qv#DPhcPTp;UaMWgqVZsgFmor_R-Y z_sRLbT0+jdc7gG1$3a(p?(Z4Nw~g6vgS*%-z~k65XAS9X$yeY9U>;P`Z^hMMRH-`c;?%gDEt%tOA^SPC^f&fN<>Cxj zZLDu-e;YlyQj@Q)m^t`k`bfST#GF81kNtg|HGTk&)n6xi&Z_)*^JM<4b!uAfE_Z2H z1-VbwsVM$DI=(OTS+A^)u`k12_4O*-(R`WjDtv4~^K@r{7U7zH1(4+lqWA zuEd+cNn)O(KSoa7_*TE;aR;`2$+>eTxAW)fBk{Jd3w!P<|BzEh{vm%vt|oR4-z}x&abiVvqb8xuJcd57|U zNv}bFA3u%r`$}K-5&xHQ;G4#F2pX0B@+zU(TN9#LdKa;&V zzM5@w@k>4LHR#WloX?#g>v|TwHUArIACOavIk-UqHT&FY7UbK9ju{?`enL;1oV(XUA8qfz z@05z%IhdUL!g!qcj-K-vP7dbVE&FSv{`REr$DaB!kS+PR3v5PS#g_ZP6v$jhZeLaG z^NHm1)Pcjr{K3~vpK1Fkd3Ux>ao($a zxwt=P&7Pyz;{Q*j@zV4E4ZNIfC3za;`D<|gok!|Sz5#MaO5M)Aq$b;qV&>rd|0m_W zYj{@Xu_w7D{GgR`!#uSh?u3?9Y_8ZKY<^CbJ>r@cSF8$lb_k2t>M4#S!JEcqxqLXp3@FS>Elp5 zk$)||s-!I9_2{#3FZzStsTsXLJ>yw`Ytz@M@EaWdm7Y8P1@yk;#c*AzsN=)gw`0$` z#P$VW=5H_ZTQk=Dj+gQ0+aUjr z>|&fbXv&wnP+<5=&U_#I(1-?30z zjTj;(-!Qw1`IxO8`8BrGs6Xk$$o=I&TkXb>H)hZC-yx^YWo@dUI(yz@3p|^iy`Q|y zd^RRW|CT@RIEB9xIcuHV`iPwS@*tdZBxl?xz7ufvckb{X7|SK(h0q#u@5nuB-HPH2 zX^*#o{rD>R7I@#}WX|*x#pPLz`0A0H;^ax{{uFxe*dgB;ztOMdJCZLsUV^cnJqBK2 z?*=(5U-0)1PUbtGo*bUa_Eo9)F4>ZvxyoK0gj?dbaK7m}Ycsi2{Fz2$w&do1IQPNq zkGJ7awja|Uc~Z$f*X|Vh`ubS`&qC(3J%2;~4amdUmOyH9&dcd+8Grg3N6-A^JNPW} zy4nqfYGQ6<8=;R|pat85unP?4yB(*l++HfyE&Jdg_KEaP?4N75or2k=!;NB^z) zvKMZo|AH?Tce{7kOxvbCi}yN%J!8F+eTBGzkbF8)%qQgZ71Yr7Pkpv!&-#B(G;H&tS!r|n+R|&3!dGt*l zD}Iy6I-g8`5_Z+!@%)Fw3ikBXoox!vJ8jMOi~f3%8?x7;?@T_kRQxWospls?d2l)I z#`g=~#=Z1OZ%j@e*CDS9)A>ij3b=qhb2f*Z{(9KI`{D-BO8YxJH+4RBVoPzU)eo>w z!$a7cv9-X-)6|Nwun&2nn6I>*LEl?Uo_9CfY5KW>-jr`F&O0XWUM1JY-+GtTeCLvP zVLK73>#r02B(cfWoM*W&SK{pJwd_ZT%Nl)1ZUU#lT>X5EHz-L<@k7Ms9qK`iN3~ zx#%hWKyv0WeeTNE1^?fAbff<_Z}=C*hmS9-tK@@lE3q@##*wchr~a?Um;9IrheFQJ zChU)rukapMkUNtbvcK>7$=&2&&h9tqkI-{(&fT#C+y3<1aVPp`xGTNBwoPEEb~ETp z*fTec*p6pgC-z>xogZ~xc<1EsL*nkDr_LUwuRp|R52junCH5M~I`0q3_ant;-Hxv) zzI`k44Eo>NtizYJ*@-Q4cO!WLK7?LFKRG|n6I0*wcDHX+lN0v@+kEnq@H9DdI6>@n zW(ir(R*57z~?gq)ZyZG-Tr^ftD{+BH|`xt&1 zHexag!_6Hza0!G=llOg{Ak%73};O;w!if=MIR0M|73fS z-iAGO=y-a*IWEQb@@N0#y$*zFCEuj5DR~thYFuN;-OXXG@y^l7 zZN$tgsWJLZz5I+lIerr4zW60yax3R<*1rKHhbO})o-v+%OK}g-Z-fWj#LYRn)-z-f#eWYT54kY)fCCoLs47??lf1<23pY^lG?2eZKbV;Xd??H|y{* zfATi=*ZGsrTk$`~emFU6mh-kc{J`Ij?@V%6_G8KSi@OeIZ~TJapg)asKJUwxeK%Tc zUA9$h>(CFvv&D^O%lm&sUY{*B@jMs~6|f0pE>eFV!fVAIB=!(`*1ZS4M}_?9ReZxW zrDxt#dvfl+L+**s!e2qYG43ci=W*`2muZ(Z*%Z%`zgw{N<$qV~_Ur@LQ)gPxGxo#r zp8VPS50iHxueE2pi=9h<5tj3P&OR4v(VOvI0zK%5(!1&B26F1(hU}Zd2khhNHSp0m zXUbh-Qje>V)5qq^(s!_0-2xGy_ebY`|~DjKeJ`;3}Vlk z)`3gdcZGKP&R+gX?19=0=gZpf2-%}si`kDq@0WXjC9WrKJe&KP$m@5Z+bUjV5E zeZ=MOQnbf)>B;G9jl0C3dU7-##5awceV@6x#&gbM9|&i_-E2>=eM&xxd>kIDkH+Ne z{T;QxiS0nR6@KS?ntcZ3Zt|jkQ<>U#I$z?Rg5-F{^PIDNIDMXXtX3}OJ9}gh+X(t+ zcw2hTrR>9<^qoDLey4e_Q`sM3yPw>NJVnf%^kZ;^c9SZL@4oESZ}}hVU3{zDBBndv zw>Wq9ywi*DigvYdaxw4xEqx<$8~xuH*3T%|jXihNy~R|J$M9_kZ;>0~yh9ymMt|Eo z<}R1LbO~QQw&`#P|D&Eiooz78=WhY$vUle{T;DIT_n|KrcN}z}mtic-DeIT-T=JKm zk#$erZ(CLD!N>V)!Ci1P+e&l1F3vqHdoT4M^)PpnM&#tv+w^zg6IfkQ+(EMEhVf-i zwkF?>4-=m{ojTBw+(RGx<7WEK+71?fjkx~sDD1=5zM?p19^o6swjE?$@_wWAm3Qs# zzSNGKx&8^K_75blE?4{SpNDiKzvme{vma_r)`(9ny@P#~nAC>&uB4BxDE>ZE`nZfe ze}3GUZ5Z4@9|Yf#&k>v7aGvnItm$P{<}7x4O`Ns3ANPap#pS-zzpD6MWvaFZ!vm1@ zSPo-h6#uKnc_K8?ZX0a|;M4eSf~?`0^riR;zSN?;N5(P~Hi4XtOK@tyrZ{`DzBaj2 zT#0Ma2kQR@T$?@*YCyxP;=CMyQ;XW`yAJtZvG>CTrl+6=f zz?OQQeK$!TUwHRD$$vxYRL<(FadZ77zw@kd;(meo?0@U88v8HoBk4KQW|KD){~M0{ z0xZ|>I6Rbnusl9iT;}C`ZFepgzn^r(Y4-uH&7XP9S@%Q99E;l?_hV}WQ|TYj*NRJC zcNCX(|D10%tizY{EN8=F@)6{panAkec#`pat4^ek>TC`CeqBkdYUO~sN zu#bbu?5R`vU-oqsYy-8#eafD-$e5>)J3;2MKK~l>7+m7Zx~@mg+zjJSpB3cy+47z( z#0s{#L1`jPYz&;U{k z^Y3tz=W9#yfbDC1HJl(m^EDAtk2c1y;oN0r;nvWVoP2vq`#teQII7B-!=7Bsem-A6 z_p#p$xyPOeyO!LutE{28&gATY_3+t{*xY?{e`}2!<3;9Up9=E@Kk;{G`&8TOt@ZOcJ!_w}%sjTBf2eKFmzwxRd>a1{a)0e+i91Q$Q2Z_& z&7K@foyfVf!n3Q%`yJVir{~*kQ|)`feDQ7E%R8FeR_yr($l4EMe~3SOFk@MWw_?xP zSd-ieZ%*Hc+yL*4H^d)%)^>O@{Uqp1PEP+-Dr(W8^gJ{DME$Jsl}AT^?@T-3Mx z+mGbRY1-y2=n2^?tHfoWuHZYRs`y>?2Xda9oI8OpbGe~?FrJDWXYIG8?#ei}|r55oIFe$)N2RE+gb{rv_xXWzu1@lP|a)x^IlW}4WX z)g#Fnd)6xbCP&+AvkNp}KbNfwd4K-gT@&{ow&YoAQ)jk}^)`A-7)7pN%i5#{rp9lK zAA`L6Rr-Gx=T1_Ahl~9ar;g0V9q~0Vj{G!qBPY*xEEVJEt-ryr8g@z?`xW}UU2OjT z-6!;)>8tQu{GfOKot*#g$-Kt|^4*Zy@E89Ecp&7R`}MNV+#j|fw`E%(t{MCZC%|+3 zV{z6s`zZ6!QEb*RwI_PUd_9c(pZzLw<~6yP{hoTeE$$Af8T;Yep;cMl>+?vq)VSpD zD*CVFoxQ_i{JWg2St|DPqx>c4OK)6Re1q;yP99x}^Ika%Gp9pv7y85Gk6mCo8hcCrC;QEl7wp9Lx>{;SFnwRXq+{Zs+Tf?*L5s>wHIj%s|B!p3^^xBk zkAgqwKSJtN1M#fmcH;Her_k~YITtsRGYDs$re4&*S)&bl7Hh4Y^>r+*H`LI_UidqG zJOkNt$IDIMsVxK9dH21A&%tBlY$qr6BWo=+@?G2t-;6ucZ;;!apSqvEvqm<8CH%Z2 z8+*ooiRbz5)L$7Y;&;KV*3B#UX4+%olVLsnl%D2QUm3$(X9cup=e?Eh`}59!p1+ND zISWnU-vq;GUGOrTxy!r$X8F_jbJ*YGTr*?$ws_7TskPZdO~lUQH{u_~pUThg<@Mla zEv^)MiN7DD&6VPr=UVh3J&W%|+OVHxUoVz*`Vd~<+RYl?5K=F)X7lVB(|6<^LthBl z=gaWZ#;Y3pc6OU`@xIQ_PKLPdX!`l|e;{=>pY8LRXc|9z<=e9JInEsA*>c~h8H?#V zuPnYx&zh+Y`_QuY^O^Kwd29I%`KcGr@Kb}27dr{(^ZXO+)%0~M_Lbb5=$Xs0{J;3u z{x6niY({&4mb#YlUoJM0pW2tYIEQ_pyn*SLUteCHDf9fMSZd91c$u9u^3(K>*j;gI z=N^!?_m94JgLT9!L~`8Ve>{#i3$;;fOS?9|EZ;a_Mw!EDI5rk>_}l6OmE z*bTm+9U|VEp3l2)80(y^`{O?JE!{_3{>`LDRu7`ufv+o`i8(~jo zoO3UKCl|7wzQP&%)Z4dk*7FqTA~)mHMnB*2ZxPRa*@~VzlzWPvH8mL5ff2OV#b(nU z=D*_Fx8Q30Q|Vb3yW@$F`MHGO3%Zw!y)m2iv**k+e85g^djP*JeiOT2RdEiz7muV( zf<2*walfln)cP&tRbyY}emkc$%VUoSWFl>86asX5u3e~R_Q_wlDg#xb>WA6UuXN^F)k->_eVQS98~jnJ0; zr*U16z8!xaUI7#J`5gUf+88;VX#2A_hSZ0em0}DtSE;K9=UR9MeJEa9F1|beP9OcX zPrW>r{v*Cb-`Q&y@-KpU+GPDVfv3b$1G>?2z8y_#2-lR4ZQ3pT8ETj+{qt z#r~pf-|#o!|Npx9owm7H_Uv5T($9o%$1@?%G?3l{vff`PJMZD)ICFEloL0CdEpwDT z{|{}D=f4hjrd=eyDZ4Muo@{Qdb;LjFy9Vud+zv0M%_^&7+GahCVb`TUwL-r*?~~1G znak9|thHn0Rm9Juf6Q;ipUiG6KA1g8n=SAIwCvM*?7i_qe5rOFarXM>t~o%PGvu}7 zAFZ#A+4U+uGq8uzvQGM|{U7jCe^QsGu(!p}nvbE{-^+ieTzseSsGQWq)@iH%S>pM= z=}6lCwEFNG`&vj{&i<;+&i$sY<~puRp&p>1CJGsX6lA703uAEJM?mLn`fHpzS^^jV0H@^n%CpL(kXX(n$ z-~ad5?j-Gd(MHfFia((q=X=xV*^B9$X*P=V2=+a2F60>}(Q+mj4@=npK%OgSyCd28+;Ay>5OmX5_HEYgw_>S3*?+IF z^ISXYBkz^0=kf4^wx@_)OUpZM19n~bRO}yE3x|ju=AQF8CVh;@dzXr}nDv@{lxyU< z@?PCYY+HCA9;P>vpU?Z5=WqGX!L#sEsrY=F=UTvi9L|^X8%~YLIOjPJ6;FN0*}SJ% z>dH`BMW5OG7s|=mIW;4DVmkjncqM$sJ|8cZTgA?v%e;z1@wC&RIlKv5i%-zz5y*LaBrWgs%^+)HLfJVF zUZy{Z?}VJ4GKY7`*_r+~JL^AdJKv?GMqkvkI6s`i{z&X1vGkq&dK&vR_82&eKOC3v z)AX+FIrw_K%J&sJX+Mv>7KXtg_EA3P<=GZ$lfJ)$6X01G>H0m&#T?ILpCV@i$b8(1 zQ%A03SMaXU$dk8n+=h@cCTNgK}cowl1?9{al`FUqe zVt0Wr;YhL7W%G_R-g#$i3M*)PYC8&N-{nl5G3+hxDt&b3XDz)?%eklyJMWFVC?q8WPi_2?sn|;VIBBdUe|XSi`sl;XS?>vJ@pjkfA@^O# zy2$&X8onB*pU2tNtBUJpt>inA&h#MTe?h6(k9p^;6koy5I^GnvraeGA7`m|Y{Yd6* z7~EJX-lG}cx7}a9|Ldu31Gzs!?l=F2&wQ~RL4f zxo>@)h*N{RiZ5cnub-*nJHY<5duZ+0GjZ-~rt7U+Db~dj?XIO&iQkO3r{5^=VE7a! ziMOTi&VC=~v&UHW-RwJ{f&4SI&3GKgKbn0!zRx|ZPy3af_QT!hTXMgn&)_#^H;0Vj zhVmY&^4Sw#B6cYIRQ7I=YyMlCtka3GC8R#~!Fle*@UDB>NSo~UxA+VB3)s7`ThSNr zm+&)2*{e0^ng7B3S{1ff9mu)=6}%oiQnH?LYD4x}_HxGaJX-eB-jKCi!Z%gi2mAkP z@Ll@ZOkTdX8i_xbbKd{r`FApM&HT5V{wrJzXVEkEXR@=$uad z442;%%Neza{Cc!~AY(il4urdmN!HPR+O)0|wP-Z^Iepzhe_Y##v^k%4GUVO%20vqS zFZ7c0gq$td53|FI;6M7!obJaSg+D46=gdJk>u7gqQPEyJ@5-m4m40p!uRz`{jpej~ z8~G>jm&*Bty#b_lZ;Ly_HzngIK9`-nm$kJe^ws|we5Ks1wLSP(^1p&j*pF#50dFSu zJRHu>JF-_*@m)~9e;Y&_Lfc>cJC&aPbFNrHZ-M{j->RQe`E{Uwsn{bE*?G3yfA(jd zw=usq{u7dW8@(obh_TvKAK6zs(^5~bcI|n1cQ{PWze?r~AIML=-xiWPOx|N+OCbB} zUj00bGlqTSG{Bwk-HE|$XsAv4=)>N#J?GC)_^B^toX;%p>1$W9)%>ilW7tQ)85MIP-V{#`myNUg z%wEYjez?N8KtFa3vCr5Q+)-{mGwr~>5;9l0UdH1vc9~X9AN#iDl@yK`^6u^saJ>LbL38AUq{~uzYNFFGq?F{mwD;U&zd~T_-}|)%dr?tT?E5&zdsfEAMvOf3555m6e`|(C{hKv7GQgiT1d<6Y^@#oo@ z*Sh?v>?>$n^Rs65gPbX|mfxgrfiq{zNzyEp?!lcKj+U|Ao1HPuTFHK#MNi$Y zB{ysC4_XQ5`q#7b4w~B2I|UEq|AaFSbD^$w*YNj{Ka%|{dm{bJp2cUVbN`ojsaWRh zME+hSYXqX@Z1fW?wP!Z!_n~&F`SS zBX_R6?8n^S$*`Q(7#}S+_m}taI^z9cK0D(%&vjei7W{5yIAfu{HZwS;XG^pJ6&Gh z?_aCwSx;m5hlr*2W&A&4XH8~4K4w2a{}0}iR)VSY$K_Swo?>h8Pk61Ihv=*M2k3j7 zs$#8Yyf%jOX{+&nZQ>R!A}pb9M*Zpk4OfMslV?PyP$~X(Ok%@!nCa1}*pV zuKeux)SUyx8`4jK@8st_(F}5py=b4ihgNcyXqUa#0jG{$hqvG#t&gVSIdfdYzMP$F zW*=QIzK`5*#SRtw4`gkAz~7y|g56wB)@o}1y7*dcyK37?EcGO7q8dBb|Ad~LOXTh; z_82?YKT}@~`Ge^r_@}Yg#ZS_o^xaD4{!0Fv6wliK0IJwolVfpeOy0*iH|!;M zJiH*58d7FY#M!Ia|5=}B@V7Sq`Mbrem)+=R;?%7t#g2fS*>2?@&)yeLa8IX}i+z$h z{}FvT&YtY-=Rm9E=lzxEy_J48yPkG)=p$&4!+ctWzdx?SAB1c9UZ-1?&t%$s!v7qm z%Nd3Hl=bC4>xgd!Eos^Bz46ZS^M1%U?h9L%jXmV7e=+`@c7~iiaBKdn(21XZKBQgB z&)&EJvd_+-w}tij9r!ia*}okIS;zfe!-u>;!^QF`=zw(`RDi-LiXF;v>VIC z=g-FMti|#0m{{t|F6_KVcB$xJUK2bSPNwBN{t)eT_FcV-{a1sXv&kfSzIz>tUx8+{ zcW}n-0q6vaAosT;bY@R*|3l$D{#B6YJc*vMNZvm%mY*8C5p6GiXS^66r(FZQH)PCa z(tpGwA?>p_HiXggaz1NU@f`fkaK#QV04?w2b#?{1`M@;kxjaSZh20a=kzJ zwb-d+?e&rId7YlR{t*9ZvFyQD@h|1~VK0Fb>3i_kmy_R}%v?Rd zZ^l0ZkHSMCb>d&RiTFVkwO2pc=WQW%ri0i5c4~LV>lS%`&~K;J!Ryj@iq^9j!_v~y_ zAJhJ$kA2jP<@hiDNcuPW+kxE~+L!D*ZHChF`~$Hd(vOfZkn90^!W{bTkK&x z2WO0{(PK}Car{*{XQ|ojf3)qQkBitDw@qOgY_IS47bG_?u=65)IQ+Cs;q8?0R=gf2oy@&X#{M4`a_={jA|9t*>?96p)+Rn5G z;81o2=N;ArvVYg3eP}!e(SL^A|NZ<^=&$j!zsBNC=sCM&Juhgt?1wD>u|iE zyo}9EoPM8Wr_VOflm9Y5&$|hH#J*9x-SJs)2z&*Hm(?)bH!N?lYpst@=V#wNs!j8% zqHgDNTCTsj_%9C^-y^N!A1ZI0SZdD2uvnWb`G>KO7Wmm0)UVdtB_RJvd|IHstKTv!_+?alz_&nF&UHoA25{^@ zi|1^Z^Hv|+R{U!AT>6X9Th4g;#yGK@>w2&chug&Su9;8E=a1jSx1)c6OYo>z&Q)LH zO>yd2*5Y|M`!W9xzz}{XvFdmmc#wWAOp>z(#)`FoHe&Um4=w#Sg_BFgJioziBz7gv zdO2@JaRzV0ewmg&AHiefw4{COe)I19f|fnM8Ge?Zy53}^+J_I3JF!%}(=X;{on&8) zqo2T^0vV6Y!%?)1#b4~~*Av-q;VtyN$}-|d+%WuFbZ%YA@$F>J@4<{q-%8u9NFZ;OxRpC~qt zy$|GmM&sQ31MKvg^?M;>n)l4)L|@eza@E|K_)6Z&_9BkKFq`&`eJ5;R$U%@?5DY*$=bnU(h>o^ab)7vk$ED83U*8=d*sk$NKNQ z%V|JM&Dw!JQGOeK=6iprExrvtl$Q6=66h{3V{2&pSO^Hb9|(NErQ18BYZGvtrM z`||UB$Wi*snJ}NJ2HLkBuQtr*n^*U(P+F5RHvt2t;?>Bf754c z_9Z2A2B}?no|oA7(x>BH_ygrl!qcEzRq;;$n)V8NpnJJkZfauQ$De6)4?Z87(3kT^ zuTXQ@d5#D9SHNwwFXUwXruKI8nd=?)OSGpT@0%m&+iG(i-Uz<*Y%?mwx;%P?x&(db z_rc(@vn6Ey&%n97sO@U8)UC6%e^GpW zoOSTE>#yY>B%b@v`KPtK*Y)!cJA3pBoPXnX4LkcUXY0I+vtKsXeh{82mKxDV?h~|= zY3<+zTCTG(jH>$IoX|Gle=M)D{F?Lw`FpYdfSK%TXw})DtuUr|Tl{KCZD2pF-Er&& zaXULC}5#r}>x*}UcZrsLo` z_I%o2V#E2DvzxLf;f&cf?EIbj$Z~O3OYPXAVh$nyAIDn!?5)SNJxyQPPwR+fU#9Nm z@BdHXuaHxp_8cVle*WP&&-?=|&)kRoB|ZD<6UbO!3m3EV+{U25GKIeCaEvoEU{u$b3Ki|Orn13t#Hpu^1WoLXTeH#736~!5Q9=nhB z*&lC+Z-ak=+{epu_R`;m^o(uR&tH(wOqu)C+SJ%B<^97?eeTB3*d7am;COm|_H_>X zTG*I>pS-%X6WBG4Y1Zkr_Ll{@^arf$5yAM zcKx95WjJTN_r>xqZ^NDpC&_6JZQ(9$`qTD|k29XHz%TT3Av~0x^*tUMvUkF@<@duK z`IqCYr_`O(+2c#a_c>V)nbTv$TEZ**)VYjXgBA9n{EW#0{t}$e;n~l(uul@Zn*A!| z`8F^%2Ws0^&Mxlh069%z!2fcVvo{kzSZ>zt1@g}jdySsYZJls`I2<+?|Gix7%e23j zb|K`Pm9@SJ^nzMqsUg;G=R;cdwQ+5+>8j@Hzc1ocGSwI5l!J zbJ9hd^WD!RdS7<*7wn&@{Cn70KilbNdwzbuJL|j^T%}F+=fF~NpI^xPkl#-qxt9mv zG{_oW1F1jl#pu(Len>HE$mb6psg&Sy1#Fp{Dg)Ja8@&kF@*s1sDLhrJ=z#oUx z=XLDVnCti>jPtf~?qNSpdmazRSx@Qj6nM_Px0jRW_y8WFEybCu={WCz(XN?wl=XFz zc>MnS-S`LdGbdTM55h41kC5NzY~=d&X`SS@uDEAYV)%PGPhep?sGrf z5L(dN;XCAJe9q@r_?z-aK;~c=zYlI`J-mQ>@ayY0HTr1zJIc$m?1IP8-o@YG#-3#s z&U~b9<+)!lzi+cQ7t4CTN-XQMqcKa}y;H0mzCrG0VqeJngO;(Y0dKHVZ)(Af?5w}s z@6)gm|8?9*-s$=n!C%53$xpo<3=`olZLXtzDK?;D53_SFc{RFtYG-~w?gai}a$528 z*(GP=8seYPR`K_7&)wK*KV8l(w14>R_}}7;Mdqh7jDw8Ff8ROm*(GOC*U0#82#d76 zm!9VzgR?%bhIRFIEnW#TpiQ}WzKrXpuKBcf2eLD74~u1A^>&^8v`d})+BGs(+0(m= z<-OIKpU;(_x&PF*Z-mXlicwRO>q z-xc!r|8MFmYxZJjR#6-ALYP-l$N70@ZR8qNa5p=-Srd8pzKc)Bjo~2rR>`L|fI0m4 zA?v+4?B(9h5zm;tsjmlI_ZD{c+l%6l>0=r{?|^4orE%g?An4f3AlihVi@w;lx z@H@0;@Y(dW?8o6X*O-s1Rm>L*fk*k-OFtXqJ^5|fN8%^w9Up2a)55xot+CFHD~HL;z%-L&bi?Fa0U$0Gd15)A<^1q9{cQSu^v>*z*BYGpe+_aL7^c7V==rxr+RM%FU~P?_MF%fdNp~E;5G0T`(k_-&U_v$x1qcl z>@}XTns%K_#pj@$A@ZE}uPB};`J-Si*CAsPdMXVkKG9m zPah@s0eP<-($g5yeui!2q=sEc|3rKaEp;tt;?HPr@mtV4REo2B3Acdkn~lU%|8r)j z$)6|p@Ty|3F5<5v_NJV{v~TcZw7Il(@hLD2vhSZOdoMtqySLow?7UwG>8}x<0N2P( zy?Rrf8N+{-ze4;*+IW0A9zg${ej8+eW}ddAuLu3azH{y2wA0x68}O{}^~H{-{fpL~ zeGfa>N9n~$Fn7tH#L$Ad?Lhca$M0WP{(<_Rz zYvU?wQ|wB-KkXd(+25@p>m}FBUh2p`Qfx$}IKO1Pdh)aXPsIm_|0;Jiy(RRf)za<& zcEr{z$*;vv+_a#p**R_DV?odrN#5 z_8Ql&hxf-n>31;uF37*JHkY_`))OR zWR^DBdpAOA#B}^FWDo8Hx#y?!|0Qit@qF&=Mcap+>)sAE_>=jWlihGD?MBPbcZF>s zH8^#s0=tM`OWOdSkeqTcCL8io4+d52W5~NbdvCRIJ4Gz>|BU>n;7xgF@h=v?lwBqF zRd()wE&pU#TPohqm*A|2rT7HbJzcCne=mF@ZJBlluv0_tVc*3*i2b;}wlKaI;1&FR z=oy0xEk_NJ|Yv}*^Mr%&k9^pTpE z^*mSnXUP8VES@#;wb+^mi?h%yTE=<-Tuje;`9S;3-EVMtxtP~cxG{WBOMUACS$9`j z3q#mtcGhvm^nKi4A9>e*6Pi!rIMX5Yzv6;i9dgF8ybQGZ|HfAO<+zSp)hev-z&e=F)o_n8!&(u%q_D)rvUHmxywd{PKn>F>7{$A}-oGqrZGp1wV zU2Ri~C&^jD&X{Da4At&@@eb^nve2-MMf~-} zbAN5{N%R`@`*G@RYq`1Kd=Hm<=prxAl^U@Xq-_teg~snnv5aZPa}&=o6ppDXzE7wl z?}Y#5cau|tejVNs?`vFh))^pwI4$3oZ_58cKdayn+O`$+~0T>QY@eLc6oYzoF-x(w*I#y(N2nC~LPft%bb2SI@&!#mC^c)v#OG zwb|RjAbC0eH|$x|wQ>9g^fU2A&=`I$7d80b?8BL*Q+F%r%EU%c&)2 znEtX(^SoKVW7)&l6WNc*sgJW)vsZdR#;ReZIMbb7F4pRLp7{!SoweHqUXjyBY#Pox zsv$djbz^>&yxd1)c?a{`X}cdDPtO@B`#{cpE#Dv2 zp?wN#*;ym0VcAnzV=w4)1kQPAW1RPQuTpU)NqyTNE)c6Pma~7ZoAvk(tXr{PX?L=- z-m+)g;~i*owK;=bNB&c|Jx<$?thGO5z&lF`Q<|m$Ywlkc--vEE(I(d$xU^V~6l|@ba(RE*>P1d%f+hbSMzW5@%iuNdEEFQ+Q=!fFA{2uHd*%f(z;?WgnHrjLidi>7p)Su(om%>T>^&xYY zv(J3?5S%&w1V2H)iv2adgP+#NbSkHx)Edyv);$kN6F8#SHr342ginEOV+2{ zCiF%8mUsf*sHgXzSUztJ=V#p=3DqDq`&IED=y&on#;vt$MW4p5hqK;JgtOTtu{QE= z#edPp$+=sDS<{wA9m9c$&QJ z@pSr2>?M%e(G{}CMnj&r1P5u8I+A-?+7MO#k=(@{H?sy z!drai$UCNi6S( z{*dpr_Qn~H;q-0UkIAcFu@~_Z^qDYTUe1mgi^25o<>q~r^)UxuhU0IAJIY(`+Ic@@ z4QIU0lHWos^U#sjL(UxjHDZm}sR7xaztXD1aQ17E`O6yZg)>gmX&2#lacakU6?4RI z05{Td7QTy~x!HsNJ!CG2!jWP({ZHG0o$;)XGcMI}u6b~!I0L;ewm5NG>hyF-?b_G9 z?7_|$)#Y!1o8w&b6ZVr(B|ZuFht!DIAa&^*G?flOc1R_harWKQGOi>n#6e zXwN=PEc3Yo&VCw8&$DK($FLv7sV%4AtnYzxhqJe2N6)?Qs9kSeLn%)iPz@mK5k=YFO~3K{L$>T_`<4UoN|AsLTYLH znF5)k=J1`d*wuTdDQzz~cj8(2KkxxNYp9*~OxEBb?A-VDIO`|tsv7@LxU1y5Qrcno zYMgmm0j>3a8@_Kv@!dy1JXE{d4EGxf+5yz!skQ3Gu-t`w5Sd z-$5VM#2#S3tzFjH1o6~{j_?;h=hH3O*=NJ}>&QQ-V!g9xL)K*WRR_GESn6Er%eHdP zVyEpH_%*qwyN6u=7ub(JAE%bpDi`nj$F*w!UFaFp8|eMv6@L2N8rs6CWotrzSrbRY zRr#q9idg8T>A*T8Vd z_@##AOg6ziRu}*8GtdXJSGi6D?K|Ra%WAf_r{0gSLNm$v5fx^|8%^Oyxr-wA#=C~d_>RBGM<$Cp1h4`1 zY0$jkd; zQ}L~FE&g&?$<7*|P%hTm?f5X*2^y7(w%g;T{28!;{H6S6IO}-NO0mB4JDcB#KSayg zn&EzDLLc$})-zlKUYBz&-U`~V*O!xdp2FTzyAe3G=@xqSWNOqdJqvx9Ypj)%G0E9? zuz1Gta5?kB^gb|H&Y%3v@jW>A`v-Jo_mg{&b|3Kf!9U|VIJMvmef|tP%Bf4s{pWde zKG{dx%+HDP8{&ibPqJ6Ql~7OK@$?n+wfZR2)?**3k6ddL$Xa?EH-vG{wnO<_v8&{7 zfWMWS@yvYY`dPbo(rd|kgFTU!eSSXeOn%-EZ$lLf)+T%Ad$=0ri+3s+Yi)AxC-ro0 zf%OApiCpu)#`tsA~madm)&03uVbK$a*^&<9}KK9V(U2^uPH=!Lz z+Zks)&ZgbWehyDxUff%1+&F&rdwxglQ~od7cg3kUognAV8Ibo(-nTD^&D7>-NdJBK zQeT{llVIP1KwNS>G(^2U8u(1S8g|WRNK+~4*1_tLd^EaLu$3L{q z&u|Z<9nEiyvyUg!Rznm1Rk(}TQ}}Rx)=loAM%gvs3mD1YXNCEemvyrr|4}>{QrkMi z1MC+dYxYC9m3=9`2#ylJ23oQQRN0rVmFL|?&MaC!Cp5>o{w=iZ*Qe#=v&&!XHm;qT zzChkc_AtCFznj?9s^YwV7d!K~y?AQGjgZ=Q3OtcE%RMthYXN!oKOt-Jv$FaRyR(0h zTTA~rSLFHgewf8h|C#rkN7liq^LZx@5qnYICbaDP>FjmrInV9{XVWrnKhkqv%lvF3 zJ{q@yeIf7Hugb-_@@HI!wx8Ty?4|5{&e=fDAbxv({_Y_6k$rp`E$b!w`V;>BJ&XF> zSeqeo7ScWzZ_hrIwggXr$KgbNo~v%J;*3z@KP~rn+I;o|e4M;ZRu*++L)=3=^D~qF zi=4M`?&Wa$DE|4-3SOmU4K(om-U@ccBYX2-?&k&eo9^>kT${EJd#s%7^~^=~WZtVk z@h_$~b=_<6i?m_l)#(}Ye1<-RmUYz@r-tQOZo{cZBWcg$e*CPhtj|aAb$G68G?Uvw zJkOgNz8RDu&wGH_WAf6+Kv)L*SJX3j+B)jZPd&I1y2IZ3xHkQ;OWF;?CqPd)m)?N3 z7%qsd{~e(p|K=+73{Ry`W3PtnjVZ7JJ^hTqUGU}Z`R*!f-@Vn;KA(sC(lb|iH_e7; z_!)~^U=sgse#UzYq+VUGuLbmt*`4)qJbo8)-EHxP;#adTXXjeuiA zw{#hYw0lJS8}=0Ed&Ak>D;-`C>%itiSm-Kff%!N2m)f+75@%hZe( z{Nq;^HLD38uFqMt?sq_a+_9nlCzvg#j-!GQB^D&;mAAzrd z+~ZV!7pNzHkUp~xv(|D>N}WjmuW6gPY9*F=-kP1~If1^d_+tKg+U6XZd#^)JUHJ*W z%s-aiAF>7?F=tuFAL8uWM$pXI&!L@Qd|%*CWar}@+t#VC#J?$CxeDSsHXKCN#@zzF7 zdQ;DkI&>R5_uAU^*QISw%Q&Z>$Jq0<`%!!(-bBt1`YX|!L09>g%G;0rt9Tt+-r2k3 zjl}Z#FVA`>d;~XDnJehTZVOLBUFcgXKCfO@(?~@<-x9coaTaUfzqZ@F&R0|0nSddVBVKdipwp{RP~||C>KoZVUc6W3m~4SNH;E z(r4i(tL#6y)z}@Aqt86wOX4?(CHF+gy}u1t!_dU=7-#|`s)}o_($6w|{3)K=a4#N0 zOFg_US{xBz`_`O@h?yJi`=vHT`WTuPb{`_NDSRg&*Y{fPWXe8L#JlGWQMT z{K?K4>IFQ2){M5NoUybIa81Y=KfmMhfOup2d2qA*rT9BY`?JMPglk|fY%Bg3-VLg2 zyG<{BL2AfhFs@RZ3C?9_ez(F4;YhfV-vhr2Z|eIq*oL-A$zI3X(!1fe_*?1odhtK_ zzd_#P&BT|weh+?gc2{UGH~Vxoo`(C-hQkQ?9q>o+UZr?{E|aq>|9Ae2?7#Gtb>83j z_v5$48JDcJ?1{`<&a_?Rol;g4dlq%~4f@V7L(Zf0ti#m4tn<7-?v{T(&Ys?nc9r;Y zc^lB~5F1B(k9Lgfeag>g=E?j=p-P*(w*~EW{(X2d zEP^Wd$@Mm*52*5f(xx?J4W~wrr5{T_j{Q7sjr_K>D`~&+kHz4v#$8=+YrGj` z?6QvA;wJL{t)D001^)if8nP~T(&yVa@1&8mt}Bbr=AW_utBIwbo%l=TJkCB5x55K^ z7U!-b*q706lQV_g5;Bi@#zXN*_$T*v9(^kRO#W!Ni{A;dUxSveoipLbVpZ~L!O!A9 z;74g$ul3z;>d6p(K3BX9Imf*qb`z|m*Q8y9r@~%vH(X4Me|1%{@9x#li|iV-jOn_b zWeocd_9%QmJTGS+|3XNO89~eShO@h|>#%3wtnb-!Ge$d!O)h!YvR{K%FrRiM9!XCf zEaTcM^{;)-VztE|$2pf=h%dvhKpQ#tiZyj_9qH|8$vup{7H?cq!=M{|2mbeXU9n~C z{%||wp0f|%7rRyNJ2=AifFzGr2hv9>~ug%xClQ;(LqLqWuC{ zcRPxmM9Y}G>OR-UpV9Bqwk5j;?8?tx%=2vt+ww>9PnUnZysVpdA$xB>$at4v8swhx zE@&g~H*LS;=l&j&74b9lwu~UEdlbgBToVK34oGq`%lVN?RC+8cn%i+wb zVtmKazvQ>!563O}nX_HW#d+mx+?KW{43l#r+(gSYUx1(4`QAQzVPAe-$h+!!e$9fVMZMz5B02+t_{WrvLHs7UKKxr?{V-Q^fP`%i8JWern+( zVR)5#s;`;2nY_GTn!D%o_}k;Wn{(z^qwTM>v6cUQU&9{^IVbEQ?%+#mm;?!}({x4hg>*46L&tSz2B+7+Ioe+u`?e-v*`TP?3Q zdkgmExFIx$llV*U{xDwt=kk7IA7GraUiwt5F*t^OKHd^%uItFlzZsG9!pHQS5!coJ zCHGQO{36^4uLHY6-lh4xGmo9`MYEpvkUvBF_V^RX=bWLi2qr+@A35_qFSmzwJC%#? zpx&VG$v#*=r{dmnYKb2wZxU_{hp~I%pXB}v|0Z{QPy2~|qgXS@n##Pkfy`;v;Fj_$ z`b<4ejmi3}gR}p(bDjIeZ!k79#Cy_P>-Q!04fK9;X2DbZclnpG=gL_Z$L<9A&MDV@ zT~0eW)7cxt!D0(=_QMo*YR1Mm`@I@eDrzRaPdxLxd9R}Gm-Ul5x)<^ck9o$_kGA5E z;?&R^;70Mq;<<Fv8R@c-)Y#2pJ!-G`x8&0_goJ8{+YHI{-PZwmc4P6zV^U-Lw(xj z_!9Bz?5{mv$+P9&oBt1+&@;vZp}*g=zllCkA8+x`lfSRFYvEOX)=+BG29O#uT3(}y zz4VAWg7coJf*E4h^Yi}+TZQKsn@eejiKX_o(Jt%oD}K)6JJW{BtMW`Ie>ZBfbM1^_<}Pb$DLbDHGk$w%{{XEndo*qVx640DPU=stIg4Gr zS5YU&vv<%oH6k^B0M1(O#hz_lr4D=z)8u8X{|nA78DIWhiLvutS3m7etFi}sItSCT zPQQSSXx;cLqk2Zw&lN4D()-Sn8lvvI!Cj! z?+=0&?jdXI9MAQXSk`|PJ@+{FnVeumSY*X|CSz4S4jyuv)fF6`BC zFMKCwXXvcWH2zK%dk9juG6&hWqs7LR#dr4$dlv7he9lkJyG8#Q z!y!<1&4qB6HoK{918~MH_ccuX0i1i!*xU~1vFE^W_FBky>oedS_Pp-JJ0bh)k*Z?; z7Hj_nY)Si;ma)(HUJ2*GUSdnNAIyG?eQm{jvsbb6J{<;GW4qH*fAXEu7M^1yyBh5e z_*hPIvj!&dHx_#wH-+kubNU3@sqB~7S<9(i2eFsKB7SfEzCc^5@7wVd+K2cL+z;0; z7yG##J7=PB2f2gkHQ6Kak+@-{I7eM1C(r(&dCT=4)PL6I4(zqKo!HIz68b8+xn@4| zUnicmn0>buzO85RGtBGx3*kfBbop6-`Oe^d{t)P5?4PI4rN4}CqRlB6_q{K>LO;Z{ zX0Q*3*>YAuuJxMOJZQjv6Hlez1MfiI0e3ze;L&KkK}-dcU%ic^EH zs&d}L4RH@SP4RN=ZeqU#IfFa`*#j5I+Zs272We~IV0f4QwRZWAZ$mtvHj7q)&H0Pr zME3T0Tb%nTvsd62CGRBnGn+k+Jr?eS>iXJ^Hl|FI`;r=veV*rN$?pj>pnat{=WoT% z96rfExl}w?{ym!0;8*@YXv}V--+Y!zPTJfE8)$O`z7)@dyq7xiD|jDh&psDYk6TxY zz4DOu{rMU9&mi^dbg@pfar`UG>I%O(zXSXWw&3T!_k^jQ`Tm~8cXCbW2heJ`ufK4< zqkK&J&g_g&ZTb@YApd@-TX9XCIjhD#P3#!F8fSkWOFt3P=NGOs5nl_bGdqbl!>jm9 zD@Fad9G^wc+DYB{iM=0t19r!&0zo2KHe&!#J|GT%?znPzw{9D+W!&-Q= zO0izDZl8jjx9`SVx&FASV*j7O|ARdg4se|=csRTVdA6PLXnJbd?)=o14a-G6$}?@p zKV83<;n#7-rXy}7b{~Eoe`^eOX5YYGg!`6@cgY-nYVbRiqV8=&zXh&^ed*WHA7=N$ z>H99oo}3RkAM9d$?(y8Y)=4nZbvDI&YxA*t&iA)_%e@4$h9^KRZSD};PR>XAI6y3C zfz0oD#^n(_oc1;C0r|OyR?vxkKeUB5@NmVv!z1jKFkGMc9^f=X=k?rUU;f2#P^qYO)!o}d@r7c4dagIve~aaNl{azL^l7d$fS$dytG1iMAM{D^ zshr37+e7AQBrK^a&IE7cmi!;wd)6(hitpL`(B`mjgcj_Pa*t;ZHfGE4hL0BSmUqR{*9K5W>}Rp;rPPht>?ZoW z31_|DppTjEX#x8P$l6^F!{ls?>p;CK=U;vouGs?D!V_`UdG`15d$7eVvZG z%lo=wJhV9mpU&?h{}=Xcc%Nuh);9Znu~qyP`~%tdvA1An{xe3i_|N>Wp9cCb(?`%Y zm-7~640HW;Ap77BZT`D<@p%>JExe7`eD-vFF+42qOa6GVgY;eB^Niu=x^3~kkU5%! zJ6ZpUZ(c6GXE<0+H|=KeKhrjI(gQz$zoY-H&0PL<_&J>M``I|3hilLdgss^};}ffj zu^%mOCVw*hHumP$c;08DAospM{UGt=jACDlU&al^_JHB+tf5)jrcHmyKEJ5|_(!n2!*-B+cmnsMpTr&rkClr$*uz@+g1-?|@8ujve^OqPN^zEMOUw9Y zyq6fGA?!Y4JK=dawJYBx<(-{%`L5^4xKv}mB)$r=UpK(-y4LIE;&XWZE#r*U6gUZ9 z(dNJ^*IZdV%Qx~C>hovXP3*?(wen7scNzZ`_Mu{Tvge9rzB7JxdlhS-o>&v;Pyb^@ z@fj}Xl$-fm!Ci1FRs5WR)8|>r}1mx)%~`jF2dS$F&4%;&9em2no#wBHSm;@=>? zo;K6j?PysKxxX`EF!Y6P@GY#ZD$dE{Jxj*-JlGd9cXPB&&Hsp<{HyU&e$ECNvoW;s zkk4GHXZzC*!5Nb;mv943}nr{FFsoT z)hoq%y*c^pXK>DgyOfIWdSd4@#?JaVU3`_cxmMQF^Y|zHvz*lQ9p%*1=cer1V%_B4 zfm=c9KqEK-_7u<0;quH^RT(RM82%G7uN&jlCGVMvwaecbuYqX8#j-9pH?9rXIZM9J z9|w7^%_05fcYs#A?+@vF&>qls1^si#UVj*#gM(mq=nCm0ZJ(<$KkN_j5PoXlFVRay zEzL9KT0?MM*O|*cn!Pp78Q?;B58%|LJa^tpzqpsB>^%Ru^t!av$G!P);q1Q+=p*Rg zv#((vM!ynzvJb|;TB93#-kI!i?0na>TJHVgz01Y=JeNPiJ=LYDV}Fv{U1Rai?jDj zkiDMg`3!$g--KNYx5INP#aUq`&VG5aRMdyeZB4kEpZVL1o#%Z~A0ydY^zyv&>$#6n zv?})b;z!EQe(lFzgtwP>I4yIZ�?#1G%5zJ8&(1=R5g~|L^=2RmJ)D51eyb?qNH6 z<|y@{H9PY=fPD(>Mf|BTn?~P|pXXg)yN>(^Gyk+Uu95Fv@^iGlv>CM4rQ*BVFX(r> zelLAyoy>=K5O#hZ zmbKQJeY`&Mp2^u~hI=`xwbb(32MXU*(e@gCq` z#{Ny-4tP1O%$~r`H8b9+4~IbR>%VRAaQGK=p$~GsXW0E+GwVCgbv(}g>J7Q}X3(}& z)cm*EFTh2WV*kvPUx&Ss_`3AevS-;_uyftoc&a&?OiQ2RXno}T3J3H5cHQkfe+~Yv z{N$g_ep~)(b~9~jvOCg7!7j9={Aup{e*9LsIG6NccNL!rd4}ucjb+y`{&}D80uA~9 z$m_w*etS~hKwKNY;XYnsZ!2D_s;J$)a1*&t;H%|a1gVK{@UsSTtvT%Vda3D<{dEJ> zDHZjjz1VJYo;26{vQzs`63ba&4SQRh=iNk3OV|@$av%9#Ir}){mAyO+w}aHf;W+E^ z0Qic(yZA$JO{I7@4Wz#XIkRQm)?@d8dGMF~sojg8m0u^e1RkbU6FUm7fZO3rxCM^T zZ{~g`WL>2WFJoT`lVB)pNgD?bRf_ZJINDM;g|-zf<2DH&=b5gg)n#YQX5d%&x4`fG zhwx-PxokbNr{LZA8Mo#9mexqVk&gXo9qdolkh$ozdpTPgMvdmhfUa^1{%d)lS0^_Y3xo3^Z*Js~Hx zuRrt@pN&7Ht;Bu#E2_k`nU4FyELgu%+(YJJ8~y;?1uudVwEY|JKsy$%gZsf0cEc6c zIHZrcwA6{@r>;viB*pnNwGiR&xle2R6e(Jy?xP$#YJZgMWSAJ$^E%d>u>yO~h{H*P) z*S+-fJKRRgIJ}LY;-AXiK+b6WJWkt{_EF-Hb7RKhVmw$Z&zd=(CZ`FzA+08VFnbI> z9)4lx9a$<08LtBP7Yi8hPY(mm$h zvgcAmK4vduZ$wY+oXuWH+Zi8@vp2FHug0tRb==QLc02Z3`f|^BGyl_)S_aqaV-$Y` zY%Hg?e$#e5beBJbHmjF0V$We`EjQ6;_D{z2M#xy4jXU9dhu9t8Ej|wCcZ+s%ji%z? z!PE3yGd~M>SbRdscR2Lyvv%xP@MiR}ko}vpbaL~1NE6xlH((aJ#su+3Ro0)J)Rr-{ zw9jY9tohy8tHq|`KOC~R@*c~% zYqq@e@Cp2xm7=cYd&~=I`MrXHv>n;oh-Y3O7JI6!Jv-|+W40Xf4BPUb#aR>8+0`KX z-~?J~?^!rCG4_e>;ZOGW@KISGV%NcT{0aOiu9NSMdeJhU*$;oyhv4krO<@sxCT$J- zES$d1g1pDFj&Fhr|5!W^^8V?77s;*5PQQD~ok-gP=bXL2*lGNHub_ zq7nvJC?ec*F;VR9ZhdWEJHFp|_y6;G#xv#|bFQ`bUNP4`=U%QS%4>n&cfDFT=jm|y zhsrsgUk4_zKfw2E-&wphzkz<{$Q#9fMXWXd1^$gjG zK#9M(K5`Go^IylmxXym;HRSacdraHw>9^9d-m-=piS=h^4gUc@$a&B+3&PvaR`@$j^EPwy9x`nfb^6a^vtmhu^ zB+P^_OU2I!@_v?Uompdy^t~8oPVx@FqN=!;O=%bDD>ZEsv5fc8?9|zB*!ga6OaAVV z_1T=ZiCE5@`LXHeTvdTp7|xYQ{Z{_e!eTI#lA@1LHK^_{Sy2vKl*NXGQP>Z=5y6f?Cj?TxD!;B zj34eOmUX!?dy4o#oVwDZU-8}268DsSJ6wL&T}3QsZSMVTcFwT;o58o_9l-toat7Xs zvlh>Uo8+ZVHG{W2)2p^8ylBjRg}%FZo;iE?J=$>b+HeIu^>1(XV6p4n%OmVj z(2akBHV4yYu(Lk)hYGCpJSSOmYR^{UJ;{y6pc6%7$8jW#k!iP1kfm`Y; zd#8#1x8OHrkHnwSvu_T7{Fn1I^O=6bbLqA5mioL3AI|@n-ISg0%Kp$_>ZkMxfgz>PK zd*0La{uRsm&G=_OCU;@ESPu*N`40L)obxF^Xa7LEo88yNw2brlxQuhHMKDDBz4-Ua zPrKCUymQ@6pQK&J_5-oa<&1;=a*m?^Mmt(;7XKsu`uz3z>+&-{9oV&KYvOj0cbD1x ztepeIduX$o|10D@p#uyq8AE(smAx$f4f{&DSu6RsA~VGw<8KE;YkWT8KSD3FpVDTA zyo1G>@YfSN9{0z?AZtCJQSOD@Q`+SIH`M+yb?Of7%9UcB_S07b{=4Ei`~Cx)^7j?L zi@mG1ukureyFte81ln@E4X%ez#F^uM_(S|O+yPIxUY<4jTIJ&Xe}C7@x;h!2my`9{ zUF&u;3s1(^(%P}-LB7j6Sl|C>ld;}Q-+w~JEobiIko9o_9th2Aiuc0*-=2L6Occu;Zb&-;dTO_fzpvO} z_Jg$9(cR-fdd9x5*t6`cg$6a|NG#8j`m`Bjy#B(|@GQBl#nyq&{H)Vle*k<0d9MDR zp*6b>dx-dYuDf=nsFl~?ci>9;DfIjfMBXQA$*t?U8{+)kjK}2UtU4At(0bwNxHf$W zWIoTRDPsBCI}eMemfuBh3LD9t77f3Eccb+dAH#p0eJgD_q@G?&%l!qv(;D)>H3HXqk^(_bgnSmi>}?{IPyACK;=7wB%%eHWb?!*5fzicdQs+{H$6M)qI$W@rg%n>CVk z8cyB0GYry%3LuYT6X=fgZXQ{gaLVmYre2K(dG?Gf;fc2%$@+#t3U zY$-lm>;?7+cJ5~)zO>|-;XT;J80<}3L*A|8t7)0*!{EP=_l=B6_U}>hUZ-uw-;Vu* z*nj9Zus5Z>&yJSA>vXid%*g=S`Z)d0bgicF9s7IZy*2$h@vi-?1$qzu7_qM9;=8%n z=fE5}OCjSvgg*i2tXY8HcAou9OKrRxrm{<7_rvei#k~jHo zOMHTsZBOwI z{QKZsc4x?1ou$SH9#&{xbe%koTl#A^Y)2W3*U)YxdcAa=G~ZsjU0Fd-dSg)&2wc7>3gt({k@e z^2hU!z@PKC!I{%{*{N5Tx<1*qxSV&wAZd{xEzo zUdy%mvzLn3g+19b@l@EjQhW!J+ynXTamFcQFo&J*zn_FfwBu{k9@;SWDo9>aCtdp>{u=O;J&@lS|It0}D}IpNJMlto--hhlANUtSwb*Mg%e6CC zyJ=s;-y9EsmHK^!-iE!TWWAe*{61jD@l1NY8`(&#R@rA%+OarwI@h|2o;v%9yxU;7 zzSiY0V24xl#^ML~%S+}$U+3f7@d@y>c!*L7Tx&%{W`EKdi&w335iI=A5dJJmZ%*>nM9PHLi@aKhDA5ce+NZvrp^x@B>uo!^XYo@% z?&PON+`vvBng6-8@$CBSDtJzR>F0W!@y;Bzf~(~`M1O|eSDV57)aGrR@%8ZC{QKy= z@S1Wy!`JfD_d7VhSN|9M#Lj-6ssF62=lCn>2e7lAGcFw=^U9@X}k+P-^skq{suC(xu<;Z)34gKA?xi&`45zf zS~6eW4fIa5oG+Wwr?K;V9mVdG|1EwV9)_dndA<&K6Ze_AI6}^a+P=olobCmiLGHT_ zEj6S$q-OtGD!%_}&F&+fy7w3~iCr$<5A*Kyl>Ty_rM{&$e6HQ*?4xmKxSf`{>;VVT z7t;QrWh`INc72@vn>D$Lz9VGsG%gjN84sd=!M?Goc>masKU91hHD@#0j^*NaA!^}E z(&YnXbq5ZS`Q`vdfO%1u9HkI~)dAOW5OaIAD-;2b?()JXurdQ+U zVvF$sIQ94*oc%VFJqL!#TbK40t&E?hjbpc`pUuzO$ewx(vfeY7dEV>z2iB;c{9oB) z*o)wF_g)7NfbZxt%brzVKgoNRpY?q&Ej6qQy9s>;eKcMHsTZl^Kd>{mUum1??he@p zFGA+!VyGwg5&pg~3hsr}ts}H~l>HfARWbMMTk#F@YuHt|HNO{Ryo1rSJbUIb{bs&$ z-P}X^?TuUUYw0s{K9irnLwOHvV|M1eF8)t!TI*8rv(2XRH=yNw&R$CWU50zfzX?y` zXZ!}0jJMpaaOV9sxKK`QNPl_98jbt%-!jHIzcPLk^qcplynp6=nh&$+d1pC-y{p(B z?4B@`e@MC5muv8w^0TH-Vt19dH9OaR1A4*~eIHFbO+53kH6BwkUpTo_;8A%wCtrrF ztBUv??A-4L;`tmg8*fWnhPy-NqaEx@`!D_(QaAVKKOp`%e@&bkvL()El#eRv8Ey#o zRCzzQXK&Hw6Z#Rfe{1YDdR;u2f0aJ6R@$)#!PWG)W4*O$QNPJ1IN`k!}Oi;eN8+yDK%h! zyc_O;Q!}QC?FfIv1@f2G6n*Z&&N!V{UDUB>*f(mIeRijuoEtC6xsNs%uZH&e$TRN; zA3{4hYeUw^@=8&w@?G0PcE@t@ok40q#xc+H8GR32M_)s#Jp(&_*3+gn>KMBNJL`55 z)R+G{Y$kTUHn+mH;=4n}V{5U@`}(lGoP*#2v3@W~Yy;d%EaNZ~Qd_TvJL#{v{%-8H z?5wdlHN`#N!QO%P0e)4^bo!C}tK_B*)8EJ(IDy6~^l)ZBByWHO0HnNV$9Qr`kU~Xr15}ZCb!~75f-}fp>vSpH#_yv| z?lEn*gN*M*@()ygWmEV z3d7TI5`UCud0yKG`7O2SR4J~1mRRN_=ksfLEd4}TOF{+42?@0-I!_kN-HDNtK{2e^pVP|o@6 zCh&;ZU$iIjb^N2mQpftk*pf8|9ca1l``J6O`?I^^)QEv){Xz>`&WhiBhR-G&AjUEW%-lh4<==7?y}pFc|b zd@fs0JiiY=LcAqA^P4*Nc%?WC^ZPKVpVx`MLfciW1O6KSi-*&{sVcrVOwCK%t>~M; zXYAZl&XcS4u_5H1j^?M$&4*P2Q0Q?wk zfOC&a++R!n7`OnkZ?A!zDF@+!kovX~2FN|NVxN>fE7Z`k2AZ>n;nc9$Pq6EXEro9U z`Sk3EjOT~6jez zmBSo`yzIpy)j(MZh`BH zrT>T7$@^2gBjHuZSftjZzHBSDKD)Z<&5UnzOfox9ItPXDV$ze^|MAC;EV$^J^~t3Fp0elAJXmYv?Wd zh^nHW4?W`$+PVC^pO2N3wVJV5%+Gpm4fku4xqP4f6wW%Ug;Qfz;PKVPcQ>D~(|^`e z{+||I*n{MxKHkcn%>IwqP3(}zowdLNv1*PNDT%JuHkM=!DKs+^g$2JAcW8aUU@x;lz|Jo^pyo!Xp( zNAWLbzsOGg-k(2%A3eV(dxPh!%OA=A%{5+SPj!t6IQ27Yejj`cY(-1W%NTX#k17?P zCFikoZuG!;4{ywF&(8Zz-m&UK?yU;0X5R>x`&r>>+CQ(a7W~wr|FUme*A* z_mgvJ9{(}%QQGBM`atU7le7h{dn9{2tfDWL-w1Lq-_o*Qw_@Kd?_`|s1TvOQ@f3d6 z#c?nZ2FhtuGOuEp@15C)(f@!uX}9yAV`ts(Ek2I64s8p_cj)VAzXyFjKhO6Mo>Q@& z;Q(zm;%B{0+Pr{Yhu_2o%ew(C zZ{9Jow;!S(jI;K#fAX9s;W9L$9|zm9uP`rd#jfLL-DO_B z(`Icv!W!8K=R1M)H=w5Y?EV~{K+pQy0JoCAJMAI}@E*#0uzf;Ot@rRsFxC6a8&b{XTkA#K%Rgm-G zX!Gxg_2FR{4*70pFL^t{ zAh}z}X~xdD4Tp^TT*#XFy5fCV&c5`8Fq3~beP{7e?)4&CI~W3o(z0GNepwfd<)x+_ zAuoGimize>*M;d7&n0%6+;8|5*BC|nS56mNb9Q&!0jHLHE~g>fMLR{#akM(@thZyd zy_1&tZ9=;bKLy*er;6wPb3Sy2OKSME)UBLb*?T{U*Tq$EBl}%_9U%TZ>_P_w>G;go`%nWKI|&ov|N1uvnxOMQQ_ygr;Bxl zj_il%lS{?-C|Reeq1Vtd{wwj8{NZqxd(QXPH_-ANhtQ_UDdFbu6Z>R(YC<>u;rO14 z_jLRZ&e(o~o9LsHyv=ax)m*$5J?BM!=Q@2RXG?s#_$GK4Xbr#955_Iz-k^^;?B#ND zfBEkK{s$G;6f3c3)3ZLN^E1Z%#6IGuhUDF+T0Had8EqtO1O9ODTVJqm6n_l&6<;Qv z_1B7)oO%2{{4VUAeK|WW!0&k9d=Kv`@7^k7E`FofMEnf>75Y*9v&D|2wV|B|*%R4s z=doWD&mO!DG8UcrnS+_yq|SV7&Ns#5;A+>)9Bw7&X6P=zt$6lw-c#?Szqy?MttGMSxvbq+*(ZqSJA&-})Q~m#ITNz?u3+cd`|@Yw zmuV+iE7?0)TW{cuWea&t+MPWUpIa&BY->Lg&U!7TPFxP(1J?k$BkR?kQ`0X1O>=mx=cj8vtjx?k;#Uv2*BO$jjbHjqS_7%vg>Q z`;Fa+mbSh5E7+qUYjF(DTxFg82d-gXRWk07aowK(0Ho%u549gI<|g)H@vPzTmEt?F zT&oQ~b^HtWmc6`mrFR0not%@PsWy3+_%Hi=_S<+UKWpMN_@JhEzuTHU$+O=qr$)~I z-dky@Y3KJX)?jCT#_Bjc1-6ivdmYVw0JdUpiAUl~^pmx-34aJ&#O_FIqfOomv%b^U z04f%+@Pcr<$nJL~LQ@obxm_c%k^$mE!!%`L-SH z8aZF$;C->z)ZBsc#?rG6kHDkETH;s4QwuVO$CryUDQhUT>1VO2@+R?f1}&vs!9KC3 z*w-J69|PU^sWaIlhr$fn6v(fDUaXouh@Jkk*Rr2F zXqSDOXYNI7A=Z+e^Yl|_%0E|LGul~r0bWVZ-prYGp6jiRhsetw7$lbWi%;}FKmQQ?>i8FQ<=lCv~7bTqkxP9zf4|*ADLs8QUZH=i?_KwWdM2_&M93+N91N zMbB90`s<4QCyFU93{4orN)i6cgqm|;mFW`5VGuL%4pyhruhPnQ_{8rEe zGR_&_cBP^&=brAP?*=2_6Z^hjY#^_%CM4);PM+Io=BfPMJAp)P+^jXA?@;Z$0)lJ9kC?V&M0 zwQm@H3wP9KS>AJUjuD>=YeCljQDVL5OW{u#!O#1~E9}nNWDbs&JCdJ!>dM}O-5oa- zZ^~}M&f1yIzrz}vp?%idBFJZj)X03s*-bp-(FuBrW$$)?{Qct8$NKDx zDca2z&-+{U!(!Li8~@jJuGc30F2L{cm&uvOUZGv)cq{%P_$hO>67OD?D|Q=xSpP>u z_FfZsxZ<6R-hiFC`HTM*ZVe}Co6q92@PqvBu!{eA#XBK8bJa)e>gu9iKE=;EO55Y< zxAAMi$g;i1?~RkQH9NJa6~7i_&i3Qy`dRlGujcGG*#F?kunO`nadX;2=44~I-nFkU ztHt<3IUOqMw!F>6hVd^ITTFY9b`S1BU%_4_{|5dXIrV4**}dfcgpZ}|fyW#3Cvj`> zTWOi|kG0(vXAe)Oek~Z`{%+>KNk6g1I)4+&#XHbKMy@bttu=E&C98_wlp;mh)$2tl0OnuY(Fa2W!J)#^DJ0 zN5kE6_NH~j?eQIWZOAargbd%0B9&ztxyU;sbw z9+${@N_YqHjOjF5*7P75#KdEEm`Ahj9u%8w`jsFC!18>M_i6=tdMStS|TJcWE&vRriW_{mM zF7{~lb?)OJ>)|Z1C$$+Wek9D{UxPL{wMs}{G(wf+UNS%h4wRhU;JBDQ4^-~x0I7H z&$xU}Ka8C@$(fRMIT3DR{{y*B8}U)>MsO`Z`+bI-lOXf@x_Bq}34Rk>fyY2Mu|fO~ z*ppqSpZu(ix1gt3`l(GjQlIzGvX&dbmXLLKu-GKeT*c3Ob^cb%Z}e{<{jaBw2{`q1 zGrS=`b+$jw97anmO`Uqh7-enDsVVlrm6hWAj=%9&^19JC67MT-9d_Qq4q(6F-pa0> z&#ryx4e6;5wYA9}x)^t+ug%WB9n8N{n@RF6VCOyNAa<^CAD&k!ejc0t|Nnb??)!Xt z>U-8}`mM|ESyOyRKAS(Ey$w!n%-B50PR(qNPv;-TZVa!v=dEgrccT{kYu)$r{CV!F zDgW=j#b<*Dw8>m$-!2q;f}QpAC2pk6&XDurIR1Rd`EUy@pCeLZr2O^ZDmyTI&8|?7^@pd?F|3;D7LFT(4{n=*R0X>*iK^_WC*W zjKhhv)bHP+Bkg^$YIeruPw{>EC%T`nVK`jN&)&;({iM%&73Uc2B7Znup^we*&Ts=Q zW1Q;_Efw!Ldx?L}K1e)g$zd>`|0&)_|H;i-9uBqnCHmpCH`)KnYpcJl#AdTkq5W4p z_t{2H4gE*Ilu7yrX|uiN37Fa@@T z)c8B)&6M*O&d+?azdwTA_&HZ<*k{5#xfkQq?-AM_Uh?^emi4g*?&3LC(#G<$FB`(% zu!xpxjnVE|$UE-a|CiSV??uZV*+R}-oM%YhwK#R{V0J#6M5_&pV4~ctp;zH}v77XH zJG)7#_#S0P@ndL>@T0V&_49;y8@acz$Jq~=&wbhXUgr&V#;~E>GjYcFGuo5l+4INH z-?ay}qg?=X=`*3F*y(ub4#jsfdCqxq-(Oa|XJ-tu559LV?`fC65 zvF>XjJ+-zuyw5(0zBvql+E698Ki*#Mfi=Z@a6SipOV56tj7Q=PUSTAFc2DwExh~G)CR{+wwE!8_+)Ghx1&?{{z2G z%eqT#?@6ns^~KxsU!v#S$@64PhtsBr_r^VN3D?3K;g)i5fYJOz%4#26$^Tz2K97GD zw1SNB%d|Q;>mcWGK4X4O`_28_T`Bf&efFVpwiRzHmbJZwSmr0s-d1c5J_%=@$3QKy z^Y~l9eEz}wRi)xPu-xAka3q`t_e0*39>-a4GvvNdF@EmlP58mwe*pEYqdn+GWo!V1KW_OZXS^bN!KUJzPuMwN%XWJ^H(smNwhcHpbbbRdNo%YwBk_ zJOQbto%|dq*Pg&WRn9u_vHQ!oWv=$DQA=F+T{$;G-i>B5N}1 z?^C&xXgjhS(7qDO`^qo)A(&Sw>g+J_gXHai4~M4ocOifGFzY;L_$~Zx*sb7Jc!rTU|JCje_QvA5@BQ#h`a$e9p}YBQ%|D8s+MC+ZgWVBlFJ$fJOr3$>#~T)S>L_W$ejvGQRig z`vrFT%9!=wXC2*5I|uS?8JnC-r{dgS=IwMjXW>0?p5rU_o)vSVeO)zT5r2T(tecm` z|8yVUh-cp(0jagM;U!pu{vd58d!hWS=X~xf}Dt{M z2D8^-zbN(`>=Sm4jp?gP_7Z|?%jOK4_K65X((X%(V)i(2yeVO^& z4PVL6JJucAWghO8a|qraT511**!p7c%iF8$TF@JI5w)l z7B+-+BEAdK@BU)PL(Z6??sE`5YxpVJ*^n{HH7|m{Gge~T(FWm- zajud1{}4Bjb8pF*$=ilDoP7dbB(@>Wee8e_!+FHajGx$~P7sQwI$3Vt#7%lhIUq89$?j^Mw z)@0`%&#fwIUgqE(s6$(gZ}&{O&x!22aQeN5{Z`3)2b{tlB{%OS_sAVg%O1^|IvW1s z_tJOv))`b zY<=)IgS?kz441&J)oN9x*cel)HePd%Cq$e2&|mp6|L2q-U<*hIaDOcOCYH?2}7szS@&}UXMN> z9|KvVS!daMSI8S*DbD&I@NV>fa6@`~_WF2l$oMUGy)o|Ld3x5_Fu0B0i}n`&4N~W? zgb89hLF)DE_&?_35E#TC51Y^y)Yu=DV!z+dp9TB#4~NXv{*XOzKF)dHfL+qIL8W*{ zxXN|27jkZ9U9T)TR-hmIBKNvJyECN5W*&yoN3zeQHPct#Hy&W$0d3iT$aw~*j|a4G zB2{%$m>q zFQso!KOK+8S^p(m;~pC0oY_rq9l5z~<|A`B0P50K$=gRxp6x&5LiR%T+Vjv*{&kT1TP<%Ov|yhowve59z7fwXy9T=h zJ!g1R_U5z_en&iiXZ}0m){Qm^QeURPRpP0IU$gg_l4&xvQ}zzn=Eb;u+d5sT9xNmbN)Gv-}t2l`O<8KvSgM%LMP?&lPKeRj^Y^~Bp~`>ZiNjNXl%Ib9~NhgjCZ5L&L0 z^|_9`PqoY0)EM`nSLB|?|481OFoFFiJ@w&I_BGIw-c$Z4{FJf&S1jvf5_|%;LH5@C zQt@5Uaq^yJFR%8_LO)%68vh;KfR;W|J1&5pHJ$-)BySQs>#i<-UmusVSBOo9?3=8K zO=;gB~Ga&CD@|?+Do&6X0?8(3$=?yCJ?++W~kW zKXoPhs3T-=K8Vw{%zlLZa!I{`>-DiN|7mSTiRE2td;S6J9q}A~=6xp4dqUP+*2h>l zT>ClY;&)g&;N#`z8Fyjd%YKR7wQuq6a}j@pem2EV!z_9=&i?($du!HT4ZP2OnV#`z ziSy315Bq3&GqwK~ch&#f+Bd4P#>D&5_7+ z(x4x0s*x_M5gB@_UIrPRsb` zS-Xj~g002w=3lN|&bs@=Q+xCCx9o{w{2k<-2JiBFKx$9!Yp7V};7Pf)=;FseSOIDW^HvgrtgWJ09(>BuAAs@BL8;wI@&&nv;K39)X;-)zDpUx z9?w2sKR@FR{Csz|0AGPGFBN_CB${1qMt8QZ({(N=tvyhC9wJ7br7yn($lE#saw_!~Y${%5o;_%GBH`|KNf-s2C@ z?hE=NxKwNpd3)e%#5co-z-l?mjq!uD^tm?Fm3KUU5U$qGX!ad&CHo0ubhmc5$-AC^ zmG=KamE6f>&zpQd-l188gEN=1FfgXKOT)|h`5 zdpFwt>_x_5SGifIdAEFxz7{?W)~NCR1$kduP2Z2*Tbqo_Kz81*=Hp)cC)m^38NYMO z#d-EEJAEAm|CWj~_;ooW0@^6?N42YuOX8Q&E)mOlI~?*IM9!6QRcb%Kf!{gqCogL= z=fk!5P?%6Q&h%;Qt6?}C&%YZcS!3b+?eh z-9`LGV{{IGvy$iGXRQx1-VNBBKIRn;)S7}*KKjU}cf6pwQ?^d&BYQu2xUGa`3YnOctBW-|1tbSjKHJG^1z#f8bsY!?pMi%jx5qnUB<@!FUnm=Pbj;&w@=`AK-b z_QT|ipTCu;8b2tp)x5o9% zPwLlncHU#}h6d$g47URAs+ z^kQdScENe?I!@j#kowm`Zf$&r+&uR@oPY0>b@i#-hT1G+=M2tuPr#p=uk@Yqr~@PD zpTU>3=Hk22MzfE{$KsR3PGz4imRk8N|CLg4UcSK|$Q~npK0E(EjobJw_$R|Ua9WM~ z!}*MPp;$e3>c}VZZ-)|erJpLV4b&A|7jHq&SU--lR`N5!CHP3Osq|*BvZNmCH`nP5 z*=M=`@9A&2*CF_#nqmwZ@o(gJsaQLB8+rS)ucK$K|I|;$f00<~X?J$^L-zR*(2l*X z*v*i!e}I;C@~?Io-#_`e_l?-8d*9GgGe-0GqUW7xm_8mT74N>w@Nj5HPo3yp?fScB_QBA;?uC8> z{K?-!znh9rgpB3a^6!y*5PNU&UFg^0d+AGQN64Sg-;4eTZ7m#sLp(|B5IHx}yYjQ| zuY{NN*APEhF5bbmW#`^E$LpwDxz_3YU*H05cjaFU&x_Z_<7l(>ooAaSXEuLFu~+rm z0W#;8(KBW{(DPmBrnLOqkBrMiZ8NS}uVp!T&aK(^!G7$=^)ZTlAbTKttlZSAXYg}4 z*S}sL6XaBI`p&uWiE$q%e=&Uvc#3^5PF=eK*Vle~cJ|lLVrTH1z@SP|r$3_Qep7R< zm2(?>u3gsq0{UEf_Q1ZhJa=RE+OP-?dq>V-e*T{U zz1aVSYv2;d-akZ6*3CdXjh1s_OMb?24V=2tSYN5Nmy73}^g;X?Eqm>Ec{lMhmf1u1 z%1iAYiL-WVvr{_`<=5afaR=IScmsaM=QMrYN9ze)VFBE%?Skr}mZWy(+WC&|1Lw>f z{(t0TjE}_ML-y9o{5;cLIOj{&Qa%eGqFv@-2Am@P7cKAPbs_8Lv`X=gQJ;N{HfQkv zfRE*O8y|F>Snf7gH7J|TV@ z{aAc3&U@64IQ2H`d>1)cPbb42>>1+OtMyrJ=D0rXYW*BdyF<>da4!FOv0deSfe)r- z{IX8gg2O%E19Gah%{W{OgP>8lsMQ~`v#z(ScxKuy+T{G*3I_3?5L*o`_~Y=V^0H2{ zwny-**z2;Fy2elJTVQ?dhT}h7`{@$hePy2O`%H8=zl(Uj%WJ4z8?giU&(inMX1smf znRYSz!Ij0nSkC^Uq}Ir9#m;)I1sn61;@0|lS0A_GDg5>SkNb*mPH%+&S@wT*pT9~@ zYTGFlbyIHE(nYk?(40+q7p=!01o?SNNj&d;zx&M9o_#(03$gT_F&>TE*q6e& zkTJXxQtRr9ok+`mOq*G71N%za);N2ABuuF;=3u<|L#5)J$Y-?7S?1^)n8RL!_9c5= z$bMc4C&7!5=Q|9}(06UfxaJHP0+aZoaoz{kgz=s?^?9iLXI(q*5_y(C*m;g;v>h(z zM?AsWPQ9H#e_GBPu}j7GMYrNp=o`!3lb-jV?5nrg8{n+VxAgfc|1axe2`%?IjeZ>a zXLfyjGK>`;Aur>(4nMVb5N)-bL-EeEVeJ2{T{#D^$GPsl^uys1{%4TSC0S#&Abamc z*UxjmtWD-M_pm#pUhO9K5&c+po^?0)7w+cgXR6oZ?3p37W8}_fzr-F3^Vm&jU$OI< zbXZMMgBR2D^R?8Ldh9Fc{Yo_UW$fr#hp8(^@$-&+ruGYQch}6^9ird5Vr#>%`pVkb zo4z~!80~tpC&<5={k66?!^?8^;onH#3vY*ikbfxqUG|wc>+WlOq5K;8ZTRir7xB!& z9XRLOUD~C_-p<|_f6m_pAHjdKRD8#DAl`+)OkWx6oHKR!C-a+#?Zw^<4q;zcDc&b{ z!WpN_E|$geH$Ve!n@KF~<~Q%D_tR4i+!KYO9`H2=P5Fzq7w z^X1%tkHSaO(*G`Uv&K6a%XMf6YPTMAgzLp~@425#U_Ewi?Jvfw#9yb~R4U##7UFIB z8@S$BvAhqR$e&(S)W@A@cgr~&kE$;o_7fjsAE zye57Lza`cjH?9=#LwD1su)E@}^e3UZy7*3I5&blJ=5rBkrP%hyHuroe?R&@>+o5di zX;}wj;VFhHKkAi2}*Wv#9dr8iB?DYLEJ!@rp zeEQM+_n`|d-)kR9&)nyIAhlx)`f_&aL2t1W>8V4tO*j&Ps* zOZ-aw32ZI45&r|ZALD8GEeK!5Zz%pSPTd-#ea^Pjtoy{r;?&k3N`-c`_$b-|@QL^b z{M`2ounlC)UXgnm`+V*4{KMG!FZ(T@_tw$w3dou|22of zr3JKFkn4@=TdbE{b3^`X@~Y+h#ZK*dv&I^S%vaXTJa+#5|2OO|u%J}DuV<}%qTSQ9 z+hK&ftiy)5GmK!LFQ)Q;U(JVUJfo5`{+4Z zd-=D)v0~Zlo74V~e;~WI+>hAD(z4g?p#6kzl6wyBDW3UC+xB9Uao%52&+wv|KMm1zx%_?g;tp`~VS%-;~_nz^ql zU;;m9=WSKq71&i`L;1}h`@O=?-oA@HP5yFy<=Jy?XD#O$k7Q>|_LY}C)|&Pm?N)e$ z-M(LO*5zGpoOl!5ot}57-`TC$^KtCd`s?K6To?zZ(K0UeYKnKkt@-=F$(}W9zmwQQ z?qeCPO?;TkehqI>F7|BJ=)dw#;MdV^7kJAG_H|CE+-&78zOgZ^Jw zo7R;-lin3ZX!9s-ZdLI!fUoV}W#apZ-6%gbJ9Bpu?LUz7;~x2g<${7o@0N+{#<_Ebtco!s|5A{Ce;&JKqnyM9*5-yr$T%4fu1#Mwg2Bt%dXt*o)v``WkZ2gDv@eFUKd8^?B{*Uk=djkC-`3vB_Xl3UnJ`*?A$M@nnFS5_u zCXb!#oh+Vpn%Z&_`(Sw&if7&K#NQ0xC$9zjN%ki4e&To6=N7d3a6SJ$7$9~d&O7J4 zigORYj6bLCD1RY)HKa!5Oc+Z)4F>YR=ikPDL0*}E5qlh-fV)7>=;QP^u~M9$>1!^n z1MM*J^p)qkMEiVp?<)Q*Ewy_)`c_cI{(=4ty{())%T@eo^vvnU^qdQ6`x$#UWL!=a z8z^TyJ$*k+|ByXgEMxLCq_3=@>FhjPWB$wb%6BDesoLiNu}9b$>vJG`b|URx{T(L% zdT2&pj;rA$_8E|xdt%Ah;{9NeHaW*~uQ$Pd)y0~drcKskLv7Z_S%Xi|#>lzU=isHd z8$bJ@%uY?6gB!tIxwk?s$iDAVE`BbOdru9TrcD!^`?>g%`o*qu2cD_Urq;Z}{tf3dW^y*id0)tU zw1(4aiq8t4%V{c>{rC~RJv;SmJRT*d9{p;_yTBp%0kNEesTUhp74Q1_tberl(VlA` z`WD&^;Gc|h<{YWrq0rIurr$hM_QEaNuU}DL@J)CQ91lmy$$n@L!%M~9y#Y_==XtU= z8_P=_e;%?2vbV3MXFi^TA7LT9;9gG^+m-z>`wQ1PL*5M)>jYk>Wqto3K8Zh>zcs!Z zx1fCwhw>lcKZP?V3$?$os(A0ode{Z7^lW?Lne;8>ESHmd`zrfgxC%1oUEoRjRq$4+ znEO8XeDTzS&9(m>PvtkqxzDB0gFPRggZGB4=^JaT3)h`gat&jj_oqGK9R3|**T_jd z{Tn|cZyFvg{s(>t|LAuGQfG7SWen=kGgcqqALZ|*&wPHqx>W4pr^QB!)!^Cm1uz9t zTUXF#>8B0*KKUK_*=H}{tm&;`Hh(?-a`DuoN$i}51L@1~U3dy)|6Zf7yW~uRd#cr2 z_cD)m3P0_$F5A)T$vu$1xMB^nyRo;^*AQ4NC+j`6ejfCP_4&u)yx*@cJ`!GJ=i2+x zFDV!A9s{e3_mB1HgXNqq)(nn;2K+tcE!XZ^XvNQ5yr@n3xraRtkD~9yPEE_%@-;i_ zGwZ8@JJPe3&eca({`=b3=MRU?pi15ow4pe4C2Qw!dTVG!n?>7{o%{P*A78?U{Kv!w z))e2v+^_w+>iogI{zHzjC zdTRIQ6>V_V%8rnB`@;zMO6(r7_2lMv+ke!yC-f7`9zL8ZRONqx2L6M#=afzBKJ{s{cXG}jG@0w zUz>f8Hm~y=!cF{c^whUa`N!~I0rEpeliBBVLL$PIQ3*4EM!69XoVhqlyLPlI;e2>YZntvr4$>Cy%-=$O&h;xHZ8nnE6Y~78!e6vK z`B|?A!YQ;4{M*>~vS;DctF-G{F1|axg?+7Sr`@`=ue>uakdrakj+QyTUb{~Gar~LE zFMBP#yIAI8x%gxH$mfO3L+0&X`c3R+a&|Qr&1qSGsaaN#|DT^ZIH_E`e|)H~ z@BREX^VU+{Wc->~U3{4FI)Qx{eLvir_LKbV<(BXf{T%mk4m)+HJ?tzeD%SB^au&;Q_Pz9}@=s;&&EAZjf8Ucebx^tZ-ggc=-z&T!=S21f zcojZJd>}i22PsI~-EK*T8w+wQ%Z0-uYHQ3)&31OW5`48?ZBekL$aP zvo`y{5%k>8U(kVmHe`M74B1E76KmkC)g|n6a6LH98F^P(9Ti(iUySqL9%5I+gK#r# z!;&@PIoh%R!){970Y62{GoA`Bmy7r1*V${cGj6S65PtyvUH*aWZP;1Q=kd>}6nm?h zy*;g&*q^kP?8)Zl8TV9A>?&I3;2QD1(4UrC+)I2-+=iC@_ACEr_*H&y`aAd-vDEO) zRo3HOwA1+MuM>NByj)Idz)^?iOXUa(( z$nW=MK9)l6zZK574`tWFL-_gpx~OD*u~TDi=5Ld+WM}UVh5VkuvHE+yRJ=>yPTNxZ z60O8O2@itf;2rHVPM6jcedavQIg-5l;cl_aSyPT2=+BBkNEv5cl4DH$dVGg}DzP+ZXk$K0?z5OZYda-Ku=j+JpP4@x+ z<=D0Xydh0RNGT~TmCDYj-#?NFg+w+PB{O?#ZzUBHGLlM4cGB;knUIyex9q*g^L~%# ze%|-@9}n{W&Un*t?c0`{G7Se*N0)>Qt_KcZ~4cHKTms> z_7@(>e-byrFVS*lvk&u|>1ezQtsCThVsJ(AZk2WDATRthq^@kk&%QiVPII}bk5`n7 z@9B-`^Y~41=5RT^EGPTncXo4H1M%$5)TJA6U;c1*JF(Ys&cZLo^rd!Juv2q(;7?`G zhi~|c#p+cQb>J4-qcDPhro3h1--~CDq|VR7sYki?C*6xPx{RNFdJyh~_lEBJ_zmw) z+k(BAJp*U2ew(q-bMKAWi=dL7wauP+2eS6r>)B&lv45>_?p*IIc#q$V-jJ64(Sn_Q zc{#r&y9azxDxN1}Xg&EkPm{GBk2mLM?I%LJmBuOO9d`bAHIt2{zIaD^#@t10HO{lV zKE56PE!(&B#B#pQrDd!&`1x-pTpOEy4dl#yh4-Xgjo-!n{Db+egkt>?*iy4zI^IiC-+XqjpcRXVX@A?_Wt9 z%>M~853Bh{YO`M2z!W$Dexaw2YL)6c&i=1~``|odvOZPui0;KRF=Nd0A-^BI!rq^q zy}T{{l=cC;On(OQPIneeqgCU7Dc%Hc2D9mh!!u$hLC)ZoFqfVF&tpF*uPZ(&*P_>C zSEt<|J{aeBt-n_kHK?`NyR_7~S~%xD=W$V~_>Cmv+>MraYj`j1_;;#cwa<9CL0;2wQX#JR_;dk?&gK9;ku)yMJd z3)t_m2Z?z)ZjX_oXI?sTGF3oXRq##4}Hh>JmC!Gx3=$T6JTzodPr{togn8pXZt3x&N$D<1+?mr`$?Tx3>_Dmj+_L#ihv>(}5xaN^!qw#e5 zEAkhL?Fie`|HeIF4Xn?$e!rE?a*&AJXdSGiR*2~AaRlT{XHK$j-Zb870k$+mTR1+s);qW~A0;AJ4^4 z)Az=^(+A*Nw0U3b-4#W>f53QGiRIhqHSzn{9pGU(J>>qb{VDvt#4d!+@_vS8a4Nrw zSnmA?c`g2Ttv&QT9!}K0J=Yu(#T4tpy(sim2t+o29jtSH{E zex)}Q-b7t_kl9XtO`{PoyZ%kPC38uLGREj*>&<9Lm{oU<0}K5!L3e`6etpIlMY zuS>+Uj$6_44)9aSH#x2gshofkLTn}EIkAAhvp$>gw`1SM{#@Tjv2!1j@Br6X zD&CS^kCvLf4eUb8Z@W9Ovu^nvq$|$Z7$-KF{ySvso0y~QqXu{hZF4wJzxDBL@EhDx zDt;%aBX4@8cNALe4cK`vcv5^8tvUY=cFx`^{vC3r(O#1C-)9!>4B8+#mX_z|o85|c zx_rYu1iOjV(kA!*7-auEh!?|m^uut*`!7A`vI{Rh#L+oYR zYW$$T%$TDhs? z4^p}on@zPlMVH-1(*Z5oV2^fR3~eX-{jPF*Ucg zbM@Uzy92bV4S&gd2$sn^7pL~jmD^S9#8UBl$@+M6{{3Pz*w5g{p}M}l#;wGLXfufa zf_}!b52JM#yA+>+H^X@bzEdjd=c_pHMw#Qx?-A}{LD_iO`Q36g{R`urO}mVC0jy8E zg+G~{_1Hk4KhiqWUSS`LKhSm_?RnS>GPe4-3fv%`8uGig*$X*Gjp*yNS%qs_`xWBZ zb6Kkg zwD#~^MKS)#v<>8^?)9X1fLZ)JQ?tJ+&3D!!wI%E+L$M_fd zTf{`1dc7|_Yd93PVQ&WK>FWXh^*HO(&^jDS&tA>ho>ea1Q$|%3wd}4H-W%!Jqi>1L zbDbCP-~80$Kk@DSck%aP*~eGuZyxOrSVI4mR)^LP2I_Az?H1ZDZ~|?Zyw;HYa|S=p zr&swy*^l7r+Ps9X6|a#TI9pE6&-HLBJYZgW(bkIHgmb>z;S>2kYCna&9vn>X4t0$6 zN&fm`?JCtA+KVubeUR8G?CiJPS4;M2{hY+F1#2MxcNZ{{Z&Q<>GsIZ~P^+qEE;9d+=BCKj8P1*BBmQ=S*ZCYRegkZ*osLYya?{ z|HN6B89%s!T0pZ_tx8=gVC%r!pkZhpnGCmzRn=g9p}(C-`U6WL?s?$6I0 zX3zbHmiwwnZ^i$S-5c*t-v{#FIDMKw&-k-8d&+wapDynR@r*P3VGTQb{}^`Oi}O6J z%03*jXTH@>GxnSE4;0&iwkJRH^9X*Nz6m|=&o9&3yO(WfV;-w}>kDIz zcd0RrrX5i#zG27FyYn9xJCl~Z*$6(Qoq&(0ZN|>C^$_;mkoEai{v618-=Fra+`*7< zudDcH(8u9ZXf@c~*q^{@?Ca!w#l8ftgB#&`dAribu*ZdAQ(9_X-hXo4{NIP{>Bh1h z&VSo*hMd>roMJ3FAM+v4>XYD7dhFHsG<+OvqwPWbyn9Y975{#&4SN`*1|C?UZi=Vh z{on$)R9^Pd36;f~9FI4G?5XVQ)VQPgmH1VBHSHiORL)q&C_^q`0 zLm!RUpTijXCfZD=XYY)_`RzORbT>b>XkU0q?&%f97?`Tq<5tCWDkH9{M^rCS|@&8s1JA1THqd~q89CnQ`fh|%i+~> z@f+}EwD$Zh@Y}S!E1Xhky>QmE7v!w<7fb(>*~xnVGROb$d*M}LjpdHPx8sTUFtHZw z55%8j|AqI#7vUGp#Z`E)yq)p=^rr0eU#U$G_5=UZw&2$?Z|Qq6?fJ6#!r5EH*fsEM ze#ZFML&dk=aN0qx(VYG~?uozC$1J=(d{QoI?L2*^{kHsx(1ZOX&U<0TKUqBYm$k_` z$vNLvf8WvWkdwBb$s2_$tkY5AP1xBR-_uh+RziDzGi{oS=iM^*lKrw1UL&t7tr~93 zuSw4}Gf$VW^G>rH`x@~Y`pWMfwd5`2--@#rGPl3N3>d1-09YWt3%^_T4?pMrRc9c3 z?D@e}f%f^*@_%HsE^P5n)H zEA}7SYzAM5HRopyZ^C^lig&p(drMl*+Qsa=yLZFu_-{h?VruR!u$Ou1OZ$V?pWh8% z3q7@YkY5WHK+eLa<>LFM`-`wG{{MC>eoAYsx@ziIJgzUis`- zBWS1MX>yv=*Jo$1ybpPA%{$*2^ta(0u@*S*85cka@(%bbE#sL64a>#eO<(Er?h5D0 zc(aGj79WQ5jo3%Koc;dt=HVLj2KZZTa*m$Y=3RDvPMdEyIJ7Ww1(b0FjHr_GJ*26Csl{ypq7=y?{e zM>~hz0zTKz`n1&Hm-tVJeblWu8;!NA!p|8>{(W*D;^+KyVCP&WzP;FR+9FzhJN$-! z3;jD&~hL7cU&9N z^L(hoA4RXuKSka(VioLZFpJ$-e3|$o{H$|BIk)ojjCcs=EPf;}`zT|)NdBI*L&aCX z@vf0|Uo7@JKl@++{|=n}l>OftCi8R6%*UDhuizf`3TOkV@j1V(*^iaIC*l717~D>~ zCvoQZLwPy-9ic6Mb9&y*uV7DsqmAbev0QI0eNX;TIPWo2#Zx0w%XVU~lan>b-pli& z5#9wJ70>TegW1`O`&Ab2i>uk0a^3;g7W$s*mU7+)djVx0Y9({SZ5xXXLGNhrmXV zzYU%Vw~6(E4Qczr3if93Jmi^`{)T9q{dv9oTUQi&zXLyQYT?81P~(3?JX%*gi+;D< zi}?>|pL?p$PW$HU&2TsVi{(OVO1qYwb$gw@N}K#WcQySc{*Cgl!?%lHNvp;_UVKA# z7bwebPkWTT5Bo=X2l1bg^E7-&%k_KGd*C&AJ-MkvSMtZ=hvnt{G3V;K6~(%L<-W4d zuYzv&UiSO>?9BDv>{D>Amzte@lXLJSe+&E)-cXy1>2+P>3x0Ln1V%&tt;g1t?iF%A zn!}-TZi0F23$^Q|pJQ;=<_UV`_K$!L&t}iF|I2gLIQ|B_5 z4PZg3cwP==Zva1woq`{7t?Tfh3hQdzd7s;w{XV@KeSo~YFV~^n&(Az&5B8yrhNu3Q zmwUOUq%P4O#QoqWcJ|Ur_N90^9w%=KyDz)5=g#H)tV7OX=BhXQO?-lJyen@muFF4< z-kG00HBj!p#(Dug?|u25dYHVmVt>(c-J`^tvp*Gkihnn|nRfTcOD%ba_8WYo%~+@^ ze|!G=?3~Z+)A@K${=>MQesU%>-s!?b}a$4|-~O+8zhjidTiX?ArEH3w9+twWyW6)!Mv< z&!jJ*9n2rWPMzL|-^VpB!>_^1@}@)69`f;RavsB9nxhkO=4Et+8b#0V7$f{Xkp0?* zKT`ZR7%BG&TIS<d z_U>5x3OQt>0{_i57#Z$RsX$3WKXkV@~TID6w;IagTsr6n5wGI&z_M*OF1@;pE(ET8vl9x zrp9s%KhKLV__ZpE-{2k--=tjZ#rE{!&_(Vt`~l=0HP6EZ?9|8;(uXmPq1{Hi8d|a+ z)8<0Pu z&(%BF6><;6*YIal7WIA$Jm`_)ed@R6#ryvev|r>bgp;+&zeU_hyKnd_@fqSd>p2@6 zir?xQ**o*-zj`On-@5V~>B7%>xfH6g>*D*x%J4n=3fizzF|ItnCPDU0{f3yrdFsioDgH4#&+zQShuEKL+fD3N zc`Ip`@SE_@g*k9v_u{*BomeaF*5U^EGWj2ieIaeLPg7q8#23lQ`%msKxvAktK>m)~5a&Lph`&~L_Oy8qUszex{M7NW z?0gfj@agY2n%|7FcG#?xqb$y*;EL;Di4FWT~R=IXO^ z@5|`lxz1(m^Wh7A6Y<>7EPm!-8tol9YuJ~-J^Z?O6Y$$vm~AUB#b+GdJ_}xv~CE7F&;h1I|0n1LEiLn?h>J z6_96UciaJ=iSrEn!1}!?=jqDg+42E+5ciYR;m}Y7x*9HsrVG=Cnx`Vip*ml{1iT0{3fxbxW4%N zklOZqH|y%YvR-o`^Pc-%1gU3} z7PYRoam=K3kTXrZm%JhTAK1H?i%!O{pRr`Wb%mUhP4G52d$EhWH*r;*K3nt{t4guaBqTj`W&1^D-1}^xMR>VogilpWs~Bil2T4;FnzESK}Ta z_xN%##(dj7Fa8n#N;#Q>1?araJ+Go={ogYmX}g2`y&&hOCFC5=f$XX5$KEgtE`zT6I0$Jg5Jj5F8A&_9)%n*K98>-(A5KD5-+?C;a%WgW8@7bb_@t?b>G{WJSYc!=Lp zyVRzv?+m;V?JGIs=&3ie=TG#J>|@~~ z_Bh%x;{Di%L*7Z=!gKX;L%H~7-bwsM{?qg}IQwHf>;)^`^IUr7EZs?$KzdN9D6vtEtb7dVmD!bufJt@ki7LF*V&F=2U1@f;JR{$<28`E$hm*W zeN@M%z}IE>?o2Ni&z%4NeB^9A&+nzpf$S&boy0%Oyk&h_(og1J$R4AgR{T+N`gZrO z0l8jnde%7ibro#LZj3jkS7DFF2jK_gUP#Y8X3VMUIrkH2`LH~C%6OJliP!qH;i)ANm-HS167?H*F|4y!C`R3mogt18Tr^BO!1Rm;V< z#!vWK{+)OaJR35XZSV&Ay~8!TSMtqc1O8P0Xt~ooAFA?~$~%=mMVs7z>QeUc&+L}a zgntVDS-ZVU?hnop-yiQtyBShP{xa_DsjO$#Blk9%e-xhLTKnoJ_mTQ=u6Sxj9rjMN zjo>Bz`}#`%P4$(1K7#%%KhNwMa?fDbq^AaNEGKpHEB1rp$Kx3|*H0~;jt_-At3JSw z!UnJjKl3$FZo6`E=0@u$WB!!h2Ja!KZDp~Sr?C$c-+^9YKZa8e@{QgS&SvL4juETJ z-mg@AyG~_y&`0+FD6yR(&)b@`nsRb4eZ-dFtX`N{1m_J!x!?f3)jTF^JuCUrdLYB$(`eZIE&E~_h68?w)5z_;f0 z3Ho-lz2s~ye-^u=*!z%cEqbKNfB*i~gI`9*}+XHvLj|-bwG$ehvG0ZBBxDu9Z4=8SPE{t9WnrKiZ6~D4wr}7(?#;F7bJA zt#%Xb_jNdX>~8s~Q8Tr@1eW$ttK_A>cJvMLmN@?%i~QERO3t_1WQ z$UeCd+Q0?cT?7r}WMA%%xS(9_+Dtq z?j)WXd^`Wp3Ueo~t8qM2D(deU^cl1o_;Y&ZV{5UTp*(+%)OICpFLR%((>;3p7>w-$(bHbA1m)`epkp` zoGRxwv40@zGQ#?4hryVV{R-K0d4E|A z+53&)WO>`Vm&YD4@AQfK$@%O<&$?yJhC%*23mwH4m5aXf?veWO3P0!eM>!iqbAH}U z_LsW_w1M8@>*LhInPOvc?k#&FHDnX9JWr~t5xIw9h(PTl=M?sWE2 zTKfBxmS^KJav`;79m7Ilt$*PU_-S{M6+MV!6-!-D?U?tt`{OcCFv_IUDwspZ`mrQ{=bBFVL@M zZwj5|4u#L@)A&!oW#SF>RSkb5mfwaO=w}Ffg1m`xYVtEb*~i&Cm$TbxlX^Cc--Z1d z9xZPYYzFh-7xAi<_Km)$i$BcHHAjjyhRppPIM1u=*}3=Zvp({&PrKpN!QG**yiPEy zRD5@&UcIc}v*iq-?FNn6*pfU|aS{?sNaLXH$iCw4AN%;k3Jf zzR*198I*U&%)!I7yd&L$lb1S>^O5~J8L}USi>-#v{Jn7Q`6hPS_Y+S&O51a_-PiS> z6mP|!h%>j_LFQuD3hyCus^E(t>yz^xe-6%lY(+Z)^1M#X$upuYJL__fKK3{MP32_{ z^IXZdaL!HEG~a-^$3wNtoV_YINm*-U4XMbk?XUoYovnN;Tb3Iz_J#%{$ zZJzh}?6=gqJS#ei)sgeOYcJzxK9@l1L*_GcnK9(7_kq9v*S1z=F}FQRMgKWZhtdvT z=}gJXc}rb8h~5v6FBS7KU0-Y2JJGUFAJ$(RIeW0{vM&}(oyi%VN6Ygw?+feLc@}=l zUj>=ZTqEmO(=}7`Qpd8-rikqck9Siy^}Q)O&z#iE+U%@-D>xn&>a#7*9GrtchvnsB zee=86d-T+mV`%+hjQpJIzVui4O>uo_8;zg+l6RFI@arXKSsxjD`mHW6XW%`3)Wyfq zXTlNmd$c(kM(O8g@ip|bk!&%!yAn%9c%EejjslUA2o=YFd zJ|D8LI~q%#H^0iA$iI?(WTkn)d8Zfz$I`Pu$Kd(Un>L%jd089RYo+Z7*oO8Zo+76{ zyR*F2?4I(zqo=MNNIQtuk3Wt7I^-njz z(~N&Be$LKVeP(ax_qqITJC(f{XTN3+H|AfV@A>>0rDA{N9rarIsdrz~JIXnReFgn- ze#V^kn`-y}d$Xx{bJ|BRvsAo)RAuLU91S_69pP%oGw52#{XK%4;OvP*@Nk^Ha+B-t z%1>=bJvfU0J#5Y1xK#Z6t4{ol`Ds(LqSzZ*+fKAm=AZ^G=X5>(4R|})NZZdK>++i1 z%*%)TF6@>#cy{#7t`{0s>}JC@c%TAhp|&@a_@hN=h?6o`*?Zd`HRKBrQHR0u>Zn4UQ-%)QxB5 z<=XGTYf$ptnLU0Qye{5{wiLEBSIyi@o+B4S*19EB*Y9&;zqr;C`i4-4ezo?0vm1!# znR7Zj>(ZM(S$tdt9iM~m&~HPrvZ_Yt#Q^wJOeJ8ZxkK|8*`}kR_ ze52$XmRbp>xdpmmr*p{BUQ=fedJOPbq$LZ@b`dDk1=XPWE z-mp}k+0&^RznGt9VgvXe(GSIG(^VheiO5zBB9^IXL_B!@xvFyPc*q5_&cFSUW!8VY0m_KM2mF*44 z|Gn9xcsJfmJae7%mvzj3Y7!sD!D;$E11jiO^1pRM3+VV!yFNN&;W{~r7x4FtS@*M2|J=h<}Z>)XZMY>iLXQDm+@43U-``H&? z$w_Vdn0-8bpuR@oC-_;HpYc9&|7EX$TyG(+0jWPjOP-VQ&E~=-Ds1_q_Xb)kpTne}4;+mwBxvrx)#3u}{>nRqWen zr^8aXf`1RpmYZ6+1U_c(3t86+{j4XxIeUS7SjRt^ojH9SR>{x1@aC0jJU`>lKAXzl zfWD3Rsuk)E|4wa3;OE49(avG-Am<|fW%Bah^LP|z?QVqpJt6yaBKsdXqu58{wff20 z%%JCQpI5N6&+@#?w^K_zNM80^o=2$#XR#~c@=7@{N?uj5FKHX$-(Bkjd0CJ1aL#tt zDZg3d`!(y?5a(KzaHITB%SN3-1IAd-mwiSPOToqDR zYUw}obv!?7K9imMYK0%gr@>WnriqP^KNp`$%f0Q%ehCibkHC4JWZqI2Ze-sDyU6>N zKgPP}-RymQA3t;PA>_UCPWoN!J6vZO|HN{EF7%_c&pl?0?d04q_L7{F`1ANHw8>gr zrQh89SG3d2RqD>Z@R>P&Qcm`6-luk~DBfAF#cSkWK>txZW39o?_!o<1K3{?_=sV(f z<-CDU=FgQghyN-&d*T6h_WpnGlRv3cod3+@dHl`9o@GA|YoMz3x6ofV&J)?W-=pO` zDE0$;DJ}apwL8z&+*2DEE;a(^TDkY{oo9ERx!D`r!)k2?!X4Tz$H&Nh5BDe+@09sY z-xb~xAF15}@kdsuU;N4RVeAd%c7dn0`*~&Yje7_GB{?nS3}o-9-9PGU<}mA@-^TK; z+=c&(_(~YUzL(yeej>XjZ9Vn?T5o(Ies+cLxeA*2Q{r#HMf%Oz8BCwepZ&ks$#@rj z&SsvUd4BbR@$A%wjJr2`B2MkuN&hP#^|T%>&%Zx$-Xl6fUHYT6^_=b4*-wkb=i=A# z6WX3Yzt(-HZR*F@V(-9G_*Q%Z&VE^h`~ENIMt0uw8kCC~`-r^G{O)+ha_hgsnUK5Y z5jnI6^?#E#M?ydIy@_^R#CvE{17FX76?TN*OX{%pc^>2*zGr`E4=mw-$Io8N9A`c9 zd`%4)PG1SBF^&0~vu_d4y=Kj_KHt+)7stYO{L{srV&}hQwx?@+z`p@rg1!~SyHGoJ z8@UgZifhcowfGm|j4kV(`FR8S)3dgJx&Mr(1^;ON7y7=Ne;Io+oWX93-^BYtdw7PQ zIqw7aL!*k~y}lnk^RlbHzQZf^wTIZvICEB;p6A~}T8EOe*}eFiM818q#<~Aj@jCn; zZO)f>H9zyV4(C3$r*CMTGAFh9$Ciut*-!C_^72ieHOk)YFFr_qbv##_M(LAYUCtE9 zK25#sT5A7(NGn#YMQrl* zVr}yNl{M(c{t%x5&x^I;H`mvm>?7Eh;SEoR7 z`}w=W(~x(pBjxmFm)N_opDg(ujD0Zt01fza#h=63kIR$8|AM^*w6w>*!CBjlqOU0C z=0aNLGxN9~z6K6r_m+3D`yR-iUskv24cHH}H^=#o${glBY>WSCkIMO+y%svkxssnf zojtVNJ!Op+x^CvRi(0V&rv}fYXD;gKuNmA&Uv0bW3ZXhN^f-Wm_pZhzWvocH2=Xo=^n*M-B`-QXDU=Zxza?YiL0`FBFbv$1i!$(~1_ zCZ4`lK+fPmJXZTn#ahbE^L8SAI=gT8;(X4col0vW_e}b~0lOFOsNDx*cgXpemVK19 zNL|X`E~h{RdphkJd?a+JEPj_voqb#WkuXO*`)D@14s0za-yx~-U+|CS|B5?l^Es`Z zSiU#Dqt#~TelqsQXsLBaKt0I(Ri)?OBbF+Qca83Nb7#KB(*I8Mjrm=* z`R_T@Z}$1~;#{L@=EObkeYj|+?=~F+1W#xzcz9n!`VMK;!W_2`pw$j z1pCV04KH#1I_yK?Gyd+{Wvp%ax#w+4#WU+w_C>VhUQf$)SHm9c1~|2NXR$mV(tZQT zGx{C=N$|EW z8OH+r1m07dCiHpyd=I{bpThf#odd;`yrKZ>7mmW%zeBW<`?^1l?DsC_kl z?lbrM25r9UrDiSX563fc)_eqHU)O}w*|p{V(%tps=gbbHr)K1TFOl^gE$18js+^PL ze$79X|2{lT>s?v=R#r`$yd%t^=ed44-U#moo3ZyQ74O=4&Sm}b4u3vATAS~*y@-87 zS)Tliu_@jdYKdiETn6LmdCt$oSK#>H>Zhrk)RRZ`op}D8QFr~G%g+6c6w7mQOT13b zfO1g>GAG$%Pt*6r=ZW>99j0CWH`4RjgK_4#g7%iY`EU#U4*4B%U;f|PrF|1Qcku_X z*M~e$I^(gpVYwLNMfgQ%?{C6;%P;ABD}11wKKLq}8gxHiq~AKUMsPMQyjg`aZA`m~ z@5g_d{to*}`~uGN`B?UJ{brsIg}?bnLp`w}_*L!K&^KmpjaT8eVpp@fYPS!51QxPK zi={44!@1wA$EN(|^7HMKnzE8UrteV7`$9NuGZ zAl68~`CaRUvgeHV_2K;E#fHJlka^7c+d=$gJYAdI*BVG2`%=z0$bR2e?6(!g9$LqL z6SCiK5nl%n@N))|x1YRT{M3b2w9IMxy%-+luhVWD{NFkt=Ss-ln`I1nmZZktET_Gk zpY-*-cKgv=&<|q&%FbNp@3l>Q6MZe`GJZF>j-9=poUYn8#pjppF@5K66i=Cl4f$Wm z+gf}d_I3K5BQIxqGj@M^)-knsEj+2Vtnm(fr9S4;vuCs47RbwQAD8lL!Ex*d>3Jrv z6RRXQkr(V|RJm(I)UGin%-3?)y&uwU zlgi>-?j8Di#x$9BG&|>Ftemm@bMRN?;+lEJ{Vs0-WNn{;yicXC!z!J5d3S1?=hJl9 zn?G_zv7c|1yEpBA+D&)~Kkovm%Tu)f2tNoF<>L8sH|+ff-}qfz zGw&c1{I2v^-C8esi{d=U;~R5x<-Lr#}9BzWDXzjKuHjEBhq-bv6HO_LI<0yJhl6((AGJ zz}0Zh@g#Y94%{X08rXz=n%rjkDv7NxX9>PZ>keBz$S=u*bUjnDGo9SbFIX`tP z+P*?-!tWz@TlVba^0P;B|8?nk2UyL{wX@&;;?Jb7#~v;>=PBQiThg+2;hg!G#j=m< zv0qtHjBhq=GuK{?bAK6I=I#h>I>I!_{u)Hv6km+jbN}hDX4x8xr{A2e=D;Uh3}gv^%x^ z*ggD>^E~}nPFp#rvA2f1;Bwk3c#wV`WZaV~i!+|ucbuGmXk%b(shHzQuG>PtNAov> zuJUr9Z@^@+Z}B+e$n)+ec`Y(d{p9&DotAxio}3Tu+x6++l=Te@#L`c9`Q7>`pM7y+>WzX7dS6D(*yZ$clV8h$IDIM zcfks%Ca*1RPDu@B|Bav5=4`Rgw3*2tME`+3h<+Y>iu`B9TZxUrIg7KzHp0Ec#>q(y zeFR#t^Bp%^?v7$PyAAkzv2*5jVQ0=RmzVjef>RfU(9goZ%ek6%;ELi*b&xxTeS=u$ zD&Go=Aba@#_u3S8YU776M*I90ybt?*_P6YBw97N+0`_$3j0Orhm-Nx^x!1 z4JU6PJL}Rxf0>Wu{jS{$au0;>tZT;JMDBKwckP^q&F!~5r`NfkL!l=7P5S7vIcJZB zjrrF@&h9^Ox3&+#O=9Wq!xiQh@?6@RpFNo8TF&!`^rUa%$hzRFMI4$JW*aB zv4`;+_*mL-IlJ*Mt4)X&g1Xw7S<&Fq(-K$jmER+U&vj^|5~3z zDvN(JKADz#x*4)Yc~{DLUWzxAyA68;ejjpXa^@Q9s{*?2ZjaM4=b48-_y~UQvTtX0 z_HXt>YC-*q;y1IL{WIBj;XK2CFBi|U2l%tu>q9HJU)wU?M(%O$Cue67eK5sb*_;bmvtK`K8XD;do=vZ zUkO*h-~2=IA@D7GXZ)o5XoK_4IZI#JyLoO_()PmHW7n~3;ivE>wBhiIcIUZHS-U6b z%V?RGGvOgf?M|N$l*V6L5 zIjqun zYm)wRHv5Tx=Wls!X}7cA6wkiPzP^Cnoc=63?}9mV8ROBkZ`j%E?O`Rmzw2f1Y{Y*H z*TH=&ywkGJhRN)qcnr*k4*!d{WZ$RnH(@RNF?>h4IA5v32e7|z-v816iTpeh#;_Nz zDE8?0v|Z(7E|#-%{daKwO~nj-jpP3<-dj9#Q(x?U{+@;jxl&I0_zvpQAK-r>wl_XT?mzUL z*9btYhrEa3^hl zH|LqVOJI8Xf=#uZDqf=P3hSVOIh&~e?B9HwZH61lyH;#ok7B?4qOYy!|GH+*_|0N% zq} z_lR9sDxNnD`FpcZ#d&V7VDAGj!{(6Zw7hSL}l@PaT~oVKYKFQT*n_@R>$Oy!bic= z^vq|Tq3z*o{)3Qb%QQUJwNJu%=X{#hlfN2|l{*1Out!!D@4(rE>y^wg{XgvP><{1z z_jU{aa`kvOxp^i$LH}??k$<@S?P>XqCC}7P^ln{kkoVQ-kNujbljPmP2v#NVPr=g&QqQ@IX|bcbIu-vf5o50`M0r6^m(*c_GC+WxyFCQu7vysx}2W>21uUine%ya zQX@WQ_hWx2XFstUXtzV|A>Zh!e|fLp13ymBvum45`x%a=KSRs@o&+zHi*Mx$u{~f5 zXhgpf?^U6;va{#9us47k`Ppl~;g@jUcYdJ1r{5OrQ`vdv>I!+!$vHWny`S6@Am{!V zEZWpb~SllQVb_n+r4;a3;Wf8XkR z{bfI7ecoVaPae$whCLg0hH>Sh);Fe4WoLe?RTOKK{q!|$Ei8h%{NvyR{Tx>+`u@AJ z*sI;~-LA7EZV8(})@8bO{b3b*JA6FeL;F7<>y!QYsCKDk+22>-2gLHtzqi=W{M>t0 z*c^tG%zLH1$KSXq-%A>xmNOK>T`YeSL~O%7xR<%mDIEa`F8?x_v2To@Jtm?J>ZeWK6_8OtBvDr+G6;~7_$%d zq1ER9$<7`;36k>*)YL9>bD*}(ty}8O=u)vCbFIEGS}b*X5xuwgov@aDGCl7h2jILf z>D=h)l1n~+xzfJ*q)tQIjFLDmQ0{s#m~C@%sv-di;ahW>8C-)G#+wp&JxeN_2K+h z?9_#i=x^a`aNaH3ie?$+S+pLIG4XMHv@ zhWw`a3qSw+;g+}SO;&~{h$bUB&RygOxYWG}X1zvMmU5^Y}Nci?X*=TLUW zl6UXat6}WT=o9hbw8^*w?h4hQQw65=f=?lNuj_MvdQ*Hb{+`wvwv@kXxp=3}7-quO z^zc2#+nJqxm_3v^Th2}$$@zGgy^fZ;llP=Yp+QCQ{*>`;15My~IbVuh1O53A;rrm3 zQt^GUS7q^?lkc?m*<m#m2l4R>v$;c4W$b8NM8ra z%kMF%6Bo$chyOeOeSS;$kK8R_wfu|uGhmXOJX>C2Z-Mh}v5hv{Wq+~1h8FZkOGP|q zV*reWaTOR^iJyn#chz=#vD{DI#oFLj^yl$;B{kK0?||RY|76-?n9sl0{(0S*ZO9%2 zJ8F~b|H00jFNgRU@sK)}I(3`a2sjz?JMim}y4$BLUwkA?qRpr*zH72pnZtXsn;LVP27&e-MhYKe8`Pr%RdkH)DrBiS3sc?oB)t!4M34>f1$cNP5!$eia)_hIK= zE)g5NqB#FKqp3NmAE|?L#d01;;URc$S~qQnZ%~Kb z8fvrijxm+p6Mx6=tNp(CIsO28`fAI5yj=L5aWnpl_*$`Nnmx4=oqtxtM<%eF~mSzmZ;_pSgI0 z-H<(y-h-d^7t_}8Uw~=Gl;>u~)R&)k*|XX8*}4ASP?xqFPW-QOvCkVBd-l;VV|$NY zV*kP(t?kqNopC#UzUNkHlX>pN?}{InJA?fJ{b&AszdPo-Ps-gyyR7YENPWxucjoqX zcA0ht{3fpho&cwbzfdlo_sz6zPV2=Vfjje0(YBfVd9b5L-__7vjTVM_QgSSFuyi^6ohqSA)xG`krXo^aX>XAh&NUZ*~#R?U%rIxTD7 zC3n6?d}sa}@GN959)yPan1-{T zo8Z=v`^&q>{Sd9a{`X8CzCmmt-VSE#cVGHe?2PRkc01V87<1k}7Qb8jCA93}Bls_{ zf1sU@b3RgAvp%V#bLchrL+yzh*jcAPabtb`1nEEf_DI@m_+H3-H-^mP?l^nxo>H;D z8fkMFJ#%;}J-_Si)4e#eZRmH>PJy{_Aas`3zf}BfXEXLcw6pjV@iF{7Lw`3JZ2mOciF=iahc$3lLOxeT9(v-aut5PAJ!7QcdjfIhmynf#tGQLHi4q8)(eK)%^B zr}f0o$BEUXrB027!y)f(Wotiqh5HlBIar@|m^K%&bCxov8yNdfCHslrU+hSnoa~AB z>A8OP#(H@F3VT9--)_IMw7l=h1@b6_Yu#!u~jihVUs4a?ep zhL`In>ydq#eXs#s!v6^Bu#-DTY=L$S*vI0@FvDKTJKSEzd<#8mmHE61=eN{5aR>3U zjcpG7?Nad_w2+qh_>8_x&SC76aSh`*g#8j^j`D9+SJ4*Bn@YxogXPO1}?@ zZOyI=*RubD^x2c&N^WX)O~`)Om7jflnw&4)!*T45D~t`Fh_8WmD~k8ytMUDi`KYDs zAhGPb)VdL)Vz0*@ zDb|iZo!uR$){Ud37M=rnW`5{8xwovvVR$Ugd)8BOOYH2M@%V4ozYQAmuYuIx?4={* zwWl8`C+`WT(jLHh7d?%3g4`xJ^O5oA9ONALVQ0K~ckIcoVCSsn8}>YWJU@M&CYJY< z`?MR&?&A4ncBB|Y=HK07s`kFialea*iXPLsD-{4q#f+eiBg<>haV z-}29ApTvHbK3Ch1;a1wAaCD`<%Efy0lG~hr6aQ-V8SK>ZhWuraZ`#B3Q=k0+JOA72 zohn>IzgfpWXzxP4k8&>acdFF2Z)p4B(fB~IUA4JD{7#%2@&TMKz5^}i>R9?w>~moW z{{sFr@!Oy(`)k)bl%2hl^S83(-I6|^-3ebwug1OsuMyi5E@tN}4v3Fm#0_ZI=<5J} z13aLjsBO3M&t|`gb7oJ#*Wy8?;+t$Ae$G@I_=BDLlXunhowd&V{op<`*8ijFKHz>V z-}ixA5eaFC6p@q>Aq{*UN@;7Tlr+#%X=ra5Wkm>yLQ6tZNqinH?UeS`-h1!=`}p18 z|L^rWUdMSJ=XqWCeP83e?)&rj_DkKpfIbAq(Y6%NGp8P&ES~@0yt%J0___bv;U@N2 zvB7fBb6;=qFJS-5o^MRMh;1q_d*v0e7VHM>oOg%H%lb+^{+!>JzdziqE-!HX*JzKI zoEQ3PO@Es96s?i`vGm{BuS3qelVGHr)WvheCPK#gKIks?LD_xlXDmEPJ0H%24Iuw_ zx4yp;dxQTrdjR|Baxvc{^tquvzTxl9{!LESaK^AJEj45mY%J$o+)MilU?YCk)^_Yp zrQ&SQ8tBcQ40)gLh_9n%egDhu5B1rv;TgD%oV@cb#2vZJ-`)gxGcT?5FIY6nc3<3De46-bcJA{r_*#EilT+w5Ryn(IUC2D#E}ng!we$qcg51;Z`iVB1 zooi$qc7{2$oaa;U&XvXc&!y}|^0N+87soKP~@vWLYdK-I~*fM%*NcQ-v z?0j#hhHT6q$e+rt4Z|ynx;=<}p_~c0QhYRhG&}dURGTI}tSNflb6WGWuTu*S)@DOI zQmi#CW0duC1kPON{bdDy$@so4x3ztBENy*SE1bGG-`rK_x8vV{yW?k#>5pPrj|(fz z7eD)VFuNNx*H`x3O7^pmwZEjYcyH+???T#Bw7uX0SPA3G#XDg=vElqS+E2ru(c9yk zJIBLpeP@i8KwEm&+${HfQ)O|_U-6HYcer>p^PPP@o3^w^@q1De_VN12^Dc)g`FW>) zQC_Z{^Kt^aIeSlhx7fY3iSlaounwU;Ep@Uvt&L|-|2gM>px*)?^S^}dwA=Ml8)yBj z#qIFp__C5SULWmY68}f-Uc;BrH;2@UzxZo=6t%c2Ep>B!`o-+s@c^;ZtgOjBA;6DBoZ_LiS&5!KP?CGAfJ2aDb z0>78MW5x2{r@E20M}<9rQwL_zH^L)nUD==F>tI`LZspfzKZ5(?eJb4teJY&4qBzex zirpo)4cyHB8?yfXDHZ)~z%J2pFW1Pq4o~H`ft(ke`Onca*Qe1M$iGYfce9s^XP;$% z?SnJVsbxE{FBdJ?KZWUlzLv?*<=1 zp7R@=IY|x9H*)sNGupJ|XI*CvpU8d%?*f~bi~IeZeGR<@?j&yie-qvezs^4hXKr7|m2jGVHsk*cJ^7j6C-rkMKl`tx{)5)^8j$npOL=R>dh<&- z|68;!ax(VW4;hop>l%JL_9gPR<~QZ%`QO8*YFj)QrIBhZqGvw^67F^5Evu6Ko z&aMlq*~7)NH?tPM#;FN4#g^lZarVrg^i%N|{0rngs;k&maxy<@JCxrLXDl{he~4${ zx#ePxuEw+Z*W#gKbKU3t{4Ln&Gv{U>ylZ9gd)i*^D{D04nfI(-^c~o%X?gzHc=JcB zQCezJ7xDD7Ka3T77)H^*#`zw}ct33%*6^Fsv%W5)<@#0h_ZiMwf1I7#v4Wp95xo)q zFKzU*4?FkP7U#P%{r+1n>QsOJhhl@&zk+vScHuS46FzZpIrE)gFi zmhZ0V?(Zmg4IZG~FP3_d{qrE6ApgVvX|Li5p8Oz1^J2@TU9sVX_nU6Z`>u^)toOUP99z6zs z$4~q0=d7JGwar?}bLRbQj#!@kP_c~n@$Pdx`xbapd`Ed#dd@fav-rDce-k_Pt&i9? zc)T`G;tlzEj;x)u>xQqOchSC9rM1M)*^^qB+EgEM&&hcazYO^|($nSq43p^@k1yc} zxz!=>9NA~1X$Q*r%h>0AF#YaF%R2e1WUOgfgZJXQwA)v_H@zDBIM>J--xo6XPsz)8 z{w<#O)UR*@Je77UWc_5FE@h9x%lO+t@|HvD;!Cjc%HliV9a{EZ&d*EOgV{gf)Ykpk zS>tJwI+Hoga~|cM?(#RPX8K5-$-3Q)p7F`JW}lA7vvKA;_xe4*1LU3UD*C*67=`LF_zN#`!R@o#0V+75dq7PUJ5! zzxDBvFwb=xihT}S@Ux!s8(->C?xzz>f^W-3&E1`yJ+*+J`>Il*YyS4|I6wEBv3gk_ zN6_MrD;4KaAKG^Ew}d}wU7R~PL${E#A3yid2X>=9gg0`|++11wo5R$ZoUzsMF!79U z7hEd4SL5}nSpFT!F0_HJ(+eN$IWyncKM%2Uuh+@TobROFzhbQ_i+75<_;cCWm%p+n z>Z1kTlvY#TV)o;-2JDROqVCp~*qjRWjFz>N{_8>JBhQuZ&oglLNc=iD^K&5Ndv9!| zcLx1*7dsG|(ry-O>^ir=@Umxx{C2Y~e#^aWC9jQ~%yq^l`(_W=gk7DM+Ltw&@4Dk~ zbNJIWHpYYHrH(wregda{UkP8s>#i|fyo%fzcr08XXMtyPu z%Q~3K&e%RB@2zri79J`$_j*46bX?Z%YIfec`pG@nc;%eVR^&@rl z?NafroB!X4!`NNLy3+Fv6?9~q!YjL4iOUQlihfmSpMshmyQ~$n$`{2TocQ4#X z&T%+3;xKus?YXDahNsyr*{$gl`NP?-L2vqA=JptR#(yoWf=7CI=Png>CTCD;RQh;H z+Z)((@Lc-ev3Axv&(1T}koP73b^b8CvI%D~ti!Y{K z2Tj@U!Vl11`~ccJa2db5zIVWn@-r{Tu&c^BL|@g}shhXM7wp8|ESn2_i)*C6C$v9G ze%_^14^lUCzjxsL8~tFx$ocD2jh(0GRPXwnp_IC*?&P5@frNQ*JT}j z=04KLS?v7(!MUo!{)P?d|Ix?N^UZUNc2nv9uv2TYwvXj6l9L+xD<0t8E6@Br|6AM+ zFNRy?9RjPgX{q0>#PWY<;i~j;at6Rq{w;DccKPpjo+kHG{IP3h3|+;Q}~7Zga>vc9;AQan{PdwAAn3{LDl4UFI~`%X|7vS~vMS z>vw#$>p>&hFm2}HKaEEN`S~5^H0{q9|6A@*v8>ZAaQ1Xv@jPo2+EX}Z z>Q1zrp*b6_rfrC8KodA$Y-epBhvxh`uCt5&tI;cIPeIqp;*3ts93)km2o}qDN z@otk^IYqk}a`%JY{PW~ouT9p;x3rJ>AHmBo0|wJumDDjg-SqV*yCyVc-wM^}&*d68 zd$xX$V$Yt=&UHR4+wWqT&+hC~*;xynVSo1a^5QSlE@Sp+$+som4z6G?t?>QC?rppt z!!Pl-WTz&L)vk-2v9yc%shw-Ttsl?sK$_AY-0)sA1x5p@;Zf zd=ae=q}{LOVytuCWxuVKQ=K-PeKHE zBfl~KT5UR;->jSZVyEF=wB5!3#1GSc)V>it<9r$9%+9_#5^p2t82F2xwim$O z<>DPabDneZVcHjXTFIPfSIO?n&Y72W`vXqxPX2%DQRa3hedagc;q0gI`TUIY+j35I zzt_+P={L3JBlh$3)7j~}D*jyV*?76WmjQeF1{^(hJQ1=o!D{sEB-8c@-nxp*~fYpUSGT!KmCr! z8$tG7f7i_W#$?Yw5>KQZE|xX^6l5H(gAM3;CtJhLx5N7UnlKfX(LRNX=^yGZ{dA?T zrsWx4pv_~?ht${mAbTNo;%~9Z{H^3??M>pJFE)jJ0&Rae^>O}opJx~#cD>lM@-E}= z0;%P%(>H^cjo$&ZzvaBZAIff9;e2FIg0=L#7uD}6M$a0}w?P^2ELNSiF=W2R$=j3N zN&l$_iPe(#m)MSYI%I9mg6zjj#E0XHVN-2Z;e+wfIBoaVN6xa0duq+axR+SYm#g?0 z&vwwIvZ(tR<4?shKM#pN3o9YN9UQi@nD6|aF-OjQa`T*9$W1+ONqY%4=Vxz!$KI0t zdr6JZ<{DZL+D-ia?Bs0&*}MDkhqGU1Zvhwb4|UD7&)>@1`#VtT&xpaZ+)@X_#MR4*TMY!8?0|vsSU25nthFUYSwf)KhZL-hsaIcSs=cG-;19k_zPCq$Q#_{il-DufM4?yZ> zd;Y)lU-%i*FKDAy*sI#)*|Jtng)iMl&gslme|bmCPYqcM8Ox<&|7g2N+Xehx@ z95OETXf<)p%j?SCHG3B8GxPbJJ-Hiw2YN3#wb{$qeb_lS-h}kIk=*v|`{TRAU z>#7a>#D5pkPijQ&d1Lz4_yqofaZo?2&S2$XSXT;0N)Uko->KLrT^#{Weq)(AwAF>w* z=;Ksp$|`Mckn@)OO4x*-zN*W~|NcMg<7R&To2H9tUHRAQ@3zX~H@H2;wt-6_`{D`Q zs-$+qT=p|ilb&_blRXvJf@8(UR}^P*AJ-j4%RDr|8T)N%%jIVr!c*W?Z8n6gm+YOa zjqI5{XnDq?=-0slcE%xd*&4c66zd`N{w(pGp&dKdJCB_@G`(!S;mpaO?917?@AN$b zGS(Z>&)4Q~ocHYPzwF(U*{9L>k&{|{INV7aLCfC`a@N*`PmTGG_<^#q6wAKb3v#dX z>D%KW{GY`$j(OJCwLh1gd(QLqg+0v0=VA{-{_V%UD~ozolDDn+kMt$%PV8GMig(hy zk7rJ==C{F5itonG`I(xLeV4W;)5pTW^aE&ju;0NyxW)u_a(7_&WxopFz^!thDHZ?b zGGn=hb{VaMYwpCK4XH{0(KEj{^6Rj-rDa?;z_V}#PF>7>yd{3Dybamex6Rne&wS0~ zR}&u$W7(&R?Lf~O`iYh~+zux4pXs4ivKPV_ey4JA4j-;f#&ac{2@UAYan?~!oM$?V zb`Rtm@o0YDg|-w={h20bEA{^$@vGn|u|se*`jt5E8r8IUNPhO+v$UadZiU3>;hXf6 z^D=8VbDiryi?hD-T>sMlhKB4Cw1k}V6JQ7S!SeQp?aIY(Nc+%_fEW0e&^y|{Z@PZ^ z`hd0-{L4NGQjg!@=Sh}=wGt?(m#~j4N{XQL&oWB z`{zJBP+sc7Li+8{m6o-fdz~#O_jNCAtUixpXC3tB-v>?Qd<&@qAMvvuTf@HW@$@PF zH~$svp4#@|KlHHQ=f$7o52WYbcdxM5#ZnV;=5N98g|iMa{(s_}K~2QZ;;%C%|Me)o zMKX`Q=r!oW*}JjRekW~SbB)gYcI+|oKjP;c&;ENGqGvze4#VZ`fJc^!-$Rb!cjOPi z(_vFMslqdBf4Q7i?A+IW@B}-xZU#Gjy#(1)b?H+p)iQpbCD+Qju8=znXP@@ROXxqC z*Y^OAbwXlf4KHJFq(Y|?Pxd`GCz4w_{DwY-@G>y zf0^DG&!ne5wSp&TdAGThmVJ1ozMjNS^S2V)N309%L0`;2kUd!JH}-A#4A@-$NP5oi zyI^Df7Nw$wH^r&fnfqp9FGJRI{#KCjsUr5E*qLw-zb>9FUL9Y;KTbUJk~&f!*1C_( zMbJV{8=QCKo$+b$p%=SWW${hgSSj`*|Qm>7IoLh zF}R^N*W*+9`Hk^0d!{G<^KwzM2e3Dj^8n5natEwo-(uXiW`8GtzIX%3*xw_Ty>>SY zpjC(Lzm|~m?s$58XaQAeXVR`K6>UoFtgoE+ncqhs>w7xRo;aSpse7E>!&-^PPWwje zI`~aFn~$?vX_!Go1WS`lD`%j z=&QbK=KJRt`W13^ruWCKVKi;Mieh~qfxqTIgfE8F&ZFe6q#daJ*ZLYk|DBdKpLe=r z_*pacXj!v){_NS;?Agz0H?Zr&_mK75T7GMGZG43Oy0dGrpTct>^YaqU^Igi`5q@;- zulSSMlS{^ue->2FM*q1}z z1=`EYoOcyF0xp2;mwFY&d)nll#)rQi{8TQ+cPp{%{TwGfB2p8&v+n=7JCMNQ7*>*1>BwA1lD6;0ei}SgI|xG-_`2sH+5$W+(OIz z<$WX9cud}nkhvSr&tAN&N1<)Nu1~uguPd7$e1N>H?O#gd>v^2M*2C-5X0j{s*8J4i)S2G2ePYwIe=^7K(i_v4>-$2ST9&$% zXFFDGUtFWI7?(EM{R(@)*3eTd*Xmm;>hmuAR{SZjAElBB2w2F@{<;mPrr%6k3h#SYd)WTTx%4vR47>uWdY<#dZ`EcvJ>Lgs z;E!RXczcLmrBuAfe!@>ZSk9lx&U2;?&!?rHx1*&d*M#h+y=Z6PtffO}li&x>u>j}q zY8lI2%C0BpC7d;$x|4N~wOj{k@q6*-iciO3D~sPg8}YAXXDr*}k$AU~ z^@CgSUzghg=Q^!%HU4=O-VxYYtIzf**5iJ(S6~>7hP@%{HT&o%ZGM9f^l>%34P7hk zBb&EGpZJ9XkBcE&l+lz-RO-1}f^#Zc`=!6fmVpRd6#{A2i=X}2Dv zmXD+#3|q19(dWBz?$&;1{z~{tYym%KMdqeHE%$R7{3AE#%0yVk{~fAZ4_R;h;bZs* z=4!j8oUEmT@Fs8$WZZwH{j8rMmDUgY6DXI9_o#zt*RXHFkHD4snFs6S@5SG|qF9HE zwaJ)fALlx+@T*o9dp7$fdn(sTt$7D7(Pzf}S=f%9x;a8TdpvtCW7!=4t4^+>_0gsu zehPBlWR9}Gn^l?z{HOlkprya;>spQhTTrhP=uJ{=|}XW)+fYW%!^uMb=gFCh~5i zm+`f7QbX3K&xLugGgPf8&crL&Psq!A*jW8!ZN+aYewjY@=I`xZH^YnQ57DFTg|jYl zogeWsSPrQl8T%P>X0iKen;J75_u`MG-_P$-F6zgnIQ8aBe7X1uVq@TO{&4z{u_3kP zcs#*9rjPFYb~xw9e7X5X%>AWy?2c#9P7u2Q?~BXY)y5CXn?QTs7|*BeZH|B7U!dLR zrQ#d-V0qt1lbiY8U0!NzS3K34%o@uXn%_yYu8(2=K^uj8!xoTdUXB08UG)7G&N$^B zPteb9+TDRK$L+-X(%&vyZ?wBsT1&758oAFi>9Kps{feFMqBh!OujZQhMi`F|rRAR2 z%3J9^o8ya1RsQeabMG$x4@~FZ0HefGn~v8e?5dO{hJY0g#>}9TTfSlR*Gg|gp zTWwEcpU6JL7>ty+0Xuat^SLiS_f>~>r~9cP|5pA(&;af%sTZ`<#cT0Tl5-)9VArLk z@BH1Sp`54XE`+hkh0J;0sW!wr!1ZFO9eMX?4w;+m3FMgH7UaYpoW%@?4ZSZ(>4 ztE|u2_{y+3xfcH*e-f_#i1jEx`zYFi=;dN<9a2$z<96fUBZnX@BwhV(I@vN#)S;getsde(Wy_h!#}E4ymB7@NbzAK^c&--hn# z272mx-r@3_+hyXLvZvB6ghTnK^S=_RZ9lMW1}k zvCK*CwJ~Jg-}B51Y4?cDU>}I{UbNt0@73bD?&h$PHVG!PQ-`Kh`i654`M-VXec?IR z8AZQCdY(~wee-__WuFP)$>JL8brce~sk`nedll)Kn{|H^FES2^#OY` zd_81;+yYBo^Z)8IdymSZCS=UA_OF-!5PM#!ct`k-eJ>LIRB>QSJ<9@McICFooDIUEBELx=kzLmP4hMg=ldaRWe@qO zD}C^C$a-qZ&vkxdZ%ez;xaACdNSmyySLr|C9pDJ|1X$C;HsGee+xK^BwV9$yv@{1LMW6gqPSC!Vz*#!dV~P*pt{F zmWukH+MNGCk*N#ov!_9e%HnspoJ9|?x5IgNyqNtH&i;B^{`+tud#`TAZx6%yd7j0v z9=#!MgBO&Hp+0uy=iYKIp3QE+egI#Kv)8tV+)rzK{L12+BX#LxejnTzE|*&$r&gub z<&4UB?gjVq@5H;qXY5BR)%B9~V7{y3_1(jLxWD*hoIUeJ+4lthcARm^*yk*&PRrk@ zvtF~mtJ0pwBk5V2W7*k{X_IGtu%!M&)_iK(`|M~v#j>u~l#6pQyc=ZgJdN-7oV&`e z16R>fGq+~-WE9X*_54YZ^Nz--vtihcfp5?JxZU+ z&%1UnehYraDPz(Xu8^0rcs$Phyhc0Rvo2yEg+Hff52P+#3E4YM-G6>F$TMUOKPUG> z_Bi|;eLEPz&)==T(YB_XPyEi5y0rm}70WYa%{?o37k0+=K$yYK*q;u+!czVRxNKhX zjz5lnm42(^j`AB<6#Fr?G;8K*{?j;nHUCY{nfMX;bMP3r1u`}{)AFo$v6t)njupk8 z*hTKkV&B2emG-rq?70Kvoy%X3eWrW9oc96L@tbCUHv31@DXvZvA;u)oo6 zB)$!Q=Xw+H8$F7@dC!%X@vpDHHu7I%cY^gw&PjRsKFWF@C)NmWkEe>|y74=*)5o^3 zR-fI)kEZE}VPp0nTI$Nd{3iY; zd<=Q}8LY4sfja zSnV=@ZRz3kk>40T$JfJ)=IxbI@tgN>JX&7silPo@9*>ig^|k`9gS+MA4CpQIXnqy; zw(Qz?Ab*bd6xfU1oYs}SCq5T$U}xRrJK_-hD$aNx3pW^p7ibxqtydLmb_Bbsoc-ya zvRld9Ox~xsPo;H-o54cyJ4)VN@JI49ulGXg^1I^M_qmr#+mu54A{9;{C4~Wu0}tP_NZ}4Ups312|Z`u9Qq<{uQCo>(#DEEDxR^p zpT7r;V80ICp^13T@mt|k_6&SIY{H(dpN#j0?5<_^xT5%7q=Wt%(AvV(%3^Qic^;H= zI6fSo=la)b+lW7|!ZYIW_#Ai^rzJasMiv5dV1-Uo7Stq%*?H_GiRb~65+zo~2g zhtC)P#&7UhgYUz5+O~2ofqGC=d^?z+ZPw33?RKDNoKAIbc^7F--xun#Gag6luN8ZO zb}!>2<@aRI#96arpt)Czjlu-~zaZzZK5j%X{0A za+czea3?MM<3gPGlbgkE;=i}DSSMSva}OD-$Ho7F)U=+oT)#TJB-RjT{|toF<(!YR zH?Po7_T4mgYUy(JGxEpd?AP6C10d@qW4F4pI4g4B?`xa=ehh6KD{gdOJM$Zf561uE+{Z)g)S=$|oU3*8o3YOOa-O~V|KiQW@?P|TwRS3g362)e zePtYWleb8_jKvB3#q6CbJ)d#!g!5bWM)Z2@335(i-zN40J+&x%@e%rJ{%rPV(A9ls zFJ^B%g)`P&VQBIzi*Z^>e@B}^cx4ao(0Kcjx`h|wjLY`yuI_u@|Jr75j(~pjb=qWn zFA_gYpXob)U&~qd8+|kWp6ra(Rs7m=x0*0B#&`rCLCfBIsZ{6}(GKKy z!Z+hy^e0yq@4hdxThTtpyWp(-OUm9EAp0V9@d2@1EBCq?dzt6S|Fy)Q(>{Sy#7E10 zM8C;jP8))=4zsThz-L3=t*?_aO6(B$8@4IirwW_!e`j?7#*EZ}i^7q3}(Do948Xqh-HTh8b415tiHDaXstm0l$bGOIS={M5* z^Y8afz8$*?t(i9Y?ytt)UhE+L$+!zGH9qG-YU^&!ohr2N*^lArIQ4Om{3WjSlQy4~ ztu4sCw1f@Wne*G&PvAHCneV#%yV$?8?}lp7Qv9Tq#cwmK*^?pfur+ZT{Fj_%mBqV% z#_L9Q_UI4vs&Z1R#>kt)KT#~>umL-DY$JX{e%9&za4P=|`f4~8ra{_YiL>sX#f$Yd z4%+fZ^7GrqChVgsJOfl?&!l%RJ2S*yz{4Qltf?=rh~>WjHfK4fj)POQ>j;mDeFz!n zvtW?+hv7}M-&}k-JNH`y2C>)Si*V}b9gsaSl9sxCu50}N@8mCN14_lW(3b3BVzcFW>Qd=@gSLnt9>w2*y~UjB6YA?&&MUv0*Uz02N>b{Br$czwg3!p?p4)xNuS&Dh67 z>ft2Z8uq2P;Aj53Ku0)@wspDK|2OeJ(EqOdJWp!bbnED4cHR?OYkPqBWOMb9d-

z&1_tq<`$KvPWx%^&`ef$CcGMstbU%&0?^(%_|uZBzftg-LqOohW-YYKlN z{Y>%gT(>Sg-*?yQe-3mO`;VUb-<02tpR;L>yq)PD#XIpxZAQRhV%ayt#IjblS!uj+`q&Gyj(&#Zwu78Y^YLoet5;FX zUjw`+EYTKodiGeABRajJO{Lums)r~d!+b${vqrR zw9I*a7x{|4gkFbV7mnvo<$ossHMyIz^DKjKCtM5m@GN)oQ&)DxB* z58)`Bl;y>eDH*-0Pp4##)`#9}}@H1zZLC)y&`Tby_oYnf;i@h1W8awMc`}bn8 zTV3lGSjA6YgK00|muWw+bKeWJ$sBjAP*>nUxJ1rDV)f+hVw46J){o2EGOr{P;0gmKllA3zAZJuHB&qOZ;tWh^c^7gmH$@%skH3%%b+cs zT;aU{j*)Xn*>$vk-aOpF-$VR*+SicZ1YV{sX0MWy_;Sd9pQ#h=V!3-l>iA6k^~M>W zyd(X~&O2AW=`Z2m%D=bl3}xTW&ilst{8Qu{!EQ&Z33+d<$$wY7Gx*o>@3U9Vpq)fJ zhqj@6&HZIwQdi#OFVxR0v7C83u@}jm%Fo=^HwF{gsdZDK9_@DAtkQMl=H22QoV}Yq zN6Y;by0Wv*o)T*&e+@L`=bfWPMX_)1r*&d?hST)Z0WwAh!*ybf=)cg$K-Oc{%$+NJ zGgTUQxS4+we=D(9*qQSy*s0fd!DL7u`92*W|8228a4ppl6>yY0YJwWKTW|f0l~h8G7h< ztlZZ4L)zPLGCR*d3i3Q(mi48N%;)+1<@h!^AJYfGB-(V`eq~Y5{$wwvrB>x!$iA6n zelMkGK0DCfXSWkS89%DO%-L*sNxTbvB)!D`30APPuh*yN@5TGb8x5(i^=YR-J$CBo z7V`Gg-#zU7jyj6|1Uqv-%=qVAZNSc+?+Op`bDzJmuVQCE-{Coa#(ntHp)EV_6n{a^ z%t~Ana{cCVk7F-@qr|Q+SwoP$+7{DbO{p(kDp6LK>HkW&Y{<1d8v}T^`2--31jMoM*k>9b0JujB^x21MF;ad2i za`7E{gZT5bX1F^2PWl?_WU{`SYJUf8Mz2NR5D%j7$DTpI8ovSg)~mt)Ss!m|dmAk^ zE@Qj8!dGRse(JLK6+0Y%F2A?yw`6~>%|PRS1v~rm9{g#!_^!TCUhb<0eXab9 z>BHFhTU=M%jaHVEb#)P3r|tWYn)nYt>*Q>0axOm`oqdIN^VwNvnTtMhcfjk>a>ibb zSBf1%@5P?N&e}g3H`C7$_Ez$|<$@*;lha zknP^nzk^IGS))QxN66l}r&R3APIw`It$q(HUg*T19bAXAZJwhv04L zbJ)kpzf_;OzwC|X@S6C6^sLKg#b(M~B;K9YnD&JDagaK31N}qsCAgW`Q^spY+(K-G zw#SMshhE~Ds}=YRe&(PHKj+gvP?dcoZ9Y47YF)ryf{&m-2dPiZ_4}o3e8$dmE`hw8 zPUL?e_W;}set>H9td->}?F(xnea|Qr`}#AvxAPyyeQBv(v*mxvzlndTvHw%Mf&3Zt z{UP_+MC_P~;(g_OemDBF^1d{Oc@OLfXUiEx{}`u^<=!&qBjIrRp18Jt*U0U!uk87} z6Qs6gPc6n(@K55Yi8)hNm5Vu=D%O~tnt87I-Bc{=`YXAar@1)q_?_5gXJ0(EoL` z7hpA<&ELYcFNA*L2g*$?If0-1&N*D0z6*OTJO6F57qvM_{02zgQhMHJa+ZE*jjW~h z<>x*#2l?$`fmmwztF$*^p*9(ZkJ;P>FXNy1o9X92@zlEPn~Y7qeN%%{V=r>u)P+si**{q)4?}9fP}sX< zu8nK@T?uRWZN!hGW!|f>?|}RDv8Vj^*q72XepMmYxCB>-_ZEMYeAOP4I(6zZ<1&js+x1rS7tpgVC$Xo{vKC)s&x7o{-1}JiR&qY0b>pY@oWmcC z$3t~?YS`J@X1<<))Y60KHDLumdvh22_Y?M4ka^o5))#*cA6F{oZdDK0;^$gFv9F}} zm2)~j<9{LlM15tC&oeID(1MzB8;FgDx7qnSOx`Ug@>8p;m#uwy*%ukxN5sEi*I{QJ zWt^sof5g9-z7D?(Uqikf`oQ7roM~r3zA-Y7jd5Lg#WhnWE-}6ju*b0fagC|$n&P9i zT?)ss^KGB`%06sVQJibr@zdvO`B{@$3waOAc(;eF>x^ag!cOeJ#ZpV_^RL2ruKv;a zSF?|jo3ptmeu?%nt&Vt0+|QYoz12%>D$cp~g51a1EgQK&uo7vgpJt1>6ke_um2%p7&j=iL!_`NK1{S-at&Byv1 zD^|iU>1U-_*7EiIymJTd$UV%yJwV%{Mh^9}bm{+FNsCRgV3 zS@tkI4OfR2`s{{NW5&BiUv_HK-SV?0@^3e*(o+w=;^+M<*ZkUZ<+{JHzk@gRQy-_k zrIu~So(*r5i@!l+tTu#4`M;~+^SKf*rNif74w%{i1c{ujPNJZtrQ_H(!vy#YJ-{UQHgn9I-oO=MreUIP8t zvmw_$3BGY(c~7dsULRM7(eCf`QkDPv-)daM-bL(C`by7M2agfUdd-;BV85Y{>5y^i z59iPu$y=uFK)6J13;2h91+E2Mg`?ELRDdf|-2 zx>fc$d$id1a$m+dOWvU^##g(~yKw5rYqY;{bLcNO&$0-1)lYp$p9A<~*w3;@u+M<| z*_%PuV;}tu#ksew%l2@Eb>!X_yQlAI`KB90`+z+izL(RPedrE-mZqQaFh|6=$7ij3?qF^>-k<8KnN-18=i8k(;?( zEGKKF3O>Uu0}Yt>!thhBt@&_xG0J)VQVc(j9@#j9wY*hkQ_?=vU;*i~rh`!+m{mU{EFG1w8G zBJaMkYtUElr$TDek^Gx_sP*FO#Iry4t}M>&rQ+GQpVIT}S3)(pS-(57CyF(Jtf$d9 z<5CB*2VU3TR|CHe>GNQDU*o1Q9R6=@(?+se={sw8RvGy3 z;rCs09=j2{7VH7J_b;Jd$$PCfM{4s6{}=c1DYW5F6#I?#K7VsLC(+u`7ScxdP+!>l z(7wRkVGxWht5w$Cm*V;VP5&e90B!$TWj6zbpuEpsDo1*GLDazi}&AG*yqBe9!1UQDqat=Mlw!0BlB$8qq+C& z>1XkHS}Xi5eYR`wPrrh{HC}}86~7;6zkH$Zn*4|PsTDP7ZDB}7@r_lBegOSa+7`6x zo+JC`8K}#@kbVk2P);Lu-YZ_v=SuO>@TUI_P=8u$u}vY*mv_5v?D?>{*oVgWOTLGs8vL`IbM%otumSBeobw<#8S^GM`ykKUfqx!OO}ZH# z5zG3W1-03yLiX(y_y(MRr+1tC*_Hikxfr{CVuRW5;MDe9b1?n~SC_XR{cHEM)-&!$ z%edwo$(mcrKLn@tX1zA8D85TK@VBYwiqmoxvJ|<*RD4A4+eAvyZ-`y(<3$c2nB1?0>r#=UnQ^T6*5$`fGogc1Kuqn`-kstjEt8vk7d< zzFdClz=Cq|HeY_;J=!qw zws?hWWPV;@XMg41Ho-@Uf6LDQKJHMlliA(yvoI8X)uuvT+Fe!l4g=@2U&lAmvo{`N zUu7(t=xd5t>hW^zvY%3OhQh;gHi64&TkEh=@muW7udhiUV!{>D6=+B7W*EL z=4UNGBzDdJ@D}}W@!n9C_6C2noNoNA^`qHYJ6WeMi{~3YbCg)FQI)<0zdPQ6_AV{{ zY@Bs69{Uy#Zej50;C4+fIBg|7`dOQuFS` z`KCRFy_0@A!L9s_arS%G*-mUjmIF!+X^nq31%iob=kPx`QD!*q7m-!yzuS*@=u)@b%frG5wU z^KCQDm|eoop05M1iw~#g`9{!Nu$R%Qv2$*oEhp=87CZ&t%Ij4seuEr^^WO<*N54gW z#yQ`Cx&N$(joG>9JbTWt_t-1s&lTI3)|!@YlJ{}m3-a4;>cx+q{o#tDp5{Itp|$5H zw+>F78BBY=RD53@$j-X$Raxw-E!i{0@~)k;Y(x*wCzgBqm7U+!v;QuHNBA?fKU(ZJ zoH~*3sGPBR{(i=*A+9F&A`D^YISZz zcZb~TMEB8_{*8M$6YmP6Y1jNOey?k6B<~4+AKVw8EjQ;+{$596nHjAmDZ`Sc3+ z4xD$1+~1p!H8&b+YQH~B)OKjD1E-3&q8%f?mv)Iwq2)Z7A-Pz~~4vl?>W4RAO2@wog2{8B}cdkM}MJw`uO{7-$|FP`56cHn32421!-aqzx- zT_%4m`&)YMR&KTwVNZtNUpBu72a&K>H^MW?R_@A?HV~-cV2Dhpx>UH+(7+S{eUO5??4QR{R ziy+^)8NbZ+VEtSGQ{Y4T+w_?@b@Lgy*)Li9J3u>ro-NOsy3iL=FLsc-PAv1=O#3D5 zD?Lwpb}N4qet|zp`_BBF-L)%=we>0gd9hBA@%Wsd`jNWyDeY5Zp7+1jVrTF-hU)C^ z<$VAf@lz+3uwTXbts!U1ApQurR{QM9t?8fiu_ z9=?N}JvCo$YTX3dhhlB{hx1<$uZ|z#kE3tJ-$Bkr`pW&J-nXFth!2L1*?dW;;*bz==9}6q!2f>}Nn7#?V3f7g)Ej|*raQ!A?=kw35ENVe=zJ~GiUbJu7 z+u$MPV$Z&cf6?wF`UdP-P+#uJkoxujQe$ z`h$2YeDuoVeD5G<5gcQWrzSiue!4az*{KOn@~5-&Oba0Qy%s-;2dpUe^C9d5VS+w~ zXtM`oJshZ?8tmoxKK?B6j9L0xpS~&kM0gJ#bC0{xu5cgY`I(oF+Eu{ykoZ8HIzACE zE1PH7jsGIt6f{eq-0)3TK_&ApczYYW6O)oQ*TtnXA9? zSK{s1v)He&Kf_PK9kjgjjbY!VziN0fjD(zzpW;6FsdDj~&v2am(+&R(N3m1$QXA&- zGtV>KXZp`x%G&P6{{*MrWj~FEm1VUJx@ms^ZVgvxJH1r2Jq4$BACfj=Gx%4@{Q@6` zWBp}jlKrQ}TFb^_jJ8Szi zj4c=Mb5*s;7-jATR~B_BdwjNd>ha#%!`}&?}e$c zwb*Ci@7-6{XA}N5IA`3ArJ}B7J=`c~tb3}<&RU;ikGzK)^0QtSYhTH}5l_{}MzlOv zPgo*v6P$U-{```Gz#=lPN0o(%8ZV^5Ewk18^H{;;~ zdOO;l`cECs`)+Dt_HX(g0=d8I#P*~OXK&J@7^}Qb+$f&+jgjJ6GoRAFPCnj;ej1$3 z&zhf$Uv;nfmRQIBfc7?||KD)t|0O(ze=(fH&f0xpWie;@H$Io~v-Yx%GM7iN8|1ejxL>=hr;N`y_CI1J8bvWN2?d1BeP zvortC;mqB4*7Wh+tx0`U({2&unrq}{jbEtUBl0pH1KsO~wA;idKt0;7{H)Q7**oE^ zt2*qX=s(K8zM}Y>)4uxop0>JM@lLh}J$q@T{C@08v3c%c3-;c)jo3BXw1SM`5*P^^ zLFRO_cpH3}cyspKTZ}>0eb#uH zegbU{|8-o8-&EY9)uX?wcYv9#2Hns`^pS@KA^*i#GG-?87uOSD_h^|EJo zmcN7cFS4^wFBMxNZzbeg`%XMWJilT5C!V#K8n>0aGsUx>?~{{fSS6Nr??8u&V(jXQ zPm{MJ&V1I??rQ5e+B5vrwJXIkch_rI8!qENAnzSHN7Da=)S$zm9y@j5HL(qG{Gs?+ z`9tvj{ALx!8qFO3NV^M;*LEGwv!?bRDmH?Buy#2svS+%`^SpWYxP>-_-2e}Qx5UQt z*Wnfw_7=|dR>3XS(h%BS@;2bl75fo7>1R6RzMIlJz)X1^_&Y-@IU{L5K-TicV&|C0 zEql0M?Q+d$A!B>3*e^Ki;wAA}^ht8M(DS#8d##&lVp;1O!jbHq}(2ndP=ihzKMg#jpW4+e^23yR&{-AX7e-5@0(A|fa%sN92MVFxO9D|UB% zzwz$>=kttb%sJ*(i_AGiUjmqYe1CR20|A zzS^ejETMfQUQ7NfnNw{uzpd%H-^}?!*?XgPP_JUecJVX!k4XU$mt|;C|`>}7)&&N2|Td_i&^Z$Zo+HNB+XHe>5C-MAE;Ed%Q_89Tk z*i-eH=VjiZH-P2PiI(%ECVUDHK>E+xwinAYczi|i8EFtc9dZt5-dY7^pE+HtA^U#% zZEzMl`#)oS8g7Hs-P<$Wvj^B4a>`U`Sru!rKccqCj7`A&Tmt&!ZrU_ILB`nuWm zwt>`d>LF)#A9-2ZpWpzo?7uz5Kfnvba?dT<9bpw@U)9$xpE)|fGyG-ron3P=J9Sk9 zzs*n0?ifM_?0kp#vg<#l?O^&z@C#%gyayM^`G~&^ zMj7{^cnCZqHl%D%LlgbyJX-|&>E{#tz1*Csc@Mc$ZWTHA9CY-=NrBuAvPZP_#*(b0Gdp+aXQcgZ=A4JQ3If%c6 z-QJk;?3)Csw?k-cT{Gw50^0T!&P;qD&brN^pUplHuh+$z;r-}YmyuB0wN}%9*Vj0_ z2-easz<-Ec$li>d_oDOIY15ydefI`5=FijbJ?yRIkHBliuc|E0<3ZxtgV`rP$U9m; zdB%>Uy^gP!Td$k5gZ~ZWb6Dy;Yn1wH%+57)wjUwy7Ji-;&7n0vby|YdO73w@Me*ES zf~U|ohgTqdWhWaJvr9KDY)c#J;TYdxA_<+`r-xCJY z-odS7!@ZEbeLnPJXB{SJvp3!p_wQD`D|TZqH^%Jsaj>iS?{F6UWSrxTF`qZD;7=`^ zZ^(S#g10r+zwr^SJqu^<+QS~DVoy%gHe=etwRWL3ho|(_4d+Z5tDpQoP0H-G^pSYy za`8MIi?bI}_m%p;1okl2i}3EoyM~@IwuH@K60C)@#NV%U_Q7`g-mzSqA)~}Hmp?=L z*p%j_W1?!*z} zmBku;$Dds`FZ@ez>TEi^pnd9j<5KZG)gbmSFoZrpZt8S8d_cPuZi71dtz!(kiSGsH z)3#huoC%#v&Pm!~6`lpO^mPdCg+CQb?X2P7ES|O55cj0@Vb|?ed`~!z{WvXW{VQ-P zdxE@A;Y0qt{M2_Fc~3)U7zyo4?u(x99=3xY;SnrkGkRi%?E8L?8VPDj%QcH`JLc<{!ibsYqIl>bcLME$yDghULU89Zo;Q)_ap4f&N;TH z{Li#InLi(AjO&Zs-9Ap3C2q-%I=y*BeqQex6#DK8(Ms*kd^7&5yLy`-8Oh{D1Ln{LJ5_`rZbQk<%M* zOKIUz40f|0H#1qTKaoPe}CZS753~3&k=fyZpG)yhVovbZ35@R4f4*U zwSakD%{?9fFF=3DUjGHZ3VH9Srk|YuSC@uGv!U@PiN;Hew6qV>?_58 zz{x)h-_Ji?d?>#k`(pMR^xa^Jaka!p&|Zc;x*5Bi&*d(F`eLbrtnJeO8+YFA=h6=s z?}}f{!eE>UWbIy$1-xrYaU4ys8o5Ba|_i*NKGkO2I z?ghsADZF5ObD~3sQt|$=3p>x6oneOjI{e&I&Vxts1h@j?oyA>mF!h{OZa2pVEzXB%o#M0e-uAw z^7oK;_?$~wz2kjRiC;3NBDetzE?SrKiYMs@pG?z_zUUjKlPLMo_pE(oRjzeZv3z4)8GdB zY<|wrG5kD(@{TgFq*mo-4}U;Ef&VW%`zoJ72k~=XGvpjc`yRH@Za>;<@&|M+o+)kV zFZ1($ai!R&@^gnCfxOzdzxw(Puv=k!^VpLdkp>xJx2kawc>X*nnE;&*@v zv_qhcv7AkBK%0$M(K42f{JJ>jNam@&J-mRw1^-I?EZncp>-ag_Gp}{nshtzW`ms-8 zZ$dwapR=N)ex}m0#=U4w`QP%#vr`8d>l5;S!A*_pHrff0`B_2RAI_7XzBiWl41GOz zjTObSa}fJedgi_ddj-z>zv7eOez*~az%g=flAE>9`Sbzdi;i^W=&%nv=ao9}m@ zgq-8GA1$ueUS6Kni^SVPJ2S?+c5BSn4Z%y>jiZ;SKYS=RFe$lhwGZ7p2p-wTuY zc{ZlsQT)6=B(Ey8hdXFft?!Sn)!a26qn*QUMDL4_fX3|9$3Kv>WhHJX?&=AxH( z@+0YOjpISua@a`x40+dzkB3Xe$MLs@{&1-H!}RR)>ipetS6D*Ndul$vzRkWvtQvn7 zK9#>1Ze(X|8_`$E+ul9B%D%U<$W1L=1KZHPlRJoAt!pvH)NbCrOSoj7`)dE8cA3k& z<<6w<05!z2o*CObey+K%yo==z;lImH4V(yVV_i3EZdMDxYN@^D8bM`dX-c(-R0W!9=P=$XTKkJ_OEc~24 zb`!fotPVf(nR(1voA0tR{){#A`!{Vzya|k;odD0!mhyX57SFP@yO@1TSI-KZ+WeRO zDEkI^>2INShrluH$DpU2jpQt)P37Ot&tAEUJqMrrf5^T{+g-%J6C23RIa0Ka<_>3-p{*`F*xs#dBZ#i$4kLTt9WvO74dIv0{TOig%Jq+(Nts z{U`Vm_7l%%zno>GwP~RLKjr-lH|b{zy@tHqAnX4gZF}00?A+5(d=&167l|Fs?u&1O z8`yWi&9LnM;@MMa|9wSKkJZ?}u(vXKRDwZiJNHuCoRvNwfJUw_CWr=*%`Fd-y8JI?X8eCJe5DxwQ?@44^Nhg=T1ZV zm)d=2e3_>{_%HG8;1@Y7D~dB{GxdJ{L0+aCWWei0rgek7g>?-oawGN9j6OrZKf?O-F>=qLJ;P3&UMFvJmD*Qn4GuZyt4b^FBV^? zrk1lefd7r3HpKdDso%RH^KiSIR@(h2mcE|V|MTVI`7{lGNIOMt>S3+CmEz~%bH!f5 zlVLx0eR(q>??PAOtmBJ#H(V3vz35@~bL{BZqmAGr`kDNmuoybR?Qm12TGDsMe>=ND zSzr9_{E6(G=fmjZ**8IF{w(|$9I>K!@7S8%gPl6hTIXK2hg;dR@x$7D0H+wk5L$m2 z%dhW#r_-jfd(ysVZ-VRd^UiV-`;ME`q(wzGo9V884E1^@?J>avt>8 ze!f_J`U~uPp$Yw$Qt@tk3C`!c)ZlzLn*R!(r%hY-YJ3@dz|QS|+cn~SB!%Xka^o$S0{eg@}3o*Pfn-hqY5fs@tf zQh!gX6?>3))~K!cIDU1g3A1ULr#IZwkL-E0DX>qaG1Bu~%l^9`e}vaJ)|?9&TXVcG zJ@=LO=9~*Ti@Le)F6?VyC$ZJES6~u5_nex03+Ly22jFfnxKzCV^kQGnJ{+!M=R5vK z^poFHOikza>kp)#N6UM}&a?~R663#twj*RNd&{{Qr~jt7vE0;a#_|V#6>94H(5}V1 zcn9{s^7@G1haZH?lpt|&e? zrS4n9GqmYqW7)^RY5cs;yv_cLp1Lf-w)}j4&V78t&wKFzxw+q*^*tc9rhI;f^#1ops}2-@%=d0T30H*k6nZFj*;{ApbFO) zOP}-cdvGgzk}>Qp_OiSsv=*+vC;#vj#pj-H#TUstT6}nC@5{z;2R~ZAFUX#HMJ#oi zvoZCMe{=C~S^el^XPmj}0!OlUmfIV4keBbdYST)PYhOV>uVgL64&}ece~O>~k4Ia6 zUwYOo-zn}$-{>)Oi8IG}@7fD?63h2EsiDsN++%*%tSdfE+m~pmwvTl3pM9r%uR z9lo@zCf)mo=5L+cN9kjrJAIsZYCh}T>{0VA=NMW$dEd~thGzVumFA0m8|~?`v;JW< z#?HJS2KU0&u6sKzdwNj0IA`*GboN6Pu{~jw_!MZNzy0Lp-vVVl2gtbqFQwJTjpQ7R z^L@rl+)_N}LDpckHb1gcvr}<%T$8>BULP)FXD-^|Tk+RWgMK_e@4{EIAH@69Z>24& zH2?4kETZM-ab5A1#ygMJUVfhU|KdiFe!s^#tAnQc8pF@Ioc+Ddm>1%FxAL*}+p@<& z>Ug6GJYGWUGPTq(7+*1YrGF_cnKYcC1zst|r_a1J@PYte?|A^~% zc8#yu)w-!4d;?yo@0oHQ)#e}hjo~w~7I-slQeS_S6=v2wy`@=%$Z#;!{iZ08EFy>G#r`xK{S&t!HT2KehR(fyOYKe=+?5?W^I(@f7!birDq+&7h8)|MmZ;1 z@5XP)ds8g+u^&Hko0?dOm+Cjq_^kJS{LE4I*r)9K;URw3{%v-HviE4?8-;WBeNM|9 zcZVHlpSyxp?ld@6zXr zeZbB>&VJ50o&D8Mes6w@uEqJ?jXsy(3Lh&T`*fV^HNfNedyCb@`96OO_EGG6;WGX` zc)HwS-RyZ|=*jN`gUZfA`UC8I-A%wMV2`e(Dnxj4U5<2l;~;Wx#9!})!d z9_)-cXXwZ5JWHGLtK#kPk#bJMoAQ6srlO*FUwe<;mzHz1Cq7WlVQ{iO^0#o0W8Wz@ z3tv?(*5uU{#X0kuoMUKz!bIoAEAmoH!|~(Vex^-o;(ueo`||%1zY$L5|E>KAv^>|Y zr{#U48~shFYP@yCPGN5gd0(i?&zxo)d465Y-UP45Z^+&cR}%<% zg_-iMgj%%JeHXZhp1zmhU+}fGV;(NvmFv*nqUFCCc$C;3>U9i$u+rHNsn^u!cXIpS z*|3`ZIewy~R@mFH_kqmE-rcmNoy^{mwt$^8r2#+Z=svj9O4q6^&id@dNwoXqW}JEd z>c66RznjP3m_AMZVtP$}E6AEH!8wao<93ifM!?qc=HLVLk@wOr{7(GCwYj-mJU^D= zb6}}ho}~@--@$$Nk#`HN4Soeq6&ofmpYwBWJ`5j=&!;EnR=Am+@0s!)+=244uP4*; zUVVjn+(Q1l+GT!zpiicqf$uf8%8buc;{3X@#7OGxgR4XIv-4HSFx! z`^D?ASK`Oy>_Xe0e=r`2k8r=4uUuz3WL!-DxzSc0$#^dFD04Isx!*3uz&-2tyK1Z&V zpR=J3T*Ka8d^@q-p@Nos&OEGQZ$Qh>Agkbq^^wn8qj2th0c{p!-wmcsrajG1d=cIh z?z4Ue;t$Kk`Tvsk(<{`4KDSOk+HNM^4|kLEyZCH=8+Lts9o$Fjh%@%A|5&jvAZP7{ z^o8)HKJz=ZnUfiCr~dY$<@=_0#h2N$c}EGu9N+iXIid#58SD* zyeni(8_1~&Td+68`3#=gJdu60+@bt~D%A@+d$SSy63CgB_k$G`_PcAocyn5QtgF~S`Z8DVQ zq%OawRe=HQWwiB-^ICR^eHmnbY@yG)^|QuW{!~#sCy&EFimw!ZNPIti)Ucj;x5_@t zcXFx4JUfn))0fs=eiQmMdmwX{1-yCL+{RwzCy3?#vhJ6&AEl*^uE9U(|55o>@w0d^E$45} z#!cnk#J`q38+XQ!>Zc2PAv<-O=i)o;^nC_w4|#6ncM-CVOW9A*YOp&&2l=}}p0SUV z><8Lt>o|jbmb^RV_kyD!_mZ=35^mPjKGxEUvZ*6wkfdB0RFp9>!5p8|gxZ%uk)&qC%h z{!3-wi&Q!X(jR+w$a=lT-xu<1iJzKpB5$yMYVaTB=Xv=Mt(v^N*JbW&>-QE~BmRHz zIkb?Mcesbd_vhywp(DF0egX~@-;Li2>Osbs?~IPXTb7Hl=Gm0JyGDNEx6m)EES|Xo z@mBQ4-HLazjI|H#4fC>tyscmuzm53aIM3OI%lMf{UToM*<6+HMAUcYGM~UHAR+&VjnnUjCi( zuadJj{s3pcRuy|s{ww?~;Vk3)ik4@{9@?bdPl7xzbJl*~dL6`bHavsBrssS3s&cQP zP2y)?Ji>2}Q=6%|2jKmRB7W6My6e28eIv*@`HXnxU{kTh_yXu#F5acHUN=JOr=@-; zvhy9l6YOg2?1f*-#qXV@F0wc8qGccb#7?cZ7XO<43mgPT@H3A&@3VFf8qe4Kh0s^u z2h(DISXq1)ebGH$2Pe^Xg-yh>zn@@VkH3^R0k-64{O__SR`^_ls~Tr&=}Gzva5g*l zcOyOmXAf@TnpvOJ$DS|;QUiJZoPbC2>)`zCCHIq@vHETfHQ0Hc{LF4Arwe~H&f5IR z&RKIh|8;g1cAke5#ZKneV;_ls#rx}{2fhvNkl)I+x>kC}5uXp)cXP}0<@0vd`OFpW9k+m<=>rza zOTX{YQq!NvZGiu!J%zLX^A7h73}D|$8w$hqwFB*FoPCsi{43l`%O1#mga`AN$ZyL( zn?Hx&ll>*^$bS;|hu)>)^Yb?PdX2q`mRdMQ{!H9M?l>4B_8fmL&N|P+x4ZXk;54}} z@bkNq9ppaA&$*M&9^divE^(MMuoKQ2Ps7j2`HS78tiEw?{tR{-cvEir=_l`3e(pVI z&0mnUdXv9ad^~@5{3$*Q=bTTCUM$`RUxcg6&v*K_vd@P6oZ(o=`EaXPU0OT2>+?Ux zZ^HHRGv_UgKeg9qFYAfti_L`vmBt2nmstp-;3<0IO|_XI_j>W;=(n*`8+qns&zwZR zPhOe*1f+IGXmdKw9G*nW-+mgyenRf0{QPWgJAURh*ZY{=fSvO(dMD#;!#+xE6Mkyz zG02&j=ifwjEqVuePvOkP;1xxUr6x9o8|392T?C(%>^J@8z2q}~?!6V>3-S(r2YogB zMEce2e4c4e??YQ(zy0ac*)!>f;92k%dq;X@h3nvB<^OG^wTJ3*r{Ze#BU~?Y`iywyWD30lu14>Q7x0f|XFi{W&&|nQ+Pwz(`Q%hNxn|x; zQm;$cWAyc%eUo$RXZ{^x&*1IxEbWeg{*d{<75u};;D)|>4@whSGMEq->IUxL;Os3?XJaVr98Ke zfpcitck8h;p4<6H;YE=3`VI0NSW@Ylkaw%;kn?I9&e^h$HrGJbENk)u-U{}D)L1*Y zbzyt<573yMdTA(rGe0#voWB%0upbxSQY?L)#LqRdZ}WSr|9>2V*L&eYAf?RiT{E==F$e? zmi#B!S&vHN&3E3_X%A_8H!eZmq4M0D3lGStjfc}xcWd!-$l5H0*D8v!q;@i<$4bTL zw$rr9d-zY#TDy9#v$6P%^tWia=Ck+)`6om6=60|zqz1;|)aB--;`@XDXwCQ;cfQ{^ z9CzjCtXr&~=kZcFf*t)9_Sf2^&JR};cknNOd~Ul`yaWAXxd%b^%rw}HevJD)PizMJ zQrdaOy@LN1Kkp?u@2;a~9P8+#@VW4u*zP!MlX|HEIiLID0rK**wA4X&cFw1)TlUTs z>~VM~9z*+6ylf0N;QWj^XUVnnkNAz`WluecQ|}Fp?FRP4>{-yLT%40%von92i64a@ z)V997)cH!-k@mX2Q%ldl#r*7@=W#vp%xw+%S*KO>yXc>>>(F0jZ?0{tl||c=_|NdO zXFAE94gH~?+=sRK1gB1J5ziifLu?y%*8EiZ2avt@K7Ag{;Ac*=rjz-*v3G{M`HRcN zT%F24k$nhlKb)G$v+FGRBk^S=Ysx-Sd}zkXUrFDMJ&xX)-;|$c-AU}!!Da9sQ}XerhA%t-b|$$H+Tv=Ik3PmYR`6fbZyz~T>67Un<`#yh;>xUci}QU-$iB3&*JA9 zxj@cneVpZf@?GwQ+NHMh8FV)}Id|LQW;pZM7B7-BPTLo?%bvcDpYH(1ij~+Mo{l0Be*;MSzc;5^PTmd&%OjwBj4k+Z^~{bb_Y!1zs28){WQ#APs6G2 zS&(_n8N7^s6y*0VEAeKqNx8^Rzt#Eqer-DK0r$B|+tkI0knJ#OkHNohv5IT`-;6K42ARMWPfDO_QwzNmzRon?bJ&4;tTZE><&2d zIgkA}{eJdZSYU0lMn@V;L)^d|=Y63%`yd!0cOAcH$vFA*jWO$U9(@D64=vvbUdSG8 z{!)|k#PYtJGbHmqAKKBj5W81jbNTzSm*P!v>f{VKk39l12eqK7J~qb3YCDKtg`Im( z?Pkq?!sq%9^;5B3=q0>9ex9B)D|_QETmcu-b4H$sEA-n^?htlMId{-6<{V@V> zZ%hN^=JV~N;-AnrqECba`0d3mqfO_xW#_Y5=Clic9(1DZ$G=!k_U;!rXI|#?%gUmk z)MRSv5Ps_Mb$<3tN&B;1w-=s5dlQbO?+cCC8;W(-F7@yjUdtcB?!nIIj6d1A&XtgL z+KYdxwa$CNKeU1HBKtM5=8*F#b6wMVRo`cm;s zx=miri`3&H7@*B$ettHywY=0*z6VRY0oq>=x#sCOMW4H~hv1CAg;;xhsJxkwdfS$E0c=6fKHa@*@y^*!EPF3w ze3$k=^xOJ~^S2v2bCLJC;dqgpf7pG*+u)4pKKkbTsgV5C>p=D$kh*UMGij;!ezfN* zis$G{{LNrv+Of31**D_J{Hi!L)d%-46=%jJ;+N2l!Rfmvy)Pb*zw25&i|>@b9^4Ig z%3Cg;cZjU_C3petS=x8(Iof>4&pCaV=gzL+lh1Q5aQG6uKfGeOwWpT!2uD0i=ZZp38 zcMtm?@frLHcnIXazs5Nec4R;6x>>t@<#fQeiRD_Ur|vjs(x1j&n?D9NqqV4T|B&ZP z_FCrbXvq1r9N!JKvuYrVv~ z^Y_6G`So$mfy;1D7)N^>pGLb1vTl_)_tjmWkK*Li!I$DI#a@DZFLng23uFvWLB{lO zWpPgT#d(gWj#kSbF8(zBk)Hi?EKGxkO5XM9JF)Zgj%Dn>R=AG58(}#=^LmwdYIl2D z<|;p5{D}V{q?YH($vOCmwqLvEJM_%U5WES!qKkWya}Xr9F`k7_lrv0he|F}xIlU*X z3C=Z#({oNV;7@^N0lgXR0I~boGufZo4`+yN0gd=48pGD?+adcVdptEhRjjJ9&t&gM z%QI>V@r&5`Oq_bjdC?D6$bDG+6!vm@YH?S{y5;%XgPpl7iyg$ik-ax0-WZOd-Bc=` z9qaK=sx)r?c)Y~;Zo-GrAEzBHcL1J23tG@queZ}@^YaYOUTluDXMWOt0ptuyt>y2p z=ghAG8}jq)Oir0Uj{T6li`kpghPcn1ZwJWR33h?^`GZ&3H}XD%uCy)jW%5UerEX`j zm#|M@FOb_({CL__e$Lc2v_ne92}kiS$DLpX+{teaHQ-czTyG6tWY1#1tDl@F>$6j9 zThULzsnOTOQg@$=rH-`Y)#p^1IaZP7e zI8&SAX2$;>eL8Ffb7-l%o3yiJa#q;dz%|3|zL#5}AK6c{g3}_D{jbYQ1`vnOkpJrm{Mjfcs-mX>wRJ(gf|{?7Ps+D7u) z@%M*}v!DE@#HvFpeqYG{@AO3eYM2BgV1jmSO2v1K=gP@Bp8493oi*(zwkdxa{V{fG z<6?cELpv2Rzk5Vm;Y^f2gguX*v+Wc9jkqT5G@LnK$!^PTf)C>VBxf3bC_n4b2|j_h zT;uq1@tyhh@^WsCca3T*y{Bk1oK{ud&iGMz2f?xQ?Ah#r?4Rx2?-u;~wd=`m1|RF^ zQ1K0*1?&P>%jqcA4S!bhTxQ>|_A^Jhw|89g0exf*j~45P+seHj=e#->{?+dg*SnPd zxZDx)D#W|PKeXrh_d?!-ayHI}!?a5uwb{4O|IltF`!jfg-vKhmb@};@U?28@wEW)o zVA{UgAF540ho|oc_*w7%_-b5(K2!e9Vs-ef@s(oPYX|V>;D@w-68Gn4Tn)wVhgaC2 ziLKMef&5$fRpeB`8T)bc*7TfBd48WMHr#pCr=s{=l|8d9{olh^}rFR{g9qg^ZGTg*@0Wl!9red;yy zl6TZD?0(vuKp#rWwQ`-b8^@o?u8F_k50{tvI*#@j3}JVt-|ISg=GM_io||jg>%?-M ziL`U!CjL6HujKwLez@3t{37i<=mV+A3*!l&2|S?59gz44(H zo=xnm%{KfOjeiEMHl9pt249Nb19uzKuk>?iS)0=#=hGs7o}YdBo%y%3YZ~V?xd-!e z4&?pm4DDCRS>tHm zmG_YK8OvYG&%0=E+8O*B@B?fMmuj^!ifq>>tVAnZ2C;sCXq*rRCXiJ7hn#g00y3 zH!p+Ov-S5=$upImXZS;6(<;{c-+6rweF7XqpGdz#n;!Dg*ROJN*0$mQAZIjWE{~*5 z!y7>EJNLK)JLk=#a@O&WffH$6@eF!(_Koc2@EvBt!+s(V#lVUr`O}ao$(*T-izG^7P23Qn(T>X z^J1JM*u7wX=u%O<1H8cAyIj1xE|H&ipF?QRLKVpKcpFF^WWP6Xofoyud9ofn1B3ag zj~491wBHUNS1vv~<{9x5?Q%J3znVUieL1@yJPw(QrSzV#3tUMXOB<a6YzOryTISZ*`1bpt`6J5jr7}a#+W(G z^)sIA_2`Xpe|U$zC0;CcIPA)Q2--h-9md<&0XhuJG1|wm2iJKr^?BF zyrE6btNYle(65EL?4I&Ag*Vxsil0w!i8F@Od}`$@{26?!|9sBvC#QnmpVpe)65j&L zX>Fh*T*2QOpIR>V<5cz<+9d54vM;PG&V}4Z-W?Y4%j{R=429H06ZdqN+$HqP#R~o* z^#47>XnB4fC^vO?5`QiGVD{_A*^573{42PTeT{1j5Nipe(8gCX;uaa;^jz9)YWdmX--KcD>*T*-ez{*yTMF@d&>y^}dkeKdgj@Fy&CKUZqo0&ak> zX^mmC%Hp}1XHn*T8Egsr$iH6R5A65F{^jRff2P8_D}5K33z?6LY0F`?Iru`(Rj?m_ zm{{gFp8*ERy#OZj>&e@Q{hYkE^4?-+9CPsVa7&3VXC^H*lX=K`rl!+xXE|rk$FQ@H zc2O(&ypi@fgI*QO&mG=^CHx9)r{GEA-)VC?JJ%S^Z-w`ipZm+XocX@fc+a5k!F~zm zLGC?g(I|Q+_8{C8j)!UV8n{lmIGa|8Z|<3T3P0z=zU(=))M-Aal-U>2QibFa(f zr%pS_xeMPwt08tOe8GPLXFc!0eZ_j?2HL#F{$Bha_8t1pTnwh=IWw1@?+|w8=WO~H z*A@RAp9?wTa;@ojvG%FCFZmzq=Noo)xjTy0a?d$KGS;sAtov!Ox4cGpgtj}Ci|-4b z!%xxg(f%8K)fE4W{Tbv8+k>_dE$;!%@CC+qwb&;7^Wc4U_E>5t&zV+_xHdaKuRNAN z#*rWBIxzD^DOJ8hk*>88VcZXBiKj^y?dv7^c zL-$hg`L#Cf0(R;m_mQ)+ISiA(RJ$&aXTka6M?vcJT&Tj%o=nYc#vTXl+1SH3g}Wjg0eS@nhjET6^*Fa(`wYgzu9x z34UbP#-r)Ehq3(i_zmQ5hxeo1%zhCv-*q8pXU>Tx;vMv#>kW0iw9ow2q&1W`L*8z9 zE?n#Rafev0xt`oHxEZ|#{>krXK8;iN2jEFK^ZTg0m-RJ*{aK|m3Ffg+gY1Vsav!8! zS}M-NLG%jRFxpG(tiykBGC$|%32+bOJGDI1Qd37)6u-mJL?0{IZ_qojj}XiI@xHE~ zdO8{EK`;8Y)*{d7to3CuN6yRmQ`#Z=xRc#mot}+5xmNmom!7t_vq#WJ;gASNV zvOj{C`59N95&2o#GO-!#`%A@hs;by(_Rh5B_z*armUTN*?iM)DppN(<{Fl6M_4y3{ z4LqLqIX)d$LM{6Gv_~ony9=#0Eo*QWtvbBJ{!7k8cAja?*r|ooS~d1eoO$gdw!rmn z70Vpw{CJ*S3tB@1m`r~GKMIwV=0uyF_0gPuGru!^BYiCA4-wl4X2N!GlC$J_ddBf4 znnF6MbQ|5g5>kaIUbgGoItg`c!N7EYtTT&WJ&CHO*YKD@}znrEz2T>DCP#xf2+ z0Zq%!D1LQ(3Vu`0336Lo<8HL%&!ay~>&-q62C(bm%;mOXgUZEs10(P@klNS^vaY-E z&obWB#-se4v&Z1m_aGd=G}KeLaPlXGMd`zQ0T z5ch&w_T?G$)ZxYa8HwxrcX*aPM7!WU_9%QZ40esLOU3(FXL{CrSVi$!Iro&k_!#{L zePq5H(>k&nv8&PFbN$U|UEo*udnCSB`#g8@EXn-jjCv8q(oWZA5Pm=`b$kfD8y-i` z+H5G+5>7L&Par>==poivYzqA-x!qiU(?^TX>23MB{+V!?oLP7?`R&*@Lu#|SoNMr& zV!yJx)3a8o<6ZfG;ne71Fr7VD{3X|IDK-n9q@`BIvpeHf_;ESs!5H?j@FY8P_c)~X z^IzsIb-hgNL>SD!Mm#l;vE(cnDZU1;gZJ1wLiWx3mG-PL3}NrY9xUDh_AM86K8W6o zowKDkq~6|wtDNxgsW@M6m6Nk@vAnf7b-Gf#r~aq% z^GwXyGz0Gp>z9i4Yl^?8-%?rhbrgSV{@rr2zS+M$-RG&aoTb0u)I#?3Woom%c75q5 z;@{vZc4~7t+{Vtj=N#Xkox0Ci_lW+Q$j#q2+f+ZD#Ab+#Kd1$-@F(i84nALAo*y5&{v>`E{+|3za7UO|D$a(iarW67ytljw(2YMz&XMph zJ^k0!&-rRJhKJ2ll1u7|nw0To4m?d0do$a7)~+Hn3*{&To9f16VAj7_a} zWZyzR*gdV!zg%7w+9oiHUm-v1y9>VqJLkrOcuU&L;&-#Nx2|4M?9rRVGv=wZJ;Y9i z+WOBmw`b>h(^_nDrFr(*>rnQVVj26nICC^pet-5u(4C$+%YI%7IZN_R+y);EE96Y$ zXFNS1&%oK*|01@VSk9@F*o|RqS2iR`o3cj24)N8{1>p^D<2 z??JIW^tS;$`=kU1K}XucfY;c^xU|3w8ssS&+T-S%p2# zZYcJFSPh)B5tKb`b>;~`AKjm)|f#DpgsM4xwp`V%;WCThFg>X6WZ^cJ3qdJwog~+A?`pK`+{Dd2`@lr~#?V z^~6%Y=R!VvzF1j&cHNb}2dw62-=*IE7N1ov_QO%M4cND9cOO0w567vA`%BJRV_D>$ z`)HT*DEna}xDGO>`@nGaonlAh?7v*IC%Y=XNc;}=OE~-G7-$b$h@As<=sUsx?x)hc zvTwzECx)}v>&x%Re+;iOmYiKL^S6TiUH2$R`^@9ru$_2Aeh={-*}1O^VG_)wzYOQF zYnO`sb2_^OHTlnIe;|_z&aMWS;*6@U7x`C&~PE9PKXW$#cCBzN{zi~7WZg5jSJD^Z2WjWhs>AWL2{4mZiL;mgW53o_4e_(T zPNX$~e16E^gw1!29a2Yg^Q{8iNnm+S8UGMTc(`Uf7P`BjVp)H1Ipmk;OyB?GE zwWHXt{MNX!+}dyu)RDIfKDgxh1`o*j88Vlz;GA9gyMzx{*n{vQd;?jNto`$q#pmbM z>dX8Klpg+gGtK!f(R{mBr_kcX1VMXNtYXZjP(M8IUvMw*Q;sr}+m!3vDaq-71#P zezzObp8Tnhvn8JozUpM2*@wet*5C;37O=CgM{2jC(mM=%%g$Us3V*Zv)4Jos5`(wk z`wHX6x6#kk_d>ZRvxm8M#+mi~2j|=GaK<|deq-mlPwIaP45OtEGk^8*4PxnM6n~!Fg>p`!Wxrg(ZzNWOpS9VY zy(d0TZqAnce|oCWGWI-Avu`r)O=yp^55%dtMsjn{&xkeP9|G_5H-**OZpojWHNtoB zm*Hi&q4=fjop9cB_Qxaj)q-}kzWQnN(Td{v-i3CAeV6^WL{9eh7BG+=dp4Y;{pA(K z8JYR3qL2K4-8N$9`?wwC4v8i=wZ2g9)9^n3WXL-1LeC!A1h#?K`KkXiv>nVJ4u`WR zKBAV``TTG+Zi7dbig%-}UE_D!tHytR#d;1b?=JWO@qX^7I^KttXXT@KFwBQj| ze|1X5XSmD5pRsQUr{ptn=@xQ z|0Xzxe-yN4*MppSRV$0<(hIJYGcWy&hn!hM%I1UK64GuF{aD%>yet0?ypEsmE#774 z{UG&I4gS+6=l&m%JvWqg3Hwp@WZYD{pKzYr+3T0^U(@C(c{zukqs^fGgRjOP;X5mf zv-5AfIqgAO&Yg_0jhZ^1y{>C)215YjEZqE3u1(K(ygB=aupdyT$<4Ua zM=kmeup#?b+O4o9zXW+_%kK-VqGdj^7gN(?%EdcR5B3Y}(J)Qy9(;=Y)b?KNTT8{< zrDn6HEAjdC%>M>rufdV(w#2`Qz8YSIoI5|7%f|HVjm+tCXrq0e{i*%?*zMRE&xK{z zgxz2Rn64J4ia#wrfM3U$o`&aj{St?@#+slnvtOuDI5+*9^m&e2QZDDnJ` zb)ILtu=l|y(l>*RjX&)UVn2ej)+^+s&O5^h?Y75*X}Q)i*Uj(A&6byWc^T$vw=d4$ z49e$#S@L$%Z@woOU$UR@1$dIY)!MdUPm!}VKAx7JLuWh(@$X`PBQ}`76}%(du!HXab>Y?sfYaCwH^8QvR~I{YQDdA!yYMqZnlR0Nmt{i{mQP750d{i z9tthAnM>bMyMed~&bvr{w*95}pOEwUI(g?;7W0sO-kjE6Ue@ys{G-?)yo|q#c;@~; z=uNu?vOf7;>eb@gLgwfn{)g;8ygxJ%pHCZ9S$uEQnf(?!&yCdSaPb-J_3%p-#rp3P zADYrqQ8EieZc?E{Efyr zlOBMqPk!e+XW-wkPHYJ7Bz6aGSuW1YdHlWE*^e){zwCj|C4Jz0f0gU!esaHc^wAK; zLF)KQXav*vzq$TAb`4{%z`ZJqb3J>oUa2_mf2Q|)`8s<@6Ue{m+(GtK71m-BW;E2UsF+hZ_)&RDz+Gx#WM$+!U6nx?&(ai%t31X z>q@oF?uSzc`5TE#@k4UY!X5EvVmWI!gtd@yZ38X36lYH>_7ccD>(0dy`nL6549eu>^AU>eN=M1=2pQ+Dfv}@#KFLl!PQ2tU}Z!a||r=3{VxFP&4 zXFSf{%Jcen$T|-w+50d`yN&T=`ds!EVqN(q{#NWC@Id~aYAl@R+aJbuA$_sOY`mw3*GC-~#oIk)zM**miX{e@qC!fzLx!Rg?Sfy0RM-F(SKyu7n{wl4dd85z_;SRyVlmU!{Kv&u78vK z?B|7WTdDZ|bSOSv{1{rh|3}kZfL&F5-vb8$2~kW$KopfmR8$a^_ksd~jSUtmiXDhu zbcYIpsFVl-lC}ue+HfmIVZ9%S%d8BD(uJU z4RH3;0QQU8epXRDk0;aD!eFt~e;4*{@Bn`y|1bCt4&!&iOUlI_dy+kjb_!ewM?&3F z@f|@+oVv;$%k!eH+}zXV^iT0YcpiSA-h;hTPFZbSs#eaW=S)9T>_)M7`F)`ZJPFt9 zcW<%OT^snJT)bnn7hA)BuH>9_f4O$9lR4fOXAK9!csNj-A@teoyT6ret9-9KO-%+B{Cr?FSCpM%Tf<=M5NoZa~QL+UK&+!&no z`$H^eYwE3&_S>?j(5tZrv&X|T{N?=PapTJ3dyBjqeX?2|^Bc)|L;H!-i#@INp@JZJ9>?9_Go{ao8E@ldh3?&CA@WnvTQ^>J&l^!qhj*16a_ zkFhrv8x7h2H{tAuGqfGapTRzpeLrO1Yme&vsZoR{nIw}-pg^=KEdZ)N9q*Mr!Z^R~Dq z{zv?DTnBQlHY*kN(;r^~e~I^jhU^a7--*w)208m4rXPvR?)$V#K6@@Z*LzVc>yvxS z^&TlZi=Y;y1~a~I#Sg{P@F{SHSbleUmG(G0f0k(md?9u+f3SA1K;7h%=hKZLXG_NWxYz~k8j!YK zp+iOSo9G+zr}Ohn>jy2~rz^_*M7(9zI@d zlItWl`ziC2aX$*zvX6!xO2wIycZ2kM3~hn7J9s~RP~JNB`R=KY*ercj!8wNp!8z>I zRO;d+c5`@${s?Vjyc}1<_2G;P&qG+PkL>#ia1Xr+PF=huR=rf51>5tNvZvvB_#d$o z*=NHb?SF=~w2oqp;ZXf_#+Qh9z}aUr#Rs$Jz!2lh{LGe@^RyD*#BV9TEPkhV`^?)% z^jmTE&aLvYukVJP#j}2U(tAQj_CWeMWi;*oD43-VA4aJ=m=(oqhU9y}t%| zf9-^?fxM%1s?;8a^E02(G8YfCw}Za?v+#J@9~H$Jopn2sp7-6{%YpoN*)`as)yuy8 zCE{884dFxfp<*Lp3jgKG;+ZlGUrNha*_r)_KALnc&ZUF-$BJ!@o8s(?oGICdpOm#> z4}#Qtp10rO!=V~nRVvw3m2x(Pr`Tu0hV`hNE3&>d1Ad5(@}x0IK8y|97} zsnek_!}Z4N?*P1&c=o~yc-y|~g|oKDRTTBKT>gAH$KoZle(VPFvfr}rZkO97x%4OX z`8MrZ$X@>zeiTc6?nav>C$-!cc5v-eXjkD&wL5|R6l@Kd$NMUsUAPT@6TCS;dvJYz z`pDpn-(2H4dHJt9?L1nZKZ|LN&0&5cxtE=1XE(T> zJ-S@1-QMiSApf4iEU^>qrCo8(p$)aG)b}0y?171u#dzn5<=R1&YLB$BmYr;FBr{F4ZXvD7LTtizB{N%Z%TWQwoX60yQf{na_@iA z?h?O2Jo7&q=E7O-;Uu{ii)9|$m3_vRKL)mCkD^Uwep%M`bCNSTzl}7fXU+%X2CxHtNBEiD1n2x% z2_3Y}Gifb7dCm3tq-(vxzJvZ3`v7)-{8h==U^sm<{44D`u}c1Gc0OYcqUTI%!mo{M z&@zYVqq)3{V>0~0-U@z&+7+IWwENhpw@o26kTp86vUsn(2rr-wgPh$p`QOM}fU`Ca z&~pAw6~7R_F1E-Ve?cb6!lX9ZAln zM&iBr+rUTs-{jE~bz5*`MgQAG?Qm)@nQ2)3ofJ z7x8nrf_|L7zmu0W`;R`2wuD`my%xUb=X3g9wAJi9H?y{DefCU!rsjXb8&wo%a&P_* z>^|CT4?oD?1b(J(C@<}+uTuYV{(xs`sjKzqXX8g;8+LGpc&>4fes0wM_tnLDmHD5^ z&zuZ|2Ih4cEpyrlr*5(zFNAzoun1@EPJ_&8?sXsbdF-5<)gbrVmOhd;qpUxi^J@t0 zcKjD@1Uw7-R}}Ax`LlKVieJk=6KBrXmz#4f&;BpvUXIVfmsJ+ew;^(}-}j)s2D`zD zw0*>z(KCjIu(`g|Hs{C-?EdUKbCRkLA23wvhb{eI$OFzdO9ZUx@F4@$_x^-Pu>LvxZy3kJ^vM`@pG{#pm^v z>~`#Tn`wnOWABlhH8f#%Ie)>F@*1EFzoOV&!yBp+wwxmzT zS(91N9#&Nr^H7DIHOe(#mVX@keA*xEr|<)Cn^+y#OzdztmzFkN@C7hiZuawg_)(|> zn~U#G%iK?>D8BbOn$`+B8S}I5cNqOP@p;fkZa=Xxv?csqar)g~-WBWzwMqS?*0zv4 zLGI$t#XH+nTK+y>_Ekr*yV!^0?5CWSbKnfP8?KTw4iBv;>Zm_nR#`mn2jj+K)o3kU zZy`JLxh?Juo!R%`r^M#5bB(I-wCkl#Qlo45UEu`w){xKHxyPLA`{R<>>&D$iY(vO< z${hYNVryLYa;QPip6CsW;YRVZVOx5ua`7HAo<7!fpDY!>33cE%6VDuFzORGc{1LFR zoUH3}w4wYZ>M*|?f5xY{(|TD+u)q(S&N(p*+=Pbj93Hqy^uN0JkFs%&K`pg z#Hr2a%0->s&R)cBE;dmt?+Vu$_qVj1-?`oy_-V1U&0fqt>4R(Mfax>OEpOSm6cDvzHW%2z=4|dL`XXGq} ztl|35NIYZQp8cix4E}fJ;{Ee-_S5X^#a|R_$UaEkOnx_?4@QbV={gx#W7=1+8>HVd zt|opzJ2ii*_5=7g%U^_7&~iTPhf`D8XJ@ciLe}F;v60$*D*qB%-cNea>%wE~?8V!( zsetj2?^Ex@!{~oh+6Vl}?0oN$y|t-W_EGxC-%ZXujpJ9ZaMt=v{5F1!f0B6SG53-> z>rPvwk5Sg?3;qm#YHzZ<)a~B-Nv%Hb8mWOR`7g=oLd#lj4HM|&VIz8eGtAl@1vz8# zyw0A2C!819+I<0B29uVrRcEXCE#n&%DF<7xL?}UxR$! z&Y9W)+S6-6Bl-&dGdN=&SYiL-BVZ$#L;rx5e)DI^^L~AsekSr4xQC4QF!8+aeoe2d zP5N3#%X#n;enE{?(*NOC)8;H~C+epvZ3o(eupv9O`KDO*e<$cl&%Nh6sU30dK zp7mUYD=Lcj%5ivwSoYNE_%xWrPEO9>{Epd>pE=L`eM7$neh?cjmh<6P`q##l&*mSp zyV7#af$*fDM4u|3Duo2AGZ}$8?>}L9GB)$(n`|BNA@)po?@BP@hzdS21 z2bC>8V^E-uqFm8uO>vNv<93byATHYC7mwOw(Ki&)Wmbb`#wZfM{_Qmyb zvxkquXURRfjIg7<>&`+by$34WapzY4D z1<&*AJXCy7wUB+iu|~^t^FZI^jEyZ@Hoo`>YpbeFCJMKwe!u|;F ztlxID2K?pxoK3TEH~vlZ#*q2UJ!C9XU=%yg^Zu|KWbfYMx`&k1AmohchCkPL_D;q+ z2Itz3vL6@Anc1hZ_&gKsEbXVs&vh=A_ZKby|Aw5o(;;i{idYwXgxC;#to6t*;2HOP$zGm)?0nvDEw&J5xyI)F>)0oWc`qE1 zJo-&AhW3uw0`_U_XT+~yH^-Z~=4UwX7MIfgZ*6g17)(oTHq=+1PutRVfbVIq;mqky zaB8K`Q}_=2n%D?-&Xew))hE9{dtdGEpkK+(xp^7;1za63g}vDMe4l&CoaK349iN12 z!z}T8_}|Dmko_P#V><gOo>S2*X)T@}SS*jxO1SSkNg z_c#N#=C`tzgXC>YZ^J)ZyNTN5zRqX&f|l%u#B;7@{igGCjnsC=-VEllSIHSzSv=E5 z@#nC&!yh_xu9s7bJr*+Fi=l7X_2}){GjYzd)bS8}FF$p7CjSLv&3?#v_A{(6Kj%{J zG3{o_`;(o&$ClXl?DuK8&M=(!gXZ!&8s9iv$-f2`u~Vnnm$#eO6|`5&#rfBkJy`sC z{2|^0x081hJPcF$o6>u;>#~RAJRASUIb){6ws01$Pq}z5zr;>0*N5C+>T)>ra!;-B zMb4=*PVMGCcE&gBZxmk3?}$_9jo7K1y~VPgvuP*5X0#n$>r?my-sR`rv@yN|dcitt zF$6ak?@=n=uX9h=@@vsQHkNJZX)}{H2`|#-QTig+N?m6TtI~IY8L+wbH^7GMoFTdA z%*XHSueI5)WDW38T2pqu7uphkCSHe@x<5%B-d|blm!-77#TMY(@G_h+=N;=o_E`KF zJ{+g+Z_sxO{@LujA3a zFiY$hvD9)`oINs~{U3~g-2c#WadziD>`rzyd8w-^@Bu&LdkWG%b8;o^I>@=Xo-zHb z?*8DXW;3VH;V#fZeslho6~%XQkGOWuy3|!Q`b1dHUQFwRbN1zo$v#Vs<#(FDaK`kD z`%1hKyw86ZR_pI}=vY~ty{WZ_=!^K9LQj|pXTU)f_6e@1&#cE$rQ%t01TD{;e<5qx zjDH}kg!K1BMX}dr;Pd$TclP(dGhn{_WpKS%9sZyA@JefFtQkl8`kGdsy^HG(;=dqo z3-J}W3qS7!Skqll z9ysw`A)jAzKUvQ_3ud#kPJ_!n7r-jlc$Ht1{Si)oiy&)~I=HE#_&k)lTS}k9UmtE0 zOYIE6xz{@E5x4_fLi>te73bWqjbDbmo36$KDvNjdcI;i{4d(9zH6e9zt$Dcs?*mJ; zA6+Wm$F5-KJii)phBTqi$LB(7{zHCWoHL*Udm8&dc*p)fhksw$Gg$5e?2Pqtc^Usd zojn)D4xu$*C+|x7Gq@GxJuBn69PY0u^o81f!0#!ZXM5_qDL?&YO?%O{WapmlgO2bO z99J?f{Jr*fX*(I7hU&DH+AP37<9z4Umc5zSRs2)n1a{UZwO9rJ1-<2Tg4F16eP;h` ztZmjH@3&pW(&ybi=dY8WGo=O`B)<#J-zTq!M^+Z!byjD$)=%a$=V<;6dY-SVwQVow zV|*k&2j_dQfnuk3wif(3^wiF`6~%kROuU}>n>c+XmT{h}-6UiDjh*u;^_+8f2RsVr z?-cG&-;D7AMUZizGA-eM2vV<+EvAMZxQcD`vW&nk6(#Z)yIysoUs#1_Oe(T{sjI|ex4Pn`;#E| zu^TPx&`ta)TF#1xp%-nScc3f~u|iilb7*a8CH@;Y`)M{k=fK^NyFeWh{MzCj;6ZjRd_^bkE9~r(T>D`>9rwb!;;Y3jhSd53W2~o6tMFeFyP?8Z@qv)} zSwheH=X2~f#&jtEe(lE68i@Dh=g)li#;f^vi5)BU0Dmsbr{xT(LVq3>(y})Bd^elF zi8(Ie7b?_(Ydj-%q1X@n&iwsxYHTO^rMNx6vTV$9cH{3)&$FqL{Vi=0r2gNeJ%?|H z%ySd>c$C~a@D6_$+z=kFEZ*UovhzKDV}9O?a&GL)zKVV)dnSH^pS51c-v)*nUl)GP z!PH3~TF#)Hp(pWcvs2RxA@j2dT*Q7FnzNrV*1P03kaskHADnly{C%(mIQ4ue-bH@a zI(xahHs`TBh%M&V!kL?a^kMvbXPPs13Vk|1wUp0^2huX{J#jUhvncOSec40V=`U-Z z--OO!@2PDy@q^fJ!3OXr+)djJQcttscXsZ5JX~6KHbCkldn9X++Ih6X`vh!HTLE)v zFTyA0dm6owygvNza2s5&QvGx;zIV+vGLH-8Wp88+C&RV;ocpQUzu7snzQy0sn&ZFV zY4`FPegm@4GWI+pzh&oIdH2a2AA;||o0O~t&e)s4M*Q`)*$UqbITH@h=W6!7^qfB< z@V}MCK73UCe)a{lS?tX9w|HlHFR}k-XC4Q#+p(7$OSiP;=iN5Xptdj^&f`BKb`ov_ zsr}T=46*f~A$u3?SBa&jf5k`R%t*avE@kKY zp6mE~^HUq6*~{sh!_CloHcrxwgT>fE%-0sXT?&-ITxF=&%pURa%I{-IM0aXw9Lr? z?muVLD!9S@WWVI|c>T)ae%_Vymv(2$8-TMvZxPRRYU`_^yn*aI7utz;l=me28Fp&o z3U=Ou@=W*`I?%HwsqZRc|I#+c`CTkyx>atjy{GswaCxbCM(?2CtiuUnsmm?kPRKJZ z{p8s^R?a**o3g9XRzd2&FTb%^o+(q=Khut2r(Mq3?)V+G)l|+Bu`Ov8v_-fUq&{;F zM6X*a>Z`5zf{NnvYAyEb>^sFTVZR1B2lH-}_os2}FWG;wb57kS_dfk?Q?f?5w%9@# zOP|h9?g#AS*~4%HZT6sb!Ksg9wM#wanV0t8uP$mbH9r^jqgSDCic|BC(DK}9#y*aH z9ImHLfBa&F@yhMMZV89;hd@vGv|QBIpX|Yv#oFI1_ZWGH(6+^?iF)u>xp*(Tj@^^p z8B()j;cj)3vnBl>Tj?BubLgp!(X{npJ9f^8Zg7aTxJo?V2c_0_rPY#?b8;tKGKM$V zpVO=GZ-d-x*1nVcnqn8Ro6zU$YYYB8{Ilfbo+hwo;m-E*PPAL(?F-pk%i&+~x|PLQ zv`v@dJFcFtS)2BQ*f{nodHvbx>tlA#oYcm9W$%~x7J9~&J^DE<@5b#by+=YD$a$Jt zIaTahT*hl)1mxMC`8`L@cy=##Z#Ujx}2ecap2+N1`v z7pK5ya2uQ~{%yJV3~&YBv$Ckyne4A&g1+*7*|zkH;WYVK>;1$oj!pZg!nOD(;+y23 zYg~Ch&AwYr|InH@>RSBy-^@eKw46U9$0QEv9ZZFpyTVxQ8Ve#n`n&+hcy zXcP3etGvbhoA|fs=PdqYxx4fGvp-^Yz~lH+@KpZ&u$A1^IQ4Kco+`c$4=Q^GK^^hU zaO&+(oO;=!qWE*vd7hNCOHF5O?}pUfB4{CYgV;^bm;Nt|GjAV@>W&hf<0GG-bE(j*YQ-^-Y{GK%i1;6W+i{Vcu)4#_yt@a{-Q5dm+3oa-C^>(i|03s zx#F{2=W_m%)kS^QWOtJH9sLvbx^i(Y-X*>PKj+A4w1?&F!Cp?!n)Lb~?xtf&43?osZoHv1g*6BWhh)&cfZYAo^2`pTZj8JF+hlG})$I?kGH z52?koX`e!Mcuj0)dd`V2*}13lacXQw_P6wx_($UNt#4}JJ$=`(7w$LCj6Ki3tLXVG za4)}^^X(RXYIZgpVBERie*Rov18r}ly=#t+*KSL>GjU7W(-qD)kENhr+ zTtG|Rj^pRqdZ9i>vJYbC9pFs%9y_IwHd0!!f!*ZtQx^8R!X9#r8ygr3h~TZ^5} z-&LE5@UQ#K+5frtCUCDdd57pldxd|7+`Va^Xjd0@;b)H0=2QMec3J*9_AuIea`XKA zGmzVepPZxc=Cq@6o+YEj{-bxHjb&dS=qGct5$y;36hGt0d6t~V_`UgS*+)Xok<`W# zb&<7Az5j?u!*a;EQnOU7&xvwV>l^ShrW!Cq{1)7*viNSaK6?@^-^J(5YpRb46~&tU z%6?mXB%VvF*}2O9J^P;IFXSHqcUYr-a*oq>eK;I)t=#KSd0*l~^qqUjzE~pG3v%Wr z_ZNNEhCKVOgq%kw!SnK8gxq)LxC%6f`0&+ve8#{Vg$Ve`n{p`v1>~ZRBhSx#xStyRoZuE}k{nce|I0 z-$gQ~o#dQ{htR6Q82LHRZ-g(@-q-v$<>c&n16srWaCk*={-kc#u(JoMRTlgHM0}^5 z)Lzb_<>I|ycXpmH+3O3TqcLX=I?C&VPooVJZ^f=b+u1X|j{e`F=RPxM!|}ypN5NXT zSBl@j-a)J*OcT#NED+1s(ocR1-Ikqt$~$HQdAU}7e)`OJ(di>+Mq7Rxv8C+G*&pd= zopYuc{)wJ-Po2zT=iE#FbL{Nr&v1is@t(AToqe?%ehqToev5yjy$3tMTH|^Q{|y;a z3-~}kqvWRc7MlO#UHc#L%uAlxW9aRTVQ)DdD~dB!Q=n?gHnGGFr`=lTw`oZk;>zZ7!K?)2M@@hbOmIe%aJ zPWa=p`jh(#&bs%ZEoEQA&gY;n@$R(L@MfjrUM}HhuPzhIxSPY`Qt>@c#A`xdpbM&Xc0b` zf3kK{aDI!-^D^gU_Rfd!MydFmp0U^D=lsgwz0UUp`)OAPa(-@0&-&&WatBU5rAAwp zi_d8H;J(^yB>tiGx{v><+PYfKvFw~v`@#h69;ao`WevOWCu*}0vUfYtvd^c`o5NYO zli@1%i8$xsf$Y2k9a&M#_c`p;L@$^Pxklc_a;_yO;~d4#p52(eDSNQF$Tdbn8_3#c z441)P@^;0EJ;**Ec4kkJ(~Lgb7<$syL1%vMaV`5eoX=M?+4smfQmmbJJJXM5r+)5d zuTRVS?#6O{_K&)BK+7sR(S-+AvnN#0ld z++(hJJU?@K5$y+jHKZPHhSltsD~j=C&mGSn#=p>-eX6hi+BT(C$6LV>uzh85_FYLo zg*^q2m)n$mhPD~+0qk8$#a_;P?GX4}tT*(aXFN^V*~iC2_Izq9^)X(&HtY+j|HZUt z;7sE?mNti$b1C<~75z!PprZJkIY!R7Qt^(IbLX6F=i#&SbA+r_Ex1;xpJ;$BXeN@UXUZ__yFs@f)t4Xa05gYw?XL+$XG0+ev&Q z{5wB;<{R<6t89c*qhsiq%lWRAccb)w9X)MQbN4`ZxB|w?c~5*#{5{TiQ@d%Czt^)8 zx2P!2>g>~;U(4Y^+BNtP@e|=Act*TEy$bzN_7SxFyHmaR!`ZipWgc^#UEnqTr=5%Q zEYFVB{O0&ev5aR8`$t+WZHB{i$mg)3;&b6T{(SyEawf|EQePX;@*E!x=ZWR4-W3j# zll9Iyc666w&()&kdW-Sx+HR>$YQ8qVEB^QhqBrZ^|7{9}KzY|6KPE+9B+mh51hN zMs`c_>CT3C#UAD7-R2frQ~aQu74+r&kHk_xr|A1(c0ch~#Ig?C@YldwVtHnzeKqmF zX?ebNz)PSOdpzzTc2~JrmqF~^wc9{n2eG?}?~I=l--zA?a-ZX9+p{mHUm0Kixj1W) zvn9V@{;3YCiqFB##2#T!&@MHV_uTGy6lDEVA31kVt0?@e;d->(+wE|({CD7T*ZJN2 z%qbV|HD$5wAotN){8aIJ+RYNL11Iq}#8t#H_QT*J{wQP0XTOsmb(k|bV@!<^J#;izR1C7w<>>0b)5r_QXs1J3!j@q>aV*(`y>b2lTV} z+d}5xPW_xFr!GJDIT6>FlRa0#PW|RH(=W8V&*x0cUYM=z`m_t&&s{iUNge0>-x+GK zv(^XG^R9LxL~AXU{y)Q~iv5ba)4!puVrPHkT&#`Xslemna+Ihpez>}Isx#&obd%Q=^)^aYTJpg0G&!y+SI@5orT?38z+sN%C zHkWzbmcbYS)z44Dyb6mH1Qa)c@mwyl%AqIQyzn>=yPBVz1-h z^_Az!AeabyR~FwpT_84|{|4S0=eoJCoTE8wFDQ8;X~X;e4mo@Tu$aB z_5BgN$R0<(j=d`^D;M{DGtRkp9Bm^!R$f>AF5u_9T1hL*8G-Z6m_^?hr#26y*M$Wz z7^cBodg`DmxM1A8~I{pC(% zXMbj|Ob|;g4#V}uC(!df<*$%={7I}DZIO1V=ehjM-!!pV(3O1*tvb}_zlo>P9;@(< zsE?Crt2!5-Pn)v)i}w}(oc5^r0qoSyMsOehIPsicui(q|afQ5N**TNH#hIhj!wUSI zb7B*E*8SawoX_UvHTiE=m}5K`&x4#(_u_8&)Ux$u_tNfP*Biq>l097PHdsd+%s(D- zzTPLE>u$l$b>_1($3rUAF26Ob;-{ut8^f1)o}9IiHOl$6T<#6*3)$Jbshia0D9G;= zIeU)Q$4K#x>}7JY_P6lwmvaC=XK)i*o^?xU7t_w?&%i6io~8Y!uUzv)+6Vl6SNl3U z&*-N7j3J+OGcR*t_ps8gx<{bmt6=Wym>FUb98eCa1Oa1~^IPBq>q%GQ?M6*kbmH@^a)A1l6(c6lGk`IR$g5}re^&)x^$h4Z~_zAMij-UsrG&%Nh7zKp+Ao6+n$a5Mgf zkmtkq{H*O^?5$zCHt*ANR-XXb!#CrPU@P%gA#0LxwK1+QTyqY7h_(T4hR=eWyW5r3 z9CT!VP+5G|$b08v+G6c;-KieG?9C1)c28TLJp^DO5=2X;65&e~?rq`zZu=Hgo@ zu^Zx1a?i)-q>Y^E_T6PXH3qWe#X*+_8cwe#d+*qao(?=(QiY@7+2EsomAE}zZbpB&hzea zu|e=6RFku)!g7UwNbX^f=fu6*^fbnH z>_*!RkK^YVwA}m+lapGyOF!8!rBd;Za}R$E zdyrV3^Enf;Z*oS@)ko@}JuPdR=g;-{71)m575A2R9#l8Z?zkmwVYzsB+RJr2%d3m4 z(ob;RP32@xGv}$9sp2hgFWANP>hbdqk@d)2*Mf(%%h(sfFlf9Nrqz?P8uEE={Zer@ zb;h^I*&b)CsqLTeWPKk{_IzVsAhwL2bz5KC)KgXVE%YPB?|{+toG-oPZ3CCXSMq*w zFL`$S4f!0hoixu8=L9>?u5RLa#~a4a{4^I& z%}#{9_*LO5+DCFehCPk#WZKQH*%Z>|Ko|;#R~GefKfSf~H^~3gpa0#IJsSqfc^fu> z4aFMCuNsJLOUr*bTXQacppQfGBl2d6XRce~>#g;M_%8kw$TNI>*jny^IOj~>O}hNg zzLR|>WFDTy+3)|dcfc+9(>i;vbFClPtzZg0*Ui~E6E=hF#eZ;qf6Mc%wVV&}l`s-& z${$;{M(jbjq4>}EPuP;Zxp;e+p>`VKoKyEgANHx0#wu?tJ7XSzGlmaoHSlc6bL$Y; zo?X%|W6Sq^^X268@JDzmZ7|NYKXRSz*)7;vn@iZ=i%*2@A)i(Mqcz7H;N*6NB^B0D zf4AWyac}}VXYO`#r^3hVe)NMYi{H~vr{#>QMt>Euj@biec-%qHaQ`vd;T`K;(8n^`?5066DV}1R8iPuyVvAXQf z*;7jPTIb?=7s#!omGQCkdF(yJ8sG-}I@)9}_v5EN@?5%4{LOOl&M+0XqtBOgDZ-?nEjV| z+REPlg|?4A2GjEFItwO?HGnU~+CeAr>*$%^fvasOyb(WV;oHX90q320BCQkjhs@FF zO0@^sBRlicCij|oe$)JC|1aWa3=3%=i|0H~`8T+H>5Fux8gFBRjsLe3Mg zFF*f2-97AD@*4B^l$U2&?r}IgBR0+$v)<>>o8X4DNw}?eUpRn&qp`e*&oTbh@^Z$0 z2lvWLjUB9&xu?s_uEY@ zd-Df<=RBLkpTmC=Xa3KWzY}EccaU>Aj{XGhLjRf_D zb2)$DoVD3Qxu+)V9#ExJoP+hnQ_sKSs^V|CX5O8v({7Y=G3^fg1b=g!T6~9=^JYtV zd3VYfPZqxb?+&%(q|cLJ=IY}6zTMdqr8eii51xv$soW_}ZHK|h6k2kjkw-ih;!dY_*8$h%d2T20!A#`H8hXJa4y zhPiuOUdC`T{*ymR-mmQJ<5v7TAlH7&Idq>kS+jvS^E`&#PhP%T%Khw3@4!Ec{-yZq za4o%scz@VRd^5OT&Qh_|V(POFy8&JwGH*liSF~DqXt{`8T`HbawfR}==*{J%9#^t| zhyMJ=U0hSVojP2ruly!8Lu{7Vy>fRE%ipaTMoZhZ^u#*zPZqmNn*rLqBPZ`UFSCcz z@>zdt{_Z$uPR@$?Vn4HMX!kIzC!U(gUda1uYW^AiGJb7b1!u4SDE1`IIv>T(HLk#^ zq3n$nFi+d;o$SGT+4(Qe(9Iq$K5q=5AbTR$${bh6BVZq~OSE|m_F`W}OAY)5ZP^|4HICh1Y*)w` zm2u_FP2Fa{=KsskfPW4<{pWe~HhUTSNZ68Jvx{-kCK=~J{OR;RA$zwwJ@=cuzWfdF zSNL?=+xR(ru$&v%E%3GcPvqnta@LQd4S=8cH^J*LoS%KY0H5!gIZNt8>S7!Bnzc!d zZ-|4VJxPA9 zbvV@HXD_V8f632%mie>zFU!lHue^aalD2@q4}A*GIg)2U+UI>Z>%GP`n$t%`t0>;J zI^p58J!mhM>^*sV&~C@6fr;!B@Nqcv@EK&y&#W{?$a;>^?^d+^`14>Kdpb;%J6}AX zkIuxs#n#}=^=+XiyZ-OWE%&P(j)@W-pI3p;(@fwT7-;_T76w1zlq`eC^^ zKTi|uL%RYpcZcAU@EN#fWwC}&>LX)+7+SM8#(DN{400`+Q5gh@JagB4?=B1^n^s)A6Oa742EbnUH;y^_yiZ zEAWomHKVm==N#(6z8=rfZXe@IUtP7!{r&&-&%NgvbRsSPp2Y+Dny1Y~x%uv7jQq{S zSMyiOnSyKcyTCMgx6-y{FT|<01GK5HO-T$AbkI}9de>Q((_7yne?~1$O zr(7>%8p|I6)#Uu&bC8{L<9U2BWDVEgPIv`g2YHXoKFeOcm!C7J1J0h=R4nV*TI@l5 zChbbG{Cf`bad-Y8oPGHp&e?eZZD;m{7tD79IXhQcBgh{4UhY_0?)4o0YdFv5cI<=cd&qmyXNs)tyYz2y z_T?NE8fQSv+Jdj@`xekVKU={ef`D(5`; z8DH+Z4-DgXg3Nus)BRRXCv$$D*xArQY$9zhedYdchMYrUqn!LE`7r%%HGM43y5;|Svq`C_fq!W`vLBQ`p1rSe9fGq) zGvNiX)I;`FBiI7Q!HjZIbM53_$KP9C)?h>ap6n&&ce*~?^WPWGJ|6%_!v?hG<>E7B z>Ujb?>y`J>p5m|Y|J8n%Qt`alS?nWv&iU5tJ8=)OjB`K8cUL{|7PQRyUaniAkEizh z-`Hpqv^g~G*m?KbR6J`qhW)mCPR*>Z-K(_h^|N4*{QmG4G!p+so8Ig_#j^j~$?ums zqID2Yt!I5sVIK!M=d$PPRN9y3{Iv&*`q+S;^XGV+GiERGD*V(>FZN-O-yL_N?aH3S zenh|7Q#oTYcLT)!Vdso|K;IwI4yPSN8;4ioPaw~nvYhIW`5Z{w1kNiL_n$p7gr56( zn*J-?!)}8w$GP`1?uy&X`vK>=hiaF;mX?cWVh48SwgR%2+tQDqRl(=*KY@Hc$aP+V z7x@>Jd={mxV5iTU@K&^8?5B-8b&di=TS$1$m}_pv?;WwfqNh z<|gC23OZ_&wH$$S{_ezn0+#Ye!UC8?e-^gaPtJ>+_gBjKTz%}!e}dk-(z62pLC^Cf zXYq&pyt|wS8SgYa5Negoqnz(n`(2Zs*dYEBVgsyM3;ri!JJHUkjexhc$ve$4V&Bqp zE^g1i1E0iS4&Sje{~7Dwcq`~fo6SEK=JB&1vsb@{>)F54qYcJ8is$>XrtYhg_=)`d z|CidkPILB^@}8wPl6y42EB||b-Us&OFNf6EN_;;~-DQ7dU%$g2r=Q$Q#=0-$Gf3+1 zQpjhMlALw;Cj1^=2BYOH6d&&zyW#3M&$Ky^wJpQ`^sD#{;T-Qc&8t}J}cO>m7_RcVeJp@M4 zJMb4m=I4I>W`DPU%d}shzts2?{&dK*p_RN%aZmok>|RjTu8X|9pXHpHj5mj;#j;n< zhP;dAY&np2Fgy46YsoVe=PaKCFG0SapG2z(-?KAMsp)^j9$j5LxBtXv(Xxl{rKk4J zgO~g}6K}}RUOdV*4iSG1u9u(tm_}cQ>&rbDHiqNn?S_}am*NlN#d6kYHwK@LGtXbi z%l+ow_J`eJBk}q8WLV=G4J(V!3Ymx8&rJSAc2n(c;J0Dtv)5F)m%@DUS~z*9(8pTC zliB$`>s@|BZTj=yg>EJF3Ln#M(qCux3+$0_9eYZp{mkx(mqK0Gu~giDYJNR!QY)kB z-?1Cu>ipmEMR+YO^HS4wZ-b`nQS=osjGY?zP)^n%HJW|8j(f&?7S#rm~Oj>K4 z=Xg8zJa#R(mAzIUjVg-g-uB{O(askeO3R!zVrMTd#(nr{pLS=n?}IbMPItZRgZ1Du z*n|I(oR#8T`K#E+!=~(MwAt2fIc^V&pl7*wcBU47WA6ul7}qWC@y<$f4BNxa-Ur&^ z?Vzf5*_(IE`w>6F&-+DVZSy_eJ=$h|2f9}7tG;;N9k!I4eSQsW?j7%4`Pah%;+f0I z;y>so=krN8??(At^14`R_HVqOSo+94pU2O-WPYmS#JW`!--kUfHbVQCwa?JjE3v^ts&#go^Ol~;7=(RXG?1S9es?pevgP}?pwmK><4ol`TgXiCNh6_ zi=9c!IoJc|9L>H=f5-7t3mx&!`dg^q)b^jWYjJCMpj@o;wfG)*Ozd2GANCpgsRK(r zZ*#u3XP;&a3&m4&+3Qp3nfqV(W3{OZwP}lCDQ!3SMw>eD9lI%h3lG<(Rk@gxzxmmB zEm!NG-I1L&$=v3CGL~li)b|*iI!P^N{r9HrLQ4%yw$^jmUEo{yo4I}&Qah=$cVJKY zg|t<)-SBC&AMmUEP4NnT*1RhJL;RQV?o&~m=}qmC&1u`X{#~@o<#dn zc(}YdxC(p&Idc}#_7QL4jQ(AXW*jf+t4&$Wv4@Fu!&PXh^Y`eDUD@$^Jcv{TZxc zH}E@9&h}pVUk4fE74)3>+22>=U12|VoD z{WN~A@e02htkGr^|0Kv>%=008c`x@fQtUnVvITw)I*4Ulshb-75{!VHbyeM4-lJA& zyOX@Bv^)Q>hdv9m|={mgFKZYU@FaE|dW!OO+A zfiaN1`V?fp>f!~Q zJ(Ky_`hU4Olk38d&;XWHxDLJ>vJUm|6v(;K7P9}}!kN!z6-9j zb|}o{Ux0TuN7*Oy#PZ$tAlhn3?LUE27dOd$1pi1|T`KDDbUd53p82gWzO}X)*G#yG ze*@eLS(nst>Sm0bT`Ro9bSd`ztz!3x<(W~*o{T5(f5UzFIk#?vYqZHZne!v(TK=BN zd;Cx3tPiQV{@QE^HCGq)a4RjfG6sIt?nbe!_y4V(wujMf7t6jq7$1lS!hZBqA@#5x zr0zSyRLJ)msmV|AO#X}P({XC%4R-2mTfCsGF6p1T&+)h_|6|xo{2Is@&NA=Yi*3rz znw_MNYas8~@A0#@AE_wb-`~So%c=NK`gQE-xF!Bln>;((@EgIRvUAYnnLb|A?-jJv+=lMu5d1W}A@^UryLPXL-{fBA z;JFpnQQIZ#zr=Uq_kzP=SNapQ%n%0!xy`(?><@h0fp4*x4k$5ehBCi?Fb+2aU zcY#iL0{<2{n~5zo)(wqm1UvIK8CNmh^W`mtYxv{j&%inJ9-w_IKWmyAc)L`5Pg#xM z9P&Ke2i`X3#rn=X?+6RDtF7Hn_&8Wla#q3DVvY23G5$cC&g_eDe)nz0o*?HnJP$U4 z3+2_VDBivIU~eG48N4cXCBG|9?r(B(9&}>oz6RmcM#k2Vzdz*p{t~TJQLM>X{OrA~ z^Umxh`2Y9+T4MY>4|j531Nj3Wxz*uyeT>A5Xw#r3yD>~vo-^@X$EchSJ2O4Z-sXfdz3vx zn^yeZ;^|{c&-jk~jnq`3i=Zj_Ah*5OcUX0$h&NwVOOwM zmaUV#&0(|E-Y4~O1p5p;N<8)3guapdlH8ofIeSyD*Fes&O`wwA7QavHXda)Dljr!e zc<;*M8PtrO`Fk8L*JsY+#xPFXx8?Msr3Qa+Pgmm1RUcaZ?8%;Tn(%wV&)V#Y^F7DL zmBrjVEB{dTt@3}OA1}VSyzlAf(QEQkb2VTvEq}MYPh$K*mBl;8Tz>0v@m}{4djRBf z=8d!i%;}yn`eb-g2q1e9gzIGGwrMNeKrc~^e zli3%uv)A&u^i}u!9xeMmdmz^yqtB;HYR$UmJgi0=$IpJuoVABM%Nov&Yd>8&|@=kh;jd zcBki=czi|ioBvRpwM@HQFZ+E9oM-Ny?9@`uRl_NiF*(7CjUxH;r~;WEe=Qg4l||M&cyTVvq5%Hn)U?cTu7 z-q}<(d{{ zz4k87_}#Vtgx|=xM#}3Zo^j477vF;)A=Zl4g1LP`^j=^WE*AVjHkGgbyL}JPJ}Dso%TddG;)5r{CYSS%|;p*VWgDcz^tU*|UIt z6g&H2G<&(&v;5)kpzlOaqz#eRP(07Vw99ktLiPam?&e@ib`AD?{3&jzkDSvPZ*5u? z_Aao1-ve@94c1@wZ|3r{5+C}q>uEa)a%Szz&v@>HBVny;W$(PgK1_Zm{4*S{ugB<5 zv!~(bSNkpwPo>Y-$Mvv;-yTnulY2<54Pie(zYVv9^C8cHjPp76GVM>|Ps5w?hto6v zIV*Bs7eGCBUwkrjgW8qF+HJ>v8wSEXwBNP=L++*Q)%weKey6b4*gs?Whst|bd@!ws z+*kS4*<;zayYJNUXnAXC4PY4kZTZ=gbNR31)A>)q1b*heHU7nTJ8GAEPR%{Vp5of0 z#5d&c0r~Fuv5MmJ@FVhergeeT;&YI?Ie>njzFvis*cn4r$Qsr!d5%K*`T>^7?`JRc z!CC7ea5n5Jb_?x%_EYSv-OO^azCRn+JlY$y%u7}FO0gFFoaeb-=CdB22{mC9|4BJ3 z%}*D8_GTSgUFb%CgMK{yIQX4Em7aR82D|g`l7EL-SJ%#az?=MI#dZ+OciDr)-lO&7 zU#PFixGm&)H;>*}{5Cj|{S>ZTZ9MGb#D~Epv;la1eWebT@+WA&QqD(^@0WVwwqgr# z?s;$69)9KLEU(30R4&fDk1LCJrR;~SQ6qi>b_F|U>+||+ich6yF1~@5<>KtQ!`Su_ zTOudtL(cIpU_31~dnnHObb@oBTv>cqagXmXrpPOK2iS;yE6fr*kaihvgg1c~plaD? z3h}IQb$kenl=B}w`+HaRxp)KI4Dw!h8h?HHS-&%Je@H(k7;pNx+IUZ+Mo#`c+0@rD^q*)wX-Bg+g^~12;TA}LThK1l zHuq2yua@71-3o6dzRLc|nVqHx>?H!V3PkVxPb){y=`# zKYMjgJQ;6XDr)I#Iq4&NA?r~$HXOj7CU;AC8`jYNFs_T`=gdf5<~_cpSf0DKLqbC{y1I(Is0eQTi{mwQDT|D?PAkXx7oLuhkL}HV-JOW*qQHM+74#F z4fo4Q-M6PthWr_iob_vI%W>X?-_~zxcnGBS@;NJW)2y=i>`_;4P1-NCBgLQMH^Z!wzt7si^4#T;~R{^z*Np zOWk+ZrjnL*I-Z{Mq8F`KpG5@3VeTxo2*5PlVv;2GxoXbuP<{6&v zKxWAKoR)cRDrb90zj;nRYiviz$@6Tjwr%n2^b=qhd#3TU!UN1n=HgoRIQ*7&edJWp zHgi26r{0f&3*az)WsjtevVYGi74O77*nQv{{sMjEj3}|MqHPUtRd{Y{yT;zVnO+}W zeaJlXYl!W^zft@m?Y1zc7PMxx*YQZ&r;s|#n$)c<&ZrX3-Z|6tvaiPQCyDP0dC$*W z*P!P)(4Jir9|b>%pU(E!@j$oO#a~(LnrFoPGZQJLAb2`Ul(zIeV(Rk1xwbUf!e6W@oSLi{F6D zoRjtcqTuuo<$GN)_U|IxCa2FZO6=Q-ZHR6L)LW>2Trcg@Vt7|7X} zcg4T(k$4VXM$gzgR~FBLNjQ7r9I<@HyOo`}$eENqkavOQ{L}dx!XvN~KeaOp=e+9y zwfKFZ0j<8A+t`!n*;5blhv~Z-Ew!Has5fc(JbVK!bJK@Ocd;|^{rDgH40`Im zB|CMI^X+TMIhcCc88VLZ#PT_}qO!{W)q7R`^oruo64aq>ET;|sGdR|MN!_Q9Cin{6 zMgC9FRUf&(peFrvT3a{X5c4HXtnwi(z=>O3(|C_Nlqvsq<9jAVqz!dgkoU`w%vN^?%%6*8P z{r8PFO=)=tIgNg>v2?}viO53Xi@i62JzmZrya1=Bv;SuE>#+~TwY6QTudDbw;xV4hLviEy;uo-U{nS`jSg%X* zUT{f8v7T??Dc%0o*Kl9b-X#RKn zEwtII?5vkRA3p^rz>13EOgK?qYxW9xXX5qn|D)+Hz^$sfuYuDof*>j)1}G|sl!0>3 z1;s)!0K2d;u@!w)x=TP16$L~@wb5Sucu)Djv>-GDMcmF@1XFOxhG3Q!)?X~7w zd!KW!+%@7|*zMUle?Mk_TIqblYw$z*8c(k$ZwziryPuufNG*&th9+`{(Y}ZL9%lAJ z*7RGkcf_92$N98fM!?IgMFWPL;W10edcLrc00U3RD);XgG%?KP1jY$`sKXM=dS#Wc)mUt%W0+m8MM3g zdmnq4oYYv}Ra%Q@{jamP$MAC>8RuWnMeHN}-cGww>_vU%cfIqze3@AGLH64gIOo!d z&{@0G;zap}!|m`EKlgkKd#T)9;~sr<(06KZE`1bk0kTAIXP?RvKP3=dv@|GfVcq zapYXj^Yk-VAwCoTB$hQfh`(0ubNq$)Pg;GP=h{uQoEJy%C(yHJ20&_IBU*FX$IzHv z$Nc7uI0rU|Z|PUlvoBL$ol3>eF!JnL9~SWIYICOCvtb4dle?SPnPR*1JK|NeLvZHx zeX%@`XR^<6Px;<%Ic)+z=f!REQjhK9vme17VJT!jH^FtZ*{(|cu`h+~U`^%_*N{`Q zsr^l|i`*ww_Ifkw2{ z>)Z5okhz+z%}4Bi*bUih*bm}E;dyoq`ia`js4CtGo>Ut@(NfC;;Z*u4{!Tb`m!Gv~ zzOv>cN<|H4KmRE=>%JLn7k(GK0WIfj&h!uX$(szj!5*|$_)}a54;DY5Y&`7O*ezi_ z7*5*N|k~TRDGp-k)1kGqC!8VYZ z?q6BFALZGS-|uNczl`>v*psvt`a1#dz|VbV-_K>YmV3l1_rSi5mU@3e`~Z9m?LXRG zS*5;kelK)K_Qhgtan?0+Gm%{zXWetQti|uk{f9kH&I-H`FU6B^_F?`lrR<}u(O&lB z!pdUKX5yjRE~1~y&Uzom&S$us|9K9cPk$dTfSe;oLEb-dwzg!yf&c5i8nVBmf2-Z! z(2U+48q;U!cRl!+o_4p&&(D?H@%QEDI=8}0+BfE>4!6Qf#7nrpwpH?b`d%sHDT{B2 zQ@_XJDdG#*{qSOa^r1bq%KG9yv^>+(KIigCb|X3cjUjb0o`0|0gXLyUJL5Up{3&M# z_CB=1;qj3eH|T*pL_TP?@qrFFXoSdoE@Wa zdwSldri<0%=Y6!Ad&s%BFYGNo1>R-n8hbGX^F`|#`V|K?xG{tx_@KBQcX{@uK*s1Je*k{S9!%q9vFv5H19P!jn zGkVVcPw211Q~bA~m-rat`_NeTVlNV_BX0@2IbJH~N!kt8=v;+t8S#H*0$o$WOrldcRr`_8^cuC zfu41J9VV4L1N1Y7mi_nze^2@&kh#6dwGVK;tbJF0o;~;D+)HYpH9dJ3;6*rVJ6cX7 z{F!!{&)n|;crSc|ye;Yb^Iw6Ddloga3w@OM47h-vd(Z#l{U>{s{95H=tfR%VcHi+A zv)7ZCwy*M2TVr7`duRL(J`Y#mC3sxP{Ztg6NAg{1>L~Az$KWpXWq66)&s{I`{XKM| z<#+wF2VaG(N5jhEUAP{54_f*iM>~(dKMdpNd$7E-9nXH8*0F4ly4REB)qw29ZRj_% z*NXMRFF|Vbd9mBrr@&bL7p^~(){}p&+)Huv_5FW%l0#?0c$;b-#(7@#ehf zir;}+?DgRq`B~F>#@W<3$IvFq&)%7$Ua|+K;vTfe*{8$p+N|K`T%5|j6c+LK<=2Jb z`pG*%-lNW@zXomS8`FkEYGEs}9ogH(hm7@P=nVIYXD;)5RNL}fSJL$H8)QFb?@!de zddV8Go5-nSZu;WrX}^ay-JzBL|Nm8-Gxjce8@#`qr`UOhwzK{x>0=rHpH;;^x|N-G zir#X2;Fq8#d&8=tPUf&zmWy}BCOG%?4xBEYy)~DenoR6(=tF-FFLAy5@H21#?MOL$ z;M7_3QgN?G@k{g}Fj?M1^q28Q{M2<*bMg+HK+p3lW6Rl|J$Y)CGl_N>JNy56{FQih z?f1dE#iqY4=WbeE@vke2b8l0z$zlWH0D4dHv)PT<56N%Hz6EZOUqdYWzrNVt#_}P1 zkl49!r1<&#E3HQb{|X2@oNev9)4HyY6sy-W$K*?<4jWdlKY)9z)xYmh*)9YmYn8j)QgX z`2@K)!3||9$hNVnB`(Q zpN7fJ__9`c23$j{DOVy)Ppisf24H-el6PeWVrRruC&G2XnJtWV2( z_blz_(I4X%y`}Pw&&Ml=ef58@_pXFas`}2X3bMKspk*b+pr(gCUdtpe?$KD?BzJ`Avu#*;+&)T zZ1hRF_#SB%?QUc5K)aLHK;Fr;d3Y7SJ8r^19CxWK-tVttr>=9aYjO7O9detqH=*U3 zat=THEdRDv*8XNWx6yvrSN7=puta=2*R6xIj=kYMc_&A!wBF|L6+BhDJj-iqa{siq@tGxO zDxSbwRrL9X$r>t;M7;c{B$`f@k{ zUgY0VS$zMpC(irK6?g-B*K$#7E4BZ^ob(cVm7eRifdk~#h4hzqxSYuw%gb4mxx1A1 zm;AZLlJj>g{!{#9`s2~$T#Fmasba5#-^F&~-?+;4;0k{BMcyeg{)^=d!1d(ho>G75 zD{K2K3Yi=YI(XeU+{@69uD#=j-KHoF^pMakzsefRh7 zemj2`vDwBnjNMQy2tuC*^ab@qn%7ciCI6S2(q7J#prF2IQT- zdQ}mhP5%V0p~O`xTDX?oIq6?G`+~R6Or?rXL7T)B3F{YWx#9hv>5*J@vm% ztgHOT@o4@qybvbP8u3qpoax;wi}#x=wa?Gm$I?>Ehv+xYt(?z!j%A&4Uz@-m?3^L# zCuc=!=eWuu_Z6|pw5~9F4QQG3oRM3}S&yHyA!qtJ{x0wttb|;zAHKO-4*+_$hd#>$ardV6S{hyGK0pK9xNXKHv|iGC#D-*vG4(>v2BsUrPUt zo@eI2^iTLrwYiI(HB6mO=fCYbpR@D*^`-0)?4RV^pi% zwe(+0#WP?_`ajwp&ff#_OngD!5S)G3LH<@fir*DFh}{;N)7N+H6=ma)`<&dZX{poH zNXDAF${xwto9EM7oN=TclJ^hoTz(VwC+x2H1%93d+pznx`)HHcd+a;|7ei_wdwM^- z*D4S{hrN&7zud=B{5-?*ZaD%!O3%JM4el*DpR~Dvo$r#*V*kTlroS~}7qhR^?hE=* z=)k{M-ZA2-jWz5S@HLRy&c3b`%V**WJcXW`%Dy}udb9U|)b%)i_I1{#;c?n-FSdYx1$%GWC$5`)(~mxwKNr9GpjxBd z0?)%J`8S9SgT>;jY1!|M*;hi^_rQ-qYdOd8vvxbM4|JW$^o`wT&h`7*9mF4JcarlB z`%cKSwmGz*H{t(kPhP=qXMA}Ed`Ry_&)TFGQio5_7C`Q|F1#&wJ-m>XXHmwIdHGMN zcz6GkpLN|F216UzOf2t84Pd5t-oJj-&%02;KZyP%9t~Hs^ZQ&mN4oI$E?Ez}5zhV2 z(WXAX75{2>u2YM?kl!9p#0|wC#n~snv(KVW*Ju7VaPH}n%3>}K#?Mw1?QWF2NY2ml z|6>0`?`S@s{>7~CJVz08V z7QdZ8j6K3N-sWG+&iUU!PWJfv{Jt>28r|lab?7s->4URoHStN>HG$O_{G-`N zY5yIbjI+n`Oug6p$b)i5&`*YaU~6svqUGG)guRyCA76@7i*2=i0iNbRDQ8D~5AI&& z9SZL*=QCP$d^-F}>jTG&?}}T(pbB$TR{P=?;${3R>DlKy!BpC4oVjRHRn$#woOAI6 zoO<|$eGAm8EbeV%W4eK!Ip31@kv@l)NW$yD%Sc|;ZYs zXP?aAr~l#nhWrEZo&1^Xtl>oQiTri+5i9MP{~P1i?B-%+IfHOMLyW*TK=xsHqV=6= z{ExWrmHK#4?j-0aHdapB=b5=79Lvvp-UxX=yGG8G10nk$|K)mzi)SA#m;ZKI?c&Tu z);IN7XLX?+M4wK(2EK%+Tt8zwSk4Xn*qM{eS)TVFvJVk!!Ec4fz@4;5V3=#>T+Op6 z-;vi~?^?Eg{6Y8_ZE8Cs?q+W#mU++mw$X2Du%^5Qw7Il;atE@9>nD45DLZq~2VQ?j z&B-~Op5HafyKUxt6zwQ^dB@DU{)}&y|0nx$_)Xpzd7Hwo`dB0PO{7>{2Rr(iCszS2CLa`(Hh~`#rKkzKB_~Wt&hR&Fo2%A z%6Eg=hYk3x*emgC{C4;qd?<8+?vV3jqCOvk^nak7MJ4shzXj(VbQV41Iz#SM_Mx=B zaGnvbs+Z^KIXCib&3kI*U_~I~ei~YNmoUYop z=VzaNFTQ}CGhsU1!q2=^Ru<>o6#1>#BlMT+Ei|;mGFhBe8Pl1WBn|8NBYU;dI#d&uUe<=GyT6^}%Wi_ntX6%Ej z%oY6$<9S+6)+yKfo0jLqu5v!1kCi`=mU;gfcMv;T?qK$n_&ECW>>KDcaWidF2UErF zfaK@w`o}$HZC{f!4v%!*lj+%iX?LS|o`rdjIg7ppOqaKt*p}>_@%8*XAMfKI#=j4y zv(KPS6U%kWw2j%>gUg~pEA|=m)LjR>HBPhXQubr; zaJhJQnhb6|PGg zCudXo`Sh3AZ6R}=bMh*B#-4YRQ^m7}eW9M%H+UK3d+*%q7SNc!K%d#q!=a=2EXWzO zFMKQKIasGnbMaHz4e&^9M#xKz?k{g7E%TARnzkF#8{_PmXXrojkKyNB7|VZ$e?88c zHl%F}$I&Wq<~Q}2+Pk>QbCI38%lUH(9;n@O+C3_MBhK$xyhrN-2gu1jc|9@y(>V3h z9HyG<=KLk>4_6guZO-G1XhUe@;AhxPEc5e*SoUq|<{sD0nR5>PaDJY-xsMmv4e1lu znYYd9lhjK3zm4`6?P>n2_-ESrw47H9VPClo@p;<6jbDSGj4^eSJ)8JH&In{!(^xc^ThsxCXvV&T#P?`PV_-nHtcmaL(zjVmbdGgzVXj z@eh2E+y()C7*1a&;}V`x>G_TymY15&XP0;J(>UMZU8l{7a4&SBe~XWS)J4w0$z|i0 zx006MRjE&Vja|Lcc#^ZK_|Et+d2QuoZ{~N&>&wYL`vY%6>nwJ+{C@lz_!+SuwA~ul zfbZnwnec+P<909BshxQ0<6p=)QZHBI-q2N>XT-0C`{=2I5wMP5gTD{#2RU0$Di`P8 z>39`nKED=A`|s@8cW~BzU)ZP$v^gDmBnR@l)U)wCd40t@K)!=%RG~g$B7cPZ*7!pB z56mtX_mKC2ZM4m^cQMY{pJ!cNJd}S3`#yc-oIQn~_otGT;f9GsXxp+?) zi8J4si+f7GlZ4eU5pI(^5dPD%cz?ajT(;2vsbX2fUCPDJiCeJ0fHC}?w|C-b=v|2MdrJ(89_ zUV)s6*H?M|i{)H9ca^na_lKRuvfqY^p8&hFKZp0kUX?fAb@NQGOS?(@V|)lc7-t>o zmy74eQM3=)d*FxYe`}lP&HdU>s4SkHzwp1M*OHUEXiaa$UQ(epan|bVa`C>rT&y## z7S3GGptr(Rka2EJKY*Qj$+>ko&c3`87V_K4xtN{0ds}RKyeIzEHHPAh=~ebHd7sPQ zf|j}4(%N2+Yw@4Qjc^xy2y}<6&1g6S*7AG9#?VQBsl5sG9rc?UXru34Y13&(@dvw3 z=5{dsX8vUMueAQ~ANI!dMcU3}uMc~(E45!A-sC@P{~S?vzR0_WmiNV6Z@l)G;g4|Y zDf9cbyv)N~7_8=w!wuz>@RwrUw4d^5B zgK~DzSLQA2@q@g(*g2=x=jWX6L(7?ydFcfkicg@QM0*UjU}rwJ#d!|p{Jxf++RgJP zbJ2pIT6+%fP3y$I4<6-jmbJ|qAkhY=N!R*XM3;wq-gm&1f;yEy!|31HkKJ#9a^P~>+ zrtb&`X#2nCf}HlSp1gnQugkeqEYF#IH$E+Tp7U*g55kCUTn5`{2}Go{PWWrg#Tho`tEw{*XF28Xt-`#hLTtDxEL{5T$hS7@IxZN|^|Q`;Ha zHRYnVAHrYDSx6tn{z_eRzza&nyTrcqp8QQ<9t_mB2F`aZsqNrf{t?#jY4&5Vvz!-b zb=ccz-;v#uwi@3_`&8RM*`sJV<8F=4o=$-pc51zqcu&awSP#ymH|1yk zdhx4*Qqji-^gLhog?tb3jM!kfU3_C&U3T89YVotTH(+VT!%j&cH%#(9&f;@tKMSA&|mZO?z|IUqMx7n$I~;+pv1tK;0y5@=uLj5lAmv(JZ|6L*!0p9QsH4}t-5@=jQZ_ZG`D=R9`y`}^W^+1pnZ z&+U`(2>!P62C;Lt-y-J}xnJX*;VSx%_|H=Ded0-S&erD<^z5-bAEx4ZwCv#)a1XIa`+rMq z{30i$#94{qf5$4`hYW`6_W?8RVns3w;0gL1Y!pByNTEcvFi8-yg$sBvjNPOv#FfeE7)t;)8swJPW?5lC_Zy$J<>k= zE_*T8o{V$v*W!#lNh&;OzAiT8r-V?*uA;&aKp<>E};NbD~5+0ajX3jaF& zWj%l3XO6BByTx@nv0I4`$4zNjkD+q1pKFT$%Fa7hu9ZGt#`(_SFuXH7PFsgxgZH7C zHb?63XLjBdQ;$1o{|rBCIR_sJWm@)r?(ck@cl}M}_U5;>me1p};7a;D@f*v<^DBEf z=lhfV6WH(KmtlSOuedCBCtj>hPr`Zc9-yBeac4PsH#)my{q@_5wg{%fMA{@=rcaWW znmST$E9eDTgU7`4UBuU7yYoN5*%PngF8}9ut18}!#>@Q)E`fL8Kze@Hf5%dBUgWIG zwfn*T?7PL6vM*#eXD^dC#~-v%k^5?MC;L)(S?>E{C(2oY^9;{BV&?QNsLvj$O=@s{sX*QfX1Vr1 z{Hbu6K046G$mzgO9iPXpSEWDb0kh<^UF})Q&i4iRSwhaI1K}`jZlIruSM%HAAK_5; zOWK{w&b&6klW?AIQ|aUMQG;C(;{9W!dz@X%cwUvC^>3%Ik@!KV z59zBBOo3@)rE>8se;qf|Z{|Di?H%-WwVdq1(a=Wj5WEvT=f{2J;(R-dp0%0He+@Q< ztXrN>$CZljJolkB+ z=dtIOisxTl_H@`vY%{Sn?qw>wBV5PN-2ShA@Ir0=g_^L9*kD=%sO`SL(8nY6%t!WR zz5k1)M%MCg#E*)%#})j%`_JNk%wC0`z%^-C;q22(c-)1P)AWDgxCXJhfyNCmz^UY=QlaGv*vj`+3F<#Ej`H*MSTIfj6&tozN{rJ`R^X1$~+e`k%{M5}* z{ZA4v^K(8Aqvg!VJ>1OCIoA?ThddLqPxggtU>bibynng)j_Oi8iq=k_opIKtdS&r> zV~A%_FY(mD-t^pS-Vt&?d(xM(N74_&-CX}(`bwPrvOUhTaWpw=@5Ln`tbu_^35aB60MocHXkM<;&PF3omiyytiJ?&iR*pmwFurz4)``pUVCQo?(x|@8I`c^AC3V zPrcWd`w`CB^c~KerrxtR@{Arx`yS84|G0j>d)z=Q`{+~1JT8SzE7iKZ39y*m!e^qo z;+#Qnj4x-*9Qq6Jl2}(5B=$6JWlR}+iNC(QW3A&8co+U8`t9OZu(y_b z6h4LaD?9Tx#F$ejd$Du&x1x6yKZtf6Zuo!ssnIL>r{GokOAWWIDt?A`I?n&Ily|m_ zd6k^~Xn8Jt&YqyJTKv>eKYdKZ`D~LjBx_sZUxLTOj`Z8`)#c(|XRt4X>-Dh%t-1II zxIcaaa<=6>%)GX*Drz(1_)`8c+TCtk+t4%CcJyKRC;6$-A^hFtb;ldhQe$=4>8n#! zG1i}Hx5%vyZD@z%KVfaT$T^1$9paiS+)j7h*6d&5dU-kjKefjn6x&a3Q^*|W{+^T5hCi9zg1sv}bNntl{ha|h zFLtAC%FeU@bNm(@2K$xFiFi1l)!xOU`Lo1sW%s1b#5sE|t}O2RIGi)&W;y%G-JhRl z>t*-^+GyjSQUPLh`A^Et{>{6@IC;JCTxbA0YttIerOz!Dex7f$`L*cZ>Zcby=gMY~ z_m+=nM?&`SP<>obReZNFUtacVP5!s+`S=?CWpd8K`D~lL_p;b}a0YE#IbEO|Tu<9w z?r?lEqz*pj4`SDbXQ8#c>GU_*!^%GIdWP(TSMV3&<@(QgcOUzE$l0?!Y^zPqw<+>+ zU)etk#NXh*fwO+;qem56>?!sVedQc}u%dYGUWl`I(k}m=O*MHvVKzU{?APSy%)biW zlAF(0sn`4UmCrBlvJb~&`IEGNX_ft{ZLWJtWwEzYHT z@o&Q$il??(@f+Zr%h?Z2R~B=1J^fes3QiZx7_*-`dG55uua%6sqIkcUC?`MT7>V;Y z_x`{wVSD}6DHY!-evGr9Pts%{G2IQ z%A3y4xtC|_gT@@cvA%D1%@wd4J7@bcIrI4wad)^;UVf&O8qC?(88V02|GSFkx;g*v zQ-3?-!R{-$P0Gc)N^hKXxtrD-9+vYPKWp??`gN_;;{te%mbp#eIRh5JzL2`w1Rn|c znN^iGr_k~{kfU+X6t)w;9X|+t*`K-3-Eh7;J+!iTXPSbuhqF%Wpq}^=eNJYd4f(f+ zHWDAeUPB+l?jz@4edL*z>vn;+`B}3jw2j3(%Bzlh(cYqE?@y331m}D8w)7b=Oso-Y z2!06ug*@AGX6FAnS<0SAdknG`qbjwdm2s|>=hDID;ybX6>tWg>{GHgHAnyq$!%mgX zNSuB02JA2P7p@1_mW-)F-HD$qFVEqeDSL^%j$g$4$iIo5Gjl8PnNSbf!PDa3L2Bj~ zWBv_i4d%e4D%Vugsl5Z)IU{Rp^AEoo+*m5kvN2+9;6U14_*?ood70N4+B9RIL$9v? z?3W+Kb5Aw#X*lm|2g73agS3&5HD8bals-1+*C-c1S9qO2R6oD~_ZIx`^*f4|`mckt*Yi$3SZsG~ z?)H7`5A=Mu^b&prz7^XS7QzektYahCR{mIg6&_esjC&G0XV9nme7;oFTK4uPaE{zO zJNA^58tg=yAusp)0ec1gZaELZ7PLcYb!fZr_mKA&uB6X_6=L1_)!3gK(;0Yo+OCzw zb0GKfk-0pYwm{wq{AGBKe)2x}Rh7MD4iY~Ne@CBAI|=rXmoet7-VJ}se?)vEc~`Qx z)+Xb;TYNP8R`>=QYMb*c{b%huz~N8>9@oAX{W*5>29}EF#}E92#8%>Y^v~FpVt?X1 zn>V9<$?wMQ&0ffEhL3_>Dtz{lcfHshkouWT%bB?uWSw&^m1*<&U&5aJO>pXHpf@~#ObR-d<^|vs0QO9YqO8`H;Vm; z&%`F{8tmhHN__}q-d)sZ|m%|7- z*We-g*&H{Od#Lzz`pA3LJ>r)@&XGrO&bdwHWK7%f*OrQXpYH^2;?L9n&EiYwIR{44 z(tqabO=H+myUf#Pw2Q<})L-UiI6wQZB$oTWh5rKmMg6^k-=}ShQ}37K?9FEI4f}W3 zK9yd@KLXEus5nzo-=ET2g;K_3C=TVGCOPBSA1()o<*mXiticf;Qstocz4`e zPR{lY{LXTR@vDq?g4iEraa#7-V0OlHKTgdzVjn3so1NNPpHQWFO`8&N15D49jT`%RSh6xdvyf2h(@Nvmp1nDL-p_o_43RFJxyPQ?F_BuzS0K zUY|CM|GMjDO>-|n*7awYA$AhvyONA+eaJq#p0)sQ1Q*Lst)#ws@iWiaWADSiv^?V) zLH0)Wz`Jmi*j4)c8@GkAkh$4G&L?oc*dNB2xqNtKQCG*y873$7Jciv^-Yh&4K7@ZE zdo|}t1Bmt)Or~Ye9S6Cud!bFaSi{!1COvy0@4%UlE&1oW=2)@Y;R0j%0e=8XjH#Qv z_aXQ5so4L{T=w3j;=QQ7KC&Obm(!SiuXa87S(E+bZNX2QWnz!BpMkF-bCmU4iH|O+ z#V~(MJX$<^`!AfiT83Z4V`xvZm&lpV&%M-PPoibNUIrP%kz()QR{ZVo^ZeiB3|2!q z>o%n=6I(^gzq@cCZIpZ4pZ<^gxCctKTs!B%vpC~y39}&cUc0PD@w5Eukh44Qqc_v5 zKjAdm-@!pi0onJ{2}Zq^0JmSa9jBsiEYk5 zR%{f0i1w-2`uwTve3zZIsLRju^E60JR}(vho%PDPtiyjem)_Ceq2gJG=6DEP2{|w8 z%E>&RrO#&K^CA21OuPlYM$Q#@Ze=mInTzbZnY6v+yv`mEFVM3m2kU31HYd?W@?VC- z%f&m!5A;|0?`pf4mUVa$*Oxm9+R?l4TeI&G8^s<&JB+_GevID=-gi&gFT2qO*gvPR z&$eG`^mKnE&o=%RIP05zmi660tSx&9J9C}dJdT~Sqa{4R|ChgCxfo+tTAmGwM*ZVe75Q@z7b5*BJv7^`r z$iIaBJFPbVU^o)4h10cL4)w(Lz@I_d{fM*9Z^9q28)QFT>sqO+=&NB6tbi?Pd5(;R z+V=Vx{1s*Ihw?wA9ii5zAhK9D(^BX3uB#+2(;TV1T(M0&pGxP{gRvR;=! zUwNx=JA5nTzEl7C`T85|2k@QR-B2k{d>k$3`+@AQ#Ll5NVmHJa!okpqpEKoVd}pax zi^cSMFqJ+B=h^%!yE8sYjb-lovonU<**TBz;^)1qDeX^w?Q-!wTh@B8SO*pKbW5vE=uWI}MM*50uo5_!BrawJrTo_T^$3PaC*}z0|eu!Oy{^v`g`x zC3VNnIo~pQaHG6D2d)-tt>1{)}T=F0kAOCQbN)>?e1-Tsi;$(isd`(F8V@gm5( zS$*g)p7Sa{TX=*2uGn05J@!Pgm9W4y>iIcPOR<~uF&S=#Yx!%%j%8hkw_s;ZHiBw$QyaOTk+2KobLJ;v`OI9Me}nv-QF*RhYJB;Bj~9vMESNz5 zyi{EC58RS}4?SngPIwX?L!VE7X;txz8%tkG&;8~3khRU3(u_UDeSE_o$piRHlDuhoX5ww&LBKaJolRM?1^XNf8~uAyPMwzPo<4u*Oz+; z`%q{AsoBi&t@ItWS)ZLX`IetKeHdE?BC!jxSMu__P59zQ!dWr zujntqZ^?x-Eu+@hr>p>k6?&^0HUA$J;KhYqUCRZT!r)8 zJ(WHWFOYM*es*NHV&{y02ezYSjpkMs&-m0s=5jf06Y)9h>iWxb^cmWo?Ch;>W#e*W4=!AA7^<=h3+ z#5(ggq(A6BZ>Qy*HD`LB3zNkA!?ff;b@qBtg4Ek^+J$fh|6s^xmh)X}K5Y>{=U_c^ zb|!lc)Py{*?tsjDYgosALz}J2#dUAtPls0^^}Jxt#w-i6b>W93X7PTK$v!B@c=-N4JX2VkbbgV4CGrE3SA(o=_It+k z4?Ami4Q$9iO>TGgUhHS!YU6v=wVJqx)L#1CpwhKhnnQXe?NN zl|_B0CNe)e$muJ$E6)9I2EXugMkn_N`Z)YL?o4aw8J}nWP})s$2h-|{y~xhH-lhDW zVn47`=WF=W#5ZAQ&Ej7k@JAc(;qvmFs4b^DeG30t{_n1_7dz`VgSHN#8r!FW4dyHw13&XerZ zYxsZCv-i^HSlYeGW2e6nIP0Fzjx*@l^8<`I{eD_1&hfh9-Q4dYTF(9vIO{%@wnR>Q z@pbI%`)46@p6}Lc%W2L&59-Uy`^Wtkj-Quz1g&3HF`oVKr7%}~ zN1VO9d!_w?FQ8pTdkts4x1?Rq{+Ycoo*u3&KBq6&_rvTn<&0b9vkm(`c1?NzvTKU} zMSr%cc<0!czKnhueWJX(=odmh`y9vLU+ibG)YcT*I(GKi338ss|3V8nU-A2iXN+yx z&(e3s*+ci!e}`palj(24zVz-m`yu1slzk-r7T;GX*IwzspTeJtSHo!f`tozWw}bO& z*OrYr=YiN~w5-JnIEkMc%riFoGH34}{G8=2an6VK>@CIblDm@sBwWItPtQ3!5m(4P z2*&Vpc72RzYSWZfO>RBBj`l0${pl%u0@R{s9S?<0P*Lf;*Y4=VPM_PEp_#THWxwKW)6>~r4ApHcXqjWm%NI0 zqu3*mxt>nX^LaSC8ayty3f^S56sxn^J1_3a{~h+%?r+?e)zh7Z*LEjYG z(6)nc&hXK+9*{Hr8yG@=*>y87OZhpEQa_DL#b<{{w8`A3e%EMs5adkDTx5=K#L4NR zkC{02QbQl_(Wl@%*M^BbMn8)77ruhtot?e)g4j#^I*|K$C;9Bu)=m5Yc&dKe;-l%q zaB8d@`#SM!`5UrxjvT_S1^NG&Gv>S}%@x}UqNg6-r_IpcQ?&2-_u_7N517Hu`{gG1 zPyPt~3@jDTvysMmD}5V&?r{tD&vJTK7C(C#fiu3WQO@nuK+cS_Y5Vejf`0r#IO7;f zKM}G&cH=M8|3~7(`F&u0m=3A`NyeM|?#a&?)ZVz(le-x^>)4i^+P|N^tjaS;fB)6r zGFr~Po<#aWsfn8n^v{`L55?-{MMdD1+umER0< z-qa9#)3p}Jf6Db!hgtIt@jr0Bc;@)9a`Blz`*d4aF19!B03Smu@zi>rS@rQh`n<^X z8jDrq?*kL`pL68nvYLRk;&~>lVCTGDAZHX_0;AbG!`0$hgGYMMarR1k$lRQz-TinC z^s?@cRN0%ZI~wnx?Q85Ia*pI*A?F3!_Wab!k1)ub>_zJ@{tiFSq*=z8eSHnQt4+@P zYR1`-pZ$`1o`A>UW3<^=>=Ze-((a%Q*M1?s9P;jz^L7%P%HNcqXGPX;6YcKNKIi(~ z(3Wd1MOof_Q@J{Qlq_87ehKWn!ef4Q8Mj>r2z-Zj3KoBrP7-;R%Sy}c^k zyV!cTFKrD}@Hdx}d&?YUe&)mP^waqBU=;gwoU=G{m2)y>+1xx%ljscc*jNo%A^u{^m~?KUm%v{uFlZ;ZJ(@^MO!;U-_de zi?jR&{x$5Q-0ul^H~!A})K$gvWsKnf4TYYuXNQsrYfU#?XbIG3Nh~TgA@4_#H;FH^Z&v?8n{?r!J<_p2u%N#`g%m zR(~6bf6m^oWX*QT#V_3)D(>~8Ld?@5+`)$i=22$%w;5T;admj742VI|^`aGIHfS&vK z20n+r{M3K$wL5G|n?V~|c71lvyPPd~7I(zea2sRF*q5=-pnVD#u=kW(7gyk8@KF`s zYjFPFN&cSTx%AZ1Bc%u^|dK&5-sOp-n)*1KaKTkP#o`t+XWVtsaMKe5thu=d$h6md-85)pXgj& z#vUVgB|ZkXflK60!uQE}nqH4RFE)H+&JGu!PP>=CpLm}C*Bi@~^pnId#Q&QM_Ac_e z$=O|Q#bIaUO+nvZh`Y*3&`1)cI~T* zchx$$6a8V=&id>GC-C=&tVO;XUWk9wZm698u93B0Bd;BQDf>fq_R)XfWO)zJZWh~# zzXQ7|?7+_6*aTOWYo}gz> z_J=&X@*F;+$`~N;ynT%&^_P7-9-6ymT{(Bd$6~LFy^SBCH`L}2ey*2wyNGrV`%BnW z>`QG*^v&>O*pS^EUetaBZ%3*`jS1weg%Id_A%{c{?#xYros=j ztnJv6_hD_bzuNMf7)LeQS?piged&1zZlq22bw~Pad=33-_D0Z4d>YOf`?T17_)&Zy zjD;uk(@MM_J!`WUduO;nEPM5ETE>>z>1r%J@Nu*s@etg$RIKx{a-L+rtiLDOhrr=* zxtyi+t08As_Q*x_Bk1qouVEARVfZn~=eP6a%Ji4TQ5Qy0*?uwOB6-?H=j7AMHb*m94Xvb)k7JHKzo`TdAfqr*V4oa2Y;H~YRn z`*1lAXgdTJ(*MO5LiXWA*BH*;kDa>CnXxx}V5wNM)WJNsmbM*ajc<0nTsP~HwFsXC zIoE2?A9Rh~@R9s0@IF<}0-SSsD|Tve5njT-96Fb+2i!%gtZ?6OHv0|QD9C*0b7H03 zU0^w!3AvBFPiOA?@$0ZZ#aZ{9JDKx(rQ+Qz?-CEu&(qH`_Qzs%_`AYa>^3;_+6|_# zXVbRF@8V{(O?x^&*;$9n@e=$q-a&sI&Byko;@#s;`grzkxLswj&+ExqPRluX06b`% z+u)(_D*efF@qU+cc^kN~%DIHw}3E>pl*~S6Wxv zZa8(HIzNh?_r{a)8*=C4V{sF)i(n`IIrs#8iZ<2s(?NU=PM;6aKJytZwS6C~m7C9L z>$6icwPEY9+}TxX3c6^Mbx9r05<3V#qD{U(8P0#2e-ln^ZVWlM_o%YA^e@;ysQYUC zE#M|-OTQoQ0;#z(_`mC`Tv^=jUHTqLTgcB?2Exs-eO2)cNe$;((dx>*N!!fBDE5AG zD#Y*8K4)US?;DF}iakbKPu@A~HLwJ(ffKd)Q7qrh)WjdGD&}!Rx%-Rlr+puGBX)kK zn%Zf~ZUGG->(v`F&g|cep#ysfE%Q7Qj%BAV+ps6P<^$~0XgM=e+wZ{>u74$eag}#C zI1H}hkAh}!15DDcT~%=wUc#TtKN>ct-Hf-wS*x#MM5R7y*TA*qVr`ew&xGvb+-LU5 zw(QpUD>;qvBICGIn@b>Pch@rE9hs5r#@2i zsrCOo8`wi3?;~%Dx1whrzfhC8erl%|KV!=IumIXt7X9R$OFd5G-}ZlfZ_56Xwm;rO z`%Q4}Zx6g7F4M-qa^sqW7nX|W_LX9Xv9qRMicJ?A$xhu3gfF0frFx*h#s0EK@eVwT ze5t*V;dXYOi<$rS z@Mcx<{fbinB&}|ER_856eo{NA2E0JHOJi z7E+fL^gm$Ss^VQJb+=U8%yB(v$$uMX4d&DTh1Rt5;VkG3JHu`8i0fp2hOzU${~bH? zP>G+x<4X2Cdna~1IVa*XaB3=hW&?g|caFT5;5c|%Ki%;1a&cxRcCNg4R~2(ISG<-s zpTQ)kE0!8*ColW@zuMi3r^!2oy_9`kWwCbC*qgEcl)GAND1T3NxH>xRWjH`Q`=}}G zYW~ilO=M@?vVTtDp9%X^74P@kA{bEi?g3f1 zsr=N;L3ls7*EPEGr;5GKewUrv`Tx(o?9Ld&9`d$#tycI5IG27EeoX9K`ULhjjc?=?JAUX}QU zuqC^NoSY#u@eS~ooa*>q{11Gq@4fMt=Iko^lg8KsuLr$g7k*8g`K(_yUb#==`SLU7 zoS9j_PU1(wQ1%n_`{dk@r}O`n(}>@OKZ2j1fgL7yCwBHtXM8oz9F^FkA)kM9uDnIh zzBt%@?psx?eNTFQ_6u;PSe_r*uP@8FwB%ia)|uVVbx&h25MPb2hku|RKl40QZekh# zjX3psmVUCfP59g5cDO5UgX`e~AvH5f-cOKoVPo2Mcm|$QWuDpDx4E}Z(kFikyBefc z^LeBK=be7Hs^Z=Ees%i7>4#BN) z2l{^O)N(cUQ?&8?%zgI35x9fcRDI`sc$vQhvafo%Zl1rH(`|5ndgfy^Y)!ihrnARD z_GL}jn^xlAj#JO?=(h>Jng1+3bG-%*;?Kt&@#m%Ddy=g0|K9oJFQq?$8)={Y`@L8< z`S~2Q8O|Ph3$muoKwaCF+i(d_h*_vw$=ea-DP z{O{RKaUFAzI$y)DR#kjQbSFFYcdXBAABpAny*9wdiTBn%?Y4zs{E_q;`tBTqP0HNurRYmsZ6u8$w_7w+c-+KX^LbfS%}Dn38FfwN{W(YKQ~ zgPl6g{kMQem*?SNT08M4X}PcL!%fV^{&j57U#hg+P$aG)%XbdIOEKH{v|$*KHGEg0)339<+;`w z-z|0)UIq_|O~;KOHP8dDfS;{+amfW?kBaj9sHM;&xP&TOWDuj z&T`Iot>KlvBY<7tGxd_^5%+V-g3{g*qcFJcAgW@x^~_x4`6Q#YvnCB=H+s>=RZ_wY%mm>KU|!N zH_6F79gjDZbHCUs{`2gAT(b`>QqX8sQ1}39;quGw~Mu9oTiD#812A zW&JK>PcPd;>>sL%bGQ~e{|{#FqXqj2$o|L~xq*A?OV2ZUH-4_)K&%As@|*H+Gseu{ zZ?utk1-%K3<3EN^q3v2#JliwpXY(_@(c-bMgTvX4ac}vVqjmgE@&BG5?0aE>*h1Gj zvdX;%6~%M+Ms_c;)YI8G^S#PCA0fUC?GN!hZ?a~Gz>ng224tO{W-o-)-4Iwy>jxib zcN%^PFUEJ^ljLRn7Z}5$O3w@WF!~j6kNFuudxt$qye&K5hu$SW_fe9YpFbylBkex^ zr|j13TXE)nUKn;|UrEn=WIoS>ocHN}JpFulpPlDk#*r~Laz7RPN$e3gb&$F^fuD8h z$iE)4PwMgC#dY|nvOkA;a;x(Xrw_w9GmoU#!rgJ+Q+vpn2!GMemGd>!QSYtzBVer9 zuCyK5C$XQYa8CHyp1U4gXN?K@8%CI6+iboR-3J%Av{k10`g9g`sk@m zzKhE}q#iF8Yk}|Mr~Uh`^^Uyk*WKY5_Db4T_!$1la*okQ5B?1}bI=d}0FQ{L-{WAW zYg`~_5WFYnXt8hLT4>GBeMHaS@Y+buUG#h>-@W8$hUcBRwP^3k*%ha@GPWP^kN8)d zdmb&8XU>WImh9~5jrnz9t@u)Uo+qR6vCxWsFFosB4YCdc`8Trn63;l2vp20Ve;@o5 zBro}S_saLUThXqdwT7GMH{tA!`=LHe)%Ibis`Lzy`@H!`O>NHqgnqJ|>+oSX`)euv zQ1)HyM)*>G5BalkK6B*0vNyLgM}yge#q#@kvvB$ti8q6v<^O3;e`jY84}}BRBj7Lg zxwxU6H}SbR_wytD|Iu_8;8s>!7l3IH0R>ShB~%a)K@mlia{v)Bz(VZ8?vCp<=ul|{ z1qr1=5fw!R_C~S0>)Kt{ymtNXxZnMKp7D%1$DC_@>s!6|IUEmWZw7yri#<4V{|q^G zY2Uz({9WW;C3i7NzeIpA#~z5k#jn%q^D2+kUIds=Q$V9KICW4T?6No?PJh|mb1Mf zo+~%|Lq0c6eF|>?*{{BimcJ{qq>Rg29 z!)Bh5@9dYB^%MR?xy;?{F>kZGil+^0_&4CAl{pk{hCE{jJV)IRvgenJ?<@9Y=WM-A z*>!jq_=2{Dc*eaSJL{)9+*4VMV;fox_DI@z`4iZ!XgjdCqCEhs*#qhSq3_Q>n?2=! zsE?=8zLt}BISO89Uo3a1c1&T{mD33((BAc~53p;q|IHrHPT$-LvuJtG4sZix-_E=| zh`l+iFBRVbPN$87rEqcCe4y`yd-I=RFM;~}oG~AI#zy!6TGh&8UH-T4v}JKMuB* za|pi&3=q%0m@#U>ek?Z5`pYx+#^39&eD~X%oim^W`&8Jc#Otu@v9rHs%`AsXc7AXA zb^37e?)-@`o1HZ|k-nJUkG(7VTDh46&#||pr++RN8$e%)Kk@9$-^X$0aD|*oZMu*) zR=JM+7I322a9YOxQupzP@dx}@ao#)o$H9;?`{9~my*=wQ{&f|_y>k!xJ?sX!p?J>s z%-&eILrXJ?+Cm0YnsA!C&G4u?kUR^kV+b2mSfoq4enWc+u8 zGa&0D`$!#mY4aGloAU1|7i(z+J##nxH5jtr+(_FOIMJMvQO?x#Sr7Wv@HTu}QJho%q}P!%4t}6D#&_Xw<+No#uFjYF z)62#DnT9uq-`5r6mS?=k&Kjy!S?nvbl<7^+82_oBo#^k1cVuUsy#*)puf_Mk)$Gyg zm{+!zQx~ldG=Vy_yvwF=qFCNHn4zR2fl(?QC8d$n?D z%NG7^;IZucj+S}#E8NOXKR1WWudKO?xj?3>x|&Zg~$uPYVz(4O?F zFcL1HUw|`KSqnLTGyiYI+w!a6{650f>@JnXXPB&~Cj5u^AE>(1vUSFcWE_W09O)!=} z3>vc2j*jg9_y9Qvm5a~XlW3XWgJ3uBbSnQqu@>^Gvk%4@_ssF#*ez-4$0gqB2>RXh z^mQltDCPgfPJ1dL`*7Axet$me{8?yCpH3T4>Aa_>{Tui%z+hT^{wwTb@#$~?WDK6F zuqVOOVrRe=^w#{$nOXeraZ7n0(-+X^vUg%<+!u;Hg>%QrIG@i>AEmE)7`M&%t@v9) z&Z{$_9y{~(GC5b`tb-@nx8Q?}Z{}PDJMVdw_}}o~J&gYqy@A|n?99_g*|qVOa`N1T z-g`W4h<1&q)m~TJNnRG~CNF*32G)u9!CC)l&&9Av*`x4A&=X#R-OARRdh+}8Strjc zvn^x~e8qQDf3O?M*#;j-I}VydKmOen#hUuJHoOMS=-p|f#XeKtV|Xfk9e$U7wD@KC z3I3Jk;?A)r`xV-s_##@~=>~b(cW+~-pK^b>9<*8Q0h7Irl!n`HYx; zJ&T_CkoKo9muW}#imdByV%zfju&2u(Sy`;bTlqK1KLj5wC-d!He){P+cIxkkM~bB{ z-eTWD+Z=krNOe9zdy(B5TEe|>dqwdXFniry;_dj?(BJ3xNapl5wBu<{;@mmYx7nvQ!=oW{G3$N}WG-~T=hB{+a}_(!IS2aDGJebG zSF^JozSP$AMb3o$ZH%|YzhTdSo#?Of=gMn^`$Ffn#lCeY?F{~1a?Zks@mJ!B>T8bw zg}0!6z}`{L;0p7ZpLO3yYz*FtzAr6j#aez-yftK9*YoIm;+bNN*g2Dj ztt&oXc4FUEQQVP^728AJN$`&6oXXE$f1LRFICK9X+)=EP*eH0znY6q2YA?1F|E1pa z*9x3DTVKvfeBoEc&G?Ob7W-fN?OXl| z+y&R7uV!bg*Yg{|p|tEpS&!?)GXF~SCtw=84t^SLEvd&`NnJJNPo?FZTKBR}wPQ7Z z0}Q7vmoowXrtVtojPD81vs|2E^S%2V`q!`!9;c^Wv*k=;=R5Sw_gehP6-Ape?{{b4 zjo+c4&wfqLK*%1k7wt%P?$%TAtF&X)(_Q`#?8Z2I|1^1-*O}9qBTHy`{#pF2zjOC= zH>IU7_kcIqIfGJ12Y%jjBGjav4S#yxPyGF0V9D6woT-^_eJYFZ|DU8ylD8%Q5_Vg$ z>)>$O?#fK3t>EYPBX-Bl%#-YQ57U3GbhoAVrRV$@ukY&7)4zwoE|7H_REPJ(!mr^x z<0{DS{{1^;O7?HaSRd~_v$y4Yf%Nrl_*{N4nZJ2u@eU*8AI#3aJ)OQQKkxp4{L9$+ zj&>YwL~98v*oS#u-s}Ae`v#1sSH=0P@)|vJ{x<&U{9($kqSc~hd}h!a()YsSXxS$w z@=s>(1>f@P;l9>w2RX->lPBTy!&hRh@M(CU_NKqC7V9Lx88n2P69>>g#_xzdDQ`!1 z&i0&DNAk~Lx0h3$|CU(3ubRU?ik@}$3+-9`mood)FH}_BLJ@aN)div&R{JFYr65E5H``5>wm%Z#Oe#ZGm@!{-MP_4EPMOGw9e|C@0||j=L{{;viGc_XDl;c zdO+4$e%|~0T6;Kq1f-sS$;RO{2Rxcl+BvTy=0P{oC%p{nJ2r^ z%Q(*%?ChUep^nPpb3@i#1NP19^dCJr^W|qg*H?E2zDF! z&2i3*tmEJ1q%E((Vq=|H=H!0xqgdu~`fw6tE$8#u>-4O*+$FlwZ^hXcTEc(SaS9$! zYcDTzd4!xF*lG7u{JL-h`%rC~AeOzeC452O8`r>H;j>ET7=8ku13i1`<8rZIq)(2e z9R>$M_VbL#cNONVddJgJwiPX(9kTD|jO+%BllF4Gzx zslI2l!7uTj5Z_U36g%T}4&2Cp19E2?jyuTz9^WQ*9OU0q9?hRsVZRl7i8hSiiGLWJ z58Lw((7r3g($_us^UL-a_BwXj-khIy4(wU1k<-}si;bpdZ~usXw)`vD*WjILiU*x#XH0E>{*q?UFb%B&V(m$_M=Vk3HW{GGjH!I>1XJnOl`99Y3GVUSh9SCUa|$e%VjX+5BPjM&fDXR{TqGC4Xyqx3JSE z`HmyMXOni+pg%*eE%ylL->I~L?D;Sg2J&w%7x&!s<)8dl*=tJ zpZQQ9vhH&BWG!d!S>5x0eNDTRov~em59BXozwceLwlY7*Dw8>%J#Y-|1vy!dx3JS6 zd7qpEyYZi5Z_i$8{Tz+27tgskmVOPSjmgO!ZX2=B@dNlSTIPIT{&I+&_qbZ@VeQKA z)MT&w9CDUqKYFKB-082Bx0aoA;V-dAwEa`qOip*`qWvwzw-dVn20`Xc?f^a5m*Xko z&2iqT18m37*kmpq3T6J6%523xhdnnr?9sH$&)kOxvG)=?lD|)|w)mOKDeSkzo)OFW zo#%hXe*-307QeswJ^M>-zK&lN9+3MQ&Uc_C?Rt#8bJ^UI*A#CGGuhWe_U_O5nU`tD zg{9)N;+1$7ybZ5IrPz4r1_LUJ&!_Lw7sF)Fnd~{$#Ttp_J#WJIh&7`<0FTf{@bmei znf&$q&iwqWV6b-`DSs?Ke_Q1y`B?`!Gw*;0*%k1toO@xuG6&N~$=`@xA0A;}h;N1L z@f(-jX`!v$eb#B@r&ZSz*jwsF`Qjl>~oppH;ZTOzkuWarp!8a=ER5c zvq!bYnYXWa#y0E&*-hmP!K-oRYsPPwXU}5yXU`N{A(po8PoGCSSZuiRnVZQQE0#Ic z38vH9!|}8u`ERiQl9RpfW@UQOGSBk4_ikG9Cc$g#iaD40)S17TXFVjoR{n0Z+-ncR zx6?DeyNKnCn6I7|^c!enXpiHp*`sK~@DFed?RL2@v-9j@`Q4#4KX<0uw5!CoplyXS zHW~lD@g{H+JLB^N`#s3|&7K>-EeSB7JF~8ZIz!ae~I|zIOA7GEO>`@4}ToYVh^GXP<|W!dF)-4eVCp0j4u^u#7nfP zVk2ow#WMb_`LBp)E}a1n(7WP1_bX@xS$n_IAAwW&>Hpj0+{T}P*U+A1=jX^9a4+o` z3!TJ{gX}pIE4>4b{*IjNfp_WS^uzFyJZ(E){BHI%v8&{Lgf|x72Hq8~f*XlHPX9JO z&YBzmne#`Ki@BGx;%9kXaMo~F7!A{u3Gb`!=$n^)Z%(^b?lP!=E$Cx#>O2>Q%E|nn zgTJQdF1{Xr0!?T=@YVP)$QY&{F2FDF2eLEI(?{8>Z_z(}_*whIp&u=4D(^RgJrTyT z@A8bj*^T8cfvxx>=ozmGC1()c4BF88i)9UF|G8Q$^X(G&gufSVDfR>Xvvr=uZwf#0 z&!^{IzhUQG%U-hpy0NF0i~aNib1`FaEj{D@40}sl8)yCfLtg%#?VsB4Hm$$BX>guc z51hZVw3hxZ>+Y?=4a39jgP`F)1JfGZ{J|2U%!xh zm@@aXhvN_Me6iN-nvlKn-AeO<-JV^6N8$g^Z!PwXI;;Dg0jx?8F(Lhb$mZ$9{%ll^=JnvpYJ>KURn3&;5xL&n^c}-y}n8@E9j~1JwJ>AOs18UGdrOjt=0r{EqO1YOpN4P<54SIE) zJKK85+_(ddRJNhmQ}Qx)Y5xKI@%;2j*4h2?N8mT~Yv%m6^i6R3{4RNa!fJJIg8R|i z(l3U$l)DL6(sxj%HZ)L2+WICtdsg<47Ro%Rd`+D1Z`b3+P>+^*w{yAp-se#EJMvrM zBk;|5JNUFhJ>_DrU4^d^yO-7p;;um6|WY|re&pQubbvDvsAo@%@^ z_xBOcxiC(CC1h@OpwFPEp0C6f>|X3wvy@HWEfPCe-nr}-f0jGMe&_*Lk?@$+5TTeOYTbG4imcm{ovHfBG`U0^TVS?oMzPQ^pSr_-{p zf53kce*^o&VEUf81J0SSj-C0KxjIr^+u_XnEyRYcE527ff_<%=d+@QijQ_?{<)_c4 z@;{V2ALkCZ6)p4na5*jI+`?`~KZ&3F!A<-r?8dZ#a&KpMr|Te*vH)?m)s=hzR)t-=oH9Nr$Xmw%5l z7p{iG;rLSV&RK(-%H4#Y@!ntTFaB_P^0Jqm1$nQx>G87%PM4ec-n5tdy|VvVSM1j> z(Od9yCN0I;SI%VD5br7f9sCBr3;bz}bEn&0eOXJn(;kZ#i1lIj65G90tnHjn`MkLV zZwWobM~SEX)#PLy_2VC$ve1g1GwI)+@dJJe{vl@#{YJbo|3Sz*pIj=wSI!(w9hZ1c z&cdg~QtxN%``F9Zn&dSa9X7?>QJCxg$UlYz`e}`*{ z)q{bR#lD<9WFY+pNMC1?Zs@E#rAVzl)r-FF#K#^D~xh@tORT zeHm&)UD_G6?bJ0xPWrwnTn{z)y>R;cXxJ38e`en}mc2%~%;h{Q=SiMhR!{c0O~hx? z*3zzE9|#}wmy7jcr`!kZ&BaUVIf(xYe=2_`cHVcAywho?^0OB$V3+6(=?_EZ_?0l8 z-C1k~q)#8hZ{zmL?1o2+zs64c=0X1d9lO#p*4aNV)8-TTFO-V2Gktut{EP8xkTM^_ znR1SStiylsd$3zT%04A8WA%4s@p<_;oc`SiUqZhKryoYM2SfV#9`$_7z7;lO-`%sA z^Y_VH%s)}iME0|#;%?QCpM7ElUMTlk_LI<=e{V&RJ43m)w2SyXt(Q~SnO}|6lfFKK z{{)`JKac$}dmU~qr#)?B$euo))~(VVivKnHS9w`O4SQ<4c=pzJ*t2OTi$BIcNB$V- zrp(5@l%Z$tXT4{>WIk;UPsv?|)27U==IWS(&xXvmAFQnbcs;bGZ6$s>yNPn+Jv00J ziMSI!5NB^5D(7d&e$}j0tffKtB}iS_4>BjVd|rH- zeK~zOeK0$H@Tj(>&DjIz;}Q63oI6&|y>G-{gRHgh;7+k4V7OQvxfSpWJ7@AXkUi@+ zJX%ir^dFG9+aCtN1@s@3$y}eMT-sWXeT3L8>_+VLTVMVh_A)tR@qM(MA^A5>f0T@! z_pU1^v8=%w;uq3CUuRFDA5P18(2JIS&*$W|{Gt4g;(2aA_OY})JA2nBo|QHI*q%jR z?ig3m+sWA!-^)ZAi=90vu|@C$Z8QEZrQ-A28`_l57Yq4siVcGO#q-^1b@p)j8Mq&9 zJpPkj(l2kQYa}iEbXDcE4x6xB(w=4S$xhw*J-Al125$!P~TSgetrYU z-)PJ{&RWYj^n&H|LA3lWgEsW+AN^^W8~d@BLhe>;`PmDacy2BJYWCxhy?1O$KjY=} z%<=pA{rH@xf{KXan4ccN%tgJ|j3PVAhu572L=EzsxNu}8AsglE_r%Sqp!B!7RH4wE47 zzOkH);e}=Qd%U~nd_~LWs4dv_*@w});2IUh7~D=D#=j76Bz7KDE1RdDeGI!6JN=w~ z@5VnCnnFif;`uJ9F0Q8kDrh+yUX+{p{X9Ku=4^KUje5?;o3-UsJd>XNvp@SW{IciQ zpiPAn;nPwvS2H*7g-P18NZx6Xeg6y?tW4Hlds^-%cgjiMWL{0cBXIg_uGlp=_kvv@ zeN+REfXv0u@tI|FSv{-Nng0BoeJyPP`#>1O&brBb&3ONb)80DlGo#5l5f<}Ds^g2E z-c|f*$i9@d_;F(N-(W6%to)2wZOB~M3YNg8^nIZv{cHZYa<5e9-~4>GJXvfgWDm$1 z&K$}f`na)pUEb#M_N47d8^O-H`&GG%XdmHxF8hME5Hg=LjydbsvNKORm5RGS?&;UC zd(%=^bDVdsNy{8b`J4sWQ@X+f^uPGe;VmnR&w81oTRV43^kJTz+-^{Xegfn(O+IVw zKs!v%*0hOu1Ej8VOHye$*LuoLUreN3K>L}WyHeK5jGo#`+nU`1MzJ%Gc83KOp6eMu z(sQp{3JKJeX;%XKi(-XMHrr=i|)(tf6}BQ}Mr*$y({c z&mKMwR`Z+TRpLX~XS0X1(--+Y?$#y!$Q~xvUTh{iXTWpXHyk&EZ^SY;w}W2vj8FP$ zwb(Yex_Z)v<@BtRyK#Qj)dj!Rqu5I_t^?pv@dqo5`$pD4D_V0}VwG?Q`()VHyG*Gl z&iC8!a=AHca{qgezY@~_yN78l*mtt4;+;Hub6VQp0cXBsZlqu8<8A2s$Zg18EjRno zTF={sKbJjBEWc}%eRvrxlvBp(%Ukis;(xK%DKi1SRbR>sqU{S6@Cz;Fhe7%}|Hk<$ z`bAI^2GH_%aDI@N@9rna+ZcMnm!7v&yq&!4qfgTg@|=fhhp-pQ|AReItcLRGzq|Pv zgKq4V@POEZ-r*p;tFqaL_Jhp1UbvI|`s^9v%iuxwKKLv8apHe#L;lVEjqFY6nFnKF zl=w+7m|eA(vrMciTqUnF41&GIp7+cf<=w&0de3>1KDh$UDHlJ(&AfbqzJq7wZ&vLr zHbU%OxSQQhtOXv-f115N%wlJ)Hm=lm{m@AKAY~uHKfzd7RAEoTvpshsdF_?SXQR&a zjLjt6mwr3We|d-R#8>lM!9`FVvMvU}Pf)j1tn2)K;V5?2Xe)JOET0kY2Is@3{9Wix z@eFxi(&l=<+xX+?b@7e#mH0Jvb)eON=JK)!EXS#%1A83%7@YcUr{!HT&*rli+Y>%z zH>HhIf5!eaxfjw-Wbd%P$e+ruL*G)F$6*ftZTZ*9`OEXShFxjz!1MC*?98ig*x7ro zfZ8xyes{UY;*4t_IrH`ZWc+H0CYJr~T6Xr<+vJSLOZWqEJy-|1>n^4BU}w+I-=&{| zFQ~infhd`g%F-KA0qaF@GWZIY{694Exg7;Mo<$=lSf5 z|D+wFo_hC0QVrv0f~H8st8DA)Za2Nbe5$jJuM)7kyrZ?=omF@^j7|!Ozch za&`@c>tG};?L8t|sd(S__)o%tmBn`nm*dQt^W|i|9V>5;=bS}}7{T_L-B# zmy7jg53IC)aoRqMeIUCXZo%J_eJ*<|_<%pZWbJz1KKv?T9lS%%?c6a}!=CI_xNAjG zPxkYx*2jUjR%aSPvtL_pLh6-eJCyGN+HEmp z5}(a~nLdx7=cO-Z$#2Y`DE2Sho1XhlW3i#QD*tG>QT$507d`#(f>>*IUwY>3#kBN6 z*5L^J2W<@dd2QZYEL!%owDmQy8Dh2Be|diYN@EN;tEHd(`PoNX;#J1~TH3Ah zr}48Vw`JdnJIZNSDn64}<9{eV4-bYsb9cx*m?LMjSo-fT$g}ePt=L)f+u_XB^DFfw zB}Rzh`}!sPg>W;}pyzMy=N!-Ta?fc-%b9UDzLS>z&%B@6vseQ?rdE?y$w#E zq%SUl+OUk)4PT2h)*G?s;PyCey#lgFWRJ}IzAjeDPXA<`G^eFc4q}hRnO9wv$@{F; z&J)<9*v)bFwY)>svON|u{<(84#eFN?*OdKO?ymgwUst#uPN!!L-U)NXbG9yn>`il^ zHtjxV%LLj4xrf2C(1qU<8bX<$Io<}Yg|7UUEA=I;hb?KDXKj7Iw~(FZTp?$sxz8Ib7`$$gubbDo5(mKh@esnf%8g7IK!FT$grkwuD=5KOtN6S2E&cB5{ z+dHgb_hDxqOkigm^E>Wm@Q-DWE*0x}lX7vl%RauFavA3_+LC$o7kj?khauk~pGQld zui(Fff8l5S*M-;FAHs34$@=2+epPvUs%wPYmC#IV5ba)g1@gXk;Uy*g#Qu#vzpRh6 z;~`qsKo9zBuo6xZe@{H)v)VH<4=#b;@_OOi7jo}g&u&k9hkrfJyL6^yyq2=>qusbh z8*pF#h{V*F^}B|>#Pd7SYpZLaGC3oU=clgE_<83w>?-2Vv#%F_MSN%e0(>O&W#=4i zCVz_YYQ@gpa~GkM)bVw>c;^Ieh^?Y7TKmG*$ z#8#HQlUU|K&hLph_w>xejB5{=EcbS?^uxts{}$Vgy;R$t!I>v>aPBafyP2=)m+VR1 z*?ZxO@sap$^)!%oH2YHA8~0sb+=cd*Q%k%z{)nDE?^)bMI~TAk<>f3|PHXL5elQMC z%T0f83Pb7VDPPIH1it`j|5nO816wLTR%~bM^;G`H?Dpar*ST^V(wlhJHT+NcOXW-y zKa4*UXDuGVegKXWyMSK{eu7r&9;8j*h-Kf&cQRG+S?Wpu=DB0}nU_6r2hZuEj&byy zR~d`-_;FYQ7wYFT#D>D3rDDDP#C{9TD;M9{{YTyj%Du{;1UV}*o|k*}8Df(vi@aTE zr}H<{zHQj!O6EJBf=`o^Id}(uUv=FsFJrS&Aoe=WzSxlcpMXDB&i(j&xQLy8S`GQR z$af`sp}gH;fANO=NpKVU2JI@#dy)Nywp6gcVNb%}!Vs9kZzL~!Q~EdWd^x`x|9ke% zFk4;&oPBi(9Lc}BRNRqXEBoxg9!cMd-JE?f&RAru4ugDOF-Mu?yopECGR_Y`ch8we zzngwIoMzwqx}*(a+u{beueSfk?}Bs`uP#6Lfu3TOaIUf+>CaA{aVY(I{z>$G@C*Fu z%8$lP#b?p>|b!!*-7j>wd)u5 zo3N2O&cQ?I*_%HW+Z9q@_NqI@)AscD4syouXVItOn%cHC`wRBz>>csTaG=~zaPog= zUtTiLApM!K86~#Cn!JpCCG9Odq?h}?=e84jTyX9~!C*%Y?)9?iiyfIY!8cEnBL3HF)vU7#Zz zPP-JoQO7Z26WK@7XTy8^^YEjP{W@cr`Ft|_d3H0r0N8HH<9+E3Z1vYW|k%uk;;fTgf+S>LPoKREZ3HnjXc&8yxm@02z6p>lKC8M8;& zyVClx?_dvrX8P;`@u}?eYf$Yn1=HxZncQieFQ*C-R*i${f!{@YG%ICbA z`@i@IZD`2OIyj4+v}He|%tH3lVnd)Be{-C(VT1S>_Bh&I?2XuO%3TZxvumpN zXnK(H17MuCjiuLQw}q^;$?`|ztnZ82*`G3(SLvU-*2`yqCU&lzK9KR7k8^h!kAGUL zE_qkdn`>9**t_bb1riuXHxdOC2$bj0~xQe;_sG=xwoACC+#pikoK!G z+iUwE_EYSQ#O`1Z!8u>*xo31>A4{tXpRqHZo8xWx)7iNTUc}G)Ji{M|=hIGSpDpJF z{z?DKNk2ZpULMVJ(@z7~Q}tbbwvc&ru6G?FmV4;0{Eqadw0Znv_1(63Bz;!O$(w;| zmx?`kQ+7L8#?RUJxU%EQ<_7ITb^Jx!ZoPY;a#iU0ec-hFpX?XJrm@q$)O$OAg zdk7iF9oc{27x5OAML!S4n?S~CGf3=AxD`(2-%}D}H)IdN_rl}qIDlT)^D{pOi`@>N z(;D_FKJPt6&)+}X4fhqhRhd=zPWVXPT7G?Y?mJuK-T9qy#_%QfYuY?dUhW3z*9mY7 z{cF5Q?_%s1^K%EBgjd1xp7sa)0Q>?i)%B|UsyN^AMZ1{)B#fh-0?TRt;%Cok#s2~_ zKW?JuZkD}td-gJ%?@4OnTi}LrF<PTOWLEfYw>X~fwmO# zj+=Uq#8$I2@4gj3gnvLqvDZIA`^57jK2*&l0SyMEBoJ&cg;B52ld(C!2-+WNJ;P4JWaFWJrT z9LRVKE*E>;L|jeT(X_MW+`+$ueXqUeBYtgm#_e8q6?|OTyTfwtx(CjEWGcSBRD2J1 zioBno4t)+Sd+>Ga#q5mTgJSQoKgUnu>@io-FK4fZCG2+P;_Mm9&YDWO_K>~c67{W= zH;-M5)&plevgiB+jl~+EwL-1isC$(fNp?k{&Fdpcw^^v9E)u{*z= zdfHSJ-$!-h-v~Rx;ByPcQAjPtdLtPn)ijGm-xk z&U|@ZJ-zWHu`Y5y$h+XJXbt6LPELfhp?c3^d`Gdf?z3;zz{7A=&w8Zn3|4*$|2g)v z%1y>;`-L#jJB{XNyx+z@i#Jg20RB(Pyp3nkn|r?!JLkqCd?D>U&(3?~UObFFlir`c z9qoN~4R+ok^Q1RF&mGCm?=dW5=d;61{APuH-lex*OwsQY@_fv`WELwkv~s-ifHQvMx#R6&XO&lBb-$(_Nr^~F8cW<+BfWJc&RdJ z^FI74+MIcE8Z0Okkrc75|O>B72JGWWMjqZbILNo;s$$_w?MI|HS#c*jcRF znqth36(6Dv573T-LGseC_rY@c>DyDw#oT_D{Ufb5Hkg%{(vyTkrsITP=M*tgPh*6hyCTAYCMj$6`h!I?X4 z=`Z4q@G!j6yPYnc{k#powf0XC`%8Xf{5{UznfbmC&f3b}{tW$S_G$DyEAwRpjG~X^ zKOyH8Xs5j$^-I1V$#`^zyvqo&9*{99(bAT@!%!FlljW?jp59Sk&a*#o&ZQlPO*}X; zcG~?g+oNAYkC#P_s>IVXF`6~`vz?^ zdlf!Oxjy2v+3S_*Kp%p$@9aX`ggqVqQ(i+jMEnohR@!nNJKss)Pd`FVNB+^cJO4vD zE!O)yPHO|h`2+Dn=vZk?aOP0X%FgPT%RZUD7k?f5RLI>jKTphheNKDcrtQS;53@_= z5H0ibV&%TKA0EV?FSefF0%pt4{{u5?=Ro=S?D8o){aFj=9DbVCh5rIS^YCMwedjm3F&fPAR#eVm>`qF3b%gJ275AvORZ`ua-ySP0pgMHL>jJ!$oN0rMSTwzY1 z!asmtmH#+g$6k$Z)TXEKPyAz`#9mu2#yI;->t4k?db7ea;12OxxKi$Dc713sZ?flh zrp;sjLCb!(1iynPYHPH!;b`_b%4hx@%s(BU?HPN}-{POcZvYvqC-5HXT}V%#j9*v$ zJnR|vUeM0-Zijm8XDf@(6p!NVl^qRdD!YSvuNBYw>yH0L@5pZBnKR`MrDdO3jECZB zaEhF%ID1X@#;xVo6yJ>93kHd0J?8uL?(7G}E|QbJ>%GQ(f&C@4VR72grHqUAG8&Zv#qBOz;Ud&oZAUG7y-NnZ=Y zpaYa>uat^2s2%@ye)@81xwuPaPV5Qe=sj`#-Q}mw!#wXO&%c2G34am1!_Jz{vo^3# zGk^D~RGxi4-U1Gim!IW*uWjS_dH=L0Yd_<906%-_PB?3!Htk;M#ZEuhWamA;le2>V zqk4YFnUmKhu3ZDy3+OGIs@p`nk_^lyh(2su(z6y7O z@$}5Yl)0Qg4e!^>IC@?W_FlBi*InRPZ90g*Kl^HRbil{(^RBU1z*NtCf|l@*w@KFmEHy>^HX*XeqF!5CGRiK--0#_M$z-TK#S=) z-A$zM zd7hkm*l*A?r?Yov-F?r_+W&@~F-?0L{m;)@%{b;g4#dN}^9lGmxqWEU<(nQOoH=%+oXp3J_n>m|Gm0B<-YsSOil2xd@vK8=%PQR;U?kj~7+zB@?jS?? zFD93k`Fa`TJN3`STfsx(4dCC9d+-o^ng0Hpo%`x=rF^(he6(J8~9u7oUdftNUGCN31%no_N}FF>Mw<@9`1r4f+2S-zT<&{e4Ao#`a-v zV1En4yz3w0yDE1NR8e=ni_H9ZLtfgEeJt&0XH9g+nP+z@n|J#=I%F=)fd?5xYP;63(h zu!~r0I0@2ExqsCt7k8G$w9f2iI5`8@dqC>glArT_KK}&h#9vQO8}j_sus3~2IE+^E zPG#CEIk!AmjB&iq`w7au_y5c`W@;$m>;_tJL8bK zI#2u+e&%7;dG@YbA^Y?U$QtS`mijZ!HE3PDB66J*vESo z=guAU!^J!7ZFT&HaQPDJ+ce8&G2e-c>3#HNdNs#Z=}wB)j1M>50BET z;{QN1!bUlQ~+?yvu!ee5oi`O?|(>rE=4cSBw3? z&wSbwzlW34SN?C%Q{Oa$Z#^TwQ}e5MA7uvkEVBqd%WnnQkDJJE3pq>X;EYu-_Al)1 z@EzXkI{h$7ZcX?^{4V-X_Coel_QULv@Hkw_Z-u`Rdz!tRy|4F4{n;Dx{(p$gC>5Vu z9?-^xvPw&IT+A7E1(Cc@W<7oj{&-kT%NbCeb_oAk&+2K8FP2xOqBvi7 z#J`L01JBFN9C-p~%r=4P?97K|FtxJye(!AD9P)Ga$@I*TF7om>Hfymn4w+|}8|UC% zD3!+O_Os@I?NDxEgH4&K|V~u7?-WR>2PZzL1~8PUL?G zuk#yv)^vIEOU0hN8SPW{xwH`v6nE8^v}LXQ!{9dYv~Od)9@^0|_7}lV`YHb&Kl|`D z{0kxb$SnTYa&c_Jwoc@jL_f#`*4YEBTjcYxa>R==uD9J*_tFN7``q z$?U)B6X7~qO?mnGc>b-=qvChNwvhMyNorOBy6y-kvGeoXnYbz3OWTs4&xAm?csX<c$X|&_TvX^Z} z%e<-2?nYn5ABMB{&ZFhb`@6!P<=uMX)wGUc`@&=JwfyaA_1GWTKX2t{%r8>zU6{@O zMSKZ;Z~QUs5uARjrOsCR?RGg;;beN|+D`2A(ir z?=_6}7f$;Y!LIxR;STn0>RzJ0{_Iob%%}I~&&6xhKZf3#o;CX{KX-#6V(oC|&3)n* zu}9)gu#Ngo6>rZTDgOnWxskOwkN-12^D6T-b8;|zK(CHR!XvP(vZ(K^3UivjulV(} zS}{oxk&HV{2~Yo0k;{t38yx%iAYUi&g1GiEE{SN3D#7s@Z=bmv^=Td&wV;A_mZ0~{r>b)AL9lx^|;n+9Pve)#7_3X{@OE~R2Mfv;L zjn!R=zol&k`JQDreiQZtxR;%^a5!G-tXjdoQQ6Fq7xAgulr{1N?7(lN%~fb8${z$7 zxAf^=VqKN}ihY-y+?7W2zoS>}Rou-!SW~{DycU)T}J_+cn~^D*G1yVAxgpW&BU%|;~mAFL9p7s(y>v<5I%g?-@M9bRhz|K5qNw3a+0*+-TZzU|yu9Cd8Cw==n zyu$yk(me^U;Fn+~`z^WYk1mkDNPXYa^Zict`>8OiT%0?9;Qo;DII5z!<6ee)@tf0s zz*#>p(4JkZE?V}S!)U|V8JCCom*6F|=jCO;%iSaOwxpead*XclTaBCWTPkxKJL@NF z<8oT&T^oM(#f-r<^h$N@LCgMl63(0$2&2Tl#98T*3%9+&q;q=EVi|_Wj)pBuY?U^ z&G7AT6{H`g;N#2A5Ba0V8Lj7upH99nSii zB5#3Mo>v1u51Z1Thxgz)@0s7x&YW26y=Kz#oA$X!d6(zxahC^5@{=@b$P2Wc=>L@51Hs&d1AWV{pD_cnat9b{ks8Ics5eIG>$0 z_TRO||E5%YCpMdw^JJR%k$CU5?qlMA^H0KCh-Yq&gBRHoaCa4Z{sE_-K9j$K-&kG0@N4mB;tsT)_*KZ< z%I|I0rDc9zF6StGC45#c?vj%{=QsYrupD~P&w;(^`CN26J9DB6zY9CR*P63)FLo_; zk7KW3r#}|roOfAc+p*t=#`Mgk%#Ul}P4R!>tgm}u0{=vQe_V-gSN0Efz6;FvtFbe8 zN@5v<$rZ)VcT-pP#HZxf5MK-XD3g6T_ocS{=j3Fc>WI_dv$g#*W!mv?gthW_lb=5M zj-G!bletkx`OLA#VtKF6#lDlj6+fTVPQqDh-D&xM*o?)S(pJIsum|Mb$H~d~cZJ^2 zTAjI5M6c@^m&lud*H;vG*^A|*u4$fes+_E!m0}Ni#@77Z;0)Ro;{EU${Payj^$fu? z@j#sReu|rV-rIW?Kd;RmGqP+SgWBxzunFwRPhalCzFWC<>K=-BR(1p}>#nLcPmyz+ z`tRiDp3`2u4lU!H{bN(O6_zWTbLp1K;?A)5Awei`yFTQx1^s2&0!_~R>)ox`)>JJ@Be-0QgJ3`QwWzSDn?@zd@p?Cs<~L(ga6o61EW?!j)#UJQ4Zif5mO`zn*Z9uD8@mq*1{ z(&ys-V!1C|#Lk}GiTwzk%>NCa4h`fS;+ZYge;xZUsK(A(xm0`$+Mt{7)kiBmv%EU- zXT9#gZYOsMdt2pK(>Eydl4qxnj$|Jsb|QNPdj-A$r{6}yDV4?Cx&|-TzO*&_{!3zq zLgwW5w0%p(cL(`9Q%BMFpiPn23;M7h6hEGR1>D0e#czcU)wTf z*;_N_EyPyf+V~+ny22R9c^W?tdCpw=<9Icm11~`S%URVMeiT~;^Vwfj6#M9-?1Oq2 zbG)6{HD&XRHiz8>PG4`0Kx_H`f|~3N^gZN#3uTxMX?y$1Vvo2~`TSgdK5ZXbzAv6f zYodJSa@OnR^!7OCSALFvGR`{7XYKrKEcc}Y`Q7Mu;5+!sapw7D#vpTQvHf@&Z36pd zdON(5KNx5JEWi!maQ%4zJ!jSRkbS1U{Hyn{4#dav^K+{o*>5P9G0o44Cd#>MT`?bC zq|Jii;-Aw$WjCmF*U{c(?0nZb8nQ=p7JmU>kI$w}fb4Ub6JOERiX93$?>B`>70!%a z>SDhl{tvhb2GetQSX8z@iJgb*LJjB)heO6;Oxn=XILaSXS={g6VfSPIf?vlg#M8e$ zpjWB*&LO!u$9ljsv?Xx2w(cjls`iwXeO^w=W&hnBGCz-iiRI!>atj`<+z{GAb_bl# z;Az)v_R;VPJN@63{**c&;#X(C=vgPQ|D)b3)o};>0S)D}p^sB0>wFb^j5d8H){>od zeXdx>H}hb}Fh6~ky6Gp^cGOon67sn#mb- z1pje-xx7B?*6@^A#y|JZZ{dm9^z60S`}$NCXIK9J%%j=ahstmobcXcRJY{<0N@%Ox zKh^FA$`)aro{(qmzS#}vbFFppwL)Oo>a&D!kPjVg&rHx~!AFg8O3_XQC5Ay#V z`9_;ECq{VQ>+&{{vm3iDZc1+k&3hJ~t_LxIy2gx7HUWC)H+3OyH z>)6Rpy*VS6u=BokO2y~6j9X)N=22~S3!KkGce88Bn}Oeg+hH+0Nc-@hs2&6VIsh{EA}DZQvinui?47Ll^c?$hhBzr#G<40P%;EIT=6BZ;Ug}N5M|zV%!?wIibqK=y>)(uLC%^d@x3slY);@qJ##O1+A*YL|F(8lv4c_cCU_8jKwA#LQz5_WdLXo7_pdDG zZ~8WUG!RdvjaJXg;^~K*)w_tE`tljQ8Qzqh_s>{OU}uiz4pft!@*ndvc2&y7_dgrN zGWRmK(_d%8bZxKj%xmP_j0cs9v+71XT|8s^lGq;nK6pAmbMAbxvtR^!gK|%>A6M7C z^7dt?-)>{?DnHL}z&?YPv1}^ue)g{P@9>$lHMCv$Ip?zGzfkAd^vwCH^yPR5TpQ=} z!&*7N%H52A1UqZwD0*4n>`mW?--bVeeGU6OoS#MQPy1B;~?i?SN`ej$vE@&0N9z|3I;>QX&hvJ%32q_wIz4x> z%!@zRndj~BEs%G)!CpQ|Y#IA7{6|Icc{1aFH0@E(JDh&UI^&|uc5oYQCN1Y#7diE5 zce5L_)7Hi8w`m>N+vBU~*-O4;XHR{S`dyzc?Dzj5wBABuhEIVZBG(_V#j z?By_peObAG$GaXj#)Yvn%kvIDL@6 z3)f%ycWAX~2dOvj+)Av>e+kmpGv(BxUB|A;rK6KKAcWw`5O+C)fvzrLU^PY4C;j%$|3y_(_m>=5+cZZApK>4e6h0@Cp54{-^BB zv+D3TJI@)YJ%92yhu>&hi8 zc@rK8bHw%#+lbbQeWLg2%>R;ql6dCw4d!Ki@3tE~cc09?FZg-(58Bj#zCnJ@x9myj zmtDnkxB3HTjby*b{K#FQH{`pbEBOQDR#Sd&_F?Rc*=7Bf{u=^|`Oz`OEqg9rG(Xh+7Ut9qx1rN3t3^!*SR8>a0j_Z0T_xLaj$ zmwKL^H9EV(yzmZn*m>7J{G6A=R1kWBMCwpb)?^tE7 zU=L-#SK%&$Gwu(tPp8lJtUK{=XhUC$j}yBC=J2QS&xSSdJbxKI^K)xl1>a52K3JFE zh&={g=MUy@3f0+})6cN~hCY=={4jh7>>$1cJ#EhZaWH$hHVx*_OWb-vFLUgOs8um4+yk zgv{P2DIz1;WRD1$*`vL8NkxN7RD_g_-X~=5z4zXGumAV>-TpqG<8z$nah}(8U-vc6 z`+lDH?M2&8Y%=`J&itImeg_{UZ=t;X*!NU;m%(?6<#W>-_Ezjk=JqME)^Z2ZYS8wA ztVh;A^SKCymx|}mAUup-mzH?WNFR3gK`Zt+@vWeb>u!!;a;+|Sow9kAe=u&!KL7^* zXXiVbyZDo}$+_&nK3nc7_z~l)M}Jn|>ElY;8)E4*V_X3FZa#H+CpeX#J#jzG`hAly;t(b>hB+UEor%zL&R@}7X0jmoTv9Ii`se-`yqG*MvI@K{ZPo>%6)8td&$k5 z_F(7SZHD(0%l=LM&3WrWYfBr8Qv*(yzYsp4b#;$V&{7xjeCSNe+)SqpXJ>uR=Jye6 zi%*0lwAAJW?3}ai{Mv8~|2_WmVn?zk&_3taV*ie(;>Yl8oH=fW8|+a$yE<1C@47uo z>Na~2JR{!BxN_bu;J*vC#c!%q-|*r5VR&bL#*^`8-M8XjgmeCng4ZjG&yQ zN7-`-j+HkJTCuCsgTxHr9IVzR z=U_6VXW=lsG3+TXXZ1zM=cAnIYvs+ttsvT5{xUqgT#WHX zoHfqA_}e%xfcHwpI*pQhiav*!=Ml7PJtyAgU(P-TH^C2yT~oF%4oAA@VavTWH)u)Avm!E z@vic2#WQef#%B0Lc!Bn=YyXSW{srweagX`ktVQz1!8!73ioGmnIxH3cnEngiPpl{H zXq^3eth}w^KK_~JV?(@O`l7YOTjF`NlW{A^bK{M~UH5bO>xlKDKT1EH{jY0u$2pIC z(5_;)t*}kB*_Cz`{LIfheL17-RU1e&SK7U>cyY(-sEq_&b{2j zzDysV;gk5AmWw&Q7|(;R<-a1ng1vRA*ng>W-Q{QPN8yY=?QW)5!AIbny?mCsn%$Z< zAFqPz?pIlisx+Pd2m0@=dXA17|45L&inV!x~tkP zKegj-obi^m*{7^NLC(+h^g(!@`_1ob+^MhZiM7V|E}kUTS)YGGsZ^Ym?5&^RK>kLM zd&zgJ>GL_5U7;?Bk7Rccdjelen~C4&H^fu;XS&wg`ptX5OZ?r$I%$`CFG1cX?-gqg zXYl75(`|64yoq=VdLQ;wyncl-=x;;%5Zbx$0%WfK)%Gd)RQw=*_G_L8Ifu7Y*jKm? z{S{o5o?1PNK9Qe2(m`x%{vGfkJ7-}St`7&uy&B(4%RSwL^Q`}w_Oj2-sofv(zrMe? zw_C-x70Yv{1$_wnYxXwyJh?-075+-ddJU(YhBKBA*vZRYo{cMM*<+jYSF(qQ@61k( z&0ao(oo7(rvhygGn%9=UFAQKGOTP$j!O!Qsx|K!!dJ+HH#hjSS?Qu`BE$yZ3xr4-x z;h)0($GzXeug0aC0iF2&ieG$R zasDskXKgaR^XWC&YsB8gJ>}1H&pD6TyUYILHuP86sRbi&{mSCA`)HiCIKh}QCk~{RTXJ1%ymigC09cavNgl`wW5?9gJ1$Z#M4mSImXN&MX37y8Ki**5rJ+InJd(ewR4U-s|x#8PMP#EbAK`bVpa&#rZ72k=`!bM_Q{ zT3VD}kJD;|Iy+TgT)I``)o7CFZ z@f~8>%MJPW%0G;qwMb5FS_ixZ&R#m4elT9dUyq&p?7`07n8ZFuYz9By?X{)Vp-rZ3 zYz$}9pI|>r%YJ-U{B3#@`W|o>?P&ft;te3}zoK;&n~VF4k zF^<&JoU@nkf&Bb#!~fQppS`rin08=q!9EM0fqOyDM@N3sO8WQB@7VH zyLL@HQ_gfTKXqfJc2&C+@8a19S&R1c`8a2%(m0bpRo*N1 z|HJHs+BV{^sVIJ~)t8Qw$Une*($p|sABF%E}3Z@a@FIZs11+MfJB z^pU(BwaK&MdYrZHBIgqJmd21d+yv^;meY1&Z(#l&)3!Hz65PYSU;GXBx^kMapQKfk ziucQmbqPKD<#RcCCLGIti?)irzPu0l>)|}BuchaG^=7%ZvY${(8`HD*Tg%xU_80rW z-pPCBRrEfzwEGGk6ML*e8}?M~u9P!}o##TiYjMUu=AX!42-D@}-8Sd&SN3Gu5O((E zYy3P@KH*Pew_?|!FJKSD)%AB5&hvE~WS`vtuT~bntJPcV9`n_l_6R%YHsgF8?+)A2 z2hr!zX0Y>2-4|z1x5l~7wY2P^JdbX4KXcvBO=ADJ|7r9#`hOoUqvyP5uY4)*C)!2) z5$pryA4zXTTZQ+9yV$9TtJ&eyk+HB9{49TS@j2|ArK|OEDb9XcL*IzK5@!xFUv=^B z{O|PhqS$J7_TfT$OZGBci@zhzT&A9P<7X@{vZt{h61&Hq%RX2{JNCc(x?9eMuAjA? zBJU~jXI&@r)QNo^9Kk*YvM9wjID zoO+$L-VARc-XCrfI|+swTQ~mJkoUQLXzR-Bh%lXQA>6yoa>HYX+ zdaj*&e+6&l8hN(o{xc8NGwhkPJR_fCKS0a<_uvmJewYbZP*iNOWEt=`*D9*&Ypljfv)A^xz!%u&OZv$ zU;T>Wo$zhB(Z9smyBn~_ip?k&-%~unKaRhi*!MWk)a;o>;?1BlOxV4sskOykhJ)Qh zO*ucfr__yOaCLc)v9o`wz^C&1!Ab1&)fno*Sta+ceO)!6p7;Q5UV?vVyYTOY@p878 z`x1K#Yn?N@8>F9+FoON5{8QL(m5Mn^|9i1_HI_W1#;|j4vW_+2UT6ua$@zI=>gGcJ z8+bWPm79ILl3haF5O$*slC^Xw`1M7iHWLt37D+u*OX`9{1pJ8P14t7hIhvim|O`EB8J zcKxzFqFv@NpDj0{<$g})FNI$04e?LbGP#+@-mn{_E}aM$Lp$i9eVzmJ<{VTZ}WNgYkHr^|>8ih&$o8 z<*!rndDQbJ&&Qm#o3!u4K7zgsdO@yNpT23?yPsI@{Ra95{A1W1AL=%bBeKK?~E01Q!Z-eOxI4o`EDocni_My_$-`z z&AoM#llyx|d=&o@`opjbeK&ZG{kpzWa}ShrB%CfkHMRz{=RXcR(i-8{TD^dgwNz%)uq^D@AI>kcS80`O|gr$ zsliU|ZoyyW>>R4UFZrK~rEVMw*Rk`t?KgeRz=LVGmy3I9D7QZUCcMAcdGuZQ4cXb} z*(Xi-mEzggnWql41~_LuwPrjH>d=?NKVsJyPu{P4&vKPZ-6{b7fS?K4DkKQ~B#v6wkSz^m!vYV;x7oRD1xxyZ#=+sYQQ^%@ix)Cm`=S zzu-j`_ALEmIj6JV(pN|Ro&28i^20^L~_fh%?y>^fQdTnl_q0g55=%x%gnQ1@tZ1IZuuG**72amq8Eq zAV_^ayF`<_5&PH5;(M0ca5Z}B(onICbtJnj9!<~t*D`+g)8%sSmXmuMB0fMY-_>Nz zvTrx0-y&~Ad7r|5{24fBtt6gv)4Ghabli>EHQ$k$;c1DSLu(WX?LU zr{SFcH;wZFZF{qS*Y0QS*5ThKz7y<7U#@*~+(Im$^Dbn!gw&bOwfTg05ze|#En7qO z19)S8&d|~PI&y2nNV$36T0zSiz2o}r@E@*uE4>E&X}FBNq5NyaawZ;vvFz=|w#Vzj z8?@|;MX(QjtCDlXe$6$aO@@Auv(ZxSboL+e7Kx9fZOYGmB*>%)#CAHsar$sj+=BzEeFDLKMXKHsUziP=l4?EAmPuNZImGo<9 zTkx}A8qo5+PWD~qVYS$va&DtdpyhrB(X#hbGwz_BgZsdXa28ZA7klPR{EK!AGUK8aQ>}FtH0^3S0z#(({~XjhE;z zpK*_ppY^`JRLoiW&VJ2#Z3Y?R{qnQlo|E5(|EV_p`AzX8xSpPU@Dwd)=uY-JCAAQ0 zv1{RL;au2Je)iTQ@|Mcap3e7#ZE-v3S}x`!&%#{yExgqGUGAj?dluwe?uw^C-nmm_ zhSHK#1?SyycFFv~bNapkXI*}w=N&G;*U$v_DHq>KJx9NzqNouc(lhohv^{AVPhIxy ztBdDFeRl5YImn)Gg>%k+t|-1oZc{3rA)C>2KN;5(+NNfG0 zV*lnHY#r|+p`3jnpT|F~C~C$C zu?N`o=)++VG?hDtb`2gRKA7DY|FODwrfeZ6XKf~KinG@~k@Jmy*JXc+cha^tyEg7D zXB@kk=T0^JgxrH^=i;1^LvZTN`|L4rDF5Zk;+i>+Ga%=*F)e2-wfsK*M(l0jX>BKz zi*tG%eSLXZ|Fpf;{LbTVfDfUihSihvD)bi5y0;Tg?L9g%7|G8b>Cc`{&zx0r&0Zzv z2G4c9f$Y!m5P8{~na9JS8^5ER?7ie1idR9-OU_THvh%>scn52L4$gdZqU{S$$jx5M zyyu*~txZ)q53;Ax8o_=1P2eGUJ44P>avqY~O+0(4F2B1r)!{()aQrT09L@PTpWDR7 z8`FQy9>PKD`foVU9xAL>+NtpC*)@)JJXMXR&w@) z-Po^do7y~$zqC};suAq$?~CdGve&}B{D=72JF8)(K5wSK=YCJpcWS_B$XOU5cMkg! z`7>Y{`!t;SeHG?tvlXsIJET-RGc&fG*w4`W>N|TVbI^-^I&252V;jra0@}eW+THrv zh+dz)1MOjLy5NKOhrlrQ`S3e^pu9WSC74`RNBOC*c|I?IYx$YaJVP_~?2AWeS*NY} z9q{bR;`8hRya(-9IXB438NV3ko(^QMg4g()LvwcaZd16RvUnyu$nF7k`5WLGu(oU; z_?~zl_5kBbo&8k*%WX%rO)l;q{dvQ z&x82O_&NJIi*K@Drfn}i9cPYnKeOO$@!bDy&;oXZjOQk3$i82H7eMNI&P242ppX5x zQK=YL)_zy95s-J4M)a(GJ^Hthv$7vOdopvG8a+gA)-^R?j#%b)a~LK+YdW2NH+!LL zXKtE_zD)cGyeaM=zqj~XuF)CykTXFnHS$#P?4{S) z55m;=Vn11@GXEKE?-U;iThlYPm-X`rK7#fq`vXY*o4_9|Cv%*de&=d^;$8WfgZX${ zTF%T{`fshx_NC%IBqTUq>G@F(oA*-yxsiZ6y+%SC-##J&S2Ytz0$Es%Su*aX_f?2Fj% ziQUnqIA<9{zN@M&p0OP%b~`?R_D6+li08SPJ-o*Kz2#oU(!Y`O2E8w2E-Gm)aGvE4 zv)7^LohfxE&)1yOYhibGL!9&7r)(|cROe?+vPZY3U);qUx=!lg6|_<86XhiD0r5BB zak0bsE5vi{XZT;Ud&BAcHZVZ^0d1S%JOi_?pXj?GJMUIaVR&V69{R}1??t@Dzkr?R zYDxT7?RU`bPWBD>H1SztxqjyK1i5)O&BH7BIU{)%oey8~Q{Ube%kwk)BI8X>`arA~ zdn#PdKM}Gv*Sp@6>>tFlpPykr$L;`oiFYa$?@$}Cb5E&X=i*`beLPouAiK7HPSWO0 z?+iJ6wduWZeh!wsawfYjEoX8PKc9*BF*n(NyV8HeIg?-E26&vD-QgO@o*5mKiti^n z;`R9dTNC+zRjMmEdTRYBI7+-ee@k{#oclVSwx029&))>n|FQf-+;^TguR>~j`o5R_ zi}7vFU&g<=vUsLkhqHeN)9w))A?HN)D*E-XV?}WvuZh*+&lWp_eHY$b>}>dy{}bMr zmiupn(_cH-g`c{Uv%Ukq3bNl;K(5yXzaiEhXYS`3%Z}xu|E~PW@T>SLyr;4CWxs$^ zXHSK!bv3aa=n9MJzS1Ej?#Hb3KCHgPr}H`dNVwhO4wYvP)6F@);*%pGE60 z)&X)pbC!CtC(v8s1vvHOH1=YAKje9r-&Nf}eu@4le;=It&AdF!zD8^bt_u6XP@Q^lJ;e+&d3w;q>Dkt-Hrv9&l!^CIMYSPY-e--qgWltYX{{n88H%NSp z*h>2^XMHXIeL16PnYaIcj^(-e9^_o#OwT(^Js8AZsh^hR;(PKL{KvKZ9mdJ+$6pS4 z$KD^R)5q|Sf_(P8L(XOFNjT$NsIT_&n(bHx$dhSpbJZ9sWSP0c^*f z1^eiyKb*wA4xV7ogpsA<^T|v2wu<6?{|ub-mhooqEQ6`ykMVOpZ@_u(WG!=+4>P_; zXy=K|f+J{Kiho7xEGPMS7Nx#r{P~`CH2ZP(B>Eujvrn4yx8`?+?A69<@ZyT%d!X#| z``KgUjfWY~4^Ef66V5pPglkIfnbscf%Ac#vZ8&q0zmIf2UZ0kIGhANw*X#HS$egE+ zKZegRrZMa{*;~t5fv^x6)7CV-`2G(`28RyyTto?6dPxG(DE%-g*D|n9fGVMJW z#&3h4p)G-f_|?nyDxL{%i`9qrV&nO5;)`(dPN5ILnaexa3->6#kNb|@v0R+1WyW-h z_+k9b;6Slifjyn;{D=qPz9o5hHuPuj057vMkFVihD(o}u+Og-cQ=9XQIT@#hzUs5$ zvFw}0*Ts9|61*)Z_b`Z-dbt(o8kuzjh>Ussz6 z?7y^GLeII+^ZiepxyU@8Y#h7bD`;oao)llrP7T`|r)ED-ZwdRb52^I5mA@r_Cw6t( z+4{)d`%{~H@ig(1aDBWreu~~j-avNt!9;%c(_nZ{PM$BF#6K~2=dm}E^Ce`S2kFS)2CkDeOn|H+PNwgtP89(`Ml|@Vhp7S6O0j)WvtfZSrQ9in@FtJ_521 zts%8@VY#TcBlWWxe;a&zVqJ>&+OFa`*R}PN_tdjtV}5mdQ@lGgfqa(DUf4>z%tKA~ zA<&JTb(*H#yRZTK6`XpVv46tO+Kk0t;Jauy!btX7+Di7(?2I?>2e;zvfsB0)e*p~V zUyi?pCD)iN=O(zAwz*j9d`JE;s9-msXOCX$-agQ# zPDSyvp#JPL=-G!oA#-qn`~EeFDxLEOU)x z<$Z}WPd%gYAA;yEcH=AY!B%x^1pCcHzR zhkL^yIJ#8S@NL=I6V2dDIgMR=3VSnHkNpO%pS}*^UvEsCvp*_3=Ut1>pxM9qJ8Bbf zer}d~K2e)f}>%pOj!jdR`nPDLkn`c7>~-Ka&+7<+3o+qyi1 zUlY3k=h?Xj+$r7;Uk8UmL&)c#?aRJLfX4E79O_8V4=eYanyH z3Dkt2`TvS-h+CD4?S&?Pe_eQ-V1OOJ8ODj@?G<3ydHlA-kHA?QUmtIo#al3na0-;uk^faj2o1S z_ovi_h5SJ{?^W3gRmJwEy=N>tu(M9N=WX#V;+u=@&i_!KC(sV&H^wz^*8VQ~iFlx# z?1SUkFThM~b5B{n;dmN9H8X3zGyg{XDWpEkj0Rt`Q`2^3S1Y-;zO(OkhyU#h`fd8y z2U#E-x=XX8&YB!nw5$zPnzjM79 zZ%TVlALH4nFIkW8VGJ$n|DAq@(o-w8=ReE8n*F~1j)r`<@Q>UsVwvMS`@e(1a66nU zccxgz-IU#?qNpkB^0S_KPG73ezU=An0Hn^;hML-C&#r~k@5%CJ&|Ze^u&r{ZhnPNQ$gPo2uW z<(!_y&OSQAc=q7057)T%wX}n^OC9=DZl14Wa5aAVevzL&JsCD@0G7hZxF!%Y=F+rSdq zF#Kh?s599sUuc_aJ}#Czu}J(U>$)BL9>}_P#F?+G+XV4t>~++Mjrg;TZGm`xPwP$m zj`(tXx%+CyzFW?V{M>U>I1vWZ^SAEu_c@b)pFT2|+0WC(M`}|GKa209od7v6{b@(T zk^DSovM;V+w}*@&^VSWT$bAb3Q&i)t9)aFjy0G5_~ z|1CG~6!|>5idI8>B2G=s932OJ#HZmAC2PV?oq7oG0-I^KEq`nA=y^_dfo@{I@Q2_w z{8!jpv1hZhAMV00R21)Y+u@8o&+pXav&_>d@#ksF_|M{@{Kp}6IM3>x*=y+Corzp` zD6JD7B)$jxY5Xq#^|G3d$HNfzNBDW1Ihzc9*=NAz?DV||Zw&KkJ#qRy9d>0epgqNY z0-Er%=W^}E<~q;&CFbH8+Ox1Rq^>O%KTGU>d;t7pUYCibUM+!)V^7HX>?pq$EoZU| zyN~`x^YbiB?br-_FgZuQGxf}_j`8hA^LFPMW=|;7K@wwiN!7SIhlpznAFCA@iNTvwWi1 zkC1+cl!|ezk{7)%91pF%^=VESMjU(R>;_H;RgfRnWOrZ z#docB^zi||nOH-8{SI5PcczVnZQx$Hxwql45Pp-B8aPYa?75t~pUv0T?AzJdugA&D zn9gPA-7J5XC1*A3_7A-c{R^BqpCC3^pKI|Y^wg~RP_9&0w4E+*TYfEm`pEl0J-CJ5 z5%S)e{%Wvurfw8#gy+B|kavj8-ElAo+Cu8fD9CfT7rf8z=2{2i^&oROq0)Wg>i9?F z`+~iW*zflnH{OAMB>oit03EFDaC{70O@AFfSW)cljAO5|`M{}bli((3%)eUB{cF+_Ff4m(Xw|>p|#hhCwm)O6R1f$ zA6KQXU}sLwfxqNr{$6Hx!u^da^Z$sPPjFNHzEDx@nS5p&B`vZGizH|Pw?zzs-;&?#HsA68Wasl|_VDAC#au0vyO($y{5#H`>n+{_ z_MzqPEq_jXT`b?5jAVBZ>n)b&Y2KTzsnDl)>FZbfOa#|p}PoQ&yN zyb=HL3Twb#%1%v6Uz_0h^cQi)e!hF{DE_x7Vd0vn_ef`6~ir!YvK-w(VxsZP@za<{dZzV79j@Q6-aBmWU&0xX_ntiOCX2OaABa;gGR{0R@~oIBKA&9= zp4VsgYid(fd|0XYe0B%B3VVNiC_YR-BiSqD9}K_fV^{o{*t7H!0I>Ef>Ik5>$rsr&)57~d0@N3eKsVsix za|}Cs@ocz`y(PY}!g<1ZSNRfl(Kh$^B>g&lZ^drHz8wx__ZDx(K7^fml6jgA7t%V5 z?ascL{Tl3RezH%G#Yf=PmBllDeRdai)^$8y%Woimvb>J$ZRu~~9pPT~F=Cst|B;*P z=RN92oS%{HP0O<)&yp!}vtC!@e7@S8{V$B+SBKPx-0KbO)T(8xi+!;rJz4#Np7ayVRB6jt1(LU?75cYz6 zPnVzV{qGF(Gp3yJ6|~I#D0X}3%l=$H&Gpxc{Q``I{b3vo73)?i-r@dWuMgAU1vrU* zH-1ptC-7&PvX9 z18cr7{TKF1+Bsrrlk=6n^M1Pxt_DYoza%$%^9FGG{n@W;A(}=YdPt6KI{cgL6x%g)uyU8*LPvlc7UVdcUtVsb@pRw=+o}! zBlfQJ{A~SBIeC8NUJitg<}+)Xx|3(`iTDF}U0&Yf2C$DS7q#p${gv2P&=%uUaaVbD z;TraX^6Rnx!FkR#pw-3Mclm!AP2m5?&Uoe<*S2^QT7TH5qUbMUdzzivIvjFOS+8~J z=Rjv#7kGx9{ZJFW@XnW?xo*bJ`(@7W6z%eUK9)9vo%L!@Z%z9eexvQrzsFc}zA}!i zV^3P<@-#e8Ugo_kTq-B;>esS&!P~=$@VfiBv7&fSXpfs{S4~dN^=R?TRnFE;W#?7A zDZ5$M;{7B2JW6|C-($o-=I1>ld+$kkXX7pS7t6^x?La#PGR_|$^P73PMb2BWD{UQG zo)t6UQLz`{Whn7$<1Ju&``~o`ByBR6CqqkZ^WE}+>@&q`@^c^a@G6|zdy?2^tDQe~ z#&c9<@qU>7+tKgFufxy&m?*yoJ_a8mwiC{|xI_Q59vLl_A)AQm~+4KQ6F)9DY=zU1e#NuDwHYI6d=VX2s(ez=mJ_x9!Z zX|a~Hdi=5actZQE^-E$s#pcjbzaMAkte*jytGp{sggjH<#Hdwe$)6f%018X651?sWIM{KMqgFFPVd9Y$Iscg4yVp#FWwfLmUBNJAHu%`ZzwC97{U|FA%Sb^By@D>chkQoVB~P*`1wQlRh$@{Jh~0_x3L>b#gtN@oy!bwRw}D z&ndfC7z6)Uep79ZW}gaI@h^sZOU2q`9kRze@R!TGsiLS;&2ZjFN6}L!uj6M<2U@2u z`Ca+@$?s5EymwcljVKrIUwH*aOF*{h%9 zyuWqB_f|MZ+SlaQkdr<75A6v48g}Ytehzpee*rslvN;~_8lP#m6rXC2T8Y0vUrKwL zeJC8DpD$pMHd*u3-v9p&qfD}-#}{Wws==OKz<)~-Up`gpT{|i zuUA+f{2guyPqVWR=Id`Me>HoO-28i>(d^uR&eKGk^|>9kt}JRt_P_&T8Bab}B(D$L zN`DcbCHG|Z0CxJBC~pkTdCg~%Ze5Erl(m>3XEi<(O5*Q}H-&uHGsIZ75}yrci|5_# z4ml0j-Dw}Q*M$$|Z^7@&u7TT#SKxU#_cBO)H}>J|xA5`!NV$1VH3^0HW5-Aa?kOX(BFoq+|yk5G>)Fn zWx3Xl_&xjt9#8*8Ufb1bNU6B*UHIMLCb4gJE#7S(lAHCfN4rSd5$yb%rtR2|(6V0n zH~Ss%6qo_S#Hz^K8t;q`a*bQq*)z-WhSubM_B^?npV5`Y?|5evIA^huw%1j9&&7Rd*&pMy%h(SyC;RfZWVeHZpcy

3v`PGWxI6y2qFBdj;yH)!!9MIf-=AbJkeBy|+|z1&Bkqi!!`a8f z*?HECgyU&9XxD??LhM5RSjaq|OS_9*Nlz^q#U4U`8Q%hR-AB&GN$z(MZX%}!-W7hY zkgwl4w25#De+q67YqcE;Z*G^;=B*6*{FV2d?Acc0 zpW{FHml?xF{Cr0KYmGX_zC~_oX!b*Gd3nDWgqN1J`*p% zb8*(Cm2qU*Ecc|+;Hif>B00Y68Z#oiI}^V*^R>ACL2`}8fR z9lL`53A;7CqR-#(4BFH1UWNB)sLyWE)%nKRFZujCmwqG6tt{^4PWmW%opQ04X5wRM zsUyeXJX`X-Pc7UH)@pluxj2VQa2Ym&no!R?{)-38$^ILLhw*dXGNx`NdmU#l48X(1 zwqSR|SBQN}3;M7Z)33x2;XD_&j1BF}h4vwRL%9ph$sF2^FpGAgoTX~e`|KX<#q=}e z<@)EqxBAMwZ$tk8XOGWk_xO*;<5T#H#I`Sa9`O4^J=$Tkqj73Yb9}jYXR+Bhp9$4kkt zD4rj4*!h24Z|Q!YWd8`cpG)M|XJ_BuhHGeB2U4g0f^hcWV`6(k=6DFsyeF?K-Wjh8 zRoSP@KNw#w=TzEC{t!IWcEWs)P}R@ z{b45kaNOuWKkpIkpjWwgryH9X`wRW%oTlG*;TUL0zY(&RC*u5l(Gp$`WA%}_$hq18 z=Urwu@x`U$yQQ9Zx|~j8sSyv@^Sja(K}$H2)(kGvF6Z$toEo}Rn>+D^a`wk(@?XG1 zA^=`Yjnhnv`kXq&w;24^2_D=%x)9^U0w;s3DOyEFeZe&*#dW69opa}Uo$oPBpe zxmcroCpTN(LfYMOU!vz8j%J^SFBO}`UQeGN!aD3-{?lvV<6#}RLoC;y#a^zjY4|Oi z^N_lcdw!U|1kb~{?l|`U_9H$C@;uoC=kv+ZF2y;RE%!QlSsyu*mB4j%$Zz(TgbbUoik7gS@V%{cVyq?nwjg4v?t`|9#Y?)rtij1 zeQs+mv!@rz+kjuQYw@}0RrYVRwql>L&np!_mpp)#;Y!03T1wnoW|EXUc8#I_!+U8ND9l zGgTLSFfC`To4i%*3379e7UARQlkgOLXl(i%SVL=qbKUIseM-d{+7j=?Z-a-!*vewx zbuhoT(Z|qo|6|mYf0n2O#U6n$xIM)SroX2mW31 za_%Ow*SCL9V%O8I2F{so3GH1Yb23Ek`eLawnY-8c>#}ohzQr3GNA5Li{7^LUJjb@D z9YD)I7*G39-iGwlz0|8k@F4#c*hKt-fc6Wlq^*Sw*~yuy&Hehz^&82n59bGB9U>z#xz8qc`{~FGM*X3_c<@ zj5hbv&gADTJ}Cc9+7R4Jo1Cqj`y<&qi|@m)gR}mrAG^Y@;zvM3sK$?-dD)4bnm7}( zhhO05%*?7R*5Zfzi}#JxqU5YEK2W>{`$e}rChe2b0p2Iha^?heF)*O~ZYoOg@{a`Us}f&3Bl0niAhRTlgDLp(v=Pu6&S zIi1*>imzlJ%FdoYiG7f~ta~rW7}8gsx2sCU^Z7R1uS@a0X`V5c^AE;jwc7&k&d+{) zMb5{x#q4K|E93vmd}cr1D>hti3tHCnXLdt5wd7}C<(|Hjvy|PAo_g{lq>i2d{pmyS z)n(6Yc53o`e)i1=?6$6xeSJQyf}c5_3(ttBPVYiL7=MM|f)1{cpEu^a=CkOjr>&sA z{LITWax(s(0N0eGop3o;5!gM#B2=g1Wk6 zxp-Dg!r6OU(7PJX6z#^*QZEZi7TcfR*Vs3d zUx$CTSZc`ga?jB2Joes@8vD39T4LW!zgu3;V|(rQ#yi1OT330uu`Ahci8Ww9St>pk zKf%wqcjW(rv%jk0H(YBxJ8Sm5oHG-Hqxp;Y$zLI7g}#rVr$$@^-C%REtmpUaXQ6s! z@otm8@^j*kX?agM9eR|#W4hPP#O|;c>(T~S7PVn4E$@_X^0Vi*gCX+A;;Uey_`8)w ztt@NXTr6u-4L^mGJEN<)*T*XPi{;oT~SJ^*s>kK$IeOXZvg=YIgr#r}ot#Y4nqi{;)@Bl3Ca16ad_vGsds(Ej|bDySjLf&d(0_mH&CAG2+p3&FPJIqW6{iB4j*we*R8*0Ude*DC*6(7dlnN|fF!B@0;w4D9tjpuWD z$FL8<-xx=8*V&x?gS_ov5PP8bQ}CVptSaXjInU6S!U67kE?z8tN~!o>^#=Yva?au3 zSXsO`WREw~_B8%k>=$sJZA0Z=R4(dK589{N1#M{W;88fwqLi}C%Ig{ycHoGIf zUe5o&-{p61l9%(7{dR)*%lz~=L;iBeS?DKk5Bw^>lblI%a!(sW6PP4cPanPLhw}4p zv)jmhKg3SAu{2SOa*{Q?(LmhZdzwgnqZ%@aM)3V+# z>{hJVqwLXgKE&0<@*GGl&AoJB_or1D<4OEV{@ajed(LfsS79A|i2M`f{K}syp83jN z84q`hWu4!l=lL-VwwBvo>^qj3ctLDqiKFj{whCUGDpC4OJ=H=Up;`^h< z>?#30>o`x|S7JxsN20wXmNDjct5TD0ht$!z^hY4iv0M4OYu62TDHr?jSiGV5O8haN zP@#YJWc-P9b%^}j=STKl-BL04CyQ^#-je<>WE@$q?DwbHIg9)7r^(q3c9ioL#Fh&Y|?9VORd<^i$bw@f7~v_*;H^$Qs?H z&(X9o?5s<7Nc#`V`W35&^9)_AR(HfZ!4tH8VwbS9KW51P#kjH$GFSQTbP>Bhd?II& zoP+6U(-7zLO#^%weoM~R_yK)Vsc53EWeydVZQ!=gwV)OYgv9tEo z;a&E2U5f9ZHsEJX2jKb@#aZ~7pW4@rb}8P9|A^Qc+?YQI*M-!iwfu>$`Ka8SkJPC4 zcnaLez8N+wYlGLp*;`vxcxM)`YYbWEue4vl-=CfLrT65t!=K4pLQg$8#<{&lydIoQ zZwI5rrow|T-Wp#?+l0MR+nk%!^3;rst-tvm%%4~)K4Z+H4X!9YgJ&Q7v%09s6X`P{ zb6g*${MXk+_WklwH)5ZFheLIJTm?U|S8DgL*f4mN{eV6Xqj%-!o>G%`z?sWcFp8ad z`oh>p@Lyz4pk0P@PuU0g9ye!oV}70)-?3-myJ2tbvM;AX=Th-A&^G2NzoWO3zYjh0 zHANqp+kE#j6hBkpJVDlVzCD|HU044(li5>O!g@i;`=h+t_;~08dvqzjlf9%|)Q4W; z6WAS~zj#xaq@O0Z62I-5*R#K2PsXW*4Pi9oJRe`Sme2@BiVuX#;XUn|(=v~T;&rUw zoAUSgug!eigWd)I0NGokD%}e^b)>2MI<#f_DV3a27)Z}v{2DU8uJDEUZTyyE&$4H; zx1nE#Q(KzR^W9?dQomDgQzv#{PhhWQXAj&C_i8t!vZxUU@UMmo#7EHk;J^KG?xlAXP{KL2Al0dh8TfAjF;71kSa z)~djV@O`DZf$X9A^3KGU!p*d)_#fKExGl71cY%-Dz3q_&_)~ekX;-r!t0>04f<1zE zyI5zK%+9*(BmT0zxSRXv>Hbn19)hfU_Wu6bWxUJScf$IW#-ZI3T20uPeGXh;FSiiy zB%X7&NPi9S4RYRMXTP2!mi2v%owkGVk*?Vpny~9a1=NC3+NK6wh{tM|`Robb>nrn~ z?-usvZz1PfIm7U_u$=vn{$9pMb~UH|yIcNRv0U?nvUA06sD18hTXxRKICu!Az#gUI z`}(sxV9vvxlHY1oawCuBdYhqFEz^KG!7{>Ra?M(341lf?dlvGna|z2IMd zb@Mhx{584lXzTE6;EvFUHd3rT&N&|f**B+Y*Q%s1cJ}sm_+H2!p053twEJNHQW3je ze(vu{obO6mGc-a z^`ZfN3_IU7oXkFlU7Mcydy@SG97}J{KNHqvXD|1F{Ekn3xp%>2m??J(?ktvbnt2$_ z{|JV#Kc`*HUMxPVqS*Hn_#GjtcOI9*m;CkUsWS&?oAHd&<{bJ&d>Z{|S}p!) zobRC5;FhsrtUkVQy;k~oQT{>YBCjsax!On0mvXw|OKE@8YOr(v)nHRts(t37HGK*{ z&#>IjqmVtd4S!emo{8hGumsNFKU^-}bF!Cfv!~J0Z#Q=4J7;t&_Hamluh6c=SK#wq z_XB#)$PE6vrQ&@h&(D7BG4$5#o$zPwA@k6XoqJd&|3Ey9pZV?~wmn=$n@d|HJ^|kh zU(rvHb1$tM`)s_Uyo@1pzcXZiw-FlzKk_$%RWqg)+KJ$Ev^S$XnoEnkmPHmh$f2O>e^gGzE zvY)4)&R)dc2`=GR7vCM{-`r&0s?u+v&B5uv7yA}G9*?A*2HW!6@t5Q5m-S1<`5%ls z@TcI^snmzmf=0&oE_-9BgzqYfXW?S@?U@(x@8zeKWla`nGZHFjo54CTL++PQNA3$^ z2eCKSb~E-<>O>Xx5^bM0Uz^i*(WWWh74~4)!@b2*mv@qz^Op5^5I&>tApVCvFp`~T zQ$6u%?1|YI>=WfRruT;1=~?fow7cPOm`vY~z8abvLrX|)9Vf3pZ4MqMFZbMSyNs3Y>Yr1afw7aE&wM9K=2m8t5MC}S80`NihYyk z_dojkQ+$-XnYae+Js85?4f1(6b2`1Uct3lTKN5B>Idins$*kEg*0zN6Ue*#HvAQ^u zxkhThCiJXz_RhVo`3^1Ty$a4}yE%9^ETQGx<=tdQ#uRv$9Ln@28NG(4W2GU2-yL%Vv$3EHh^0%#?(*9bQCnxuH zKF(Rq@7LWWzb!xG4z{M>NV}ZfkbM9ig=fpjz3u|_%i`Kr;E%<+&~g@ry54Ykwdp(3 z-!zWy^xQ{2r`5svw+*S)E#(~qsfV}mcQVcsaQ5&$v_GL4e+hpO-i?1V&U--4N9MB` zu8vp1=+(vh%m{Ys)6cH+toX(J#&|mJhR5CS876)ldoO+ESv4M4$D5bEQ$Q2Q{(ec` zEBg5gZziW5ZJ1b9JdwYE{W;D&q@Rsp1q_p)`TiPr72gHYcL`_BbFItq`;d8V>l%Nt ztIB!W&yG@izvXX=^GwM*!cFYfP?x?LE#pa_sT2Fa>Fj>;ZeZWaJ`-N8D8AGB?*C}I z3vjEd?QP%!g0vu*7?h-efPuo{4(A0#Jd zHfL=fZ807P+4FgB-z;~3_AuB^UVh&swQmalcYQW072ijECH67>bN<(OC)`NRscAb{B5O?p|l{ABD| z_lxA*LHnKG2H~tY*dY zmR}F&yZ@Zg4*Z<8{Ju?N{%T17onR*Y7wfVoduw=vKSq3P$(Z5a>`V3iCc7Da$@k{9 z^>dKiT4G&s>eF!T7UG-uU%-6+B%J%dqlcgM;1?k0B(-gj*gf*6m5O)iCj5nB%W0>V zi@AOpuYuFW?!YiB~};Cp&yECi){?^#WU{ZkhTBc`JtT; zoAXm6vNs!wPtWFX6ZiDw1TL*W-jnJT6)RJ4+ z2SVl{HK!l_UH&@u?Q5Jt_VKjVIBSx6md_d+vfIk3D|Rw_Pxjej`S;S*@J#wjT3fLP z^>ZA1t#*xZH}Qe=kNC;ilD(Du=jb=Ef5X{pCqM((LC#(D56jk!e>MJ6pLxDqFLsRB z^ScX@-^d3Ve6YXJLk z@h<${`p^A!QRgPPhlj*hh&_&b(SKlflJ~oJn{XHULU}9c)7(?`)NuN({5$lO{hNE5 z%if&cT5itUXvq0#Mf+85J%09ETlP3y*Uy9xV^4vL<(!2#!QJ)q7<-wVyi2_VTe2V3 z?`G`1-y**Q-+li6z_?F_thcy^}qYpK6x$p*T^f0<@vIPy*>RJ{sg=a?K=DnJ^0o2 z#)_quO%TgD=mw3&=JH$N{fss1crQFbJ4C!IT*Iy>-hkcGUP^8H2klZ?YE0gv2EYUG z5UqqSwq|*MdrX^y;eN<^4H3^=cVqu0?*Mw%?J|6_*k(|T{~gZ$%=MSDGl%=}r&blu z`#H3GX`S(G{J#53-a_$5AbT+9c40~FbzgUjXD>bhN707CF|bQTO~U=PxeaGsrpvEQ z|NrlBIRiKFFT)$)#@6Ztx$WU$SV+$^vMxJgxDlVg|A75@l|K2o&iSyGmipAt9OMk8 zCS;r&vU9&R*xSJOaH+9;E58g+!I4l?&aPt3@DJkeRjC&OZ#Z&9{ z5qlQ0pW4dLbEqDB4EzYgAm?HW`7_vE*jdMhJ&R{h)@vGnIAo4ql{blf5PL_M%kB(! zy0?eLUWVSVIW7C~GdY>N)A^}&bJ*MB=~cyCeS=%^XVG(C@4;%?0DK88H8ErT31?kW zd&c6Nr^W19IO7=0UI)j@%{=UmkL9P<4ibL_|JS-r7u%Shac3Vq3Evv~Gqf+{?2O-1 zV~%65r`I{BOv|;7lQW4v zh<&6!7x8PuKD6rY`7n9e*Ehm8aGKa)5#`IWvI z?R)(`qV1>rmmtrAoRRaiIYZu^w4Ur~v~H08`x^V9w5!?IXuqqRIbzrHKgQL?PN7|l z560iq_JvdVugTB)4uIXu#rLf5vRCPIK0ounL&-hS&Vq?>1pR33PQ&-|_Y+TDnn$}# zet*0V-j`lYPJ8n-nw=XtR)%!cfti*UUvV~8y$bu;UIJuTOJ z2Hyvd!}4Md z@!X^5TyFJ<@5HpN!O!QN&1t*J>&R~mZSCjO+q|F8(XO6&&dAra$M9A7W}LCs!Fgu& zh0o=GCN>g=L0@h2+{t}CDVBbEReautoY&N{?~O5IN{v|ut=ZW->7$9fj4{ue8uUl; z=JMOHZ=~ma=1q3$WH0@vE{uXa7tVmi;yE)-wHpFIL7u_+FJm1j*1A%BKlu;-OtD+^ zpELP6q`$}3m;?6HVySmSp&Fb6k3vn@sswVX5w(^V{U*b3*q$BX5?Cj0nOcJA{w_WN=c^LKWQ>Fg2g z0j{-!_z^IlpXX$7hWrt7PKUPi-u!O*%pO}rOTFHY)|TH7=iDEGtMDOWsbx3ld$UsU zz4pFkYX+H%tVa|2Gwh{!CeHcF8OZ$X%0CijYJU~vnO33av(9UHti0{Szv7?6--Vt2 zs`2w(-+ubaUg(cM6Q3xa{hV6726h*_0&>qCaB6T5@zk;_^sxpXDK9^NpD1Sw@k#ug zbLw8MmGhX-!r5P`ZgGIim;L+!oI^hivi}FlUu-Y@!p?nm$BpG&O5Yye1usDE z_Xpa2xMsOnv(Cwfar6`1=lQrJKj&;Oyg*+5-#hbhFR?vnE5))eJ~XbZW8Uerj~a_- z9THoFtK&NIQ@`54R5@qSIdzF63Ff8lGTpDrX5k#y(gt zXEWG|e z{4DPleGR}H@P9I%d)PUrm*Bj^>?XfI|5cbw+n?V~-w(lB_LsOWy$4()C+nL!bvfkh zE!XE}a(eS~MpyGo{Ct=234dYP`v^bZp=~XHbAIZ|4LvE;hHYS)pTy?Y!zH8SUT2)_|M z&;NIv&(y;v@EfgBrFeeU;%D4BW2f|R_RP){9N&6w0wvD6YW`c_SenyE1+$a{_u~uN2Pclx(TnNokwfQuHLhF_nGA$axO;D z)5qa>Q+yU}3;EUL9s+ae_uPyQ~F_)XMvvw72vR`(i*MT!>o6*|hvBsQp`a_tu z7*??d$s3F(i@mPxk;am<`VaO6aJRf%>ut#T^}`q9w8?eehwShDaMtWW__pHtrtQmc zFhAosn0=+Z3!okQLfD$VrMb(Vs_yz@i z;H+29-c|Cl7dK$%bL-abXJl3Je%_OIt9Z^{&hldw?^#fro%e+aiShFu@GFdGPo~X< zX8ecw18{y1CVQuc{^Y3NP6Ts+tMjh;I32!2x^ThgDWCoktDd*Dy8E$|Uyf6`upgV-}@UyEhm zZvz+ef5K~wVJZ7|_7n1^RTXFIR?qglrz~)vS&P(+++QnN&gee0rR-Vs`?a~1J&pdL zoKa%!`8o49(7NKwb8$3E;E^pVeIA8J3FzXWIfuA}XPhe0ED`Wz0o zz%>YkCwvlgU2#S-U0lZP|O_3F4_CXTd+9I?R@nwhywa@KHGZb%68um+|v&&Q9mA zlDi&uhO^;7;~QS~{k6OsAhl;l+EMI@&{G6kueIqp zM^oun)7!up_A7F`(X*bNX<4t#cWT4mV)sC}8%-8t(x5QaBvunVb9N%%NWn`8iO1el@W@pqe&K`8~_( z5&w1j;1ryidjNeFUM**JSuHVFE%}q_o$xpKFM3DZ56UI?!OwX**>%(RFn-?8rpnK~ z9SW(h$K!7Bj`##>J>HvC!IxV>=yVKIhpfX{JdYZ!FAvq z`c*j3sq`_*zI>LJGjbE|2M@!QrDBdYV5bKB<-6(qtyf9j5cA!Yp0((~&%ZI-2|o|x zXyZ!8$^Mv~b;^FP4!^@bv~5d8%@~T?yRS#-zn0Az9*sYuZx6G*Pi4>NS+$3}ob^0A zPmy;w`#?F9@DBVkJ{@OWQ>(HTy-Mns{5Q1CcU#$aIj`&KAHXH-hIjyYnFXUm(!&Yt{&eIwk@z6b9Ong4(DpMtZk z?}_{d?03adL$hW}X?yS|^Q-ZHWTy@fqd#a4CK$sZVjJ==!M(*!$5Zj$V%b~SPua7n zY5%6}r;nVYP2^nxsTuc*eSnXGTqpbYbjZHgQvQxOdd_5?7cI1#%|8I=ezI3zgTqV3 z_gnSpz4&ACw~)O*9G-HYcN#<1Z2-=i=bfU9cJ;+Ge>rOpz*TZGuJiE5^iA2y@#=Xz_Z0u6YIk;e%39|-K@`bIOnU5*w3zW zFTX$gBAohn6uU~^4E|*H=5jxQ4($DCIs3Ojo<&RfC$h)lGvMFaXN8=Ur!i(QJ-K683DJ2jw#-2B_EdvWwp^qKVb?B+OgoOh0l zC+n8E%UQ`=KQ3-e2+iGZ$rMnnxb79AIEPCsWV5=Zc%f4)88l;?}yfyz$@x$>!ax%88N$O_y=V*Fz(#P%A@L%-(=KG^uZ!v#1`$yU^cFxjOkU6R*eg=Fbp1P26 zrcUM>Ps4n8!~L(vi_68k_RJL5LEiM&l+<0Jm{ zFbI}c><4XL79W7Kr&r>PeHeSRoUG9f>@jecSR^Tu-rUMMyVGXDqT*R{KcJrOUIJByvVJy-rI;`trXHF!t3Pdx8o z*-vN6X$OtyXVK4Kr^ejoo@>+cOdmtfdtB!35%!@ll>cm1G48eOto^Q#`^@>uecz6E zmz#S|tw?MR>_@*>|2dbn`4{8<_&fT8a0+`VEo=Qy+5EyxYdH#c6dS?LTJ(kg(tg6J zFUPR6_r@6WPVB79c-WGbpPgjBA7Gycce(fN<8`PzJmuU+H4iG3<863@CP_8Psy&vhT>XU}B+9q;~Y>a#I_ zh0o>7#eZ|He6BhYE~IB2>d@Yi-%LNh;S181zD%2q%+r_RsW}JIH-;-Ab)pU4uu`12 zv*mou-=fO;)AEcup1&S%4|Sk^$@_>gZHa$`Rj{FUhsZm!Qk?(eXw%sFZz3M6zXNE8 zK_7nhLdKrGegVE!+w_;dv-dK`sTp(NN?O*cFFRwsUF<>E&-}Ee-$6?q908llc@rpUS_6-&fwRekZRUt&R5Q;UDO?(eoafIl7#GI(=jQVf41_ec1c3m$A2GXHIhF z+v6_!$-mhfr`?VG?I8cpw-tD~b;vtS#*wjZi&LK;Ggp0Z&h!9!e|~rVp8P}P|G^%D zXW<#*N6=4B9xl`GgV(jmzt7o?_O`s-<7(RPkUf7OKmXokGr2FoP5c4$zu3Fro3!Zw zJF}Dj6niE`Gt&UK~j z#IKH1$G3$#<>L7`S9~}%U+*CdsS{Dc%1$ztvM}qDQ7=x`?A=2S{rui zz+rfuSarF7_n_e$_@m$h?;dSwjc9fGJ>}lZ&V0}1_u$uJe~tUWu`r)MLYwh8XQBbj zq%W1fCkz++n0DnFbAtC4OWk-4?+nM<%iZbwmON{5PnbhL2OfZY50J4=fSn+7dXSv# z=a(RJHWTN0`vsmXrw#nV?n>L5-2)DV?Crc$CUy(XzNlrd{2-QkkTo6z`I+Z6wA}mi z{H*W&<>DDPik9_UNdLmRWS`#1e+;^?XW=IJMe%x&ci(={B=0zAmdDpPW{R}%z?~x<|gy?34bK}b-cP# z?2SqM;1<}Nwl%y7r}K}5Jg+w4=kt7hysBsM9NS2L?aaX-edVm)Aiky8VE52cyDMo` z^0W3sA+_#1oOx=7m+JSFvO0tF4!>HPlAO!6&)B;2vv(emo3%?{+v5kc$@@)1_?i7L zctzfazT3H*y`8+RDqOPP>!&UGQ!E^mVk@A^3aOIA1L9bT_lx&^}|QPA-s}-@(6~Kb}1u=F?|G z2lz(*T@}v)eGQef6@M3c=I&{^*(-0tG=4pL#&C(8+*{UqD1DB$Q|127@6R8>UMS}Q z`~)QTVfy88lH9TU@#1I6e^UHtdSmthcrSdYoH}wc*C&bZOK-^E9j5Vjf;^{op=Y0G zY-6FFy^wp%{pUG2nw{_PPAL`N={%y%Htgr{uk>p2PUSzXmVJTm)}}GujGlX*2?z7D zzBOTQc$b!%x0&|Ks){+wJvJB1c(Q&E@w4xH$$NplC(aqq9;u6WgWP-S_bl28a63{aXB}d&|4q9`sK9 zZT0sbd_SHgCui&fcCK+Q)T$KkczxNovTHz|G41e%xRL9B=sbO-O`aQ``M{OFVlxX z#_xsb&q^EvNg16XNhwOtL(dP;5-{9Z;t;ACE`@*OE{9C_Ka;l4O z?%LPjvmxWZTReStW%sV=TfCv%ru6E@cRKr0dQ&-{vo~VryOP7jQ|F$<@6qnSshhvZ z>q5`_K<+K)=s&n8|5unS_hX!WlxOKYe)h-#cphyr{Ny@+;iDnXr41{^y?5cKzU1Ao z2|I1yWIxoSSfBfyGNVzj2Vc%)8v@<>LE>zvS%4uT$~f!Jdd0i>3awmHVRD44nB$ zEvbi7S3aSC$bXYP5_VyC78``KA6v1vh4ygqn&NEb{Oktb$UoCq2D{!8c3)bvO7Sdz zmVXp`L6!N_F5}I9zK)(XzYk9pua7g%+U!YCN3FRJFV<)7{YSBW{OPa(J7@G$cAl?Q z;u-IExBReXOMD^kQGFanyGcBGABz18H^=qy@AyCRda_rPig8TD*$aQe68a(h z5%Tiv%DLMF|4YBy+NYiA`I%#$zXwCk$?bAl#5&k#oMx5um`bkf}W5Xk$d=oJ=1l!V-IF$PO}DU%EkP265EMi5x)awvQu+2x3lGr z;5Xv8z%}HqH~#F^qr|h1M&dkoPl0RsdH$Y`UxxIT^V6Dk7du=NkJQgg<>I|7_cMv! zkp8^f?6qHMQy^=Sd0lH9+2h%F&p|!<_xu&?Qy^=dHb+3_{SBP?I|nAQw}QM+WRGXR zjFZ#e7`n0Ni=Bfvh7t^hD%!@SVos0dr!HO2Z$}SiR2A3g3pMChFwA)n9-uyTD+w!xwf1@1+8Pg?V zll651&X_N!jqg!>4jCvXH6zc9ockAX{LIBw_ zbANEDxYqM>H)H4BAoa0@@&CadCNIC=J({1j%^7-IU+vk)Ym@ht{_Ka@1K}|CV4S`3 z9eh*eoY5LXKYs2z?;3}&D`FXQ>e?j8y5}rCfNPeFOFwzP|0gYdP2_KeyTEz0)S;=g z4cNcoPw{A6GTv6&^@3~VwW> ze+hpMdjMqZvetXUe(da-S;m@kl+U2^`LVmuJYVFNBnTw(Vo*aO2zM7yoLwTHW15qK9}%M=V#xi@0a<* z;bcgCX)PyvDbJAX?ZL)3g??^ho-vD#jdH0y)zT$txzK5OqcO?vhh1yo|IJwOr@BaB2@13;k ztBN|CdVe4sM%!NO${x-p`=9t$Z5rz%&$AubyR$R*kHgR^Yhr)Yq_uJ=k|bOZNA2Te9<<%sThB9#_$GjUjL@twxo8@F@Na zc~kMn{Ov16{ae6)k$o=YcTq+smpumhK!505D)#A7`bzy@kMkb)13fi1dns$ZL8W+? z9>>r3o;BcPSX}n}GPYbZYjTe|a6R6pRJ4CU{$zgsPIa!ihM)bGvGr%aO?w8{=jU&? zZLZI`LAiLh7)yT~vj2M1uc05n9)^2GgM*+(rMQ>g^lI!SRmD5{aQ2tlr3U4BH(z{f z+K&9A@l-e!ZglOpX$Ol>;Xe&I8^@Q5n)ACpwqrM-=V!~g?^on^?osT+|GMuJ<=jTQ zPG0up#&UBerpw(Crw)89{uJI7vL0>u*>@`;&)TbKooQd{Zx}7lztQwO`~G+4^_#v1 z(K^dX+bh}U(SFN3G(v*qkY zyAz*4ds_dw&SA8B#k1$0g!%j(_#co!HZxu?G&*l zuoFG^nDf*X?tyjUUB&v+-{t2l{%`$gM;XsFenYX7#IiQ;@&965`7rUmMZTZ>Td*I9X%ji$w32--k z9zSdL8UK3ro_H2aqUBk9zjiu?MELs}E|!6onba0RUuJ_&kPiqG{8^mzn5HMbKj&!DONG5ojjPi6ZY*P&0LWgn-; zeM?)X?P7YKIk|`Azxj~!Kzjvd$U9!ndSe>NKULeMkh+|jl3G%mpE=I{&mKsdP2qp% zu2SsThj89syTS2tQ&T!X&hwsfKWDeWbMOJU8r;ghTK@b>aVE#%`>Kk%Hb8uac*95B zJ3G(1K{#i(O~q%UHQrIQ&ENUVv*I`YKJ-rF55wK^=i>IT4(7|vyF|`R_R7wc;`4WE z)2{S?;nC{##&|FBFJX~3GvvK!Zkp2TfHtjOldPM7yi`*TWX0o}YC*8E%K~;4yd_vQP4!asl3xpZzhKzXY|17&loI3L*oCUeBcC^&J^R#VLDt>M-sa(uw-bG$v=d7K_ z{#D<98RtptCY53zPZ4hmlljx-wPH6DKf)Rw$<8xAYm#%6Ii0F~NAb*c_VA1BD_{cq z9*Djbe;V8ZsjcI2w~Bs@ad&oK_USlv=M>tO{9EMJwJ{nemV0 z=NWJjlpxo<3(u+&)7OQ@cTdT?2YWlQoU7OQ-)MWHoMr5>wCtZYkmvPf^2XqM#TK)B z!h2#Xaek+AsMrf)196^Nb5~m{dd7bk{UdqD;>^>x(3ZXk{$O{fch_bjqz;^f4-wlO zw<{IxPU8P=ynl=5z9-OY;lViVuN2RB1s&MgCnIomI8d9pmEt*bG5>hj8SceWVg zX2to12JBq#HCzK{j~rfA)c*(B*RgMOjnl+VhAS%Gi}5JeSSp?x^c8fa55O7Mcy1S(N!*Ve{kJ7Fedk|krPaSIx|A4{t_?fReEA|8AJeRet&`xJ}fXDe8 z!Z5WabI^sIx|Th=7P2SKb=~J+fc3ec2UHcG zCGreCoBvOGC!A;EC_JEK-0a%;E}ZY=PG{$y)8-X+?z?8C*aLZH=NWqu?O1l|`nf%d z_xR4V4QSnIL-0&|UD^2QxsRIoLi%Vt4c{x4^Uz!Se&X-Ijrwr|l(U-qgMQuKix}Yst;r z<~cJ~{Acmh^TxD&;2^nqU#>?x9Orid-_-5}cJ{yw*pWX@UY?yB!jX^~-%o6z*xPb* z7B7OFwNCtubtx>6^Am1F&wV~Cb~n2&`&{@Be*$g+2bYRx>TqN1L7xRzl-<7A9Cpri z>g$WJ8(a&giG6}oPg7gg=_C8_-*_q>U1hF}HRt*^oX-_G8+Y)RzzyuLuz6(I8)x=;FM(oqb-Pb~EiOId9;MVT@RN_9KwJv9s9G>kF7R-v zc#r)zJ!c@-JdxfKe@IKM$@yy5y?7T{oVKp>rPu^^=I9)J24w&B#l4{$dpP7X{-@<) zEL}^*cMjPD>%@LAwwmy@`0ccufzj-I7P%bWgPil(xGo+pcaAk!$R0+UiL?Lqs1(nl zUghEpW>1_W))c=lp6B)z@*lx@w|f(&i8ZD*fIJ)X9(0$!hqF_28bN7drUqfd?EH$+Q3+`hu~3muJZ_#`PZB0 zyf;73PQ6IphGOp;+n(&lXj|}8OJ8RHP%ietzBtdJM%w3RGt_?y_Y*tm=d-h0Ad2iA3-^29# z`1{jq!2R?J{~zr2?2Ywx3_rDJIjt3Y06q{huCDN=Ywd(nTT**FX>$|%SnaRiU&cOD z&i#=3bUi&aWq+}4*`Kg;Uccf0$sYrAY1i`mvs0%^{Bzk~;^+0BbJhg%{Y7g11?>A^ zANVhSW7mq7`f-1y_`KXj+ZODsO)t`LMQJ zyqj+&H)lBKYjmmjj4~BB*EW4*e+?JQ?~C0?--KNszeFFwzV!dlo}U_=`_4Q*$DanL z!iBEAik|1peei_7C(-h^((l&q`?S{L7if0_&Ugp1UlZ@lK9^RK(-~3=9@lmftvz&x ztobB)sdu@Tf68f!FQotET6eN@{u@Ki&JNmV?75fQw0Ryf=ilNfa1(t4{%i84z_I-2 z^z%Tus6BhLPZob)Za4fDo-gM#xy{6mVXvYMf$Za7=||x@@Dy#l_)G5lBYul=@w^>I z%N|J0$unkOu_2J!bp-7TdCl?n{9Nl@d={JtS=TPKXI*=2*|{tg?@({jGY_fF_e0it z3EmU;z#kdINpj!SF6TJUx#V08EzJF4<)XGMrhmz<;2w!%#JdB4fqoIRbE zxy)J1Gwlj~zMD7*$G(&Hg51ny&T&n6SA3N@o2=aoIk$)}V`r{Y4=#s^knfywpUbrU z%4huDaq9HTxTpL+ax$maXrH}1L9A1y_zp6);!%Fq;70K;*xRzF$xHqN?4>22Ghl1> z?X;YW73^(jRq~tuU#u3rJ`54d8vQJHAzZ+o)l(f8%iNCFW*qxtoUx6j{l?Gw<@eT3 zaP8MR$HL?)p0!?8V>;gEjaw$aCvSxi5+Lm7DXEF;^FBj$88Q!VK6K&W7ts#j|0k_-g1cH_zg% zYo4unH|UEW#Ci8mJ|}5{JGq=xF!9xN^u_B^GCCf#lO?HhdS(2%hn7QLEZ)L z!Z|nNtBU%X_0HU!iU;8u;-etre2;deyrremO_ZIWyJq9@o2Z(3Q z&e6URf0dlYcsef8Tl>A1>*eNi*1{gn7_9?8`}7`m>O~7}&*9H!k7Vb*vbNRi@Iz1PkNY9=h>;6}Z_R(bb=5K2pL(g-vuDqV~4szbl zS>exO&&7Gp?}oeS=RS68d3ipc;66s;tbaYRw5hpKxk7RF&d-KQ1%U;Po+{QEh6!xX`?Bj>o+u~ax=lBwQJ*}nvG_9mxIGJ9Zejq#b zKJ7PPPcgnt`2XR*WlUGei{D4=CH6k-zr?P^&0sjxD5;lX+3Ts#{{uVE-7ooP$lqMf zNBnE`e=@r<{boqJcf{|JpYiM}mRj*G?K`orQ8&uKOe^bhyjr^S9Jfrr5mSSJY{m@tkv&U*vi+v>fCV5@q zG+O3wF>GUPbH+ZpzPI%^vxYmc29!#)|F5@5sIZCbCb#shu_0 zW7ye~IV(G{e+tUQ_bPek&b__>&(ojbpA1dqUZPFbC--!$_(Xgse-5N}tdM^byf5!k z{>JPT+GbB}%&+iMQ}$=KXMaRLg#D-elX%Ya651cyFB1O^GREWR*(aSKHL*5cTcL{& z=coQ;ZN|d{e)d)P0RCsLpL5raeFUr$OJ3`$;&bF$vE2K%w7O#1gPXvy?Ch5ltBUU% z#_DG>`zU(vwZ>wMSF?|hn>BllpYPhQWLKjdf!m6o%+B1s4Cg`aqq*20^k(c!*d5qc z!43S*aHnf6q364h+)EYohwS4wXw&ffrQ&&b0RLQeW9SC$`TOGRfw`5U-VMQ*@Q?`5C2$Jfyg6(7hR!mdCYIqQrw?+44F4(whk*8UxB z8?v{7yo+xpmgjlCGtL;Z7Y>(`ce0$ZN7$L04)nYDAJ9|Fucf_-ucPfFC-h*+=>1*`%U_-cpNP?r5UV)?~Nh%b(+{k zkn8M3{~YI?^h4VB*6w@S_Us|_yi=#9%%F{;{f#rnE9led8Ryr=GM9EAdlTAo|HpZM z$Y-=;Ma$Ys2N&M0Lcf=2ncNYIPctreJ*BgK*(^K>IW?w0H3;s&>6nS}{ zZ3SKUIg{D1nd4{RU>GU)+^XVd@-xMMe)7sz#n_AGwh zGhN<={OpM>*>AaK_Dd_;zW4*$w~+bG*yhl;bMG1VLiz$)ld57IxyPFPhw%xJXJ_8w zwvyk8o|>~iKYRZrcIv`bcPhRFe<^P`u8rRo?*kvfz1nVq-zgXG4hPElj$K>3ZP?%Ae~3Ry``H*b z=O0a9!oG@rF8wxrY|q{ms@W?)%9$Z2b$@T#MzrjOt?5DLp*NgEyNI8?oU^bW{|($t z>^|CO`uz{yj(!I`4U_p35D|U^sZo)p6_MEY;r=85tXU4WT?-Fl_jeutS>-Y!5IDR!b zsV`IM{h$u}2wKiui?S`~{Jg_%BDNI&Ov^jKdO53UnTOQ%fv}i;Dc+D?Lq8+=d7sOk zt;v21au0v-GY_}2^IRN;|I1$k@5|5lvX@>G>jl}bKk%F3dWe`CH=a zXiFe9W`$UD`gZisAm?lVe53yp#X3OF*UNahK9|YOybr_maT%Wp)z~#5&$^W`nAQzv ze!nRf`=ZQGtxPTJLu)C26+h2~JSTFlIzsCFWcsDh05;=i4KjvYbBXpj(^J`*!(YS) zi>>50=HI~n-~MJl!G1t|S17Ufkh2If*GJHn;l4O?lWT0w&KYaNpNJ3gIV`!UALGS_ z;#P2&{hYPRe(%q30a>%uiYagu9Kjz2N6UE-uYo^H#WUqV_wZd0HBUeHh@Z`_CANUw z0r##dKCh*|oX@_CzJk69zZd?3zdbwqeh*m3&-u?>O{ps4qs4FJ&v&f_`u_t)#HVMS z&cJ(VTN6%%8~8in>i9HyTWhxxkEhMR-)i$b{sd3Lnd{DSvmdIlZ^a)$zDw&aC-r74 zcnK~m74NO-Yb3urPG1)r(+P4r^5@dCw>#j}qKV>5;dcFfM$f(MOz+CSl|KnjliM7I z(aYjH!bi|iycaZt`TR{F&zJ%7Qtw9LMsPUPg=&@JUE(SETkwa<-IM(jROjbitfia^ ze?8utmO8W$UyJ`$b}x{<-J1U^PQS0iaTWDOY&iQBb>e67jc6~fE#4nDV1G<&DxQ80 zR})hc9)_&fA>vQ;0CqJvO^s|JH*>h2o%iPY_R(Wv8Rx-rQ%keAKch`%FNX`+d6w5J z74yANd$CG6+lFHA z8c#ld40FBIg8Aat;}tmTk@ukc#j_V~$N6lsw^;T>?(Hntk(RUELGC`rwurq0?RT8> z{f(R}*?BK(;yIOiR)Kt9SX-a>KsB-Ev?t_s;CIni*6?cmq~?Cf&iPBfyFkv*?X=YJ zUhJ0I-wYifXE0+PFE4v^DfgzUS~RmHl!OmD+ZzvJ;0cz^NN=~oABFqTXUhM`oTsK;Aig)ABcAV3v$y{gzYPC?M~a_= z+v5-A?5dATa5M2^w99%8g!Sy1a-OyC2jX0(KfV#Nr_z2g+tAKo9}hW;b!a=X^Er70 zJ9RL%Wfji#|E52|-&yW)unFxooEnw6oB%`Q^n~8*N5x)pk6Yo%ko=tGci?6iuCGb7 zJU0&DXWwNUW!Sz{^qV#R*#7xgd>`6R{H{4U-0Gh z8}Nn3vK;6BGQQ&=XR&p~o}tYUdm4JuI@6w^RrHnj;hf97kKe@plKwFN0J+h7l!|dT zWPdHT5Js` z@4^nH_Y)t%zYNYdo(G@-Z3q0h`0wnj`5gXTrQ%sI6Msb?N^i_go!yqdNR9m&=lUaY z>fQ?61MdVUv9mW0mETLw4iGyv_Eb2G|Dc@CYl^y*{Wp$3MNam0V_3j%C$~NOM##?# zme4xF_5AGD{H)*&Id{Q|lCk5}Vi`})(5P~8)^}xp;#yt#{n*dK3}-5Nr)igYUSD>; z<&5T^1fTP(;gOYMeGcbe!EQv$e#&R7hiJ#}Z--IP5fbkXTULtmIt70(zlwGsJf}^6 zIMUd=i7mnZvH^+K_j){ClN)+4;LhIX|_;bCz>fZsMoyUyw8V zHolqm9v%%JR(a2$uh-8m?EDSR=)dq&`>%wJA!qnV+*;12xD%WyZx(+IF2M{r`TsJ! zPTPl_{gN?uV`u*^#RtH6xwG+s(79C9s6S~LYvwBJ@V9;@^Y`H&02i_c;;r#IxhLS6 zkomk%e#X3g$$JHkwvu)d{t*wQPr!BE=WP9U;XlW32=*uu^<(@6PU8RlMV+pPKx$arQtpJXfqa&icF#se_Yf6WAjY!`b(Z=(n;D zr2PjLL7uPwb^kBGyO6WHxtvRJcWBEVkLQ<*Gm`b1qs>A5?2(80ud}zt$z3ki30`*( zslSsd8#qYCJ5zi9DRLimom_tpetuq-nl@1WM96$+KYRvxPwj)VAM*c_xP$*&xj1*j zwOvimyXX*{xwt`Jb8(){m&(h(=WWFQQ+x$&C)&g8vuRu6{On~VZ7lm*c0>A5erIS- zdzN+~Eo+gvI|~noJa0FoKZ&=)-_Uk|tkc!5nK_ybX?rs*d3Un&ex16KK3>Gx4>^z5 z@pE1??@gd7J?|kcX$#o-yUHK)w-C$T%Q!N}H?Jw4E%)lT1-wd|C1)dT^8S90yiEdH zt~(C)f)4z}at1)=?{xf!y!Y7Wh+VZI8!U+xfVL_WR)@VS-o_{w&wJM%%m( zZ^zI1%9%V>o9v6<Q|NiF+@bH^ zwBLvSg!veSAJBeE;;<<1GKW(#@w}Hm=rFaHC`*CA@MA;Z|>hycY zkUe=O|4#a1_BXgceq)U>Lav?qkoEiyYO)`NJnK7^T!Yp~oA>dKw0m$z`Wv*Y_gem! z_(r_a81g%O8_3I<>4kIMhVZs{Z^%CT4DwDiSxzna@6gjG^<`&T_WLpHM{!d>6CTE% zr|;bFYuaXyt`GfT{f}VYJ zK;n>RQ8RusoV}1Sen#5b>wsdALJtbDa0^$>m}!d43(j{~kXH z8N){S6TA=}(@!UyGo9zb3t}VT0rnkY+1sfhC%}d5)_7T^Si9Wok+g=;j&?FE5zm~= zg#%z5tr5=LB-k7A?vdPKXciKe^SFxMJ^Zd>5iTchN`VZ_4O-sdGe$LK$`>?90J*hXl$sbJn49=7H zGXHYOc^QTCEXiC>WT(ETepF|-V{dCd=9P=HH-NpYdr^1ix_*DyODx~2k zT;%!Q51+;FTrQrG-SHgS9{3!1u*w|5O|=hHsJyVF*(yFz}KtrtK0?i&6tu!FV8d_Gd8ZtElWcQa(}ri%9z zPyUwdp|C<9M~mg2v!}0NUoGARufTukBWJi3e+1l3%ROaJ9D{R4>XtoQ*gN2fcuJ*s zSKoJy{l~A(za7_vdhFiX{6}m&-jVi>Yuu!58`{p|bJ;5(H8%UKT~$#l?q_FjUa8Fp zZE}upaX*>&9<hr6MAIRTIZq|8k_VKi=$5{6Bv?IFJJ7eL_n{5JxpvM; z)@-MW&u4ze^Ey15z6f8k#`$CC`l+{DJYw$}TU&NDTK3wb>?QJQ$#1V_b;A>ATjS5F ziv6_?XRLMT2jgM*3EC?5J&?UM5RPMSTq)i`n~0~br|rG;>%{Zy{Dwc7eYtT2TZ!H1 z`dPoL#fPx-H@;Ip@;P>u{3qofga^{LfTeIg)Pry2jKGs&(@ODu-bL)?@)wJxu5SUU z;qU9GA$tOQcYJoK_|7IZAlG)A>VDiEpG+8 z2mUv1rp;q`EWZc7QGVtl?@`0?{g8WEiQj_H^^rY%CEN*lJ`bd?DVulvKGb7p4Qq*Y zlJhZb48I3E`}}BpFD=(f|H;X-qjstIT(BRlm;AkG^I$hv%m2)^C&3N7 zjNS$x8Se~jvUhg0&uY-J_nX1qP|-H)Fr#E|%bgGH+4&nqJF&AzU(r5e>PCMG{vT2I z0cBN@eGQx>SwKWlk*I=bXP;Ab>sFnrd*AD(U8YUer8^v5_I#Ij7i|oGBJN4c`Pq$~ zKBwUx{LD$NcO+cRz6E!oABxxYFecon!XD9P82bP@O~i91T>`_!uB82ikC*==9xncq z*l7M@+(NuPE$jN3c!~WUJ=b2#Zw7T~nbVvLx6$*R!V|8!Kdm#&_AZ`!&6=Mep4|LC z%(n6~&l}`to!ZdzT+P{;{XCGp6JAhJe3vsSarXbs`FZR-JHEp;#9F6M_JOpVPn%cz zo)0(Y=b8DSpF#fzca>Ak+5NoOabg)y?&VTE31^(Y!nW3I8}Zfj8OE6R*D3r7uw|vs zlsL6^EB!lm19s{@^O>{m7UOwRyF2KoKzli-;!(7J;e*BVne0V+Bgnnn2NSd%3HiME z6>bTADvJAfHo2wZGjPtZv*};c&XAM+_lP#1;GByu=s(ZRoHaA#983ErKQ+@oK0RmH z?eg;emHRrEKUqBWn0K{d_)_|wa$li$$4#I*eJOtxT*=QFH-$FVo_J8Z1@x@(&>qIi zt|5LSWY1N>uhC{}_qq5ae)eykhdHM!_)X}`Y56{7ANv06>Cgw(i)X%WsZhsa53-+S zZ;p=_uZbtAxvn^MbsWUrNX`fBy-LM7d+A#5gwO-#h}RR(`&}mua z{Z(1i<(p#vQ6o7oKcoF+tb5|7at>$TA@&SApXGm}PnMVG%nbhH;zPs^rfot?UHpljo=F>tSHfuar`pexlk3dHIV%qktIhve9|uAg`tSVg z>j&^4objc$dc*H>=JN-Ot>B+ra=tA<5d;LNTJ$3g5y)iyf>{`e>aW_0sJae@Z)L2G&^45YsZ{o!5uR_i?T#YXXSCT477 z*^QtL1_FolaySn6@l0SlWB|J;p0&W%m zfPQQD;yrFXza%&NZXo;yIae~b$Fm>9e`r6F{Ry6^k9pz^=zaLlvFEZcV`uCavvaO& z!yf7WkClJ4F}}>tdYmJ^BW!LB^SPn*|NIKE;gILiBw8(Yo_(pe)M(b@M1Jaj6@La~ zTsb$+p)KNXfIG@;NS*hD?4!(W4P$vv{9o)L(l>Z`5zc>IXigY1>if?WmPlAGW8&AhaM z{JqPZ7n%Q2+GI|zfUn`Fa`BnsM7)E(x5Kx|8Oi>doxPNIj2fk)?yA#Q(|(50kTbGT zrR%Dx*Ku!cs>@x?o{lp=WAL`{V}*Wkow9Y-XKmxk-pbjvAD#lAL!Mig<2_;Ll6~dA zD{;=9TWB@p&*b01&-Y+c_`Uf@@fYHC{I~EkW$%wTbGVwF=l7K`pMMB`8>&Hju{`hI zt}LFpAG2%FnuvA6Tf#}M(~h1R+X9-fo5Iy{8frJ2HW=sGSHs+AUoU}->y&a)vjcF} z@NV1}o|pduv}K=LD&FxA=hrYVi)riF`5ckj$Ufbe-w&s@GcP$uj$xO}?nB-Q+Kpq+ zqmRe+AbURZv6l8a&Uc8TXp<`KPu!9|6mO1GFWGacqdByD-~@hR$KoS!6Iu`UMHS9U zxPg}1X|Lbo=m+yx(BEWl&(6NS0k&jUrFX*lZzk<8emk7?c^LBSx(lz=c5`-~lQ+r9 z^W-ernJ|Tx`QDTEirin=hr4#ph6nlEut(AI{HV$Qntij_h3wqN3G9qK1`rTw<-)Y;3BGhmi}Qd=c<=6*MPAl?~2p^ql| zOkdaVr{O*1UI*XFN&jEavfoeaQM~7kVNZv*J$vq;wUM(%PWo?+C-R?y%ithd`ug1d ztIuBv-Ds)lx^STUtnub@PB)H@w8gYzpg-ifoI2?Me~8z{JC%yh4ujcQlTrM!xLi@x z!whzEUXfo1--`3Ir%mYVp*cLQ-`4U!Vh=L6fAE{h$unshJ$t2p$-crFU&fw#uP>H8 z`kvgYXtQZq_xx>-^zm1@cy1gecDKCip&5H*4{Ij=oA~S6ZcW=)ES&qh2{OLN;Rt!> zl#Ef{KiK!1yZ^{dy=ITMl#{WI*XMXSnX~lqy_^^DOYoU?S>OFi#ryXz{G16Z!h`Ig4nnEeNPyHfFa zy)J%GPWs8S>T-MwZ5#IE_;I<{(Qei@_nYh2f^qClJ&Kz8M*E#R&YG~P4Ue0eUS5h8lI1{ z78y&gl4rWUeqc9d_r-aB<~|OU_Y7?gyFV;-ozzV@l!} zo;EG;!StL7yW>t`Yw+v*8OFa0K2FYhNUiQ7RuAt3SHrjTJhR5Kv!|Yfc9q4u+-zFr zax4B)Iitm1!~cXH^kH}&ZEsjn>3yD78@@6pO=%0n2Se5&wUcpHVGm?y@BFpaImuql zzD}FZ#qwTNy`+X=6*Ps{={vg<0})HqXW{!}wD1PJO@Fbl2IK_5|%T z*EouO=>Kx7i+={G_q0o`XPl{#m+%hS9!lGW-473dIbzfJ|A7q^#d*<}{vu=_t;Fr= z`>`{RZ{iVhSKz7oyGQ(JXa|qO)^c{lZ{bVuMkSvM*x8RcUxz`S;RoQ%OF#DFvbCx# zJ_l|>%irGZxz@9Qegzx}Z;SU4%UQCfRJ_0UqJ5Y4^yF5fXZ`PhN!mXvb{*|5dDCfM z+rPWgvk$M7UlZRgmO6V_?$zukeO_sZPY_Ejs{!n9t7n^nl##tL)P+v@jt7xS6DmpyPa?IfJ@sIS;Ue)hl^cAg(svTtFh{%UjeHl9F1kuof_ic9VJAnO#k+Hh-k}HiP}a+Twi4zB$x&Qg`VieRX4R#Ga-y2DX8?oP znWNKTqQ0)uHs{?3rJ^3sWA7`s4t)bHIoZSe(Qk)cA#;@f_ur5DyV$ipEV-UGS%chX zW7;>gtl?wy2jq=lKS?`OJm{?d!*J^QCpizYPiNba^nEhU^|CKdt}Nc?{>0bwzZUxvwq&0xC;Q=9es?^d_Pe~y@hJXH z{AY1%oHJ()J7?>i?&(?ndHl?6ZT=#D_F&dzE4YAt@59DKf9ZePRP))4o-;S^M_-9; zFP6EDe?QDN{z>>v{oY;j-mC9iGv85f&A(LaMSea{oWS2#AOGTKF7i8Z^JopACx5=! z7_m+1U$Upr^G1o=DQWXi1s{f9zV4)1fS1u zfsf^94eGGpVRx3Bb(>Es%byfXY2EwobBLh zSVB7+XAVcQQxDnGYqDo?Q^>q$Po2%K3nNRO@%l+!RoDMK7pov{Ufn5|)$unaKZ9`_0$~ z&@O{RXx(5m?GH#j4bpb9z8`1r*t7WFrb)T@T$A?e*jf90H_=8;6?WD&+J7L|d0pNs zw0+oDL3Q(U8O|P0A8-5FWyv*iHrH{zto<&KbNW6x3rp^W-5ib;&$=yz_WJoi-d^IV z>)UAe%gOU4XUI*E_0RkE2~Z1e6l;v1!H1WM&+J>Vk7ZBPcPp_^D(y|kny=BvDE5|O z1LgdHAL4frTLm?Zp*DWM!o676C2|gcD{05jet=!rqj1ih-2WGq)(uhv5A$EfXW*Ky z^?;n**P*nAkog=4Q=mS7C0uU|qwsRM2jV6CoF`ZCpVIHkxVyeGXXnu;$(aiqLGm;9 zMUeK{U!T(QciJzN|4-P!Un7=UNNv~VAI(0PozHV)3_I`e;mZ0uU!LN3$8O4_2}JQ<2%>x%Wfu~HES*Q z9{!2e0#|{t?BFf-;T6{I;o^I~)~=hHo?b4_gMY|*g53cxlbdH;-d|s5cci_bUDi7D zk#lfc+NJmm{&@B@dD#zp!|nX+pUg|@>$bGid zZew$nxjq#(E~`t}Sl(T7!uR02ao#g>)^yV5QJgbl(~>h&EN9EE@Qhd^{GC|VZ4`8X zv+18gCwu!I@wK!kA$8UbI9k19Q^sc|*sr&hPaxu5IsSpHJ+W%xB(1?@Cw zT4AmD8Asan!{^Hx#m?T(-pYL64{y^C)JHRXmH2R&OK&UZId=Mf8ctMOJ@~1G-eT2o zM|#GS_5W0x>-gJ3Gk)eOwKY%fO#S!5_wk>C`{cfYr<97H*_?>8*V9MpIpM90F~_ikerElvb;UnxA!Q%1AK*DjdozkcM#By z|Ej$D?A-SPv3rbRFuMuua{MaoVZ1-=1~p1_+IVfVCiUO}_FeEPeLX+xQUzb;x*L^> zb1L;(qW#66%ifc{j-8+Lryg$8W;OdLX#G&}zA{YScJii*wUU!M-+68EZz)EJJxjk6 zK4Whowh4X)p9)pW+KPQidmLv!XMeZFEAX3mYdI&W(+Bjq3I9&HbItMZxUZaXv|)0} z>^*3w!xDC$jpwrmil4*o1)Dv-B_dx1B_nglZc`vwof*88m7x@F^` zpNq%JA57apyfOb8Ij0-PU-U}akNn>3LF_$X7XMOt2SfJef3#h}&j0tR8vYvc&M;H| zpNoIV&wb|SZbR^~@QXHE(DS}DliiAaA^m3dd-};Z3~k-RU;#ryh}#yOq68Dx%UmApgsD9)>f zw47o0!vNTlpLte?D>?#vzyS?9?(-#=VjD(3ZIer@(bTE>; zmcm0~9V?3U%YChuzaL~TEv0v-)nt#6I|gryN7287)bUK(arjJp6YVyfx%mZhhU{G` za3%Za%Ay8_(_&|=*+=Ujo;CMgH0J7PRSjOSpxdd$|}=yH}f&?5EAx`$OVc%U#$_@KNxAzP9H7slPWM>z}=F zq1bycgr9xUhjudV%0C^J@JH+aQrgKSpHJ}RaD>@cD8-lQWf_y_$FQFU2O&E6c9?uyvqkogPp-S|6>#sxE z*!dsAY?uqF|0^KpP`*Qajr{|=r8XD9^X$2BlD41m$3Vu^*L}Q%bFOrOadNidUsvHw zhpx2qA#<|~&VVJ_WbJcaHOD#sQ@4A%r$@Bei~hBKUN(+;?32Zh#kav7^z`wyyxsYE z_T?Vx;_REQ;&G^{AzGjdkFP5&DhESQDY<6kFyUF8^GSzewe21GyDbgZ}ErtFR>3`J-a<* zeO}=21b6fE-S$u5_q0dF%k*97uhVL{UQ7Hu)TDR%pZ}b^tlc^M>)r3I z;;-@N$(b#deK7|f=jWaMSX$nRwxwMI?aDUjf!H=X3rg7F5aP5(T4N$ zZw8OV*@q|6Ch6xzcAiV8$z6hTUY##zCjBe+?U1oFko%sTDsYxqEjexJ-T9~B-(V_z zIsHd=&WyeB{o0MBW&L-+gVq+`O2n zI=uv0hsz-MUK2lt+sS=etSjW_a9PLf%|~Hv$r(ng%ia%W%Kcn?C_DS8y;ws$X>IXL z&3w1AA94?wzkiG8EIkB2gdf!IV0O-`#&XBwlki@+HD0HmKJ4%1Mn8a^`Wb_d!o%=o za&E`tAorNF<58S7oC6!vn?OH!tFl)ieP;eALe9t_|9T+cf}3&?wF z=5RMT`S*JJup7zg4Vj;t`8%>Zo0}^56u2DLX!oO7UuaoY-*76sj=ZeHA?$n>DzW!w zFRUn@+i&rw@t5#(p6tfnN84A~2jcua=2vO2>;EuZ4cds+(e4qP^_sxWy&lBQo*O0S zL;hFnjySbBoxK@6FLpD(2RzRI!1xC0`*(iM<*aYDp2o<|vpwHcUm<>;_#6D+jAtWu z?!7G>S=Nr;2wGGWb^Q%(Hawzj4LSRu#v@9>*Rbemp$D&l$1;-^Sm@e67a!m5R^Zcd&CN*26g$zBIP)@IrV;o0_nr zSX;>bRY3Z^9deHKfM)Dj^0NMiXuqap9_YPkSJT?^kHr_@Z)kZo zAJDT#&0!&Z0j(E%3A+})AD)oY6*Au3?-4lX;}rIH^0UWMbCr0Cy!QAUSOWK!onLbD zJiG-yWq$*ChOFXGgY2En%*PY7&DoFRDsY4`ZYh2PEw%j%JGJ~Y9?GxA?k6^ppFL7l zAGgr5*XE0Ng_-PA;Cwh*?ziG2aQ1V~?p&)8{D)nY-U-fz)64b>o`|2ocZ)Y+Zx30& z@A!ER90HFS=V;mw?7Lup=mvR)FOwHPbNdW@%ikSO)n-$?mfy8xY}PQfJ`HZ8HL_Og z&F^tAg|?bjSKe$mke|<_H&qmC)QEO1yu-g7XMdl~&R+amUcMW@o!tSZilr`7|7CX8 zy#xLPcc%@;_u>X{6MG%ZX0OJ1$7xonFLtgy6*AxNt}WgdE~M|t{}Uf8wgY>$*opA< z!^QI_wVZd0s&Fp!fxV$IY*JG9t}zp5PLg*SJ`%D%kBe2IeXs8>_zwM!#S6u*HGerL z#_-4E2jw&qZ!4Cw<#76av{tyb_-$}1`%GHusEwSn@%8l7Vb=aaoO!#3{;q4MK6=5? z?0VKGYo6~}+d#p7A_CW4yPkPRteQ2qlzvT2_w}HIpRe8j- z5bp~OXyfqy@-AWDS!s{5o3M{`jS~Abc0PxtCTa0ov&(n)2%x~uGO}LYO1MRl8#rH|o*n6=X zi=}<`>G80f){*wH_@4T^k=+tc#F?9>?9^S(&mBFVN6?<&&%htx^wm-y!}zJ61A7#o zce20J=W2X~*lc?0{tI>^c{gfv8M_L75`9bj1HUF@9sj5-zW3@V)=>V}wBFE`o$txh zcT;^lC_WqVEXh8~ypJvUz60-vv##H=v$nm(Gl%=Y0RF$=5B8_{OF4PQ)ZjP97r+wQ ze0-zpoku@c-p;Q1BRyyMGyEsTK9O@Q+)djK*3fr{oFy5{!Emc~6=i!~-U{viK|2fb z%vpzzqg{t{Zda9aCO)Wa-@t+VOFvOY53i}|ha zBVzs3NA`BsIoD3zKg7>?8_BDOXYt>0y|llMpZCtp?RYp-&biPHZsLy>>s%^oFF!jO zLAw_Q(@vAq6lYDcuNRbE7w24W2KT{9a_%o%GyM$Yp8^N7Z-86b&7&F1Kz;Y8tuH%2 zDvM{@ICjpC4tOZ!yOvt~J>V9(N78z-b0%aSu7p+gcAuFrH2EbF|L1C3tmrwNz1j zrddjB$6f(#lAp0@^O3yav^!{7r{OqzYR{hLm0w#d*U0m%uH63kP@I|?ssEhcTho4Z z52@v0?8{2_6hHgnU|Qy2OV`T#*~1mZyV*%%4e9my|B!Pmy9KQ^T*^<~p2eOD`CcyX zHEqhpcOFOK%yY*2DLl>3e;L!;?rQ?AI%KT5w>9t{dnheuNzSM2sm<7#pFEEa%4g2rXxGKU^DXu~V;~Lgsc8cwXM|{0+DVG>38Y zn(zaBFZN5RxW^O3E@bB{KZZUR9yGS9^bgtH@#IQ-6K^K325lpL=ClzE;!opW3ET5? z-w(;#iCxB(_)FTo?5thR*+Kl1E8H_)LmNh$#eN#v(7!O2?zDHrJ~PKB;k=975r!w^ z3^T^;uNC}J`~~c_>>b(X%N@ebzn9t!Z^a*qSBvF&mES?hTwOwIf-fi)?~~oc=JRvy z0b)6)kHTyD*_&^|2=T1>R_xRC(?zTowC7(755ZIXtjky8t>r$-{|I`DrH(H)p44eQ zT77ovGQT^NdCUFmg!iNMVh_L@%t6lhW3|}??xM98dzW34J|B9sQ){WOw)hG>Q(o@# z6?UG@4e$i`(zsGDU7-PFZ=XT04^#NpmWnmY8l57RGvsKoCiYC$=tg$VlH^|_x1X`6 z&pZ!n(w=~i*tPK+;-vzFO2^|i?!Si(x{&@%7$LPO{Px&N&HH)5&R8=xP*6TVyfAOD9L{E2dM4$p<0Az6>q zcIQ%|AN_oSu2;ryxEooF8V(jMbaf%9mYv)YjB4bnF6XtQz7-Rkt~oxOi(_b>e% z#{XNpVPccmTO|$yA@8u0%EfbPww(5~?Px1;GyZCQ?86?-&a>bU_Kq;H?EH7Vzr{Du zTk-S$d#}6~-~!rS|I@$Vm&Dr`(-rb}(*6uwL4RFNUD!@M@3G%?FTUS?hM%zu~%t*jA38?EwH3q z+`|^^W9iw4Z|r3+$$Or50PdoXJPQ`c&Dov0JXq{-JV%=&=&7IS?8fx5P!s+(-qiK! z+TSXcI-Lz~^PiFbB0GIdXD@~m**VvDF4=eN3)#)#7C4hXQMMk)QMXcWoY^&B0^wD{?Luze}6P;5hbBdDF$Smk0CPK%T+dKwH>cJp1Gm zya3ND8xNch8P5#;KCOKheOK}?mv=DEdvEr}B3R4bzf^n&)m-i&{C0AF)^-QHo>m`U zA?Gdmxptn}8QVGk(^9+Z*vHcIOv^sly<9v;7U2#2srt$Nr>~4P`y#)il0LKES^s=T zkebWAU8inN#F>{#cxt)$jPefqMD}}fQ%~8Gn_JH}E4;TrResK)dhC_jW?Uc0%{`yU zKMme+-Q;I3#zOkYGqbV!swVyCG({lf*m-P36 zSk8}s!Uy74(0kHHvoFK5jbX7^J$gg_J2JBek@%Pj^NLgB$G`#nZE?=>Pvw3Ohx1>@FSte(dPnw!FruP3BfHS*$$6S~08YJ+ zVdwMJ1e`s!8$P9baUY|_=hLR++VpAKWW9D`|HD15$En*9IA>!GcHRX}$D88=<;=jD z%hc=u{36U0--^Di_$PQ1TF$Y|)fVh;U>+=msg=bU&{6xf{4MwPj-_4hWh?nvw>s?0 zU4H^Q`!oMGd#rYw$?rtl6dKc?cAlLnb~JmXSbyA4EYF&H?A_Q^=qIyp!f(3Hbz*br z_we`U4-h+-o!ZK}q~@pa`?&tirDE+j;_pL0o1ZbICf=jH0Hx&eOOShO0Gacw(T=n& z;7oqIaMj2Z> z+B~slP*p6~ngzG_EZ&!&q-Cu2U1{_+Gmit zz5w#hcw2?JkTVD8-lowu;@7nn9oX5cm%u+^FnywSITtp={b{Mw-|^c}LC@b$_?z9A z-jzQKhRU7l-X3HxgP(dh2WZnF`}a7wy5!7L=NDGkt8((Y>ig1Do12TZaP2{O7qJ8A zRq!VK!`WHmzVd4Ex2I2pj5+;p2X|_lIot*2h^4-cC>3YkGOrw&g4sns1y=79Yhy(jcxe~ve$H^;}~9cardop0>! zv|RrT>zjAroJZO3XTv%2hVTc%YWCIUr7j*Sma(tF*K5;=-9t{^zs}%)$sZxFALKk- ziT{n8;JYe)UnXZcdr)GKvop_ztncpp^YJ$PUeH6VKi-p;{neA*1>Y<$&+Wg}+c4TV ze%>YT!++{O&%ETNwy%MVXD|5FeGZYAH5#Q}a{rm1Jc|a2$G=zm_i=B2&a_(iCg?$H z!O!1@dxo7_$hmd~+{W&z-FSLFBm4;~D$R-5N%(8AkzyIs-gs+zeMpV{3B&0*Ptqpq zoO`KFzmC2DXB|@$yNJE6uZ%5odp-OJsiQkwvpqX&a~I?cnJw>md@27EHJ*2$*Z3#9 zhpcV(Qa2a{$M9bf-xlvCKYM8<`w#htu*b1S;a%i(X6GEq9)7Xxv!9&Q`&?S)=vaOY zegnwZR%z1!XP?idrC$5V$r-#8JR<(@wf4Mmwbo}d@zhGr+if7Vy)iyQY!zJ2&iOVI z4}I7;v~O7|o?8dwoI$_Smx*r=E%|pt#{UkChFi7yzdfGuYzsRW!xBh6<@#^HVDmN@ zuc0--C&=p${j}d8cNp$XuP&Z*;WGS`_|5EB@EKyQ*tg)^>%O=ReLTA{-k6^;{*5=O zFpu;#{8`WvUf2InT5a*!>iu|H4S65KrnH`7J!v^x4lsXj(sDn&AnyhlYt}Dkz&x=R zaNdzN!fo}vTzolyYdK%CYtm=9ulM+)_&JAjM&Cfs*_u5uM0^U)-(9IGHoell&~nDD zh3v27Y{br54uIEbIa`wRy|z7QKhW}gjP?%H(&hlMhGIwJ)JM+$WBA|6Yw8)_hM&Fh z0PQ3^s*6)n%@I_zE8`G0A=XMJ|3WxjTV)Z`!dPWY{|*psQNdeBYm z4*Fv4rx`8g0r5vr<`TuXivda>@Kv~a25X~_Z2^DJhq}ZKW^gZ?0Fe?i(W3~ zIOE8gkHSyW=E<4J&l!_*eG&h0TBi@p}PH*?AvmCbt^y%wL5w)~g}wl6ooYFLRXo{Fivn|5~njxBEJe zekbi7xew63#r5zQTE>#vYsc>6n&?@IYq#SdktZttO2 zXCDmL$;&?49Hv80ehb_S@*K>+>hQ_Uf%Og;pe;W z?8&U>W6soR>`%p4;QeViXH&CX*?I5yKtBV-^Iq@`&UtZ**sr)u`xxi^PoD$n%iPm} zcr$pLmOYZaoZ6p`FQGjke=ByL1IfKGte^45wpjZE_&w=0Szo6|*@wBf?JByYxG;1`< zwSMMbESB@HDNe1d?_PZ7$z06he}n(hPjlnHOnxJI2f<(TC+OcoE%p$(zv%NgJWKo+ zx%b0S>}Jp%vKHBQ^C9oDuk*8}r?H3jEZ*4~m(`p;ZxTCQtPB}nZ#j8Br-oa~$?sP^ zjJKDYxvk9}4|8CmoZaC|vC}I&BcKi34Cj{AS7mXYpM<;fE40~@Jqb3j^SrzRm&I~_ zxwpOe@2xe@_&V{SwCvm0U>5%qc{u~##1ru%T6>rQr~c1QU1a{Q=Rb$PHSW6do6rZr zvI_5v@TmA2$Qh8i+n&}WzMMYVOrd3O<^7=-G_G(hv8?TK$lT`Nyya&LnYY&R52D}l zkn@qfAMFtMQM-)u0$NLcN5~${8IrXgkG~h+hqi|Qps{8j=I;~zXKiubFW{d99mRj4 zKP^A^ojo?GT#We_oH;!S-%TH{?eWkBKGN1`!VFa$o+1t-J^0}6mL>CKkngH zdUbwR7{LA=|6%Tivma#VthiF{IqdA4|DI`LJFp+n{u+8a$leNXBX$Aas={Xuc76H| z>@(QebBl~;6mE_eiFJSq`P0pRpY_Xr%Ut~c3yd@KQb#|J(Kqc;?3H}STu*!ey@Pl^IGWuU567eBrj8%e@7C-| zkl&x^jk}0vd`DCk>wi9b89Vhe81f#Helv!5U~Bk`HU!S5pAXN|`r^)|qGpqK5Wfnp zj*p?g%Dx1jhezO974Dz?5~N)NoO8S(&hs^AQx<-va8EtqrUYe^}mHZEMp1l-HBpLu^yAxp;4WOZFpT%j9ecSzJD+`Wj_-tXA1BaLPt)ZNcIBc@28-3@r^eFX7(5tXCcdmk@mw#l zkD#3c`_N8=-{22@UcI(BkDp`byR_lBuDQ#+EzmBX-}>Vd`9sB8;q0lLt0VEHP=j5n zES@Q^%g;UCgtJ~bpLWDU%EhxKd-6K=hmhy-iTvz~{ERO1o&7KwPbhgV;umqQ(@f60 z^84_UpE=kGUxu@HkF0Pe;5Tt&d)_nt@}%1&Q{@Rr^+(pS!l|IkjC ze-S&^IaEAzme{8FVzF+tv)MPn7}$cp8DtIGi>Li)e%_U8<5TG`v44Q$*^Oy6VRU72 zPX0!}m_84d({pb>(>`!cG{nF0U-W!PeGX%1ee!OUx!N3B@$2Ak#L{+md42Fk_?@-I zXT|f_dqLLfAKKQ#kIGpiehWK&=PcU_a&}xH_8FWU9lu)PTE;L;EYI2Y^ta7h?)!ML zR=Bgij-`Jr|15kT9*I+fbs%#*mDUp1;D4sw1N^7iA3}q&y+Rwy&biqcYOZzvP*+|x z@v7pt<5TcL=p^Sl$ohP*c1Ph`>Gf%Aao*8yr+tOT;}Nt`P@CUSzb~2Najti;d)c7o zZ!CM(^0OwFu&dyHczLD$s?DD4oafp5somVi0dgv#iui{x0rp6rw2V7vV&-ofNG+WJ z2OI0n{AKJd@D6g)zAxU9e=gj|ofJis0bb)YM4EgWww6ZO%Sy^j88^7sq+tKbaUQreNYHOv+Jl0O(u;x{Z? zd*iQxv(5u)&*S|6S=-T4mwOshWBGZu&xAVc8{|DGmVaCM9bN*5h-Dvk7t6Ecd3mX~ z{rTTO<}~Zp8J^YWeEt{iu@k>6?^$g&VL!zF%-pnOuYuHMYUncYJ8_=<*&jDUo?#7e z`pok_f0H5kuge`HR#k4^FCM0~5ueO%j2qyiXj$KkYa#zMZEutRJNrS}CU5{$Ef;g# zk^PJOwK(TPzB>u`51#L)p8O zigV>G_Dp%_;G5tp@%N0aop@*VTkNs)C332;Z^REmPuf&DpXzfwKRKtuy>Jx04;%-( zYSW{#cZy@r=(p<$XEN%lq-CFpE7L*MKUnwX2+Y+T?rshuQmx zZ_LkGmCtvN<8A3H8pqE%re-c?FBZF1ZtC!CXwUu|HlnA_ zr|CQEm3f*9Ip^2V>gj6%zX?BQ$2PRo)NJ-?wA94W><=o6?{iAHAO8}VwO0Mg*^PZ4 zJ!{vBooC-NvCPMV+TV^J#+-qcVg3|)E%9q; zN3iqGP@A7xeh2^ed}UXG`tZDas)KilEuOVJ5nl@DRTSU(j9~u>{aho@-3!?h@PTqa z!e?kZ8Xp2vD~q~M?cBw_#`SY%=6qPoZ>mk^>T7+a&zki0{9o~F{G6fLpS5T;;TPI) zv2N^Var*fjQjagfGN@Kr%vH{~oFQ4Wm2%#I%yY)SHEk#!;(jwvN3*k6MzCAJBxoe3 zx%|}CPIw16kUbOUyjxl>zW+VAvN#K_iqB5XH-{Zz0&O2iE#~L={b|$L`MU;p}*TR`ILFDN5DQ`%$&$w&Z7@jhCC}<~fkQcgKD7^ESSm z|4N1Nz@EmmLAD4zY>m43Rm`?9}!*nNsMmOl*Y!soQ%a=w={+jR%v z)A(mb8$Gd`|tZ_y58pAEkl@3FKgv`s5q-}~l2@koAZHhZr&ZVJ`qog%h1dp7<~Y!tf& zdmmh(%_~r1kHUFYT!BbONr=Mvh_Dpv6Fb2-3EY6br4Xf1S86{_keotZ#f&*c=6eqC&T z?H_@m^h@RBcNsro=Q(pPOsp1>++p|Z; zhY6)(-{v{^l=uk`xgR+zpr3eC+8waMxE|tQn0HrnIj=_jB2YT}yid!0V7py!#J^J=j%9)l;! ze-NI9DRO_nxyM7;PeIR0W0CimK0c;ThQag^ICYab*&BD(_jc?B^we$d%HrCy#JbaK zz%qJjsxW5;1YW6u=#?g`g2>SrJ4WKn#MNge%ofnFq%+AG|yEe}I;biu7cJAw4 z?at<}qtC(f8sp#|epCFZ{-)uUaJ&5O zxP|#Y8c)~mFL=9$vk4w!UxE)4%NVDX)dMYO=)1VTzV@P}P8ZT{gw#hh{y_1waD9II zUd%oos?aO>SCxzJQ|IIM{M66iVn^VNDf7HF`%u?EoL>iafcqiqu#epFayql?my6GA zS)&p3O6Ulmz;@y}zZTe{_UZZ87W`48Bw!ka#=d?9twaOpKKZsq6b|lWa&0(iLP7^;xyaO%g(cW+; zyA`}Z&zX{Y>I!erc7V1e=O#W6GUko&ow$`T#-Cc@v!474$b0IUIQMtBc+U7c#jl0>u)n+;=}p-~`(Ra0bK1GNArJWr>=j3dn$Y;q+KO9KX2QGe=GeAJd%Do{bqjND+jW-hnzq6 z@zZV!`*qrOcnfI9&OBbt&R#u|UsdiP+6(+2U^?V{>w|}iog_9-UhZvU+EqQ&A5`V% z%&3QRzNC-7rQ$ti8a?&*KF;&{W_E9}ukiWuTCuOjnUg$IUx&|OA}#lLE-bPxd*JMW z)cGOsDSs4f#GZ@?;Qr+T?^PD--=AJ)=ZxG({4;hZ_NFkxJJWic+Pu-Y28s2SpXdMO zv_r)%qNQFo;|CdYcYGdPE;bCdflB)0(2IQ+tSlEltNz6`Qh!6`r;f6}pT@i5$*wt2 zY!3T2$lm!7r#4gDL)bYlYU93T>qu*am($L~FVKHtze?|lQwy1+QS?4=9sgI%g)?x$XOs~t&y!3;-ldY~S)?)c=o2R z#9Pt&Kohu`R!@H~%FUiVgmxc4XVpRMr}UlYe$JHSZVg%AIxw#6?1eKRbDr;(x5H1! zT?;vPr{l|wWncNT{2S+q>}AlbTzm%2J#=E%#W!f*9`6EsLe89PH?;U6x@(DPuud^;ybC0XsP+O&=Lm0Nwoa#hA(sd3*cLJ_UB&o6>vE_HGG5Ecs$Vk z%qSJ_1H0lwX|s)GGxjL?=kil4ITuI5;rvdx1g+Wci%o&v>|5}zmBqb2&F>-S4t~!5 z24dM~`B`IQTK39($oJdd(AU7@V$<<>`EA9Xlb1Qkd&^t25pWwV*MEifIW%N%O4|o# zPd-yFzH?kfOYN^v(}&SA-;dM&a*aQF0eC9T*!N~{EB6X{53mo1 zoISbE*WnZTd-A@a_r~AR@|@4v^#tuo+J*c9uqAs4-UNEU3jPu~pFn>9BG(+R-4D>2 z-J55Xew`4C9+ZT4E?T(w!vM2Kl+?IbJ z`*zv^kn!g}QWJxW=YHB6T52>uvl@%DAF@Y(=6}DocwTqbrWI{-$X;Je-;q6@ewMr* z+GMR7(k6)SmmIO26AS5?&pbn$!iDsYv{{db^DofXN%%2-Uv~Cl4Sx1n{5x^AaxwPH zwcAweY1-7vqJGa6I{|K|&!;z*Gm8I%cn6#@d_;f5S+;>)i@i~)n9pPFtDE>ax0ljJ z!wT2@jbC5xU;Olw^XOy#Fu0#zhJ25Z?bi~ogxt}Nd98uN4C-|{Em2XXqjr=ocG_(C7w zz$*9jk$9e6o0N**AzvjY^^m&Cby9mvwQXN6>a{EXNBCUZ=fyt|tH=KapD(8o&Uu-0 zDD$@wWUY=V*#r1h=#)6lSvZ5;65fUP_1W)#ZFa(4;Wc{ZFylKDnzHk5@n=Qx&TCl9J7i~E^b(Q(On|~Ai9rk57@6lcG3ds48y_9Fu$9Son zo7kzZ5&Z0}YW&^UnfILSbNOYcAt!7922Kq&hn<{r&r}xQ*_|aP>->SeRMk9XZCcRE zcoxoevwyFUa}K;!QTz-g&z&{w8}USX=Dj1%9=SmNpYVtNp2e5au9f>U`!m;RB6ktH zHe78im*f0hi`2^$cE+0T6 z8t(#qU_bh;cn%&88C#{C|G?bPB1s+0s zSic7+PP?X5eD`xDJs88!-uPH-wzeDbvlbJQi?c?xApd@ z%{~m?WB-Y3d6&o@cnO}P?O!sl`pNfjchX*<&A?aE7giSUw++R9r`=7fC*BC>?@OLc zOZ^VzA7-4{-=EWRUVQ{NXkQx-g7xL%=VV*p3H0l6?lWsRElkV&WFH;JzZZVgXRETB zaQ!{`kBgr#ekyw<8=Ia7OA7SDi%a%vjabXs3_-WN`QdF=l1oxF4TnbY5( z9WDEF4L$Xmn#$hJKB|VBRTOKL`r8>&v+vQfrWdk%8vjhW$$Ns|6<6|~HLk7kI=T1J zcZdDh&%zeQm-A{peh2>r55ceHVjr(!4~7QXACE7gf5_g5Jr;I%&74c`voroP;b<78 zzm9lLS>4OY`%;OYx}F6M;UPGxByL`(v8&2^4G)CI`dliu8s}_$1A4)qW#=B;&E5q! zf@j%#!(`)X5B2nu^W<$>Q)p1OhWKTDWt@ZHQ+DPt^V8Pn#SZLGU>j{~;{1Or_K>rg zex4~8dnxNUT>jNKwfLj>jkp`XN~!o6M8-b`vZtqs=Q)!-xl&$LIp69pe}V}6XE|FU+~p#Qq!`T6Ys^i$+MPG1B2!M*%Gc(M4a+P{X=whw&@IgJSP9wlX*;`ATsw7NP42n)4*k>>&l=Td zKg7=eyD8V$$UOF;qR>p|DerW2>+3Dm(~$B<)48&SJ>~`ZR#3-mpn_tVuNT~^AC`7DCAz+ zz#jCS|f*r^DMaei_?=yA_b8?@)y3vO6 z^Zmm#u`Trbv-}rmAL%#e>GrhL%`5C5@Vk(jyuReRw9M&u+FpZi!#T%lvu_elyCrgN z#;2?+&a2ttH^|Gr*$Qrj(WT;ypTT~MT}SR2_+WYM*n6{|WUq%>a-OSnJ@#w#DTQh#+Je?xT7a`7C=-*lR%-Ba`pw7YR?YJ+yG_)o#ra7<4% z;Tm}^)n@k;FR^dY_Io_fXVu%;Ifr|fihC-vm&*HwpEK-T_xKU}Lp+e4&yiWn*~V}V z-iOwUmi^IE-U1k*UCy$R-HJ6%9jt%A807X5-&!o!e*+HZe_ymxz9;O+ewyBIZSi}(-&YntlkVjHKB6BWXCqk4Uhe!lS^i`ETqorb`X5POqyN;`t8ks1{Ooj^ykH-gNxvT-1`o3j zlz)jnI`9WT_Th*6%YL|XHAET?ckby*w4_;=jUv1!;f7_TV7dw_p+R}2y#Ck(T>N*LUY$@f#;Np z&jp{0?G4vgi2pyD?gM`7v3(zSyHH4JkR(Y~8dlQx4%v}e*(;CiJ)U@|wD-^;(NZe3 zNL1guNcP@)lfAcx|NHoTp1;@Ycpc|?oac3YuFtja`~E^Z`_Z=*f0LHaQ%B$_wDa*8 zIUCr0VP{xN`yQ^7H7Ahw^rLS@%bckPSucm+ z+#^P+>lDbImVGN{|6=hM#54CYei!4};@NvziS5DO0}sRtD~faZZhWzJ+)dvG7Hh|9 z;Kb!9#tq_0zdzH1yuOCOxbZbRD>avz*mD(&nUhgD&6ct)}JkaMs$ja$2(M z!0+;M@BNu}G}Mxx|L1htnzl9Z%p=&J!;SDK?RVv#V;|PFsCQp>+FauI#Q!KW1}-WU z_ko`B=d*J+|IxDsuww^pv_{A57l| zpQ^sxw`=h8-}akM%V)Fu_#4=H_qy%f3)v)=Y2q7sm01Qt>(Detfr_kMQew2_68Mx9ef7 zGVkGQaQ3w&>dE~p`%LzQ?6EcJU*OEe9_&U?RXm@6a!$m*0)7-vyDDg-**mchms5@1 z#=JR#p7+bV&s_QthVyIivp?qi9avF(E}KMe4O?hWZQ4|f$U40W zx6!W4+4($@J#aLwrGC$SC;vWV`ut8hX}BCtM4v>x;*<@qOBR%3g>c zR_0Q<4~Wm=uVWv_eiHhLW!>LN{{qIc-=KG5r++TS{rOkJWBSwZusLxU?P%@Gd%uhKuC!0G z&xUhL#hH=5JyL$o*7U_@(8=>B@*9bL%0GvHft-KCE&PFy^)?M3TPn`PO|>iYuNG97 zzYS!5W}apoQ+E}Z1Kl9|(ZUj4-dT7P+8r<-4u<^QQ_j^6`0bK);CZ#wm$PWO`1vr7 zJ;u1?PBaO&rnQxuXLg6`;*Hfms%!Dt_hzx7kbWuA_EKN=hbHp2QGOXTqfZx~4BLpc zgkNA6_3Vw`hnMB0f6if_0rkXQ!h5bR&g|FO3o42|cv!jk&Eqb-jGjB(dt$>Od)vXD zcO!1DZ1#tf=u<0;^}mjvJz{04xMw!k-p|E4>XV!yojmsl{)gg|<>lY2&RI~Eo_(#J z^4Tv&;@jj*!u{|g_;|=(UR!Pzs7=p4FwQf&D%)S|CD@9-3QmO`#1F(f(;I4UCw|8C zZ28yHABEBI5bXop88=&N?rPUV{EL+PLAmrr?z4wMbNV{y1^e>%7Q2Bx7T4ncrOYe* z)A0VZ;fX=&JCxQHD&&2N2g0Fp=E8p7BcD(5{@d}hUM^Rr4m)$_C-x7JvT4J)w9RmB z`Tg-*c(S&RS5MaIAmz`aub{t5+m7FYegtHmlpuAU!0#mI4ftNpDYzSc1|KN)q&m}g z)8!rlE1)_(_s-lec9oasXRPM&FT}Uda$m|`(4C#J&lqPM?t!f!carrL#awEn{*Txd z@((W+cao0c@AC6Grm^yWDwFRHVEge{joxw{H5};w?0T8EO!h(mzMk2 zc6bY@DSmgCVoqf5ct)%}G-m%7f3Mu`>|uGY`(ZS^qmF%Oe?Zplrceo8l=~ZMmGz69-|)ZTH?f>ukBX(- zk?e)A9ER}^f${8<@d|tkeY=aD zgLmeiy{eeQugJ|@%N%_`Jat^a&NFA0i{BP*VXqQ9P+fn>&pG}qZGiYr$}WZWVz<$9 z=Kdg_e#v}3MC?NPllWCU7pjQehtt2=lOATbgE!@@#*Jy0z<$cNT2q{5^~7!!>j;^{ zXNYIrWdDCcY@=vUL;j`8?Mxe323j*11AoyoC-S+b18yZ}8LeYiXV(94JG~2x_nsN+ zjl?(QXFZ)#D$c^#*)K9S>Gyg#&wZGl{<{>elRJ~YQav;91N?jzI0%2)r8vvl^79Vu z%f)vGSqtO&dF}xASoV(W-NipvN4|qhpJgBU1OKI-4)peLD?jb7WFLq>fFs~g&;Oug zZ9(SEj-FW`|5`Q|#Qw(D@GI1r^|PG)7#;;*sq12XP~z{4@AS;m<$ezNeRfk??iAnS z7v!HM?_K^Q?BVz!`Y_0PpFq1pd@5wl?L^O6RG(iPuhpKbD~iuHSsVYMWltVP+sM1G zgj?j+6T4Y#4LkEP&&^$_D!)5y294xCjh`y{%&eVH%iEH+G5;d^P=4N_5oAv63whtH zr#I!a=BJP6v(x6I=-1)T`Jc$kxbFu`Xv?4%{XzP{_-$NWett{JT+JANXpgFa|E)~= ztD+tb#D*X#(j-;iIZ`tRR z+#C5fLB5yWL|s48vS-wUN93GTc81}{_?b7^r{0&}iTytO4Wq@U(3i3wW6w9%7x3?A z*HLHI?ipg$Xqg`)VGJ$(+mD?&m-6Y$^jqf1wXj~<24$aN#H-OWmJ9eN;;yuRymKS= zROm*pgEz*R%iFUj;(R8^+WiEE!OrlZx=y0!Zhkrcgi|C#5x*>{g)&%o*9r=TP^@4X3}2{mcitIlJ;0GGq-v>uRq&xPv!+B@doFMN;o z0IjON>xJ{#AbUYRPai7QOnWk>*#nn~XRb`*UjW%RTi_{j8;a*U*1Pz5*Y&X4bJEVP z?3-}f`aCUjFlWvr$ewa7?hXstY0uH@tiO&p}ea|kCJz+ zDBAcrKXYvs|8`~f;a?+uF&v|<*&|M&AItv;*22oQo~1uNEjuRRLRwXPlsYmOci>+E zxkuhk%Y3_8UHQ&p5A}5xI~zLEPc0X7G;91l{*LtY&F=V9ej8jNC-d_K{v>`=&pw}j zCI2`6o;dqg_OjR5_d(WP%4ZB_;h}R2WKv4UA|JOPPmRX4Id}}4Sn86yecjA{|A0&->vMW>?@Vay6+2b z&@WMLJNzYXFXsvRw$K}f)3Z;#iC6ItVqd~edy_W^XYS@)+XY6lH=|z-f5=@;%bY3m z@4-Lww}$k`#^P<+HMRL3eVe;!=I{2}w;CTJrwwEcZOwmE{*mm}a$B?O(&E1j+1qZT zWlr>EAFqxB=q(b19?+mvoI`iAH)p4R7qM?w-zT1(`SaqcVn2J8opbab7{z|0i#p{T zfe%&YK>BH((OK+Q{0!|JcJ|8?rUhn;4eWL}zf~+;SZpA(i-wT-wuhYL!?mT5?@>9>t@CvP&+-mA*4h?7nXnk?^ zms+J_-JHgM05aFFQ^%)k^p83};Ai~af(Gi@Ntygz;2g2~&_-P&@V;` z=UJcO>=#RvS z`EemmUrwdp%069Q*4dq&S(~3Z(1D+N`|!_|`#b&_XKm)Q#EW8A!3sDTG9ELa8vAFQ zSby){9v{ljoXuy7_545KZqNEmJnxtOe5+ink8kMLdB&&wt}r$_Kl|^qFcBVu+u(Dt zw0#i!WyoD(4c<<@zpE#6e*-PQO+1E|ish_5OS$RzxGqJTvacS)&zMexUBxq=@33zb zOFieSFMCVclJ>qUCu8~){|T{M@fheqpDx~3?o#}QoV@cq_!(|0dlpQFZu~M#f_wOz zK_7LE;~xNDLhh|q)RlUZHy*B+vko_<=b2}*^WK>Y8?pDIFQbnsI}`bX@CRaZaQfm> zTE=)CoWO2I|5p4q^=6H%;XjNY#`)eO-@)t;FUa{XeHK1REcdG1&!#}eD`)lTcrny~ zj9L0oRT1=aW_Zd{_1&9;JPC^gz z^Vu`^?}PZi!TY7+bIGsDWKAaiGOb!y^9+AY+nQDd&xPy*E9nzqM>(gcGw+u(U=_P# zx%iAwUzwZ@heJ=XAN*~|PPh}K{(i8w?3w!I4E!NK^SCQs9zU)BoMro8!cu z(GQoiw}pS;73H4BElcid+H|~}w`ke>G8ekwyxS1?408W`guWI&WIs!rs9eUs6Wjrd z;8o?e!8^;@5@$ZYL7RcQmx_DRE&K)Sfspg59zGrqhnLtpi65(cTYhVOlKHu5;$mCj z+Mb*9qJ@6Qx%IL5GJGCAbLnn5RrSGac3b*$a13p8d;u+I=2~|4q1t#iXhXk9A2pIU zf?b6@8aJ2wc|~z1zb7_{o$rt<#Bv9?OxcvloXWnRKFWUl7v2-bm+d+7Hxl~;cA<^M z!=N?$Iq}2bNB$wOxjJjo=hMD+*5({JjlT)x??aEk8K<|Qq4xKbS5N)x`D4Vg=F`8e z=%lBo?@x}bt<-Ebpe9QXEUG)a`Z;&%-W!cz6XSj}bA!JSN z$sa3TUtN>;T9E$i54*85Uo!V@f&86A z6?(qII)*z{(R-$$E!-(vAWo^=kmAU=bRbB-b~Kc>^kg) z^z4<3)iINuy(;@d{{6_W<<(ka4B-&5b^N~c%!Nw!qmZ?f-|$`(p9N1>7(+akHVxlc z_E`aLr|$#VS2BOHmYUFag`dpXyKwgP+_x69Z&tno`xEv7P+8I^@C;->$$hAy_`}-W z8>cU`_pHFVr~JjPP0#loFU#%2Pdzoncfxn#OK{fhP<)r1WjOodv&!|hPfW$>|IFQ` z>PeaS>EBn`Y16*+oB36guZ3?i4&&$tLM6X}vRS(ii)U=}+u2t*`)5<}8u(VHXS}!*QM$mBqwd}K)Xg> z75s&~GnJc4&l=ukO>uAOC7%5>^Srm5%$4Eg;ttu0c7}LepP@g+RiK_&d-^~409+0K zfX@`009E-XLe}I+=v}f$;>^brwKsF>EYHq5dRKc+7u!;~j8!|a?4u3w4Dng)7SZ8y z{&Mu3b3z00Dw}*}RfAHJl$NBFm--4h1Je}5${VD#PpZ@E} z&v#FA*e9v?1+gvIgYkE8t=QZAQ{WnH&wp=xEB0cs?A48NUH-PpM%#jx_sqUFM}Fo> zBie7;zK%VJmc8cca}ixWBsIpuZ1$%E>)s3jIrZHU7QytKmVIPfr_i|JstD{UF~D@VS9CFEzYOc;CJ!dz4-g! zjVp|Y_!VL!#Qp_O!uz!BtE1sMIeWt;^hITR0i-V;!Rd=L@iJUXnXmD7aH4Y0DRX@1 zVh=h?&J_9&_;_d}p7k*izT_XK-G?i`l)WwOXz}y;IYYCDwN_UHoX@gPu%Cjq{0reQ zx&M@k`%43Shq`her!6(a+rV3Lmf*~-t?56*75tnbV<7LofIkGs{+>3Ne*n~hiz$KJEVdDGB&lrqlxAg3q{Ayi`K3T)goXq|88N5=?({i7{t7)@oJFv&W zT5T_j<=v{&b9bHs-+5jW^-gAQjK_;*{oDcXn6vqPx~007;e*A#W@lYyZ|KaPfscYM zl)Z`mJMB;Yo0UbI^Ico!ZuaDfFpi&h{$9>-yoxpxpTxgR-h1q$Y1v;lQfD)I*58w2 z*?(V#x7q2_k$3{W2xkm)pRC1xg`NCQaXWQo>~jaq*i~b<6x)v83HRqW!gup8$0y)H z_;C6ZWip=zu=n-;HMJ@AFN7;;>9eQw-wb~0$h~+RtfLR%--;W-#n4baopAbmDZ4S= z6#iY(HtkBidEZ}^&zedb^IgmX{K@QHAnV{svCnC_qjs+>*8d<{>a8Z{UfMeRllC4( zds<%Fn)hi7Yx%A1dx_n}&OQ6WQt=sPp0e?CMyEaboA*4sF07NAvCbUKXS}X*Uxb5b zJ>f!jTS(v4fUL>Q=~+{out%}0Dm#(=jJBokpO-&Oe4$uV&+UNM(_7=s_#2CFsm!Ic zT_O9%5wJmgEJd<$qLJoW;kNw3EHF z_yhVs>vMBCcf)qto$(tc=T6vDZ09xF3)9$H7ny^bd1l^Y7Q3$47Uv z{Tli^@D2SI+zfB(&UO^eym^qe8+=Nerrega&)^8!gO$a1%V|d)T5o(AUPgZga>r?; z9huJ=zx&Jj$vgbU?#s^D-vc8d^Y|D%73X`6-|#Qq{dD|MMREVm*|Ho?k(YfV^^a#C z%}%@j#m;@UH$81Si=Ht(0gj|Cm6J1f&r*?}bF?FWGk(UdFJwQQgr9}aA;7{wjJ5`;S-fjonP_CiIv#bdhVw5%$2w4H_>0BzXkIk z^K~0JrG!`M(r$In^1R)k8asPI=5a6hl{SfA*|~V{e73lmpXcQB@ki`A?5lD1 z;{Dm_!?Zo+b57mMUT+`VOWmhyPtMJJF04b}Uwv5%8P|?-Yty2qZ(iY_04>?6yQFLj zxp%SiUU!SFhn#c!D}NxQ{duqKtvAuPheova^0H?%Q*Hn|{m}=PXm4v2d}H^XMQzp--dr20jGufkQM?qC~*3j3`RJrk8in-XIev9~I`m?yi|BRjWcP)EUSOl4` zGw_IVaklQG{#p9weSYdb2>%B%SF6eW7%pVrUozj-cNEV0$~n@N{tCYm@*Ttvw8gZ{ zfjldFRKC-_6K75A3co?l$E>BH^sK|&6PqcYGw*tKb@Ag%#T{{b{(QJVEPLBM?5Q|y zeGljPjrG9@{JNa=P>20mx!AL=lasN%i+%*Y7A}THP{(tcKt3bippM+hYnR-&x)$e8 zo|7@n*z6B!|3R=?UAL9>k$7{tJ^2&)*@rUczbjQS0QO~iv@z@(@Fo1l%B*B(AGp_i z-U+XUIr?cY&wrp~-K9=>Y1gUz0m@w<=S^6{&l*mjUxODy&Yy$vKw2BQ-{58ZOK~rJ zvU*3*^P5)Mm1kx@=npO63$gpWf7V)uQt=so6#G7UeuKTOqNwvucGf{3u@7kZE?_i2 zf4`8iz5!07b)k*N?fB!Mw|MsF-tZ7V^C|me_O6TAcd#cw#&=VG=4|$hhiKR0clFZ} z&(2uhXYCEf*)y&d+fL5gIPX=hT&&$a*gc^!f4RJk@EUpfP3uT@*3FjUx55kRJ(~VK zeBm73AT|aTLR0ZaA?s+La&ZstFaJ??)_Cq!r?OYdttW2@oD661Gwz48yR$PN-;zHE zKc&nww0w>~8g7#J1{_V#-nOlr9{lEb4?K?c7yJa@diO2ppR-rUpSy3-kEhTwAF}4N zpSKimh~Lt0S;H;qIUjDOW!{}6{x!cN`%JO?zxp!oGFA^L(~Z6}^piJ?o_(suYHO6= zlb&+ff4Yf{gEPg`AJy48tM6go3h&UZGN(ZYM@j{MC&Ai1U`0KHh$RbI$zFK9hdQ+#Ig#Q2wd>5&EJE&i*)=eg-~9ev`6wZqBBUIsp*+6KG%3 zK9N-gr6x0vHA9 z*L*iVD=~Zv?cd7Zi918~qFykTeG@(Xn!dl2Hm_3q@kZ(#fp5d9?|f)fa+i^NF&x3J zD)$Tam!46V--^A2-2m?^za4& zxr@)&koDb{{y5%>{yl9edy@P-XQ6qtJ3C{Y_e=j&<8RE)GqZ1A#{Pmm7Mhie4ebQl z{l?)f_F#4^v0L$$Fpu51QV%j9j&UdZ0m9+A14anD(F zjaUt+UM}YPPu}+vdhQSF*+O_K}VFX>-oDQF6Az!*JTL z3uGSe&R;F(Iezr4$0q!$xC>5S?FQSH+!Y}AuP@;%NPW}Hhav2b<#exfx0UlIy?vMB zbNxPI8(^~dZ}iNO)c+3jgRJ>`eRK3+rb zOmEH4TI|ceA4br!AHKz|E0%fPg8jEXyo7!$zbigPY+w9?SYPd!&(D6hj<$?Fm;I=B zJWc*2+6Mm3>@i|FZ)&mAFDJr~vb80iz3>rjNk42*=LG)ZmBsg9cZ=_~rWn_g*;(5S zo^XFtj~ z)Rc1^{}h~m&R5?z`xW0a@5Qc9-wM)43*}9Ot?2Dwj`GiI-UaNP5uJkeevJa`;b^yTIOW_`I^>OJafN7 zEO(mOV)tre)?f?Bb5`P<6`RS)-^@Ks%bq$JMzQm~{yJs8}@Sz5c?C_esa!*lV~U6A0TVzHP79QeGYVk{*b-s z1GueRj6qjUyG-} z6BYKVQgJ80o&TwOdwqWHj(72MZnRcM6=hcN`@`AF{OB1+%F7(iddz&eo!<(U@+*~T zs_bw`UoWRU2ziHm7u^!q=65pYspCI#uBX+hEXF8x*Q5O^cPaZP`VIW7p{$L}!x7Q7 zV<+}d+T}2wUtP|gv|f;Tbu9i7r%$rZo@Zz6EKv8~_&7-aO;q+|@!SiB;>P@a@C>mH zrQ)2<__pECQ|3Ccw0$>xD!q4?;`h=k#JkcC!A;lc7Z66?W!$oCh=@N+M^ z4xdfS-dqj(v70N?jNU~4lXxAx1daK7iRbwfaO#*@S@c(PS_5cI&wFRyj}tpcUH8#e zuuJ$W`qSd0VKqPZ$lSN`?2N_Xv>o9JxR^F2`Ql61cfj#-ZlXPC{Ce?g%gY?E13AMc z(0;(_i_P)Q;`iY};#vR8*q!k8IOl8jw?_CEXh!dXhvE`}@tqt{?}Wv|fBS@#Xh?L~Mx{V&M8Iu5p`ZH=GstOdrr8ha0GF!!7> z&|B{2o_!NL^X4&mS+kkT53PlT$K z-dWCe`f~3s#k$M8+%IQ7PTpg3(jQ0jNAYvkq>r*6WL`C=eWmSp(K2Vx#|Pq^d+F!2 zeJ+20b|24pf0aD34bYl?9%NjyH{A#q(qDjc<&F{CihXCLJqKqW9wDbUq~9Lp*OoI( zY)k(AIO{cQ<80-p(=z@u%W`%-=FzinCsBqz%E@*M`Gr$oa7r2FiO5 zpW$8mv8S;!wwVh<*-h13Q~8tG=gJvCp9|MQ{&wUKxrud?b1x*8b(%eI6Fg4rIlMW) zmoslB|4n}0eL3xS&wQOWl0RSlN3w^j_g#MW*jMn0wC*^2(Mt9m^j-L;l#8=!3I9c9 zK2ldz`~ZJPI9EW@=obx$n>~5vv?vlBib2@V)^|r%}#lMnY6Q|!Y z*ZY^{Kyx{D;SuZSA@TI{&Eo0f>{|!n%>As-jLH7Av^#TkfBBD!m+)v5_s!C4QN%iBP|5bq0(Xith~uIB%R za+}!p^pE(*swe9;`%CtY%<=5KGid+tr;De)@9FK!#do`H*%RnH@c-|uF@J88lRn8D zXwJ^(>6`<{^6#cEXWx(8@MpuiRaO4)Gse`4;Z!@Y+A}>-YnwJ zWiL|CAozy8C(f9DiThU+?>$1^II)c78}yEl@33+{ok{P{-&t%542F4P8|+IN%hO<) zyjSI%RWA1YO8j41#(5igf3lBL=G)cAlfAp#@AmZ^_T9AK@eEoAe4@Opm($pr!Yp*-zrx z(3!m_R8|(}b^2;NysO>Uia!E_`Nxaz%f7Ny+%N9qS7ldIXWH;Bq%Zr>HpREg-AMa7 z;p|2GiT?zR;3j_dvw`yNRzBaff1S?G+<6B#!;K+r z&RA`MGjCIG>ymX`;eLsqgGq1=98X&f3*^0t|Hhg7@8NpAowBd2;QxW|;Qy=S9>jkaZm%>3{ETg1@kjW7nD;r4 z-e9kRyDEzB*;dHOo^u{P6!#MQL99KjgPm#n(3-+3{$kJC0Z*d8f-?`(zFQ&t*sbPg z)=K8+2sxP-uhCDXZAqIf|2aGXSHX?Ne-v-TehseYw-S5NoSRC|xD2Bwckha#ZB=Q% z^M99@XJkC)zykgn+_8%_hONCgL%;t=R53`{y0_ zKlJ5g&tngPd62b|ef>Fh_J()FXURVs=d;gZI2ra4e~C5}>hRaDF3#)Bz4XI!xJ~RF zdLMS$c{SW4ehB|_JV&_)*pJgc;@`&3*`B|to64R^YmR5(f62)lo#nkxRjxHZW0JMf z6`HX7LJ4~Er>W;R<*tTFP=(%x{uldh_F@=cS@hd>;>1 zWWGEl=Tq9D?5x=myE|MXzB8Q04rd;;gQF^nJL11f=8V{HkTIJmmj1pUKO{DR_C7mh zwrB4GDO;C+F21r-f3WM6iurma`*r&E_$FG;p0w{-+80pf*X8G(TS03`fBX$ipi&>5 zQ)#c1m+y&xruAZP1^w7t<66*PY&X12-)~zgK67RcZlKMBE7Y+UTnit=&9q%0cbhhN z0A$Xzf$Z_k@Wo=sz+UY1!7}d$I;Wy%-id6_O}P|U-%AM z>M>I{XK1HiRX#WDHnZ~{yK@4z4cYd8tuWa39Vp?=cbL> zf43Cdk9|8t+mV(zf3asw!}X)nCMq+8e;Z`1vW|Bb+gxlKdkw7xKjZUXc$c;>96;Mt zPBoa!e+zQ{yjoFwM|`t*J`e4P>(MWvr`@f)xHrL0?DX+HkhQUYMKM1w!qt@7m)23N z0sAGKwcH=7h*#oIXz}x28PhY=c{+QT_>269@i<7^ey2BtaV33%U(>GI_+tJjIoaR0 zg46lSp$ZHt7w7$>a%;$WTzn0Tr9X>v=3eKSb)hG1G5nYQUWNLgCjT~lojKY-ZuXML zU{m?`R=VHAfKo9(d$7~5|JPUISJ7MG{`gq+WDb49KVQyh{^E*a+}Gm@u};w6e3{Q* zz)t&fHsyV@HZpJ4<3)H#*}Z;s@w^x1SLJ7(Wd0q2w}VsUO@QX??D12?UgtlJ58~(Z zXwJ90X!$HZjy72=Yo`wTOZMKS;>>&;UrEb2FQxCoe-md9JP>*-KZln8ultkq^kvSf z+O#(z{^9h&umHB<53~o3uPnylOga0|(w8|;+d{_kaDFXl!rn@`tdq>=oWT>=w?fu; z`YO-Qdtc7)iH|N9_l>W$Eq^zCC~dqtGA|zzn=kJ)_C?y6wsoLw&foWc*hTzGXhqLn zFb`iUFa4eUI{VlVSfu_(XlK$|^Lz4Z@z3DrzO%8i-RMuVv!C|IUHKd6=T;VNPMu$| z4}kvg9Y1p`W8BF6+g6#K*@xjf#M16<#n-X(U1VK+IjxU)^l$ikv3G^6@r>n_@N5@j zN;?a(cdl19^YCbRNNg58XW>q;E$w*zpOwXZEo*HsWG&Ui4PmBO`v2;Z_r&*BVERg& zG0HeJfxpFC;_N*a(`Q4@k~}x-X(qIS)4c0@>Tf9b812~xXOHNM7xI_LS;g;-n?omP z0QozS&N!cEH#bs>~$Z& z|DTKbJtX~+GifTksjZ8|^Bv?k{z!K29A)5D!QxWU&l_m_Dt9eE zeYu{UHpH)0F23hVTmA)^GnWuBzZr}Oh&OTnml%RdI+Rx0}HZT>#&><5oQ)@)5^55wu_;rbQDoqmaO3s)EC*)VpVn=_?@XFmaR z*llS&mH7wGVduP=PQR1iAJ>!j4E({~UF>YAQZCNq?0ZY)9gg2xtuNT$!cB6g$orJt z9qNmxuUf!!73##7LVnLbhQCsu?|^4|PR^`0Vy7vyBF}|2{H@iOedaoL`gJM)BDq;p zO_lG48$&X{2j~1Gb(7c*(Jz4|F=3$ zpylp&2;NaW*?-cWU0@IRN6ww4V(j+e=N|rxb~IA&$zu8LD0Ac$cG~zEehXhl`_%Ju z?v3HE;;%0kf2;Te|7UrZ;clLJGCkv%IdnMfL1p@gzr#M-Z&h2tv z?o#|s!M0-Q%j~Poan{-2@CG~kaYuS5IZg1T{I~H>%091-e*BcHiL*yMNYC1vDYiRK zpHCCd|GRBAKl7nI|8m#?vcG08=t$4}&ivn&f4tZ^-r;LncYYff%$|Vzigm=R_?g@N z`I|#cxBxDtPf~VBx%i%;L#59$`s82yVeE|2S=yQPa00s*?H$iepJfbgqMwE9Bu6ak zWE&VF=L39(*hX-?yzIL-R}}Z`JJ`8%MMd%XvWzFI=P+7Bv4P61z`Lq%Kl~9q2g53g&w-z@ zo6EfpvOkZ``^vqLA7pP{ZjbMU)3@DeufkAh4cjR*XO;CVR+oLA=luowf49{TA5YKN zRlpI@L{1IdM^5&r+3eMLU!49qPW)~BG2XRQJpV0zfBvc3b^z^AWv@`?b{Ho1yx4^} z>wOCQCAdmn=IgGI_sd%SOnd_SbLCeV`?L6&b9=zi?7a75kemgOc{CVr<=jcV`K(cw zpL1}_)y2NKKYKk)5Px0#5b>?}&2aXucJ!>(oU!pUCR1@u_O@4PJxj$to9}=}va7@U=6(-3 zJt1>1>+1{gR_vo;0s9KPaoO0h(}!u_uk3uUyS;k*DU-h~&iFSFTY*Q>w`6DD?}-1D zn|ZQ1KXc?lcIHXOB74S0aI>6@&oF3Ivd4>syU|}#e+T-R{3`fdd<#AAbO-KF+l`$w zXE)_ul)oI$ru|CWUOao{Wvh$t-R?0~V`!P{Ux}Y4_b~Ph?&d!)*o;@k26@Hes zKW<1rlD2`Ldu}hBd(p|XQR=!#PIY>o_qlfD{qLaVcajnGjoFvb@?P0H>anxdj)qU* z1(*+WDvP`GUGg8{uY~kt*7Omy)$H}k&xSAA)1WImXTmAsDc2mP$)6$C3hyJ{9M06P zduStRS!-MKbADu<+zUG?cOU(o%HsT=&d#2fGI^i$|BL*Q_$q$p-<9ml)xBUpv8>NM z6QgIIUxCwqm*b3m#&$g9ZydIyZ3B(O@74ai>mc@ywDJ7(WybJw+Hl%N>}}Ce;g4RTJvBPxsUR&HWv-sKrh z@m?^oRD740@y=K!_5d8mzMpm`-h^MDoi&>|^git&@3{l}EB4*6n!OO`z4nm5lz%Pz z3AhVZLi(g`g}qiinSU$g+=ufS{1*0i_J~{f$5y)6vU|(VyS`4lnU*m=Dnn17{6%!-pvUwDNhc>|eui z=H1t{75olRhV)Ax`mJ~Z^r$HAFZ+tE=p+z$ibX6Q%D9(+z^@!ePET`T@~+PN8f*HSTGw-N8m&)hgl zd@=h@vCN@A*p2Xx{QQmA_R3DCWgi_+dy+o@KTON~9t&4O{(az9^eXI|arUX~Z~gdH z`9CSw3i27I9j!$d&zJKGdyc#oays#s(y!-d{rn=9xtlrA5;nkf%IA*XRP14~Pa)rl zJgaQ()(!aY^K0UcrJ~)9@ocg2v_F;GnV)z3gtj;PBie89DLeBx{qQ0GN$AbLlm7;L zJ>1OB*|<;i%3|N`OzX;SNb9O?2k^7D_QN^b8?m$QmRA(*Y>ZFgSEpy4w}39}F}P>R zn#VWc>!F&nHD`M6SeX~~Anm%mOYvQ6cf7H(FGBh```p#yP1$|%HqhSv^>qFZ?7Um% zel>o%()i**>aIiEA9kTF*MD2k^PWE`ld)dP&e{Ky=QWm>ewfI9RV;I*3ja>!^7*9( z|7p3+#SX`t&@K{RNk5!c7r#N@3s>U$&>6DdPJ#5}JKCH+9tXd8P9I#iT&$aRa=Wk> z;d|+MmrLPd$a`#AVGn0#f4W7^?)+nL4alB)DO|2iJu8Ycy_&pfw8{L7aeZZ@WxbDq zrE)V)2eZ$Rvlyp;A7!_rXD&Xb+)TI*P7wPX>d*(mYN$&80B6k(6?+ZugpV&fBjF+O z5uP)TeJcHC+#1q{O(6S1?iqX1j+L9e@?v&p+C=fbcrkq-Zb{E~qyzCZ$lm&#*v4Yn zSJJ;n;#y);a6SIQisCMH6zzWYuJ8$@f1iR5+BT!IxKB2fvzM{nlim)$DDO;K)>A%v zo&b=|8<7e=1r(Ys|J3Y^x3}4YVp>3!9f8-6IwSqqEm&A6Yf56V)C!8Sf7}^kZ z=4?NQzYG6P_2xbE9mYETA&~d%Bi@sKAZ){*uCC$uAN6&_nNLU32El>s+AxWID{QCE zclmi!tkb z;4+`BMvFIQ*MpCYOV(5RWDTAzKjVJ|q|a*MpYdGi#y-DX++og>mpZS4J?QDD*^qJ1 zXRuG${mR8YIaKcNP~zvkhvE(L2Fu%$owc6zmVR!@pQ=qOwevCY7sS%1ThX&W=RCPb z-V^M>w14=6*%`m=FW2C$VT{-!u^V7l_FH&wcm-C$OgSn4yys-Cr4K%-Ebb6P*{`sl zhYhfk+*)|QiejDLg|}Ar0Qw?&XZ8+ogL*P&7P2#^R{Gp<5MC;$KYdr_Hs()-^utCt z{hQp~Ab(rGIX`pyEPmcAeL9Fe3D@9v!Ar#_vfqG1*dKcD?8o_TI%m@v@^8bt)AEl0 z!kgiG_zrr;tO3q=|Dw!)alS)3T|JNBoB7pC#a_|5viRJPyII!ct+I<8hFEus7}jb^CbRYc6AxJ$IJaay$?J1B>YC-`tn8_U6d_9FLxptKRY2G)(zR#IJ+gslJB0=y%I` z)4BhZ_$^}f>G_RjH~xw2Zg?kp=JrSYtdDBSjAOTE=Y3}ITR|PqdYYaw{g{3Ufua0GtY^i=800LOs!5&<8_kN3b*hvQI5%4^wV7{V0AT z=qvwRJe@XEJZ-*GJsH!qBYVaf{F(GN?BnHLxNk8wr}F3VuaKK}Nq_H7J6Ha0_$0B0 zu#n%2pZ`YZNPgDkgE)2m?D=Pkr{BBN{v-B)`MX?xbABf|SBVXSyvH~^Le87wH{$&v zb2wx9h4`~L>vcH38Je&&pEK?m%Q@_^cmmE{Df?x8_NVO3-DCNC!F+Zve81eR)ztS7 z{;jgO6W3waW^b*1Pr;%5pXFSOKk%Lh;;f%Gcw71ccIHC%x3Qj;XSRbsX(MR)E$0Oo z&pwu({o_h@Zo6pP@CzSHeg14$2IsT>^ib|E>84v9E*d zo!diCTISPL+CP{*mYwxphux3XqiihLztI0xQOud``P+(JK}+9l#J*OUUF0sK-RxPp zi#*KFx~fjg`IYs)81nl-y9(n$%bNR{cA>oWxF5a^pC)!9ey5@s!&}&yk0(OrPF4C( zrDCs0Kkp8+`6u8#@Cf<$vY){(L(Y#*{IoH1Xfggw9f#pn;#u#jyhBs=4z#S*OW_@U z*5Kysec=lB1bihdX0It1ebWx-K9%vw{C%#n_{=g;-cz){)LjjK4f~3>z*!%8_n~s; z(Q+>Ri{Bh)&irKk=AM%^yGU-@bS^u8XK)KUIgRAc4a0Vj+>GN&{I}Tg>?Qbhe7-vV z%if0ltTDQq-&0<4ztXHpBu*Wd87PmdMbo0hfuI6j8|0KGfzC1*DK7Ipo-s<=OA zKIIH(FFshT20Q(hu|9|Y6U?kA`e=z} z_PF}e2U!Pq%bS3Y6wh9ldu&_wD7+BA2xZUcEAP{aV(y;}EC0RzOYr^byA{t6e^vY^IE8j7ZVy?rjo{T%aUR6Ky|S2p-Nm!c_MmkTe+0U~ z1o7i(E5t5h??!tF4ySDbJ3&1g*`<4Tz2Z(n12P7X}{1;#@o@a$1~_9_NF*z z*2!=k|4jAPUQ_HpOKHpHZjE<^tgV`I-mWP2?yQTtP+ja0d>%b#&0u-m_;27plZziW z7k_fLCw=V!mjd}<~)_535A?;tN^KO&Yrd{?ng@S`~EGjsn~b+*^mXXIoI zb8e;o-o-ya4|Qbk%KXW9orkj9dG0Orv&Fxm=Ut}Lb8i~YUPU_+E~3@J@6)=n`@wiv zsGj_{^V8p>*_Tz?JLHY!r>?B2<@g|c8a;c&w#sFk%F2!4=U%W3rqQx*)~qak@2rno zLgvwrID6Y<{0zS*4C8mg*T4ZV-1GOUut&jc{yy*xE%Rcoa=lj%z`F zZwb4|&-$J(KYMIbe*VVtbAHaPjQ3%95UgasjE{s(O2v0MhtV>Id-1REtOMAa!vKEf z#9c7qe;6ruV|v=w5I5D91K>=3ak$K{Ly&5 zI?lok=xy*ce&$d|$T;jn%l`N++yOVx+K6QiEn*_K*m}!cSWnM9%KrPT+!e~Vpj{0+ z$)8kF)IU{h6niZ^FE`_}L>;Txr?M|sN80`=e;58a%73YRRT#+L3$p%pz^{tWRbTo$ zbF+e-wKkZ2o_Ke5*2{zDaK_vCNK47O~21RMA_buefvaw68^_? zs+WrWYCqa~Sj6uJEj)V?z8wy)biVKp=<2sb{3d@LeN*`x!$kH>d^)ZI_pql{7ISJ( zoOL^tb|rqXTzt;UKK7MZYx?n!y{?h?VeBE=)dAP5EYAJ(!7TZ2@t^SgW$dhvjBVEF z5sJ^w$^A-}6%i2uT0 z$oWwGMtL=iarUq4VHUrUSk9LTaGact>8E+82kCz++g_bpv&V|}78{LoHmqgWh5Y{+ zX7b0w1N{6yY0g$p2}bdUz>n;It0SL(GdHu>q`tc$=VSWn91HJ2*2wYn6Xl$y4ZreR@^7vv-aqYm zkbaZ+Ut;N#&BSNWc4ntfhU4k{=CGuqcxL9oboOqLc{B&It|vn?W#+?|mEKFcGrs?b zXRkdO#zFdQH%NPz;Pc_t)B)L>zNKGI%Y2=y53a&H(FfvlwPT8S{@dikX$#nC*G+gJ zq~8vPzVb@yxEm+02Q6!_H}qmZu)0|LW%i}Cb&xirBg`TmK# z&XDKMfXtJJ@Lo`>?C#>ZBgAh>9)A(eUYUF4?ff3_2>;*;^AS(xj}goMoq1Z5b^)#i z)z$S3z8u=gJ5Wyc`fJ!(hX?R8$1~2a!xj98D~tVXXZ9M}Y<>gv4`*kNkA{8A#di`} zuY2R}Maa4IBklXu#Tx&FozIV>mC1Zad&fW*^DTSfR_v$b+=I7L=24i+ zF5^4P#hH_K?a#iGeiD5I-a*-wkhAf6SWr>y;e*Apw~XeWfgABRDi`bEW_J1~bFnvk z&aMHC%j$vk%B+LDLq9ysx@yKgLhcvPj+QetbL2Srzu}+wgT&6`|BSby^<_84+4qLg zvX4)Mx%_wV6zI;*p7UsBG2h1E<2^6srqFBfGsnMR$KRGc8BSv_!jIxjHwtnf?(k8M`?()CB5vxfXsDGETTgbm3r*HGO9-sRT zJ@wRqNsx0QbGiw;Y1y;c^Z!@ZoAR^gWuDZ)nagL$sVlb{-kzR+w>bUyKI}yM7c>+f zfHN<4#jVBi|KZDdmG^kQs~q}m?5F4h#lMq#CGBY1WOf(0KwDF0bGSn61zLxSVvhIW zzXIvQe)LLuXQ&IG!+~ORVQgh_H`s~x9{WA{FXOKGX*>;HlJ^1hWxwm4v!<@%FVesN z#-Gx^hVW7{QhFcu$$tv|9;v!_2$11vyk41pZWYa zdmY|T;m)Sc!*B<@0$(Wi;Zkv?EazV)@8e2iq@L`{3*=pm^S5b;6!xHIia z=n2n?wHF)7-k;qHH-~*`KU8X`cUp$8RcG!$_lY&aufnJp%MEx&prj;$-kq* zyTh5$@ocE9^c^sM6rN(A42NmYdOVS~06zvx%f%U+b$baveb|FYG_H%Utwh z=j{KJJ_B|u6+dsxT4j%}#`%9Q4q)d@cmf|l>x6G`&1mPsc>aO77Tz9SlXED4f4G%B zS8glz92j3-oQ1i+FH6PG&%UHDVK;_m^uezAf;L<5Gltx6_GtPXEPexAz-|axpL6&P z<>lGg1-Ikp`?MQ5$R6J5_KkaT}pGM0& z!CBCqJyhSjvkzux3^}K+#=ppa9`bK$Jf^>#`5VEm;uH9J7ucSkeY8j)H?f<;GH9t? z>M761FWE=K<@|jA+eiEj;}}7!&;F8jp;&KRm7n!YO+SO{(DIz=&3;pCQ+D3fGq?FY zxJm4M#@!aOzYZw(Y?OBh{RBDbZ+rT~^vl@S!AWv=z&Qh}Le7LsA^Risb1h^ZR%(;y z`4{XwoA)Xe??IQj?%!gmjnu&qJPBTrlQU^1j*(-PkhcW8ppVh5W4XX0VhtAJYC*_p|~2Lr&)Ren{SWdd{L6?EUa5;$`gY<qy%Um&rLnEa!UeIrDNL42A5aoH6UzKWg`pd%uJKBtPfCskmy-;@rJk?p6HT z=$VgP|9R-Zz8k8*04RxP9dc$=gX^IutsTx>ZVfq)&nvfI%8Gd?(K5H2!wF)e_HAxLo)P)Jq=lS);8%W4{=xEYh166n$h^+c<`DLEa#H8x z@V0mvZi62bdzw8Rt~HLG_*sKjEB<%J$(bdVHO>0fhEe<##vyRzsjYp{&p0@}b{{NLB;4{up2?)7^975oj$i)Zu8{H9{FrD(ULYrD!*QkJePky4GPsSurriAL>tT5vXbbt( z*m>3+f#>l1$xB_G2P5HDI7|HYN_B&CZX~axesW)>?r$Nq)oXM%P zx7jrz_ud3=1^dA-)+Bp-7`q$1R#w#UL$vn%jUa#e;_SpSc8D?4*gVh=Iyd{0+j{=WPvcpY3P=SqA$yjWqMYTJxf z2P*hkuk#^$HfwPP)aKX6f{mj3v&*%S1Ub_3u(_>F!ey%}_Z-0S_c_sfg3eslM^qc-30Q@4%b z81V}r^?v}~1{%Y$mCjYU?c_FosQ9jbbNWO$3MRnmm9DK_NA@(xzPcIuXg7+U{H*h6 z+WoX=VHrE~elYz*oHZK`Kg-W^`s8x&DzLZQ?6t16-NX;F#`ode=gahM@TOu}rv~ht z2icQ7aaFu2z6igvrg(0&Wq(b}JPySN!KLi)D~j>Ij9+)nq4J&*+l8O?U&uZXCds{l z|Cau`i{)8x9L{ z?5SNDtyal906X6|eZ*dF{r=#;D{riL?y(o7K5AFkV>ok_wcLY!3FQ4MYtvUBv+*La zwsTQKHGL@D0x!|O!q1Cct?iS>@EvV3E!Xct-%p!)><`5c!>Okk z@PX^^iJ!%r&^O1a@gv#QVF>$4+Jlh$%rp5soM%Wi_DQalYjlLaUFTJ?t4qe|dWX~U z{`WTfBl>gVQ)wIenQ-1=rif);Gau9BgXz~o-t9)=T)z>X z!OyykhU!p-J_Wy0QM@~j6mQ8s2GTzNzihdjKiMxr>Ujig4_nfomj4Fy;@^t+}P)c^mJ?4}Zt+!u}C|qpy4M2{0ad&>ycXp8XkPo)LGz zr-@a#Z=5;n17EtYjOi+N&gVstGkiA8k-H7QU8VNy7icTw|F=(m=6^cv67lWfb-8cz zn~80~?#a%%k~3_kG1jA{j`Hq(DeZoC2U;^ZTjG{@0^G^ITl^963vk9Zj-K=8D1P2c z{?*?Ae2v&I^vwGJIb-m~^vrkaWLta-G=yf5b2jHn{5Cjsd#iTiwcCN#3Hq}4rEf&9 zh1=0q(=O+K?YgP6#r$EA^CGqQmm0W+zN_4vof&Uav5WBoIQt~$#_g~%ZRP*QpE0%( zzlok2PCbpGjnm&J<|(zhA8$mqVhwvbqi7S|Q-!>3wHb!n>Sw;3)Zz$8jodEwE3H3&4bIP|vKGJd z^Zm?8;_pFYe)h|@PzKB7%%WWm=feJSw!*8#`rwE8eId`=yo+AXzLtFojD-zhgUaH0 z^`Y3>isFpQzD&Ft^q|kdwQBG)OdS#6Z|^< z?`J1=>hMjd1}BympCz7WKVB;KZ5?**p^G+mvIoKvc{3nqQ`YJ-c!z%H_}|KV0%sn&;UV&}=5xj8Ru;cAHkyC2F+50nTHeex z#hHI6e?I>!+*Qu0IRAJ0_p}e#9caDqP;K+PZ^s_4&)#?uj4v;0wjC{>SGMCfg>LM9 z#0NoLv76w8lKJF6Czf*|b6y{wmA^{g`5tH^@q=hJD~tUymA?c154fM*k)H3dFV|)g zjN*^Qsm0XVC%7?O0J-LI+SDx-`*{)n9Jvod&ZI4HFW0>jXKx=Rp7l8bX7Z=w?Acx5 z32U>=7!IV*lRu625Wfx{Nt;Q_eE;`e*{RK~#q#r`JaaSWTdys?3$871Bigxg4udVc z3w8**uhdKn{r)8Xd;BLqwb~eWh7Rm2wf_X?oXooJi~phBjZ<&+;W>CuY-jq<#*jJ6 z^>Rk$`7sIhq1DFCXvgBSaO(a_sLjs2|WYt z4w~bI@*Zbrefo*t&i|VKg4hmlG3`wFRs3Ln_QrZSJ@AJ3K)gq#eITBnqyB)$^9Sf- zw0NFnts(pRDY1q)b^E*A%=5M7-oL`MoD+G@~7ZLY4*$bGdu;3mVb)&+rwlz@6%4J zP+$D)t1hsN_5sdb&2!;rNZS>V+N)Oazj_xYF4J z{oU`Y_RAk~Z&uTbacUrE{c&=?G1lgC^3Iq0I+T7cJMV!xBfgbCou67df|k8;yu8$Q zp1m{hWbr|`f64y9d*QQOb1wT$u~qtbqRjmBPZZ1ec6Z(O+HYk{*NRUR`yJ<=--Tbb zFVV6O7V(F}>lMxmZI6M}?j!u{ud5+v#A&$}jA!Q=+7urxb|Je;Ne#GW_R|aWWzdJ+ z7REqhIq7GCapt||aq%{gntK_3gRFn*;!k6HOm6O{p6f+#s_!9qLwzmNel-4%+%h@+ zAu9@k)K|h|lA10{6vp>-0Nd9Pk_SG%!znnb__JpUzp1?UP7m5wlUuyp$`hGZPMN9GA%R#iMwCveO zXoul`t~m<-tZmllZ8tCa&=*?G*NX;;ES$v^f67GJTZsKpiRx1cFKbJiR>!y;IpHl^Zw z*NpWJ<2emarkAw;l7Aw9g!cLVX%Kw}e&+Z_+R3&)9jd7%1;9v9)k8 zq&8dWa{!(!XLmWxah_*a;Qx*p?`EvkqJHGbfv}`?7OZp2g2zJ4R0CHSYarEx^8h+i9eQO`)x|h>#z$;)q z`*nG{u(yM({Rr=pnWMACa*lot=dvFV&-Z|*h&>8f^A90o*oJ?rSnls*+=AYLy;z&S z#nZkq`!(9xIBPUm&QN;R;XT?T?9bUHd@o#DQS61BXGa@T<~!@t7;c1fX!+fi+S;BX zKWB0qIj!(Q{4(T>%`^5VJX+4v?0Mq%8{;E5_q3dTv-tgDsfCBwS+}ggdiHSk_wu{3 zKlq<^1nmX()i9L)y!dZ;W~Fo9d49KhJA%KJyr1RNhb{Se-_PH%X~7Q862DJw_C!AW zyaNs8rdCpS7ts&JV`zieBiUcce^@;CnYp+Qz7t;!pF$&g_Rrt=T7K_JbB-I}jkP_T zUdGP4eIjo=s3M+swEFCM^h4Q0x%g`SZsKLy@6XOT@F?u8k36T3qgCZ!gFEn38-GB~g$wxKLe@CX<4xc( z*IOjN8sxoUihj3}dk5@V?!5{&XP+l`752f$eF{VT;npa zGub)khtsQv>8)|bIYnRjvjh8edg^MXye8Vr6}yt1In3C;;Lp~sH*_&a8;kwIeggIr zA5$vc<=^J#`it=E<;B>tUjM@Wa{h!-;`MN9b^!Yr{VkQ7ep=8bLmT61PuoiUte`J; z{hGLasrVe-4gW_x>pF?v3BswD?(miT=S%KMKR4mq^!cvb2l)%wOQEy8=J-&0Cuqyx z5>h`San6jXxOb^IM>oZ5jpsx0tZBw_l6dYn?+pF$Vw}DDf$Pi>|Cil`zJxuRoiqAX zd=EaI{vbPbaR^L?G4P$S*O1>6wvqE6ePtidgCYEt@E$wQ_dn#e(B=yEW9G2~zY4oS z$(crPiLa8I^FIIH#SQxEBxf9LjrhO(gYY}@UX$|y{Zam{a_@xP=W_lw;#s%-aekLL z`z8DRA?@Fiw?y6#aH!a9d0RtjvAUe=*qPfIP#@~iuPGJZQB`O&M<3((`TIHR#XhI) zN4rq&N%XqVj{T0k+J&7tuZ5S(+Z7+6%?s?*b@or{Y9l#$UdH|so+&S`oxSr6e>(qY zIY;no;kC5fPxe#`xZGL(oiQ{NJBXilS&tR&??k)^ABn3~sv$oI%6_{~>8mzHrr8xJ{LAwpvb=YHI zVo!627ttHbo5;@kW$!kCmm%}<6a6OWEGK7X)~^LU&&G3LUv|!p%ujWkc3I~|&{u9P z7*2Z{ABFYudGB z?}w{voAsK~-8q5>(I?Y(({7&%a|Y>WEi|A%!#`N85_0`(=(D4VO{BFeci!=Tfo}ZU z@U!@Edg}8r{I%RE_!E93@tN#5@Fbl4DfAk zptaZq#(p=gK0jm4erby9((VxdjsKjSH*pz#6ulRFEWR3F1&!n#2W`bW@=s&$%-&JX zP`o8xtdDQmsh!+cU)UiK%Q-$4^4WTa%HkREn7lFY6m0?SPS4o-;|H8cKbCu65xYdc zhe21dtYyyWd2;T+PuEZ8Zd=dzoFOwRis$z-+KzG- z;&*A8+tmEua#Fjgp`0D%w08Un?Co)$4b9+M@$A)e*;m0c?CkBFr+wJ<#7EG3<9sH| zIQ#RnSF;8)@a}8WKP~rCgTFs~%3grSR~GyB0P&^#FYtS6DDNgiv}-|YCT9-hz4J4A z2Rse(uGD~@>sOJx2fi8aSW#SinYQ`abbVYGa&~PdXQ6nlO7A7?7vNBS_Q#R5d{>xf zP0q%g8EwUG)#q38vX-g)E9l?AzU&_I7KooB-j07N{u0j=p8{vWr)B!0JphkG7h3A) z5%zd(=T;VD%pBCAub}S^Gogmq3AB~+1|^raH~%>LIM^F1>6y3em&|qU>n3?;$a@>- zT)vpT4}E)f>h%nq`?(Ri%UeymUSFw!d)W`b6Z|t_7g|l6dwrLlH5^dp8O$$dcfuLh zQIL0lTjgZGyy2R;*UbIa^sLK1u!Y!r_-nXQtONc5YU=Xx_zv|4ePthJPrpiU%}<@KXD^n& zR(}3Xq>X9SY1`sC^lR7HYwRBEyR^wUn!lI&kDNx@EQc4^*)Lzn9VVW$r2)QCEMv?( zyoGyem-DbQdrN#a{Q_9TpIB*c(()ZjzQ1ZkZ%_M9>{R~m_-aV~lpxo99#Z39)3Yxo zxPEv#JPiA4|6)aPj$SA426j8zEn=y`N%lxPcJ|Vw7v#D4;7!B*_d;QPP z{O!r#s8qa@W-q@X_65#9N&nebIa_K$>TH#B*<@p1TJ$hq_j|7d(5lvft-Mw!1}{1?iK&q!6-sm-JK**B@V zJiiXd%lL=Wx8UcR`HWky()*EE8~#7mZiRU6b-vsV_${%0#E#_uBsN!@sqE&oD_|7t z1i7CT^xR|q9pBNF&Tv>74c?^hiT9It6?;6cE#6mub>yGSe^LMY&@M8Lk#cj5x5cZ< z&-1twZY{oyy*qp&FZGdY*Mp-gi}mXBzqUK^*N7h^mYPYu-;amW2I1V#!KUD>(PCR%{4+1AXmQQJiyg`3oV>oI{O$c3JUR=}Z36@=t~3@^9kb zQ&H5&4*Z;R*TVpM?xQn%S6o#r_q`qaFWQB$F~2cAHMFmBEOCtmu9dnUPTxgN52(&x zEhp>U8lGoo&yLh4^_6=||38W4S#XYcYNtM}34R7=e?Lv%m;XM_K0nL#Qg2U-J%S&E zQ%c3zG7{(P$lgeeoGkYW{v*bhd+trk9spu&3C`xQF;ec53(@?dItFDf$f8%$ghkgT(VrmNTP0^r`fW zz^&mT@oG5dz<=1OzoEDn&iF=D7SH&-_~ZDA&!BxLrv!c3si}H6pY=ww-=oijO=+2z zMYNm;N5D_UH;k5N)D`?cjPn(9m0G=mmS^P)_D?VV?fjGFf2iMy?C0cW?Y5SaeZQ@o z`*3f(05^lYKc=6$v_?<`)|P7n8?uiUTY%5zZ&PybIAi~le!FqxvuFC=9rhJZT~y*j zU>A97@#1oM>hTafS$rQD1=m5|^>daD)X(X1TWK=_p977`?R#h8p|tFkZTTD0_rj@* z*p=)&lZNxN&iQ=5Sw-=zTEkDxpUKa8{-IdrdOAPzJq2DbbFc0r`#Ak=3bV!Er4KNF zgW0b^eetY8cq}Xv+eqI#K<4W>yk0!l%U(}^!%F6#o%3vCv0l)Motmt}&i^M;&OX|> z7Rt*z{j2&t2-htY-}j~-?i0^>oc1m0FF>xno%VmzI`W&yTWZX|<5~Pu`UmkR(NOR*JS5={pXWVyB()3vw?|5%)7z?q|PS+)s%S9p$H zrrlQTXXJd1Q`@y6>wf`lu$*P=arhl*M9X}B#m@R3&hCwe;GM*7H|7iU`98a?oZ9r4 z#n-Vn$914TwBWxkX9VQmHOTWJ=UV12@ox03aL(H8*+0Uo?2L0O{4BH*UxIJJIcqY% z8Fx##!~M>~x9a!R3Tue-KCx0>vr_R)$(k+l-E8LaX0eQQf%vVl#g1d&A+|q$S^K5< zKCvrtU;2m6iLC$K{EzL&D!F&@lW1#KdzYba#eUmZ(%+l<*jYU5y@l9RvD8LiSix_D z$Kd?`G%w=C;&;P*xQ?GXI9Ol(@P@9P{Fm8JLiYJI*w{ESr)igG(Z6tSIbCc5Jj&1K z@tg6sa=*seXM2lnTJC!S_88dIb?&0&IWeA}x|$)L>zt!~YPEuXBrQ43*%QU)@?UoE zi*e@VZTPmLct&LHUgLL_SBdAl&#d7f`a%5c`@9dO4pLjG;q~tGD)upOg7{3aZ|FIv zyRbi@WzBAc!Th`rZ14J4!}OC64&U5i&&I;XYYQ!MW*nGaoQF9F63@N=Qc-*_lp5QfzYp#PiDzDN2IYCs zu(CL#C&};OK2zJ(X(ON+Jjw4_D&83;_9*ttA@)a8T4V7mjBO*FdEFf6ne>GkNu8Sl&Z8vU&=S5N$BVX>U$^vvfX`gmK?N{pXX=pUtxC_&-b=3^0PN{<~%08G5ri&qpbL>@F9N~Kj-YrazWcL}qUFq3P8$yw8Otr@&Q|x8db?cwQ@NF5JIKpD zf6G6cJwQC)q4$tKg#Ig>#2$w?7u!Vad47NSdC$Cn-3?N2z3>jS&J}8ypYPqe^7C_} z?QqtjJtX%Bxv%vou911U4u;TEBSW=s#;*fM$jjQSf}B5Tw>SMyeQqXq0UX25d&i#Q z*&A=bKkzI+a$5;<+POHE{c7M5E}vEH=r_&GQC<>&o3^?F;Gvyzr;kA>`s0r-C0 z4ktf-7~TaIRusRZ{uVpuSN7Gb5FW05J?*lV z>1$hBYB1+pPn>-{h+RgnuJ7CBXig=k@b_MRDfO6z@p;ng1!wk&}DPy=P46_bm20oU>!EO82kd-SKq#KkTiHV-^1l z@p+JQsSTV1TgkZq&xPmZwuIx#>~VV5cocuH^5Q$HZuCj)X83CPqad+Qp`*NN{6|a0 z=i;Rm#hT8eHKP40{w{5Swg;I1+q7v+uP&a?4XLd);sfZn%e@V{IJdG-J3}q8lks@@ z>v7&$+H3O!eP_9wuy=+>`Awja_!j&Ycr2X2&RO@TYdxraYuYw2ly;f+x8lvj78zSr zcK(g^>*(9C-|fylIUDkSg6yp>a=Wn0^iv-)Uv=RI*La`) zy_}}nqz+c{e`o)N_m#UX?R4=sA+=o#Hm@k2n`epTd*M0!oU=pt_w)02KU%TZ(r+^E zi?#VnJm=jOu(j9>c^z<`Evv;d=UK~%w1qHL{13=y$ZB#P&@O-f=vX-!({XUmn&SJs zoHf(s)xh)REt1m;=N>bkM~K~ybFJIy`9Gj*v2%7l%pYEkV`-GFP97jvKI za(6kiaDDtVy*ECR|2MRdpJz<&?H=t$8t3-nd9EB#?i#fH*ZKDfWwws>kqu}3qG%;iC}y=j->w3`9{|L!rJHi@5SL7mFt zGiq;kV|Mn~D6#%{cYNcTqHV@85K{No(ksRD4znlzjGt#RZZD^msmh|0l-nHh6<$PR7dx$n059A*NC$XoAFNgQU)Z3O=_*IUf~-JB(M8~Z6* zXLjBr>g#Jae&%jd{uSEPWIqip<(vz5Y4eWQdAJGWj6DoLS}H!rZOYEMlr@=&e-Zys z`>f-5+*RHYVz07yVduHg1ZU6mD=+3cXW`fUPx$+=C$l@q*%H@-TD0(A`p)}DwQ{w~ zegrQUOFex7Bl*|yKZ2}v&ZnmA>tSS>x`e8TV})v z+RSsUZqeW|$iB|m-V>)zFA;Cf&z{ZmH?=&tvUqpNJYUVf1yW~VMb%EPpe`qW>?enZUl)pW@6V7-KuPpXo&WOY4sim6m0ec{9NUsVjTw{5Ka}OSK z-PBU{cK*+wyYLBeat>F8W{_H4hHutxC$Y=Qi)TvOw&1rH%d$+3%-j((Y zJ8L-_X0ms6{Xuf?(Jp7h>v$KjBWMjvMQvqH_T&E})*9z}*{?$&bG-*Ye-9;R@HTR~ zLSK6B^(pzIaO!s_`gqsKI;3uLf0^s8>Bq4zVGm|!-|h_u@`pJ4Q-62RH>YL)6TcyTE4~n4Ps=lO3C{0}_15ljep|6}_PgxPcpjc7r!M=+)!vEWGh?hK zZ*$r@v9slz#?LxGBep5KKYancE_)-W;P-Hyg|x5Od8ZqU2Z-gfNF#opd3lax?lxIl z?3L`_a&7LyIa6=Mmx^Bm6Zxwl*Lc`G)T*>c_ziJC+9>TFf~-SoJvEcF@CBF(_lq^4 zZRvfg7W?GN;=7rgkH3|vH~bH7g!9>SrQBELbq>pIfv*<(+BM(jPlfFH%=^h=uhX+0 zIp0szem+0-_BT84vzey};un~gH|2C==RG-fzAa?Ff0I{5e%3#GZ3sW}c9i_pIA>Vy z^-OlwYad8Ick*)_;8lHUk_p#AaseTlz{{UdD@yN%r6T_@+>$F#d)NBxYZFJb37 zI1c{1pUR6fI5pZ4#_;ogJPhX3iafq-<9mlNACGu_F@>${Amoi`^dIho{0%wQdq0$) zwLB4iVW<6d#`h{-?3(`?L*9wAzCGlmrk1iZe_59kVLI(wdHF72EBbfhA3)wy*V3LA ze->wNZz%sPId|Y`uq*okd2Kv*KGY`P6K1ZGbGk8ZPU{FAJZHMdAI_eI@0W8rPT#4= z7WC?sYSMLXFsDOyFP?Xu#I~Vtg5Qz53wsOvCclK|X}5x%8h+O}a#n25zezvS*i~`% zf6jx$>6gJ|d37N3n0d-PWN*)gW8`PQFBRVvrn2+=%pN~dyb1p{Jh7rU|5C>z;3Rs^ zwybBgyiY$RC;MrcIr&z<$;rNc*1avDXP@TGxz#-F$-Wx$>}X0`US7N(?#6Bf)980V zH)Huq>}6O?&)>n$y5_r$mb9F$kK+ps zSpHqw{KlWfUXELd?@HSWhO+m<4e<{62JxPFC+M#4`}jEzQb&jLGv_rci}NdI-ht2w z#=uGXc(hcU)wi-!gQ?H!@h1HIj{R)*sqCed#ryER?%`xwcUleF&FrB#@85@seM8@m z|FoK(S>aw`KCBSS{bWv_;g`WR{F99_XU8e!#plqbIQN=&lm_@tvD8WpW69WNYLoY~ z&Bd}V2bULT!2o*Z=O~=L@G+#uQzLue1L111E&16?nUia1*Fw#TqGmUyrRJWc-RT;S zurG)9^wi()^bdXi-JQKmdyid{@=i~oXLcPjoFrn3hae@#3R(mv0ki{MVR zG*`~?v?Jg&_BOZ*UWaopOXNL=Gj}`6FUL6}@*LUJHM6I)@Ai0cD##qC-3t1n+K$H`#}}LGdO1sb%4to@{bub;cnZEm ztgToNyaE4wocsF_w}$6okbCHica_&dPA7I-XvBU}|F4+i?Bjak4I%sE9eV0E^_p|? zaenT%PFeB&(k|?c>C@=f(Hgi$7ye5XY6V`U9l-C#{t}PXPc3aW#i_9gwEFCa@gdMy zY%hKNB(^Km5xbIJLqGM!R*J2sZ$_KT&)PM?c_w{9t0&%$eK1Ub(_xXE!KGr4rY>HG ztmENu4LkSSUp#+zt1CM-mNhHm52K$;I~(^mhD+cbxZU$$mYi+1{|@@f&G}kg?sM9o zBX$$)BzGijlvsUv4cJ$*Pl4UoPt(4UcZ9s`>zo@|yX@utA^qofNj_swW}hIwz4&>F zyUtte2k3ulzY)%vT1(D9>~eNh{0n4mX7SsLJ;UClqSz~s;M24nE0*W=#r(X(-$+YM zr(FZMOzdU;$#|NatmVP@GkDot{Xs6nWq_IN71fj&u5P=83&|>UZkBUce(gF{5|YIn^0zK za?fBNXfC(s=iOy*{t-C$*nzgSoU`CZ`qs2bIOoZ=?9^QL@(Ovk@vmWb#ZMSl)^0q% z48Ea_m0N3#vl}P(BmDUPa+>q&!34Qi(szdJpZ|zw?K0+^YmaHaC*-`!{>)y#82*(1 zf?7L>JzhNTDL-qo17yx~kArZYht#vKQ&+9{IS2lPwX{|Ge$@4ru@BWg`>TJ2Gg@vLu8Y5un`iM}V&_5jL|uM0 z_8nrAAhGt9#XD=xiqzUdxFB{#@w|AH{|rBM)tLP=o>lT$oOUdpN2^NP57Ng4wDfZu zKjX@NO&#w9ce8W?95|7`ja?oGZp%*aZUb#>=I7DmHL?@ z|03F5tM$R(fS!NvGv`qD+by)$oL{}<`oEEPZ7>deoXo4Fsw9z&lfz67VPZlK>zKO9=ZYqYFk zH}iBoKl3+-mS@$;kn80Cv${l1*8g`H;a=~lEIyZJ|6F9Q_T~Q!w~2Saz3B__W&CmY zW%H0Z&wS*(X-$8bzg8?~_g-)^Oc1}PLLOv3_kxevm*dC0r(};z0PE=^=y?vm4@XzHH+r7aC&4<} zU2@jQ$vM#j&%~9uncD4*59Gg9Ry-@O;dg@6TK3X}l4ossQBOC@YphMam)VY;&+bp+ zkMQVnXR6%nmjT*k?3cj-(4^$O25%>JFgyG3v@krO{k8ZRu?^S_#D=+c>h}-!RqUhK zU%^e{v()DSu6Ms!e|X7!=b3nsyr;zPmX~*?dubQru5xyPJa_&s`Me=-Bz-;mTzd9I zEj*Y1hMfEPP1%d&)u#87`y)NixGwx_**mjW%6Y;WIi3HH+#dWX;#X?Ff_*mpE_SKh z)O|C27S7q(9%oG&RTl3OIe(vnztmyoGkavRocHi5{<_5Zv+x?)W!mO!$@%pIoKzoPK|ilRdZ-doBA%@psrin)B8C z%*TfK272rP?Capa+?}Gc99S!QPRb|GzG0!O1X;wt`ko{uSD_agF)>ZtTqaTTol<2-=z2okGvK zR#9H;g;o4v@Ca=%w56X8kFv849ku%iXP)!U@P;$?cGs9pA40oE?$hGe@~gp>?9^`= zjFgw_rv~nV#pXHVJdakjq`qib)2{r@|D1mKXr0F)PuXB5iMiO zUQfMb9N9nl`Sh>lMeQC*yO;f=ysr2${7IQTL4Q`eZ`g0Lb4HzmvxZ~gGd0zQKU`mr z$$N{v7>*UI4z2VxmH&y@Vft7^%enP{{HE-zEMZEI(TA=NEaO>0@)a6LL=GxljX+ zf@SWr3BE#`?Qq_k-e5OpA6+Wm399mQex4{+m(~Kh!#A`gFcGffSFJSvu9vl(&VRyK za>i~(Zw1@Y7V*d7Enp|w`|v!w9%OGGs?BZqXnC8n2jl+y$I6|*coywXc58EeCeEHo z?)bDRxo39PBjbBl>^oS?ujxLAd0!8LEfKf>ko9&jO~4!5M8F7Ir1 z&X&yo`Lu~RdpPsfp1r4e$o*!o>_*SKLM`_7CHE+v`Rv19h);s=%Zu-*my7kJmqGez zYR)sR-^8cV&S1X+jkGzyJ{e29mz^_Vh;csychPo)oZY7-kG-At53?uG_TcZpe~tYE zK7rnrb_74)Kkv!TxVDDuk<01NvKyCsmWZ9n-=CkkEr;axr`;ev1YgF#1n%Vj1^3W$ zcCD4$oj)F5hBMBsA$3;^Uq{P*wyr4lR@UceSOQhWTS3m;ZQ(}2vE%8h=vnLRr~m(1)*t5h5Ps%h6TA#^W?zWMYxe`3$nL1W&FE*d zQwzE0)N=l=Wd6*e-3QrkkCdxbxD}f5v$w}U=J8VPKER#vBH9D&)Y=4gUAP&t)~UHE zV%y_G@ZR_;*j#Rx%HlJ~srLkn2I^ylcAaP$`=R)D{2R`>bESCdEN9MX{5-?***kMR z1v==vg+Ax9N5b*!Pw`-R_u>U5=Mro#FZOs^uJMd(Hpe;pn$nKPnfn?zb#)cI3FWlp zrRFlPXV7lIS-%%>_Uc?nf1ARS#+m;E@!9fX-gcm6ee21|yv*l6VT`GvBj721)->~- znrlqE0amjcLf%u4=idmk<=g@JKR7#86ldlvewo-QawoB8(D#Hjv~#uHk)3@z5*j^N zd|#92&fevoT{ve~<|H+6EPozs0k1%wt0VQ%4$pww%8PnREk9aPPy8d{Z)jpoI^(m% za?hEY<8jW?pCIpE&xq%<$Ot)W_}L#hck}oB^R7M@_T{gXcRw_sZ_n>svPZ?f!>QM- z$1HfEyXU*SouQ?8cc@J-;}5Sa>gqcF-}Lt&XTebZXgH3Y{V=`Kvl&-~J87wfY3})H z`M>b<9&kH9?+yRb^6w!ul+#T9marG(-K@Wy@9}*6DXj@)Kir2?@4275_}Pnj=lq)A zk9{ZmGAOZkr@f0S_)98%m$0U&jo$2Kv~$FACVfuNyzIu$cps-dh40{}k6!Gw`3y!u zbK1(1^UBzAo<7FT&)FK`{QLG<`Y7)sp?Y+^uoutPA@&TrRdbet^CXXOGlo z&xKa}TOf106a5U_hF{-!7va?VA++Og1AZ%ZRk*am9)XjMua?{$#J7R`9iywnKZcy) zYvrWgv$s+=IS2Q`Tj7j7^)s&I{f9k^-I4wpUPv1abzm&?g0sc07i$AKCziuZ`drAI zZ4QIk^>OaA1vII8kH_%gv?cf@ZC+%* zgY*BdXWqKeYeG}Vn&js>58_eUz6w7W?;Kic`;^M!yjh|~QuEg)4_BA>0?xB0;AhIDImG9Q!dl_fFt~Eyk>C54~%EB(uvsSHW4QQ#)mh9c=Ij6Gko)ABr zb{#w`p6>x)VRvU=%>GXN0RCcr>hxedL%V7?edSELm7Sj_bd%qP-4DJpw%f(7WjCO& zC>a;+RJ@-49Q_s;3s2MEp{35wEHBpWetFpgS=-exSZs})yTns3zq(#eyo^3l{Cc=d z{6xsO2FM!*xAAWf+mx33yH9@3$z9pG&r`)W!*|m245$aUY1e|ji5kmVtrFiBj$zNn zyTCW{kCHRAvUq>(L#xH!jrO6u?5Uh@i=hX9L)>wVv!l}b!iC!JPwOg{J(7FLb2Rzc z_nX1qa;~5)q`eJY`5WbatI7e>k{W$GXLs^Ub?EL|L_449u zXwCirM)7CKc~s7~?9|g>^P4l^MRsGHu|5YaD}4^Y2SL^`YqHL@X7IC@&(LlXJJ096 zq4Anx?w5-tC$*B#pLte1kME%6y!cB$ljY=0PmPq(YQXL6PvCgigFi)lAG|Zaf%_SQ zySaYeL#ndp+dof;?+JN_IfwR1g?D>7n~7DWP2j(Z|Hg;QTg%R8(F5fk$8Q8JV3<9U z@ywJr9&i3Xy+1yRzpi9Y;$OAR|Bs$A99~hJjdR%x;TJg*;YMwS;U(fHvvbea^0(vX zKDU)SjD0L#O+Olz(Wl_=XsyID-d?mDX$P{K;4`5nZMB?H_^N1Yit}|v^5J{2{A?;` zXWpH9$Z5;YTy6>Hun)#((SOEuaNaFbJFm)L#C{5@$XkW;PS%p$QhYkxNBhGyw_yJ% zf3jE?SVzB7?tb(`A@}nse=E7EsnlfZdtZ7V{1Bvej_1Du{pIDld1mawuUS!?CDZUu z{0jQrrQ)6FZg#%oDi_OHRv-SQ<>#HR(>{bU+Qu-bQk~-k;*HqN#8UgY?|C@S_WS75 z_#^O@`~mEYZMFC(IBPc*4y!D3USW3^n=bbkd?-Eld@Jk=c{a9#+{=!*gPhFQEdB+R z_Pl4uiTtMg)X9^yamGFrua%phb7dS4usew#1zzHhhMs!Ky?lh{$^BMN`pjNCg}#*E2baUAFj3x~FbS$x7SI1)xUJZ^xHkTe*!7T_ z`jVEqdIooceD?TKPTnc<&NmaXR<9ZJ2XF>E+6VBib_@8k-Pc7p`{g$6Q!gDWi)ZI+ z`su`;L2Jma4IjCWwY2ZFpH1I{y&kG*oA-eF?BS5L&3XPczD~RQY2(=!;Tz~@!N2@x z@q^}mGCR-G_RvUQslBb)Ifs_O>80ZQK7l<#n>;J0u=Dw3{2F7BKaF2a?0TFrl-N(; zW5sUAIX`wS6`v)m;Co`mxb;Qet!NNt~oca?LnoIEd9(*9=8!5M4z z!+ZE6V`*cK8q%MY_Za=^p2c?`qxkQM=b1mA{vbblza{^1cBR-AR$Q~jlVduS*(Ynyk#P5C!lvpTf9_{q=QY>M-aG>HBs>Y&;R@$%lV1@&(FPWl(^i5YG@eFek!M*B&T1wd1mK4UB|yfyfMB?&J4JizBk?( zr+&UIng7^wR;?+{jeNJ#No*zkXL>pNRmgr@2ss~e4qoXx>H9HS#&9y!r{@e94RhG3 z-F|XXAGejfm*84B{oE?Hu|A$)x5MAd+aCYLZvbb*9QT-Ym@AfjlfQ4dBTnCa#AeZ- zVb{ZR;0{>4rucbpE%&jF-1W}p*Kuk+=g~5pvv?AFEc*x8gWU`AZ^XPJ)|H)c{zq(6 z*oUA0BXJb%F8)$E&2i47)cpc}&dWFWc?VCeFQWIxYv}2FC-x!YTj0z1sntg8yt|~H zllL$_NX`S%@%Q|_sfg--#i1}Wp@)_&(0csf=|S|<1cXLBIn^d z5WR)mn#S4{-y*h_c0F`w?`lkg=|f?A{?`@7yVo#tIfI?ED(6|&Y(88iuOICt_SHD& z`2OtS_#-?P=N+kEsrX)WGW%ZF*qnbeyRrBYkhy-3K8b!4K7*fHoX&re-9~QKZi%_e z_dYpa4iw9KOU}Di(3GF~s4H*1_qyz*oOff_6z`??iSGhAYu*;iJJLAZia$t zyn?-$)&(zw4PYw&3%S>ooI}tNR`F+v<+(H&Zwgue`{^6A=ellp{;S%ahd*<@ysup; zUY)-T{$}4nUy9pQibForjKcTeHgX!^JoodQzl5H#W==A{HECDN=|H=jzwsL95r01z zTV8zEah2Ha>{D>w8a49=Bv8lHp4h#k$IEiZdI`*llt-W{vcJ3ZAR%LPa9!jf9%Xxbb-j1HLEwy=-*iyN5On=_lhAat?xd z@VR)-*}?3KUNV}(Go#k$Wx8(PQyV!Z|50kS6zb)Q~)|7o+dGVd*`~2*Suka)A1RPSy z7r&R?S&cs<-ie*sO8wS^?31dlH)Tf4jzh=Km=t z^>iZpDwxJ!i0`w;FUak|&pGid`yu*fa6zTIP*+Rwv5{p6o{&AbOz5trhKER#v ziI92yTD&`CKaYda;v2(X6~#Meem;3QeP45tJ$|KFSIBd$1Us?wEXkNY?pd5$Irle_ zzXh$2Hn+iU><4K*_4gwGC^?VdKKNF8#y7U2*fSOQAF+%lYu*=DL)P|u{cKTTLhvQp z{LF5yefHCT-v#kh+EDn7ehYt`+S^tB-TZ6#`Fw z{0jXBcD^$iT&9+2x#s8c4rfmmzXIoXBtE0hX6NT;9q|yDO8Z>fHtc4w5?&U6Q(vjG zU0@z9e~;jE+5mR?y^H^kzT2{=$e&$l&*b{}0on?Dgtplm56W9vbu)XayRFveSP+Y;;FTHkTFjddz-ct?+VlC2k>u}JA#>3hh{{{x%( zZz?tvKOir;sfqTqPvu+;ZTPoodpkSxmGdvP`w{%k4>!g+4+r7YSk^LU_RTPw-U#x3 zllEQN`-qS{(`~k3s{5G`f#Aie1WG#%KFN8(3?5o2d;~jysH=lv5VO!dKyoJ7AE-z|6_n-Q0 z3c2PZkXqRfPuFMkYbuN9;+NWf!T*%LnVgaAS8>jWE#;J8Eqg^-QO~K})O6}J{dR=C z*||>cGiyIYU&Gm{x8Lv-s3vE+SjM%xzW(4J!B0)pXJ_A)@z?P$U=M-I`3Lexvr`}K z_@mf!;Wapcwji7bjzH{CT z65AMOO@7el_iE^Ob~EU#&E4`YWT)QxLe9&V`Sa*`Ue$uE+s($-kCqzA+~(g9`@N#5 z^%DPedcMnP!mdWo`7yh)sQGKP-Hm=XG^6Fa>*?~(1zPr6KYlCu`-o?~@*HY~ zYr;F?dC#cM9?RZM`@hR-yLvc{mKyvP|0Ue0qR`HyZ-7nssXgC`-Nm2B|G)9!?CD2o zd6winHDTW&H}N*KzU(~b2GUbkXX24!IlEcw%x~(zb+nP}?ToPlJN0T*src?Q>whui z`S%fRf}ErHS(n@JUX`v5y*H+PAI_0mRXpz_*%Qy1 zix;#VBX&EaHe`QgUN5ERnV50CiRV3HAJE6c7_sB=5`APJPrzH_)TP7ilaKj1Q#lKR zX=k!;qiu)#mFyS$K4&y*_Bb?$ylYRvxredh!`K^WcjJTLKk{?l@_p=5`YgyiW?m-Y zy6^#Q96x(vcllfBCw1?9dO!Zx?5Ch!W$~NuuDDX{G3W}ZZ(YhooyeK~LwtidYN5@N zy^I%E5ubuT#n;Hmzl-&Nd(7GI07uFHh2BlhDfmZuRq2ayCwRu#KI3nMY1;ix%i8RZ zbB43;8spqwejA%Emc5oaI+xu|oA2;vkU2daFR3ivUB45{_XU&b*{56Z@4=t*N8>k) zwYuE1_4fon`@0jY5^n_gyew4Xo^0eBNxyE{k>7S50|WvyY}tfO+r& z?M>}g!$FX}+8bv-+{8X7cDeW-?LK}x<6UIVR@)dZV|CaHePJ5JpAKb(L3}>zWrsp~Ej=atF zT?el#7i)baJ!`Rne+NA^{8#o4?9`5YzUik;&fs5gp4fh3&DnDx^EsH7-$eh7vky)c zpD6Y+t}ea8cIOJu4tgzfo4PuXR!cnN?Zscl{{v_L{i5&J*$?V>20#5}oY~7) zLHa%gx5n8o_lX?~@50xm;&ajg^jrDE;6weaF|Xg!_R;QFco)tQYb<|LoEn<`{z;#S zbG~y1w$(Pz+?=07@k{tjoO$R7w^SB;YYh7(`XI

(eu*-{C6!d^Wh0oq5_%PHN7! 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

{R_SX*AcrOc7YvXzP#qifu82z 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

Ab}}Ak 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[`Ix}9K%8H0LzyoQA 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

TbMmVJPF`zd=r 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