From 0c1390f8e93788331806f2d9ad4e5d68d7defa4b Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 13 Aug 2026 17:09:34 +0000 Subject: [PATCH 1/3] spec(ltx-2.5): image conditioning at crf=0, and the VAE encoder load path that was missing (#644) Row 1 of the #644 full-port campaign, spec committed BEFORE any code. The refusal at `src/vllm/multimodal/ltx2_video.cpp:1122` is honest and its anchors verify, but the campaign record made the CRF round trip look like the blocker. Re-verified against the current tree, it is not: * `Ltx2ConvVideoEncode` (encoder MATH) landed in phase L11 and is gated. * `Ltx2ConditionVideoByLatentIndex` (the composition) landed and is gated. * `Ltx2VideoVaeEncoderKeyRules` matches NOWHERE in the tree, and neither does any encoder CONFIG parser. The engine loads the DECODER filter only (`ltx2_video.cpp:752`), so the encoder is a brick with no delivery route. * `preprocess` short-circuits at `if crf == 0: return image` (`media_io/decode.py:425-426`), so a crf=0 path is upstream-legal and needs no codec. This environment has no `av` module at all, which is the demonstration rather than the obstacle. So the row builds the load path, serves an image at latent frame 0 at crf=0 as an explicitly OUT-OF-DISTRIBUTION arm (a 2.5 checkpoint resolves default_image_crf=18 via constants.py:130-133 -> LTX_2_4_PARAMS), and narrows the refusal into four messages that each name a different missing piece. 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-image-conditioning.md | 294 ++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 .agents/specs/ltx25-image-conditioning.md diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 3cc1d0234..e2976e382 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -145,6 +145,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#484](https://github.com/mudler/vllm.cpp/issues/484) | `GATE-PIN-UNPINNED-SNAPSHOTS` | The `check-snapshot-pins` CI step has never been observed executing on a real GitHub runner; the spec records this row's GPU/weights limits but not that one | bug | | [#485](https://github.com/mudler/vllm.cpp/issues/485) | `GATE-PIN-UNPINNED-SNAPSHOTS` | `_MARK_FIXPOINT_ROUNDS = 6` silently truncates the binding fixpoint: a 7-link helper chain evades `check-snapshot-pins` with no diagnostic | bug | | [#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 | +| [#644](https://github.com/mudler/vllm.cpp/issues/644) | `ROAD-V1-LTX25` | LTX-2.5 full port: close every refused conditioning arm. Row 1 `LTX25-IMAGE-COND` (spec [`ltx25-image-conditioning.md`](specs/ltx25-image-conditioning.md)) builds the video VAE ENCODER's load path — `Ltx2VideoVaeEncoderKeyRules` existed nowhere in the tree — and serves an image at latent frame 0 at `crf = 0`; keyframe / reference / non-zero-CRF stay refused by name | 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 | | [#238](https://github.com/mudler/vllm.cpp/issues/238) | `SAMPLE-LOGPROB-TOKEN-IDS` | `logprobs_mode`: three of four modes are runtime-refused stubs | bug | diff --git a/.agents/specs/ltx25-image-conditioning.md b/.agents/specs/ltx25-image-conditioning.md new file mode 100644 index 000000000..f88d463ea --- /dev/null +++ b/.agents/specs/ltx25-image-conditioning.md @@ -0,0 +1,294 @@ +# LTX-2.5 IMAGE CONDITIONING — the VAE encoder's load path, and the first arm off `ltx2_video.cpp:1122` + +**Row:** `LTX25-IMAGE-COND` (row 1 of the `#644` full-port campaign). +**Issue:** [#644](https://github.com/mudler/vllm.cpp/issues/644). +**Branch:** `row/LTX25-IMAGE-COND`. +**Parent spec:** [`ltx-2-5.md`](ltx-2-5.md) — operator-owned, NOT edited by this row. +**Upstream root (primary):** Lightricks/LTX-2 @ `fd4ded7f2d88d3da713abcdd4ad41ecc4a9314ca`, +`packages/ltx-core/src/ltx_core/` and `packages/ltx-pipelines/src/ltx_pipelines/`. +**Upstream (cross-check only):** `huggingface/diffusers` `pipeline_ltx2_condition.py`, +`ltx2/utils.py`. Where the two disagree, §3.3 records it and we follow `ltx_core`. + +--- + +## 0. What is claimed, and what is not + +This row closes exactly ONE conditioning kind — an **image at latent frame 0**, at +**CRF 0** — and it does so by building the thing that was actually absent: a +**weight-loading path for the video VAE encoder**. Keyframes, reference video and +reference audio stay refused, with a message that names what is now true rather +than repeating a reason that has gone stale. + +Three things are stated up front so they cannot be discovered later. + +1. **`crf = 0` is OUT OF DISTRIBUTION for LTX-2.5 and is labelled as such.** + Upstream resolves an unset CRF from the checkpoint generation + (`ImageConditioner.resolve_crf`, `blocks.py:977-983`), and a 2.5 checkpoint + resolves to **18** — `detect_params` maps `(2,5)` onto the newest row at or + below it, `((2, 4), LTX_2_4_PARAMS)` (`constants.py:130-133`), whose + `default_image_crf` is `LTX_2_4_IMAGE_CRF = 18` (`constants.py:37, 124`). So + the DEFAULT request refuses, and a caller must ask for `image_crf = 0` + explicitly. That is upstream-legal — `preprocess` short-circuits at + `if crf == 0: return image` (`decode.py:425-426`), and an explicit `0` is + documented as "skip re-compression entirely" (`args.py:58-59`) — but it + conditions on pixels the model was not trained to see. It is not silently + rendered. +2. **No render-quality claim.** This row's evidence is numeric parity against + executed upstream at reduced dimensions plus a mutation gate. A finite, + correctly-shaped clip is not a quality result; the parent spec §0 already + records why. +3. **No speed claim.** Nothing here is measured for throughput; the parent spec's + `PENDING` speed axis is untouched. + +## 1. The gap, re-verified against the current tree (not against the record) + +The refusal at `src/vllm/multimodal/ltx2_video.cpp:1122` is honestly written and +its anchors verify. Its internal claim was re-checked, and it is TRUE: + +| claim | check | result | +|---|---|---| +| the encoder MATH landed | `Ltx2ConvVideoEncode`, `src/vllm/model_executor/models/ltx2_video_vae.cpp:988` | present, gated (`test_ltx2_vae`) | +| the conditioning ITEMS landed | `Ltx2ConditionVideoByLatentIndex`, `src/vllm/model_executor/models/ltx2_conditioning.cpp:137` | present, gated | +| the engine loads the DECODER filter only | `src/vllm/multimodal/ltx2_video.cpp:752` | `Ltx2LoadVaeWeights(f, Ltx2VideoVaeDecoderKeyRules())` | +| an encoder key filter exists anywhere | `grep -rn Ltx2VideoVaeEncoderKeyRules src include tests` | **NO MATCH** | +| an encoder CONFIG parser exists anywhere | `grep -rn ParseConvVideoEncoder src include tests` | **NO MATCH** | + +So the encoder is a brick with no delivery route: nothing turns a checkpoint into +an `Ltx2ConvVideoEncoderConfig`, and nothing puts `encoder.*` tensors in a +`Ltx2VaeWeights`. That, not the math, is what this row builds. + +The CRF round trip is **not** on the critical path, contrary to what the refusal +text implies by listing it alongside the encoder. `preprocess` returns the image +unchanged at `crf == 0` (`decode.py:425-426`; note `:427-428` is a *different* +early return, for a degenerate < 2px side), and this environment has no `av` +module at all, which is itself the demonstration: a `crf = 0` path needs no +codec. + +## 2. Upstream anchors — every stage, both sides + +| stage | upstream `file:line` | ours | +|---|---|---| +| 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` | +| 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) | +| aspect-fill resize | `media_io/resize.py:41-73` (`resize_and_center_crop`) | `Ltx2ResizeAndCenterCrop` | +| normalize | `media_io/range_map.py:8-9` (`normalize_images`) | in `Ltx2LoadImageAndPreprocess` | +| the whole preprocess | `decode.py:46-79` (`load_image_and_preprocess`) | `Ltx2LoadImageAndPreprocess` | +| VAE encode | `ltx-pipelines/.../utils/helpers.py:285-294` | `Ltx2ConvVideoEncode` (already landed) | +| latent composition | `ltx-core/.../conditioning/types/latent_cond.py:32-43` | `Ltx2ConditionVideoByLatentIndex` (already landed) | +| noise composition | `ltx-core/.../components/noisers.py:30-37` | `ApplyGaussianNoise` (already landed) | + +## 3. Design + +### 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: + +``` +{"vae.encoder.", ""} +{"vae.per_channel_statistics.", "per_channel_statistics."} +{"encoder.", ""} +{"per_channel_statistics.", "per_channel_statistics."} +``` + +`per_channel_statistics` is in BOTH filters upstream and must be in both here: +`Ltx2ConvVideoEncode` reads `per_channel_statistics.{std,mean}-of-means` to +normalize its output (`video_vae.py:336`), so an encoder bag without them cannot +produce a latent in the DiT's space. + +`Ltx2ParseConvVideoEncoderConfig` mirrors `_prepare_video_encoder_kwargs` key for +key, including the two-layout split (`:46-53`): a nested `vae.encoder` object +takes its latent width from `encoder.out_channels`, a flat +`CausalVideoAutoencoder` from `vae.latent_channels` — and the top-level +`out_channels` is the DECODER's RGB count and must never be read as the latent +width (`:41-43`). The encoder's `spatial_padding_mode` default is `zeros` where +the decoder's is `reflect` (`:63-67` vs `:90`); both read the same checkpoint key +on a flat config, so they diverge only when it is ABSENT — silently, by a +half-pixel border, in opposite directions. That is why the default lives in the +parser and is pinned by a test. + +A checkpoint carrying no `encoder.*` tensors is a decoder-only (Comfy-split) +file. The engine then holds no encoder and an image request is refused BY NAME — +never served by falling back to something else. + +### 3.2 Image preprocessing at `crf = 0` + +`load_image_and_preprocess` (`decode.py:74-78`) is four steps, and the ORDER is +load-bearing: + +``` +image = decode_image(path) # uint8 [H, W, 3], sRGB +image = preprocess(image, crf) # identity at crf == 0 +image = torch.tensor(image, float32) # values still 0..255 +image = resize_and_center_crop(image, height, width) # <- IN 0..255 SPACE +image = normalize_images(image, ...) # /127.5 - 1.0 +``` + +The resize happens BEFORE the normalize. Bilinear interpolation is affine so the +two commute mathematically, but not bit-for-bit, and this project's existing PPM +reader (`minimax_h3_video.cpp:87-120`) normalizes at decode time. Reusing it +would put the affine first. So this row decodes to **uint8** and keeps upstream's +order; `Ltx2DecodePpmRgb` is a separate reader for that reason and the reason is +recorded here rather than left as an unexplained duplicate. + +`resize_and_center_crop` (`resize.py:41-73`) is aspect-FILL then centre crop: +`scale = max(h/src_h, w/src_w)`, `new = ceil(src * scale)` (upstream comments the +`ceil` as avoiding negative crop offsets), `interpolate(mode="bilinear", +align_corners=False)`, then `crop_top = (new_h - h) // 2`. + +The bilinear kernel mirrors PyTorch's `align_corners=False` index map in **f32**, +which is what `HelperInterpLinear::compute_indices_weights` uses +(`aten/src/ATen/native/cpu/UpSampleKernel.cpp`, dispatched over `scalar_t`): +`scale = src / dst`, `real = scale * (i + 0.5) - 0.5`, clamped to `>= 0`, +`idx = floor(real)`, `lambda = clamp(real - idx, 0, 1)`, and the right tap is +`idx + (idx < src - 1)`. Width and height passes are separated, height first — +matching the generic Nd kernel's dimension order. + +**Only PPM (P6) is read**, and only `maxval == 255`. No PNG/JPEG codec is +vendored — the same NAMED residual `minimax_h3_video.cpp:84-86` already carries — +and a `maxval != 255` PPM is REFUSED rather than rescaled, because upstream's +decode is PIL's and mirroring PIL's rescale semantics is a separate port. EXIF +rotation and ICC→sRGB conversion (`decode.py:143-168`) do not apply: PPM carries +neither tag, so there is nothing to honour and nothing is silently dropped. + +### 3.3 Composition — and where the two references DISAGREE + +`ltx_core` writes the encoded latent into `clean_latent` and sets +`denoise_mask = 1 - strength` (`latent_cond.py:40-41`), leaving the NOISY tensor +alone; the noiser then composes the two with a DOUBLE lerp +(`noisers.py:32-33`): + +``` +latent = lerp(latent, noise, noise_scale) +latent = lerp(clean_latent, latent, denoise_mask) +``` + +diffusers instead writes the clean tokens into the noisy tensor as well +(`pipeline_ltx2_condition.py:1002-1004`, `:1229-1231`). The two agree ONLY at +`noise_scale == 1`. The two-stage distilled recipe's second phase does not run at +`noise_scale == 1`, so this is a live divergence, not a theoretical one. + +**We follow `ltx_core`.** `Ltx2ConditionVideoByLatentIndex` already implements +exactly that and `ltx2_conditioning.h` already records the divergence; this row +adds no new composition, it only reaches the existing one. Do not silently switch +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. +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. + +`strength` comes from the seam's existing `VideoGenParams::noise_aug` +("keyframe pinning strength; <= 0 => 1.0", `include/vllm.h:762`), whose polarity +already matches upstream's `ImageConditioningInput.strength` (`args.py:64`): 1.0 +pins, mask 0. + +### 3.4 What is still refused, and in what words + +Four distinct refusals, each naming a different missing piece so a later reader +can re-check its cause rather than trust it. This campaign has had FIVE refusals +whose stated reason went stale; each message below names the exact symbol or +`file:line` that would have to change for it to become false. + +1. **Non-zero CRF.** Names `encode_single_frame` / `decode_single_frame` + (`decode.py:386-410`) as unported, states that `crf = 0` is the supported + value and that it is out of distribution for a 2.5 checkpoint whose resolved + default is 18, and says which extra to set. +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. +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` / + `temporal_scale_factor`, which upstream stores in LoRA metadata this project + does not read (already recorded at `ltx2_conditioning.h:110-114`), and the + audio arm additionally needs the audio VAE ENCODER, whose key rules this row + does not add. + +## 4. The things that fail silently here + +* **Reading `vae.out_channels` as the encoder's latent width.** It is the + decoder's RGB count (3). An encoder built with `out_channels = 3` still runs + 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. +* **Normalizing before resizing.** Same answer to ~1e-7, so every finiteness and + shape check passes; only a value golden sees it. +* **Applying the conditioning after the noiser.** Produces a pinned first frame + that is pinned to the NOISED latent. Shapes, masks and finiteness all pass. +* **Inverting the mask (`strength` instead of `1 - strength`).** Renders an + unconditioned clip that looks like the feature not working. +* **A conditioning that is loaded but never read.** The whole class this row's + mutation evidence exists to exclude: `last_conditioning()` scaled x1.5 and + row-reversed both passed every assertion in an earlier phase. + +## 5. Tests and evidence + +**Goldens.** `scripts/gen-ltx2-image-cond-goldens.py` imports and EXECUTES +upstream under the pinned SHA and emits +`tests/vllm/multimodal/ltx2_image_cond_goldens.inc`. Sections: + +1. `resize_and_center_crop` alone, over shapes covering upscale, downscale, + wider-than-target and taller-than-target, including one where `ceil` changes + the answer. +2. `load_image_and_preprocess`'s full chain at `crf = 0` (resize then normalize). +3. `VideoEncoder(image)` at reduced dims over deterministic weights. +4. `VideoConditionByLatentIndex.apply_to` over that encoded latent — `clean` and + `denoise_mask`. +5. `GaussianNoiser` over the conditioned state at a NON-unit `noise_scale`, which + is the arm at which `ltx_core` and diffusers disagree (§3.3). + +**Harness adaptations, recorded:** (a) `ltx_pipelines.utils.media_io.__init__` +imports `av`, which is absent in this environment, so `resize.py` and +`range_map.py` are loaded by FILE PATH with `importlib` rather than as package +members. Both import only `torch`/`einops`/stdlib, so nothing about the math +changes, and the workaround is itself the evidence for §0.1. (b) Weights are +filled from the same deterministic stream `gen-ltx2-vae-goldens.py` uses, so no +weight byte is checked in. + +**Mutation evidence (required, not optional).** Each of these is applied to a +scratch copy, rebuilt, run, and the tree restored byte-for-byte: + +| mutation | must RED | +|---|---| +| conditioned latent scaled x1.5 | golden §4/§5 | +| mask set to `strength` instead of `1 - strength` | golden §4 | +| conditioning applied AFTER the noiser | golden §5 | +| resize/normalize order swapped | golden §2 | +| encoder key rules replaced by decoder key rules | the load test (weights absent) | +| the encoder config's latent width read from `vae.out_channels` | the config test | + +**Encoder weights are LOADED AND USED, proven separately from "loadable":** the +engine test perturbs ONE tensor of the encoder half of the fixture checkpoint and +asserts the conditioning trace's image digest moves, with every other byte of the +request identical. + +## 6. Gates + +* `ctest -R 'test_ltx2_image_cond|test_ltx2_video|test_ltx2_vae'` — focused, with + CASE and ASSERTION counts recorded on both sides of every mutation. +* Full `ctest` with the case count asserted against `ctest -N`. +* `scripts/agent-preflight.sh --staged` before commit. + +## 7. Stop conditions + +* If closing the image arm would require editing the prompt-AdaLN path in + `ltx2_loader.cpp` / `ltx2.cpp`, STOP and return `NEEDS_DECISION` — that is + `row/LTX25-PROMPT-ADALN`'s surface. +* If executed upstream disagrees with a ported stage by more than + `kLtx2GoldenTol`, the port is wrong; do not widen the band. + +## 8. Now + +`ACTIVE` — spec committed before implementation. From 7509e795eee3786f714fa40e124e884cce68375a Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Thu, 13 Aug 2026 18:04:36 +0000 Subject: [PATCH 2/3] feat(ltx-2.5): image conditioning at crf=0, and the VAE encoder load path that did not exist (#644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Row 1 of the #644 campaign. Spec `0c1390f8e`, committed before any code. WHAT WAS ACTUALLY MISSING. The refusal at `ltx2_video.cpp:1122` covered four conditioning kinds with one reason, and the reason that mattered was not the CRF round trip it led with: * `Ltx2ConvVideoEncode` (the encoder MATH) landed in phase L11, gated. * `Ltx2ConditionVideoByLatentIndex` (the placement) landed, gated. * `Ltx2VideoVaeEncoderKeyRules` matched NOWHERE in the tree, and no parser produced an `Ltx2ConvVideoEncoderConfig`. The engine materialized `Ltx2VideoVaeDecoderKeyRules()` alone, so the encoder was a brick with no delivery route. * `preprocess` short-circuits at `if crf == 0: return image` (media_io/decode.py:425-426), so a crf=0 path is upstream-legal and needs no codec. This box has no `av` module at all, which is the demonstration. So this builds the delivery route — `Ltx2VideoVaeEncoderKeyRules`, `Ltx2ParseConvVideoEncoderConfig`, `Ltx2CheckpointHasVideoEncoder` — plus the pixel front-end (`Ltx2DecodePpmRgb`, `Ltx2ResizeAndCenterCrop`, `Ltx2LoadImageAndPreprocess`, `Ltx2ResolveDefaultImageCrf`), and serves an image at latent frame 0 at crf=0. Both new TUs are additive rather than additions to `ltx2_loader.cpp`, which a concurrent row of this campaign also needs to edit. CRF 0 IS OUT OF DISTRIBUTION AND SAYS SO. An LTX-2.5 checkpoint resolves `default_image_crf = 18` (constants.py:37/124/130-133), and that round trip is unported, so the DEFAULT REFUSES: a caller must pass `image_crf=0` knowingly. THE OTHER THREE REFUSALS NOW NAME DIFFERENT MISSING PIECES — the DiT's unported `keyframes_abs_pos_embedding` for keyframes, the IC-LoRA scale factors for the reference arms, the audio VAE's absent encoder filter for reference audio — so a later reader can re-check a reason instead of trusting it. That is the defect this campaign has hit five times, including in the message replaced here. EVIDENCE. New generator executes upstream ltx_core AND ltx_pipelines under the pinned SHA fd4ded7f (package __init__ bypassed, `av`/`OpenImageIO` stubbed as TRIPWIRES; `preprocess(image, 0) is image` is asserted by EXECUTION, and `preprocess(image, 18)` is asserted to reach the codec and raise). Bands derived by measurement, not picked: 2e-6 latent-space (worst 4.77e-07), 2e-4 pixel-space (worst 6.10e-05). Six source mutations RED with counts, one SURVIVOR analysed rather than hidden — see the spec's section 8. THREE OF THIS ROW'S OWN CLAIMS WERE REFUTED AND ARE CORRECTED IN THE SPEC: the `ceil` does not guard what upstream's comment says it guards (swept, no pair in range); no golden here can see a resize/normalize ORDER swap (the two are algebraically equal, gap 1.94e-07); and torch's bilinear cannot be matched bit for bit portably (FMA contraction, and this tree builds -ffp-contract=off). Gate: BUILD_EXIT=0, 0 ENOSPC/BFD hits, `ctest -N` 424, `ctest` 424/424 exit 0. Focused: test_ltx2_image_cond 15 cases / 198 assertions, test_ltx2_video 32 / 550. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code] --- .agents/specs/ltx25-image-conditioning.md | 114 +- CMakeLists.txt | 7 + docs/FEATURES.md | 2 +- docs/USAGE.md | 44 +- examples/ltx2_gen/main.cpp | 44 +- include/vllm.h | 12 +- .../models/ltx2_image_preprocess.h | 103 ++ .../models/ltx2_video_vae_encoder.h | 43 + include/vllm/multimodal/ltx2_video.h | 72 +- scripts/gen-ltx2-image-cond-goldens.py | 698 ++++++++++++ .../models/ltx2_image_preprocess.cpp | 219 ++++ .../models/ltx2_video_vae_encoder_load.cpp | 232 ++++ src/vllm/multimodal/ltx2_video.cpp | 260 ++++- tests/CMakeLists.txt | 7 + .../multimodal/ltx2_image_cond_goldens.inc | 1007 +++++++++++++++++ tests/vllm/multimodal/ltx2_video_fixture.h | 56 + .../vllm/multimodal/test_ltx2_image_cond.cpp | 741 ++++++++++++ tests/vllm/multimodal/test_ltx2_video.cpp | 309 ++++- 18 files changed, 3877 insertions(+), 93 deletions(-) create mode 100644 include/vllm/model_executor/models/ltx2_image_preprocess.h create mode 100644 scripts/gen-ltx2-image-cond-goldens.py create mode 100644 src/vllm/model_executor/models/ltx2_image_preprocess.cpp create mode 100644 src/vllm/model_executor/models/ltx2_video_vae_encoder_load.cpp create mode 100644 tests/vllm/multimodal/ltx2_image_cond_goldens.inc create mode 100644 tests/vllm/multimodal/test_ltx2_image_cond.cpp diff --git a/.agents/specs/ltx25-image-conditioning.md b/.agents/specs/ltx25-image-conditioning.md index f88d463ea..a3857447c 100644 --- a/.agents/specs/ltx25-image-conditioning.md +++ b/.agents/specs/ltx25-image-conditioning.md @@ -223,8 +223,10 @@ whose stated reason went stale; each message below names the exact symbol or * **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. -* **Normalizing before resizing.** Same answer to ~1e-7, so every finiteness and - shape check passes; only a value golden sees it. +* **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 + rather than assumed to be covered. * **Applying the conditioning after the noiser.** Produces a pinned first frame that is pinned to the NOISED latent. Shapes, masks and finiteness all pass. * **Inverting the mask (`strength` instead of `1 - strength`).** Renders an @@ -240,8 +242,9 @@ upstream under the pinned SHA and emits `tests/vllm/multimodal/ltx2_image_cond_goldens.inc`. Sections: 1. `resize_and_center_crop` alone, over shapes covering upscale, downscale, - wider-than-target and taller-than-target, including one where `ceil` changes - the answer. + wider-than-target and taller-than-target, including one where `ceil` + disagrees with both `round` and `floor` (§8.1 corrects what that case + actually demonstrates). 2. `load_image_and_preprocess`'s full chain at `crf = 0` (resize then normalize). 3. `VideoEncoder(image)` at reduced dims over deterministic weights. 4. `VideoConditionByLatentIndex.apply_to` over that encoded latent — `clean` and @@ -289,6 +292,103 @@ request identical. * If executed upstream disagrees with a ported stage by more than `kLtx2GoldenTol`, the port is wrong; do not widen the band. -## 8. Now - -`ACTIVE` — spec committed before implementation. +## 8. Outcome — what was measured, and what was refuted + +Recorded here rather than in the code, because none of it is derivable from the +tree. All measurements: CPU Release, gcc, `-ffp-contract=off` (the tree's own +flag), `build-lic`, this box, at the head this spec landed on. + +### 8.1 Three claims in §3 and §5 were WRONG, and are corrected here + +* **The `ceil` reason.** §3.2 repeated upstream's own comment — that `ceil` + guards against `src * scale` landing just above an integer and producing a + negative crop offset. Swept every source/target pair in `3..40 -> {16, 24}`: + **no pair does that** in IEEE double. The `ceil` is still load-bearing, for + the ordinary reason that it disagrees with `round` and `floor` at a + non-integer scale (case 1, `32 * 16/24 = 21.333` → 22 rows and a 3-row crop, + against 21 and a 2-row crop), and the generator asserts THAT rather than the + claim upstream makes. +* **A golden can see the resize/normalize ORDER.** It cannot, and the reason is + structural: resize is a convex combination and normalize is affine, so the two + orders are equal in exact arithmetic and their f32 gap is pure rounding — + **1.94e-07 measured**, below the golden band and below this port's own distance + from torch. It cannot be amplified by choosing a different image. The order is + mirrored because it is upstream's; that is now written down in three places + rather than assumed to be covered, and `kLtx2ImgPreOrderGap` asserts the gap + stays below the band so a future change that makes it gateable is visible. +* **The port can match torch's bilinear bit for bit.** It cannot, portably. The + index map and the lambdas were probed with basis images and match EXACTLY + (`0.61111104`, `0.35185182`, `0.09259248` … reproduced to the bit). The + residual is in the ACCUMULATION and appears on an output element whose width + weights are `(1, 0)` — a pure two-term `a*h0 + c*h1` — which rules out + dimension order. Plain-f32, f64-accumulate and premultiplied-weight orderings + all land 1 ulp away on the same elements: FMA contraction inside torch's + kernel, which this tree compiles with `-ffp-contract=off` and so cannot + reproduce. Hence `kLtx2ImgPixelTol`. + +### 8.2 The bands, derived rather than picked + +Measured by setting both to `1e-12` and reading the reported `worst`: + +| section | space | worst | band | +|---|---|---|---| +| 1 resize | 0..255 | `6.10352e-05` (identity case: 0) | `kLtx2ImgPixelTol = 2e-4` | +| 2 preprocess | [-1, 1] | `4.76837e-07` | `kLtx2ImgGoldenTol = 2e-6` | +| 3 encoded latent | latent | `2.68221e-07` | same | +| 4 conditioned clean | latent | `2.68221e-07` | same | +| 5 noised latent | latent | `1.78814e-07` | same | + +### 8.3 Mutation evidence — RED, with counts + +Each applied to the tree, rebuilt, run, and restored byte-for-byte (md5 checked). +Green baseline: `test_ltx2_image_cond` **15 cases / 198 assertions**, +`test_ltx2_video` **32 cases / 550 assertions**, both exit 0. + +| mutation | file | result | +|---|---|---| +| `ceil` → `llround` in the resize | `ltx2_image_preprocess.cpp` | image_cond 14/15, 197/198, exit 1 | +| encoder key rules → the DECODER's | `ltx2_video_vae_encoder_load.cpp` | image_cond 14/15, 195/198; video 31/32 (THREW, assertions 517 — the COUNT itself moved) | +| mask `1 - strength` → `strength` | `ltx2_conditioning.cpp` | image_cond 13/15, 193/198, exit 1 | +| latent width from `vae.out_channels` | `ltx2_video_vae_encoder_load.cpp` | image_cond 14/15, 192/198; video 9/32, 36/53 | +| CRF refusal removed | `ltx2_image_preprocess.cpp` | image_cond 14/15, 193/198; video 31/32 | +| encode the image, never PLACE it | `ltx2_video.cpp` | video 31/32, 548/550, exit 1 | + +### 8.4 A mutation that SURVIVED, and what that says + +`video.latent = state.clean` inserted after the placement left `test_ltx2_video` +fully green (32/32, 550/550). Two findings, and the first is not a gap: + +1. **Phase 0 runs at `noise_scale = 1.0`** (`ltx2_pipeline.cpp:1068`), where + `lerp(latent, noise, 1)` discards `latent` entirely — so on that phase the + mutation is genuinely inert. AGENTS.md's rule applies: a mutation that moves + nothing is not evidence of unreachability. It IS live on phase 1 + (`noise_scale = 0.909375`). +2. **`test_ltx2_video` gates no VALUE of the composed latent.** It gates that the + conditioning is placed, that it depends on the image, and that it depends on + the ENCODER'S OWN WEIGHTS — not the arithmetic. That arithmetic is gated + against executed upstream in `test_ltx2_image_cond`, over the identical + functions. This is recorded as a named residual rather than closed with an + unanchored digest, which would detect change without pinning anything. + +That mutation is also what caused `Ltx2ConditioningTrace::image_digest` to be +taken over the TOKENS AS WRITTEN rather than over the encoder's output: the +first version digested `encoded.data`, which stays healthy for a build that +encodes an image and never places it. The "never place it" mutation above is RED +only because of that change. + +### 8.5 Why the defaults are what they are + +* **`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 + 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. + +## 9. Now + +`ACTIVE` — spec committed before implementation; implementation landed with the +outcome above. diff --git a/CMakeLists.txt b/CMakeLists.txt index e166d11b2..5ec949705 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -806,6 +806,13 @@ add_library(vllm STATIC # LTX-2.5 (ROW MODEL-DIFFUSION-LTX25, phase L11): the conditioning items that # place the VAE ENCODERS' output into the denoise state. src/vllm/model_executor/models/ltx2_conditioning.cpp + # LTX-2.5 (ROW LTX25-IMAGE-COND, issue #644): the video VAE ENCODER's LOAD + # path — key filter and config parser, which existed nowhere before — and the + # pixel front-end an image conditioning arrives through. Separate TUs rather + # than additions to ltx2_loader.cpp, which two concurrent rows of the same + # campaign need to edit. + src/vllm/model_executor/models/ltx2_video_vae_encoder_load.cpp + src/vllm/model_executor/models/ltx2_image_preprocess.cpp # LTX-2.5 (ROW MODEL-DIFFUSION-LTX25, phase L6): the quantized loaders — the # FP8 and torchao-NVFP4 DiT arms, the torchao-NVFP4 text encoder, and the # load-time device staging GB10's ATS penalty makes the default. diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 7586e32ac..1b7e6b4e3 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. Image chain PPM->resize->encode->place->noise vs executed upstream. Prompt->Gemma-4->cross-attn FIXTURE-gated; render OWED | Family `ltx-2.5` via `ltx2-gen`. ~29 GB NVFP4/GB10, FP8 ~44 GB, +~24 GB tower. FP8/torchao/NVFP4 load. IMAGE cond SERVED at image_crf=0 (out of distribution; resolved 18 refuses). DiffVAE, LoRA, keyframe/ref refused | | 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..41b5d50d5 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -385,16 +385,29 @@ the embeddings connector) are implemented and gated. Several limits decide what you can actually ask for, and each refuses by name rather than rendering something else. -In particular, the encoders being present does NOT mean image, keyframe, -reference-video or reference-audio conditioning is usable: the video engine -still refuses every one of those by name, because the request-side work between -a file on disk and a tensor the encoder accepts — image decode, aspect-fill -resize, and the H.264 CRF re-compression upstream performs before encoding -whenever the resolved CRF is not `0` and the image is at least 2 pixels on its -shorter side — is not ported. The engine also holds no -encoder to call: it materializes the VAE DECODER key filters only, so no -encoder weights are ever in memory, and the refusal names that rather than -claiming the encoder itself is missing. Two encoder-level limits are worth +**Image conditioning (image-to-video) runs at `image_crf=0`, and only there.** +Pass a first frame as binary PPM (`first_frame_path` / `first_frame_ppm`) plus +the per-generation extra `image_crf=0`; the engine decodes it, aspect-fills and +centre-crops it to each phase's own resolution, VAE-encodes it, and replaces +latent frame 0's clean tokens. `noise_aug` is the pinning strength (`1.0`, the +default, pins the frame exactly). + +`image_crf=0` must be asked for **explicitly**, and it is **out of +distribution**. Upstream re-compresses a conditioning image through H.264 at the +CRF the checkpoint's generation was trained with, and an LTX-2.5 checkpoint +resolves that to **18**. That round trip needs libx264 and no codec is vendored +here, so a non-zero CRF — including the default a caller gets by saying nothing — +is refused by name. `image_crf=0` is upstream-legal (upstream short-circuits it +and documents an explicit `0` as "skip re-compression entirely") but conditions +the model on pixels it was not trained to see. That is a render-quality cost, and +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 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 @@ -437,7 +450,11 @@ refused, because a stream left unconditioned renders instead of failing. returns the trace of the last `Generate()` — whether the conditioning came from a prompt or from embeds, the prompt string, the row count and both stream widths, an FNV-1a digest over the exact f32 buffers cross-attention read, and each stream's -absmax. It is returned **by value, under the engine's own lock**, so it is safe to +absmax. When the request carried an image it also reports the CRF and strength it +was conditioned at, how many tokens the encoded image replaced, and a digest over +**those tokens as written into the state** — not over the encoder's output, so a +build that encoded an image and never placed it reads as unconditioned rather +than healthy. It is returned **by value, under the engine's own lock**, so it is safe to call from a server thread while another thread renders — but `Generate` holds that same lock for the WHOLE render, so such a call blocks for minutes rather than returning a stale answer immediately. `completed` is true only if that @@ -491,6 +508,11 @@ ltx2-gen --dit ltx-2.5-22b-distilled-fp8.safetensors \ Swap the two `--encoder*` flags and `--prompt` for `--prompt-embeds` + `--audio-prompt-embeds` to condition from files instead. +Add `--first-frame frame.ppm --image-crf 0` for image-to-video. The PPM is +binary P6 at maxval 255 (no PNG/JPEG codec is vendored); `--image-crf 0` is +required and is not the default, because omitting it resolves the checkpoint's +own CRF 18 and refuses — see the out-of-distribution note above. + `--frames` must satisfy `(frames - 1) % 8 == 0` and width/height must divide by 64 (32 for the VAE, twice that because the distilled recipe's first phase runs at half resolution). Omitting all three renders the recipe default, which is diff --git a/examples/ltx2_gen/main.cpp b/examples/ltx2_gen/main.cpp index e17bb6953..28ed2cc0a 100644 --- a/examples/ltx2_gen/main.cpp +++ b/examples/ltx2_gen/main.cpp @@ -100,6 +100,7 @@ const char* Need(int argc, char** argv, int i, const char* flag) { " [--max-phase N] [--allow-unported]\n" " [--prompt-valid-rows N] how many embed rows are real tokens\n" " [--frames N] [--width N] [--height N] [--seed N]\n" + " [--first-frame ] [--image-crf 0]\n" " [--device cpu|cuda]\n\n" "Renders LTX-2.5 (family \"ltx-2.5\") through vllm_video_engine_load +\n" "vllm_video_generate.\n\n" @@ -121,7 +122,17 @@ const char* Need(int argc, char** argv, int i, const char* flag) { "weights. The row count must then be a multiple of the connector's learnable\n" "register count (128 on the shipped files), and --prompt-valid-rows says how\n" "many of them are real: the rest are padding, and padding is REPLACED by the\n" - "learnable register table rather than ignored.\n"); + "learnable register table rather than ignored.\n\n" + "IMAGE CONDITIONING (image-to-video). --first-frame takes a binary PPM (P6,\n" + "maxval 255) and pins latent frame 0 to it: it is decoded, aspect-filled and\n" + "centre-cropped to each phase's own resolution, VAE-encoded, and written into\n" + "the clean latent. It needs --image-crf 0, and that is DELIBERATELY not the\n" + "default. Upstream re-compresses a conditioning image through H.264 at the CRF\n" + "the checkpoint's generation was trained with, which for LTX-2.5 is 18; that\n" + "round trip needs libx264 and none is vendored here, so leaving --image-crf out\n" + "resolves 18 and REFUSES by name. --image-crf 0 is upstream-legal and OUT OF\n" + "DISTRIBUTION: the model sees pixels it was not trained on. That is a quality\n" + "cost, and this tool states it rather than turning it on quietly.\n"); std::exit(code); } @@ -133,7 +144,7 @@ int main(int argc, char** argv) { std::string workdir = "/tmp/ltx2_gen", out_path, ffmpeg = "ffmpeg", device = "cuda"; // BORROWED by `vllm_video_generate`, like the extras below, so it is owned // here and pointed at only after parsing. - std::string prompt; + std::string prompt, first_frame, image_crf; // The extras are BORROWED by the load call, so the strings must outlive it. // Kept as two parallel vectors of owned strings plus the char* views the ABI @@ -174,6 +185,14 @@ int main(int argc, char** argv) { else if (f == "--prompt-valid-rows") SetExtra("prompt_embeds_valid_rows", Need(argc, argv, ++i, f.c_str())); else if (f == "--allow-unported") SetExtra("allow_unported_modules", "1"); + // Image conditioning (row LTX25-IMAGE-COND, issue #644). `--first-frame` is + // a binary PPM; `--image-crf` is the PER-GENERATION extra, so it rides + // vp.extra_* rather than mp.extra_*. Only 0 is served, and it is NOT + // defaulted here — leaving it out lets the engine resolve the checkpoint's + // own 18 and refuse, which is the point: this CLI must not be the thing that + // quietly turns an out-of-distribution render on. + else if (f == "--first-frame") first_frame = Need(argc, argv, ++i, "--first-frame"); + else if (f == "--image-crf") image_crf = Need(argc, argv, ++i, "--image-crf"); else if (f == "--device") device = Need(argc, argv, ++i, "--device"); else if (f == "--frames") vp.num_frames = std::atoi(Need(argc, argv, ++i, "--frames")); else if (f == "--width") vp.width = std::atoi(Need(argc, argv, ++i, "--width")); @@ -203,6 +222,27 @@ int main(int argc, char** argv) { mp.family = "ltx-2.5"; vp.output_dir = workdir.c_str(); if (!prompt.empty()) vp.prompt = prompt.c_str(); + if (!first_frame.empty()) vp.first_frame = first_frame.c_str(); + + // The PER-GENERATION extras are a SEPARATE array from the load-time ones, and + // conflating them is the whole failure this keeps apart: `image_crf` handed to + // the load call is an unknown LOAD extra and is refused there, which would + // read as "the flag does not work" rather than as "it goes on the other call". + std::vector gen_keys, gen_values; + if (!image_crf.empty()) { + gen_keys.emplace_back("image_crf"); + gen_values.push_back(image_crf); + } + std::vector gkeys, gvalues; + for (size_t i = 0; i < gen_keys.size(); ++i) { + gkeys.push_back(gen_keys[i].c_str()); + gvalues.push_back(gen_values[i].c_str()); + } + if (!gkeys.empty()) { + vp.extra_keys = gkeys.data(); + vp.extra_values = gvalues.data(); + vp.n_extras = static_cast(gkeys.size()); + } std::vector keys, values; keys.reserve(extra_keys.size()); diff --git a/include/vllm.h b/include/vllm.h index f372c9fd5..84104f5a6 100644 --- a/include/vllm.h +++ b/include/vllm.h @@ -763,8 +763,16 @@ typedef struct vllm_video_params { /* Where frame_%06d.ppm + audio.wav land (created if absent). REQUIRED. */ const char* output_dir; /* v18: FAMILY-SPECIFIC per-generation settings, same parallel-array shape as - * the load-time extras. MiniMax-H3 defines none, and refuses any key it does - * not know rather than ignoring it. 0 => none. */ + * the load-time extras. Every family refuses a key it does not know rather + * than ignoring it. 0 => none. + * MiniMax-H3: none. + * LTX-2.5: "image_crf" — the H.264 CRF an image conditioning is + * re-compressed at. Only "0" is served; an LTX-2.5 checkpoint + * RESOLVES 18 when this is absent and the codec round trip is + * unported, so leaving it out refuses BY NAME rather than + * rendering. "0" is upstream-legal and out of distribution; + * see docs/USAGE.md. No ABI change was needed for it, which is + * what this parallel-array shape exists for. */ const char* const* extra_keys; const char* const* extra_values; int32_t n_extras; diff --git a/include/vllm/model_executor/models/ltx2_image_preprocess.h b/include/vllm/model_executor/models/ltx2_image_preprocess.h new file mode 100644 index 000000000..36936289a --- /dev/null +++ b/include/vllm/model_executor/models/ltx2_image_preprocess.h @@ -0,0 +1,103 @@ +// LTX-2.5 IMAGE CONDITIONING INPUT — pixels to the VAE encoder's [-1, 1] space. +// +// Row: LTX25-IMAGE-COND. Spec: .agents/specs/ltx25-image-conditioning.md §3.2. +// Issue #644. +// +// ─── WHAT THIS IS A PORT OF (file:line on BOTH sides) ──────────────────────── +// Upstream root: Lightricks/LTX-2 @ fd4ded7f, +// packages/ltx-pipelines/src/ltx_pipelines/utils/ +// OURS <- UPSTREAM +// Ltx2LoadImageAndPreprocess <- media_io/decode.py:46-79 +// Ltx2PreprocessImageCrf <- media_io/decode.py:413-435 (`preprocess`) +// Ltx2DecodePpmRgb <- media_io/decode.py:139-170 (`decode_image`) +// Ltx2ResizeAndCenterCrop <- media_io/resize.py:41-73 +// (the /127.5 - 1 map) <- media_io/range_map.py:8-9 +// Ltx2ResolveDefaultImageCrf <- constants.py:36-37, 124, 126-133 +// + blocks.py:966-983 (`ImageConditioner`) +// +// ─── THE ORDER IS LOAD-BEARING ─────────────────────────────────────────────── +// `load_image_and_preprocess` resizes in 0..255 SPACE and normalizes AFTER +// (decode.py:76-78). Bilinear interpolation is affine, so the two commute in +// exact arithmetic and NOT in floating point. This project's other PPM reader +// (`minimax_h3_video.cpp:87-120`) normalizes at decode time, which is why this +// file has its own decoder rather than calling that one: reusing it would put +// the affine map first, and no shape or finiteness check could see it. +// +// ─── WHAT IS DELIBERATELY NOT PORTED, AND WHAT THAT COSTS ──────────────────── +// * THE H.264 ROUND TRIP. `preprocess` re-compresses at the checkpoint's +// `default_image_crf` to match the compression the model was trained against +// (decode.py:413-435 -> encode_single_frame:386-400, libx264 preset=veryfast, +// rgb24 -> yuv420p, dimensions truncated to even). No codec is vendored here, +// so a non-zero CRF is REFUSED BY NAME. `crf == 0` short-circuits upstream at +// :425-426 and is served — it is upstream-legal ("including ``0`` to skip +// re-compression entirely", args.py:58-59) and OUT OF DISTRIBUTION for a 2.5 +// checkpoint, whose resolved default is 18. Both halves of that are said out +// loud rather than one of them. +// * PNG / JPEG / EXR. Only binary PPM (P6) is read — the same NAMED residual +// minimax_h3_video.cpp:84-86 already carries for this tree. EXIF orientation +// and ICC -> sRGB conversion (decode.py:143-168) have nothing to act on in a +// PPM, so they are absent rather than dropped. +// * A `maxval` other than 255. PIL rescales those; mirroring its rescale is a +// separate port, so they are refused rather than scaled by a rule nobody +// checked against PIL. +#pragma once + +#include +#include +#include + +namespace vllm { + +// `decode_image` (decode.py:139-170) for the one container this tree reads: +// binary PPM (P6), `maxval == 255`, into a uint8 [H, W, 3] buffer — the exact +// shape and dtype `np.array(image, dtype=np.uint8)` returns at :170. +std::vector Ltx2DecodePpmRgb(const std::string& field, const std::string& bytes, + int64_t* out_height, int64_t* out_width); + +// `preprocess` (decode.py:413-435). Identity at `crf == 0` (:425-426); THROWS by +// name at any other value, naming the unported codec round trip. `crf < 0` is +// refused too: upstream's only unset spelling is `None`, which +// `ImageConditioner.resolve_crf` fills in before `preprocess` ever sees it +// (blocks.py:977-983), so a negative value is not "unset" in any upstream sense. +void Ltx2PreprocessImageCrf(int64_t crf); + +// `resize_and_center_crop` (resize.py:41-73) for a single image. Takes HWC +// f32 in ANY value space and returns [channels, height, width] — the +// `1 c f h w` upstream emits at f = 1, with the two singleton axes dropped. +// +// Aspect FILL then centre crop: `scale = max(height/src_h, width/src_w)`, then +// `ceil` (upstream's own comment: avoids a negative crop offset from +// floating-point rounding), then bilinear with `align_corners=False`, then +// `crop_top = (new_h - height) // 2`. +// +// The bilinear kernel mirrors PyTorch's index map in f32, which is the dtype +// `HelperInterpLinear::compute_indices_weights` dispatches at for a float +// tensor: `real = scale * (i + 0.5) - 0.5` clamped to >= 0, `idx = floor(real)` +// capped at `src - 1`, `lambda = clamp(real - idx, 0, 1)`, right tap +// `min(idx + 1, src - 1)`. HEIGHT is the OUTER sum and WIDTH the inner one, +// matching `Interpolate::eval`'s recursion order over the dimensions +// `upsample_generic_Nd_kernel_impl` appends in. +std::vector Ltx2ResizeAndCenterCrop(const float* hwc, int64_t src_height, + int64_t src_width, int64_t channels, int64_t height, + int64_t width); + +// `load_image_and_preprocess` (decode.py:46-79) end to end: decode -> CRF -> +// f32 0..255 -> resize+crop -> `/127.5 - 1.0`. Returns [3, height, width] in +// [-1, 1], which is what `Ltx2ConvVideoEncode` takes at `frame_count = 1`. +std::vector Ltx2LoadImageAndPreprocess(const std::string& field, const std::string& bytes, + int64_t height, int64_t width, int64_t crf); + +// `PipelineParams.default_image_crf` as `detect_params` resolves it +// (constants.py:126-133): the newest generation row at or below the checkpoint's +// `model_version`, so an unrecognised NEWER version inherits the closest known +// one rather than falling back to 2.0's. Today that is `(2, 4) -> 18` +// (LTX_2_4_IMAGE_CRF, :37, :124) and everything older -> 33 (DEFAULT_IMAGE_CRF, +// :36). `components` is `Ltx2ParseModelVersion`'s output; an EMPTY one compares +// below every row, exactly as `detect_model_version` documents at :137-140. +// +// This is what makes the CRF refusal a real one rather than a formality: an +// LTX-2.5 request that does not name a CRF resolves 18 and is refused, and the +// caller has to ask for 0 knowingly. +int64_t Ltx2ResolveDefaultImageCrf(const std::vector& version_components); + +} // namespace vllm 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 65faa1a29..cbde9c232 100644 --- a/include/vllm/model_executor/models/ltx2_video_vae_encoder.h +++ b/include/vllm/model_executor/models/ltx2_video_vae_encoder.h @@ -62,6 +62,7 @@ #include #include "vllm/model_executor/models/ltx2_audio_vae.h" // Ltx2VaeWeights +#include "vllm/model_executor/models/ltx2_loader.h" // Ltx2VaeKeyRule, nlohmann::json #include "vllm/model_executor/models/ltx2_upsampler.h" // Ltx2LatentVolume #include "vllm/model_executor/models/ltx2_video_vae.h" // Ltx2NormLayer, Ltx2PaddingMode @@ -180,4 +181,46 @@ Ltx2LatentVolume Ltx2ConvVideoEncode(const Ltx2ConvVideoEncoderConfig& config, int64_t frame_count, int64_t height, int64_t width, int64_t* out_cropped_frames = nullptr); +// ─── THE DELIVERY ROUTE (row LTX25-IMAGE-COND, issue #644) ─────────────────── +// +// Everything above is the encoder's MATH, and it landed in phase L11 with +// goldens. What did not land is any way to reach it from a checkpoint: until +// this row, `Ltx2VideoVaeEncoderKeyRules` matched NOWHERE in the tree and no +// parser produced an `Ltx2ConvVideoEncoderConfig` from a `vae` config object. +// `ltx2_video.cpp:752` materialized `Ltx2VideoVaeDecoderKeyRules()` alone, so +// the encoder was a brick with no delivery route and every conditioning arm +// refused for that reason. These three declarations are that route. + +// `VAE_ENCODER_COMFY_KEYS_FILTER` (video_vae/model_configurator.py:267-276), in +// the same first-match-wins prefix form `Ltx2VideoVaeDecoderKeyRules` uses. +// +// `per_channel_statistics.` IS IN BOTH FILTERS UPSTREAM and must be in both +// here: `Ltx2ConvVideoEncode` divides its conv output by +// `per_channel_statistics.std-of-means` (video_vae.py:336), so an encoder bag +// filtered without them cannot produce a latent in the DiT's space at all — it +// throws on the missing key rather than emitting an unnormalized one, which is +// the one mercy in this arrangement. +std::vector Ltx2VideoVaeEncoderKeyRules(); + +// Does this checkpoint carry an ENCODER half? A Comfy-split `vae/` file may hold +// the decoder alone, and the answer decides whether image conditioning is served +// or refused BY NAME. Asked of the file's declared tensor names only; no payload +// is read. +bool Ltx2CheckpointHasVideoEncoder(const std::vector& tensor_names); + +// `VideoEncoderConfigurator.from_metadata` (model_configurator.py:72-78) over +// `_prepare_video_encoder_kwargs` (:37-69), key for key. +// +// THE TWO FIELDS THAT FAIL SILENTLY IF READ FROM THE WRONG PLACE: +// * the LATENT WIDTH. On a flat `CausalVideoAutoencoder` config it is +// `vae.latent_channels`; the top-level `vae.out_channels` is the DECODER's +// RGB count and reading it builds a 3-channel-latent encoder that still runs +// (: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 +// 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); + } // namespace vllm diff --git a/include/vllm/multimodal/ltx2_video.h b/include/vllm/multimodal/ltx2_video.h index fb683ce54..44877d8df 100644 --- a/include/vllm/multimodal/ltx2_video.h +++ b/include/vllm/multimodal/ltx2_video.h @@ -225,6 +225,28 @@ inline constexpr char kLtx2PromptValidRowsExtra[] = "prompt_embeds_valid_rows"; // resolved in either direction, for the same reason. inline constexpr char kLtx2EncoderConfigPathExtra[] = "encoder_config_path"; +// ── the PER-GENERATION extra (VideoGenParams::extras) ─────────────────────── + +// The H.264 CRF the image conditioning is re-compressed at, `ImageConditioner`'s +// `resolve_crf` (ltx-pipelines/utils/blocks.py:977-983). Row LTX25-IMAGE-COND, +// issue #644. +// +// ABSENT MEANS "WHAT THE MODEL WAS TRAINED WITH", which for an LTX-2.5 +// checkpoint is **18** — `detect_params` maps a version at or above `(2, 4)` +// onto `LTX_2_4_PARAMS` and its `LTX_2_4_IMAGE_CRF` (utils/constants.py:37, +// 124, 130-133). And that round trip is NOT ported: it needs libx264 +// (media_io/decode.py:430-434 -> encode_single_frame:386-400) and no codec is +// vendored here. So the DEFAULT REFUSES, by name. +// +// `image_crf=0` is the supported value and is served. It is upstream-legal — +// `preprocess` short-circuits at `if crf == 0: return image` (decode.py:425-426) +// and an explicit 0 is documented as "skip re-compression entirely" +// (utils/args.py:58-59) — and it is OUT OF DISTRIBUTION, because the model was +// trained on images that had been through the codec. Both halves are said out +// loud rather than one of them: a caller has to ask for 0 knowingly, and gets a +// render conditioned on uncompressed pixels rather than a refusal. +inline constexpr char kLtx2ImageCrfExtra[] = "image_crf"; + // WHAT THE LAST `Generate()` ACTUALLY HANDED THE DiT's CROSS-ATTENTION. // // Every field is read off the exact f32 buffers `Ltx2ModalityInput::context` @@ -256,16 +278,22 @@ inline constexpr char kLtx2EncoderConfigPathExtra[] = "encoder_config_path"; // * the conditioning rows REVERSED, putting every caption row on the wrong // token, // -// and BOTH passed `test_ltx2_video` at 30 cases / 499 assertions with exit 0. -// The digest moved, as it must — but no assertion says WHICH value it should -// have moved to. -// -// THAT COUNT IS THIS HEAD'S, and the distinction is the point of writing it -// down. A reviewer first measured the pair at `43aa58377`, where the suite stood -// at 485 assertions; the numbers were carried forward unchanged while the suite -// grew, so the comment named a count no run of it could produce. Re-run here -// (CPU Release, mutant recompiled and relinked each leg, tree restored -// byte-for-byte and re-verified green between legs): 499/499, exit 0, both. +// and BOTH passed `test_ltx2_video` with exit 0 — at 30 cases / 499 assertions +// when the pair was last re-run. The digest moved, as it must — but no assertion +// says WHICH value it should have moved to. +// +// THE COUNT IS DELIBERATELY NOT RESTATED AS A CURRENT FIGURE, and the reason is +// the history: a reviewer first measured the pair at `43aa58377`, where the +// suite stood at 485 assertions; the numbers were carried forward unchanged +// while the suite grew, so the comment named a count no run of it could produce. +// It was re-measured at 499 (CPU Release, mutant recompiled and relinked each +// leg, tree restored byte-for-byte and re-verified green between legs) — and +// then row LTX25-IMAGE-COND (#644) added the image-conditioning cases and the +// suite moved to 32 / 550, which is exactly how the previous number went stale +// the first time. A count in a header is a MEASUREMENT OF ANOTHER FILE stored +// here, which AGENTS.md §Records names as the thing that couples every PR to +// lines it does not own. What survives is the finding — the mutations passed — +// and the SHA-dated measurement above it; `ctest` is the authority on the count. // // THE VALUE ORACLE THE COMPOSITION IS OWED. The per-brick oracles are real and // strong: the Gemma-4 tower against a running `transformers` at a measured bf16 @@ -302,6 +330,30 @@ struct Ltx2ConditioningTrace { // two prompts the SAME digest and RED any dependence check, but it would do so // for the wrong reason; this says which happened. double video_absmax = 0.0, audio_absmax = 0.0; + // ── the IMAGE conditioning (row LTX25-IMAGE-COND, issue #644) ──────────── + // + // Zero everywhere when the request carried no image. `image_tokens` is how + // many of the video stream's tokens the encoded image REPLACED in the clean + // latent, and `image_digest` is FNV-1a over THOSE TOKENS' raw f32 bytes — the + // same instrument, and with the same limits, as the two prompt digests above: + // it detects CHANGE, it does not pin VALUES. + // + // OVER THE TOKENS, NOT OVER THE ENCODER'S OUTPUT, and that choice is load + // bearing: a digest of the encoder's output answers "was an image encoded", + // which stays true of a build that encodes one and then never places it — an + // unconditioned render with a perfectly healthy trace. + // + // IT IS ALSO THE ONLY WAY TO ASK WHETHER THE ENCODER WEIGHTS WERE READ. "The + // conditioning loaded" and "the conditioning was used" are different claims, + // and a render cannot be inspected for either. Perturbing one encoder tensor + // and watching this digest move is what separates them, which is exactly the + // check `test_ltx2_video` runs. + int64_t image_tokens = 0; + uint64_t image_digest = 0; + double image_absmax = 0.0; + int64_t image_crf = 0; // the CRF this render actually preprocessed at + double image_strength = 0.0; // `ImageConditioningInput.strength` (args.py:64) + // True only once the `Generate` that produced this conditioning RETURNED. The // trace is filled immediately after the connector and BEFORE the denoise loop, // because that is the only point at which the exact buffers cross-attention diff --git a/scripts/gen-ltx2-image-cond-goldens.py b/scripts/gen-ltx2-image-cond-goldens.py new file mode 100644 index 000000000..70aa6a6e7 --- /dev/null +++ b/scripts/gen-ltx2-image-cond-goldens.py @@ -0,0 +1,698 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/multimodal/ltx2_image_cond_goldens.inc — the LTX-2.5 IMAGE +CONDITIONING parity oracle. Row LTX25-IMAGE-COND, issue #644. + +Spec: .agents/specs/ltx25-image-conditioning.md §5. + +WHAT THIS GATES, and why it is a separate generator from +`scripts/gen-ltx2-vae-goldens.py`. That script gates the VAE BRICKS. This one +gates the CHAIN a conditioning image travels: pixels -> aspect-fill resize -> +normalize -> VideoEncoder -> VideoConditionByLatentIndex -> GaussianNoiser. Every +link but the first two already had a golden; the chain did not, and a chain whose +links are each green can still be wired in the wrong ORDER — which for this +particular chain is exactly the defect that survives every shape and finiteness +check (§4 of the spec). + +Upstream sources (Lightricks/LTX-2): + ltx-pipelines/.../utils/media_io/resize.py:41-73 -> section 1, 2 + ltx-pipelines/.../utils/media_io/range_map.py:8-9 -> section 2 + ltx-pipelines/.../utils/media_io/decode.py:413-435 -> section 2 (the crf==0 branch) + ltx-core/.../model/video_vae/video_vae.py:148-336 -> section 3 + ltx-core/.../conditioning/types/latent_cond.py:22-43 -> section 4 + ltx-core/.../components/noisers.py:30-37 -> section 5 + +Usage: + python3 scripts/gen-ltx2-image-cond-goldens.py \\ + --ltx2 ~/_git/LTX-2 \\ + --out tests/vllm/multimodal/ltx2_image_cond_goldens.inc + +Needs torch + numpy + einops (CPU only). NO checkpoint and no gated download. + +UPSTREAM REVISION ANCHOR, and a DIRTY-TREE REFUSAL, exactly as +gen-ltx2-vae-goldens.py has them and for the identical reason: without a SHA +nobody can tell a PORT drift from an UPSTREAM one, and `git rev-parse HEAD` on a +dirty tree stamps a clean anchor onto numbers that commit cannot reproduce. + + Pinned revision: fd4ded7f2d88d3da713abcdd4ad41ecc4a9314ca + +Advancing the pin is a deliberate edit in BOTH places (here and +`kLtx2ImgCondUpstreamRevisionPin` in tests/vllm/multimodal/test_ltx2_image_cond.cpp). + +── THREE HARNESS ADAPTATIONS, all recorded because none changes the math ─────── + +1. `av` AND `OpenImageIO` ARE STUBBED, and the stub is a TRIPWIRE. + `ltx_pipelines.utils.media_io.decode` imports both at module scope, and + neither is installed here. The crf==0 path this row ports never reaches + either — that is its whole point — so they are registered as modules whose + every attribute is a stub object. This is NOT a way to avoid running upstream: + `preprocess(image, 0)` is executed for real and asserted to return the + IDENTICAL object it was given (`result is image`), which no stub can + manufacture, and `preprocess(image, 18)` is executed too and asserted to RAISE + inside the stubbed codec — which proves the branch is live rather than + assumed. A source-text assertion about `if crf == 0` would have been + self-confirming; this is not. + +2. THE PACKAGE `__init__` FILES ARE NOT EXECUTED. `ltx_pipelines/__init__.py` + and its parents pull in the same codec chain. The three package levels are + registered as module objects carrying only `__path__`, so `import + ltx_pipelines.utils.media_io.resize` loads the SUBMODULE from its real file + without running any `__init__.py`. The submodules themselves are executed + verbatim. + +3. `GaussianNoiser._sample_noise` DRAWS FROM THE SHARED STREAM instead of + `torch.randn`. Upstream keys its draw to a `torch.Generator`; the C++ side + consumes the same deterministic stream in the same order. This mirrors the + `torch.randn` patch gen-ltx2-vae-goldens.py already applies to the decoder. + +ORACLE IDENTITY is asserted, not assumed: `ltx_core.__file__` and +`ltx_pipelines...resize.__file__` are both checked to live under `--ltx2` before +anything runs, because a `.pth`, an editable install or a namespace-package +layout would otherwise resolve a DIFFERENT source silently. +""" + +from __future__ import annotations + +import argparse +import math +import re +import subprocess +import sys +import types +from pathlib import Path + +import numpy as np + +_MASK64 = (1 << 64) - 1 + + +# --------------------------------------------------------------------------- +# THE GOLDEN BAND, READ from the C++ suite rather than repeated here — the same +# arrangement gen-ltx2-vae-goldens.py uses, and for the same reason: a literal +# here would be a second definition of one number in a second language, and a +# widened C++ band would leave this generator certifying arms nobody checks +# against. A parse that does not find EXACTLY ONE definition is fatal. +# --------------------------------------------------------------------------- + +_GOLDEN_TOL_SOURCE = ( + Path(__file__).resolve().parents[1] + / "tests" + / "vllm" + / "multimodal" + / "test_ltx2_image_cond.cpp" +) + + +def _read_golden_tol() -> float: + text = _GOLDEN_TOL_SOURCE.read_text(encoding="utf-8") + hits = re.findall(r"^constexpr double kLtx2ImgGoldenTol = ([0-9eE.+-]+);", text, re.M) + if len(hits) != 1: + raise SystemExit( + f"expected EXACTLY ONE `constexpr double kLtx2ImgGoldenTol = ...;` in " + f"{_GOLDEN_TOL_SOURCE}, found {len(hits)} — the generator cannot assert against a " + f"band it cannot resolve" + ) + return float(hits[0]) + + +# --------------------------------------------------------------------------- +# The shared deterministic stream. Byte-for-byte the one +# scripts/gen-ltx2-vae-goldens.py uses and tests/vllm/models/test_ltx2_vae.cpp +# mirrors, so a tensor built by this script and one built by that one from the +# same NAME are the same tensor. +# --------------------------------------------------------------------------- + + +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 ltx_rand(name: str, count: int) -> np.ndarray: + """`count` values uniform in [-1, 1), reproducible from `name` alone.""" + 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 ltx_bytes(name: str, count: int) -> np.ndarray: + """`count` UINT8 codes — a conditioning image is uint8 out of the decoder + (`np.array(image, dtype=np.uint8)`, decode.py:170), and quantizing a float + stream afterwards would gate a different input than the one a real PPM + carries.""" + seed = fnv1a64(name) + return np.array( + [splitmix64((seed + i) & _MASK64) % 256 for i in range(count)], dtype=np.uint8 + ) + + +def param_values(name: str, shape) -> np.ndarray: + """The per-parameter role rule, IDENTICAL to gen-ltx2-vae-goldens.py's. Only + the roles a video ENCODER actually carries are reachable here; the audio and + vocoder roles that script also handles have no counterpart in this chain.""" + count = int(np.prod(shape)) if len(shape) else 1 + rank = len(shape) + if name.endswith("std-of-means"): + return ltx_rand(name, count) * 0.1 + 1.0 + if name.endswith("mean-of-means"): + return ltx_rand(name, count) * 0.1 + if name.endswith(".bias"): + return ltx_rand(name, count) * 0.05 + if rank == 1 and name.endswith(".weight"): + return ltx_rand(name, count) * 0.1 + 1.0 + return ltx_rand(name, count) * 0.1 + + +def fill_from_stream(module, prefix: str = "") -> list[tuple[str, int]]: + import torch + + state = module.state_dict() + manifest: list[tuple[str, int]] = [] + filled = {} + for name, tensor in state.items(): + values = param_values(prefix + name, tuple(tensor.shape)) + filled[name] = torch.from_numpy(values.astype(np.float32)).reshape(tensor.shape) + manifest.append((prefix + name, int(values.size))) + module.load_state_dict(filled, strict=True) + return manifest + + +# --------------------------------------------------------------------------- +# Emit helpers +# --------------------------------------------------------------------------- + + +def _cxx_float(value: float, digits: int) -> str: + if not math.isfinite(value): + raise ValueError(f"refusing to emit non-finite golden value: {value}") + text = f"{value:.{digits}g}" + if "." not in text and "e" not in text and "E" not in text: + text += ".0" + return text + + +def emit_f32(out, name: str, values) -> None: + flat = np.asarray(values, dtype=np.float32).reshape(-1).tolist() + out.write(f"inline constexpr float {name}[] = {{\n") + for i in range(0, len(flat), 6): + chunk = ", ".join(_cxx_float(v, 9) + "f" for v in flat[i : i + 6]) + out.write(" " + chunk + ",\n") + out.write("};\n\n") + + +def emit_scalar(out, name: str, value) -> None: + out.write(f"inline constexpr int64_t {name} = {int(value)};\n") + + +def emit_double(out, name: str, value) -> None: + out.write(f"inline constexpr double {name} = {_cxx_float(float(value), 17)};\n") + + +def emit_manifest(out, name: str, manifest: list[tuple[str, int]]) -> None: + out.write(f"inline constexpr const char* {name}Names[] = {{\n") + for key, _ in manifest: + out.write(f' "{key}",\n') + out.write("};\n") + out.write(f"inline constexpr int64_t {name}Counts[] = {{\n") + for i in range(0, len(manifest), 10): + out.write(" " + ", ".join(str(c) for _, c in manifest[i : i + 10]) + ",\n") + out.write("};\n\n") + + +# --------------------------------------------------------------------------- +# Reduced-dimension geometry +# --------------------------------------------------------------------------- + +# Section 1 — the resize cases. Chosen so that between them they cover: pure +# upscale, pure downscale, a source WIDER than the target aspect, a source +# TALLER than it, an exact identity, and — case 1 — a scale at which `ceil` +# disagrees with BOTH `round` and `int`: `32 * (16/24)` is 21.3333, so upstream +# resizes to 22 rows and crops 3 off the top, while a port that rounded would +# resize to 21 and crop 2. Every value in the output moves, and every shape +# check still passes. That case is asserted below rather than trusted. +# +# MEASURED, and recorded because it corrects what an earlier draft of this +# comment claimed: no source/target pair in this size range makes +# `src * scale` land just ABOVE an integer in IEEE double, which is the +# negative-crop hazard resize.py:61-62 names. The `ceil` is still load-bearing — +# for the ordinary reason above — but not for the reason upstream's comment +# gives, and asserting the reason upstream gives would have been asserting +# something false. +RESIZE_CASES = [ + # (src_h, src_w, dst_h, dst_w) + (12, 20, 16, 16), + (32, 24, 16, 16), + (16, 16, 16, 16), + (8, 8, 16, 24), + (10, 7, 16, 16), +] + +# Section 2-5 — the conditioning chain. The image is a 12x20 PPM (case 0's +# geometry, so the `ceil` branch is LIVE on the path that actually renders) and +# the target is 16x16, which the encoder's (2 patch x 2 x 2) spatial factor of 8 +# turns into a 2x2 latent. +IMAGE_SRC_H, IMAGE_SRC_W = 12, 20 +IMAGE_DST_H, IMAGE_DST_W = 16, 16 + +IMG_ENC_BLOCKS = [ + ("res_x", {"num_layers": 1}), + ("compress_space_res", {"multiplier": 2}), + ("compress_all_res", {"multiplier": 1}), +] +IMG_ENC = dict(convolution_dimensions=3, in_channels=3, out_channels=4, patch_size=2) + +# The target latent the conditioning is placed into: 3 latent frames of the +# encoder's own (channels, height, width), conditioned at index 0 — which is what +# `combined_image_conditionings` selects for `frame_idx == 0` +# (ltx-pipelines/utils/helpers.py:295-300). +COND_TARGET_FRAMES = 3 +COND_PATCH = 1 +COND_FPS = 8.0 +COND_STRENGTH = 0.7 +COND_LATENT_IDX = 0 + +# A NON-UNIT noise scale, deliberately. `noise_scale == 1` is the ONLY value at +# which ltx_core's composition and diffusers' agree (spec §3.3), so gating there +# would leave the divergence invisible. +NOISE_SCALE = 0.625 + + +# --------------------------------------------------------------------------- +# Sections +# --------------------------------------------------------------------------- + + +def section_resize(out, resize_mod) -> None: + import torch + + out.write( + "// --- section 1: resize_and_center_crop (media_io/resize.py:41-73) ---\n" + "// Aspect FILL, `ceil`, bilinear align_corners=False, then centre crop.\n" + ) + emit_scalar(out, "kLtx2ImgResizeCases", len(RESIZE_CASES)) + out.write("\n") + for index, (src_h, src_w, dst_h, dst_w) in enumerate(RESIZE_CASES): + codes = ltx_bytes(f"ltx2.imgcond.resize{index}", src_h * src_w * 3) + hwc = torch.from_numpy(codes.reshape(src_h, src_w, 3).astype(np.float32)) + y = resize_mod.resize_and_center_crop(hwc, dst_h, dst_w) + # `1 c f h w` with f == 1 for a 3-D input (resize.py:73). + assert tuple(y.shape) == (1, 3, 1, dst_h, dst_w), f"unexpected resize shape {y.shape}" + out.write(f"// case {index}: {src_h}x{src_w} -> {dst_h}x{dst_w}\n") + emit_scalar(out, f"kLtx2ImgResize{index}SrcH", src_h) + emit_scalar(out, f"kLtx2ImgResize{index}SrcW", src_w) + emit_scalar(out, f"kLtx2ImgResize{index}DstH", dst_h) + emit_scalar(out, f"kLtx2ImgResize{index}DstW", dst_w) + out.write("\n") + emit_f32(out, f"kLtx2ImgResize{index}Golden", y.numpy()) + + # The `ceil` is asserted rather than described. At least one case must have a + # dimension where ceil disagrees with BOTH round and floor, or nothing in + # this section can tell those three ports apart. + separating = [] + for index, (src_h, src_w, dst_h, dst_w) in enumerate(RESIZE_CASES): + scale = max(dst_h / src_h, dst_w / src_w) + for axis, src in (("h", src_h), ("w", src_w)): + exact = src * scale + if math.ceil(exact) != round(exact) and math.ceil(exact) != math.floor(exact): + separating.append((index, axis, exact)) + assert separating, ( + "no resize case has a dimension where `ceil` disagrees with both `round` and `floor`, " + "so section 1 cannot tell those three ports apart. Add a shape pair that does rather " + "than deleting this assertion" + ) + + +def section_preprocess(out, resize_mod, range_mod, decode_mod, tol) -> None: + import torch + + codes = ltx_bytes("ltx2.imgcond.image", IMAGE_SRC_H * IMAGE_SRC_W * 3) + image = codes.reshape(IMAGE_SRC_H, IMAGE_SRC_W, 3) + + # UPSTREAM'S OWN `preprocess`, EXECUTED. `result is image` is the assertion + # that matters: the crf==0 branch RETURNS THE ARGUMENT (decode.py:425-426), + # and no codec stub can produce object identity. + passed = decode_mod.preprocess(image=image, crf=0) + assert passed is image, ( + "preprocess(crf=0) did not return its argument — the short-circuit this whole row " + "is built on (decode.py:425-426) is not where the spec says it is" + ) + + # ...and the OTHER branch is live. Executed too, so "a non-zero CRF needs a + # codec" is measured rather than read. + reached_codec = False + try: + decode_mod.preprocess(image=image, crf=18) + except Exception: # noqa: BLE001 - the stubbed codec is what raises + reached_codec = True + assert reached_codec, ( + "preprocess(crf=18) did NOT reach the stubbed codec, so either the round trip moved or " + "the stub is answering for it — either way the CRF refusal this row ships would be " + "guarding nothing" + ) + + # decode.py:76-78, in order: f32 in 0..255 -> resize -> normalize. + as_float = torch.from_numpy(passed.astype(np.float32)) + resized = resize_mod.resize_and_center_crop(as_float, IMAGE_DST_H, IMAGE_DST_W) + normalized = range_mod.normalize_images(resized, device="cpu", dtype=torch.float32) + + # THE ORDER IS A CLAIM, so it is MEASURED — and the measurement is a NEGATIVE + # RESULT that is recorded rather than engineered away. + # + # `resize` is a convex combination and `normalize` is affine, so + # resize(normalize(x)) and normalize(resize(x)) are EQUAL in exact + # arithmetic. Their f32 difference is pure rounding and CANNOT be amplified + # by choosing a different image: measured at 1.94e-07 here, against a golden + # band of 5e-06 and against this port's own distance from torch, which is + # larger still (see kLtx2ImgPixelTol in the suite: torch's bilinear + # contracts to FMA, so no portable f32 port reproduces it bit for bit). + # + # So NO GOLDEN IN THIS FILE CAN SEE THE ORDER SWAP. The order is still + # mirrored, for the only reason left: it is upstream's. That puts it in the + # same class AGENTS.md names for a too-WIDE dtype — correct, invisible to + # every gate we own, and therefore checked deliberately once and written + # down rather than assumed to be covered. + swapped = resize_mod.resize_and_center_crop( + range_mod.normalize_images(as_float, device="cpu", dtype=torch.float32), + IMAGE_DST_H, + IMAGE_DST_W, + ) + order_gap = float((normalized - swapped).abs().max()) + assert order_gap < tol, ( + f"resize-then-normalize and normalize-then-resize now differ by {order_gap:g}, ABOVE the " + f"golden band {tol:g}. That would be new information — the two are algebraically equal, " + f"so a gap this size means one of them stopped being the affine/convex pair this comment " + f"assumes. Investigate before touching this assertion" + ) + + out.write( + "// --- section 2: load_image_and_preprocess at crf=0 (decode.py:46-79) ---\n" + "// decode -> preprocess(crf=0) -> f32 0..255 -> resize+crop -> /127.5 - 1.\n" + "// `preprocess(crf=0) is image` was ASSERTED at generation time, and\n" + "// `preprocess(crf=18)` was asserted to reach the codec and raise.\n" + ) + emit_scalar(out, "kLtx2ImgPreSrcH", IMAGE_SRC_H) + emit_scalar(out, "kLtx2ImgPreSrcW", IMAGE_SRC_W) + emit_scalar(out, "kLtx2ImgPreDstH", IMAGE_DST_H) + emit_scalar(out, "kLtx2ImgPreDstW", IMAGE_DST_W) + out.write( + "// max|resize-then-normalize - normalize-then-resize|, measured upstream. It is\n" + "// BELOW the golden band, which is the recorded NEGATIVE RESULT: the two orders\n" + "// are algebraically identical (a convex combination commutes with an affine\n" + "// map), so their f32 gap is pure rounding and cannot be amplified. No golden\n" + "// here can see the swap; the order is mirrored because it is upstream's, and\n" + "// that is written down rather than assumed to be covered.\n" + ) + emit_double(out, "kLtx2ImgPreOrderGap", order_gap) + out.write("\n") + emit_f32(out, "kLtx2ImgPreGolden", normalized.numpy()) + return normalized + + +def section_encode(out, image_5d) -> None: + from ltx_core.model.video_vae.enums import LogVarianceType, NormLayerType, PaddingModeType + from ltx_core.model.video_vae.video_vae import VideoEncoder + + enc = VideoEncoder( + encoder_blocks=IMG_ENC_BLOCKS, + norm_layer=NormLayerType.PIXEL_NORM, + latent_log_var=LogVarianceType.UNIFORM, + encoder_spatial_padding_mode=PaddingModeType.ZEROS, + **IMG_ENC, + ).eval() + manifest = fill_from_stream(enc, prefix="ltx2.imgenc.") + latent = enc(image_5d) + + out.write( + "// --- section 3: VideoEncoder over the preprocessed image " + "(video_vae.py:264-336) ---\n" + "// This is `video_encoder(image)` at ltx-pipelines/utils/helpers.py:294.\n" + ) + emit_scalar(out, "kLtx2ImgEncOutC", latent.shape[1]) + emit_scalar(out, "kLtx2ImgEncOutT", latent.shape[2]) + emit_scalar(out, "kLtx2ImgEncOutH", latent.shape[3]) + emit_scalar(out, "kLtx2ImgEncOutW", latent.shape[4]) + emit_scalar(out, "kLtx2ImgEncTemporalFactor", enc.video_scale_factors.time) + emit_scalar(out, "kLtx2ImgEncSpatialFactor", enc.video_scale_factors.height) + out.write("\n") + emit_manifest(out, "kLtx2ImgEncParam", manifest) + emit_f32(out, "kLtx2ImgEncGolden", latent.numpy()) + return latent + + +def section_condition_and_noise(out, latent, tol) -> None: + import torch + + from ltx_core.components.noisers import GaussianNoiser + from ltx_core.components.patchifiers import VideoLatentPatchifier + from ltx_core.conditioning.types.latent_cond import VideoConditionByLatentIndex + from ltx_core.tools import VideoLatentTools + from ltx_core.types import SpatioTemporalScaleFactors, VideoLatentShape + + target = VideoLatentShape( + batch=1, + channels=int(latent.shape[1]), + frames=COND_TARGET_FRAMES, + height=int(latent.shape[3]), + width=int(latent.shape[4]), + ) + tools = VideoLatentTools( + patchifier=VideoLatentPatchifier(patch_size=COND_PATCH), + target_shape=target, + fps=COND_FPS, + scale_factors=SpatioTemporalScaleFactors.default(), + causal_fix=True, + ) + base = tools.create_initial_state(device="cpu", dtype=torch.float32) + + item = VideoConditionByLatentIndex( + latent=latent, strength=COND_STRENGTH, latent_idx=COND_LATENT_IDX + ) + conditioned = item.apply_to(base, tools) + + assert not torch.equal(conditioned.clean_latent, base.clean_latent), ( + "the item must CHANGE the clean latent or section 4 gates nothing" + ) + assert not torch.equal(conditioned.denoise_mask, base.denoise_mask), ( + "the item must CHANGE the denoise mask or section 4 gates nothing" + ) + assert torch.equal(conditioned.latent, base.latent), ( + "upstream leaves the NOISY tensor untouched (latent_cond.py:38-39). diffusers does " + "NOT (pipeline_ltx2_condition.py:1002-1004); if this ever flips, spec §3.3's choice " + "has to be revisited rather than the assertion deleted" + ) + + out.write( + "// --- section 4: VideoConditionByLatentIndex (latent_cond.py:22-43) ---\n" + "// clean_latent[start:stop] = tokens; denoise_mask[start:stop] = 1 - strength.\n" + "// The NOISY tensor is deliberately untouched — that was asserted here.\n" + ) + emit_scalar(out, "kLtx2ImgCondTokens", conditioned.latent.shape[1]) + emit_scalar(out, "kLtx2ImgCondWidth", conditioned.latent.shape[2]) + emit_scalar(out, "kLtx2ImgCondTargetFrames", COND_TARGET_FRAMES) + emit_scalar(out, "kLtx2ImgCondPatch", COND_PATCH) + emit_scalar(out, "kLtx2ImgCondLatentIdx", COND_LATENT_IDX) + emit_double(out, "kLtx2ImgCondStrength", COND_STRENGTH) + emit_double(out, "kLtx2ImgCondFps", COND_FPS) + out.write("\n") + emit_f32(out, "kLtx2ImgCondClean", conditioned.clean_latent.numpy()) + emit_f32(out, "kLtx2ImgCondMask", conditioned.denoise_mask.numpy()) + + # --- section 5: the noiser, at a NON-UNIT scale. + noise = torch.from_numpy( + ltx_rand("ltx2.imgcond.noise", int(conditioned.latent.numel())).astype(np.float32) + ).reshape(conditioned.latent.shape) + + class DeterministicNoiser(GaussianNoiser): + def _sample_noise(self, latent_state): # noqa: ARG002 - shape comes from the closure + return noise + + noised = DeterministicNoiser(generator=None)(conditioned, noise_scale=NOISE_SCALE) + + # THE DIFFUSERS FORM, computed here only so the DIVERGENCE is a measured + # number in the record rather than a claim. It is NOT emitted as a golden: + # this port follows ltx_core (spec §3.3). + diffusers_latent = conditioned.latent.clone() + diffusers_latent = torch.where( + conditioned.denoise_mask.unsqueeze(-1) < 1.0, + conditioned.clean_latent, + diffusers_latent, + ) + diffusers_out = torch.lerp(diffusers_latent.float(), noise.float(), NOISE_SCALE) + divergence = float((noised.latent - diffusers_out).abs().max()) + assert divergence > tol, ( + f"the ltx_core and diffusers compositions differ by only {divergence:g} at " + f"noise_scale={NOISE_SCALE}, inside the band {tol:g} — section 5 would then not be " + f"gating the choice spec §3.3 makes. Pick a scale that separates them" + ) + + out.write( + "// --- section 5: GaussianNoiser over the conditioned state " + "(components/noisers.py:30-37) ---\n" + "// latent = lerp(latent, noise, noise_scale); latent = lerp(clean, latent, mask).\n" + "// The DOUBLE lerp, at a NON-UNIT scale — the only regime in which ltx_core and\n" + "// diffusers disagree. `kLtx2ImgNoiseDivergence` is how far apart they are here,\n" + "// measured; the suite asserts it is above the band, which is what makes this\n" + "// section able to catch a silent switch to the diffusers form.\n" + ) + emit_double(out, "kLtx2ImgNoiseScale", NOISE_SCALE) + emit_double(out, "kLtx2ImgNoiseDivergence", divergence) + out.write("\n") + emit_f32(out, "kLtx2ImgNoisedGolden", noised.latent.numpy()) + + +# --------------------------------------------------------------------------- +# Upstream loading +# --------------------------------------------------------------------------- + + +class _StubModule(types.ModuleType): + """A module whose every attribute is another stub. Registered for `av` and + `OpenImageIO`, which `decode.py` imports at module scope and the crf==0 path + never reaches. See the module docstring: the stub is a tripwire, not a + shortcut — the assertions in section 2 are what prove upstream ran.""" + + def __getattr__(self, name): + # DUNDERS ARE NOT FABRICATED. `inspect` walks `sys.modules` looking for + # `__file__` on every module while resolving a frame, and a stub that + # answers it makes `os.path.splitext` raise from inside torch's custom-op + # registration — a failure with nothing to do with this script. Anything + # a real module would not have must stay absent. + if name.startswith("__") and name.endswith("__"): + raise AttributeError(name) + stub = _StubModule(f"{self.__name__}.{name}") + setattr(self, name, stub) + return stub + + def __call__(self, *args, **kwargs): + return _StubModule(f"{self.__name__}()") + + +def load_upstream(root: Path): + core_src = root / "packages" / "ltx-core" / "src" + pipe_src = root / "packages" / "ltx-pipelines" / "src" + if not (core_src / "ltx_core" / "model" / "video_vae" / "video_vae.py").is_file(): + raise SystemExit(f"no ltx_core under {core_src}; point --ltx2 at a Lightricks/LTX-2 tree") + if not (pipe_src / "ltx_pipelines" / "utils" / "media_io" / "resize.py").is_file(): + raise SystemExit(f"no ltx_pipelines under {pipe_src}") + sys.path.insert(0, str(core_src)) + sys.path.insert(0, str(pipe_src)) + + for dep in ("av", "OpenImageIO"): + if dep not in sys.modules: + sys.modules[dep] = _StubModule(dep) + + # Register the package levels WITHOUT executing their __init__.py. + for name, rel in ( + ("ltx_pipelines", "ltx_pipelines"), + ("ltx_pipelines.utils", "ltx_pipelines/utils"), + ("ltx_pipelines.utils.media_io", "ltx_pipelines/utils/media_io"), + ): + if name in sys.modules: + continue + package = types.ModuleType(name) + package.__path__ = [str(pipe_src / rel)] + package.__package__ = name + sys.modules[name] = package + + import ltx_core # noqa: PLC0415 + import ltx_pipelines.utils.media_io.decode as decode_mod # noqa: PLC0415 + import ltx_pipelines.utils.media_io.range_map as range_mod # noqa: PLC0415 + import ltx_pipelines.utils.media_io.resize as resize_mod # noqa: PLC0415 + + # ORACLE IDENTITY, asserted rather than assumed, on BOTH packages. + for module, expected in ((ltx_core, core_src), (resize_mod, pipe_src)): + resolved = Path(module.__file__).resolve() + if not resolved.is_relative_to(expected.resolve()): + raise SystemExit( + f"{module.__name__} resolved to {resolved}, which is NOT under {expected}. " + "Refusing to generate goldens from an oracle this script did not choose." + ) + return resize_mod, range_mod, decode_mod + + +def upstream_revision(root: Path) -> str: + try: + done = subprocess.run( + ["git", "-C", str(root), "rev-parse", "HEAD"], + check=True, + capture_output=True, + text=True, + ) + except Exception: # noqa: BLE001 - a tarball checkout carries no git metadata + return "unknown" + dirty = subprocess.run( + ["git", "-C", str(root), "status", "--porcelain"], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + if dirty: + raise SystemExit( + f"the LTX-2 checkout at {root} is DIRTY:\n{dirty}\n" + "Refusing to generate: `git rev-parse HEAD` would stamp a CLEAN revision anchor " + "onto goldens produced by a tree that commit cannot reproduce." + ) + return done.stdout.strip() + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--ltx2", required=True, type=Path, + help="a checkout of Lightricks/LTX-2 (the repo root)") + parser.add_argument("--out", required=True, type=Path) + args = parser.parse_args() + + tol = _read_golden_tol() + root = args.ltx2.expanduser().resolve() + resize_mod, range_mod, decode_mod = load_upstream(root) + revision = upstream_revision(root) + + import torch + + torch.set_grad_enabled(False) + + args.out.parent.mkdir(parents=True, exist_ok=True) + with args.out.open("w", encoding="utf-8") as out: + out.write( + "// GENERATED by scripts/gen-ltx2-image-cond-goldens.py — DO NOT EDIT BY HAND.\n" + "//\n" + "// LTX-2.5 IMAGE CONDITIONING goldens (row LTX25-IMAGE-COND, issue #644),\n" + "// produced by EXECUTING upstream Lightricks/LTX-2 at reduced dimensions on\n" + "// CPU. Weights and pixels come from the shared deterministic stream, so no\n" + "// weight byte and no image byte is checked in. Regenerate with:\n" + "// python3 scripts/gen-ltx2-image-cond-goldens.py --ltx2 \n" + "// --out tests/vllm/multimodal/ltx2_image_cond_goldens.inc\n" + "//\n" + f"// Upstream revision: {revision}\n" + "//\n" + "// See .agents/specs/ltx25-image-conditioning.md section 5.\n" + "#pragma once\n\n#include \n\nnamespace vllm_test {\n\n" + "// The upstream tree these numbers came from. The suite asserts this equals\n" + "// the SHA it pins, so regenerating against a DIFFERENT checkout fails the\n" + "// gate instead of silently replacing the oracle.\n" + f'inline constexpr const char* kLtx2ImgCondUpstreamRevision = "{revision}";\n\n' + ) + section_resize(out, resize_mod) + image_chw = section_preprocess(out, resize_mod, range_mod, decode_mod, tol) + latent = section_encode(out, image_chw) + section_condition_and_noise(out, latent, tol) + out.write("} // namespace vllm_test\n") + print(f"wrote {args.out}", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/vllm/model_executor/models/ltx2_image_preprocess.cpp b/src/vllm/model_executor/models/ltx2_image_preprocess.cpp new file mode 100644 index 000000000..2e10648d3 --- /dev/null +++ b/src/vllm/model_executor/models/ltx2_image_preprocess.cpp @@ -0,0 +1,219 @@ +// LTX-2.5 image conditioning input. Row LTX25-IMAGE-COND, issue #644. +// See ltx2_image_preprocess.h for the upstream anchors and the residuals. +#include "vllm/model_executor/models/ltx2_image_preprocess.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace vllm { +namespace { + +[[noreturn]] void Fail(const std::string& why) { throw std::runtime_error(why); } + +// ── PyTorch's `align_corners=False` index map, in f32 ─────────────────────── +// aten/src/ATen/native/UpSample.h `area_pixel_compute_scale` / +// `area_pixel_compute_source_index`, and +// aten/src/ATen/native/cpu/UpSampleKernel.cpp +// `HelperInterpLinear::compute_indices_weights` + `guard_index_and_lambda`. +// +// The arithmetic is f32 because that is the `opmath_t` those helpers are +// dispatched at for a float tensor. Computing it in double here would be MORE +// accurate and would still be wrong: the goldens come from torch, so this port +// has to reproduce torch's rounding, not improve on it. +struct LinearTap { + int64_t lo = 0, hi = 0; + float w_lo = 1.0F, w_hi = 0.0F; +}; + +std::vector LinearTaps(int64_t src, int64_t dst) { + const float scale = static_cast(src) / static_cast(dst); + std::vector taps(static_cast(dst)); + for (int64_t i = 0; i < dst; ++i) { + float real = scale * (static_cast(i) + 0.5F) - 0.5F; + if (real < 0.0F) real = 0.0F; // the `!cubic && src_idx < 0` clamp + int64_t index = std::min(static_cast(std::floor(real)), src - 1); + float lambda = std::min(std::max(real - static_cast(index), 0.0F), 1.0F); + LinearTap& tap = taps[static_cast(i)]; + tap.lo = index; + tap.hi = std::min(index + 1, src - 1); + tap.w_lo = 1.0F - lambda; + tap.w_hi = lambda; + } + return taps; +} + +int NextPpmInt(std::istringstream& in, const std::string& field) { + // PPM comments (`#` to end of line) may appear between any two header tokens. + while (true) { + in >> std::ws; + if (in.peek() != '#') break; + std::string skip; + std::getline(in, skip); + } + int value = 0; + if (!(in >> value)) Fail(field + ": bad PPM header"); + return value; +} + +} // namespace + +std::vector Ltx2DecodePpmRgb(const std::string& field, const std::string& bytes, + int64_t* out_height, int64_t* out_width) { + std::istringstream in(bytes, std::ios::binary); + std::string magic; + in >> magic; + if (magic != "P6") { + Fail(field + + ": not a binary PPM (P6). No PNG/JPEG/EXR codec is vendored in this tree, so an image " + "conditioning must be supplied as binary PPM — the same residual the MiniMax-H3 video " + "seam carries (minimax_h3_video.cpp:84-86). Upstream's own decoder is PIL's " + "(media_io/decode.py:139-170) and reads every format PIL does."); + } + const int width = NextPpmInt(in, field); + const int height = NextPpmInt(in, field); + const int maxval = NextPpmInt(in, field); + if (width <= 0 || height <= 0) { + Fail(field + ": PPM declares a " + std::to_string(width) + "x" + std::to_string(height) + + " image"); + } + if (maxval != 255) { + // PIL rescales a non-255 maxval on the way to uint8; which rounding it uses + // is a property of PIL's PPM plugin, not of LTX. Refusing rather than + // inventing one — a rescale nobody checked against PIL would shift every + // pixel of a conditioning image by a fraction no gate here can see. + Fail(field + ": PPM maxval is " + std::to_string(maxval) + + "; only 255 is read. `decode_image` (media_io/decode.py:139-170) returns " + "`np.array(image, dtype=np.uint8)` out of PIL, and mirroring PIL's rescale for a " + "narrower or wider maxval is a separate port. Re-save the image at maxval 255."); + } + in.get(); // the single whitespace byte between the header and the payload + std::vector rgb(static_cast(width) * static_cast(height) * 3); + in.read(reinterpret_cast(rgb.data()), static_cast(rgb.size())); + if (!in) Fail(field + ": truncated PPM payload"); + if (out_height != nullptr) *out_height = height; + if (out_width != nullptr) *out_width = width; + return rgb; +} + +void Ltx2PreprocessImageCrf(int64_t crf) { + if (crf == 0) return; // decode.py:425-426 — `if crf == 0: return image` + Fail( + "image conditioning at CRF " + std::to_string(crf) + + " is not ported: the H.264 round trip `preprocess` performs at a non-zero CRF " + "(media_io/decode.py:430-434 -> encode_single_frame:386-400, libx264 preset=veryfast, " + "rgb24 -> yuv420p, dimensions truncated to even, then decode_single_frame:403-410) needs a " + "codec, and none is vendored in this tree. CRF 0 IS supported and is the only supported " + "value: upstream short-circuits it at decode.py:425-426 and documents an explicit 0 as " + "\"skip re-compression entirely\" (utils/args.py:58-59). Say so explicitly — an LTX-2.5 " + "checkpoint RESOLVES 18 when the caller leaves the CRF unset (ImageConditioner.resolve_crf, " + "blocks.py:977-983, over constants.py:37/124/130-133), so CRF 0 conditions on pixels this " + "model generation was not trained against. That is a quality cost, not a correctness one, " + "and it is stated rather than rendered silently."); +} + +std::vector Ltx2ResizeAndCenterCrop(const float* hwc, int64_t src_height, + int64_t src_width, int64_t channels, int64_t height, + int64_t width) { + if (hwc == nullptr) Fail("ltx2 resize: null input"); + if (src_height <= 0 || src_width <= 0 || channels <= 0 || height <= 0 || width <= 0) { + Fail("ltx2 resize: every dimension must be positive"); + } + + // resize.py:60-63. `scale = max(...)` is the aspect-FILL choice — the crop + // happens after, so the SHORT side is what the target must be covered by. + // `ceil` is upstream's own guard against a float rounding that would make + // new_h/new_w land just under the target and give a negative crop offset. + const double scale = std::max(static_cast(height) / static_cast(src_height), + static_cast(width) / static_cast(src_width)); + const int64_t new_h = static_cast(std::ceil(static_cast(src_height) * scale)); + const int64_t new_w = static_cast(std::ceil(static_cast(src_width) * scale)); + if (new_h < height || new_w < width) { + Fail("ltx2 resize: the aspect-fill resize produced " + std::to_string(new_h) + "x" + + std::to_string(new_w) + ", which cannot be cropped to " + std::to_string(height) + "x" + + std::to_string(width)); + } + + const std::vector rows = LinearTaps(src_height, new_h); + const std::vector cols = LinearTaps(src_width, new_w); + + // resize.py:71 — `crop_top = (new_h - height) // 2`, floor division. + const int64_t crop_top = (new_h - height) / 2; + const int64_t crop_left = (new_w - width) / 2; + + std::vector out(static_cast(channels * height * width)); + for (int64_t c = 0; c < channels; ++c) { + for (int64_t y = 0; y < height; ++y) { + const LinearTap& r = rows[static_cast(crop_top + y)]; + for (int64_t x = 0; x < width; ++x) { + const LinearTap& k = cols[static_cast(crop_left + x)]; + // WIDTH is the inner sum and HEIGHT the outer one, and each partial sum + // is rounded to f32 before the next multiply — `Interpolate::eval` + // recurses over the dimensions in the order + // `upsample_generic_Nd_kernel_impl` appends them (height, then width), + // so the row combination happens first. Folding this into one + // four-term dot product gives a different last bit. + auto at = [&](int64_t yy, int64_t xx) { + return hwc[static_cast((yy * src_width + xx) * channels + c)]; + }; + const float lo = at(r.lo, k.lo) * k.w_lo + at(r.lo, k.hi) * k.w_hi; + const float hi = at(r.hi, k.lo) * k.w_lo + at(r.hi, k.hi) * k.w_hi; + out[static_cast((c * height + y) * width + x)] = lo * r.w_lo + hi * r.w_hi; + } + } + } + return out; +} + +std::vector Ltx2LoadImageAndPreprocess(const std::string& field, const std::string& bytes, + int64_t height, int64_t width, int64_t crf) { + // decode.py:74-78, step for step and IN THIS ORDER. + int64_t src_h = 0, src_w = 0; + const std::vector rgb = Ltx2DecodePpmRgb(field, bytes, &src_h, &src_w); // :74 + Ltx2PreprocessImageCrf(crf); // :75 + + // :76 — `torch.tensor(image, dtype=torch.float32)`. Values are still 0..255. + std::vector as_float(rgb.size()); + for (size_t i = 0; i < rgb.size(); ++i) as_float[i] = static_cast(rgb[i]); + + // :77 — the resize runs in 0..255 space. + std::vector chw = Ltx2ResizeAndCenterCrop(as_float.data(), src_h, src_w, 3, height, width); + + // :78 — `normalize_images` (range_map.py:8-9), AFTER the resize. + for (float& v : chw) v = v / 127.5F - 1.0F; + return chw; +} + +int64_t Ltx2ResolveDefaultImageCrf(const std::vector& version_components) { + // `_PARAMS_SINCE_VERSION` (constants.py:130-133), newest first, and + // `detect_params`'s "the newest generation this version is at or above" + // (:166-177). Only `default_image_crf` differs between the rows, so only that + // is resolved here; a row that moved another knob would have to grow this. + struct Row { + std::vector since; + int64_t crf; + }; + static const std::vector kRows = { + {{2, 4}, 18}, // LTX_2_4_PARAMS -> LTX_2_4_IMAGE_CRF (:37, :124) + {{2, 3}, 33}, // LTX_2_3_PARAMS inherits DEFAULT_IMAGE_CRF (:36, :83-88) + }; + for (const Row& row : kRows) { + // Tuple comparison, which is what `parsed >= since` is in Python: element by + // element, and a SHORTER tuple compares below a longer one that agrees on + // the shared prefix. An empty `parsed` therefore falls through every row, + // which is `detect_model_version`'s documented "compares below every real + // version" (:138-139). + if (!std::lexicographical_compare(version_components.begin(), version_components.end(), + row.since.begin(), row.since.end())) { + return row.crf; + } + } + return 33; // DEFAULT_IMAGE_CRF, via LTX_2_PARAMS (:36, :48, :80) +} + +} // namespace vllm 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 new file mode 100644 index 000000000..975bd0360 --- /dev/null +++ b/src/vllm/model_executor/models/ltx2_video_vae_encoder_load.cpp @@ -0,0 +1,232 @@ +// LTX-2.5 CONV VIDEO VAE ENCODER — the LOAD path, row LTX25-IMAGE-COND (#644). +// +// Spec: .agents/specs/ltx25-image-conditioning.md §3.1. +// +// ─── WHY THIS IS ITS OWN TU ────────────────────────────────────────────────── +// The decoder's twin of this code lives in `ltx2_loader.cpp`, which is a 1400-line +// file two concurrent rows of the #644 campaign both need to touch. Putting the +// encoder's four functions there would have made that file the lock AGENTS.md +// §Records names. It is additive here instead, next to the header that declares +// the encoder it serves, and it duplicates only the three tiny config accessors +// (`ConfigGet` / `ConfigObject` / the two enum parsers) that `ltx2_loader.cpp` +// keeps in its own anonymous namespace and does not export. +// +// ─── WHAT THIS IS A PORT OF (file:line on BOTH sides) ──────────────────────── +// Upstream root: Lightricks/LTX-2 @ fd4ded7f, +// packages/ltx-core/src/ltx_core/model/video_vae/ +// OURS <- UPSTREAM +// Ltx2VideoVaeEncoderKeyRules <- model_configurator.py:267-276 +// Ltx2ParseConvVideoEncoderConfig <- model_configurator.py:37-69, 72-78 +// Ltx2CheckpointHasVideoEncoder <- (the SDOps `with_matching` prefixes of +// the same filter, asked as a question) +#include "vllm/model_executor/models/ltx2_video_vae_encoder.h" + +#include + +#include +#include +#include + +namespace vllm { +namespace { + +[[noreturn]] void Fail(const std::string& why) { + throw std::runtime_error("ltx2 video vae encoder: " + why); +} + +bool StartsWith(const std::string& value, const std::string& prefix) { + return value.rfind(prefix, 0) == 0; +} + +// `config.get(key, fallback)`, refusing a present-but-wrong-typed value rather +// than falling back — the same polarity `ltx2_loader.cpp:845-856` states for the +// decoder, and for the same reason: a checkpoint that says `"patch_size": "4"` +// means something, and treating it as absent builds a different latent grid. +template +T ConfigGet(const nlohmann::json& config, const std::string& key, T fallback, + const std::string& where) { + const auto it = config.find(key); + if (it == config.end() || it->is_null()) return fallback; + try { + return it->get(); + } catch (const std::exception&) { + Fail("'" + where + "." + key + "' is present but not the expected type (" + it->dump() + ")"); + } +} + +const nlohmann::json& ConfigObject(const nlohmann::json& parent, const std::string& key, + const std::string& where) { + static const nlohmann::json kEmpty = nlohmann::json::object(); + const auto it = parent.find(key); + if (it == parent.end() || it->is_null()) return kEmpty; + if (!it->is_object()) Fail("'" + where + "." + key + "' is not a JSON object"); + return *it; +} + +Ltx2NormLayer ParseNormLayer(const std::string& name, const std::string& where) { + if (name == "pixel_norm") return Ltx2NormLayer::kPixelNorm; + if (name == "group_norm") return Ltx2NormLayer::kGroupNorm; + Fail(where + ".norm_layer is '" + name + "'; only 'pixel_norm' and 'group_norm' are ported"); +} + +Ltx2PaddingMode ParsePaddingMode(const std::string& name, const std::string& where) { + if (name == "zeros") return Ltx2PaddingMode::kZeros; + if (name == "reflect") return Ltx2PaddingMode::kReflect; + if (name == "replicate") return Ltx2PaddingMode::kReplicate; + Fail(where + ".spatial_padding_mode is '" + name + + "'; only 'zeros', 'reflect' and 'replicate' are ported"); +} + +// `LogVarianceType` (video_vae/enums.py:9-13). The DEFAULT is `uniform` +// (model_configurator.py:62), and it is not cosmetic: it decides how many +// channels `conv_out` emits and therefore WHICH half of them the mean split +// keeps. See ltx2_video_vae_encoder.h. +Ltx2LogVarianceType ParseLogVariance(const std::string& name, const std::string& where) { + if (name == "per_channel") return Ltx2LogVarianceType::kPerChannel; + if (name == "uniform") return Ltx2LogVarianceType::kUniform; + if (name == "constant") return Ltx2LogVarianceType::kConstant; + if (name == "none") return Ltx2LogVarianceType::kNone; + Fail(where + ".latent_log_var is '" + name + "'; it is not one of the four (enums.py:9-13)"); +} + +// `[["res_x", {"num_layers": 4}], ["compress_all", {"multiplier": 2}], ...]` — +// the same pair form the decoder's list uses, read by `_make_encoder_block` +// (video_vae.py:39-145), which asks for exactly `num_layers` and `multiplier`. +// +// The encoder's list has NO `inject_noise` and NO `residual`: `_make_encoder_block` +// never reads them (contrast `_make_decoder_block`), so they are deliberately +// not parsed here. A checkpoint carrying them on an encoder block would be +// declaring something upstream ignores, and silently honouring it would build a +// module upstream cannot. +std::vector ParseEncoderBlocks(const nlohmann::json& blocks, + const std::string& where) { + if (!blocks.is_array()) Fail("'" + where + ".encoder_blocks' is not an array"); + std::vector out; + for (const nlohmann::json& entry : blocks) { + if (!entry.is_array() || entry.size() != 2 || !entry[0].is_string() || !entry[1].is_object()) { + Fail("an '" + where + ".encoder_blocks' entry is not a [name, {params}] pair: " + + entry.dump()); + } + Ltx2VideoEncoderBlock block; + block.name = entry[0].get(); + const nlohmann::json& params = entry[1]; + 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 + // multiplier must stay 0 rather than become 1, which would quietly halve + // every widening block's output width. + block.multiplier = ConfigGet(params, "multiplier", 0, "encoder_block"); + out.push_back(std::move(block)); + } + if (out.empty()) Fail("'" + where + ".encoder_blocks' is empty; there is no encoder to build"); + return out; +} + +} // 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. + return { + {"vae.encoder.", ""}, + {"vae.per_channel_statistics.", "per_channel_statistics."}, + {"encoder.", ""}, + {"per_channel_statistics.", "per_channel_statistics."}, + }; +} + +bool Ltx2CheckpointHasVideoEncoder(const std::vector& tensor_names) { + // Only the two ENCODER prefixes count. `per_channel_statistics.` is in the + // filter but is carried by decoder-only files too, so treating it as evidence + // of an encoder would report every Comfy-split decoder as encodable and then + // fail deep inside `Ltx2ConvVideoEncode` on a missing `conv_in.conv.weight`. + for (const std::string& name : tensor_names) { + if (StartsWith(name, "vae.encoder.") || StartsWith(name, "encoder.")) return true; + } + return false; +} + +Ltx2ConvVideoEncoderConfig Ltx2ParseConvVideoEncoderConfig(const nlohmann::json& config) { + const nlohmann::json& vae = ConfigObject(config, "vae", "config"); + if (vae.empty()) Fail("the video VAE config carries no 'vae' object"); + + // `_prepare_video_encoder_kwargs`'s two layouts (model_configurator.py:46-53). + const bool nested = vae.contains("encoder") && !vae.at("encoder").is_null(); + const nlohmann::json& enc = nested ? ConfigObject(vae, "encoder", "vae") : vae; + const std::string where = nested ? "vae.encoder" : "vae"; + + Ltx2ConvVideoEncoderConfig out; + + // `convolution_dimensions` (:56). Asserted rather than stored, exactly as the + // decoder's parser does: this port is 3-D only. + const int64_t dims = ConfigGet(enc, "dims", ConfigGet(vae, "dims", 3, "vae"), + where); + if (dims != 3) Fail("vae.dims is " + std::to_string(dims) + "; only the 3-D encoder is ported"); + + out.in_channels = ConfigGet(enc, "in_channels", 3, where); + + // THE LATENT WIDTH (:48 nested / :52 flat). Never the top-level `out_channels`. + out.out_channels = nested ? ConfigGet(enc, "out_channels", + ConfigGet(vae, "latent_channels", 128, + "vae"), + where) + : ConfigGet(vae, "latent_channels", 128, "vae"); + + // `encoder_blocks`: NESTED reads `encoder.blocks` then `encoder.encoder_blocks` + // (:49); FLAT reads `vae.encoder_blocks` (:53). Neither layout falls back to + // the other's object, so neither does this. + const nlohmann::json* blocks = nullptr; + if (nested) { + if (enc.contains("blocks") && !enc.at("blocks").is_null()) { + blocks = &enc.at("blocks"); + } else if (enc.contains("encoder_blocks") && !enc.at("encoder_blocks").is_null()) { + blocks = &enc.at("encoder_blocks"); + } + } else if (vae.contains("encoder_blocks") && !vae.at("encoder_blocks").is_null()) { + blocks = &vae.at("encoder_blocks"); + } + if (blocks == nullptr) { + // Upstream's default is `[]`, and `VideoEncoder([])` is an encoder with no + // down-blocks at all: it would run, produce a latent at the WRONG scale + // factors, and every shape downstream would still check out because the + // pipeline derives its shapes from VIDEO_SCALE_FACTORS rather than from this + // list. Refused instead. + Fail( + "the video VAE config declares no 'encoder_blocks', and upstream's default for it is an " + "EMPTY list (model_configurator.py:49, 53). An encoder with no down-blocks still runs and " + "still returns a latent — at scale factors of (1, patch_size, patch_size) rather than the " + "checkpoint's — so refusing is the only way this is visible. A decoder-only Comfy-split " + "file is the expected case here and is reported separately by " + "Ltx2CheckpointHasVideoEncoder"); + } + out.encoder_blocks = ParseEncoderBlocks(*blocks, where); + + out.patch_size = ConfigGet(enc, "patch_size", 4, where); + out.norm_layer = + ParseNormLayer(ConfigGet(enc, "norm_layer", "pixel_norm", where), where); + out.latent_log_var = + ParseLogVariance(ConfigGet(enc, "latent_log_var", "uniform", where), where); + + // `encoder_spatial_padding_mode` (:63-68): the ENCODER key first, then the + // top-level `encoder_spatial_padding_mode`, then `zeros` — NOT the decoder's + // `reflect`. All three levels are mirrored because upstream's chained + // `.get(a, config.get(b, "zeros"))` is what decides which checkpoint spelling + // wins, and collapsing it to one lookup changes that. + out.spatial_padding_mode = ParsePaddingMode( + ConfigGet( + enc, "spatial_padding_mode", + ConfigGet(vae, "encoder_spatial_padding_mode", "zeros", "vae"), where), + where); + + // norm_num_groups, norm_eps and pixel_norm_eps are NOT checkpoint keys: they + // are `VideoEncoder._DEFAULT_NORM_NUM_GROUPS` and the literals + // `_make_encoder_block` passes (video_vae.py:56, 66, 240). They keep the + // values ltx2_video_vae_encoder.h pins to those lines. Reading them from + // config would let a file move a constant no golden can see. + return out; +} + +} // namespace vllm diff --git a/src/vllm/multimodal/ltx2_video.cpp b/src/vllm/multimodal/ltx2_video.cpp index a50f184c0..f5a2d4c20 100644 --- a/src/vllm/multimodal/ltx2_video.cpp +++ b/src/vllm/multimodal/ltx2_video.cpp @@ -26,13 +26,16 @@ #include "vllm/model_executor/models/device_pool.h" // ActivePool(b)/DevicePool::Drain #include "vllm/model_executor/models/ltx2.h" #include "vllm/model_executor/models/ltx2_audio_vae.h" +#include "vllm/model_executor/models/ltx2_conditioning.h" #include "vllm/model_executor/models/ltx2_connector.h" #include "vllm/model_executor/models/ltx2_device.h" +#include "vllm/model_executor/models/ltx2_image_preprocess.h" #include "vllm/model_executor/models/ltx2_loader.h" #include "vllm/model_executor/models/ltx2_pipeline.h" #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/model_executor/models/ltx2_video_vae_encoder.h" #include "vllm/model_executor/models/minimax_h3.h" #include "vllm/tokenizer/tokenizer.h" @@ -242,7 +245,7 @@ int64_t ExtraInt(const std::map& extras, const std::st if (consumed != raw.size()) throw std::invalid_argument("trailing"); return static_cast(value); } catch (const std::exception&) { - Fail("the load extra '" + key + "' is '" + raw + "', which is not an integer"); + Fail("the extra '" + key + "' is '" + raw + "', which is not an integer"); } } @@ -439,6 +442,18 @@ struct Ltx2VideoEngine::Impl { Ltx2ConvVideoDecoderConfig video_cfg; Ltx2VaeWeights video_weights; + // The ENCODER half of the same file (row LTX25-IMAGE-COND, issue #644). Its + // absence is what every conditioning arm was refused for: before this row the + // load below materialized `Ltx2VideoVaeDecoderKeyRules()` alone and no encoder + // key filter existed anywhere in the tree, so `Ltx2ConvVideoEncode` — ported + // and gated since phase L11 — had no weights to run on. + // + // A Comfy-split `vae/` file may carry the decoder alone, so this is OPTIONAL + // and its absence is reported by name at the request rather than guessed at. + bool has_video_encoder = false; + Ltx2ConvVideoEncoderConfig video_encoder_cfg; + Ltx2VaeWeights video_encoder_weights; + Ltx2AudioDecoderConfig audio_cfg; Ltx2VaeWeights audio_weights; Ltx2VocoderBweConfig vocoder_cfg; @@ -748,8 +763,46 @@ std::unique_ptr Ltx2VideoEngine::Load(const VideoModelParams& p if (params.video_vae_path.empty()) Fail("video_vae_path is required"); { const SafetensorsFile f = SafetensorsFile::Open(params.video_vae_path); - im.video_cfg = Ltx2ParseConvVideoDecoderConfig(Ltx2ReadCheckpointConfig(f), &im.video_kind); + const nlohmann::json vae_config = Ltx2ReadCheckpointConfig(f); + im.video_cfg = Ltx2ParseConvVideoDecoderConfig(vae_config, &im.video_kind); im.video_weights = Ltx2LoadVaeWeights(f, Ltx2VideoVaeDecoderKeyRules()); + + // `ImageConditioner` builds its VideoEncoder from the SAME checkpoint with + // `VAE_ENCODER_COMFY_KEYS_FILTER` (blocks.py:956-961). It builds it lazily + // and frees it after the callable returns (:988-993); this engine keeps it + // resident instead, because the encoder is small next to the DiT and a + // conditioning image arrives per request. That is a deliberate divergence + // from upstream's lifecycle and nothing else: same filter, same + // configurator, same weights. + if (Ltx2CheckpointHasVideoEncoder(f.Names())) { + im.video_encoder_cfg = Ltx2ParseConvVideoEncoderConfig(vae_config); + im.video_encoder_weights = Ltx2LoadVaeWeights(f, Ltx2VideoVaeEncoderKeyRules()); + im.has_video_encoder = true; + + // The encoder's LATENT WIDTH against the DiT's input, asserted rather + // than assumed. `_prepare_video_encoder_kwargs` reads it from + // `latent_channels` and NOT from the top-level `out_channels` + // (model_configurator.py:41-43); a config that got that wrong builds a + // 3-channel-latent encoder that still runs, still returns a latent, and + // conditions the DiT on a tensor of the wrong width. + if (im.video_encoder_cfg.out_channels != im.dit.params.in_channels) { + Fail("the video VAE encoder emits " + + std::to_string(im.video_encoder_cfg.out_channels) + + " latent channels but the DiT takes " + + 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)."); + } + // And its INPUT width, for the same reason in the other direction: the + // encoder takes RGB, and a config declaring otherwise would silently + // reinterpret the image planes. + if (im.video_encoder_cfg.in_channels != 3) { + Fail("the video VAE encoder declares " + + std::to_string(im.video_encoder_cfg.in_channels) + + " input channels; this seam supplies RGB"); + } + } } if (im.video_cfg.in_channels != im.dit.params.out_channels) { Fail("the video VAE takes " + std::to_string(im.video_cfg.in_channels) + @@ -980,9 +1033,17 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { std::lock_guard guard(im.mutex); if (gen.output_dir.empty()) Fail("output_dir is required"); - if (!gen.extras.empty()) { - Fail("unknown per-generation extra '" + gen.extras.begin()->first + - "' (this family defines none)"); + for (const auto& kv : gen.extras) { + // `image_crf` is the only per-generation extra this family defines (row + // LTX25-IMAGE-COND). Everything else is refused rather than ignored, for the + // reason `CheckKnownExtras` gives for the load side: a mistyped knob that is + // silently dropped renders the DEFAULT and looks like the feature not + // working — and for THIS knob the default is a refusal, so a typo would turn + // a served request into an unexplained one. + if (kv.first != kLtx2ImageCrfExtra) { + Fail("unknown per-generation extra '" + kv.first + "'. This family defines: " + + std::string(kLtx2ImageCrfExtra)); + } } if (!gen.prompt.empty() && !im.has_encoder) { Fail( @@ -1110,27 +1171,96 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { im.trace.video_absmax = AbsMax(v); im.trace.audio_absmax = AbsMax(a); } - // Image / reference conditioning is `ImageConditioner` upstream - // (ltx-pipelines/utils/blocks.py:936-993, called at distilled.py:212). The - // ENCODER it needs is no longer what is missing — phase L11 ported it as - // `Ltx2ConvVideoEncode` — so the refusal names what actually is: this engine - // holds no encoder to call. Refused by name rather than dropped: a keyframe - // that is silently ignored renders an unconditioned clip that looks like the - // feature not working. - if (!gen.first_frame_path.empty() || !gen.first_frame_ppm.empty() || - !gen.last_frame_path.empty() || !gen.ref_image_paths.empty() || - !gen.ref_video_dir.empty() || !gen.ref_audio_path.empty() || !gen.ref_audio_wav.empty()) { + // ── conditioning on pixels (row LTX25-IMAGE-COND, issue #644) ───────────── + // + // Upstream this is `ImageConditioner` (ltx-pipelines/utils/blocks.py:936-993, + // called at distilled.py:212) feeding `combined_image_conditionings` + // (utils/helpers.py:272-308). ONE of its four arms is served here, and the + // other three are refused BY NAME rather than dropped — a keyframe that is + // 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. + 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."); + } + 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 " + "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 " + "image-to-video."); + } + if (!gen.ref_audio_path.empty() || !gen.ref_audio_wav.empty()) { Fail( - "keyframe / reference conditioning is not ported for this family. The video VAE " - "ENCODER itself landed in phase L11 (Ltx2ConvVideoEncode), but nothing can reach it " - "from here: this engine materializes the DECODER key filter only, so no " - "VAE_ENCODER_COMFY_KEYS_FILTER / VideoEncoderConfigurator path " - "(video_vae/model_configurator.py:72, 267) puts encoder weights in memory, and " - "upstream resolves each image conditioning's CRF against the checkpoint's " - "default_image_crf when the caller left it unset (ImageConditioner.resolve_crf, " - "ltx-pipelines/utils/blocks.py:977-983) and then re-compresses through an H.264 " - "round trip unless that CRF is 0 (media_io/decode.py:413-435, from " - "load_image_and_preprocess :75), which this build does not do. Recorded as owed."); + "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 " + "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 " + "audio latents here. Recorded as owed."); + } + + // The CRF, resolved the way `ImageConditioner.resolve_crf` resolves it + // (blocks.py:966-983) over `detect_params` (utils/constants.py:166-177): 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 + // unsupported request costs nothing and reports the same thing every time. + int64_t image_crf = 0; + double image_strength = 0.0; + std::string image_bytes; + if (wants_image) { + if (!im.has_video_encoder) { + Fail( + "an image conditioning was supplied but the video VAE checkpoint at '" + + im.params.video_vae_path + + "' carries no ENCODER half: no tensor in it is named `vae.encoder.*` or `encoder.*`, " + "which is what `VAE_ENCODER_COMFY_KEYS_FILTER` matches " + "(video_vae/model_configurator.py:267-276). A Comfy-split `vae/` file holding the " + "decoder alone reads exactly like this. Supply the monolithic VAE checkpoint, or the " + "encoder file, rather than rendering unconditioned."); + } + image_crf = ExtraInt(gen.extras, kLtx2ImageCrfExtra, + Ltx2ResolveDefaultImageCrf(Ltx2ParseModelVersion(im.model_version))); + // Throws by name at any non-zero value, naming the unported codec round + // trip and saying that 0 is the supported — and out-of-distribution — one. + Ltx2PreprocessImageCrf(image_crf); + + // `ImageConditioningInput.strength` (utils/args.py:64). The seam's + // `noise_aug` is documented as "keyframe pinning strength; <= 0 => 1.0" + // (include/vllm.h), which is the same polarity: 1 pins, and the item turns + // it into `denoise_mask = 1 - strength` (latent_cond.py:41). + image_strength = gen.noise_aug > 0.0 ? gen.noise_aug : 1.0; + if (image_strength > 1.0) { + Fail("the image conditioning strength is " + std::to_string(image_strength) + + "; upstream's denoise mask is `1 - strength` (latent_cond.py:41) and a strength " + "above 1 makes it negative, which the noiser extrapolates PAST the clean latent " + "rather than toward it (components/noisers.py:33)"); + } + image_bytes = gen.first_frame_ppm.empty() ? ReadFileBytes("first_frame", gen.first_frame_path) + : gen.first_frame_ppm; + im.trace.image_crf = image_crf; + im.trace.image_strength = image_strength; } // ── geometry ────────────────────────────────────────────────────────────── @@ -1315,6 +1445,86 @@ VideoResult Ltx2VideoEngine::Generate(const VideoGenParams& gen) { audio.positions.assign(timings.begin(), timings.end()); } + // ── 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 + // 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 + // conditioned tokens pinned to NOISE, with an identical clean tensor and an + // identical mask — so nothing but the noised latent itself can see it. + // + // 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 + // 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) { + const std::vector pixels = Ltx2LoadImageAndPreprocess( + "first_frame", image_bytes, phase_h, phase_w, image_crf); + int64_t cropped = 0; + const Ltx2LatentVolume encoded = Ltx2ConvVideoEncode( + im.video_encoder_cfg, im.video_encoder_weights, pixels, + im.video_encoder_cfg.in_channels, /*frame_count=*/1, phase_h, phase_w, &cropped); + if (encoded.frames != 1) { + Fail("the video VAE encoder returned " + std::to_string(encoded.frames) + + " latent frames for a single image; `VideoConditionByLatentIndex` places one " + "(ltx-pipelines/utils/helpers.py:294-300)"); + } + if (encoded.channels != vshape.channels || encoded.height != vshape.height || + encoded.width != vshape.width) { + Fail("the encoded image is " + std::to_string(encoded.channels) + "x" + + std::to_string(encoded.height) + "x" + std::to_string(encoded.width) + + " but phase '" + phase.name + "' needs " + std::to_string(vshape.channels) + "x" + + std::to_string(vshape.height) + "x" + std::to_string(vshape.width) + + ". Upstream raises ConditioningError on exactly this " + "(conditioning/types/latent_cond.py:25-30): the encoder's spatial factor and the " + "pipeline's VIDEO_SCALE_FACTORS must agree, and they do not."); + } + + // `Ltx2ConditionVideoByLatentIndex` writes `clean` and `mask` and reads + // `tokens` / `width`; `latent` and `positions` are carried so the struct + // is coherent rather than half-filled, not because the item consults them. + Ltx2LatentState state; + state.tokens = video.tokens; + state.width = video.width; + state.pos_dims = 3; + state.latent = video.latent; + state.clean = video.clean; + state.mask = video.mask; + Ltx2ConditionVideoByLatentIndex(&state, vshape, /*patch_size=*/1, encoded, image_strength, + /*latent_idx=*/0); + video.clean = state.clean; + video.mask = state.mask; + + // The witness, taken from the TOKENS THAT WERE WRITTEN rather than from + // `encoded` — and the difference is not cosmetic. Digesting the encoder's + // output would answer "was an image encoded", which stays true of a build + // that encodes an image and then never places it: the render would be + // unconditioned and every field here would look healthy. Digesting the + // conditioned slice of the clean latent answers "did those tokens reach + // the state", which is the question. Filled on the LAST phase, so it + // describes the conditioning the finished latent carries. + // + // IT IS STILL A CHANGE DETECTOR, not a value gate — the same limit the two + // prompt digests carry. What the placed tokens should NUMERICALLY be is + // gated against executed upstream in `test_ltx2_image_cond`, which drives + // these very functions; MEASURED, because a mutation that moved the + // composition inside this loop and left `test_ltx2_video` green is how + // this comment came to be here. + const int64_t placed = Ltx2VideoTokenCount({1, vshape.channels, 1, vshape.height, + vshape.width}, + 1); + const std::vector written( + video.clean.begin(), + video.clean.begin() + static_cast(placed * video.width)); + im.trace.image_tokens = placed; + im.trace.image_digest = DigestF32(written); + im.trace.image_absmax = AbsMax(written); + } + // The noiser draws VIDEO first, AUDIO second, from one generator // (blocks.py:576-580 builds the video state before the audio one). const float noise_scale = static_cast(phase.noise_scale); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ddeaa7484..f4c3299f2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -127,6 +127,13 @@ target_compile_definitions(test_video_engine PRIVATE # LTX-2.5 L7: the family behind the VideoEngine seam and the driving loop, run # over a reduced-dimension checkpoint set written in the SHIPPED file format. vllm_cpp_add_test(test_ltx2_video vllm/multimodal/test_ltx2_video.cpp) +# LTX-2.5 image conditioning (row LTX25-IMAGE-COND, issue #644): the CHAIN from +# PPM bytes to conditioned, noised tokens, gated against upstream ltx_core AND +# ltx_pipelines executed at reduced dimensions. ltx2_image_cond_goldens.inc +# lives next to the test source. +vllm_cpp_add_test(test_ltx2_image_cond vllm/multimodal/test_ltx2_image_cond.cpp) +target_include_directories(test_ltx2_image_cond + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/multimodal) # LTX-2.5 DiT parity gate (row MODEL-DIFFUSION-LTX25, spec phase L2). The goldens # live next to the test source, like every other generated .inc. vllm_cpp_add_test(test_ltx2 vllm/models/test_ltx2.cpp) diff --git a/tests/vllm/multimodal/ltx2_image_cond_goldens.inc b/tests/vllm/multimodal/ltx2_image_cond_goldens.inc new file mode 100644 index 000000000..19f201664 --- /dev/null +++ b/tests/vllm/multimodal/ltx2_image_cond_goldens.inc @@ -0,0 +1,1007 @@ +// GENERATED by scripts/gen-ltx2-image-cond-goldens.py — DO NOT EDIT BY HAND. +// +// LTX-2.5 IMAGE CONDITIONING goldens (row LTX25-IMAGE-COND, issue #644), +// produced by EXECUTING upstream Lightricks/LTX-2 at reduced dimensions on +// CPU. Weights and pixels come from the shared deterministic stream, so no +// weight byte and no image byte is checked in. Regenerate with: +// python3 scripts/gen-ltx2-image-cond-goldens.py --ltx2 +// --out tests/vllm/multimodal/ltx2_image_cond_goldens.inc +// +// Upstream revision: fd4ded7f2d88d3da713abcdd4ad41ecc4a9314ca +// +// See .agents/specs/ltx25-image-conditioning.md section 5. +#pragma once + +#include + +namespace vllm_test { + +// The upstream tree these numbers came from. The suite asserts this equals +// the SHA it pins, so regenerating against a DIFFERENT checkout fails the +// gate instead of silently replacing the oracle. +inline constexpr const char* kLtx2ImgCondUpstreamRevision = "fd4ded7f2d88d3da713abcdd4ad41ecc4a9314ca"; + +// --- section 1: resize_and_center_crop (media_io/resize.py:41-73) --- +// Aspect FILL, `ceil`, bilinear align_corners=False, then centre crop. +inline constexpr int64_t kLtx2ImgResizeCases = 5; + +// case 0: 12x20 -> 16x16 +inline constexpr int64_t kLtx2ImgResize0SrcH = 12; +inline constexpr int64_t kLtx2ImgResize0SrcW = 20; +inline constexpr int64_t kLtx2ImgResize0DstH = 16; +inline constexpr int64_t kLtx2ImgResize0DstW = 16; + +inline constexpr float kLtx2ImgResize0Golden[] = { + 133.555557f, 109.907425f, 68.1666489f, 123.722206f, 158.870361f, 160.444458f, + 118.388893f, 53.9444466f, 53.0f, 92.7407455f, 153.481491f, 193.27774f, + 144.574188f, 77.7221985f, 102.166641f, 177.999924f, 125.361115f, 85.0231705f, + 34.3124733f, 130.145813f, 185.097214f, 198.708344f, 167.150467f, 53.1689873f, + 81.4375f, 141.212952f, 124.175926f, 181.437439f, 141.356598f, 67.9537048f, + 63.6944504f, 121.958275f, 156.111115f, 124.627327f, 87.6041565f, 108.99305f, + 132.798615f, 163.875f, 202.393524f, 118.134262f, 106.375f, 111.046295f, + 77.342598f, 160.999908f, 139.044083f, 71.1134415f, 53.7986298f, 78.9652481f, + 200.398148f, 204.555557f, 202.923645f, 85.2384567f, 46.8333359f, 87.9097061f, + 208.615738f, 223.245361f, 124.0f, 25.9282417f, 44.1689835f, 144.833221f, + 145.527832f, 96.2268753f, 76.5972443f, 60.307888f, 109.453705f, 138.777771f, + 158.798599f, 192.78009f, 211.388885f, 210.034729f, 186.087952f, 199.606476f, + 151.0f, 107.71991f, 137.627319f, 181.166626f, 188.250015f, 156.699158f, + 81.5139618f, 42.0440178f, 77.432869f, 51.969902f, 70.2638626f, 155.449051f, + 215.192123f, 212.090286f, 122.564812f, 120.15741f, 126.125f, 142.925934f, + 173.851852f, 209.201355f, 168.101944f, 90.8171844f, 39.2431068f, 93.0161438f, + 103.613426f, 72.5185089f, 97.1805496f, 106.254631f, 174.872665f, 192.152802f, + 88.3564835f, 67.6157379f, 103.625f, 146.314819f, 146.129623f, 190.993011f, + 142.046417f, 65.4282379f, 66.0763855f, 128.134201f, 161.115753f, 173.277771f, + 200.666687f, 86.2222443f, 137.520813f, 177.381973f, 94.9189835f, 71.6782379f, + 103.25f, 128.261566f, 84.8356476f, 143.062424f, 132.946823f, 103.687462f, + 136.187469f, 128.775482f, 143.143524f, 117.277794f, 65.333313f, 126.444427f, + 174.868042f, 178.340286f, 117.766197f, 168.414352f, 149.75f, 115.497688f, + 118.182869f, 169.437439f, 175.294006f, 131.368149f, 29.979269f, 105.539268f, + 180.807861f, 188.442139f, 156.250015f, 109.583351f, 123.870361f, 151.833328f, + 164.409714f, 190.798615f, 151.5625f, 128.034729f, 202.756958f, 208.416656f, + 149.458435f, 73.2384491f, 50.9236336f, 63.9814682f, 191.918976f, 212.150452f, + 213.187546f, 95.6875305f, 68.1411972f, 103.083321f, 182.194443f, 139.972229f, + 96.625f, 109.430557f, 239.98613f, 175.27092f, 120.131989f, 84.4120407f, + 76.819458f, 97.9189606f, 160.402771f, 164.393509f, 182.958359f, 110.73613f, + 59.5254707f, 72.083313f, 172.215286f, 60.8263931f, 24.0f, 68.9953766f, + 209.365753f, 79.7154388f, 100.548485f, 162.252396f, 82.7153549f, 189.80545f, + 104.152779f, 123.754631f, 113.041649f, 198.597198f, 221.789352f, 213.583328f, + 191.506943f, 131.784729f, 102.0f, 100.412041f, 127.449074f, 45.340374f, + 78.9512711f, 141.099594f, 91.0069962f, 175.638794f, 131.710648f, 100.40741f, + 104.062469f, 209.895798f, 199.192139f, 163.451385f, 158.622681f, 113.53009f, + 117.5f, 125.833328f, 94.1666641f, 112.874977f, 122.226852f, 119.333351f, + 101.000015f, 194.608704f, 160.953705f, 123.574074f, 115.937469f, 215.104156f, + 153.428268f, 79.5972214f, 88.0740738f, 131.037033f, 160.4375f, 150.777771f, + 73.5555496f, 151.249908f, 134.247772f, 95.8054886f, 156.916611f, 188.393494f, + 172.759247f, 178.759277f, 133.055527f, 227.129623f, 126.796326f, 13.5555573f, + 6.59257507f, 190.29628f, 229.5f, 172.166656f, 58.8333282f, 141.944351f, + 107.407524f, 74.8331528f, 241.499832f, 156.703796f, 143.629639f, 81.1481323f, + 129.333313f, 187.111099f, 123.518539f, 77.2222137f, 134.333344f, 107.666672f, + 106.5f, 101.481476f, 62.9629593f, 171.777649f, 168.166748f, 103.592636f, + 62.1111526f, 100.796249f, 68.1203766f, 53.2198906f, 104.888855f, 203.407379f, + 167.465302f, 133.958328f, 188.060196f, 185.46759f, 173.0625f, 144.768524f, + 93.2870331f, 191.395721f, 213.016235f, 167.574173f, 72.3889847f, 143.446686f, + 51.995369f, 47.9768486f, 67.8055267f, 176.879593f, 153.104172f, 133.208328f, + 201.539352f, 183.113434f, 188.625f, 186.553238f, 144.793976f, 175.437469f, + 192.023148f, 171.713058f, 79.3056488f, 164.189713f, 100.125008f, 63.4328842f, + 32.9027557f, 106.606461f, 92.9074173f, 92.0624847f, 164.590286f, 108.201393f, + 155.1875f, 213.097229f, 190.319443f, 127.909798f, 120.618027f, 129.513931f, + 92.2917023f, 151.680496f, 97.097229f, 41.0300903f, 47.6527786f, 48.0231476f, + 139.018494f, 193.104172f, 130.465286f, 149.076385f, 198.3125f, 214.236115f, + 130.347214f, 120.034729f, 171.215179f, 218.819504f, 162.708389f, 125.09726f, + 133.039352f, 106.21991f, 93.305542f, 132.379623f, 170.581009f, 187.930557f, + 172.150452f, 198.168976f, 179.5625f, 155.479172f, 164.645828f, 106.465355f, + 129.312424f, 161.5047f, 87.4306259f, 134.462906f, 108.025467f, 103.587967f, + 83.5138702f, 147.032394f, 173.226852f, 174.222229f, 158.03241f, 189.328705f, + 149.375f, 111.527779f, 149.305557f, 76.4028625f, 92.6179733f, 121.178329f, + 36.8264732f, 158.629501f, 36.7986107f, 30.6851864f, 29.6874847f, 87.1874924f, + 154.152771f, 172.486115f, 106.379623f, 141.564819f, 128.9375f, 97.4120331f, + 74.4490738f, 38.3958778f, 75.5600967f, 116.268585f, 49.97229f, 169.532288f, + 78.2569504f, 33.1296196f, 63.8124771f, 146.312485f, 156.513901f, 167.069443f, + 221.879639f, 197.064819f, 152.5625f, 98.5509262f, 44.4768448f, 37.4375076f, + 38.1435127f, 56.8795738f, 112.805496f, 76.0602264f, 59.4490776f, 21.4652672f, + 58.5624657f, 166.062469f, 170.182877f, 132.451401f, 92.6342545f, 143.004623f, + 136.5f, 90.152771f, 17.9305496f, 16.3055553f, 60.7614937f, 106.877335f, + 87.3403015f, 106.858772f, 83.6851883f, 92.8263855f, 91.9374771f, 179.437485f, + 176.446777f, 114.826408f, 18.0578594f, 130.372681f, 136.3125f, 99.7013855f, + 81.0902786f, 45.5972633f, 68.8726196f, 109.412041f, 101.81945f, 137.907364f, + 158.962952f, 221.000031f, 158.090271f, 182.627304f, 171.585648f, 130.659729f, + 66.9907303f, 181.620377f, 160.5f, 123.581024f, 189.599533f, 107.291771f, + 57.2916985f, 59.8702812f, 143.944382f, 133.719925f, 213.962967f, 204.000015f, + 155.631958f, 127.391212f, 159.432861f, 199.618057f, 215.379623f, 235.564804f, + 187.5f, 119.84491f, 79.7523117f, 58.7083588f, 79.819397f, 96.6482086f, + 36.2778397f, 76.3726349f, 108.164352f, 116.571747f, 152.687531f, 73.5208511f, + 82.0254517f, 115.590271f, 136.106476f, 180.365738f, 179.5f, 152.243057f, + 115.298607f, 110.854172f, 139.41893f, 170.381958f, 156.770844f, 86.8033066f, + 64.9907379f, 85.5231171f, 164.416687f, 76.6389084f, 74.1157379f, 81.9861145f, + 55.4259224f, 112.462959f, 139.375f, 141.430557f, 121.986115f, 109.312515f, + 136.627258f, 170.770844f, 159.937515f, 118.185226f, 108.000008f, 118.370346f, + 186.500015f, 126.500023f, 136.314804f, 121.500023f, 24.9629593f, 63.4814796f, + 85.0f, 90.3888855f, 79.277771f, 46.5000381f, 76.1295547f, 103.814896f, + 26.777853f, 150.499863f, 236.833328f, 216.944458f, 181.666672f, 177.222229f, + 107.259277f, 52.4444427f, 64.4444427f, 82.2222214f, 164.5f, 221.981476f, + 163.462967f, 107.111176f, 73.9629822f, 87.9073029f, 189.388779f, 101.074173f, + 114.912041f, 95.4398193f, 78.2291641f, 86.2847137f, 129.180542f, 168.520828f, + 179.363419f, 168.900467f, 171.375f, 151.460648f, 72.1087875f, 143.430466f, + 149.171341f, 114.747711f, 84.2847519f, 67.8680725f, 59.3842583f, 77.7245331f, + 95.5416641f, 94.4305496f, 146.284714f, 191.979172f, 193.328705f, 171.476837f, + 155.625f, 116.481483f, 22.9629536f, 111.80545f, 149.074081f, 141.414398f, + 104.28476f, 116.916656f, 93.9814835f, 168.562485f, 229.131943f, 204.224548f, + 148.773163f, 106.076385f, 103.775467f, 94.7939758f, 130.3125f, 141.125f, + 56.1249924f, 41.9027939f, 79.7846451f, 149.749924f, 224.749924f, 222.92363f, + 125.314819f, 161.71524f, 236.923615f, 235.90509f, 124.189842f, 52.5347137f, + 116.317131f, 129.002319f, 177.1875f, 215.652786f, 198.430557f, 156.652832f, + 116.270874f, 94.1388626f, 116.916641f, 169.243011f, 133.372681f, 147.958328f, + 171.3125f, 215.479156f, 127.534752f, 45.5555534f, 58.0138855f, 108.569443f, + 152.8125f, 180.203705f, 178.90741f, 124.527847f, 81.2870712f, 52.2430687f, + 40.8541794f, 86.5925446f, 122.386581f, 109.743034f, 146.520828f, 140.131943f, + 125.888893f, 90.472229f, 27.7291622f, 81.8958282f, 108.75f, 114.418983f, + 103.400467f, 81.8194656f, 95.0370026f, 105.132004f, 51.5208893f, 51.6967545f, + 119.60186f, 73.7800598f, 155.368088f, 45.2754898f, 114.418953f, 159.743073f, + 55.2893524f, 76.8634262f, 78.0f, 61.4120331f, 28.44907f, 75.6527252f, + 162.238312f, 219.097305f, 138.541748f, 78.7130127f, 229.768524f, 164.460678f, + 54.9097176f, 75.3726807f, 106.71064f, 137.868057f, 161.136581f, 156.599533f, + 141.0f, 113.995369f, 74.3657379f, 158.236008f, 181.557892f, 178.236084f, + 205.458313f, 147.546356f, 213.233795f, 195.094925f, 125.80555f, 153.212952f, + 178.539337f, 182.451401f, 153.731476f, 192.990738f, 137.75f, 67.192131f, + 59.3217583f, 170.437378f, 165.854248f, 111.84259f, 115.361107f, 100.037048f, + 167.511581f, 160.435196f, 158.888901f, 147.40741f, 171.101837f, 185.326385f, + 163.39119f, 214.039352f, 145.625f, 72.5462952f, 108.842598f, 148.208282f, + 151.715302f, 121.717651f, 60.2361755f, 95.0231094f, 128.525467f, 87.9490585f, + 124.020851f, 65.9652863f, 95.7152634f, 153.423615f, 201.305557f, 213.527786f, + 168.3125f, 136.1875f, 188.6875f, 99.6320496f, 140.74292f, 195.011703f, + 72.3265152f, 147.060104f, 142.456024f, 92.7546234f, 109.812508f, 72.3125076f, + 156.45137f, 222.631958f, 181.916656f, 138.583328f, 143.1875f, 131.090271f, + 36.3680458f, 21.0902939f, 102.75679f, 178.525558f, 88.6181488f, 155.532333f, + 159.673615f, 147.409714f, 157.097229f, 118.393524f, 162.662018f, 181.701385f, + 107.145836f, 67.9791718f, 111.5f, 139.974533f, 54.5115662f, 37.9305725f, + 74.5045547f, 121.032417f, 112.513901f, 176.967529f, 156.256943f, 180.777786f, + 153.555557f, 112.629639f, 118.224525f, 113.201393f, 61.9652786f, 45.5763893f, + 120.125f, 167.439804f, 67.2546234f, 61.409729f, 79.3379288f, 111.351814f, + 146.722183f, 166.162033f, 135.666656f, 182.537079f, 98.0000076f, 58.0000076f, + 48.0740776f, 51.5f, 58.2037048f, 66.3518524f, 162.0f, 203.481476f, + 44.9629478f, 70.6110764f, 109.314766f, 149.962921f, 182.555527f, 128.129684f, +}; + +// case 1: 32x24 -> 16x16 +inline constexpr int64_t kLtx2ImgResize1SrcH = 32; +inline constexpr int64_t kLtx2ImgResize1SrcW = 24; +inline constexpr int64_t kLtx2ImgResize1DstH = 16; +inline constexpr int64_t kLtx2ImgResize1DstW = 16; + +inline constexpr float kLtx2ImgResize1Golden[] = { + 119.340904f, 176.340912f, 178.363632f, 95.9659042f, 54.3295479f, 92.2045593f, + 117.011375f, 171.011368f, 62.6477318f, 162.329544f, 105.602264f, 101.750008f, + 114.443176f, 108.340904f, 170.170441f, 125.749992f, 100.034103f, 170.227203f, + 55.5227623f, 63.6022568f, 79.1363297f, 46.2045708f, 151.590897f, 85.4886856f, + 130.20459f, 63.7273254f, 31.1591549f, 144.715881f, 110.284119f, 51.715992f, + 157.659103f, 46.0796013f, 137.25f, 69.0f, 88.625f, 38.0f, + 113.875f, 163.0f, 170.75f, 192.875f, 160.875f, 192.75f, + 105.375f, 84.375f, 199.875f, 215.25f, 133.5f, 172.5f, + 133.659088f, 70.1477051f, 51.5681877f, 155.579605f, 171.284073f, 126.352219f, + 54.1249046f, 145.147705f, 38.3408585f, 165.704529f, 114.920494f, 35.9658852f, + 219.784088f, 158.045425f, 28.6817875f, 53.6022034f, 95.7159042f, 128.852249f, + 66.2272644f, 103.420448f, 88.6136322f, 113.090912f, 144.863647f, 112.170456f, + 182.579544f, 62.3068275f, 124.10228f, 43.8863678f, 222.863632f, 133.170441f, + 71.5681839f, 150.284103f, 149.340973f, 60.7727661f, 166.102371f, 77.875f, + 115.261406f, 82.0567932f, 154.67041f, 84.681778f, 168.761353f, 56.2954025f, + 71.2612991f, 35.943161f, 88.227211f, 57.2727013f, 124.590942f, 190.249985f, + 134.875f, 164.125015f, 143.329544f, 94.954567f, 136.420471f, 228.124985f, + 131.727264f, 109.704552f, 115.181839f, 140.443192f, 68.1704483f, 81.4318161f, + 177.602264f, 150.840897f, 130.079529f, 143.170425f, 164.409119f, 198.193192f, + 159.545456f, 78.3408356f, 118.715828f, 126.045395f, 112.556839f, 114.534088f, + 143.761322f, 98.9317474f, 82.0909424f, 183.988724f, 162.715912f, 125.079575f, + 190.954651f, 140.363708f, 154.158981f, 81.8865051f, 89.7954865f, 48.920536f, + 188.897797f, 93.6138535f, 125.250137f, 149.454575f, 157.488647f, 123.329628f, + 62.1250496f, 92.1931152f, 174.659149f, 157.477142f, 91.6478806f, 145.477127f, + 29.2727394f, 56.8863945f, 70.6363754f, 141.920441f, 228.488632f, 206.97728f, + 121.784119f, 88.579567f, 153.500015f, 149.545456f, 114.284081f, 140.704529f, + 109.295486f, 133.056808f, 138.60228f, 18.5000076f, 117.125038f, 190.761353f, + 79.7954941f, 173.27269f, 89.8863831f, 207.261292f, 207.034027f, 223.636276f, + 169.625f, 58.5568161f, 75.3068237f, 105.886353f, 132.159088f, 197.374985f, + 152.852249f, 43.3181953f, 129.556686f, 122.909042f, 112.215813f, 121.284126f, + 175.522766f, 94.5569153f, 122.386444f, 133.738739f, 137.272644f, 71.8522949f, + 63.6590729f, 74.1590729f, 84.7613525f, 112.545334f, 97.7159424f, 133.238739f, + 21.6478233f, 37.6022606f, 100.636276f, 130.94313f, 201.636276f, 65.0342407f, + 169.965836f, 116.102249f, 21.1025047f, 125.215881f, 206.692993f, 230.147629f, + 124.818237f, 80.2955933f, 170.340851f, 168.772644f, 131.25f, 104.625f, + 89.125f, 74.125f, 156.625f, 130.75f, 132.375f, 102.0f, + 154.25f, 78.125f, 87.0f, 104.625f, 162.5f, 143.375f, + 90.625f, 85.75f, 44.9317474f, 169.738632f, 148.25f, 83.6477432f, + 202.772751f, 109.363647f, 160.170471f, 206.977325f, 71.6704254f, 129.590958f, + 139.363663f, 188.386444f, 159.85228f, 134.625015f, 57.4431877f, 115.125023f, + 170.920547f, 143.579514f, 60.5796242f, 84.4090652f, 77.4660797f, 75.4885712f, + 101.806679f, 78.0796432f, 152.102158f, 191.988525f, 183.079498f, 105.352371f, + 112.193344f, 138.306854f, 131.000107f, 169.284195f, 179.579544f, 152.034088f, + 110.22728f, 185.602264f, 77.8863678f, 50.4090919f, 106.988632f, 116.840904f, + 94.5340805f, 152.181824f, 34.7727318f, 167.52272f, 150.079559f, 167.295441f, + 155.238632f, 147.329544f, 190.056747f, 57.5454407f, 110.784065f, 77.4090805f, + 139.193222f, 78.3068008f, 235.4431f, 133.27272f, 75.4772644f, 206.829544f, + 132.57959f, 86.420517f, 96.8295517f, 95.0909348f, 74.2387009f, 139.079483f, + 146.375f, 132.125f, 81.0f, 105.375f, 172.75f, 115.375f, + 142.0f, 163.0f, 114.0f, 110.625f, 127.625f, 192.125f, + 91.125f, 80.625f, 149.0f, 58.25f, 101.499916f, 158.261322f, + 180.318222f, 87.0454178f, 91.5226898f, 124.999969f, 200.806824f, 73.6476593f, + 91.1931534f, 66.2045746f, 89.5000305f, 240.318222f, 133.386398f, 177.840988f, + 86.0681686f, 214.147797f, 123.431816f, 79.0113754f, 173.727264f, 162.85228f, + 99.3636398f, 131.988647f, 171.784088f, 221.079544f, 119.102272f, 147.704544f, + 101.897728f, 157.806808f, 117.875008f, 174.77272f, 136.795441f, 113.681808f, + 174.556885f, 164.875f, 175.988632f, 99.9658661f, 55.8181419f, 66.3180847f, + 174.011414f, 128.56813f, 74.5340652f, 197.909119f, 134.204559f, 92.7499695f, + 180.886337f, 57.1022263f, 172.670547f, 47.7272682f, 95.7613678f, 171.511368f, + 162.75f, 89.2841034f, 104.943207f, 99.5568008f, 72.5795593f, 63.9886665f, + 172.238663f, 159.25f, 93.6931763f, 178.034088f, 145.988647f, 29.6704578f, + 134.102249f, 141.329529f, 144.147736f, 108.727203f, 72.1817474f, 172.000046f, + 193.454514f, 137.886444f, 169.647766f, 159.386337f, 163.590851f, 75.5339966f, + 163.829605f, 142.761353f, 64.4431f, 113.465973f, 48.0454521f, 138.011429f, + 147.079651f, 138.999893f, 112.295349f, 117.852295f, 73.9205704f, 140.636337f, + 144.170486f, 200.011261f, 218.477264f, 89.477356f, 59.4887733f, 89.2160034f, + 199.443222f, 126.454422f, 204.886261f, 156.647766f, 97.2273026f, 113.624985f, + 156.113617f, 148.170456f, 160.727264f, 52.6931992f, 64.0682068f, 106.98864f, + 135.386383f, 147.89772f, 195.579529f, 199.909073f, 111.73867f, 37.1818199f, + 59.0909309f, 96.7613831f, 153.488647f, 203.465866f, 66.1818466f, 108.931839f, + 147.772736f, 122.954529f, 158.340881f, 57.7386246f, 153.85228f, 156.545456f, + 158.011353f, 216.579498f, 158.670441f, 137.840881f, 189.488602f, 113.534065f, + 149.477234f, 71.886322f, 166.125031f, 210.068268f, 102.386246f, 141.1819f, + 117.613678f, 142.431976f, 176.261368f, 155.738617f, 167.272797f, 91.0795135f, + 180.056915f, 166.181976f, 51.6363106f, 117.227386f, 81.2501526f, 72.863739f, + 135.056854f, 157.42041f, 105.46582f, 157.42041f, 193.318146f, 171.02269f, + 67.3295593f, 134.011261f, 198.136383f, 214.32933f, 239.443024f, 65.2727737f, + 108.625061f, 147.556885f, 172.0f, 123.625f, 94.625f, 111.125f, + 126.375f, 145.5f, 191.375f, 134.375f, 124.25f, 102.125f, + 215.625f, 131.25f, 98.25f, 147.75f, 185.75f, 154.625f, + 156.556824f, 126.181831f, 66.6022949f, 114.465912f, 51.5908241f, 63.6590424f, + 139.079514f, 60.9772453f, 50.3976631f, 166.409103f, 151.738602f, 198.034088f, + 67.5454559f, 185.363632f, 155.249969f, 192.95459f, 104.500053f, 138.590759f, + 148.988556f, 105.693336f, 144.693237f, 146.875f, 152.522522f, 124.193275f, + 172.465988f, 201.136398f, 172.772568f, 128.454483f, 102.76133f, 177.420441f, + 58.2727966f, 80.0909576f, 100.090912f, 141.681808f, 158.420456f, 109.840919f, + 147.113632f, 118.886353f, 151.045441f, 228.568176f, 80.7840958f, 156.806824f, + 124.636375f, 63.9545441f, 66.8863602f, 77.2840958f, 96.0113602f, 140.295456f, + 160.693207f, 184.079514f, 177.625031f, 154.443161f, 222.17038f, 174.852234f, + 31.1250114f, 46.0227356f, 116.749969f, 80.613678f, 193.068161f, 188.431854f, + 195.840897f, 68.5454559f, 64.4545746f, 161.06813f, 196.375f, 103.75f, + 221.25f, 90.375f, 114.25f, 69.625f, 104.25f, 151.875f, + 114.625f, 121.75f, 110.0f, 189.125f, 138.625f, 49.0f, + 103.125f, 121.125f, 209.147736f, 162.306854f, 102.124939f, 161.375046f, + 81.0226746f, 64.1477356f, 82.3976898f, 81.8521881f, 204.613647f, 43.0909042f, + 222.375076f, 156.886368f, 47.045414f, 106.772766f, 66.6249924f, 99.0794983f, + 81.227272f, 124.556831f, 149.079529f, 66.0000076f, 74.8068161f, 145.715912f, + 214.625f, 152.511368f, 191.090912f, 85.272728f, 106.295441f, 55.9431763f, + 129.784073f, 158.89772f, 162.159088f, 198.943176f, 156.977341f, 101.57946f, + 64.5113297f, 151.477295f, 102.818214f, 212.181824f, 53.7612305f, 118.397675f, + 52.0112877f, 179.68187f, 100.545502f, 147.45462f, 140.318253f, 89.5567932f, + 155.045471f, 193.5f, 125.727264f, 141.136353f, 90.0454636f, 153.761368f, + 159.818176f, 102.306816f, 185.329544f, 111.113625f, 101.454559f, 189.102264f, + 150.818146f, 158.795441f, 170.443192f, 46.5909195f, 167.761368f, 94.4318085f, + 73.9090805f, 169.568237f, 150.818192f, 152.079498f, 125.625f, 124.73867f, + 100.30674f, 119.227325f, 150.943161f, 108.431801f, 180.86377f, 181.000046f, + 190.136353f, 108.318207f, 123.249954f, 46.4090652f, 178.897644f, 140.477402f, + 162.806595f, 149.57959f, 191.465927f, 162.590805f, 126.897614f, 91.5796509f, + 199.193222f, 90.170578f, 187.2043f, 114.829552f, 104.238808f, 92.0342331f, + 153.545258f, 170.795349f, 190.784073f, 191.840912f, 144.999969f, 71.8750229f, + 82.9091187f, 71.8636398f, 107.318169f, 177.75f, 174.22728f, 102.465927f, + 109.943161f, 157.02272f, 86.8636703f, 71.9659348f, 54.8295364f, 119.499992f, + 105.011421f, 229.409088f, 50.6250076f, 122.113625f, 213.999969f, 122.875046f, + 113.772758f, 127.784088f, 234.215866f, 196.681763f, 32.6364136f, 50.2955246f, + 115.261368f, 117.306816f, 147.761353f, 116.568138f, 164.283981f, 172.94313f, + 81.2613907f, 89.545433f, 86.181778f, 230.931808f, 171.090897f, 136.590912f, + 59.8180695f, 62.579525f, 207.840973f, 134.340775f, 68.1249313f, 92.806778f, + 137.193192f, 91.068306f, 16.7727737f, 41.8977623f, 36.0795631f, 100.000069f, + 205.340759f, 199.647705f, 147.03418f, 60.7159538f, 155.693146f, 130.806885f, + 126.352203f, 60.5570717f, 211.613632f, 183.658875f, 141.363647f, 61.318222f, + 39.75f, 48.0f, 68.125f, 127.0f, 137.0f, 120.5f, + 192.0f, 124.5f, 109.0f, 160.0f, 62.625f, 208.5f, + 137.25f, 92.25f, 127.375f, 71.5f, 207.579636f, 113.045502f, + 206.363708f, 37.5454178f, 67.2272186f, 175.943253f, 128.147705f, 102.374969f, + 54.1931648f, 213.295486f, 79.3181992f, 182.715912f, 172.420517f, 218.47731f, + 195.47731f, 158.045517f, 158.647537f, 108.772713f, 104.261307f, 133.715805f, + 146.431671f, 123.11348f, 154.863556f, 147.636444f, 59.4205437f, 161.795502f, + 113.159058f, 139.306671f, 164.431885f, 119.909142f, 102.841034f, 166.76149f, +}; + +// case 2: 16x16 -> 16x16 +inline constexpr int64_t kLtx2ImgResize2SrcH = 16; +inline constexpr int64_t kLtx2ImgResize2SrcW = 16; +inline constexpr int64_t kLtx2ImgResize2DstH = 16; +inline constexpr int64_t kLtx2ImgResize2DstW = 16; + +inline constexpr float kLtx2ImgResize2Golden[] = { + 47.0f, 65.0f, 150.0f, 227.0f, 58.0f, 150.0f, + 134.0f, 0.0f, 167.0f, 112.0f, 233.0f, 209.0f, + 35.0f, 172.0f, 246.0f, 198.0f, 175.0f, 138.0f, + 221.0f, 237.0f, 116.0f, 104.0f, 108.0f, 243.0f, + 32.0f, 253.0f, 89.0f, 209.0f, 46.0f, 31.0f, + 14.0f, 23.0f, 73.0f, 53.0f, 209.0f, 138.0f, + 73.0f, 91.0f, 46.0f, 239.0f, 124.0f, 74.0f, + 225.0f, 218.0f, 234.0f, 225.0f, 65.0f, 109.0f, + 41.0f, 248.0f, 153.0f, 7.0f, 88.0f, 123.0f, + 171.0f, 137.0f, 216.0f, 4.0f, 16.0f, 158.0f, + 37.0f, 182.0f, 243.0f, 158.0f, 182.0f, 252.0f, + 36.0f, 228.0f, 172.0f, 1.0f, 169.0f, 80.0f, + 162.0f, 54.0f, 123.0f, 55.0f, 135.0f, 249.0f, + 233.0f, 91.0f, 200.0f, 177.0f, 88.0f, 96.0f, + 25.0f, 179.0f, 169.0f, 2.0f, 65.0f, 36.0f, + 148.0f, 6.0f, 65.0f, 204.0f, 73.0f, 93.0f, + 97.0f, 111.0f, 136.0f, 140.0f, 125.0f, 20.0f, + 88.0f, 241.0f, 181.0f, 129.0f, 171.0f, 212.0f, + 105.0f, 7.0f, 128.0f, 135.0f, 194.0f, 240.0f, + 5.0f, 14.0f, 203.0f, 23.0f, 76.0f, 60.0f, + 80.0f, 228.0f, 99.0f, 222.0f, 116.0f, 94.0f, + 117.0f, 25.0f, 174.0f, 32.0f, 17.0f, 150.0f, + 25.0f, 41.0f, 47.0f, 185.0f, 206.0f, 25.0f, + 178.0f, 18.0f, 44.0f, 109.0f, 229.0f, 148.0f, + 1.0f, 116.0f, 206.0f, 98.0f, 213.0f, 193.0f, + 25.0f, 126.0f, 226.0f, 13.0f, 105.0f, 251.0f, + 168.0f, 167.0f, 85.0f, 60.0f, 12.0f, 232.0f, + 82.0f, 14.0f, 124.0f, 127.0f, 66.0f, 244.0f, + 1.0f, 180.0f, 209.0f, 125.0f, 92.0f, 3.0f, + 169.0f, 11.0f, 41.0f, 156.0f, 122.0f, 49.0f, + 173.0f, 65.0f, 0.0f, 169.0f, 115.0f, 154.0f, + 108.0f, 40.0f, 241.0f, 86.0f, 32.0f, 239.0f, + 94.0f, 227.0f, 183.0f, 22.0f, 209.0f, 96.0f, + 107.0f, 168.0f, 171.0f, 128.0f, 147.0f, 149.0f, + 74.0f, 129.0f, 143.0f, 253.0f, 139.0f, 162.0f, + 223.0f, 168.0f, 70.0f, 160.0f, 229.0f, 73.0f, + 165.0f, 102.0f, 137.0f, 111.0f, 43.0f, 94.0f, + 10.0f, 40.0f, 225.0f, 132.0f, 180.0f, 123.0f, + 46.0f, 56.0f, 238.0f, 208.0f, 72.0f, 136.0f, + 98.0f, 177.0f, 84.0f, 162.0f, 41.0f, 216.0f, + 117.0f, 9.0f, 158.0f, 37.0f, 79.0f, 126.0f, + 135.0f, 74.0f, 95.0f, 46.0f, 85.0f, 217.0f, + 200.0f, 252.0f, 54.0f, 255.0f, 30.0f, 216.0f, + 95.0f, 245.0f, 253.0f, 9.0f, 16.0f, 191.0f, + 194.0f, 163.0f, 187.0f, 216.0f, 118.0f, 242.0f, + 243.0f, 196.0f, 71.0f, 227.0f, 14.0f, 40.0f, + 118.0f, 182.0f, 158.0f, 240.0f, 227.0f, 146.0f, + 219.0f, 89.0f, 174.0f, 19.0f, 222.0f, 216.0f, + 205.0f, 190.0f, 197.0f, 58.0f, 237.0f, 228.0f, + 106.0f, 177.0f, 216.0f, 195.0f, 168.0f, 194.0f, + 14.0f, 161.0f, 119.0f, 255.0f, 38.0f, 182.0f, + 18.0f, 199.0f, 199.0f, 162.0f, 222.0f, 71.0f, + 152.0f, 111.0f, 96.0f, 150.0f, 33.0f, 63.0f, + 72.0f, 121.0f, 226.0f, 194.0f, 48.0f, 85.0f, + 187.0f, 137.0f, 42.0f, 200.0f, 204.0f, 95.0f, + 10.0f, 122.0f, 170.0f, 116.0f, 255.0f, 40.0f, + 246.0f, 65.0f, 66.0f, 184.0f, 217.0f, 113.0f, + 247.0f, 250.0f, 179.0f, 17.0f, 248.0f, 101.0f, + 122.0f, 53.0f, 57.0f, 98.0f, 44.0f, 127.0f, + 170.0f, 32.0f, 215.0f, 21.0f, 109.0f, 1.0f, + 226.0f, 177.0f, 150.0f, 237.0f, 235.0f, 196.0f, + 8.0f, 214.0f, 0.0f, 22.0f, 134.0f, 218.0f, + 227.0f, 238.0f, 169.0f, 133.0f, 56.0f, 10.0f, + 209.0f, 78.0f, 217.0f, 127.0f, 23.0f, 237.0f, + 157.0f, 112.0f, 244.0f, 188.0f, 224.0f, 57.0f, + 176.0f, 91.0f, 237.0f, 252.0f, 159.0f, 117.0f, + 198.0f, 90.0f, 37.0f, 207.0f, 90.0f, 219.0f, + 77.0f, 244.0f, 82.0f, 195.0f, 50.0f, 109.0f, + 152.0f, 107.0f, 1.0f, 83.0f, 244.0f, 15.0f, + 97.0f, 36.0f, 37.0f, 221.0f, 235.0f, 105.0f, + 16.0f, 124.0f, 94.0f, 89.0f, 18.0f, 77.0f, + 169.0f, 244.0f, 163.0f, 45.0f, 57.0f, 109.0f, + 238.0f, 169.0f, 76.0f, 230.0f, 35.0f, 176.0f, + 150.0f, 240.0f, 145.0f, 163.0f, 12.0f, 132.0f, + 164.0f, 147.0f, 35.0f, 230.0f, 47.0f, 37.0f, + 221.0f, 242.0f, 38.0f, 148.0f, 40.0f, 185.0f, + 109.0f, 0.0f, 74.0f, 11.0f, 200.0f, 155.0f, + 4.0f, 168.0f, 251.0f, 203.0f, 128.0f, 46.0f, + 236.0f, 103.0f, 87.0f, 213.0f, 85.0f, 210.0f, + 85.0f, 167.0f, 88.0f, 123.0f, 69.0f, 254.0f, + 213.0f, 163.0f, 179.0f, 8.0f, 100.0f, 91.0f, + 217.0f, 225.0f, 160.0f, 79.0f, 76.0f, 128.0f, + 0.0f, 104.0f, 114.0f, 15.0f, 106.0f, 94.0f, + 193.0f, 116.0f, 46.0f, 12.0f, 170.0f, 42.0f, + 36.0f, 224.0f, 239.0f, 79.0f, 189.0f, 161.0f, + 192.0f, 102.0f, 11.0f, 101.0f, 59.0f, 206.0f, + 56.0f, 153.0f, 44.0f, 252.0f, 117.0f, 80.0f, + 249.0f, 138.0f, 81.0f, 84.0f, 50.0f, 224.0f, + 90.0f, 47.0f, 31.0f, 186.0f, 216.0f, 216.0f, + 74.0f, 222.0f, 250.0f, 186.0f, 210.0f, 7.0f, + 42.0f, 92.0f, 43.0f, 37.0f, 209.0f, 195.0f, + 124.0f, 9.0f, 126.0f, 59.0f, 12.0f, 165.0f, + 45.0f, 108.0f, 112.0f, 118.0f, 86.0f, 195.0f, + 103.0f, 137.0f, 105.0f, 43.0f, 160.0f, 1.0f, + 141.0f, 73.0f, 168.0f, 80.0f, 108.0f, 19.0f, + 248.0f, 42.0f, 37.0f, 9.0f, 248.0f, 199.0f, + 86.0f, 27.0f, 233.0f, 219.0f, 19.0f, 162.0f, + 115.0f, 197.0f, 6.0f, 32.0f, 153.0f, 54.0f, + 38.0f, 255.0f, 34.0f, 174.0f, 18.0f, 159.0f, + 245.0f, 225.0f, 15.0f, 223.0f, 249.0f, 96.0f, + 144.0f, 228.0f, 45.0f, 227.0f, 73.0f, 236.0f, + 41.0f, 220.0f, 10.0f, 16.0f, 85.0f, 177.0f, + 219.0f, 121.0f, 95.0f, 144.0f, 78.0f, 27.0f, + 85.0f, 158.0f, 114.0f, 116.0f, 193.0f, 114.0f, + 136.0f, 225.0f, 3.0f, 208.0f, 221.0f, 148.0f, + 68.0f, 242.0f, 178.0f, 52.0f, 76.0f, 0.0f, + 235.0f, 4.0f, 87.0f, 137.0f, 66.0f, 225.0f, + 119.0f, 26.0f, 187.0f, 7.0f, 108.0f, 153.0f, + 196.0f, 34.0f, 206.0f, 107.0f, 19.0f, 89.0f, + 103.0f, 229.0f, 45.0f, 241.0f, 216.0f, 26.0f, + 17.0f, 43.0f, 243.0f, 36.0f, 211.0f, 153.0f, + 237.0f, 222.0f, 169.0f, 107.0f, 61.0f, 49.0f, + 114.0f, 207.0f, 139.0f, 100.0f, 190.0f, 181.0f, + 223.0f, 92.0f, 58.0f, 14.0f, 53.0f, 228.0f, + 139.0f, 241.0f, 204.0f, 200.0f, 88.0f, 189.0f, + 214.0f, 228.0f, 132.0f, 167.0f, 128.0f, 175.0f, + 120.0f, 218.0f, 49.0f, 175.0f, 215.0f, 226.0f, + 112.0f, 139.0f, 122.0f, 221.0f, 55.0f, 12.0f, + 190.0f, 43.0f, 211.0f, 229.0f, 87.0f, 193.0f, + 121.0f, 25.0f, 158.0f, 88.0f, 35.0f, 23.0f, + 158.0f, 10.0f, 34.0f, 122.0f, 4.0f, 227.0f, + 174.0f, 218.0f, 124.0f, 75.0f, 147.0f, 4.0f, + 176.0f, 134.0f, 36.0f, 52.0f, 231.0f, 63.0f, + 41.0f, 2.0f, 201.0f, 60.0f, 96.0f, 178.0f, + 13.0f, 125.0f, 242.0f, 23.0f, 18.0f, 12.0f, + 109.0f, 46.0f, 203.0f, 31.0f, 117.0f, 92.0f, + 108.0f, 210.0f, 60.0f, 150.0f, 66.0f, 253.0f, + 174.0f, 0.0f, 239.0f, 154.0f, 25.0f, 186.0f, +}; + +// case 3: 8x8 -> 16x24 +inline constexpr int64_t kLtx2ImgResize3SrcH = 8; +inline constexpr int64_t kLtx2ImgResize3SrcW = 8; +inline constexpr int64_t kLtx2ImgResize3DstH = 16; +inline constexpr int64_t kLtx2ImgResize3DstW = 24; + +inline constexpr float kLtx2ImgResize3Golden[] = { + 210.0f, 210.0f, 220.0f, 230.0f, 240.0f, 232.333328f, + 224.666672f, 217.0f, 198.999985f, 181.0f, 163.0f, 156.666656f, + 150.333328f, 144.0f, 97.333313f, 50.6666222f, 4.0f, 21.6666756f, + 39.3333511f, 57.0f, 65.0000076f, 73.0000076f, 81.0f, 81.0f, + 183.666656f, 183.666672f, 177.222214f, 170.777756f, 164.333313f, 173.111115f, + 181.888885f, 190.666656f, 179.777771f, 168.888885f, 158.0f, 164.888885f, + 171.777786f, 178.666672f, 135.222214f, 91.7777405f, 48.3333397f, 50.1111145f, + 51.8888893f, 53.6666641f, 71.222229f, 88.7777939f, 106.333336f, 106.333336f, + 157.333328f, 157.333328f, 134.444427f, 111.555542f, 88.6666489f, 113.888878f, + 139.111099f, 164.333328f, 160.555557f, 156.777771f, 153.0f, 173.111115f, + 193.222229f, 213.333344f, 173.111099f, 132.888855f, 92.6666794f, 78.5555573f, + 64.4444351f, 50.3333321f, 77.444458f, 104.555588f, 131.666672f, 131.666672f, + 131.0f, 131.0f, 91.6666641f, 52.3333321f, 13.0f, 54.6666718f, + 96.3333435f, 138.0f, 141.333344f, 144.666672f, 148.0f, 181.333344f, + 214.666687f, 248.0f, 210.999985f, 173.999969f, 137.0f, 106.999985f, + 76.9999695f, 47.0f, 83.666687f, 120.333366f, 157.0f, 157.0f, + 167.333344f, 167.333344f, 140.000015f, 112.666695f, 85.3333664f, 108.222252f, + 131.11113f, 154.0f, 138.666656f, 123.333321f, 107.999977f, 153.888901f, + 199.777786f, 245.666672f, 214.0f, 182.333298f, 150.666672f, 121.333328f, + 91.9999771f, 62.6666756f, 95.2222443f, 127.777817f, 160.333328f, 160.333328f, + 203.666672f, 203.666672f, 188.333328f, 173.000015f, 157.666687f, 161.777786f, + 165.888901f, 170.0f, 135.999985f, 101.999985f, 67.9999924f, 126.444466f, + 184.888901f, 243.333328f, 216.999985f, 190.666641f, 164.333344f, 135.666656f, + 106.999977f, 78.3333359f, 106.777794f, 135.22226f, 163.666672f, 163.666672f, + 240.0f, 240.0f, 236.666672f, 233.333344f, 230.0f, 215.333328f, + 200.666656f, 186.0f, 133.333313f, 80.6666565f, 28.0f, 99.0000381f, + 170.000015f, 241.0f, 219.999985f, 198.999985f, 178.0f, 149.999985f, + 121.999969f, 94.0f, 118.333344f, 142.666687f, 167.0f, 167.0f, + 159.999969f, 159.999969f, 158.222183f, 156.444412f, 154.666626f, 158.666626f, + 162.666641f, 166.666656f, 145.333328f, 124.000015f, 102.666702f, 142.111145f, + 181.555573f, 220.999985f, 198.777756f, 176.555527f, 154.333328f, 142.777771f, + 131.222214f, 119.666679f, 126.333336f, 133.0f, 139.666656f, 139.666656f, + 79.9999847f, 79.9999847f, 79.7777557f, 79.555542f, 79.333313f, 101.999985f, + 124.666656f, 147.333328f, 157.333344f, 167.333344f, 177.333344f, 185.222229f, + 193.111115f, 201.0f, 177.555527f, 154.111084f, 130.666656f, 135.555557f, + 140.444458f, 145.333344f, 134.333328f, 123.333321f, 112.333328f, 112.333328f, + 0.0f, 5.96046448e-08f, 1.33333349f, 2.66666675f, 4.0f, 45.3333397f, + 86.6666794f, 128.0f, 169.333344f, 210.666672f, 252.0f, 228.333328f, + 204.666656f, 181.0f, 156.333328f, 131.666641f, 107.0f, 128.333344f, + 149.666687f, 171.0f, 142.333313f, 113.666641f, 85.0f, 85.0f, + 13.6666737f, 13.6666737f, 15.888896f, 18.1111183f, 20.3333416f, 62.3333473f, + 104.333351f, 146.333344f, 173.666672f, 201.0f, 228.333313f, 196.777756f, + 165.222198f, 133.666641f, 127.999985f, 122.333321f, 116.666672f, 134.000015f, + 151.333344f, 168.666656f, 154.111115f, 139.555557f, 125.000015f, 125.000023f, + 27.3333473f, 27.3333473f, 30.444458f, 33.5555687f, 36.6666832f, 79.3333588f, + 122.000031f, 164.666687f, 178.0f, 191.333313f, 204.666641f, 165.222168f, + 125.777725f, 86.3332901f, 99.6666489f, 113.000008f, 126.333344f, 139.666672f, + 153.000015f, 166.333328f, 165.888901f, 165.444473f, 165.000031f, 165.000031f, + 41.0f, 41.0f, 45.0f, 49.0f, 53.0f, 96.3333359f, + 139.666672f, 183.0f, 182.333328f, 181.666672f, 181.0f, 133.666641f, + 86.3333206f, 39.0f, 71.3333511f, 103.666695f, 136.0f, 145.333344f, + 154.666687f, 164.0f, 177.666672f, 191.333344f, 205.0f, 205.0f, + 68.6666794f, 68.6666794f, 63.1111183f, 57.5555573f, 52.0f, 84.8888855f, + 117.777771f, 150.666656f, 147.555542f, 144.444427f, 141.333313f, 129.222214f, + 117.11113f, 105.000031f, 111.555573f, 118.111122f, 124.666656f, 130.0f, + 135.333328f, 140.666656f, 149.111099f, 157.555542f, 165.999985f, 165.999985f, + 96.3333588f, 96.3333588f, 81.2222366f, 66.1111145f, 51.0f, 73.4444351f, + 95.8888702f, 118.333305f, 112.77774f, 107.222183f, 101.666626f, 124.777786f, + 147.888931f, 171.000061f, 151.777802f, 132.555557f, 113.333321f, 114.666649f, + 115.999977f, 117.333313f, 120.555527f, 123.777748f, 126.999962f, 126.999962f, + 124.0f, 124.0f, 99.3333282f, 74.6666641f, 50.0f, 62.0f, + 74.0f, 86.0f, 77.9999924f, 70.0f, 62.0f, 120.333359f, + 178.666672f, 237.0f, 191.999985f, 146.999954f, 102.0f, 99.3333282f, + 96.6666641f, 94.0f, 92.0f, 90.0f, 88.0f, 88.0f, + 239.0f, 239.0f, 183.666656f, 128.333328f, 73.0f, 87.0f, + 101.000008f, 115.0f, 76.9999847f, 38.9999924f, 1.0f, 12.0000057f, + 23.0000038f, 34.0f, 105.333366f, 176.666733f, 248.0f, 240.666656f, + 233.333313f, 226.0f, 214.666656f, 203.333328f, 192.0f, 192.0f, + 195.666672f, 195.666672f, 149.222214f, 102.777779f, 56.3333321f, 78.4444427f, + 100.555557f, 122.666672f, 107.999992f, 93.3333359f, 78.6666794f, 68.5555573f, + 58.4444466f, 48.3333359f, 91.5555649f, 134.777817f, 178.0f, 190.444443f, + 202.888901f, 215.333328f, 210.888885f, 206.444443f, 202.0f, 202.0f, + 152.333328f, 152.333328f, 114.777763f, 77.2222137f, 39.6666641f, 69.8888855f, + 100.111115f, 130.333328f, 139.000015f, 147.666672f, 156.333359f, 125.111107f, + 93.8888931f, 62.6666679f, 77.7777863f, 92.8888931f, 107.999985f, 140.222229f, + 172.444473f, 204.666656f, 207.111099f, 209.555557f, 212.0f, 212.0f, + 109.0f, 109.0f, 80.3333282f, 51.6666641f, 23.0f, 61.3333359f, + 99.6666718f, 138.0f, 170.000015f, 202.0f, 234.0f, 181.666641f, + 129.333328f, 77.0f, 63.9999924f, 50.9999886f, 38.0f, 90.0000229f, + 142.000046f, 194.0f, 203.333344f, 212.666687f, 222.0f, 222.0f, + 121.000008f, 121.000008f, 105.888893f, 90.7777939f, 75.6666946f, 99.0000229f, + 122.333351f, 145.666672f, 157.333328f, 168.999985f, 180.666641f, 159.555527f, + 138.444443f, 117.333351f, 111.55558f, 105.777802f, 100.000031f, 134.444473f, + 168.888931f, 203.333328f, 192.999985f, 182.666641f, 172.333313f, 172.333313f, + 133.0f, 133.0f, 131.444458f, 129.888901f, 128.333344f, 136.666672f, + 145.0f, 153.333328f, 144.666656f, 135.999985f, 127.333321f, 137.444443f, + 147.555557f, 157.666672f, 159.111115f, 160.555573f, 162.000015f, 178.888901f, + 195.777802f, 212.666672f, 182.666641f, 152.666626f, 122.666656f, 122.666656f, + 145.0f, 145.0f, 157.0f, 169.0f, 181.0f, 174.333328f, + 167.666656f, 161.0f, 131.999985f, 102.999992f, 74.0f, 115.333351f, + 156.666672f, 198.0f, 206.666672f, 215.333344f, 224.0f, 223.333344f, + 222.666672f, 222.0f, 172.333313f, 122.666618f, 73.0f, 73.0f, + 117.333321f, 117.333328f, 141.444443f, 165.555557f, 189.666672f, 184.333344f, + 179.0f, 173.666672f, 150.222214f, 126.777786f, 103.333344f, 128.444458f, + 153.555557f, 178.666656f, 178.333313f, 177.999969f, 177.666641f, 179.88887f, + 182.111099f, 184.333313f, 161.333313f, 138.333313f, 115.333359f, 115.333351f, + 89.6666565f, 89.6666641f, 125.888885f, 162.111115f, 198.333344f, 194.333328f, + 190.333328f, 186.333328f, 168.444427f, 150.555557f, 132.666672f, 141.555557f, + 150.444443f, 159.333328f, 149.999985f, 140.666641f, 131.333328f, 136.444443f, + 141.555557f, 146.666656f, 150.333328f, 154.0f, 157.666672f, 157.666672f, + 62.0f, 62.0000038f, 110.333336f, 158.666672f, 207.0f, 204.333328f, + 201.666672f, 199.0f, 186.666656f, 174.333328f, 162.0f, 154.666656f, + 147.333328f, 140.0f, 121.666656f, 103.333313f, 85.0f, 93.0000076f, + 101.000008f, 109.0f, 139.333359f, 169.666702f, 200.0f, 200.0f, + 47.6666603f, 47.6666603f, 79.6666565f, 111.666649f, 143.666641f, 140.77774f, + 137.888855f, 134.999969f, 133.444427f, 131.88887f, 130.333328f, 123.444427f, + 116.555534f, 109.666656f, 115.444443f, 121.222237f, 127.000023f, 135.000015f, + 143.000031f, 151.000015f, 172.888916f, 194.777802f, 216.666672f, 216.666672f, + 33.3333206f, 33.3333206f, 48.9999695f, 64.666626f, 80.3332672f, 77.2221603f, + 74.1110458f, 70.999939f, 80.2221756f, 89.4444046f, 98.6666336f, 92.2221909f, + 85.7777481f, 79.3333054f, 109.222229f, 139.11116f, 169.000046f, 177.000046f, + 185.000046f, 193.000046f, 206.444473f, 219.888931f, 233.333344f, 233.333344f, + 19.0f, 19.0f, 18.333334f, 17.666666f, 17.0f, 13.666666f, + 10.3333321f, 7.0f, 27.0000095f, 47.0000038f, 67.0f, 60.9999962f, + 55.0f, 49.0f, 103.000023f, 157.000046f, 211.0f, 219.0f, + 227.000015f, 235.0f, 240.0f, 245.0f, 250.0f, 250.0f, + 34.0000076f, 34.0000076f, 37.2222328f, 40.4444542f, 43.6666794f, 44.3333473f, + 45.0000153f, 45.666687f, 69.888916f, 94.1111374f, 118.333359f, 113.888916f, + 109.444466f, 105.000031f, 124.111122f, 143.222229f, 162.333298f, 160.444412f, + 158.555511f, 156.666626f, 185.555542f, 214.444458f, 243.333328f, 243.333313f, + 49.0000153f, 49.0000153f, 56.1111298f, 63.2222443f, 70.3333588f, 75.0000305f, + 79.6667023f, 84.333374f, 112.777832f, 141.222275f, 169.666718f, 166.777832f, + 163.888931f, 161.000046f, 145.222229f, 129.444427f, 113.666618f, 101.888824f, + 90.1110306f, 78.3332596f, 131.111084f, 183.888901f, 236.666656f, 236.666656f, + 64.0f, 64.0f, 75.0f, 86.0f, 97.0f, 105.666664f, + 114.333336f, 123.0f, 155.666687f, 188.333328f, 221.0f, 219.666672f, + 218.333344f, 217.0f, 166.333313f, 115.666618f, 65.0f, 43.3333244f, + 21.6666451f, 0.0f, 76.6667023f, 153.333405f, 230.0f, 230.0f, + 86.0f, 86.0f, 138.000015f, 190.000015f, 242.0f, 235.0f, + 227.999985f, 221.0f, 161.999969f, 102.999985f, 44.0f, 111.333366f, + 178.666672f, 246.0f, 174.666626f, 103.333267f, 32.0f, 77.666687f, + 123.333374f, 169.0f, 187.666672f, 206.333359f, 225.0f, 225.0f, + 124.666672f, 124.666672f, 152.555557f, 180.444443f, 208.333328f, 195.222214f, + 182.111099f, 169.0f, 131.888855f, 94.7777634f, 57.6666679f, 115.000023f, + 172.333344f, 229.666656f, 178.333313f, 126.999954f, 75.6666718f, 92.8889008f, + 110.111137f, 127.333328f, 155.000015f, 182.666687f, 210.333328f, 210.333328f, + 163.333344f, 163.333344f, 167.111115f, 170.888885f, 174.666656f, 155.444427f, + 136.222198f, 116.999985f, 101.777763f, 86.5555496f, 71.3333359f, 118.666687f, + 166.000015f, 213.333328f, 181.999985f, 150.666641f, 119.333344f, 108.111115f, + 96.8888702f, 85.6666565f, 122.333344f, 159.000031f, 195.666672f, 195.666656f, + 202.0f, 202.0f, 181.666656f, 161.333328f, 141.0f, 115.666664f, + 90.3333282f, 65.0f, 71.6666718f, 78.3333359f, 85.0f, 122.333351f, + 159.666672f, 197.0f, 185.666656f, 174.333328f, 163.0f, 123.333313f, + 83.666626f, 44.0f, 89.666687f, 135.333374f, 181.0f, 181.0f, + 156.999985f, 156.999985f, 152.222198f, 147.444443f, 142.666672f, 126.111115f, + 109.555565f, 93.0000153f, 105.888908f, 118.777802f, 131.666687f, 152.000015f, + 172.333344f, 192.666656f, 174.777756f, 156.888855f, 138.999985f, 117.777771f, + 96.5555344f, 75.3333511f, 91.222229f, 107.111107f, 122.999977f, 122.999969f, + 111.999992f, 111.999992f, 122.777771f, 133.555557f, 144.333328f, 136.555557f, + 128.777771f, 121.000008f, 140.11113f, 159.222229f, 178.333344f, 181.666672f, + 185.0f, 188.333328f, 163.88887f, 139.444412f, 114.999992f, 112.222221f, + 109.444443f, 106.666672f, 92.777771f, 78.8888626f, 64.9999847f, 64.9999847f, + 67.0f, 67.0f, 93.3333359f, 119.666664f, 146.0f, 147.0f, + 148.0f, 149.0f, 174.333344f, 199.666672f, 225.0f, 211.333328f, + 197.666656f, 184.0f, 152.999985f, 121.999969f, 91.0f, 106.666672f, + 122.333351f, 138.0f, 94.333313f, 50.666626f, 7.0f, 7.0f, + 79.6666718f, 79.6666718f, 99.7777786f, 119.888893f, 140.0f, 146.111115f, + 152.222229f, 158.333344f, 180.444458f, 202.555557f, 224.666672f, 194.555527f, + 164.444427f, 134.333313f, 116.888863f, 99.4444199f, 82.0f, 90.3333359f, + 98.6666565f, 106.999985f, 74.5555344f, 42.1110725f, 9.66666794f, 9.66666794f, + 92.3333359f, 92.3333359f, 106.222221f, 120.111115f, 134.0f, 145.222229f, + 156.444443f, 167.666672f, 186.555557f, 205.444443f, 224.333344f, 177.777756f, + 131.222198f, 84.6666565f, 80.777771f, 76.8888855f, 73.0f, 74.0f, + 74.9999924f, 75.9999924f, 54.7777634f, 33.5555305f, 12.333334f, 12.333334f, + 105.0f, 105.0f, 112.666664f, 120.333336f, 128.0f, 144.333344f, + 160.666672f, 177.0f, 192.666672f, 208.333328f, 224.0f, 160.999969f, + 97.9999847f, 35.0f, 44.6666718f, 54.3333435f, 64.0f, 57.6666641f, + 51.3333282f, 45.0f, 34.9999962f, 24.9999905f, 15.0f, 15.0f, + 148.666687f, 148.666687f, 134.666672f, 120.666672f, 106.666656f, 110.444427f, + 114.222198f, 117.999969f, 131.666641f, 145.333298f, 158.999969f, 116.666626f, + 74.3333054f, 32.0f, 54.3333473f, 76.6666946f, 99.0000153f, 84.7777863f, + 70.5555496f, 56.3333397f, 49.7777824f, 43.2222252f, 36.6666794f, 36.6666756f, + 192.333374f, 192.333374f, 156.666687f, 121.0f, 85.333313f, 76.5555191f, + 67.7777252f, 58.9999428f, 70.6666107f, 82.3332672f, 93.999939f, 72.3332825f, + 50.6666374f, 28.9999962f, 64.0000229f, 99.0000534f, 134.000031f, 111.888901f, + 89.777771f, 67.6666794f, 64.5555649f, 61.444458f, 58.3333549f, 58.3333511f, + 236.0f, 236.0f, 178.666656f, 121.333328f, 64.0f, 42.6666641f, + 21.3333282f, 0.0f, 9.66667175f, 19.3333359f, 29.0f, 28.0f, + 27.0f, 26.0f, 73.666687f, 121.333374f, 169.0f, 138.999985f, + 108.999969f, 79.0f, 79.3333359f, 79.6666641f, 80.0f, 80.0f, + 187.999985f, 187.999985f, 155.444427f, 122.888885f, 90.3333435f, 66.4444504f, + 42.5555611f, 18.6666756f, 43.1111374f, 67.5555878f, 92.0000305f, 94.5555878f, + 97.111145f, 99.6667023f, 108.444466f, 117.222237f, 125.999985f, 102.666641f, + 79.3332901f, 55.9999886f, 61.777771f, 67.5555573f, 73.3333282f, 73.3333282f, + 139.999954f, 139.999954f, 132.222198f, 124.444443f, 116.666695f, 90.2222366f, + 63.7777901f, 37.3333511f, 76.555603f, 115.777832f, 155.000061f, 161.111176f, + 167.22229f, 173.333405f, 143.222244f, 113.111076f, 82.9999542f, 66.3332825f, + 49.666626f, 32.9999771f, 44.2222099f, 55.4444427f, 66.6666565f, 66.6666565f, + 92.0f, 92.0f, 109.0f, 126.0f, 143.0f, 113.999992f, + 84.9999924f, 56.0f, 110.000031f, 164.0f, 218.0f, 227.666672f, + 237.333344f, 247.0f, 177.999969f, 108.999931f, 40.0f, 29.9999962f, + 19.9999905f, 10.0f, 26.6666756f, 43.3333511f, 60.0f, 60.0f, +}; + +// case 4: 10x7 -> 16x16 +inline constexpr int64_t kLtx2ImgResize4SrcH = 10; +inline constexpr int64_t kLtx2ImgResize4SrcW = 7; +inline constexpr int64_t kLtx2ImgResize4DstH = 16; +inline constexpr int64_t kLtx2ImgResize4DstW = 16; + +inline constexpr float kLtx2ImgResize4Golden[] = { + 143.956512f, 156.358017f, 191.082184f, 219.076096f, 159.57608f, 100.07608f, + 103.811142f, 124.792122f, 157.119553f, 200.793472f, 228.703125f, 198.810455f, + 168.917816f, 129.575409f, 89.5061188f, 75.1956558f, 123.086967f, 130.393341f, + 150.851242f, 166.847839f, 124.847832f, 82.8478317f, 110.658966f, 157.509506f, + 188.885864f, 204.78804f, 208.390625f, 166.894714f, 125.398788f, 104.833572f, + 85.8783951f, 79.108696f, 102.2174f, 104.428673f, 110.620255f, 114.619583f, + 90.1195755f, 65.6195679f, 117.506798f, 190.226898f, 220.652161f, 208.782608f, + 188.07814f, 134.978943f, 81.8797684f, 80.091713f, 82.250679f, 83.0217361f, + 107.434776f, 108.627037f, 111.965347f, 114.585587f, 107.870911f, 101.156219f, + 136.939529f, 184.313202f, 204.442963f, 197.328812f, 182.612762f, 140.023087f, + 97.433403f, 90.6997147f, 86.7241898f, 85.3043442f, 121.347824f, 122.879753f, + 127.169159f, 131.949722f, 143.115479f, 154.28125f, 160.567261f, 165.522415f, + 172.241867f, 180.725555f, 182.096466f, 157.387222f, 132.677979f, 113.091034f, + 93.8980942f, 87.0434799f, 105.326111f, 113.617538f, 136.833557f, 159.689514f, + 177.864792f, 196.04007f, 175.677994f, 144.80571f, 139.056396f, 158.430038f, + 171.620255f, 162.137924f, 152.655579f, 136.558411f, 119.952431f, 114.021721f, + 52.717392f, 75.6148148f, 139.727585f, 200.110733f, 212.008835f, 223.906921f, + 180.379074f, 121.735054f, 104.667801f, 129.177307f, 148.970795f, 151.472153f, + 153.973511f, 161.341034f, 169.08287f, 171.847824f, 17.7173347f, 51.2873154f, + 145.283264f, 232.907608f, 237.701111f, 242.494614f, 179.771088f, 98.6338577f, + 73.0020599f, 102.875702f, 129.22554f, 142.654892f, 156.084229f, 177.223541f, + 198.955887f, 206.717468f, 82.5f, 104.453125f, 165.921875f, 222.5f, + 215.5f, 208.5f, 149.078125f, 75.359375f, 56.765625f, 93.296875f, + 125.9375f, 144.3125f, 162.6875f, 142.671875f, 119.703125f, 111.5f, + 147.282593f, 157.618881f, 186.560455f, 212.092407f, 193.29892f, 174.505447f, + 118.385193f, 52.0849228f, 40.5292168f, 83.718071f, 122.649452f, 145.970108f, + 169.290756f, 108.120255f, 40.450428f, 16.2826309f, 173.260864f, 167.418472f, + 151.059799f, 135.186172f, 125.61824f, 116.050301f, 93.9055862f, 68.3308334f, + 61.7112617f, 74.0468826f, 88.410347f, 110.209259f, 132.008179f, 113.296867f, + 91.4693909f, 83.6738663f, 192.391296f, 169.565216f, 105.652206f, 46.5449028f, + 49.9117393f, 53.2785759f, 70.522438f, 91.5509338f, 89.4965897f, 64.3593826f, + 48.7092667f, 67.8451385f, 86.9810104f, 125.484367f, 165.477524f, 179.760803f, + 157.717468f, 137.374374f, 80.4137268f, 28.2526798f, 38.4863739f, 48.7200661f, + 69.4456329f, 93.0326309f, 99.1528625f, 87.8063354f, 80.5379562f, 88.2227554f, + 95.9075623f, 133.681381f, 173.769745f, 188.087021f, 79.0217438f, 77.0890045f, + 71.6773148f, 67.5190201f, 79.6548843f, 91.7907562f, 86.6195679f, 76.7282639f, + 93.9762268f, 138.363449f, 170.891296f, 159.934784f, 148.978256f, 138.613449f, + 128.294174f, 124.608704f, 37.9347572f, 48.0944099f, 76.5414276f, 103.558411f, + 111.985054f, 120.41169f, 103.647415f, 80.012886f, 105.297539f, 179.501358f, + 232.297546f, 206.599197f, 180.900833f, 142.281921f, 102.669151f, 88.5217209f, + 109.673874f, 112.972115f, 122.207176f, 129.917114f, 117.80027f, 105.683434f, + 120.237083f, 142.064499f, 166.112732f, 192.38179f, 206.862793f, 178.120941f, + 149.379089f, 142.159653f, 136.595764f, 134.608673f, 86.2608643f, 105.734375f, + 160.260193f, 208.403534f, 173.574722f, 138.745926f, 131.581512f, 131.96196f, + 152.158279f, 192.170517f, 217.953125f, 191.560455f, 165.167801f, 147.95993f, + 131.458557f, 125.565216f, 131.478256f, 134.17186f, 141.713989f, 148.974213f, + 152.56929f, 156.164398f, 142.586945f, 124.326088f, 124.074059f, 141.830856f, + 158.89064f, 173.394699f, 187.898773f, 184.033279f, 178.754745f, 176.869553f, + 176.695648f, 162.609375f, 123.167801f, 89.5448532f, 131.563858f, 173.582886f, + 153.592392f, 116.690224f, 95.9898148f, 91.4911804f, 99.8281403f, 155.228943f, + 210.629761f, 220.106644f, 226.050934f, 228.173904f, 187.021744f, 173.529892f, + 135.752701f, 104.025116f, 150.942245f, 197.85936f, 176.82608f, 137.260864f, + 118.010857f, 119.076065f, 127.280548f, 161.662369f, 196.044159f, 195.232346f, + 191.713348f, 190.456543f, 185.717392f, 178.611404f, 158.714676f, 143.141968f, + 183.781921f, 224.421875f, 204.135864f, 167.233688f, 156.733688f, 172.635864f, + 183.571335f, 176.295517f, 169.019699f, 150.04213f, 130.164413f, 123.065231f, + 169.739151f, 169.508163f, 168.86142f, 170.544159f, 202.510193f, 234.476227f, + 216.508835f, 184.923233f, 178.893356f, 198.419159f, 208.869568f, 186.043488f, + 163.217377f, 129.290085f, 94.5088348f, 82.0869598f, 135.82608f, 143.067932f, + 163.345108f, 183.628403f, 203.991165f, 224.353943f, 210.625671f, 187.599869f, + 180.809113f, 190.253403f, 196.288055f, 189.820648f, 183.353271f, 138.406921f, + 90.500679f, 73.391304f, 103.543503f, 116.648117f, 153.341049f, 188.134537f, + 198.235077f, 208.335632f, 198.147461f, 182.42601f, 174.931442f, 175.663757f, + 178.719467f, 190.294174f, 201.868881f, 147.798889f, 88.6793289f, 67.5652008f, + 80.5f, 90.34375f, 117.90625f, 144.03125f, 151.46875f, 158.90625f, + 148.296875f, 132.765625f, 124.890625f, 124.671875f, 132.890625f, 172.046875f, + 211.203125f, 158.75f, 99.25f, 78.0f, 57.4565277f, 64.0394135f, + 82.4714737f, 99.9280014f, 104.702461f, 109.476913f, 98.4463425f, 83.1053085f, + 74.8498764f, 73.6800385f, 87.0618286f, 153.799591f, 220.537369f, 169.70108f, + 109.820648f, 88.4347839f, 71.3695526f, 73.9408875f, 81.140625f, 87.907608f, + 89.0489197f, 90.1902237f, 89.201767f, 87.6324615f, 96.7010651f, 116.407578f, + 140.530533f, 180.847137f, 221.163727f, 176.044159f, 124.35257f, 105.891296f, + 91.8043365f, 90.2316437f, 85.828125f, 81.5489197f, 78.8858795f, 76.2228394f, + 87.122963f, 101.722137f, 131.239105f, 175.673874f, 211.522385f, 215.887878f, + 220.253387f, 181.574036f, 139.583542f, 124.586945f, 78.5217819f, 77.3634872f, + 74.1202698f, 70.7439041f, 65.6365585f, 60.529232f, 88.377037f, 125.212608f, + 157.444962f, 185.074081f, 204.206589f, 192.184845f, 180.163116f, 158.841751f, + 136.805038f, 128.934784f, 37.6521797f, 40.6379128f, 48.9979668f, 56.3009529f, + 49.8620911f, 43.423233f, 92.3580093f, 156.394699f, 176.833557f, 153.674591f, + 132.820663f, 120.418488f, 108.016312f, 112.985741f, 119.291443f, 121.54348f, + 9.39130116f, 15.3152142f, 31.9021721f, 47.3097801f, 47.3858566f, 47.4619331f, + 102.986404f, 173.633133f, 182.540085f, 129.707199f, 86.3022995f, 77.4666977f, + 68.6310959f, 83.4375f, 100.062508f, 106.000015f, 18.9565163f, 24.2010822f, + 38.8858643f, 54.6739082f, 84.8043289f, 114.934746f, 133.557053f, 149.040787f, + 147.200439f, 128.035995f, 114.386528f, 120.958534f, 127.53054f, 102.812492f, + 75.6875153f, 66.0000229f, 171.195648f, 149.504089f, 88.767662f, 38.4014969f, + 122.848503f, 207.295532f, 171.400131f, 102.684105f, 66.390625f, 62.5196991f, + 74.4945602f, 144.570648f, 214.646759f, 194.48233f, 167.376373f, 157.695663f, + 115.108711f, 109.585609f, 94.1209259f, 82.6813736f, 123.568611f, 164.455856f, + 132.527863f, 80.7411728f, 53.5781288f, 51.0387268f, 56.8858719f, 93.4837036f, + 130.081543f, 124.129097f, 114.903557f, 111.608704f, 59.0217514f, 69.6671295f, + 99.4741821f, 126.961266f, 124.288719f, 121.61618f, 93.6555786f, 58.7982368f, + 40.765625f, 39.5577469f, 39.2771797f, 42.3967514f, 45.5163231f, 53.7758293f, + 62.4307175f, 65.5217514f, 106.630394f, 107.764915f, 110.941574f, 115.062515f, + 131.459244f, 147.855957f, 111.389931f, 60.506794f, 41.546875f, 54.5101814f, + 60.2995796f, 39.7846413f, 19.2697029f, 33.9660339f, 51.3709221f, 57.5869522f, + 188.804337f, 171.868195f, 124.447014f, 84.4375076f, 140.779892f, 197.122284f, + 147.993195f, 70.0991821f, 46.859375f, 78.2737732f, 94.1990509f, 53.3308411f, + 12.4626369f, 31.0040779f, 54.1154861f, 62.3695641f, 191.34787f, 173.827454f, + 124.770378f, 83.6005249f, 144.964676f, 206.328812f, 154.20314f, 71.1256866f, + 45.8743248f, 78.4490509f, 98.3716125f, 71.9028397f, 45.4340706f, 52.8960381f, + 62.9680634f, 66.5652161f, 96.5652313f, 99.8328934f, 108.982338f, 119.171196f, + 142.872284f, 166.573364f, 123.265625f, 61.6827431f, 37.1922531f, 49.7941628f, + 66.2112808f, 96.6175308f, 127.023773f, 105.165077f, 79.2860031f, 70.0434799f, + 24.8042984f, 43.958519f, 97.590332f, 146.956528f, 140.869568f, 134.782608f, + 100.343735f, 58.1725311f, 35.6555367f, 32.7927551f, 46.5502319f, 121.248619f, + 195.947006f, 147.949051f, 90.5129166f, 70.0000153f, 83.5f, 90.765625f, + 111.109375f, 130.625f, 139.375f, 148.125f, 122.84375f, 88.28125f, + 74.609375f, 81.828125f, 97.71875f, 145.40625f, 193.09375f, 136.984375f, + 72.890625f, 50.0f, 142.195633f, 137.572678f, 124.628395f, 114.293488f, + 137.880432f, 161.467392f, 145.343735f, 118.389938f, 113.563171f, 130.863434f, + 148.887207f, 169.563858f, 190.240494f, 126.019707f, 55.2683449f, 30.0000057f, + 168.369553f, 163.695633f, 150.608688f, 140.103912f, 163.167786f, 186.231644f, + 167.208557f, 136.707184f, 130.943604f, 149.917801f, 169.185455f, 189.529205f, + 209.872955f, 137.944962f, 58.9191513f, 30.6956501f, 188.804337f, 186.168457f, + 178.788025f, 173.351196f, 193.181366f, 213.011536f, 188.961258f, 152.943604f, + 144.516968f, 163.681381f, 184.035995f, 208.754745f, 233.47348f, 153.909637f, + 66.3240433f, 35.0434761f, 187.000015f, 181.021774f, 164.282669f, 149.991913f, + 167.529968f, 185.068008f, 175.262955f, 158.000671f, 156.902161f, 171.967392f, + 186.384521f, 198.425308f, 210.466095f, 140.603302f, 64.4402237f, 37.2391319f, + 167.0f, 153.277176f, 114.853264f, 80.3179398f, 96.3342514f, 112.350548f, + 132.559113f, 153.910995f, 168.315216f, 175.771744f, 178.504074f, 163.914398f, + 149.324738f, 103.347832f, 54.9565277f, 37.673912f, 152.76088f, 136.150818f, + 89.6426468f, 47.0128822f, 54.8022919f, 62.5917053f, 97.0292206f, 138.73439f, + 162.245255f, 167.561829f, 165.627045f, 137.103943f, 108.580841f, 85.1005325f, + 62.0081367f, 53.760849f, 155.804352f, 150.879059f, 137.088287f, 122.814499f, + 102.26152f, 81.7085495f, 83.0210724f, 90.2969055f, 103.726265f, 123.309135f, + 137.760208f, 133.394714f, 129.029205f, 123.877693f, 118.665733f, 116.804314f, +}; + +// --- section 2: load_image_and_preprocess at crf=0 (decode.py:46-79) --- +// decode -> preprocess(crf=0) -> f32 0..255 -> resize+crop -> /127.5 - 1. +// `preprocess(crf=0) is image` was ASSERTED at generation time, and +// `preprocess(crf=18)` was asserted to reach the codec and raise. +inline constexpr int64_t kLtx2ImgPreSrcH = 12; +inline constexpr int64_t kLtx2ImgPreSrcW = 20; +inline constexpr int64_t kLtx2ImgPreDstH = 16; +inline constexpr int64_t kLtx2ImgPreDstW = 16; +// max|resize-then-normalize - normalize-then-resize|, measured upstream. It is +// BELOW the golden band, which is the recorded NEGATIVE RESULT: the two orders +// are algebraically identical (a convex combination commutes with an affine +// map), so their f32 gap is pure rounding and cannot be amplified. No golden +// here can see the swap; the order is mirrored because it is upstream's, and +// that is written down rather than assumed to be covered. +inline constexpr double kLtx2ImgPreOrderGap = 1.9371509552001953e-07; + +inline constexpr float kLtx2ImgPreGolden[] = { + -0.444880188f, -0.359767616f, -0.324618816f, 0.000726103783f, 0.00595498085f, 0.0880173445f, + 0.499055862f, 0.853449583f, 0.533333302f, 0.0262889862f, -0.194480777f, 0.179520249f, + -0.27726835f, -0.92374748f, -0.732026339f, -0.0727675557f, -0.360275924f, -0.459803998f, + -0.319172025f, -0.378721833f, -0.495134354f, -0.419880211f, -0.000127077103f, -0.00521057844f, + 0.097058773f, 0.142211318f, -0.0386165977f, -0.138017297f, 0.0238376856f, 0.134895086f, + -0.297929823f, -0.1120736f, -0.357661545f, -0.629357338f, -0.436329007f, -0.254048645f, + -0.480864167f, -0.656154752f, -0.490867853f, -0.478522122f, -0.108823538f, 0.288725495f, + 0.386764765f, -0.0412849188f, 0.0200794935f, 0.187073708f, -0.162962615f, -0.216757417f, + -0.344880104f, -0.723747313f, -0.575327039f, 0.32881248f, 0.0119827986f, -0.552832246f, + -0.743445992f, -0.324419022f, -0.0352941155f, 0.282625318f, 0.790250659f, 0.44466269f, + -0.151560545f, -0.581282139f, -0.210185647f, -0.264306366f, 0.213289738f, -0.19477123f, + -0.205065489f, 0.302559733f, -0.180174172f, -0.617538154f, -0.346605003f, 0.181354403f, + -0.152941167f, -0.523583889f, -0.10746187f, 0.48779881f, 0.662817836f, 0.638652802f, + 0.761383295f, 0.26140213f, -0.0770334005f, -0.0485113263f, 0.221568465f, 0.365359426f, + -0.203213394f, -0.722821355f, -0.69431746f, -0.0356391072f, -0.233823538f, -0.560639024f, + -0.297748685f, 0.344661593f, 0.230646968f, -0.119989336f, 0.117483497f, -0.263725042f, + -0.438144505f, -0.274818599f, 0.19689548f, 0.207788706f, 0.0449346304f, -0.270697057f, + -0.703413248f, -0.0556282401f, 0.0823529959f, -0.10230577f, -0.447258592f, 0.156589627f, + -0.125725329f, -0.604248822f, -0.159804344f, -0.140577376f, -0.537291229f, -0.514560819f, + -0.104684174f, -0.0494916439f, 0.411637545f, 0.487254858f, -0.305664539f, 0.0952068567f, + 0.543627501f, 0.48932457f, -0.656645f, -0.0636172891f, -0.192156255f, -0.439488649f, + 0.211927295f, 0.555010557f, 0.178830743f, 0.658641934f, 0.522113442f, 0.385584712f, + 0.184295654f, -0.0532679558f, -0.311328948f, -0.607625246f, -0.218137264f, 0.0396513939f, + -0.661873698f, -0.406100512f, -0.434422433f, -0.380120635f, 0.380227923f, -0.179411173f, + -0.138725579f, 0.280827761f, 0.490413904f, 0.597893953f, 0.114633322f, -0.193736434f, + 0.151597738f, 0.0586420298f, -0.0509803891f, -0.152578056f, -0.220842421f, -0.389651179f, + -0.414433599f, -0.330283344f, -0.190849781f, -0.334222794f, -0.131535947f, 0.0184639692f, + 0.203485847f, 0.171532393f, -0.154211998f, -0.240631878f, 0.249854803f, 0.440123439f, + 0.115196109f, -0.222240329f, -0.070461154f, -0.295370102f, -0.375163436f, -0.2901963f, + -0.0156865716f, 0.072748661f, 0.197694421f, 0.0915213823f, -0.010076046f, -0.549654901f, + -0.526398003f, -0.287745178f, -0.0463508368f, 0.17805016f, 0.0671569109f, -0.157262206f, + -0.282171369f, -0.118191957f, -0.207334518f, -0.152433753f, 0.694334626f, 0.707461834f, + -0.520279586f, -0.286583602f, 0.760838866f, 0.225617409f, -0.261691988f, -0.403758228f, + -0.0229302645f, -0.508769035f, -0.11813724f, 0.225526452f, -0.404829383f, 0.443245173f, + 0.769135833f, 0.512927651f, -0.39193809f, 0.251578808f, -0.131227314f, 0.252704978f, + 0.837309599f, 0.132153273f, -0.123892486f, -0.191884518f, -0.225817025f, -0.0820261836f, + -0.139215708f, -0.124891043f, 0.238943458f, 0.636764169f, 0.509404898f, 0.126725078f, + -0.0969496965f, 0.432406783f, -0.0303558111f, 0.0705699921f, 0.227015257f, 0.208133578f, + 0.00339508057f, -0.1610021f, -0.129466295f, 0.338943243f, -0.130392134f, -0.419880092f, + 0.601906419f, 0.648692608f, 0.160766959f, -0.27550894f, 0.230663896f, 0.208170056f, + -0.370951355f, -0.835294127f, -0.738126755f, 0.55744338f, 0.121859312f, -0.34074086f, + 0.237908483f, 0.499346375f, -0.125490189f, -0.497312963f, 0.542629004f, 0.568627477f, + 0.00217962265f, -0.448802531f, 0.300652862f, -0.422366619f, -0.0530138016f, 0.0644880533f, + -0.0958607197f, 0.560638905f, 0.273638487f, -0.201307178f, -0.385766208f, -0.287000775f, + -0.368627429f, -0.406826437f, -0.174437165f, 0.0409584045f, -0.179810643f, -0.459114313f, + -0.203486085f, -0.576615453f, 0.0853304863f, -0.318318725f, -0.508170187f, 0.337145805f, + 0.410076261f, 0.11214602f, -0.306699336f, -0.378594756f, -0.461764693f, -0.469644189f, + -0.309876502f, -0.213126421f, -0.0862202048f, -0.00395774841f, -0.0896512866f, 0.0523781776f, + -0.125508249f, -0.546859026f, -0.801797628f, 0.0892698765f, 0.440141678f, 0.293355227f, + -0.318409622f, -0.0351852179f, -0.300000012f, -0.608478606f, -0.459604204f, -0.233551443f, + -0.0799565315f, 0.0497455597f, 0.234204531f, 0.192955732f, -0.633097291f, -0.500490069f, + -0.691394508f, -0.0929921269f, 0.254793048f, 0.180991411f, -0.397984803f, 0.54537034f, + 0.0333333015f, -0.697494507f, -0.427341998f, -0.00849723816f, -0.204937696f, -0.383606553f, + 0.463888049f, -0.254465461f, -0.740177929f, -0.233714879f, 0.433442473f, -0.0487653017f, + -0.339324594f, -0.424564302f, -0.283932507f, 0.0450435877f, -0.143137276f, -0.220152497f, + 0.450871468f, 0.0215691328f, -0.455918133f, -0.789814889f, -0.713562131f, -0.570806265f, + -0.707098782f, -0.282389581f, 0.561002135f, 0.341684818f, -0.27830416f, -0.507516444f, + 0.141031265f, -0.256935358f, -0.162745118f, 0.133006454f, 0.355228782f, -0.115032136f, + -0.0420483947f, 0.218554974f, 0.0863835812f, 0.124836564f, -0.395279586f, 0.0624363422f, + 0.537309527f, 0.59032321f, 0.12578094f, -0.0838236213f, 0.430246949f, -0.163798094f, + -0.187745094f, 0.0259803534f, 0.163235307f, 0.00947737694f, 0.330282688f, 0.681681514f, + 0.351253033f, 0.157189727f, 0.0665214062f, 0.677995801f, 0.452396393f, 0.615069032f, + 0.600308776f, 0.54793036f, 0.538834572f, 0.288289785f, -0.155882359f, -0.364506125f, + 0.109713197f, 0.322657704f, 0.484422565f, 0.421187878f, -0.164868712f, -0.433187842f, + 0.0246913433f, 0.364270329f, -0.179629862f, 0.575635195f, 0.366866469f, 0.209368229f, + 0.733932376f, 0.757897615f, 0.344117641f, 0.00444805622f, 0.256445169f, 0.10697186f, + 0.420151949f, 0.683388352f, 0.0668307543f, 0.191212654f, -0.00624549389f, 0.0234932899f, + -0.378921747f, 0.202777743f, 0.35274148f, 0.290740728f, 0.161419749f, 0.539778352f, + 0.633333325f, 0.633660197f, 0.72516346f, -0.139214694f, 0.164250851f, 0.761946797f, + 0.249238014f, 0.245715261f, -0.113108218f, -0.204284668f, -0.239215791f, 0.24444437f, + 0.280573845f, 0.162472725f, 0.0806462765f, 0.40453887f, 0.478921533f, 0.530174255f, + 0.782897592f, -0.236763537f, -0.0245290399f, 0.511456609f, -0.0992913246f, -0.14039582f, + -0.132752359f, -0.206082106f, 0.0156316757f, 0.616212606f, 0.187999368f, -0.0838780999f, + 0.538035631f, 0.490831614f, -0.000980377197f, -0.29713136f, 0.25769794f, -0.112199962f, + -0.0541942716f, -0.0239825845f, -0.781426251f, -0.607153475f, 0.67421937f, 0.32997489f, + -0.416067719f, 0.232443571f, 0.502923012f, 0.547494531f, 0.459931016f, 0.582661629f, + -0.100980401f, -0.81978941f, -0.805265069f, 0.349236131f, 0.184478283f, -0.522567153f, + -0.526198268f, 0.156026959f, 0.427450895f, 0.68275249f, 0.23284328f, -0.0874181986f, + 0.302051425f, 0.578649282f, 0.222058892f, 0.00637257099f, -0.0490196347f, -0.045878768f, + -0.0814633369f, 0.609421968f, 0.328559399f, -0.252360463f, -0.0577344298f, 0.355210304f, + 0.081590414f, 0.486946106f, 0.634640813f, -0.289106548f, 0.106572032f, 0.419662356f, + -0.229357243f, -0.501688421f, -0.150490224f, 0.342084169f, 0.504756689f, 0.635566354f, + 0.209550738f, -0.284059763f, -0.076361835f, 0.269044757f, -0.113580227f, -0.102832496f, + 0.58344245f, -0.369353473f, 0.0341320038f, 0.228758216f, -0.75686276f, -0.75686276f, + -0.388235271f, 0.127668738f, 0.580827951f, 0.495860577f, -0.0930999517f, -0.690196276f, + -0.53333348f, 0.0534489155f, 0.159767509f, 0.777051568f, 0.448366284f, -0.423093498f, + 0.0111835003f, 0.583442211f, 0.56296289f, 0.650108933f, 0.835294127f, 0.852432847f, + 0.42251277f, 0.0832247734f, -0.0322440267f, 0.15773356f, 0.802613735f, 0.86172843f, + -0.382443786f, 0.0610927343f, 0.662690878f, -0.0308459401f, 0.110130668f, 0.46361649f, + 0.629593492f, 0.244698644f, 0.448039174f, 0.639106631f, 0.216448903f, -0.243027806f, + -0.311746776f, -0.192574441f, -0.197658002f, 0.0545386076f, -0.381898999f, -0.330737412f, + 0.411710501f, -0.177251041f, -0.00751644373f, 0.272440076f, 0.245007277f, -0.229212046f, + 0.0642156601f, 0.469989061f, 0.341448903f, 0.0401419401f, -0.0522695184f, -0.136310399f, + -0.535729408f, -0.494480848f, 0.122748733f, -0.289016008f, -0.211274326f, -0.6883986f, + -0.264669657f, 0.0715686083f, -0.301833689f, -0.453612924f, -0.135784328f, 0.363779902f, + 0.771187425f, 0.821023941f, 0.647912502f, 0.30379498f, -0.152995169f, -0.624563813f, + -0.108079076f, -0.416140199f, -0.153431416f, 0.0622549057f, -0.0374363661f, 0.059150219f, + 0.662763238f, 0.506626725f, 0.17892158f, 0.137745142f, 0.863235235f, 0.717102528f, + 0.577215075f, 0.279939055f, -0.47750473f, -0.758006334f, -0.446296275f, -0.41390723f, + 0.0816992521f, 0.151416183f, 0.0795025826f, 0.108006477f, 0.394153953f, 0.709331751f, + 0.515686274f, 0.197657943f, 0.13011992f, 0.648855567f, 0.541285992f, 0.181917191f, + 0.173202634f, 0.199419022f, -0.330501139f, -0.0123820901f, 0.276634097f, 0.278812647f, + 0.00352215767f, -0.192647099f, -0.0677197576f, 0.596042156f, 0.536274552f, 0.201307058f, + 0.0183006525f, 0.245261312f, 0.00615537167f, -0.182190239f, 0.517156124f, 0.350617528f, + 0.144262791f, 0.635693669f, 0.434695005f, 0.531281829f, -0.181898892f, -0.672821403f, + -0.273602128f, 0.42937541f, 0.278431416f, 0.0311909914f, 0.437872171f, -0.258604825f, + -0.714215398f, -0.658443332f, 0.319770336f, -0.322275996f, 0.0364196301f, 0.64941895f, + 0.786655903f, 0.556445241f, -0.060221374f, -0.241775692f, 0.533587575f, 0.818264365f, + 0.231372595f, -0.44048661f, -0.417247653f, 0.448582888f, 0.169990182f, -0.439161599f, + -0.075327158f, 0.00637245178f, -0.0913035274f, 0.214796424f, 0.806372881f, -0.128267765f, + -0.186274588f, 0.229302764f, 0.859567881f, 0.693264365f, 0.186764717f, -0.170334041f, + 0.123057365f, 0.606208563f, 0.48102808f, 0.102560043f, 0.0263072252f, 0.472003937f, + 0.170134425f, 0.224491477f, 0.516830325f, -0.59210217f, -0.333170116f, 0.186165452f, + 0.26839149f, 0.102087855f, -0.0323529243f, 0.00526511669f, 0.368373275f, 0.158660412f, + 0.0726941824f, 0.0777778625f, 0.103921413f, 0.684258699f, 0.527505398f, 0.515414f, + 0.0722224712f, -0.651089132f, -0.348765552f, -0.127450883f, -0.672567189f, -0.55564636f, + -0.28039217f, -0.0355846286f, -0.0123456717f, -0.578212857f, -0.713471472f, -0.464670181f, + 0.0916116238f, 0.498093367f, -0.601143777f, -0.457462072f, 0.0663400888f, -0.500108778f, + 0.148620009f, 0.817647099f, 0.742138743f, 0.270824194f, 0.201960802f, 0.0559185743f, + -0.652868629f, -0.28540349f, -0.365104914f, -0.440487266f, 0.246513486f, -0.276306629f, + -0.0828250051f, 0.0448800325f, 0.427015424f, -0.389251828f, 0.0781043768f, 0.512200475f, + 0.0886710882f, 0.0276688337f, 0.245588183f, 0.23928833f, -0.511619568f, 0.169171214f, + 0.365159988f, 0.244644284f, 0.114651561f, -0.481535316f, 0.140758872f, 0.19656837f, + 0.685839176f, -0.28729099f, -0.0620916486f, 0.188180804f, -0.26710242f, -0.323747277f, + -0.107352912f, 0.00448429585f, -0.373874366f, 0.290739894f, 0.535475612f, 0.448729396f, + 0.194553614f, -0.066339612f, -0.193028212f, -0.215105653f, 0.755555868f, -0.203049839f, + -0.142338455f, 0.111111164f, 0.0291939974f, -0.528540254f, -0.65882349f, -0.554974616f, + -0.392302155f, 0.0875811577f, 0.147131681f, 0.107770085f, 0.485402703f, 0.701815367f, +}; + +// --- section 3: VideoEncoder over the preprocessed image (video_vae.py:264-336) --- +// This is `video_encoder(image)` at ltx-pipelines/utils/helpers.py:294. +inline constexpr int64_t kLtx2ImgEncOutC = 4; +inline constexpr int64_t kLtx2ImgEncOutT = 1; +inline constexpr int64_t kLtx2ImgEncOutH = 2; +inline constexpr int64_t kLtx2ImgEncOutW = 2; +inline constexpr int64_t kLtx2ImgEncTemporalFactor = 2; +inline constexpr int64_t kLtx2ImgEncSpatialFactor = 8; + +inline constexpr const char* kLtx2ImgEncParamNames[] = { + "ltx2.imgenc.per_channel_statistics.std-of-means", + "ltx2.imgenc.per_channel_statistics.mean-of-means", + "ltx2.imgenc.conv_in.conv.weight", + "ltx2.imgenc.conv_in.conv.bias", + "ltx2.imgenc.down_blocks.0.res_blocks.0.conv1.conv.weight", + "ltx2.imgenc.down_blocks.0.res_blocks.0.conv1.conv.bias", + "ltx2.imgenc.down_blocks.0.res_blocks.0.conv2.conv.weight", + "ltx2.imgenc.down_blocks.0.res_blocks.0.conv2.conv.bias", + "ltx2.imgenc.down_blocks.1.conv.conv.weight", + "ltx2.imgenc.down_blocks.1.conv.conv.bias", + "ltx2.imgenc.down_blocks.2.conv.conv.weight", + "ltx2.imgenc.down_blocks.2.conv.conv.bias", + "ltx2.imgenc.conv_out.conv.weight", + "ltx2.imgenc.conv_out.conv.bias", +}; +inline constexpr int64_t kLtx2ImgEncParamCounts[] = { + 4, 4, 1296, 4, 432, 4, 432, 4, 216, 2, + 216, 1, 1080, 5, +}; + +inline constexpr float kLtx2ImgEncGolden[] = { + -0.241159096f, 1.22298455f, -0.218436688f, 1.04175878f, -0.0579345077f, -0.170286119f, + 0.36133939f, -0.979117632f, 0.290022969f, 0.0848331228f, -0.29933691f, -0.477867126f, + -0.224734351f, -0.0779257789f, -0.457862705f, -0.312173635f, +}; + +// --- section 4: VideoConditionByLatentIndex (latent_cond.py:22-43) --- +// clean_latent[start:stop] = tokens; denoise_mask[start:stop] = 1 - strength. +// The NOISY tensor is deliberately untouched — that was asserted here. +inline constexpr int64_t kLtx2ImgCondTokens = 12; +inline constexpr int64_t kLtx2ImgCondWidth = 4; +inline constexpr int64_t kLtx2ImgCondTargetFrames = 3; +inline constexpr int64_t kLtx2ImgCondPatch = 1; +inline constexpr int64_t kLtx2ImgCondLatentIdx = 0; +inline constexpr double kLtx2ImgCondStrength = 0.69999999999999996; +inline constexpr double kLtx2ImgCondFps = 8.0; + +inline constexpr float kLtx2ImgCondClean[] = { + -0.241159096f, -0.0579345077f, 0.290022969f, -0.224734351f, 1.22298455f, -0.170286119f, + 0.0848331228f, -0.0779257789f, -0.218436688f, 0.36133939f, -0.29933691f, -0.457862705f, + 1.04175878f, -0.979117632f, -0.477867126f, -0.312173635f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, +}; + +inline constexpr float kLtx2ImgCondMask[] = { + 0.300000012f, 0.300000012f, 0.300000012f, 0.300000012f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, +}; + +// --- section 5: GaussianNoiser over the conditioned state (components/noisers.py:30-37) --- +// latent = lerp(latent, noise, noise_scale); latent = lerp(clean, latent, mask). +// The DOUBLE lerp, at a NON-UNIT scale — the only regime in which ltx_core and +// diffusers disagree. `kLtx2ImgNoiseDivergence` is how far apart they are here, +// measured; the suite asserts it is above the band, which is what makes this +// section able to catch a silent switch to the diffusers form. +inline constexpr double kLtx2ImgNoiseScale = 0.625; +inline constexpr double kLtx2ImgNoiseDivergence = 0.87261331081390381; + +inline constexpr float kLtx2ImgNoisedGolden[] = { + -0.338830173f, -0.151256427f, 0.267070055f, -0.00758534716f, 0.86769706f, 0.048100654f, + 0.112138331f, -0.0327227563f, -0.0503813364f, 0.399275422f, -0.103254408f, -0.272276074f, + 0.901173115f, -0.605140507f, -0.402147412f, -0.251273006f, -0.0780501515f, 0.290990531f, + 0.508082986f, -0.0305619389f, 0.587033451f, 0.297063053f, -0.0872664228f, -0.419992477f, + 0.184497401f, -0.302416623f, -0.286860138f, -0.430119991f, 0.191023424f, -0.0793055296f, + -0.0607184321f, 0.51918453f, 0.618465066f, -0.31636554f, 0.57936877f, 0.231961042f, + -0.445122898f, 0.183641195f, -0.206588715f, 0.441616356f, 0.00255125063f, 0.142263561f, + 0.613677382f, 0.518635511f, 0.348936856f, -0.106226586f, 0.488095999f, 0.14958632f, +}; + +} // namespace vllm_test diff --git a/tests/vllm/multimodal/ltx2_video_fixture.h b/tests/vllm/multimodal/ltx2_video_fixture.h index e2191ed9b..77ff8fb32 100644 --- a/tests/vllm/multimodal/ltx2_video_fixture.h +++ b/tests/vllm/multimodal/ltx2_video_fixture.h @@ -621,6 +621,55 @@ inline void WriteReducedVideoVae(const vllm::Ltx2ConvVideoDecoderConfig& cfg, put("conv_out.conv.weight", {patch_out, channels, 3, 3, 3}, 0.1); put("conv_out.conv.bias", {patch_out}, 0.05); + // ── the ENCODER half (row LTX25-IMAGE-COND, issue #644) ────────────────── + // + // A monolithic LTX-2 VAE file carries both halves and the two key filters + // separate them (video_vae/model_configurator.py:255-276). Before this row the + // fixture wrote the decoder alone, so nothing in this suite could have noticed + // that no encoder key filter existed — which is a fair description of how the + // gap survived. + // + // These are written under the BARE `encoder.` prefix rather than + // `vae.encoder.`, matching the shipped Comfy-split spelling and exercising the + // third of the four encoder rules; the decoder half above is likewise bare. + auto put_enc = [&](const std::string& name, const std::vector& shape, double scale, + double offset = 0.0) { + int64_t numel = 1; + for (const int64_t d : shape) numel *= d; + entries.push_back( + {"encoder." + name, "BF16", shape, Param("ltx2.vvaeenc." + name, numel, scale, offset)}); + }; + // The FORWARD mirror of the decoder's block list, reduced to plain strided + // convolutions so the encoder runs at the latent width throughout. It must + // multiply out to the SAME (8, 32, 32) the pipeline derives every latent shape + // from, or an encoded image would not fit the grid it is placed into. + const std::vector encoder_blocks = { + "res_x", "compress_space", "compress_time", "compress_all", "compress_all", + }; + const int64_t latent = cfg.in_channels; + const int64_t patched_in = 3 * cfg.patch_size * cfg.patch_size; + put_enc("conv_in.conv.weight", {latent, patched_in, 3, 3, 3}, 0.1); + put_enc("conv_in.conv.bias", {latent}, 0.05); + for (size_t i = 0; i < encoder_blocks.size(); ++i) { + const std::string bp = "down_blocks." + std::to_string(i); + if (encoder_blocks[i] == "res_x") { + put_enc(bp + ".res_blocks.0.conv1.conv.weight", {latent, latent, 3, 3, 3}, 0.1); + put_enc(bp + ".res_blocks.0.conv1.conv.bias", {latent}, 0.05); + put_enc(bp + ".res_blocks.0.conv2.conv.weight", {latent, latent, 3, 3, 3}, 0.1); + put_enc(bp + ".res_blocks.0.conv2.conv.bias", {latent}, 0.05); + } else { + // The plain strided path reads `.conv.weight`, NOT `.conv.conv.*` + // — that second spelling belongs to the `*_res` family's + // SpaceToDepthDownsample (ltx2_video_vae.cpp). + put_enc(bp + ".conv.weight", {latent, latent, 3, 3, 3}, 0.1); + put_enc(bp + ".conv.bias", {latent}, 0.05); + } + } + // `latent_log_var` defaults to `uniform`, so conv_out emits one extra channel + // and the mean split drops it (video_vae.py:308-315). + put_enc("conv_out.conv.weight", {latent + 1, latent, 3, 3, 3}, 0.1); + put_enc("conv_out.conv.bias", {latent + 1}, 0.05); + nlohmann::json vae; vae["_class_name"] = "CausalVideoAutoencoder"; vae["dims"] = 3; @@ -644,6 +693,13 @@ inline void WriteReducedVideoVae(const vllm::Ltx2ConvVideoDecoderConfig& cfg, blocks.push_back(nlohmann::json::array({b.name, params})); } vae["decoder_blocks"] = blocks; + nlohmann::json enc_blocks = nlohmann::json::array(); + for (const std::string& name : encoder_blocks) { + nlohmann::json params = nlohmann::json::object(); + if (name == "res_x") params["num_layers"] = 1; + enc_blocks.push_back(nlohmann::json::array({name, params})); + } + vae["encoder_blocks"] = enc_blocks; nlohmann::json config; config["vae"] = vae; nlohmann::json metadata; diff --git a/tests/vllm/multimodal/test_ltx2_image_cond.cpp b/tests/vllm/multimodal/test_ltx2_image_cond.cpp new file mode 100644 index 000000000..3dc88ccac --- /dev/null +++ b/tests/vllm/multimodal/test_ltx2_image_cond.cpp @@ -0,0 +1,741 @@ +// LTX-2.5 IMAGE CONDITIONING gate — row LTX25-IMAGE-COND, issue #644. +// +// Spec: .agents/specs/ltx25-image-conditioning.md §5. +// +// WHAT THIS GATES. The CHAIN a conditioning image travels, against EXECUTED +// upstream at reduced dimensions: PPM -> aspect-fill resize -> normalize -> +// `Ltx2ConvVideoEncode` -> `Ltx2ConditionVideoByLatentIndex` -> the noiser. +// Every link but the first two already had a golden in `test_ltx2_vae`; the +// CHAIN did not, and a chain of green links can still be wired in the wrong +// ORDER. It also gates the three things this row built to reach that chain from +// a checkpoint at all: the encoder key rules, the encoder config parser, and the +// CRF resolution that decides whether an image request is served or refused. +// +// WHAT IT CANNOT SHOW. Nothing here is a render-quality result. And one negative +// result is recorded rather than papered over: NO golden in this file can see a +// swap of the resize/normalize ORDER. `resize` is a convex combination and +// `normalize` is affine, so the two orders are equal in exact arithmetic and +// differ only by f32 rounding — measured at 1.94e-07 upstream +// (`kLtx2ImgPreOrderGap`), which is below the golden band AND below this port's +// own distance from torch. The order is mirrored because it is upstream's, and +// that fact is written down here instead of being assumed to be covered. +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "ltx2_image_cond_goldens.inc" +#include "vllm/model_executor/models/ltx2_conditioning.h" +#include "vllm/model_executor/models/ltx2_image_preprocess.h" +#include "vllm/model_executor/models/ltx2_pipeline.h" +#include "vllm/model_executor/models/ltx2_video_vae_encoder.h" + +namespace { + +// --------------------------------------------------------------------------- +// TOLERANCES, DERIVED FROM MEASUREMENT rather than picked — a band that can +// never bind reports nothing, which is the criticism test_ltx2_vae.cpp:172-180 +// already makes of its own history. Both were measured by setting them to 1e-12 +// and reading the reported `worst` on this box (CPU Release, gcc, the tree's own +// `-ffp-contract=off`): +// +// section 1, resize, 0..255 PIXEL space 6.10352e-05 (case 2, the identity, is 0) +// section 2, preprocess, [-1, 1] 4.76837e-07 +// section 3, encoded latent 2.68221e-07 +// section 4, conditioned clean 2.68221e-07 +// section 5, noised latent 1.78814e-07 +// +// `kLtx2ImgGoldenTol` covers everything in latent/normalized space at ~4x the +// worst of those. It is also the band scripts/gen-ltx2-image-cond-goldens.py +// PARSES out of this file, so the number has exactly one definition and the +// generator's own assertions are stated against the band the suite applies. +// +// `kLtx2ImgPixelTol` is section 1's, and it is two orders wider for a MEASURED +// reason worth stating because it looks like slack and is not. Torch's bilinear +// does not round the way any portable f32 expression does. The index map and the +// lambdas were probed directly with basis images and match this port BIT FOR +// BIT; the residual is in the ACCUMULATION, and it appears even on an output +// element whose width weights are exactly (1, 0) — a pure two-term +// `a*h0 + c*h1`, where dimension order cannot be the explanation. Plain-f32, +// f64-accumulate and premultiplied-weight orderings were all tried against torch +// and all three land 1 ulp away on the same elements, which is the signature of +// FMA contraction inside torch's kernel. This tree compiles with +// `-ffp-contract=off`, so it cannot reproduce that even in principle. 6.1e-05 is +// ~1 ulp at 255; the band is ~3x it, for a different libm and a different +// -march. A structural porting error moves this by orders of magnitude, not by +// ulps — a `round` instead of a `ceil` in the resize moves it to ~200. +// --------------------------------------------------------------------------- +constexpr double kLtx2ImgGoldenTol = 2e-6; +constexpr double kLtx2ImgPixelTol = 2e-4; + +// The exact upstream tree the goldens came from. Regenerating against a +// DIFFERENT checkout fails here instead of silently replacing the oracle. +constexpr const char* kLtx2ImgCondUpstreamRevisionPin = + "fd4ded7f2d88d3da713abcdd4ad41ecc4a9314ca"; + +// --------------------------------------------------------------------------- +// The shared deterministic stream — the exact mirror of the generator's +// `ltx_rand` / `ltx_bytes` / `param_values`, and byte-for-byte what +// tests/vllm/models/test_ltx2_vae.cpp uses. It is duplicated here rather than +// shared because that file is a .cpp, not a header; the duplication is made SAFE +// by `CheckManifest` below, which asserts this file's parameter set is exactly +// the state_dict the generator filled — so a copy that drifted is a failure +// rather than a silently different model. +// --------------------------------------------------------------------------- + +uint64_t Fnv1a64(const std::string& name) { + uint64_t h = 0xCBF29CE484222325ULL; + for (unsigned char byte : name) { + h ^= static_cast(byte); + h *= 0x100000001B3ULL; + } + return h; +} + +uint64_t Splitmix64(uint64_t x) { + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + return z ^ (z >> 31); +} + +std::vector Ltx2Rand(const std::string& name, int64_t count) { + const uint64_t seed = Fnv1a64(name); + std::vector out(static_cast(count)); + for (int64_t i = 0; i < count; ++i) { + const uint64_t u = Splitmix64(seed + static_cast(i)); + out[static_cast(i)] = (static_cast(u >> 11) * 0x1p-53) * 2.0 - 1.0; + } + return out; +} + +// The generator's `ltx_bytes`: uint8 codes, because a conditioning image IS +// uint8 out of the decoder and quantizing a float stream afterwards would gate a +// different input than a real PPM carries. +std::vector Ltx2Bytes(const std::string& name, int64_t count) { + const uint64_t seed = Fnv1a64(name); + std::vector out(static_cast(count)); + for (int64_t i = 0; i < count; ++i) { + out[static_cast(i)] = + static_cast(Splitmix64(seed + static_cast(i)) % 256U); + } + return out; +} + +std::vector Ltx2Input(const std::string& name, int64_t count, double scale) { + const std::vector raw = Ltx2Rand(name, count); + std::vector out(static_cast(count)); + for (int64_t i = 0; i < count; ++i) { + out[static_cast(i)] = static_cast(raw[static_cast(i)] * scale); + } + return out; +} + +bool EndsWith(const std::string& text, const std::string& suffix) { + return text.size() >= suffix.size() && + text.compare(text.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +std::vector Ltx2Param(const std::string& name, const std::vector& shape) { + int64_t count = 1; + for (int64_t dim : shape) count *= dim; + const size_t rank = shape.size(); + double scale = 0.1; + double offset = 0.0; + if (EndsWith(name, "std-of-means")) { + offset = 1.0; + } else if (EndsWith(name, "mean-of-means")) { + // scale 0.1, offset 0 + } else if (EndsWith(name, ".bias")) { + scale = 0.05; + } else if (rank == 1 && EndsWith(name, ".weight")) { + offset = 1.0; + } + const std::vector raw = Ltx2Rand(name, count); + std::vector out(static_cast(count)); + for (int64_t i = 0; i < count; ++i) { + out[static_cast(i)] = + static_cast(raw[static_cast(i)] * scale + offset); + } + return out; +} + +struct ParamBag { + vllm::Ltx2VaeWeights weights; + std::vector names; + std::vector counts; + + void Put(const std::string& name, const std::vector& shape) { + std::vector values = Ltx2Param(name, shape); + counts.push_back(static_cast(values.size())); + names.push_back(name); + weights.tensors[name] = std::move(values); + } +}; + +// The reduced encoder the generator built (`IMG_ENC_BLOCKS` / `IMG_ENC`). +vllm::Ltx2ConvVideoEncoderConfig ImageEncoderConfig() { + vllm::Ltx2ConvVideoEncoderConfig cfg; + cfg.in_channels = 3; + cfg.out_channels = 4; + cfg.patch_size = 2; + cfg.norm_layer = vllm::Ltx2NormLayer::kPixelNorm; + cfg.latent_log_var = vllm::Ltx2LogVarianceType::kUniform; + cfg.spatial_padding_mode = vllm::Ltx2PaddingMode::kZeros; + cfg.encoder_blocks = { + {"res_x", 1, 0}, + {"compress_space_res", 1, 2}, + {"compress_all_res", 1, 1}, + }; + cfg.prefix = "ltx2.imgenc."; + return cfg; +} + +// The parameter set `VideoEncoder(**IMG_ENC)` builds, in state_dict order. +ParamBag BuildImageEncoderParams(const vllm::Ltx2ConvVideoEncoderConfig& cfg) { + ParamBag bag; + const std::string p = cfg.prefix; + bag.Put(p + "per_channel_statistics.std-of-means", {cfg.out_channels}); + bag.Put(p + "per_channel_statistics.mean-of-means", {cfg.out_channels}); + const int64_t patched_in = cfg.in_channels * cfg.patch_size * cfg.patch_size; + bag.Put(p + "conv_in.conv.weight", {cfg.out_channels, patched_in, 3, 3, 3}); + bag.Put(p + "conv_in.conv.bias", {cfg.out_channels}); + + int64_t feature = cfg.out_channels; + for (size_t i = 0; i < cfg.encoder_blocks.size(); ++i) { + const vllm::Ltx2VideoEncoderBlock& block = cfg.encoder_blocks[i]; + const std::string bp = p + "down_blocks." + std::to_string(i); + const int64_t multiplier = block.multiplier != 0 ? block.multiplier : 2; + if (block.name == "res_x") { + for (int64_t j = 0; j < block.num_layers; ++j) { + const std::string rp = bp + ".res_blocks." + std::to_string(j); + bag.Put(rp + ".conv1.conv.weight", {feature, feature, 3, 3, 3}); + bag.Put(rp + ".conv1.conv.bias", {feature}); + bag.Put(rp + ".conv2.conv.weight", {feature, feature, 3, 3, 3}); + bag.Put(rp + ".conv2.conv.bias", {feature}); + } + } else { + // The *_res family: SpaceToDepthDownsample's conv emits + // out_channels / prod(stride); the space-to-depth fold multiplies it back. + const int64_t st = block.name == "compress_space_res" ? 1 : 2; + const int64_t ss = block.name == "compress_time_res" ? 1 : 2; + const int64_t out = feature * multiplier; + const int64_t conv_out = out / (st * ss * ss); + bag.Put(bp + ".conv.conv.weight", {conv_out, feature, 3, 3, 3}); + bag.Put(bp + ".conv.conv.bias", {conv_out}); + feature = out; + } + } + bag.Put(p + "conv_out.conv.weight", {cfg.out_channels + 1, feature, 3, 3, 3}); + bag.Put(p + "conv_out.conv.bias", {cfg.out_channels + 1}); + return bag; +} + +// NaN-hardened, for the reason issue #449 records: `std::max(worst, x)` is +// `worst < x ? x : worst`, and `worst < NaN` is false, so an all-NaN arm reduces +// to 0.0 and reports a perfect match. +double MaxAbsDiff(const std::vector& got, const float* want, size_t count) { + REQUIRE(got.size() == count); + double worst = 0.0; + for (size_t i = 0; i < count; ++i) { + const double diff = std::abs(static_cast(got[i]) - static_cast(want[i])); + if (!(diff <= worst)) worst = diff; // NaN takes this branch + } + return worst; +} + +// A binary PPM (P6) carrying `codes` as an h x w RGB payload — the container the +// engine actually reads, built here so section 2 gates the DECODER too and not +// just the arithmetic downstream of it. +std::string MakePpm(const std::vector& codes, int64_t height, int64_t width) { + std::string out = "P6\n" + std::to_string(width) + " " + std::to_string(height) + "\n255\n"; + out.append(reinterpret_cast(codes.data()), codes.size()); + return out; +} + +const std::string& ConditioningImagePpm() { + static const std::string ppm = MakePpm( + Ltx2Bytes("ltx2.imgcond.image", + vllm_test::kLtx2ImgPreSrcH * vllm_test::kLtx2ImgPreSrcW * 3), + vllm_test::kLtx2ImgPreSrcH, vllm_test::kLtx2ImgPreSrcW); + return ppm; +} + +// The latent state the generator built: the target shape, patch 1, causal fix. +vllm::Ltx2VideoLatentShape ConditioningTarget() { + vllm::Ltx2VideoLatentShape target; + target.batch = 1; + target.channels = vllm_test::kLtx2ImgEncOutC; + target.frames = vllm_test::kLtx2ImgCondTargetFrames; + target.height = vllm_test::kLtx2ImgEncOutH; + target.width = vllm_test::kLtx2ImgEncOutW; + return target; +} + +// The encoded conditioning image, as `Ltx2ConvVideoEncode` produces it. +vllm::Ltx2LatentVolume EncodeConditioningImage(const std::vector& chw) { + const vllm::Ltx2ConvVideoEncoderConfig cfg = ImageEncoderConfig(); + const ParamBag bag = BuildImageEncoderParams(cfg); + return vllm::Ltx2ConvVideoEncode(cfg, bag.weights, chw, cfg.in_channels, /*frame_count=*/1, + vllm_test::kLtx2ImgPreDstH, vllm_test::kLtx2ImgPreDstW, + nullptr); +} + +// The composition under test, factored so the mutation witnesses below can drive +// it with a deliberately WRONG `latent`, `strength` or ORDER and show the golden +// moves. Returns the noised latent. +struct Composition { + std::vector clean, mask, noised; +}; + +Composition ComposeConditioning(const vllm::Ltx2LatentVolume& conditioning, double strength, + bool condition_before_noise) { + const vllm::Ltx2VideoLatentShape target = ConditioningTarget(); + const vllm::Ltx2ScaleFactors factors; + vllm::Ltx2LatentState state = vllm::Ltx2CreateVideoLatentState( + target, vllm_test::kLtx2ImgCondPatch, factors, vllm_test::kLtx2ImgCondFps, + /*causal_fix=*/true); + + const std::vector noise = + Ltx2Input("ltx2.imgcond.noise", static_cast(state.latent.size()), 1.0); + + auto apply = [&]() { + vllm::Ltx2ConditionVideoByLatentIndex(&state, target, vllm_test::kLtx2ImgCondPatch, + conditioning, strength, + vllm_test::kLtx2ImgCondLatentIdx); + }; + auto noise_it = [&]() { + std::vector broadcast(state.latent.size()); + for (int64_t t = 0; t < state.tokens; ++t) { + for (int64_t c = 0; c < state.width; ++c) { + broadcast[static_cast(t * state.width + c)] = + state.mask[static_cast(t)]; + } + } + state.latent = vllm::Ltx2GaussianNoise( + state.latent.data(), state.clean.data(), broadcast.data(), noise.data(), + static_cast(state.latent.size()), + static_cast(vllm_test::kLtx2ImgNoiseScale)); + }; + + if (condition_before_noise) { + apply(); + noise_it(); + } else { + noise_it(); + apply(); + } + return Composition{state.clean, state.mask, state.latent}; +} + +} // namespace + +// ─── the anchor ───────────────────────────────────────────────────────────── + +TEST_CASE("ltx2 image cond: the goldens carry the PINNED upstream revision") { + CHECK(std::string(vllm_test::kLtx2ImgCondUpstreamRevision) == + std::string(kLtx2ImgCondUpstreamRevisionPin)); +} + +// ─── section 1: the resize ────────────────────────────────────────────────── + +TEST_CASE("ltx2 image cond: resize_and_center_crop matches executed upstream") { + // Five shape pairs: upscale, downscale, identity, wider-than-target and + // taller-than-target. Case 1 (32x24 -> 16x16) is the one where `ceil` + // disagrees with BOTH `round` and `int`: 32 * (16/24) is 21.333, so upstream + // resizes to 22 rows and crops 3, while a rounding port resizes to 21 and + // crops 2 — every output value moves and every shape check still passes. + struct Case { + int64_t src_h, src_w, dst_h, dst_w; + const float* golden; + size_t golden_size; + const char* name; + }; + const Case cases[] = { + {vllm_test::kLtx2ImgResize0SrcH, vllm_test::kLtx2ImgResize0SrcW, + vllm_test::kLtx2ImgResize0DstH, vllm_test::kLtx2ImgResize0DstW, + vllm_test::kLtx2ImgResize0Golden, std::size(vllm_test::kLtx2ImgResize0Golden), + "12x20 -> 16x16"}, + {vllm_test::kLtx2ImgResize1SrcH, vllm_test::kLtx2ImgResize1SrcW, + vllm_test::kLtx2ImgResize1DstH, vllm_test::kLtx2ImgResize1DstW, + vllm_test::kLtx2ImgResize1Golden, std::size(vllm_test::kLtx2ImgResize1Golden), + "32x24 -> 16x16 (the ceil case)"}, + {vllm_test::kLtx2ImgResize2SrcH, vllm_test::kLtx2ImgResize2SrcW, + vllm_test::kLtx2ImgResize2DstH, vllm_test::kLtx2ImgResize2DstW, + vllm_test::kLtx2ImgResize2Golden, std::size(vllm_test::kLtx2ImgResize2Golden), + "16x16 -> 16x16 (identity)"}, + {vllm_test::kLtx2ImgResize3SrcH, vllm_test::kLtx2ImgResize3SrcW, + vllm_test::kLtx2ImgResize3DstH, vllm_test::kLtx2ImgResize3DstW, + vllm_test::kLtx2ImgResize3Golden, std::size(vllm_test::kLtx2ImgResize3Golden), + "8x8 -> 16x24"}, + {vllm_test::kLtx2ImgResize4SrcH, vllm_test::kLtx2ImgResize4SrcW, + vllm_test::kLtx2ImgResize4DstH, vllm_test::kLtx2ImgResize4DstW, + vllm_test::kLtx2ImgResize4Golden, std::size(vllm_test::kLtx2ImgResize4Golden), + "10x7 -> 16x16"}, + }; + REQUIRE(std::size(cases) == static_cast(vllm_test::kLtx2ImgResizeCases)); + + for (size_t i = 0; i < std::size(cases); ++i) { + const Case& c = cases[i]; + INFO("resize case " << i << ": " << c.name); + const std::vector codes = + Ltx2Bytes("ltx2.imgcond.resize" + std::to_string(i), c.src_h * c.src_w * 3); + std::vector hwc(codes.size()); + for (size_t k = 0; k < codes.size(); ++k) hwc[k] = static_cast(codes[k]); + + const std::vector got = + vllm::Ltx2ResizeAndCenterCrop(hwc.data(), c.src_h, c.src_w, 3, c.dst_h, c.dst_w); + REQUIRE(got.size() == c.golden_size); + const double worst = MaxAbsDiff(got, c.golden, c.golden_size); + CAPTURE(worst); + CHECK(worst <= kLtx2ImgPixelTol); + } +} + +TEST_CASE("ltx2 image cond: an aspect-fill resize that cannot cover the target is refused") { + // Not reachable through `Ltx2LoadImageAndPreprocess` — `scale = max(...)` makes + // it unreachable by construction — which is exactly why it is asserted here: + // a guard nobody can trip is a guard nobody notices has been deleted, and this + // one is what stops a future caller with its own scale from silently indexing + // a negative crop offset. + const std::vector tiny(3 * 4 * 4, 1.0F); + CHECK_THROWS(vllm::Ltx2ResizeAndCenterCrop(tiny.data(), 0, 4, 3, 4, 4)); + CHECK_THROWS(vllm::Ltx2ResizeAndCenterCrop(nullptr, 4, 4, 3, 4, 4)); +} + +// ─── section 2: the whole preprocess, from PPM bytes ──────────────────────── + +TEST_CASE("ltx2 image cond: load_image_and_preprocess matches upstream at crf=0") { + const std::vector got = vllm::Ltx2LoadImageAndPreprocess( + "first_frame", ConditioningImagePpm(), vllm_test::kLtx2ImgPreDstH, + vllm_test::kLtx2ImgPreDstW, /*crf=*/0); + REQUIRE(got.size() == std::size(vllm_test::kLtx2ImgPreGolden)); + const double worst = + MaxAbsDiff(got, vllm_test::kLtx2ImgPreGolden, std::size(vllm_test::kLtx2ImgPreGolden)); + CAPTURE(worst); + CHECK(worst <= kLtx2ImgGoldenTol); + + // The output space, asserted rather than assumed: `Ltx2ConvVideoEncode` takes + // [-1, 1] pixels, and a port that forgot the `- 1.0` would still be finite, + // still be the right shape, and still pass every structural check. + const auto minmax = std::minmax_element(got.begin(), got.end()); + CHECK(*minmax.first >= -1.0F); + CHECK(*minmax.second <= 1.0F); + CHECK(*minmax.first < 0.0F); +} + +TEST_CASE("ltx2 image cond: the resize/normalize ORDER is below every band here") { + // THE NEGATIVE RESULT, asserted so it stays true rather than left as prose. + // A convex combination commutes with an affine map, so the two orders differ + // only by f32 rounding — `kLtx2ImgPreOrderGap` is that difference, measured + // upstream. It is below the golden band, so no value comparison in this file + // can catch a swap; and it is below `kLtx2ImgPixelTol` too, so tightening the + // band would not help either, it would only make section 1 red. + // + // If this ever fails, the two operations stopped being the affine/convex pair + // this reasoning rests on, and the ORDER becomes gateable — which is a finding + // worth acting on, not an assertion to delete. + CHECK(vllm_test::kLtx2ImgPreOrderGap < kLtx2ImgGoldenTol); + CHECK(vllm_test::kLtx2ImgPreOrderGap * 127.5 < kLtx2ImgPixelTol); +} + +// ─── the CRF refusal, and what resolves it ────────────────────────────────── + +TEST_CASE("ltx2 image cond: a non-zero CRF is refused by name, never rendered") { + CHECK_NOTHROW(vllm::Ltx2PreprocessImageCrf(0)); + for (const int64_t crf : {1, 18, 33, 51, -1}) { + INFO("crf " << crf); + CHECK_THROWS_WITH_AS(vllm::Ltx2PreprocessImageCrf(crf), + doctest::Contains("encode_single_frame"), std::runtime_error); + } + // And it is refused through the FULL entry point too, not only the helper — + // an image request that named a CRF must not reach the encoder. + CHECK_THROWS_WITH_AS( + vllm::Ltx2LoadImageAndPreprocess("first_frame", ConditioningImagePpm(), 16, 16, 18), + doctest::Contains("CRF 18"), std::runtime_error); +} + +TEST_CASE("ltx2 image cond: an LTX-2.5 checkpoint RESOLVES crf 18, so the default refuses") { + // constants.py:130-133 — the newest row at or below the version. This is what + // makes the refusal above a real one: a caller who says nothing gets 18. + CHECK(vllm::Ltx2ResolveDefaultImageCrf({2, 5}) == 18); + CHECK(vllm::Ltx2ResolveDefaultImageCrf({2, 5, 0}) == 18); + CHECK(vllm::Ltx2ResolveDefaultImageCrf({2, 4}) == 18); + CHECK(vllm::Ltx2ResolveDefaultImageCrf({3}) == 18); // newer inherits the closest known row + CHECK(vllm::Ltx2ResolveDefaultImageCrf({2, 3}) == 33); + CHECK(vllm::Ltx2ResolveDefaultImageCrf({2, 0}) == 33); + CHECK(vllm::Ltx2ResolveDefaultImageCrf({2}) == 33); + // `detect_model_version` returns () for an unset/unparseable version, which + // "compares below every real version" (:138-139) — the OLDEST fallback. + CHECK(vllm::Ltx2ResolveDefaultImageCrf({}) == 33); +} + +// ─── the PPM decoder's own refusals ───────────────────────────────────────── + +TEST_CASE("ltx2 image cond: a container this tree cannot read is refused by name") { + int64_t h = 0, w = 0; + CHECK_THROWS_WITH_AS(vllm::Ltx2DecodePpmRgb("first_frame", "\x89PNG\r\n\x1a\n", &h, &w), + doctest::Contains("binary PPM"), std::runtime_error); + // P3 is ASCII PPM — a real format, and NOT the one that is read. + CHECK_THROWS_AS(vllm::Ltx2DecodePpmRgb("first_frame", "P3\n2 2\n255\n0 0 0", &h, &w), + std::runtime_error); + // A non-255 maxval is REFUSED rather than rescaled: PIL's rescale is what + // upstream would apply and mirroring it is a separate port. + CHECK_THROWS_WITH_AS( + vllm::Ltx2DecodePpmRgb("first_frame", std::string("P6\n1 1\n15\n") + "\x01\x02\x03", &h, &w), + doctest::Contains("maxval"), std::runtime_error); + // A truncated payload must not be padded with whatever was in the buffer. + CHECK_THROWS_WITH_AS(vllm::Ltx2DecodePpmRgb("first_frame", "P6\n4 4\n255\nshort", &h, &w), + doctest::Contains("truncated"), std::runtime_error); + + // The happy path reports the FILE's own geometry, not the caller's. + const std::vector rgb = + vllm::Ltx2DecodePpmRgb("first_frame", ConditioningImagePpm(), &h, &w); + CHECK(h == vllm_test::kLtx2ImgPreSrcH); + CHECK(w == vllm_test::kLtx2ImgPreSrcW); + CHECK(rgb.size() == static_cast(h * w * 3)); +} + +// ─── the load path this row exists to build ───────────────────────────────── + +TEST_CASE("ltx2 image cond: the ENCODER key rules are the encoder's, not the decoder's") { + const std::vector rules = vllm::Ltx2VideoVaeEncoderKeyRules(); + REQUIRE(rules.size() == 4); + // model_configurator.py:267-276, rule for rule and IN ORDER — the `vae.` + // spellings must precede their bare twins or a monolithic checkpoint's + // `vae.encoder.*` matches nothing and is silently dropped. + CHECK(rules[0].match_prefix == "vae.encoder."); + CHECK(rules[0].replacement.empty()); + CHECK(rules[1].match_prefix == "vae.per_channel_statistics."); + CHECK(rules[1].replacement == "per_channel_statistics."); + CHECK(rules[2].match_prefix == "encoder."); + CHECK(rules[2].replacement.empty()); + CHECK(rules[3].match_prefix == "per_channel_statistics."); + CHECK(rules[3].replacement == "per_channel_statistics."); + + // The two filters must DISAGREE. Before this row the engine held the decoder's + // alone, and asserting they differ is what says this one is not a copy. + const std::vector decoder = vllm::Ltx2VideoVaeDecoderKeyRules(); + REQUIRE(decoder.size() == rules.size()); + bool differs = false; + for (size_t i = 0; i < rules.size(); ++i) { + if (rules[i].match_prefix != decoder[i].match_prefix) differs = true; + } + CHECK(differs); +} + +TEST_CASE("ltx2 image cond: a decoder-only checkpoint is REPORTED, not half-loaded") { + CHECK(!vllm::Ltx2CheckpointHasVideoEncoder( + {"vae.decoder.conv_in.conv.weight", "vae.per_channel_statistics.std-of-means"})); + // per_channel_statistics alone is NOT evidence of an encoder: a Comfy-split + // decoder file carries it too, and treating it as evidence would report every + // one of them as encodable and then throw deep inside the encoder instead. + CHECK(!vllm::Ltx2CheckpointHasVideoEncoder({"per_channel_statistics.mean-of-means"})); + CHECK(vllm::Ltx2CheckpointHasVideoEncoder({"vae.encoder.conv_in.conv.weight"})); + CHECK(vllm::Ltx2CheckpointHasVideoEncoder({"encoder.conv_in.conv.weight"})); + CHECK(!vllm::Ltx2CheckpointHasVideoEncoder({})); +} + +TEST_CASE("ltx2 image cond: the encoder config reads the LATENT width, not the RGB one") { + nlohmann::json vae; + vae["_class_name"] = "CausalVideoAutoencoder"; + vae["dims"] = 3; + vae["in_channels"] = 3; + vae["out_channels"] = 3; // the DECODER's RGB count + vae["latent_channels"] = 128; // the ENCODER's latent width + vae["patch_size"] = 4; + vae["norm_layer"] = "pixel_norm"; + vae["encoder_blocks"] = nlohmann::json::array({ + nlohmann::json::array({"res_x", {{"num_layers", 2}}}), + nlohmann::json::array({"compress_all_res", {{"multiplier", 2}}}), + }); + nlohmann::json config; + config["vae"] = vae; + + const vllm::Ltx2ConvVideoEncoderConfig cfg = vllm::Ltx2ParseConvVideoEncoderConfig(config); + CHECK(cfg.out_channels == 128); // NOT 3 + CHECK(cfg.in_channels == 3); + CHECK(cfg.patch_size == 4); + REQUIRE(cfg.encoder_blocks.size() == 2); + CHECK(cfg.encoder_blocks[0].name == "res_x"); + CHECK(cfg.encoder_blocks[0].num_layers == 2); + // An ABSENT multiplier stays at the 0 sentinel — becoming 1 would quietly + // halve every widening block's output width. + CHECK(cfg.encoder_blocks[0].multiplier == 0); + 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). + CHECK(cfg.spatial_padding_mode == vllm::Ltx2PaddingMode::kZeros); + CHECK(cfg.latent_log_var == vllm::Ltx2LogVarianceType::kUniform); + + SUBCASE("a declared spatial_padding_mode wins over the default") { + config["vae"]["spatial_padding_mode"] = "reflect"; + CHECK(vllm::Ltx2ParseConvVideoEncoderConfig(config).spatial_padding_mode == + vllm::Ltx2PaddingMode::kReflect); + } + SUBCASE("the top-level encoder_spatial_padding_mode is the second lookup") { + config["vae"]["encoder_spatial_padding_mode"] = "replicate"; + CHECK(vllm::Ltx2ParseConvVideoEncoderConfig(config).spatial_padding_mode == + vllm::Ltx2PaddingMode::kReplicate); + } + SUBCASE("a NESTED CausalDiffusionVAE config reads encoder.out_channels") { + nlohmann::json nested; + nested["vae"]["latent_channels"] = 64; + nested["vae"]["encoder"]["out_channels"] = 96; + nested["vae"]["encoder"]["blocks"] = config["vae"]["encoder_blocks"]; + CHECK(vllm::Ltx2ParseConvVideoEncoderConfig(nested).out_channels == 96); + } + SUBCASE("no encoder_blocks is REFUSED, never defaulted to an empty list") { + config["vae"].erase("encoder_blocks"); + CHECK_THROWS_WITH_AS(vllm::Ltx2ParseConvVideoEncoderConfig(config), + doctest::Contains("encoder_blocks"), std::runtime_error); + } + SUBCASE("a 2-D checkpoint is refused rather than built as 3-D") { + config["vae"]["dims"] = 2; + CHECK_THROWS_AS(vllm::Ltx2ParseConvVideoEncoderConfig(config), std::runtime_error); + } + SUBCASE("an unknown latent_log_var is refused rather than mapped to the nearest") { + config["vae"]["latent_log_var"] = "gaussian"; + CHECK_THROWS_AS(vllm::Ltx2ParseConvVideoEncoderConfig(config), std::runtime_error); + } +} + +// ─── sections 3-5: the chain ──────────────────────────────────────────────── + +TEST_CASE("ltx2 image cond: the encoder's parameter set IS the generator's state_dict") { + const ParamBag bag = BuildImageEncoderParams(ImageEncoderConfig()); + REQUIRE(bag.names.size() == std::size(vllm_test::kLtx2ImgEncParamNames)); + REQUIRE(bag.counts.size() == std::size(vllm_test::kLtx2ImgEncParamCounts)); + for (size_t i = 0; i < bag.names.size(); ++i) { + INFO("parameter " << i); + CHECK(bag.names[i] == std::string(vllm_test::kLtx2ImgEncParamNames[i])); + CHECK(bag.counts[i] == vllm_test::kLtx2ImgEncParamCounts[i]); + } +} + +TEST_CASE("ltx2 image cond: PPM to conditioned, noised tokens, against executed upstream") { + // THE WHOLE CHAIN, driven from the container bytes rather than from any + // intermediate golden — which is the one thing the per-brick goldens in + // test_ltx2_vae cannot show. + const std::vector chw = vllm::Ltx2LoadImageAndPreprocess( + "first_frame", ConditioningImagePpm(), vllm_test::kLtx2ImgPreDstH, + vllm_test::kLtx2ImgPreDstW, /*crf=*/0); + + const vllm::Ltx2ConvVideoEncoderConfig cfg = ImageEncoderConfig(); + CHECK(vllm::Ltx2VideoTemporalScaleFactor(cfg.encoder_blocks) == + vllm_test::kLtx2ImgEncTemporalFactor); + CHECK(vllm::Ltx2VideoSpatialScaleFactor(cfg.encoder_blocks, cfg.patch_size) == + vllm_test::kLtx2ImgEncSpatialFactor); + + const vllm::Ltx2LatentVolume encoded = EncodeConditioningImage(chw); + CHECK(encoded.channels == vllm_test::kLtx2ImgEncOutC); + CHECK(encoded.frames == vllm_test::kLtx2ImgEncOutT); + CHECK(encoded.height == vllm_test::kLtx2ImgEncOutH); + CHECK(encoded.width == vllm_test::kLtx2ImgEncOutW); + { + const double worst = MaxAbsDiff(encoded.data, vllm_test::kLtx2ImgEncGolden, + std::size(vllm_test::kLtx2ImgEncGolden)); + CAPTURE(worst); + CHECK(worst <= kLtx2ImgGoldenTol); + } + + const Composition composed = + ComposeConditioning(encoded, vllm_test::kLtx2ImgCondStrength, /*condition_before_noise=*/true); + + CHECK(static_cast(composed.mask.size()) == vllm_test::kLtx2ImgCondTokens); + { + const double worst = MaxAbsDiff(composed.clean, vllm_test::kLtx2ImgCondClean, + std::size(vllm_test::kLtx2ImgCondClean)); + CAPTURE(worst); + CHECK(worst <= kLtx2ImgGoldenTol); + } + { + const double worst = MaxAbsDiff(composed.mask, vllm_test::kLtx2ImgCondMask, + std::size(vllm_test::kLtx2ImgCondMask)); + CAPTURE(worst); + CHECK(worst <= kLtx2ImgGoldenTol); + } + { + const double worst = MaxAbsDiff(composed.noised, vllm_test::kLtx2ImgNoisedGolden, + std::size(vllm_test::kLtx2ImgNoisedGolden)); + CAPTURE(worst); + CHECK(worst <= kLtx2ImgGoldenTol); + } +} + +// ─── the goldens are LOAD-BEARING: witnesses, not assertions of shape ─────── + +TEST_CASE("ltx2 image cond: the conditioned latent is what the goldens depend on") { + // THIS IS THE CLASS THE CAMPAIGN KEEPS FINDING. An earlier phase's + // conditioning could be scaled x1.5 or row-REVERSED and every assertion in the + // suite stayed green. Each witness below drives the SAME composition with a + // deliberately wrong input and asserts the golden distance is enormous, so a + // reader can see that the comparisons above are sensitive to the thing they + // claim to be about — rather than inferring it. + const std::vector chw = vllm::Ltx2LoadImageAndPreprocess( + "first_frame", ConditioningImagePpm(), vllm_test::kLtx2ImgPreDstH, + vllm_test::kLtx2ImgPreDstW, /*crf=*/0); + const vllm::Ltx2LatentVolume encoded = EncodeConditioningImage(chw); + + const size_t noised_n = std::size(vllm_test::kLtx2ImgNoisedGolden); + const size_t clean_n = std::size(vllm_test::kLtx2ImgCondClean); + const size_t mask_n = std::size(vllm_test::kLtx2ImgCondMask); + + SUBCASE("the encoded latent scaled x1.5") { + vllm::Ltx2LatentVolume scaled = encoded; + for (float& v : scaled.data) v *= 1.5F; + const Composition c = + ComposeConditioning(scaled, vllm_test::kLtx2ImgCondStrength, true); + CHECK(MaxAbsDiff(c.clean, vllm_test::kLtx2ImgCondClean, clean_n) > kLtx2ImgGoldenTol); + CHECK(MaxAbsDiff(c.noised, vllm_test::kLtx2ImgNoisedGolden, noised_n) > kLtx2ImgGoldenTol); + } + + SUBCASE("the encoded latent's channels REVERSED — same values, wrong places") { + vllm::Ltx2LatentVolume reversed = encoded; + std::reverse(reversed.data.begin(), reversed.data.end()); + const Composition c = + ComposeConditioning(reversed, vllm_test::kLtx2ImgCondStrength, true); + CHECK(MaxAbsDiff(c.clean, vllm_test::kLtx2ImgCondClean, clean_n) > kLtx2ImgGoldenTol); + CHECK(MaxAbsDiff(c.noised, vllm_test::kLtx2ImgNoisedGolden, noised_n) > kLtx2ImgGoldenTol); + } + + SUBCASE("the mask as `strength` instead of `1 - strength`") { + // 1 - 0.7 = 0.3 against 0.7. Mask 0 means KEEP the clean value, so inverting + // it renders an unconditioned clip that looks like the feature not working. + const Composition c = ComposeConditioning(encoded, 1.0 - vllm_test::kLtx2ImgCondStrength, true); + CHECK(MaxAbsDiff(c.mask, vllm_test::kLtx2ImgCondMask, mask_n) > kLtx2ImgGoldenTol); + CHECK(MaxAbsDiff(c.noised, vllm_test::kLtx2ImgNoisedGolden, noised_n) > kLtx2ImgGoldenTol); + // ...and the CLEAN latent is IDENTICAL, which is why the mask needs its own + // comparison: a suite that only checked `clean` would be blind to this. + CHECK(MaxAbsDiff(c.clean, vllm_test::kLtx2ImgCondClean, clean_n) <= kLtx2ImgGoldenTol); + } + + SUBCASE("the conditioning applied AFTER the noiser") { + // Upstream conditions the CLEAN tensor and lets the noiser compose it + // (latent_cond.py:38-39 + noisers.py:31-34). Applying it afterwards leaves + // the noised latent pinned to noise instead of to the image — with the + // identical clean tensor and the identical mask, so only section 5 sees it. + const Composition c = + ComposeConditioning(encoded, vllm_test::kLtx2ImgCondStrength, /*condition_before_noise=*/false); + CHECK(MaxAbsDiff(c.clean, vllm_test::kLtx2ImgCondClean, clean_n) <= kLtx2ImgGoldenTol); + CHECK(MaxAbsDiff(c.mask, vllm_test::kLtx2ImgCondMask, mask_n) <= kLtx2ImgGoldenTol); + CHECK(MaxAbsDiff(c.noised, vllm_test::kLtx2ImgNoisedGolden, noised_n) > kLtx2ImgGoldenTol); + } +} + +TEST_CASE("ltx2 image cond: the noiser follows ltx_core, and diffusers is far away") { + // `kLtx2ImgNoiseDivergence` is max|ltx_core - diffusers| at this NON-UNIT noise + // scale, measured by the generator against both compositions. Asserting it is + // orders of magnitude above the band is what makes section 5 a gate on the + // CHOICE spec §3.3 makes, rather than a gate that would pass either way. + CHECK(vllm_test::kLtx2ImgNoiseDivergence > 1000.0 * kLtx2ImgGoldenTol); + CHECK(vllm_test::kLtx2ImgNoiseScale != 1.0); // the only scale at which they agree +} diff --git a/tests/vllm/multimodal/test_ltx2_video.cpp b/tests/vllm/multimodal/test_ltx2_video.cpp index e8eaf1220..54c13386b 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/model_executor/models/ltx2_video_vae_encoder.h" #include "vllm.h" #include "vt/backend.h" #include "vt/device.h" @@ -671,40 +672,244 @@ TEST_CASE("ltx2 video: the recipe comes from the CHECKPOINT's own model_version" } } -TEST_CASE("ltx2 video: keyframe and reference conditioning is refused by name") { +// A binary PPM the engine can actually condition on. Deliberately NOT the +// generation's own resolution: `load_image_and_preprocess` aspect-fills and +// centre-crops to the phase's height/width (media_io/resize.py:41-73), and an +// image that already fits would leave that untested. +std::string ConditioningPpm(int height, int width, unsigned seed) { + std::string out = "P6\n" + std::to_string(width) + " " + std::to_string(height) + "\n255\n"; + for (int i = 0; i < height * width * 3; ++i) { + out.push_back(static_cast((i * 37 + static_cast(seed) * 101) % 251)); + } + return out; +} + +// 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 +// 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) { + vllm::multimodal::VideoModelParams mp = FixtureParams(paths); + mp.extras["upsampler_path"] = paths.upsampler; + return mp; +} + +TEST_CASE("ltx2 video: keyframe and reference conditioning is refused BY WHAT IS MISSING") { + // Row LTX25-IMAGE-COND (#644) SPLIT this refusal. It used to cover every + // conditioning kind with one message whose reason was "no encoder weights can + // be materialized here" — true when written, and no longer: this engine now + // loads them through `Ltx2VideoVaeEncoderKeyRules`, and the first-frame arm is + // served (see the case below). + // + // So each surviving refusal is held to naming a DIFFERENT missing piece. The + // point is not that the message is long; it is that a later reader can go and + // check the named symbol and find out whether the reason still holds — which + // is the thing five refusals in this campaign failed at. Workspace ws; const std::unique_ptr engine = - vllm::multimodal::LoadVideoEngine(FixtureParams(ws.paths)); - vllm::multimodal::VideoGenParams gen = FixtureGen(ws.root + "/keyframed"); - gen.first_frame_path = ws.paths.video_embeds; // any path: the refusal precedes the read - try { - (void)engine->Generate(gen); - FAIL("keyframe conditioning must be refused while no encoder is reachable from here"); - } catch (const std::exception& e) { - const std::string msg = e.what(); + vllm::multimodal::LoadVideoEngine(ConditioningParams(ws.paths)); + + auto refusal = [&](const char* what, + void (*arm)(vllm::multimodal::VideoGenParams&, const Workspace&)) { + vllm::multimodal::VideoGenParams gen = FixtureGen(ws.root + "/refused"); + arm(gen, ws); + try { + (void)engine->Generate(gen); + FAIL_CHECK(what << " must be refused, never dropped"); + return std::string(); + } catch (const std::exception& e) { + return std::string(e.what()); + } + }; + + SUBCASE("a LAST-frame keyframe names the unported DiT module") { + 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); - CHECK(msg.find("ImageConditioner") != std::string::npos); - // A refusal whose stated REASON has gone stale is worse than a vague one: it - // sends the next reader to build something that already exists. Phase L11 - // ported the video VAE encoder, so the message may no longer claim the - // encoder is missing, and these two assertions hold it to the pieces that - // actually are — the loader path that would put encoder weights in memory, - // and the CRF re-compression upstream applies before encoding. - CHECK(msg.find("VAE_ENCODER_COMFY_KEYS_FILTER") != std::string::npos); - CHECK(msg.find("default_image_crf") != std::string::npos); - // And the QUALIFIER on that re-compression, which the two substrings above do - // not reach: `preprocess` returns the image UNTOUCHED at `crf == 0` - // (media_io/decode.py:413-435, the `if crf == 0:` early return at :425-426 — - // NOT the one at :427-428, which is the degenerate-size guard), so "re-compresses - // before encoding" is only true of a nonzero resolved CRF. Naming the round - // trip without naming its exception overstates what is unported and sends the - // next reader to build an H.264 path for a case that needs none — the same - // failure mode as a stale reason, one step subtler. Gated here so deleting the - // qualifier goes RED rather than quietly restoring the overstatement. - CHECK(msg.find("unless that CRF is 0") != std::string::npos); + // 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); + CHECK(msg.find("keyframe_cond.py") != std::string::npos); + CHECK(msg.find("VAE_ENCODER_COMFY_KEYS_FILTER") == 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", + [](vllm::multimodal::VideoGenParams& g, const Workspace& w) { + g.ref_video_dir = w.root; + }); + INFO(msg); + CHECK(msg.find("temporal_scale_factor") != std::string::npos); + CHECK(msg.find("LoRA") != std::string::npos); + } + SUBCASE("reference audio names the AUDIO encoder, which this row did not build") { + const std::string msg = refusal("reference audio", + [](vllm::multimodal::VideoGenParams& g, const Workspace& w) { + g.ref_audio_path = w.paths.audio_embeds; + }); + INFO(msg); + CHECK(msg.find("audio VAE") != std::string::npos); + CHECK(msg.find("encode_audio") != std::string::npos); + } + SUBCASE("a non-zero CRF names the codec round trip, and says 0 is supported") { + // AND THIS IS THE DEFAULT PATH. An LTX-2.5 checkpoint resolves + // `default_image_crf = 18` (constants.py:37/124/130-133), so a caller who + // says nothing about the CRF lands here — which is what makes the + // out-of-distribution `crf = 0` arm a deliberate request rather than a + // silent downgrade. + vllm::multimodal::VideoGenParams gen = FixtureGen(ws.root + "/crf"); + gen.first_frame_ppm = ConditioningPpm(20, 28, 3); + try { + (void)engine->Generate(gen); + FAIL("an unset CRF resolves 18 for a 2.5 checkpoint and must be refused"); + } catch (const std::exception& e) { + const std::string msg = e.what(); + INFO(msg); + CHECK(msg.find("CRF 18") != std::string::npos); + CHECK(msg.find("encode_single_frame") != std::string::npos); + CHECK(msg.find("CRF 0 IS supported") != std::string::npos); + } + } + SUBCASE("an explicit non-zero CRF is refused just as an unset one is") { + vllm::multimodal::VideoGenParams gen = FixtureGen(ws.root + "/crf33"); + gen.first_frame_ppm = ConditioningPpm(20, 28, 4); + gen.extras[vllm::multimodal::kLtx2ImageCrfExtra] = "33"; + CHECK_THROWS_WITH_AS((void)engine->Generate(gen), doctest::Contains("CRF 33"), + std::runtime_error); + } + SUBCASE("a mistyped per-generation extra is refused, not ignored") { + vllm::multimodal::VideoGenParams gen = FixtureGen(ws.root + "/typo"); + gen.first_frame_ppm = ConditioningPpm(20, 28, 5); + gen.extras["image_crf_"] = "0"; + CHECK_THROWS_WITH_AS((void)engine->Generate(gen), doctest::Contains("image_crf_"), + std::runtime_error); } } +TEST_CASE("ltx2 video: an image at crf 0 conditions the render, and the ENCODER weights are read") { + // The arm row LTX25-IMAGE-COND (#644) opened. Two separate claims are made + // here and they are NOT the same claim: + // + // 1. the conditioning REACHES the render — the trace reports the encoded + // image, and a different image gives a different digest; and + // 2. the ENCODER WEIGHTS are READ — perturbing ONE encoder tensor in the + // checkpoint moves the digest, with every byte of the REQUEST identical. + // + // (2) is the one that is easy to fake. A path that loaded the weights and then + // conditioned on something else — zeros, the raw pixels, a re-used decoder + // tensor — satisfies (1) completely. + Workspace ws; + const std::unique_ptr engine = + vllm::multimodal::LoadVideoEngine(ConditioningParams(ws.paths)); + auto* ltx2 = dynamic_cast(engine.get()); + REQUIRE(ltx2 != nullptr); + + vllm::multimodal::VideoGenParams gen = FixtureGen(ws.root + "/img"); + gen.first_frame_ppm = ConditioningPpm(20, 28, 1); + gen.extras[vllm::multimodal::kLtx2ImageCrfExtra] = "0"; + const vllm::multimodal::VideoResult result = engine->Generate(gen); + + const vllm::multimodal::Ltx2ConditioningTrace trace = ltx2->last_conditioning(); + 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); + 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 + // separately — the same reason `video_absmax` exists next to `video_digest`. + CHECK(trace.image_absmax > 0.0); + // The render still produced its artifacts; conditioning is not a bypass. + CHECK(result.frame_count == 9); + + SUBCASE("a DIFFERENT image is a different conditioning") { + vllm::multimodal::VideoGenParams other = FixtureGen(ws.root + "/img2"); + other.first_frame_ppm = ConditioningPpm(20, 28, 2); + other.extras[vllm::multimodal::kLtx2ImageCrfExtra] = "0"; + (void)engine->Generate(other); + CHECK(ltx2->last_conditioning().image_digest != trace.image_digest); + } + + SUBCASE("the SAME image is the same conditioning") { + vllm::multimodal::VideoGenParams again = FixtureGen(ws.root + "/img3"); + again.first_frame_ppm = ConditioningPpm(20, 28, 1); + again.extras[vllm::multimodal::kLtx2ImageCrfExtra] = "0"; + (void)engine->Generate(again); + CHECK(ltx2->last_conditioning().image_digest == trace.image_digest); + } + + SUBCASE("the ENCODER's own weights decide the conditioning") { + // ONE tensor of the encoder half, perturbed in a SECOND fixture, with the + // request byte-identical. If the engine were conditioning on anything but + // the encoder's output — or had loaded the DECODER's tensors under the + // encoder's names — this digest would not move. + Workspace mutated; + const std::string path = mutated.paths.video_vae; + std::string bytes = ReadAll(path); + // The PAYLOAD is what gets perturbed, and its position is READ from the + // safetensors header rather than guessed at. An earlier revision of this + // case searched for the tensor's NAME and flipped a byte a fixed distance + // past it, which lands inside the JSON header of whatever tensor happens to + // be stored next — the file still parsed, the render still ran, and the + // digest did not move. The case failed, which is the only reason that is a + // footnote and not a false green. + REQUIRE(bytes.size() > 8); + uint64_t header_len = 0; + std::memcpy(&header_len, bytes.data(), sizeof(header_len)); + REQUIRE(8 + header_len <= bytes.size()); + const nlohmann::json header = + nlohmann::json::parse(bytes.substr(8, static_cast(header_len))); + const std::string needle = "encoder.conv_in.conv.weight"; + REQUIRE_MESSAGE(header.contains(needle), + "the fixture must carry an encoder half for this to prove anything"); + const size_t data_start = + 8 + static_cast(header_len) + + header.at(needle).at("data_offsets").at(0).get(); + REQUIRE(data_start + 1 < bytes.size()); + // bf16 is stored little-endian, so byte 0 of a word carries the mantissa's + // top bits; flipping 0x40 there moves that ONE weight by ~50% without any + // risk of manufacturing an Inf or a NaN out of the exponent — which would + // change the digest for a reason that has nothing to do with this claim. + bytes[data_start] = static_cast(bytes[data_start] ^ 0x40); + { + std::ofstream out(path, std::ios::binary | std::ios::trunc); + REQUIRE(out.good()); + out.write(bytes.data(), static_cast(bytes.size())); + } + const std::unique_ptr other = + vllm::multimodal::LoadVideoEngine(ConditioningParams(mutated.paths)); + auto* other_ltx2 = dynamic_cast(other.get()); + REQUIRE(other_ltx2 != nullptr); + vllm::multimodal::VideoGenParams same = FixtureGen(mutated.root + "/img"); + same.first_frame_ppm = ConditioningPpm(20, 28, 1); + same.extras[vllm::multimodal::kLtx2ImageCrfExtra] = "0"; + (void)other->Generate(same); + CHECK(other_ltx2->last_conditioning().image_digest != trace.image_digest); + } +} + +TEST_CASE("ltx2 video: a request WITHOUT an image leaves the trace's image fields empty") { + // Otherwise "this render was conditioned on an image" and "this render was + // not" would be indistinguishable after the fact, which is the one question + // `Ltx2ConditioningTrace` exists to answer. + Workspace ws; + const std::unique_ptr engine = + vllm::multimodal::LoadVideoEngine(ConditioningParams(ws.paths)); + auto* ltx2 = dynamic_cast(engine.get()); + REQUIRE(ltx2 != nullptr); + (void)engine->Generate(FixtureGen(ws.root + "/plain")); + const vllm::multimodal::Ltx2ConditioningTrace trace = ltx2->last_conditioning(); + CHECK(trace.completed); + CHECK(trace.image_tokens == 0); + CHECK(trace.image_digest == 0); + CHECK(trace.image_absmax == 0.0); + CHECK(trace.image_strength == 0.0); +} + // ─── the floor under everything above ─────────────────────────────────────── // @@ -1009,6 +1214,34 @@ TEST_CASE("ltx2 video: the SHIPPED Lightricks checkpoints parse and load") { // upstream's SDOps drop it. CHECK(!weights.Has("encoder.conv_in.conv.weight")); MESSAGE("shipped conv video VAE: " << weights.tensors.size() << " decoder tensors"); + + // ...and the ENCODER half of the SAME file resolves through the other + // filter (row LTX25-IMAGE-COND, #644). This is the only place the encoder + // load path meets a real shipped checkpoint rather than the fixture, so it + // is the only place the CHANNEL arithmetic can be wrong in a way the fixture + // agrees with: `latent_channels` is 128 while the top-level `out_channels` + // is 3, and reading the second builds a 3-channel-latent encoder that runs. + REQUIRE(vllm::Ltx2CheckpointHasVideoEncoder(file.Names())); + const vllm::Ltx2ConvVideoEncoderConfig enc = + vllm::Ltx2ParseConvVideoEncoderConfig(vllm::Ltx2ReadCheckpointConfig(file)); + CHECK(enc.out_channels == 128); + CHECK(enc.in_channels == 3); + CHECK(enc.patch_size == cfg.patch_size); + // The encoder's block list must multiply out to the SAME scale factors the + // decoder's does, or an encoded image does not fit the grid it is placed in. + CHECK(vllm::Ltx2VideoSpatialScaleFactor(enc.encoder_blocks, enc.patch_size) == spatial); + CHECK(vllm::Ltx2VideoTemporalScaleFactor(enc.encoder_blocks) == temporal); + const vllm::Ltx2VaeWeights enc_weights = + vllm::Ltx2LoadVaeWeights(file, vllm::Ltx2VideoVaeEncoderKeyRules()); + CHECK(enc_weights.Has("conv_in.conv.weight")); + CHECK(enc_weights.Has("conv_out.conv.weight")); + // The encoder normalizes its output by these (video_vae.py:336), so the + // filter has to carry them even though they are not `encoder.*` keys. + CHECK(enc_weights.Has("per_channel_statistics.std-of-means")); + // And the DECODER's half must be dropped, or the two bags would collide on + // names like `conv_in.conv.weight` and bind half a model to the other half. + CHECK(!enc_weights.Has("decoder.conv_in.conv.weight")); + MESSAGE("shipped conv video VAE: " << enc_weights.tensors.size() << " encoder tensors"); } SUBCASE("the audio VAE and its BWE vocoder load and configure") { @@ -1658,11 +1891,17 @@ TEST_CASE("ltx2 video: a trace for a render that never completed says so") { // was never produced, and every field would look entirely healthy: real // prompt, non-zero absmax, plausible digests. // - // THE PROBE IS A REAL REFUSAL, not an injected one. Keyframe / reference - // conditioning is refused by name (ltx2_video.cpp, the `ImageConditioner` - // note) and that refusal sits AFTER the trace is written, so a prompted - // request carrying a reference image walks the whole encode path, fills the - // trace, and then fails — exactly the shape this flag exists to report. + // THE PROBE IS A REAL REFUSAL, not an injected one. Reference conditioning is + // refused by name (ltx2_video.cpp, the `ImageConditioner` note) and that + // refusal sits AFTER the trace is written, so a prompted request carrying a + // reference image walks the whole encode path, fills the trace, and then + // fails — exactly the shape this flag exists to report. + // + // IT IS STILL A REFUSAL AFTER ROW LTX25-IMAGE-COND (#644), which served the + // first-frame arm and would have made a `first_frame_ppm` probe stop + // refusing. The reference arm stays refused for a reason this row did not + // touch (the IC-LoRA scale factors), so the probe was moved to it rather than + // to whatever happened to still throw. Workspace ws; const vllm::multimodal::VideoModelParams mp = EncoderParams(ws.paths); const std::unique_ptr engine = @@ -1675,11 +1914,11 @@ TEST_CASE("ltx2 video: a trace for a render that never completed says so") { gen.ref_image_paths.push_back(ws.root + "/nonexistent-reference.png"); try { (void)engine->Generate(gen); - FAIL("keyframe / reference conditioning must be refused"); + FAIL("reference conditioning must be refused"); } catch (const std::exception& e) { const std::string msg = e.what(); INFO(msg); - CHECK(msg.find("reference conditioning") != std::string::npos); + CHECK(msg.find("reference-image / reference-video conditioning") != std::string::npos); } const vllm::multimodal::Ltx2ConditioningTrace trace = ltx->last_conditioning(); From 247eba52bc77612abb7764e422b47e2f8bb59bc7 Mon Sep 17 00:00:00 2001 From: "mudler's LocalAI [bot]" <139863280+localai-bot@users.noreply.github.com> Date: Fri, 14 Aug 2026 15:43:20 +0200 Subject: [PATCH 3/3] feat(ltx-2.5): image conditioning at crf=0, the VAE encoder load path, and the FALSE refusal reason it exposed (#644) FOLLOWING_AGENTS_PROTOCOL Image conditioning for LTX-2.5 at crf=0, plus the VAE encoder load path that did not exist, plus the repair of that feature's own review findings. This branch CONTAINS #657, which closes as superseded. The finding worth reading: the refusal for keyframe conditioning named a FALSE reason, and a test had pinned it. It blamed `keyframes_abs_pos_embedding`. Refuted against Lightricks/LTX-2 @ fd4ded7f, and the decisive link was one the original refutation had not cited -- `conditioning/mask_utils.py:101`, where `marked=False` writes ZEROS onto the appended tokens, and `:97-98`, where a `None` prior mask makes the whole mask `None` so `apply_keyframes_absolute_embedding` returns `hidden_states` unchanged. Meanwhile `tools.py:184` + `_first_frame_keyframes_mask` mark the TARGET's first latent frame unconditionally -- the tokens the served arm writes into. So the contrast was backwards: the appended keyframe gets nothing. The new message names the real reason (the token-append machinery against a phase loop fixed at the target grid's token count), labels the old one "WHAT IS *NOT* THE REASON" with #658, and switches local anchors from same-file line numbers to SYMBOLS -- which is how the previous citation went stale. The second finding was a gate that caught nothing: mutating to `if (wants_image && phase_index == 0)` left the suite byte-for-byte at 32/550 exit 0. The replacement pins the last phase's per-latent-frame count against a `max_phase = 0` engine, comparing two MEASURED values from two different engines rather than a compile-time constant. Reviewed PASS (9 findings, none blocking) by a fresh reviewer who re-derived the upstream refutation itself, verified the hand-resolved merge left zero keyed-record keys lost or duplicated, and measured the shipped checkpoints directly: the first-party NVFP4 DiT declares the flag with the tensor ABSENT, while the vonkaiser FP8 copy carries it TRAINED at 4096/4096 non-zero bytes. Operator gate at the exact pushed SHA, main pinned to an immutable SHA rather than the ref (this is a shared checkout and `origin/main` moves mid-operation): WINPORT_EXIT=0, check-device-leakage OK at DSR 32 == baseline 32, BUILD_EXIT=0, 0 errors, 0 ENOSPC, REGISTERED=451, ctest 450/451, `test_ltx2_video` 32 cases/565 assertions exit 0, `test_ltx2_image_cond` 15/223 exit 0. The single ctest failure is #737, main's own Music3 golden red. Honest limit: `LTX2_CHECKPOINT_ROOT` is set in no workflow (#673), so CI exercises about 5.7% of `test_ltx2_video`'s assertions and the shipped-checkpoint subcase SKIPS. This row's own claims are not in that gated set, but the checkpoint-derived evidence in this campaign is host-local. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5[1m] [claude-code] --- .agents/claims/CLAIM-MODEL-DOTS3-NOTE-W0.md | 5 + .agents/claims/CLAIM-MODEL-MUSIC3-W0.md | 5 + .agents/claims/CLAIM-ROCM-DECODE-ATTN-D128.md | 5 + .agents/engine-matrix.md | 11 +- .agents/model-matrix.md | 68 +- .agents/oracles/diffusers.md | 46 +- .agents/oracles/sglang-omni.md | 44 + .agents/oracles/sglang.md | 15 +- .agents/porting-inventory.md | 89 +- .agents/roadmap_v1.md | 31 +- .agents/specs/dots3-note.md | 513 ++++ .agents/specs/gate-pr-size-binary.md | 178 ++ .agents/specs/indextts-2-5.md | 420 +++ .agents/specs/ltx25-image-conditioning.md | 66 +- .agents/specs/ltx25-prompt-adaln.md | 361 +++ .agents/specs/mamba2-ssd.md | 637 ++++- .agents/specs/minimax-music3.md | 386 +++ .agents/specs/mm-serving.md | 14 +- .agents/specs/multimodal-track.md | 132 + .agents/specs/nemotron-h-model.md | 471 +++- .../perf-chunked-prefill-budget-2026-08-13.md | 326 +++ .agents/specs/qwen38-text-only.md | 436 +++ .agents/specs/rocm-decode-attn-d128.md | 330 +++ .agents/specs/rocm-decode-graph.md | 120 +- .agents/specs/tool-parser-breadth.md | 468 ++- .agents/specs/upstream-omni-pin.md | 171 ++ .agents/specs/windows-msvc-m-pi.md | 149 + .agents/upstream-sync.md | 22 + .github/workflows/ci.yml | 20 +- AGENTS.md | 3 +- CMakeLists.txt | 26 + docs/BENCHMARKS.md | 2 + docs/BUILD.md | 4 +- docs/FEATURES.md | 24 +- docs/STATUS.md | 51 +- docs/USAGE.md | 228 +- include/vllm/config/multimodal.h | 92 + .../tool_parsers/parser_engine_adapter.h | 126 + include/vllm/model_executor/models/adaln.h | 45 + include/vllm/model_executor/models/campplus.h | 181 ++ include/vllm/model_executor/models/cfm.h | 40 + include/vllm/model_executor/models/dit.h | 78 + .../vllm/model_executor/models/dit_front.h | 63 + include/vllm/model_executor/models/dit_skip.h | 55 + .../vllm/model_executor/models/dit_stack.h | 60 + include/vllm/model_executor/models/dit_tail.h | 70 + include/vllm/model_executor/models/fvq.h | 53 + include/vllm/model_executor/models/gpt2.h | 90 + .../vllm/model_executor/models/indextts2.h | 29 + .../model_executor/models/indextts2_config.h | 57 + .../models/indextts2_pipeline.h | 68 + .../models/indextts2_s2mel_loader.h | 45 + include/vllm/model_executor/models/lenreg.h | 38 + include/vllm/model_executor/models/ltx2.h | 51 +- .../vllm/model_executor/models/ltx2_loader.h | 45 +- .../model_executor/models/ltx2_video_vae.h | 2 +- .../models/ltx2_video_vae_encoder.h | 4 +- .../vllm/model_executor/models/minimax_h3.h | 97 +- .../models/minimax_music3_loader.h | 490 ++++ .../model_executor/models/qwen3_5_dense.h | 17 +- .../model_executor/models/qwen3_5_weights.h | 60 +- include/vllm/model_executor/models/talker.h | 41 + .../vllm/model_executor/models/vocoder1d.h | 114 + include/vllm/model_executor/models/vocos.h | 49 + include/vllm/model_executor/models/w2vbert.h | 134 + include/vllm/model_executor/models/wavenet.h | 62 + include/vllm/multimodal/ltx2_video.h | 17 +- include/vllm/multimodal/processing/context.h | 112 + include/vllm/multimodal/speech_engine.h | 124 + include/vllm/parser/engine/parser_engine.h | 10 + include/vllm/v1/engine/input_processor.h | 20 +- include/vllm/v1/engine/validation_error.h | 50 + include/vt/merged_gemm.h | 15 + include/vt/ops.h | 51 +- scripts/check-agent-record.py | 60 +- scripts/check-pr-size.py | 32 +- scripts/convert-indextts2-checkpoint.py | 98 + scripts/gen-adaln-goldens.py | 97 + scripts/gen-campplus-goldens.py | 162 ++ scripts/gen-cfm-goldens.py | 113 + scripts/gen-dit-front-goldens.py | 206 ++ scripts/gen-dit-goldens.py | 137 + scripts/gen-dit-skip-schedule.py | 76 + scripts/gen-dit-stack-goldens.py | 154 + scripts/gen-dit-tail-goldens.py | 204 ++ scripts/gen-fvq-goldens.py | 96 + scripts/gen-gpt2-goldens.py | 293 ++ scripts/gen-lenreg-goldens.py | 93 + scripts/gen-ltx2-goldens.py | 151 +- scripts/gen-minimax-music3-manifest.py | 161 ++ scripts/gen-vocos-goldens.py | 103 + scripts/gen-w2vbert-goldens.py | 165 ++ scripts/gen-wavenet-goldens.py | 183 ++ scripts/read-torch-manifest.py | 121 + scripts/runner-routing-allowlist.txt | 1 + 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/model_executor/models/adaln.cpp | 129 + src/vllm/model_executor/models/campplus.cpp | 467 +++ src/vllm/model_executor/models/cfm.cpp | 68 + src/vllm/model_executor/models/dit.cpp | 202 ++ src/vllm/model_executor/models/dit_front.cpp | 87 + src/vllm/model_executor/models/dit_skip.cpp | 86 + src/vllm/model_executor/models/dit_stack.cpp | 67 + src/vllm/model_executor/models/dit_tail.cpp | 144 + src/vllm/model_executor/models/fvq.cpp | 107 + .../model_executor/models/gemma4_weights.cpp | 11 +- src/vllm/model_executor/models/gpt2.cpp | 257 ++ src/vllm/model_executor/models/indextts2.cpp | 61 + .../models/indextts2_pipeline.cpp | 175 ++ .../models/indextts2_s2mel_loader.cpp | 193 ++ .../models/internlm2_weights.cpp | 24 +- .../model_executor/models/laguna_weights.cpp | 7 +- src/vllm/model_executor/models/lenreg.cpp | 79 + src/vllm/model_executor/models/ltx2.cpp | 25 +- .../model_executor/models/ltx2_audio_vae.cpp | 55 +- .../model_executor/models/ltx2_device.cpp | 79 +- src/vllm/model_executor/models/ltx2_dit.cpp | 90 +- .../model_executor/models/ltx2_loader.cpp | 67 +- .../model_executor/models/ltx2_video_vae.cpp | 4 - .../models/ltx2_video_vae_encoder_load.cpp | 44 +- .../models/minimax_h3_audio_vae.cpp | 311 +- .../models/minimax_h3_vae_loader.cpp | 3 +- .../models/minimax_music3_loader.cpp | 896 ++++++ src/vllm/model_executor/models/nemotron_h.cpp | 838 ++++++ .../models/nemotron_h_forward.h | 279 ++ .../models/nemotron_h_registry.cpp | 32 +- .../model_executor/models/olmo2_weights.cpp | 24 +- .../models/parakeet_weights.cpp | 7 +- .../model_executor/models/phi_weights.cpp | 26 +- .../model_executor/models/qwen3_5_common.h | 18 + .../model_executor/models/qwen3_5_dense.cpp | 13 + .../models/qwen3_5_dense_weights.cpp | 47 +- .../model_executor/models/qwen3_5_moe.cpp | 16 + .../model_executor/models/qwen3_5_weights.cpp | 182 +- .../models/qwen3_dspark_weights.cpp | 8 +- src/vllm/model_executor/models/talker.cpp | 59 + src/vllm/model_executor/models/vocoder1d.cpp | 307 ++ src/vllm/model_executor/models/vocos.cpp | 137 + src/vllm/model_executor/models/w2vbert.cpp | 276 ++ src/vllm/model_executor/models/wavenet.cpp | 183 ++ src/vllm/multimodal/ltx2_video.cpp | 145 +- src/vllm/multimodal/processing/context.cpp | 107 + src/vllm/multimodal/speech_engine.cpp | 88 + src/vllm/multimodal/video_engine.cpp | 42 +- src/vllm/parser/engine/parser_engine.cpp | 24 + src/vllm/platforms/rocm.cpp | 10 +- src/vt/cpu/cpu_ops.cpp | 37 +- src/vt/cuda/cuda_gdn.cu | 22 + src/vt/cuda/cuda_mamba2_ssd.cuh | 692 +++++ src/vt/cuda/cuda_moe.cu | 101 +- src/vt/op_provider.cpp | 2 + src/vt/ops.cpp | 15 +- src/vt/rocm/rocm_backend.hip | 98 +- tests/CMakeLists.txt | 71 + .../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 tests/scripts/test_agent_record.py | 179 +- tests/scripts/test_check_pr_size.py | 89 +- .../scripts/test_indextts2_config_contract.py | 93 + tests/scripts/test_indextts2_convert.py | 117 + .../test_indextts2_emotion_arch_covered.py | 118 + tests/scripts/test_indextts2_pth_manifest.py | 124 + tests/scripts/test_vocoder1d_single_home.py | 114 + tests/support/test_env.h | 75 + tests/vllm/config/test_multimodal_config.cpp | 120 + .../openai/tool_parsers/test_detect.cpp | 3 +- .../openai/tool_parsers/test_inkling.cpp | 581 ++++ tests/vllm/models/adaln_goldens.inc | 67 + tests/vllm/models/campplus_goldens.inc | 2511 +++++++++++++++++ tests/vllm/models/cfm_goldens.inc | 54 + tests/vllm/models/dit_front_goldens.inc | 81 + tests/vllm/models/dit_goldens.inc | 141 + tests/vllm/models/dit_stack_goldens.inc | 111 + tests/vllm/models/dit_tail_goldens.inc | 78 + .../fixtures/qwen3_8_2_4t_a95b/config.json | 142 + tests/vllm/models/fvq_goldens.inc | 75 + tests/vllm/models/gpt2_goldens.inc | 91 + tests/vllm/models/indextts2_config.yaml | 115 + tests/vllm/models/indextts2_pth_manifest.json | 1261 +++++++++ .../models/indextts2_qwen_emo_manifest.json | 73 + tests/vllm/models/lenreg_goldens.inc | 123 + tests/vllm/models/ltx2_goldens.inc | 432 ++- tests/vllm/models/minimax_music3_manifest.inc | 1054 +++++++ tests/vllm/models/test_adaln.cpp | 131 + tests/vllm/models/test_campplus.cpp | 298 ++ tests/vllm/models/test_cfm.cpp | 90 + tests/vllm/models/test_dit.cpp | 149 + tests/vllm/models/test_dit_front.cpp | 159 ++ tests/vllm/models/test_dit_skip.cpp | 121 + tests/vllm/models/test_dit_stack.cpp | 118 + tests/vllm/models/test_dit_tail.cpp | 192 ++ tests/vllm/models/test_fvq.cpp | 97 + tests/vllm/models/test_gpt2.cpp | 206 ++ tests/vllm/models/test_indextts2_family.cpp | 89 + tests/vllm/models/test_indextts2_pipeline.cpp | 177 ++ .../models/test_indextts2_s2mel_loader.cpp | 319 +++ tests/vllm/models/test_lenreg.cpp | 85 + 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/models/test_ltx2_vae.cpp | 5 +- tests/vllm/models/test_minimax_h3.cpp | 9 +- .../models/test_minimax_music3_loader.cpp | 981 +++++++ tests/vllm/models/test_model_registry.cpp | 40 +- tests/vllm/models/test_nemotron_h_forward.cpp | 1467 ++++++++++ .../vllm/models/test_nemotron_h_scaffold.cpp | 34 +- tests/vllm/models/test_qwen3_8_text_only.cpp | 1123 ++++++++ tests/vllm/models/test_talker.cpp | 84 + tests/vllm/models/test_vocoder1d.cpp | 187 ++ tests/vllm/models/test_vocos.cpp | 94 + tests/vllm/models/test_w2vbert.cpp | 290 ++ tests/vllm/models/test_wavenet.cpp | 126 + tests/vllm/models/vocos_goldens.inc | 89 + tests/vllm/models/w2vbert_goldens.inc | 438 +++ tests/vllm/models/wavenet_goldens.inc | 74 + tests/vllm/multimodal/ltx2_video_fixture.h | 9 +- .../vllm/multimodal/test_ltx2_image_cond.cpp | 26 +- tests/vllm/multimodal/test_ltx2_video.cpp | 133 +- .../multimodal/test_processing_limits.cpp | 398 +++ tests/vllm/multimodal/test_speech_engine.cpp | 165 ++ tests/vllm/multimodal/test_video_engine.cpp | 46 + tests/vllm/test_model_loader_gguf.cpp | 3 +- tests/vllm/v1/test_scheduler.cpp | 106 + tests/vt/test_backend_cross_device.cpp | 7 +- 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 ++++ tests/vt/test_ops_moe_nongated_relu2.cpp | 606 ++++ tests/vt/test_rocm_backend.cpp | 160 ++ tools/oracle/README.md | 108 + tools/oracle/music3_oracle.py | 737 +++++ website/README.md | 9 +- 246 files changed, 38314 insertions(+), 973 deletions(-) create mode 100644 .agents/claims/CLAIM-MODEL-DOTS3-NOTE-W0.md create mode 100644 .agents/claims/CLAIM-MODEL-MUSIC3-W0.md create mode 100644 .agents/claims/CLAIM-ROCM-DECODE-ATTN-D128.md create mode 100644 .agents/oracles/sglang-omni.md create mode 100644 .agents/specs/dots3-note.md create mode 100644 .agents/specs/gate-pr-size-binary.md create mode 100644 .agents/specs/indextts-2-5.md create mode 100644 .agents/specs/ltx25-prompt-adaln.md create mode 100644 .agents/specs/minimax-music3.md create mode 100644 .agents/specs/perf-chunked-prefill-budget-2026-08-13.md create mode 100644 .agents/specs/qwen38-text-only.md create mode 100644 .agents/specs/rocm-decode-attn-d128.md create mode 100644 .agents/specs/upstream-omni-pin.md create mode 100644 .agents/specs/windows-msvc-m-pi.md create mode 100644 include/vllm/config/multimodal.h create mode 100644 include/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.h create mode 100644 include/vllm/model_executor/models/adaln.h create mode 100644 include/vllm/model_executor/models/campplus.h create mode 100644 include/vllm/model_executor/models/cfm.h create mode 100644 include/vllm/model_executor/models/dit.h create mode 100644 include/vllm/model_executor/models/dit_front.h create mode 100644 include/vllm/model_executor/models/dit_skip.h create mode 100644 include/vllm/model_executor/models/dit_stack.h create mode 100644 include/vllm/model_executor/models/dit_tail.h create mode 100644 include/vllm/model_executor/models/fvq.h 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/indextts2_config.h create mode 100644 include/vllm/model_executor/models/indextts2_pipeline.h create mode 100644 include/vllm/model_executor/models/indextts2_s2mel_loader.h create mode 100644 include/vllm/model_executor/models/lenreg.h create mode 100644 include/vllm/model_executor/models/minimax_music3_loader.h create mode 100644 include/vllm/model_executor/models/talker.h create mode 100644 include/vllm/model_executor/models/vocoder1d.h create mode 100644 include/vllm/model_executor/models/vocos.h create mode 100644 include/vllm/model_executor/models/w2vbert.h create mode 100644 include/vllm/model_executor/models/wavenet.h create mode 100644 include/vllm/multimodal/processing/context.h create mode 100644 include/vllm/multimodal/speech_engine.h create mode 100644 include/vllm/v1/engine/validation_error.h create mode 100644 scripts/convert-indextts2-checkpoint.py create mode 100644 scripts/gen-adaln-goldens.py create mode 100644 scripts/gen-campplus-goldens.py create mode 100644 scripts/gen-cfm-goldens.py create mode 100644 scripts/gen-dit-front-goldens.py create mode 100644 scripts/gen-dit-goldens.py create mode 100644 scripts/gen-dit-skip-schedule.py create mode 100644 scripts/gen-dit-stack-goldens.py create mode 100644 scripts/gen-dit-tail-goldens.py create mode 100644 scripts/gen-fvq-goldens.py create mode 100644 scripts/gen-gpt2-goldens.py create mode 100644 scripts/gen-lenreg-goldens.py create mode 100644 scripts/gen-minimax-music3-manifest.py create mode 100644 scripts/gen-vocos-goldens.py create mode 100644 scripts/gen-w2vbert-goldens.py create mode 100644 scripts/gen-wavenet-goldens.py create mode 100644 scripts/read-torch-manifest.py create mode 100644 src/vllm/entrypoints/openai/tool_parsers/parser_engine_adapter.cpp create mode 100644 src/vllm/model_executor/models/adaln.cpp create mode 100644 src/vllm/model_executor/models/campplus.cpp create mode 100644 src/vllm/model_executor/models/cfm.cpp create mode 100644 src/vllm/model_executor/models/dit.cpp create mode 100644 src/vllm/model_executor/models/dit_front.cpp create mode 100644 src/vllm/model_executor/models/dit_skip.cpp create mode 100644 src/vllm/model_executor/models/dit_stack.cpp create mode 100644 src/vllm/model_executor/models/dit_tail.cpp create mode 100644 src/vllm/model_executor/models/fvq.cpp 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/indextts2_pipeline.cpp create mode 100644 src/vllm/model_executor/models/indextts2_s2mel_loader.cpp create mode 100644 src/vllm/model_executor/models/lenreg.cpp create mode 100644 src/vllm/model_executor/models/minimax_music3_loader.cpp create mode 100644 src/vllm/model_executor/models/nemotron_h.cpp create mode 100644 src/vllm/model_executor/models/nemotron_h_forward.h create mode 100644 src/vllm/model_executor/models/talker.cpp create mode 100644 src/vllm/model_executor/models/vocoder1d.cpp create mode 100644 src/vllm/model_executor/models/vocos.cpp create mode 100644 src/vllm/model_executor/models/w2vbert.cpp create mode 100644 src/vllm/model_executor/models/wavenet.cpp create mode 100644 src/vllm/multimodal/processing/context.cpp create mode 100644 src/vllm/multimodal/speech_engine.cpp create mode 100644 src/vt/cuda/cuda_mamba2_ssd.cuh 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 tests/scripts/test_indextts2_config_contract.py create mode 100644 tests/scripts/test_indextts2_convert.py create mode 100644 tests/scripts/test_indextts2_emotion_arch_covered.py create mode 100644 tests/scripts/test_indextts2_pth_manifest.py create mode 100644 tests/scripts/test_vocoder1d_single_home.py create mode 100644 tests/support/test_env.h create mode 100644 tests/vllm/config/test_multimodal_config.cpp create mode 100644 tests/vllm/entrypoints/openai/tool_parsers/test_inkling.cpp create mode 100644 tests/vllm/models/adaln_goldens.inc create mode 100644 tests/vllm/models/campplus_goldens.inc create mode 100644 tests/vllm/models/cfm_goldens.inc create mode 100644 tests/vllm/models/dit_front_goldens.inc create mode 100644 tests/vllm/models/dit_goldens.inc create mode 100644 tests/vllm/models/dit_stack_goldens.inc create mode 100644 tests/vllm/models/dit_tail_goldens.inc create mode 100644 tests/vllm/models/fixtures/qwen3_8_2_4t_a95b/config.json create mode 100644 tests/vllm/models/fvq_goldens.inc create mode 100644 tests/vllm/models/gpt2_goldens.inc create mode 100644 tests/vllm/models/indextts2_config.yaml create mode 100644 tests/vllm/models/indextts2_pth_manifest.json create mode 100644 tests/vllm/models/indextts2_qwen_emo_manifest.json create mode 100644 tests/vllm/models/lenreg_goldens.inc create mode 100644 tests/vllm/models/minimax_music3_manifest.inc create mode 100644 tests/vllm/models/test_adaln.cpp create mode 100644 tests/vllm/models/test_campplus.cpp create mode 100644 tests/vllm/models/test_cfm.cpp create mode 100644 tests/vllm/models/test_dit.cpp create mode 100644 tests/vllm/models/test_dit_front.cpp create mode 100644 tests/vllm/models/test_dit_skip.cpp create mode 100644 tests/vllm/models/test_dit_stack.cpp create mode 100644 tests/vllm/models/test_dit_tail.cpp create mode 100644 tests/vllm/models/test_fvq.cpp 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_indextts2_pipeline.cpp create mode 100644 tests/vllm/models/test_indextts2_s2mel_loader.cpp create mode 100644 tests/vllm/models/test_lenreg.cpp create mode 100644 tests/vllm/models/test_minimax_music3_loader.cpp create mode 100644 tests/vllm/models/test_nemotron_h_forward.cpp create mode 100644 tests/vllm/models/test_qwen3_8_text_only.cpp create mode 100644 tests/vllm/models/test_talker.cpp create mode 100644 tests/vllm/models/test_vocoder1d.cpp create mode 100644 tests/vllm/models/test_vocos.cpp create mode 100644 tests/vllm/models/test_w2vbert.cpp create mode 100644 tests/vllm/models/test_wavenet.cpp create mode 100644 tests/vllm/models/vocos_goldens.inc create mode 100644 tests/vllm/models/w2vbert_goldens.inc create mode 100644 tests/vllm/models/wavenet_goldens.inc create mode 100644 tests/vllm/multimodal/test_processing_limits.cpp create mode 100644 tests/vllm/multimodal/test_speech_engine.cpp create mode 100644 tests/vt/test_ops_moe_nongated_relu2.cpp create mode 100644 tools/oracle/README.md create mode 100644 tools/oracle/music3_oracle.py diff --git a/.agents/claims/CLAIM-MODEL-DOTS3-NOTE-W0.md b/.agents/claims/CLAIM-MODEL-DOTS3-NOTE-W0.md new file mode 100644 index 000000000..d1f665e76 --- /dev/null +++ b/.agents/claims/CLAIM-MODEL-DOTS3-NOTE-W0.md @@ -0,0 +1,5 @@ +# CLAIM-MODEL-DOTS3-NOTE-W0 + +| Claim | Row IDs | Agent | Worktree / remote dir | Branch | Owned scope | State | Last update | +|---|---|---|---|---|---|---|---| +| `CLAIM-MODEL-DOTS3-NOTE-W0` | `MODEL-MM-dots3-note-dots3-note-for-causal-lm` (`SPIKE`) | Claude Code (opus-5), operator role | isolated worktree `/home/mudler/_git/wt-dots3-note`; oracle checkout read-only at `${VLLM_SOURCE}` = `/home/mudler/_git/vllm` (fetched to `origin/main` for the beyond-pin read, pin itself untouched) | `row/MODEL-MM-dots3-note-dots3-note-for-causal-lm`, issue [#699](https://github.com/mudler/vllm.cpp/issues/699) | Owns: NEW `.agents/specs/dots3-note.md`; the two dots3 rows in `.agents/model-matrix.md` (the `MODEL-MM` row at `SPIKE` and the `MODEL-SPEC-dots3-note-dots3-note-mtp` row left `INVENTORIED` and therefore not listed as a claimed row above, the engaged-architecture checklist entry, the rollup counts `373 -> 375` and the beyond-pin ratchet `369 -> 371`); the matching `MODEL` ratchet `373 -> 375` in `scripts/check-agent-record.py`; the `#699` row in `.agents/roadmap_v1.md`; the dots3 lines in `docs/FEATURES.md`. 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 any pin advance: this row is beyond-pin and says so, but advancing `555967922` is a sync cycle of its own and is not claimed here | `ACTIVE` | 2026-08-14 — developer-directed scope ("open the issue and write the spec, and merge it directly and add to roadmap. Use 192.168.68.23 as cuda host to run verifications e2e"), which records merge authority for W0 and designates Thor as the e2e CUDA host. W0 spec committed. **The row is BLOCKED on spec §6.4**, a developer decision between renting 8xH100, accepting unit-gated bricks with the e2e gate recorded as owed, or parking. W0.5 (provision Thor: no nvcc, no cmake, no ninja, no venv as probed 2026-08-14) is the first dispatchable task if option B is chosen. No W1 dispatch until then. | 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/claims/CLAIM-ROCM-DECODE-ATTN-D128.md b/.agents/claims/CLAIM-ROCM-DECODE-ATTN-D128.md new file mode 100644 index 000000000..b962d0dc4 --- /dev/null +++ b/.agents/claims/CLAIM-ROCM-DECODE-ATTN-D128.md @@ -0,0 +1,5 @@ +# CLAIM-ROCM-DECODE-ATTN-D128 + +| Claim | Row IDs | Agent | Worktree / remote dir | Branch | Owned scope | State | Last update | +|---|---|---|---|---|---|---|---| +| `CLAIM-ROCM-DECODE-ATTN-D128` | `BACKEND-ROCM` (`ACTIVE`) | Claude Code (sonnet-5), helper role | worktree `rdna3-kernel-porting-b9ec47`, real gfx1200 hardware (AMD Radeon RX 9060 XT, RDNA4, 32 CU), `$GPU_LOCK` respected | `row/ROCM-DECODE-ATTN-D128-SPEC` (this spec; the implementation follows on `row/ROCM-DECODE-ATTN-D128-IMPL`, stacked), base `main` `fafa16f0`; issue [#382](https://github.com/mudler/vllm.cpp/issues/382) (the ROCm half; the CUDA half landed as [PR #425](https://github.com/mudler/vllm.cpp/pull/425), `66399617`), motivated by [#488](https://github.com/mudler/vllm.cpp/issues/488). NOTE: #382 is filed against the cross-backend kernel row (state `ANCHOR-BACKFILL`), while this claim's Row ID is the `ACTIVE` backend row whose code it edits — `check-agent-record` requires an active claim to name a `SPIKE`/`ACTIVE` row, so the two deliberately differ | Owns ONLY: the `LoadRowEplBf16`/`StoreRowEplBf16` `EPL=4` case, the `VT_ATTN_DECODE_D128` gate (default OFF, same flag/default/reason as the merged CUDA arm), the `bf16_decode_opt`/`decode_gqa` gate extensions and the two `d==128` launch-dispatch branches in `src/vt/rocm/rocm_paged_attn.hip`; the new "Qwen3 geometry (bf16, GQA 2, head_dim 128)" case in `tests/vt/test_backend_cross_device.cpp` and its two flag-on ctest registrations in `tests/CMakeLists.txt`; `.agents/specs/rocm-decode-attn-d128.md` and this claim file. **NON-COLLISION:** disjoint from `CLAIM-ROCM-SKINNY-GEMM-GFX1200` (different files: `rocm_skinny_gemm.hip`/`rocm_matmul_hipblaslt.hip` vs `rocm_paged_attn.hip`), not stacked on any other branch. EXCLUDED: **the flip to default-ON on either backend** (owes the near-tie razor + distributional gate + golden regen, and per the spec §5 cross-arch reversal must be argued per backend — this is what keeps #382 open), rocWMMA for `d=128` (separate claim, separate spec, separate issue), `qg=4`/`qg=8` GQA fusion at any `d` (pre-existing, board-independent gap), any `d=128` prefill path, and the 8 pre-existing unrelated `ctest` failures (`vt: no kernel for op 63 on device type 5`) | `ACTIVE` | 2026-08-12 — **reconciled against the existing record before landing**, per the re-verify-before-claiming rule: #382 already named this exact defect and PR #425 had already merged the CUDA half, so this became a mirror of merged work rather than new design, and was re-gated from default-ON to **default OFF behind `VT_ATTN_DECODE_D128`** — the merged arm's own flag, default and stated reason (warp-strided online softmax reduces the KV sequence in a different order, so a greedy anchor can move at an exact bf16 tie; OFF keeps every golden byte-identical). gfx1200-verified: `ctest -R 'rocm\|cross_device'` **6/6** including two new flag-on registrations (verified non-vacuous: 1 case, 6 assertions, not zero); full `ctest` 385/393 with the 8 failures independently confirmed pre-existing. Gate exercised **both directions on one binary** — Qwen3-0.6B @1024 ctx TPOT 44.82/44.82 ms OFF vs 12.80/12.60 ms ON = **3.53x**; decode throughput +42.7% / +25.0% / +17.8% on 0.6B / 1.7B / 4B. **Carried finding:** #382 measured this same `EPL=4` arm **1.6x slower** on sm_110 where we measure it 3.5x faster — recorded, not reconciled; it is why the default-ON flip must be argued per backend. Rebased from `bbc482a2` onto `main` `fafa16f0` (167 commits), which required reformatting `Assisted-by` for the `check-commit-trailers` gate that landed in between, and de-linking §7's forward reference to the rocWMMA spec — that spec now lands on its own branch, so a markdown link to it fails `check-agent-record` as a dangling link. Spec content otherwise byte-identical. Re-gated on the new base, gfx1200: build 783/783, `ctest -R 'rocm\|cross_device'` 6/6, the new case non-vacuous under both flags (1 case, 6 assertions), full `ctest` with 8 pre-existing `kSharedExpertGate` (`OpId(63)`) failures owed to unmerged PR #509. `agent-preflight` fails 11, set-identical to a clean `fafa16f0` baseline. Spec PR open; implementation PR follows. | diff --git a/.agents/engine-matrix.md b/.agents/engine-matrix.md index 33d9885d6..8c9bdca94 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 | +| 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** | **16** | **4** | **9** | **30** | **8** | **11** | **39** | +| **Total** | **154** | **35** | **17** | **4** | **10** | **30** | **8** | **11** | **38** | ## Engine core and scheduling @@ -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` | @@ -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: **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 @@ -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/model-matrix.md b/.agents/model-matrix.md index e63964f10..d614e746b 100644 --- a/.agents/model-matrix.md +++ b/.agents/model-matrix.md @@ -19,20 +19,43 @@ 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 18 rows that the pinned +registry does not contain = 373 architecture rows.** Those 18 are, by why they +are not at the pin: `KimiK3ForConditionalGeneration` and +`MuseGlimmerForConditionalGeneration`, both released after the pin; +`Qwen3_5ForCausalLM` and `Qwen3_5MoeForCausalLM`, the text-only Qwen3.5 arms +upstream added after the pin in PR vllm#50210 (#490); +`Dots3NoteForCausalLM` and its speculative head `Dots3NoteMTPModel`, added to +vLLM `main` after the pin by +[vllm#51255](https://github.com/vllm-project/vllm/pull/51255) and still being +patched there (#699); +`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); `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` 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). 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 **377 rows**, which is those 373 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 377, 53 are past `INVENTORIED` (engaged) and the remaining 324 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 @@ -52,17 +75,17 @@ Rollup by lifecycle state (must equal the detailed per-state row counts): | State | Rows | |---|---| -| INVENTORIED | 321 | -| PARTIAL | 20 | -| ACTIVE | 9 | -| SPIKE | 8 | +| INVENTORIED | 324 | +| PARTIAL | 22 | +| ACTIVE | 10 | +| SPIKE | 9 | | BLOCKED | 5 | | DONE | 3 | | READY | 3 | | GATING | 1 | -| **Total** | **370** | +| **Total** | **377** | -Engaged architectures (the 48 non-`INVENTORIED` rows): +Engaged architectures (the 53 non-`INVENTORIED` rows): | Support | Architecture | Family / example | Status | Row | |---|---|---|---|---| @@ -70,6 +93,8 @@ Engaged architectures (the 48 non-`INVENTORIED` rows): | ✅ | `Qwen3MoeForCausalLM` | Qwen3-Coder-30B-A3B (MoE) | STRICT token-exact 6/6 vs vLLM 0.25.0; 11/16 speed-grid cells at/above graphed vLLM, c1/c2 residual | `MODEL-TEXT-qwen3-moe-qwen3-moe-for-causal-lm` | | ✅ | `Qwen3_5ForConditionalGeneration` | Qwen3.6-27B (text path) | text-gen STRICT token-exact 235/235 vs vLLM 0.25.0; mm INPUT pipeline (M0/M1) landed + processor-parity gate PASS; **M3-W0 landed** (vision-inclusive checkpoint `Qwen/Qwen3.6-27B` 51.7 GiB bf16 with 333 `visual.*` FOUND+fits+downloaded; 27B vision config resolved — depth 27/out 5120/**EMPTY deepstack**; MRoPE `[11,11,10]`/rot 64/theta 1e7; the bf16 GDN-hybrid loader ALREADY handles it). **M3-b LANDED 2026-07-25: image→text STRICT token-exact 32/32 vs vLLM 0.25.0** — Qwen3.6-27B image understanding works end-to-end (forked GDN-hybrid VL forward gated on mm input ⇒ text byte-identical; 27B/35B/Coder inertness re-passed 235/315/138). **M3d LANDED 2026-07-25: video→text STRICT token-exact 32/32 vs vLLM 0.25.0** — video works end-to-end too (`Qwen3_5VLGenerateGreedyVideo` reuses the M3c processor/windowed-tower/video-MRoPE on the GDN-hybrid backbone). **Qwen video modalities COMPLETE: image+video both work e2e** (audio N/A for Qwen). **VISION-FORWARD SPEED (2026-07-28, `CLAIM-MM-SPEED-QWEN-IMAGE`, multimodal-speed.md §16): the mm-forward tower BEATS vLLM** — per-image tower forward 142.3 ms (flash `AttentionDenseFlash`, hd-72) vs vLLM 0.25.0 ~250 ms eager encode = 0.57×; attribution-first nsys REFUTED a bigger lever (the t=784 vision attention is serial-latency-bound, flash only 1.04× over warp), STRICT 32/32 image/video HELD + goldens md5 unchanged. Row stays `PARTIAL` — vision-forward speed BEATS vLLM; **umbrella speed pending** on batched c2+/serving | `MODEL-MM-qwen3-5-qwen3-5-for-conditional-generation` | | ✅ | `Qwen3_5MoeForConditionalGeneration` | Qwen3.6-35B-A3B (text path) | text-gen STRICT token-exact 315/315 vs vLLM 0.25.0; mm INPUT pipeline (M0/M1) landed + processor-parity gate PASS, vision tower pending (M2/M3) so the row is `PARTIAL` (text-only) | `MODEL-MM-qwen3-5-qwen3-5-moe-for-conditional-generation` | +| 🚧 | `Qwen3_5ForCausalLM` | Qwen3.5 text-only dense arm (no checkpoint fits this box) | **REGISTERED, NOT RUN-GATED (2026-08-12, issue #490).** Ahead-of-pin forward port of upstream PR vllm#50210 (`ad5d29db7`): additive registration against the EXISTING dense factory + one backbone weight-namespace decision per checkpoint (`model.` vs `model.language_model.`, mixed index REFUSED). Dispatch, flat-config and namespace tests green; 27B/35B/Coder inert (VL prefix is still the seam default, goldens md5 unchanged). **NO token-exact oracle run and NO speed number — the run gate is OWED**, and no text-only Qwen3.5 checkpoint fits GB10. Unlike the MoE arm, this loader DOES route BF16/FP8/NVFP4 per projection by tensor presence, so a flat bf16 checkpoint is expected to load | `MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm` | +| 🚧 | `Qwen3_5MoeForCausalLM` | `Qwen/Qwen3.8-2.4T-A95B` (2.4T MoE — DOES NOT FIT this hardware) | **REGISTERED, NOT RUN-GATED (2026-08-12, issue #490).** Same ahead-of-pin port against the EXISTING MoE factory: 3.8 is the token-exact Qwen3.6-35B-A3B GDN-hybrid MoE backbone at larger scale, every knob config-driven, with the BACKBONE weight names identical modulo the backbone prefix. **CORRECTED 2026-08-12 — REGISTRATION IS NOT LOADABILITY.** This loader reads ONLY per-expert NVFP4 routed experts; the published `Qwen/Qwen3.8-2.4T-A95B` and `Qwen/Qwen3.6-35B-A3B` indices ship 3-D STACKED experts and ZERO `weight_scale`/`input_scale` (read live 2026-08-12), and our gated 35B row reads the REQUANTIZED `nvidia/Qwen3.6-35B-A3B-NVFP4` — so **the bf16/stacked MoE routed-expert arm is NOT implemented, is OWED, and such a checkpoint is REFUSED by a message naming it**. **NO token-exact oracle run and NO speed number — the run gate is OWED** (2.4T bf16 ≈ 4.8 TB, FP8 ≈ 2.4 TB vs 128 GB unified) and does NOT close on a fitting checkpoint alone: it needs one whose routed experts are per-expert NVFP4, or the owed arm implemented first. Also owed: MTP and GGUF arms for 3.8. The QUANTIZED (NVFP4) arm is the one that IS implemented | `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` | | 🚧 | `Qwen3VLForConditionalGeneration` | Qwen3-VL-4B-Instruct (image + video vehicle) | **IMAGE e2e WORKING (M2 CLOSED): STRICT image→text token-exact 32/32 vs vLLM 0.25.0** (full pipeline: C++ processor→M2a tower→merge→forked MRoPE/DeepStack greedy decode); correctness complete, **speed pending**. Underpinned by mm processor (M1), vision tower (M2a faithful), M2b/M2c numeric contracts (85/85). **VIDEO (M3c 2026-07-25): preprocessing + full wiring LANDED + unit-gated** — video-processor pixel_values_videos BIT-exact 41/41 (RED-first), video MRoPE positions BIT-exact vs vLLM, per-frame windowed tower faithful (rel-L2 0.072); video e2e **NEAR-TIE-ROBUST PASS** (gate form chosen BY MEASUREMENT 2026-07-25): teacher-forcing vLLM 0.25.0 on OUR exact tokens proves the sole divergence is ONE genuine bf16 near-tie at tok22 (gap **0.125 nats**, our token vLLM's 2nd of 4 tied choices) with every downstream token == vLLM's teacher-forced argmax at gap **0.0** — the tower already accumulates in f32 everywhere (matches vLLM), so the residual is the irreducible bf16 envelope, NOT a fixable numeric gap. VIDEO understanding now WORKS e2e (correctness complete, **speed pending**); image e2e STRICT 32/32 unchanged (the deterministic strict-pass proof, no regression). **VISION-FORWARD SPEED (2026-07-28, `CLAIM-MM-SPEED-QWEN-IMAGE`, multimodal-speed.md §16): the shared vision tower now defaults to the flash-tiled `AttentionDenseFlash` (byte-identical to warp; STRICT image 32/32 HELD, nsys default 4B e2e = 24 flash-kernel inst/zero warp, goldens md5 unchanged); the tower already BEATS vLLM (~250 ms eager encode) — the profile REFUTED a bigger lever.** **OpenAI SERVING (2026-07-28, `CLAIM-MM-SERVING-W1`, [mm-serving.md](specs/mm-serving.md)): first CPU brick landed — the chat request now parses OpenAI multimodal content parts (`image_url`/`input_audio`/`audio_url`) + decodes base64/data-URI + routes to the EXISTING processor → `MultiModalInputs` (gate `test_chat_mm` 5/5, CPU no-weights). This model is the named `MM-SERVE-E2E` closing gate: a real image+prompt OpenAI `/v1/chat/completions` request → token-correct output vs the mm oracle (needs the engine mm plumbing `MM-SERVE-ENGINE` + DGX). Not yet wired into the OpenAI server end-to-end.** **SERVING W2 (2026-07-28, `CLAIM-MM-SERVING-W2`): `MM-SERVE-ENGINE` DONE** — the engine now CARRIES the parsed `MultiModalInputs`: additive `LLMEngine`/`AsyncLLM` `add_request(MultiModalInputs)`+`generate` overloads via `InputProcessor::process_inputs_mm` (mm_features onto `EngineCoreRequest`/`Request`), chat-template placeholder-STRING helpers, serving_chat `MultiModalChatFn` seam (default unset ⇒ text byte-identical); CPU gate `test_input_processor` +2 + `test_chat_mm` +2 (full chain parse→route→engine request with the 196-slot expanded prompt + mm handles). **SERVING W3 (2026-07-28, `CLAIM-MM-SERVING-E2E`): the `MM-SERVE-E2E` CPU SEAM BODY DONE** — `MakeQwen3VLImageChatFn` (chat_mm.{h,cpp}) turns an image chat request into the placeholder-EXPANDED engine input (marker-inject → chat template → `EncodeWithSpecialTokens` 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 path byte-identical). Gate `test_chat_mm` 8/8 (RED text path = 0 image tokens) + `test_openai_serving` (production seam invoked + routed). **The GPU e2e forward is ARCHITECTURALLY BLOCKED (not box contention):** the engine model runner has no mm forward — `ModelForwardInput` has no vision field, `runner.cpp` ignores `Request.mm_features`, this arch is UNREGISTERED (`REGISTER_VLLM_MODEL` absent), and the M2c `Qwen3VLGenerateGreedy` is a standalone driver. Fold-into-engine recipe (add a vision-embed field → runner runs the tower via the encoder cache + merge → register the arch → dgx token-exact vs the M2c golden) in `specs/mm-serving.md`. **ENGINE MM-FORWARD LANDED (2026-07-28, `CLAIM-ENGINE-MM-FORWARD`): the block is RESOLVED — `ModelForwardInput` gains an additive default-nullopt `mm` field (merged inputs_embeds + 3-D MRoPE + DeepStack), Qwen3-VL is now `REGISTER_VLLM_MODEL`-registered (`qwen3_vl_registry.cpp`, `supports_multimodal=true`), and the registered forward FOLDS the M2c decode into `ModelRegistry::Forward` via the SHARED `Qwen3VLForwardStepLastLogits` (`Qwen3VLGenerateGreedyViaRegistry` drives every step through `ModelRegistry::Forward`). Text inertness (RED line, shared-path change): `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 (nullopt-for-text ⇒ byte-identical). Engine mm-forward token-exact gate `test_qwen3vl_registry_e2e` (image→text THROUGH `ModelRegistry::Forward` == M2c golden STRICT, dgx.casa GB10). Residual: the in-runner scheduler-fed tower run + the full server `/v1/chat/completions` GPU e2e.** | `MODEL-MM-qwen3-vl-qwen3-vlfor-conditional-generation` | | 🚧 | `VoxtralForConditionalGeneration` | Voxtral-Mini-3B-2507 (AUDIO→text, audio-track A3) | **FIRST e2e AUDIO UNDERSTANDING: audio→text vs vLLM 0.25.0** — A2 Whisper-large-v3 encoder at Voxtral config (128 mel/1280/32L/head_dim 64) + AudioLanguageAdapter projector + masked-scatter merge into the LANDED Mistral/Llama decoder (untied lm_head, mistral-consolidated q/k rope-permute) → forked greedy. Gate form BY MEASUREMENT: vLLM greedy K=5 deterministic ⇒ STRICT is the bar; STRICT prefix 33/48 exact vs vLLM greedy, then the ratified near-tie-robust gate PASSES (worst teacher-forced gap 0.0 nats, sole branch = a 4-way bf16 tie at -2.069 nats) — bit-exact infeasible (encoder uses different bf16 GEMM/attn kernels). Decoder proven token-exact (ref-audio→48/48). Additive driver/loader gated on audio ⇒ Mistral text byte-identical. **Speed pending** (decode now graph-captured via `VoxtralDecodeGraph`, lever #3 W1 2026-07-27: small real win 60.94 vs 61.71 ms/tok, still ~1.49× vs vLLM 40.8 ms). **Decode-kernel efficiency ATTRIBUTED + VALIDATED ceiling (2026-07-27, `CLAIM-MM-SPEED-DECODE-KERN`, multimodal-speed.md §11):** the whole ~20 ms/tok residual is the naive scalar `PagedAttentionKernel` decode attention (723 µs × 30 layers = 21.7 ms/step, ~120× the KV floor); the 1:1 vLLM lever (FA2 `flash_attn_varlen` decode) is already in-binary, gated off only because the driver's single KV block (444) isn't ÷16. `block_size÷16` → decode via FA2: TPOT **59.4→38.2 ms/tok (−21.2, ~36%) = 0.94× vLLM 40.8 ms — BEATS parity**, and the FA2 sequence is a VALID vLLM greedy branch (teacher-force PASS, 0 divergences, gap 0.0). But it flips the committed near-tie golden's exact-tie branch (`repro` 48→18) → blocked byte-exact (RED line). bf16 near-tie / golden-pinning ceiling; RECORDS-ONLY (14/14 held, golden unchanged); win reachable via `block_size÷16` + golden regen. **ADOPTED 2026-07-27 (USER-APPROVED, `CLAIM-MM-SPEED-DECODE-KERN-ADOPT`, multimodal-speed.md §12): FA2 decode SHIPS as the Voxtral default — audio DECODE now BEATS vLLM (0.97×), the LAST mm decode-speed gap CLOSED.** One-line `block_size÷16` routes decode via FA2 `LaunchDecodeVarlenFA2Bf16` (nsys: `flash_fwd_splitkv` 1410 @ 18.5 µs, ZERO `PagedAttentionKernel`). `test_voxtral_e2e` converted to the ratified near-tie DISTRIBUTIONAL gate (binding = teacher-force PASS, KERNEL-INDEPENDENT; strict prefix exact to the first bf16 tie — FA2 takes the other side of the pos-18 2-way EXACT tie ⇒ prefix 18, `>=18`; determinism anchor to the FA2 seq). `voxtral_neartie.json` md5 `3d199c2d…`→`937b9ad3…`; STRICT golden `voxtral_golden.json` `8ab87b7e…` UNCHANGED. **Gate PASS 16/16**; teacher-force vLLM 0.25.0 on the FA2 seq = **0 divergent, worst gap 0.0000 nats, PASS**. Capture-safe (graph S=1 + 46 replays valid; compute-sanitizer **0 errors** on the graphed-FA2-decode path; 3 runs byte-identical) ⇒ ships as the DEFAULT graph path. **A/B (same-binary, 6 reps rep0 dropped, steady-state):** scalar 60.50 → FA2 **39.50 ms/tok** (−21.0, ~35%, NON-OVERLAPPING) = **0.97× vLLM 40.8 ms — BEATS**. Audio DECODE now correctness- AND speed-DONE; row stays `PARTIAL`/`ACTIVE` — umbrella DONE bar has audio TTFT (32-layer Whisper encoder, UNMEASURED vs vLLM 43 ms) + c2+ batched / `audio_url` serving ingestion still open (same as image/video). **ENCODER TTFT MEASURED + warp-attention brick 2026-07-27 (`CLAIM-MM-SPEED-AUDIO-ENC`, multimodal-speed.md §13):** the Whisper encoder ran the naive `kAttention` (O(t²) per-key block-`__syncthreads`) — routed the encoder self-attention (hd-64, non-causal) to the warp-scoped `vt::AttentionDenseFast` (§7's vision-tower fix; `kAttention` untouched ⇒ text byte-identical). Encoder forward **8870→1890 ms (4.7×, NON-OVERLAPPING)**; `test_voxtral_e2e` **16/16** with ZERO token flips (naive arm also 16/16 same tokens; goldens md5 UNCHANGED), proof-of-run nsys `AttentionWarpKernel` 32 inst / zero naive. **NOT at parity:** ~1.89 s vs vLLM's 43 ms (~44×) — the warp kernel is STILL 31.8 ms/layer (O(t²), memory-bound on redundant K/V reads); closing needs a flash-TILED non-causal hd-64 attention (LARGE) + resident one-time encoder weights (MEDIUM, byte-exact). **ENCODER FLASH-TILED KERNEL LANDED 2026-07-28 (`CLAIM-MM-SPEED-AUDIO-ENC-KERNEL`, multimodal-speed.md §14):** §13 lever #1 done — new `vt::AttentionDenseFlash` (`OpId::kAttentionDenseFlash`, `cuda_ops.cu`) shared-memory-TILES K/V across a block of `kFlashBr=16` query-warps (FA2 K/V tiling, structure-ported from vendored `flash_fwd_kernel.h:52`), per-warp math copied verbatim from `AttentionWarpKernel` ⇒ BIT-IDENTICAL; encoder routed to it by default (`VT_WHISPER_ENC_WARP=1`/`EAGER=1` A/B); `kAttention`/`kAttentionDenseFast` untouched ⇒ text/vision byte-identical. **16/16** default-flash, flash/warp/eager token md5-IDENTICAL (`89923566…`) ⇒ ZERO flips, goldens md5 UNCHANGED, nsys proof `AttentionDenseFlashKernel` 32 inst / zero warp+naive, RED confirmed, compute-sanitizer 0, 3 runs identical. **A/B same-binary:** attention **35.11→19.29 ms/layer (1.82×)**, encoder forward **~1834→~1375 ms (1.33×)**. **NOT at parity:** ~1.37 s vs vLLM ~43 ms (~32×, was ~44×) — the scalar warp-per-query recurrence is now serial-latency-bound (L2 already served the redundant reads ⇒ 1.8× not 16×); gap-closer = tensor-core MMA hd-64 non-causal FA2 (LARGE) + resident encoder weights (MEDIUM). **ENC lever #2 LANDED 2026-07-28 (`CLAIM-MM-SPEED-AUDIO-ENC-RESIDENT`, multimodal-speed.md §15): device-resident one-time encoder weights** (mirror the Qwen `d_dev` residency seam; each of the 487 encoder weight tensors f32→bf16-converted + H2D-uploaded ONCE into a `mutable shared_ptr` handle, reused across forwards). BYTE-EXACT (moves data, not math): `test_voxtral_e2e` **16/16**, goldens md5 UNCHANGED (`8ab87b7e…`/`937b9ad3…`), compute-sanitizer 0 errors. **Proof-of-run/RED (`VT_WHISPER_ENC_REMARSHAL`):** nsys HtoD resident 740 ops/9.4 GB vs re-marshal 1714 ops/11.9 GB (−974 ops, −2.5 GB). **A/B same-binary (`flock`, 6 reps rep0 dropped):** encoder forward **~1377→~729 ms (−648 ms, 1.89×)** — removes the confirmed ~0.75 s per-call host marshalling. Trajectory 1834→1375→729 ms; vs vLLM ~43 ms now ~17× (was ~32×). NOT at parity — residual 729 ms is GPU-compute-bound (scalar warp attention); LARGE gap-closer remains tensor-core MMA hd-64 FA2. Audio TTFT/encoder stays speed-pending | `MODEL-MM-voxtral-voxtral-for-conditional-generation` **ENCODER TTFT lever #1 MEASURED 2026-08-12 (`CLAIM-MM-SPEED-AUDIO-ENC-FA2`, S17, #432):** the vendored FA-2 forward on tensor cores (new dense hd-64 non-split instantiation + `vt::AttentionDenseFa2`) takes the encoder attention 19,278 -> 166.5 us/layer (**115.8x**) and the encoder forward 731.7 -> 133.0 ms (**5.50x**). Restated against the PIN (`555967922`, TTFT median 46.02 ms, production/graphed, re-measured 2026-08-12): the ENCODER FORWARD goes from 15.90x to **2.89x** of vLLM's whole TTFT. That is NOT a TTFT ratio - our projector, merge and prefill are unmeasured and absent from the numerator (review finding F3); the earlier `~17.1x -> ~3.11x` used the carried-forward 0.25.0 denominator of 42.8 ms, so the published figure was CONSERVATIVE. It LANDS OPT-IN (`VT_WHISPER_ENC_FA2=1`), NOT default: the oracle teacher-force PASSES the ratified band (worst 0.125 nats, over-band 0) but with 3 divergent positions where the shipping kernel has 0 at gap 0.0. **WHY is a HYPOTHESIS:** the stated cause (bf16 P before the PV MMA) was REFUTED by mutation M4 - the same rounding forced into the scalar kernel left the tokens unchanged. Default path + goldens UNCHANGED, `test_voxtral_e2e` 16/16; the op now has unit tests (`tests/vt/test_ops_attention_dense_fa2.cpp`). ADOPTION = developer decision; audio TTFT stays speed-pending. | | 🚧 | `ParakeetForCTC` (encoder + CTC head) | Parakeet / FastConformer ASR: an audio COMPONENT, not a registry arch (vLLM wraps transformers' `ParakeetEncoder` inside `nano_nemotron_vl.py`) | P4 LANDED 2026-08-07 on CPU: encoder + CTC greedy + log-mel front end, gated stage-by-stage against a dumped HuggingFace `ParakeetForCTC` oracle (rel-L2 <= 3.5e-7) with greedy and collapsed token ids EXACT, plus an independent in-test reference and 20/20 mutants killed. Real transcript verified 2026-08-07 on `nvidia/parakeet-ctc-0.6b` AND `-ctc-1.1b`; no CUDA provider, no GPU suite, no aarch64, no speed number. **ONE-SURFACE ROW 1 (2026-08-07): NOW a registered arch** — `ParakeetForCTC` self-registers (BEYOND-PIN: pinned vLLM has Parakeet only as the NemotronH audio component, registry.py:511-513) with the SupportsTranscription-only ModelInfo; text paths refuse by task; served via `vllm_transcribe` (ABI v11) + `/v1/audio/transcriptions`; example = thin `vllm.h` client, transcript byte-identical to pre-fold | `MODEL-AUDIO-PARAKEET-ENCODER` | @@ -94,7 +119,9 @@ Engaged architectures (the 48 non-`INVENTORIED` rows): | 🚧 | `KimiLinearForCausalLM` | Kimi-Linear-48B-A3B | **PAGED-INCREMENTAL DECODE LANDS the 5× speed win (2026-08-07, §19, `row/KIMI-PAGED-INCREMENTAL` #113):** the §18 real lever (e) BUILT — `KimiDecodeCache` + `ForwardPrefillIncremental`/`ForwardDecodeStepIncremental` (`kimi_linear_device.cpp`): prefill-once (KDA recurrent+conv state carried via `vt::KdaGatedDeltaRule` state in/out + `vt::CausalConv1dFwd` tap-carry; NoPE-MLA latent-KV cached) + recurrent decode-step, MIRRORING vLLM `kimi_gdn_linear_attn._forward` (prefill=`chunk_kda_with_fused_gate` output_final_state / decode=`fused_recurrent_kda` initial_state, `vllm-src` `a4e3cb4`; divergences: host state vs paged slot cache, materialized-MHA MLA vs paged-FA2 — named residuals). CPU byte-exact state-carry gate `test_kimi_linear_forward` **15/15·875** (NEW case l: carried decode == fresh full-recompute byte-identical + greedy-identical). Full 48.9B GB10 (single-load/config, flock, drop_caches, min-avail 18-21 GiB, no reboot, §12 golden md5 `bfa5bdbf`): recompute 122/128 @ 4.23 tok/s (reproduces #111); incremental+recurrence 120/128 @ 16.63; **incremental+chunk-prefill 122/128 @ 18.87/19.03 tok/s (2 runs) — token-IDENTICAL to recompute (Gate A PASS, p7 `got` byte-exact) = 4.5× over recompute, 0.90× of vLLM ~21 (the 5× decode gap 0.20×→0.90×)**. Gate B STRICT NOT reached (122/128): chunk-prefill in the RIGHT vehicle reproduces recompute EXACTLY, does NOT close p7 — REFUTES the #111 "p7 in the right vehicle → STRICT" hypothesis; p7 intrinsic (§13/§14 f32-vs-bf16 near-tie at a comma). Decode decomposition (nsys, ours, 99 steps, same-tool): **~90% is the SAME cuBLAS `internal::gemvx::kernel` vLLM calls (batch-1 GEMV-parity)**, KdaScanKernel 2.3%, MoE glue 2.3%, CastBf16 3%; chunk kernels 20 inst = prefill only (prefill=chunk/decode=recurrent IN VIVO). Killing O(n²) ALONE reaches parity-class; no lever load-bearing beyond it — residual = ~15% host-orchestration idle + 3% CastBf16 (a bf16 residual stream = the ONE lever ALSO closing the p7-STRICT near-tie) + paged-FA2 MLA decode. vLLM-live-nsys@0.82 NOT run (box-safety: 95-98 GiB reservation + nsys below the 15 GiB LIFE-CRITICAL floor). `--incremental` opt-in; `VT_KIMI_DEVICE_KDA`/`_CHUNK` STAY OFF (122/128 ≠ STRICT). Row STAYS 🚧. **chunk_kda PREFILL PHASE-2 MEASURED — op CORRECT, chunk-EVERY-STEP REGRESSES 122→102 (2026-08-07, §18, `row/KIMI-CHUNK-KDA-P2` #111):** the `chunk_kda` prefill family regenerated + vendored for ALL 6 arches (reproducible — only new `kda_*`+MANIFEST; GDN cubins byte-identical; drift GREEN) + wired through the new op `vt::KdaChunkPrefill` (the 6-cubin `_chunk_kda_fwd_with_cumulative_g`; `cuda_gdn.cu.o` -Werror clean; RED-first unit `test_ops_kda_chunk_prefill` **2/2·4** on GB10 — chunk-vs-recurrence mean_abs **4.68e-5**, wrong-gate **72×**; GDN untouched 66/66·4242). Full 48.9B GB10 gate (flock, min-avail 21 GiB, no reboot): control device-KDA reproduces **122/128, 4.24 tok/s** EXACTLY; **+chunk-prefill (`VT_KIMI_DEVICE_KDA_CHUNK`) REGRESSES to 102/128, 4.08 tok/s** (p3 16→3, p6 16→11). Root cause: the island's O(n²) recompute applies chunk EVERY decode step over the growing sequence — NOT vLLM's prefill=chunk/decode=recurrent split — so it coin-flips near-ties the recurrence-every-step (control) doesn't (the recurrence matches vLLM's DECODE; chunk only matches its PREFILL). vLLM speed arm (§12 recipe, util 0.82, triton MoE, eager, single-seq; min-avail 15 GiB, no reboot): **~21 tok/s median** 16-token aggregate (25.3 cold-discarded; TTFT not isolable in 0.25.0) vs ours **4.24** (recurrence) / **4.08** (chunk) STEADY decode → **ours/vLLM ≈ 0.20** (vLLM ~5× faster on decode — the O(n²)-recompute vs paged-incremental distance, = the coupled STRICT+speed lever). `VT_KIMI_DEVICE_KDA_CHUNK` STAYS OFF (a regression isn't a flip); device-KDA (122, OFF) still best. The op + regen are the validated prefill half of the named real lever (e) paged-incremental decode (chunk-prefill ONCE + recurrent-decode over PERSISTENT state — kills the O(n²); the STRICT + speed lever, coupled). Row STAYS 🚧. **DEVICE-KDA GB10 122/128 + 4.24 tok/s (§15, #104); device NoPE-MLA lever MEASURED-NEGATIVE (2026-08-07, §16, `row/KIMI-STRICT-CLOSE` #107):** the per-channel-decay device recurrence `vt::KdaGatedDeltaRule` moves 106→**122/128** (p0-p6 16/16; sole p7 pos-6 comma near-tie) AND **1.35→4.24 tok/s (3.1×)** — vLLM's ACTUAL f32-on-bf16 arithmetic, beats §14's host-precision 120. The §15 residual (d) was attempted in device-COMPUTE form: `VT_KIMI_DEVICE_MLA` routes the 7 NoPE-MLA layers' softmax core through `vt::Attention` (pad-V: value zero-padded qk_nope+qk_rope=192 vs v=128, `out[:,:,:v]` byte-exact). CPU RED-first gate GREEN (`test_kimi_linear_forward` **14/14·825**, pad-V==f64 ref rtol 3e-3; perturbation fails 108). Full 48.9B GB10 gate (single-load, flock, min-avail 21 GiB, no reboot): control device-KDA reproduces **122/128, 4.24 tok/s** EXACTLY; **+device-MLA REGRESSES to 109/128 AND 3.89 tok/s** — `vt::Attention`'s f32 online-softmax is the right math but a DIFFERENT reduction order than vLLM's FA2, so it coin-flips near-ties (breaks p3 16→3 into §14's `163586×` repeat) and the per-(t,h) build slows the O(n²) recompute. `VT_KIMI_DEVICE_MLA` STAYS OFF, kept as a documented-MEASURED-NEGATIVE A/B knob (§14 `ISLAND_F32ACC` precedent). MLA dims VERIFIED from the real config (nah=32, qk_nope=128, qk_rope=64, v=128, kv_lora=512, q_lora=None; 7 full-attn/20 KDA). Both device knobs default OFF (122 ≠ STRICT, K=3-deterministic golden). STRICT residual, sharpened: needs vLLM's ACTUAL kernels — (c) chunk_kda prefill family (Triton-AOT regen for sm_121a) + (d) paged FA2 `mla::ForwardMlaAttentionBlock` (NOT the vt::Attention approximation) + (e) paged-incremental decode (needs a decode/paged-attn op, query_len≠key_len; kills the O(n²)) — each a substantial multi-kernel brick (§16). Row STAYS 🚧. **FULL-MODEL GB10 e2e RUNS — NEAR-TIE 106/128 (2026-08-06, `row/MODEL-KIMI-LINEAR-BF16`):** the bf16-resident path CLEARS the f32-loader block — the full 48.9B model now runs e2e on one GB10. dgx CUDA build (`-Werror` clean, 14 GDN AOT symbols nm-linked, `test_kimi_linear_forward` 13/13·656 in the CUDA binary); `kimi-linear-gen --gpu` greedy-decodes the §12 8-prompt battery x16 vs `greedy_ids.npy`. MEMORY: load 117.6s, host RSS PEAK **1.7 GiB** (stage-then-ReleaseHost), device peak 98.5 GiB, min-avail **21.6 GiB** (above the 15 GiB floor, matches the ~25 GiB pool-math headroom), NO OOM/reboot. TOKEN gate **NEAR-TIE 106/128 (82.8%)** — prompts 0,1,3,4,5,6 are 16/16 token-exact, p2/p7 diverge at punctuation/word near-ties; 96 consecutive exact tokens across 6 prompts prove the WIRING (a wiring bug can't). Root cause (honest): the f32 residual stream + host-f64 islands are MORE precise than vLLM's bf16 device kernels, so they flip the argmax where vLLM's deterministic bf16 top-1 has a small margin. STRICT path = the named W7-speed residuals (device GDN/MLA islands -> bf16 stream matching vLLM's rounding). 1.59 tok/s (recompute+island rate). `VT_KIMI_DEVICE_COMPUTE` STAYS OFF (parity-enablers: near-tie != token-exact). Row STAYS 🚧. **bf16-RESIDENT loader/forward IMPLEMENTED + CPU-gated (2026-08-06, `row/MODEL-KIMI-LINEAR-BF16`):** the §13 design is coded — `LoadKimiLinearResidentBf16Weights`/`StageKimiResidentBf16`/`BuildKimiResidentFromHost` (`kimi_linear_weights.cpp`; `LoadBf16Direct` -> `OwnedTensor`, per-tensor stage-to-`d_dev` + `ReleaseHost`, tiny vectors host f32), `KimiLinearResidentWeights` (`kimi_linear.h`), bf16 device forward `DeviceForwardBodyBf16` + `Gemm Bf16` cast-act at ~20 GEMM sites with the two host-fallback islands EXTRACTED+shared (`kimi_linear_device.cpp`), `ForwardDevice` resident-path dispatch (`kimi_linear.cpp`), and the `kimi-linear-gen` e2e harness. CPU **13/13·656** (12/12·614 f32 path UNTOUCHED + NEW tiny-config bf16-vs-f32 gate). PENDING: dgx CUDA build + full-model GB10 e2e vs the STRICT golden. Row STAYS 🚧. **bf16-RESIDENT brick POOL-MATH+DESIGN (2026-08-06, `row/MODEL-KIMI-LINEAR-BF16`):** pool math CLOSES (91.5 GiB bf16 device-resident + ~2.4 GiB act/norms/ctx ≈ 94 GiB, ~25 GiB headroom); design grounded §13 (Laguna `GemmBf16` cast-act + `OwnedTensor::d_dev`, `LoadBf16Direct`, f32 `MaterializeHost` kept for the unit gate). Impl (loader/forward rewrite + gate + e2e) pending. Row STAYS 🚧. **§8 GOLDEN CAPTURED — STRICT (2026-08-06, `row/MODEL-KIMI-LINEAR-E2E`):** the §8 SACRED oracle golden is captured on GB10 (0.25.0-stage, util 0.82, moe=triton, min 15 GiB avail, NO reboot), **8/8 prompts DETERMINISTIC over K=3 → STRICT gate**, committed at `tests/parity/goldens/kimi_linear_greedy/`. Full our-engine e2e BLOCKED on OUR f32 loader (materializes ~183 GiB > 119 pool), the bf16-residency residual; row STAYS 🚧. **W7 GPU-VERIFY (2026-08-06, branch `row/MODEL-KIMI-LINEAR-GPU`):** the device compute runs **12/12·614 GREEN on GB10 sm_121a CUDA build**, BOTH arms (`VT_KIMI_DEVICE_COMPUTE=1` + host-ref); prod stack (CUTLASS-NVFP4 GEMM + FA2 ENABLED + Triton-AOT GDN, 14 cubins nm-verified); f32 device==W2 ref, no divergence, no DeepSeek-class trap. Oracle gateability re-confirmed (0.25.0-stage registers `KimiLinearForCausalLM`). e2e §8 SACRED golden STILL disk-blocked (91.5 GiB checkpoint absent, dgx root 100% full, 34G free). Row STAYS 🚧. **W7 DBuf-resident device COMPUTE landed, CPU-gated** (`CLAIM-KIMI-LINEAR-W7`): the real device compute (`ForwardDeviceCompute`, `kimi_linear_device.cpp`) composes the whole 27-layer KDA/NoPE-MLA + 256-expert-MoE hybrid over pooled f32 `DBuf`s through the SHARED `vt::` ops (embed/`FusedChain` add+RMSNorm/`MatmulBT` projections/`CausalConv1dFwd` convs/`L2Norm`/`RmsNormGated`/`MoeRouterTopK` sigmoid-`noaux_tc`/`MoeSiluMul`/`MoeCombine`/lm_head), returning DEVICE-RESIDENT logits; 2 documented HOST-FALLBACK islands (the KDA per-k-channel gated-delta recurrence + its exp/softplus decay gate — `vt::GdnDecode` carries only a per-HEAD scalar decay; the NoPE-MLA softmax core — the paged `mla::ForwardMlaAttentionBlock` device path is born-on-runner) are the W7-speed residuals. CPU-gated vs the W2 host reference (the CPU backend runs the SAME `vt::` dispatch): `test_kimi_linear_forward` **12/12·614** (per-op KDA/NoPE-MLA/MoE/dense device==ref within f32-accumulation tolerance; the whole `ForwardDeviceCompute` == ref logits + greedy-token-identical + device-resident). Runner opt-in via `VT_KIMI_DEVICE_COMPUTE=1` (default OFF keeps the CPU-verified W6 host-ref compose). GPU numerics (bf16 activations, GDN Triton-AOT cubins, paged het-KV, grouped-MoE slabs) + the e2e SACRED golden stay a NAMED pending (box down) — row STAYS 🚧. ON TOP OF **W6 DEVICE forward SEAM** (`CLAIM-KIMI-LINEAR-W6`): the born-on-the-runner `ForwardDevice` (the DEFAULT `gather_logits` runner path) no longer refuses — it composes the `[rows,vocab]` logits via the CPU reference and hands them back DEVICE-RESIDENT (a pooled `DBuf`, wrapped like deepseek_v2 `WrapDeviceLogits`; `on_device()==true` on CPU+CUDA) so the on-GPU sampler consumes them with NO host download. Kimi-Linear now ROUTES device-resident (`check-runner-routing-consistency` reclassifies it, refuse-skipped stubs 2→1, NO allowlist; `check-fusion-consistency` green); `test_kimi_linear_forward` **7/7·300** (adds the `ForwardDevice`==host-ref device-resident gate). The DBuf-resident device COMPUTE (KDA via the GDN family, NoPE-MLA via `mla::ForwardMlaAttentionBlock`, DeepSeek-V2 grouped-MoE over the paged het-KV; full plan in `kimi_linear.cpp`) is the GPU-verify-pending W7 residual. ON TOP OF **W2-W6 CPU REFERENCE forward** (`CLAIM-KIMI-LINEAR-W2`): the real host `KimiLinearModel::Forward` composes the whole 27-layer hybrid from the landed primitives (KDA layer via `vllm::kimi_kda` refs + the gated-delta recurrence; NoPE-MLA materialized-MHA ref; sigmoid `noaux_tc` MoE + shared expert; dense SwiGLU); loader now materializes host float weights; `test_kimi_linear_forward` 6/6·246 (per-op gates + finite whole forward + greedy decode). ON TOP OF **W1 scaffolding** (registry + `ParseKimiLinearParams` 20 KDA + 7 NoPE-MLA + index-verified name-map + het-KV spec). e2e-gateable (FITS one GB10, 0.77× pool). RESIDUAL = the DEVICE born-on-runner forward (KDA kernel/absorbed-MLA/grouped-MoE slabs) + the W0/W7 e2e SACRED golden. **ROW 7 RUNNER FOLD (2026-08-07, §21, #122): Kimi decode now runs THROUGH `ModelRegistry::Forward` on the runner's paged state (B1 config synthesis, B2 KDA-paged via `KdaChunkPrefill`/`KdaGatedDeltaRule` over `gdn_state`, B3 paged NoPE-MLA with `mla::ForwardMlaAttentionBlock` default-ON, B4 `vllm_complete_tokens` ABI v13 + thin-client example). GB10: engine==CLI 128/128 byte-identical, vs golden 122/128 (the intrinsic near-tie profile), SACRED post-fold green (35B 315/315, 27B 235/235); server stream 19.0 tok/s wall vs vLLM ~21 (~0.90×) = the open speed residual.** Row stays `ACTIVE` on the speed thread | `MODEL-TEXT-kimi-linear-kimi-linear-for-causal-lm` | | 📋 | `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` | +| 📋 | `Dots3NoteForCausalLM` | dots3-note (280B-A16B multimodal MoE: DeepSeek-V3.2 text tower + sliding-window MLA, MoE ViT, `dots` audio encoder) | W0 scope only, no code. Upstream subclasses DeepSeek (`Dots3NoteModel(DeepseekV32Model)`, `Dots3NoteMoE(DeepseekV2MoE)`), so our gated MLA + DSA indexer + `noaux_tc` router + Qwen3-VL vision + Voxtral audio carry most of it; net-new is windowed MLA over 33 of 46 layers with a second latent geometry (576 vs 1088 rows), the padded/heterogeneous MLA KV spec, the headwise attention gate, the MoE ViT and the `dots` audio stem. **BEYOND-PIN** (vLLM `main` only, `#51255`, still being patched) and **oracle-memory-infeasible**: ~576 GB bf16 / ~290 GB fp8 against a 119-122 GiB ceiling on every host we own, so no performance axis is claimable and no token gate can bind until §6.4 of the spec is answered | `MODEL-MM-dots3-note-dots3-note-for-causal-lm` | | 🚧 | `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 DONE + W1 LANDED (#672, [spec](specs/minimax-music3.md)).** Diffusers oracle gateable with committed per-stage goldens (#708); modular loader in the tree, 1413/1413 assertions against the real checkpoint (#714). 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. W2/W3 in flight | `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` | @@ -288,6 +315,8 @@ Transformers compatibility is capability-driven and excluded from finite counts. | `MODEL-TEXT-telechat2-tele-chat2-for-causal-lm` | `TeleChatForCausalLM`, `TeleChat2ForCausalLM` | `registry.py:203-204`; `vllm/model_executor/models/telechat2.py::TeleChat2ForCausalLM` | causal generation / text | model loader/forward | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-TEXT-teleflm-tele-flmfor-causal-lm` | `TeleFLMForCausalLM` | `registry.py:206`; `vllm/model_executor/models/teleflm.py::TeleFLMForCausalLM` | causal generation / text | model loader/forward | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-TEXT-zamba2-zamba2-for-causal-lm` | `Zamba2ForCausalLM` | `registry.py:207`; `vllm/model_executor/models/zamba2.py::Zamba2ForCausalLM` | causal generation / text | model loader/forward; paged attention/KV; Mamba/SSM state | ☐ required | `INVENTORIED` | none | unassigned | +| `MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm` | `Qwen3_5ForCausalLM` | **BEYOND-PIN — NOT in `555967922`** (the text-only Qwen3.5 arms landed upstream AFTER our parity pin, in PR [vllm#50210](https://github.com/vllm-project/vllm/pull/50210) at `ad5d29db7`, which registers both against the SAME `qwen3_5` module: upstream `registry.py:202-203` @ `ad5d29db7`). Deliberately NOT written as a pinned module/class target — the pin-derived static invariants below describe `555967922`, and a post-pin arm must not silently inflate them, the same convention `MuseGlimmerForConditionalGeneration` and `KimiK3ForConditionalGeneration` follow. Upstream class: `qwen3_5.py:439-440` @ `ad5d29db7` (`class Qwen3_5ForCausalLM(Qwen3_5ForCausalLMBase): pass` — the shared base UNCHANGED) | causal generation / text | model loader/forward; Mamba/SSM state; GDN/linear-attention state; paged attention | ✅ [Qwen3.5/3.8 text-only arms](specs/qwen38-text-only.md) | `PARTIAL` | **LANDED 2026-08-12 (`MODEL-QWEN38-TEXT-ONLY`, issue #490): the arch loads; NO token gate exists and one is OWED.** Additive registration against the EXISTING dense factory — no forward, no KV-cache spec, no loader fork: `src/vllm/model_executor/models/qwen3_5_dense.cpp:256` over the text-only `_ModelInfo` `src/vllm/model_executor/models/qwen3_5_common.h:42` (hybrid YES, multimodal NO, mirroring `Qwen3_5ForCausalLMBase`'s IsHybrid-without-SupportsMultiModal). The backbone weight namespace is resolved ONCE per checkpoint — `include/vllm/model_executor/models/qwen3_5_weights.h:524`, `src/vllm/model_executor/models/qwen3_5_weights.cpp:597` — and threaded through the dense loader at `src/vllm/model_executor/models/qwen3_5_dense_weights.cpp:675`, mirroring upstream's single `WeightsMapper(orig_to_new_prefix={"model.language_model.": "model."})` (`qwen3_5.py:296-300` @ `ad5d29db7`) rather than a per-lookup fallback; a MIXED index is REFUSED. Tests `tests/vllm/models/test_qwen3_8_text_only.cpp:586` (dispatch), `:633` (published flat 3.8 config), `:718` (namespace resolution + mixed refusal), `:769` (the dense loader reads byte-identical weights through either namespace), `:1008` (the VL seam DEFAULT driven with the prefix argument omitted). Registry contract re-pinned at `tests/vllm/models/test_model_registry.cpp:150` (40 archs). **RUN GATE OWED:** no text-only Qwen3.5 checkpoint fits GB10 (2.4T bf16 ≈ 4.8 TB, FP8 ≈ 2.4 TB), so there is NO token-exact oracle run and NO speed number; the row may not advance on dispatch and mapping tests alone. The DENSE gate closes when a fitting `Qwen3_5ForCausalLM` checkpoint appears — this loader routes BF16/FP8/NVFP4 per projection by tensor presence, so a flat bf16 checkpoint is expected to load. MTP and GGUF arms for 3.8 are NOT implemented and are owed; the sibling MoE row additionally owes the bf16/stacked expert arm (matrix row below, porting-inventory §9 deviation 17(e)). | unassigned | +| `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` | `Qwen3_5MoeForCausalLM` | **BEYOND-PIN — NOT in `555967922`** (the text-only Qwen3.5 arms landed upstream AFTER our parity pin, in PR [vllm#50210](https://github.com/vllm-project/vllm/pull/50210) at `ad5d29db7`, which registers both against the SAME `qwen3_5` module: upstream `registry.py:202-203` @ `ad5d29db7`). Deliberately NOT written as a pinned module/class target — the pin-derived static invariants below describe `555967922`, and a post-pin arm must not silently inflate them, the same convention `MuseGlimmerForConditionalGeneration` and `KimiK3ForConditionalGeneration` follow. Upstream class: `qwen3_5.py:439-440` @ `ad5d29db7` (`Qwen3_5MoeForCausalLM(Qwen3_5ForCausalLMBase, QwenNextMixtureOfExperts)` = that same base plus `set_moe_parameters()`, `qwen3_5.py:443-449` @ `ad5d29db7`) | causal generation / text | model loader/forward; FusedMoE/grouped GEMM; Mamba/SSM state; GDN/linear-attention state; paged attention | ✅ [Qwen3.5/3.8 text-only arms](specs/qwen38-text-only.md) | `PARTIAL` | **LANDED 2026-08-12 (`MODEL-QWEN38-TEXT-ONLY`, issue #490): the arch loads; NO token gate exists and one is OWED.** Additive registration against the EXISTING MoE factory: `src/vllm/model_executor/models/qwen3_5_moe.cpp:231` over `src/vllm/model_executor/models/qwen3_5_common.h:42`. Motivating checkpoint `Qwen/Qwen3.8-2.4T-A95B` declares `Qwen3_5MoeForCausalLM` / `qwen3_5_moe_text` and is the token-exact Qwen3.6-35B-A3B GDN-hybrid MoE backbone at larger scale — hidden 2048→8192, layers 40→92, experts 256→512, top-k 8→10 — every knob config-driven, and the published safetensors names are IDENTICAL modulo the backbone prefix (3D-stacked `mlp.experts.gate_up_proj`, `mlp.shared_expert_gate.weight`, top-level `lm_head`). One namespace decision per checkpoint threaded through the MoE loader AND its deferred per-layer expert closure at `src/vllm/model_executor/models/qwen3_5_weights.cpp`. Tests `tests/vllm/models/test_qwen3_8_text_only.cpp:586` (dispatch), `:633` (published flat config), `:718` (namespace resolution + mixed refusal), `:836` (byte-identical MoE loads on BOTH expert-residency paths), `:922` (the unimplemented-layout REFUSAL below), `:1008` (the VL seam DEFAULT driven with the prefix argument omitted); 27B/35B/Coder inertness by construction (the VL prefix stays the seam default, `include/vllm/model_executor/models/qwen3_5_weights.h`) with parity goldens md5-unchanged. **CORRECTED 2026-08-12 (review FAIL on records honesty): REGISTRATION IS NOT LOADABILITY, and the debt was recorded INVERTED.** The earlier text said the quantized arm was owed; it is the ONLY one implemented. `LoadQwen3_5Moe` reads exclusively per-expert NVFP4 (`LoadMoeExpertsInto` -> `LoadNvfp4Raw`: `U8` weight + `F8_E4M3` `.weight_scale` + `.weight_scale_2`), with no stacked and no bf16 branch. Read live 2026-08-12, `Qwen/Qwen3.8-2.4T-A95B` publishes 93x `mlp.experts.gate_up_proj` + 93x `.down_proj` (3-D STACKED) and ZERO `weight_scale`/`input_scale`; `Qwen/Qwen3.6-35B-A3B` is the same under the VL prefix; our gated 35B row reads the requantized `nvidia/Qwen3.6-35B-A3B-NVFP4`. **The bf16/stacked MoE routed-expert arm is NOT implemented and is OWED**, and such a checkpoint is now REFUSED by a message naming it (`CheckMoeExpertLayoutSupported`) instead of dying on `expected U8 for lm_head.weight`. **RUN GATE OWED:** the 2.4T checkpoint cannot be executed on this hardware, so NO token-exact oracle run and NO speed number exist — and the MoE gate does NOT close on a fitting checkpoint alone, since a fitting PUBLISHED MoE checkpoint would still be refused; it needs one whose routed experts are per-expert NVFP4, or the owed arm implemented first. Also owed: MTP and GGUF arms for 3.8. See porting-inventory §9 deviation 17(e). | unassigned | ## MODEL-EMBED - Embedding @@ -422,14 +451,18 @@ Transformers compatibility is capability-driven and excluded from finite counts. | `MODEL-MM-kimi-audio-kimi-audio-for-conditional-generation` | `MoonshotKimiaForCausalLM` | `registry.py:449`; `vllm/model_executor/models/kimi_audio.py::KimiAudioForConditionalGeneration` | conditional generation / audio+image | MM processor; encoder/merge; audio/ASR frontend; vision encoder | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-MM-muse-glimmer-muse-glimmer-for-conditional-generation` | `MuseGlimmerForConditionalGeneration` | **BEYOND-PIN — NOT in `555967922`** (Muse Glimmer released 2026-08-08, after the pin, and NOT on vLLM `main` either); the only upstream implementation is the OPEN, approved-but-CI-red PR [vllm#51655](https://github.com/vllm-project/vllm/pull/51655) at head `075d645af` — `vllm/model_executor/models/muse_glimmer.py` (text attn `:1083`, decoder layer `:1212`, vision encoder `:692`, adapter `:1036`, MM processor `:262`), `vllm/transformers_utils/configs/muse_glimmer.py`, `vllm/transformers_utils/processors/muse_glimmer.py`, DFlash hooks in `qwen3_dflash.py:75-94` + `v1/spec_decode/dflash.py:83-95` | conditional generation / image+video | model loader/forward; MM processor; vision encoder + merge; video path; speculative decoding (DFlash); reasoning + tool parsers | [muse-glimmer spec](specs/muse-glimmer.md), issue [#268](https://github.com/mudler/vllm.cpp/issues/268) | `SPIKE` | **W-SPEC (2026-08-10, `row/MODEL-MUSE-GLIMMER`, records+spec only, NO code).** Traced end-to-end from PR #51655 @ `075d645af`. `meta-models/Muse-Glimmer-30B`, Apache-2.0, bf16, `model_type: muse_glimmer`; registry maps BOTH `MuseGlimmerForConditionalGeneration` and `MuseGlimmerForCausalLM` onto one class. **Text tower** L=52, H=6656, 32 q-heads / 2 kv-heads (GQA 16:1), head_dim 128, vocab 202048, 131k ctx, rope theta 5e5: Gemma2-style SANDWICH norms (input/post-attn/pre-ffn/post-ffn) computed in fp32 with a BAKED `+1` weight offset and SPLIT eps (pre-norms `rms_norm_eps`, post-norms the smaller `post_norm_eps`) (`muse_glimmer.py:1236-1247`); **iRoPE** — `no_rope_layers[i]==1` ⇒ RoPE AND sliding-window, `==0` ⇒ NoPE AND full attention, sliding×3 then full every 4th (`:1114-1116,:1167-1168`) — the ONE mechanism with no local analogue (we have no Llama-4); WEIGHTLESS QK-norm over head_dim in fp32 applied BEFORE RoPE (`:1189-1196`); post-QK-norm QUERY PRE-SCALE `scale_query_by`≈3.87 with softmax scaling left at `head_dim**-0.5` (`:1112,:1192`); per-head sigmoid ATTENTION OUTPUT GATE whose gate reads the LAYER INPUT, not the attn output (`:1203-1206`); no logit softcapping. **Two named correctness traps:** (a) the query pre-scale ships under two schemas — native raw `qk_scale_factor`≈43.784 vs modular PRE-FOLDED ≈3.87 — disambiguated upstream BY MAGNITUDE against `sqrt(head_dim)` (`:472-517`); mis-reading it scales every query by 11.3×; (b) `use_qk_norm` / `use_attn_output_gate` read as `None` (not `True`) in the modular schema and only an explicit `False` disables them (`:456-469`), so a naive `getattr(...,False)` silently drops BOTH mechanisms while still emitting plausible text. **Perception encoder** L=50, H=1536, 16 heads (head_dim 96), patch 14×14, `patch_temporal`=2, 32×32 learned pos-emb grid, interleaved window/full per `layer_types`, projector 4096→6144, image tok 200092 / video tok 200091, placeholders `<\|patch\|>`/`<\|image\|>`/`<\|video\|>`: LINEAR patchify (`conv1_linear`, NOT a conv) (`:696,:710`), BILINEAR pos-emb interpolation with per-corner validity masking and a half-pixel `+0.5/-0.5` convention (`:761-820`), 2D RoPE with **width-first** `cat([freq_w,freq_h])` (`:741-759`), block-windowed attention via a `-1`-padded permutation whose per-block valid counts become `seq_lens` (`:844-867`), pixel-shuffle downsample asserting `output_dim == hidden*merge^2` (`:822-842`), plain `LayerNorm` (not RMSNorm) at ln_pre/ln_post. **DFlash** adds NO new drafter — PR #51655 only recognises `MuseGlimmerAssistantModel` as method `dflash` and threads the TARGET's `is_neox_style` into the draft config, because a draft/target RoPE-layout mismatch is SILENT (acceptance collapses, nothing errors, output stays correct). **REUSE MAP:** sandwich norms → `gemma2.cpp`/`gemma4.cpp`; output gate → `qwen3_5.cpp`; weightless QK-norm → Qwen3 family; windowed vision tower → `qwen3_vl_vision.cpp`; gated MLP → `layers::MlpGateUpMethodBase`+`vt::MergedGemmGroup`; decode → `ModelRegistry::Forward`+`dense_attn::AttnBlock`; fusion → `vt::FusedChain`; DFlash → the existing speculator row. **HONESTY:** the pinned oracle CANNOT load `muse_glimmer` (and the checkpoint wants transformers 5.15.0.dev0 vs the pin's 5.14.1), so there is NO gateable oracle and **every performance axis is an OPEN GAP by construction** — no parity/throughput claim may be made from this row until #51655 merges and the pin advances. Correctness gates against the HF reference instead, with per-mechanism RED-first mutation carrying the weight the missing oracle would have. Weights (~60 GiB bf16) NOT downloaded; GB10 fit needs a quantized arm. W-plan W0-W7 in the spec. **W0 CPU SCAFFOLDING LANDED (2026-08-10, `CLAIM-MUSE-GLIMMER-W0`, CPU-only, no weights, no GPU):** additive TUs register BOTH `MuseGlimmerForCausalLM` and `MuseGlimmerForConditionalGeneration` onto ONE factory (mirroring registry.py @ #51655); `ParseMuseGlimmerParams` descends the canonical NESTED layout AND normalizes the older FLAT layout (configs/muse_glimmer.py:186-305 — without it a flat config silently deserializes to an ALL-DEFAULT text config, ignoring every checkpoint value with no error); `NormalizeMuseGlimmerWeightName` ports the `hf_to_vllm_mapper` (:1389-1425) for BOTH checkpoint conventions; `EnumerateMuseGlimmerTensors` is the structural name map, deliberately OMITTING the three WEIGHTLESS modules (`embed_norm` :1286, per-head `qk_norm` :1121, `perception_emb_norm` :1470) that ship no tensor; forward REFUSES-by-name. Gate `tests/vllm/models/test_muse_glimmer_scaffold.cpp` **11/11 cases, 73/73 assertions**, clean CPU `-Werror` build. **RED-FIRST MUTATION-PROVEN, all four named traps** (each mutated in-tree, gate went RED, tree restored byte-identical): (1) treating the native raw `qk_scale_factor` as pre-folded → 3 RED (the 11.3x query blow-up); (2) defaulting the absent `use_qk_norm`/`use_attn_output_gate` to FALSE → 4 RED; (3) counting the iRoPE mask FORWARD instead of backward from the last layer → 5 RED; (4) applying the legacy-guac sandwich-norm renames in the wrong order (which SWAPS post-attention with pre-feedforward) → 1 RED. KV-cache spec is a documented W0 placeholder (one full-attention group; the real sliding/full split rides the Gemma-4 per-layer seam at W1). **W1-W7 FOLLOWED ON THE SAME BRANCH (`row/MODEL-MUSE-GLIMMER`, PR #279, NOT merged), and this paragraph's "config/name-map/registry only, NO forward" is superseded:** W1 the 52-layer text forward (sandwich norms w/ split eps, iRoPE, weightless QK-norm before RoPE, query pre-scale, attn output gate, SwiGLU, untied lm_head, output_multiplier before the soft-cap) gated vs an independent fp32 transcription of `075d645af` plus a property test per mechanism (`test_muse_glimmer_text`, 21 cases / 487 assertions, and the SAME binary re-run under `VT_FUSED_CHAIN_ADOPT=0` so the non-FusedChain fallback arm is gated too); W3 the 50-layer perception encoder (`test_muse_glimmer_vision`); W4 the mm wiring — tower → adapter → projection → `perception_emb_norm` → masked scatter onto the image/video placeholder rows, with the released 30B's 1436 tensors accounted 1436/1436 (`test_muse_glimmer_wiring`, 9/10316); W7 the ATEM reasoning + tool parsers (34 cases / 251 assertions). **REAL-WEIGHT EVIDENCE, and its exact limits:** on real 30B tensors at **reduced depth 4 of 52** our forward's 5 prefill argmax positions are identical to our torch transcription AND to HF's own `muse_glimmer` (`exportable-muse` @`a9e337e8`); max abs diff 0.0889745, cosine 0.999981. NOT established, and not to be implied: the full-depth 52-layer arm NEVER RAN; these are prefill argmax positions, NOT generated tokens, so multi-step decode and the sliding window across steps are untested; the perception encoder has NO reference check of any kind (the wiring gate proves reachability and placement, not that an image produces the right tokens); nothing has run end to end through the server; and the ATEM parsers' channel scoping DOES NOT WORK at server defaults, because the ToolParser/ReasoningParser seam has no `adjust_request` dispatch site while `skip_special_tokens` defaults true and the checkpoint marks `<\|start\|>`/`<\|message\|>`/`<\|eom\|>`/`<\|eot\|>` special (open gap, spec §6.7). **NO vLLM SPEED AXIS ON ANY DIMENSION** — unchanged and unchangeable until #51655 merges and the pin advances. A SECONDARY, quant-matched llama.cpp bar now EXISTS and is measured (#333, 2026-08-11, `row/MUSE-BENCH-2`, spec §14): same 16.76 GB Q4_K_M file both engines on an idle GB10 CPU, 128-token prefill **tie at 0.997x** (0.898x at 20 threads), decode **0.232x**, 512-token prefill 0.170x, peak RSS **1.92x MORE** (30.29 vs 15.74 GiB, the §10.2 dequantized qkv/lm_head/embed). It is a secondary reference and is NEVER the denominator; the vLLM cell stays an open gap by construction. Row token stays `SPIKE` deliberately: advancing it owes `docs/STATUS.md` and `.agents/NOW.md` rows and both surfaces sit byte-exactly at their shrink-only ratchet/budget, so the advance belongs to the landing commit | `CLAIM-MUSE-GLIMMER-SPEC`, `CLAIM-MUSE-GLIMMER-W0`, `CLAIM-MUSE-GLIMMER-W1`, `CLAIM-MUSE-GLIMMER-W3`, `CLAIM-MUSE-GLIMMER-W4`, `CLAIM-MUSE-GLIMMER-W7`, `CLAIM-MUSE-GLIMMER-FIX` | | `MODEL-MM-kimi-k3-kimi-k3-for-conditional-generation` | `KimiK3ForConditionalGeneration` | **BEYOND-PIN — NOT in `555967922`** (K3 released 2026-07-27, after the pin); closest registered = its literal text backbone `KimiLinearForCausalLM` (`registry.py:140`; `vllm/model_executor/models/kimi_linear.py`) + the K2.5 vision wrapper `kimi_k25.py:290` / tower `kimi_k25_vit.py` | conditional generation / image (text-first) | model loader/forward; FusedMoE/grouped GEMM; GDN/linear-attention state (KDA); MLA/latent KV; MXFP4 compressed-tensors quant; MM processor + MoonViT-V2 encoder/merge | [kimi-k3 spike](specs/kimi-k3.md) | `SPIKE` | **W0 SCOPE (2026-07-28, `CLAIM-KIMI-K3-SCOPE`, DERIVE-AND-SHIP, records-only).** From the HF `config.json` (fetch-derived): `architectures:["KimiK3ForConditionalGeneration"]`, `text_config.architectures:["KimiLinearForCausalLM"]` — the text backbone IS the pinned Kimi-Linear hybrid, MASSIVELY scaled: **H=7168, L=93 (69 KDA + 24 MLA full-attn), 896 experts / top-16 / 2 shared, `moe_intermediate_size=3072`**; MLA geometry `kv_lora=512`/`q_lora=1536`/`qk_nope=128`/`qk_rope=64` (= our landed DeepSeek-V3 dims); KDA `head_dim=128`/`num_heads=96`/`short_conv=4`/`gate_lower_bound=-5.0`; quant **`mxfp4-pack-quantized` (compressed-tensors, group 32, e8m0) + MXFP8 acts (QAT)**; vision **MoonViT-V2** (~401M, patch 14, 27L). **HEAVY REUSE** — GDN (KDA's parent, `cuda_gdn.cu`/`gdn_attn.cpp`), DeepSeek MLA (`deepseek_v2.cpp`/`mla_attention.*`, exact geometry), DeepSeek-style MoE (`qwen3_moe.cpp`/`cuda_moe.cu`, scale to 896), and the Qwen3.6-35B GDN-hybrid-MoE model skeleton (`qwen3_5_moe.cpp`) are the structural twins; Kimi-K2 tokenizer/tool parser (`parser/kimi_k2.cpp`) reused. **NET-NEW:** the KDA kernel delta (per-channel `[H,D]` low-rank decay `f_a_proj`/`f_b_proj` + sigmoid-gated output norm + 3 q/k/v convs — already scoped on the Kimi-Linear row), **MXFP4** (we have NVFP4 group-16, not MXFP4 group-32/e8m0), **AttnRes** (report-only, UNCONFIRMED — not in config.json nor pinned `kimi_linear.py`), and the **MoonViT-V2 tower**. **HW-fit: DOES NOT FIT GB10** — 2.8T MXFP4 ≈ **1.56 TB ≈ ~12× over the 119 GiB pool**; no small K3 exists. **DERIVE-AND-SHIP** (no on-box golden, like the beyond-vLLM CUDA bricks): (a) REAL proxy gate of KDA+MLA+MoE on the FITTING `Kimi-Linear-48B-A3B` (~89–91 GiB) vs the pinned oracle, (b) build-verify + structural review for the K3 scale-up. The pinned oracle has NO `kimi_k3` ⇒ even HW-rich users need a pin advance to oracle-gate K3 itself. CORRECTS the 2026-07-25 sweep note ("loads as `DeepseekV3ForCausalLM`" — true for K2, NOT K3). W-plan W1-W8 in the spec. **W2/W5 CPU SCAFFOLDING LANDED (2026-07-28, `CLAIM-KIMI-K3-W2-W5`, DERIVED+BUILD-VERIFIED, NOT pushed):** additive registry TU registers `KimiK3ForConditionalGeneration` (info: text-gen + `is_hybrid` + `has_inner_state` + `supports_multimodal`); config-descent `ParseKimiK3Params` reads the nested `text_config` (KimiLinear KDA+MLA+MoE scalars — note upstream key `num_experts_per_token`), `vision_config` (MoonViT-V2 PARTIAL), `quantization_config` (MXFP4 detect) grounded in `configs/kimi_linear.py:11-148`; pure `EnumerateKimiK3TextBackboneTensors` is the 93-layer KDA/MLA + 896-expert MoE structural name-map grounded 1:1 in `kimi_linear.py:104-378,460-554` + `kimi_gdn_linear_attn.py:102-226` (per-layer KDA vs MLA-with-qLoRA vs MoE-vs-dense branching); forward REFUSE-by-name (`VT_CHECK(false)`, mirrors `deepseek_v4.cpp`); loader REFUSES MXFP4 (a real K3 checkpoint's dtype) deferring to the shared DeepSeek-V4 MXFP4 row. **NOT-YET-BUILDABLE (correctly deferred):** MXFP4 materialization (→ `CLAIM-DEEPSEEK-V4-*` / quantization-matrix MXFP4), the KDA kernel delta (→ Kimi-Linear row `MODEL-TEXT-kimi-linear-*`), MoonViT-V2 vision (W7), K3 multimodal-wrapper weight prefix (post-pin). Code: `src/vllm/model_executor/models/kimi_k3{,_registry,_weights}.cpp` + `include/vllm/model_executor/models/kimi_k3.h`. Test: `tests/vllm/models/test_kimi_k3_scaffold.cpp` (6/6, 63 assertions — registry-resolve + config-descent + split logic + enumeration + reject + MXFP4-refuse). Clean CPU build (`-DVLLM_CPP_CUDA=OFF`). Row stays `SPIKE` (no on-box e2e; forward not implemented); registration `src/vllm/model_executor/models/kimi_k3_registry.cpp:126`; test `tests/vllm/models/test_kimi_k3_scaffold.cpp:123` | `CLAIM-KIMI-K3-SCOPE`, `CLAIM-KIMI-K3-W2-W5` | +| `MODEL-MM-dots3-note-dots3-note-for-causal-lm` | `Dots3NoteForCausalLM` | **BEYOND-PIN — NOT in `555967922`** (the checkout at the pin carries only `dots_ocr.py`, verified); present on vLLM `main` as the platform-split package `vllm/models/dots3_note/{common,nvidia}/` (15 files, ~5.7k LoC) added by [vllm#51255](https://github.com/vllm-project/vllm/pull/51255) @ `9035151d6`, last touched `170592a93` (2026-08-13, [vllm#52172](https://github.com/vllm-project/vllm/pull/52172)); `registry.py:375` -> `("vllm.models.dots3_note", "Dots3NoteForCausalLM")`; config `transformers_utils/configs/dots3_note.py:7` `Dots3NoteConfig(DeepseekV3Config)`; text tower `nvidia/model.py` (`Dots3NoteMoE:76`, `Dots3NotePaddedMLAAttention:204`, `Dots3NoteFullAttention:219`, `Dots3NoteSlidingAttention:329`, `Dots3NoteDecoderLayer:481`, `Dots3NoteModel:549`, shared body `_forward_note_mla:135`); SWA-MLA stack `nvidia/attention.py` (`_gather_swa_kv_kernel:48`, `_apply_swa_score_mask_kernel:118`, `Dots3NoteMLAMetadataBuilder:306`, `Dots3NoteTritonMLAImpl:438`, `Dots3NotePaddedSparseImpl:689`); vision `nvidia/vision.py:508` + `nvidia/vision_moe.py`; audio `nvidia/audio_encoder.py:437`; MM front end `nvidia/multimodal.py:49`; processor `common/processor.py` + `common/video.py` | conditional generation / text+image+video+audio understanding | model loader/forward; MLA/latent KV; **sliding-window MLA (new)**; heterogeneous/padded MLA KV spec (new); DSA lightning indexer; ungrouped `noaux_tc` FusedMoE 256/top-8; MM processor + MoE ViT encoder/merge; video path; `dots` Whisper-variant audio tower; blockwise-FP8 + owed GGUF k-quant arms; MTP | [dots3-note spec](specs/dots3-note.md), issue [#699](https://github.com/mudler/vllm.cpp/issues/699) | `SPIKE` | **W0 SCOPE (2026-08-14, `CLAIM-MODEL-DOTS3-NOTE-W0`, records-only — no code, no build, no download, no GPU).** `dots-studio/dots3-note-prev`, 280B total / 16B active, `model_type: dots3_note`. **Measured** from `config.json` + the HF API: L=46, H=5120, `intermediate_size` 13824, vocab 152064, 524288 positions, `rope_theta` 8e7; MoE 256 routed + 1 shared, top-8, `moe_intermediate_size` 1536, `first_k_dense_replace` 1, sigmoid/`noaux_tc`. **Attention is hybrid with TWO geometries:** 13 `full_attention` layers at indices 0,1,5,9,...,45 (128 heads, `kv_lora_rank` 512, `qk_nope` 128, DSA indexer `index_n_heads` 64 / `index_head_dim` 128 / `index_topk` 2048) and 33 `sliding_attention` layers (window **513**, 64 heads, `swa_kv_lora_rank` **1024**, `swa_qk_nope_head_dim` **192**, own `rope_theta` 5e4, `is_neox_style=False`, no indexer) => latent rows 576 vs 1088, reconciled upstream by padding the physical row (`Dots3NotePaddedMLAAttention.get_kv_cache_spec`). Vision = MoE ViT, 42 layers, `embed_dim` 1536, patch 14, `spatial_merge_size` 2, `use_qk_norm`, `pre_pixel_shuffle`, patch-merger adapter 1536->5120, **pyramid** `pyramid_num_routed` (dense to layer 24, then 4->64). Audio = `encoder_type: "dots"` Whisper variant, `d_model` 1280 / 32 layers / 128 mel / `max_source_positions` **6000** / **swiglu**, conv2d stem, RoPE (partial 0.5), RMSNorm, 60 s chunking, adapter 1280->5120. **HEAVY REUSE:** upstream literally subclasses DeepSeek — `Dots3NoteModel(DeepseekV32Model)`, `Dots3NoteDecoderLayer(DeepseekV32DecoderLayer)`, `Dots3NoteMoE(DeepseekV2MoE)`, `Dots3NoteFullAttention(DeepseekV2MLAAttention)` — so our gated MLA (`deepseek_v2.cpp`, SACRED 8/8), DSA indexer (`deepseek_v4_dsa.cpp`), `noaux_tc` router, 256-expert grouped MoE, `qwen3_vl_vision.cpp` (image+video STRICT 32/32) and `voxtral.cpp`/`whisper_audio.cpp` (audio 16/16) carry most of it. **NET-NEW:** windowed MLA (our every MLA path is full-attention — `deepseek_v4_registry.cpp` sets `is_hybrid=false`), the padded/heterogeneous MLA KV spec, the headwise sigmoid attention gate + the extra `k_rope_only_layernorm` over the 64-dim rope-only k slice + the two `apply_mla_qkv_lora_rescale` scalars, the MoE ViT (all our ViTs are dense), the `dots` audio stem, and the GGUF converter (llama.cpp has no `dots3_note` => no quant-matched llama.cpp bar). **FOUR SILENT CONFIG TRAPS** the checkpoint's `config.json` does NOT carry, all set in `Dots3NoteConfig.__init__:12-25`: `n_group=1`/`topk_group=1` (upstream: *"Do not inherit DeepSeek-V3's 8-group/4-group router defaults ... A different grouping changes the selected experts at every MoE layer"* — our router is gated at V3's grouped dims), `indexer_rope_interleave=True` (GPT-J adjacent pairs, where V3.2 and therefore our indexer default to split-half NeoX), and `num_nextn_predict_layers=1`. A fifth, in-JSON: `apply_mla_qkv_lora_rescale` => `sqrt(hidden/lora_rank)` after each layernorm. **HW-fit: DOES NOT FIT ANY HOST WE OWN.** Measured via the HF API: 131 language shards + `model-vision.safetensors` 13.7 GB + `model-audio.safetensors` 1.77 GB = **~576 GB bf16**; the `-fp8` sibling ~290 GB; upstream's own recipe is `--tensor-parallel-size 8` on H100s. GB10 is ~119 GiB and Thor ~122 GiB RAM / 123 GiB free disk, so **the vLLM oracle cannot run this model here at any published precision** and there is no smaller `dots3-note` in the `dots-studio` org — the same wall [deepseek-v4-flash](specs/deepseek-v4-flash.md) hit at 156.7 GiB, three times worse. Our own arm is plausible at <=2 bpw (~75-90 GiB, the ds4flash IQ2_XXS pattern) but an arm with no oracle beside it yields NO parity number, so **every performance axis is an open gap by construction**. Beyond-pin on top of that: gating needs a pin advance, and upstream is still moving (`#52172` landed the day before this scope). Thor `192.168.68.23` is the developer-designated e2e CUDA host (2026-08-14) and probed the same day — 14 cores, 122 GB RAM, 123 GiB free, but **no nvcc, no cmake, no ninja, no venv**, and `nvidia-smi` refuses under non-interactive ssh — so W0.5 provisions it; it can carry our low-bit arm, the sm_110 axis and every unit gate, but not a 290 GB oracle. W-plan W0-W11 + the three-way proceed decision (rent 8xH100 / unit-gated-bricks-with-e2e-owed / park) in the spec | `CLAIM-MODEL-DOTS3-NOTE-W0` | | `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 | | `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) | `ACTIVE` | **W0 DONE + W1 LANDED (#672).** Oracle GATEABLE: `tools/oracle/music3_oracle.py` generated 0.998 s of 44100 Hz stereo at diffusers `c6da9936` (identity asserted at runtime, proved red first), with 13 per-stage goldens under `tests/parity/goldens/minimax_music3_oracle/` (#708). W1 modular loader `minimax_music3_loader.{h,cpp}`: 1413/1413 assertions against the real 27 GB tree, all 1012 tensors accounted, 50 mutations proven to fire, native arm refused by name; `MaterializeWeightNorm` relocated into the shared `vocoder1d` primitives (H3 79/79 cases, 57,395 assertions unchanged) rather than forked (#714). 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 | @@ -493,7 +526,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 | |---|---|---|---|---|---|---|---|---| @@ -591,6 +624,7 @@ of their own and are NOT part of the 328 architecture rows counted above. | `MODEL-EMBED-terratorch-prithvi-geo-spatial-mae` | `PrithviGeoSpatialMAE` | `registry.py:264`; `vllm/model_executor/models/terratorch.py` | embedding / pooling | upstream-derived 2026-08-05, never previously inventoried | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-SPEC-qwen3-5-mtp-qwen3_5-mtp` | `Qwen3_5MTP` | `registry.py:647`; `vllm/model_executor/models/qwen3_5_mtp.py` | speculative draft head | upstream-derived 2026-08-05, never previously inventoried | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-SPEC-qwen3-5-mtp-qwen3_5-moe-mtp` | `Qwen3_5MoeMTP` | `registry.py:648`; `vllm/model_executor/models/qwen3_5_mtp.py` | speculative draft head | upstream-derived 2026-08-05, never previously inventoried | ☐ required | `INVENTORIED` | none | unassigned | +| `MODEL-SPEC-dots3-note-dots3-note-mtp` | `Dots3NoteMTPModel` | **BEYOND-PIN — NOT in `555967922`**; on vLLM `main` at `registry.py:662` -> `("vllm.models.dots3_note", "Dots3NoteMTP")`, implemented in `vllm/models/dots3_note/nvidia/mtp.py` (`Dots3NoteMultiTokenPredictorLayer:31`, `Dots3NoteMultiTokenPredictor(DeepseekV32MultiTokenPredictor):88`, `Dots3NoteMTP(DeepseekV32MTP):141` with `has_own_embed_tokens=True` / `has_own_lm_head=False`) | speculative draft / target-dependent | draft runner; acceptance/sampling; MTP; the whole `MODEL-MM-dots3-note-dots3-note-for-causal-lm` target stack | [dots3-note spec](specs/dots3-note.md) §1.4/W10, issue [#699](https://github.com/mudler/vllm.cpp/issues/699) | `INVENTORIED` | none. Scoped only: `Dots3NoteConfig` defaults `num_nextn_predict_layers` to **1** (`transformers_utils/configs/dots3_note.py:24`), so the model card's "three-token speculative decoding" reads as the speculation depth the single nextn head is driven at, not a head count — **inferred from the config default, NOT verified against the checkpoint**; W2 resolves it from `model.safetensors.index.json`. Blocked behind the target row's oracle and hardware gaps (spec §6) | unassigned | | `MODEL-EMBED-roberta-roberta-for-token-classification` | `RobertaForTokenClassification` | `registry.py:304`; `vllm/model_executor/models/roberta.py` | token classification | upstream-derived 2026-08-05, never previously inventoried | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-HFBACKEND-transformers-vault-gemma-for-causal-lm` | `VaultGemmaForCausalLM` | `registry.py:662`; `vllm/model_executor/models/transformers.py` | backend-generic adapter | upstream-derived 2026-08-05, never previously inventoried | ☐ required | `INVENTORIED` | none | unassigned | | `MODEL-HFBACKEND-vibevoiceasrforconditionalgeneration-vibe-voice-asr-for-conditional-generation` | `VibeVoiceAsrForConditionalGeneration` | `registry.py:668`; `vllm/model_executor/models/vibevoiceasrforconditionalgeneration.py` | backend-generic adapter | upstream-derived 2026-08-05, never previously inventoried | ☐ required | `INVENTORIED` | none | unassigned | diff --git a/.agents/oracles/diffusers.md b/.agents/oracles/diffusers.md index 995231feb..e5e4333c6 100644 --- a/.agents/oracles/diffusers.md +++ b/.agents/oracles/diffusers.md @@ -12,17 +12,47 @@ 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. -**Not gateable yet:** nothing in this repository has executed `diffusers` as an -oracle, so there is no measured revision to pin. #647 owes both. +**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). + +**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 role = secondary upstream = https://github.com/huggingface/diffusers -scope = schedulers, VAEs and diffusion pipelines vLLM-Omni does not implement -pin = UNPINNED -pin_label = none -pinned_on = 2026-08-13 -gateable = no -evidence = #647 +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-14 +gateable = yes +evidence = tests/parity/goldens/minimax_music3_oracle/manifest.json ``` 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/porting-inventory.md b/.agents/porting-inventory.md index 7f5c928e4..f5a31c931 100644 --- a/.agents/porting-inventory.md +++ b/.agents/porting-inventory.md @@ -142,7 +142,10 @@ implementation targets, 261 modules) plus a dynamic Transformers-compatible path. The generic ordered architecture-to-type-erased- factory contract is now implemented for the two architectures whose text paths exist locally: `Qwen3_5ForConditionalGeneration` and -`Qwen3_5MoeForConditionalGeneration`. Live loading consumes the full +`Qwen3_5MoeForConditionalGeneration` — plus, since 2026-08-12, their TEXT-ONLY +arms `Qwen3_5ForCausalLM` / `Qwen3_5MoeForCausalLM`, which are two further +registrations over those SAME two factories rather than new implementations (§9 +deviation 17: ahead-of-pin, run gate OWED). Live loading consumes the full `config.architectures` list in order and mirrors pinned unknown, previously-supported, and out-of-tree rejection; unimplemented IDs remain unsupported rather than being misclassified by `num_experts`. The execution row @@ -153,7 +156,7 @@ Python lazy-import/subprocess caching remain explicitly deferred. | Family | Marquee members | Needs | Tier | |---|---|---|---| -| **Qwen3.5/3.6 hybrid (incl. MoE)** | `Qwen3_5ForConditionalGeneration` (27B dense-hybrid, **VL multimodal** wrapper — vision_config present), `Qwen3_5MoeForConditionalGeneration` / `qwen35moe` (35B-A3B) | GDN layers ×3 : 1 gated full-attn (qk-norm, partial RoPE 64d, output gate), MoE 256e top-8 + shared expert (35B) / **dense SwiGLU MLP** (27B), GemmaRMSNorm-style `(1+w)` — ✅ `25326fc` (35B forward correctness-grade, **safetensors**; 16/16 greedy on GB10 = M0 exit; GGUF k-quant load M0.10). **27B (co-equal gate):** CPU-first scaffolding started — arch/quant surveyed (`.agents/specs/qwen27b-w4a4-notes.md`: dense hybrid, W4A4, **and a VL wrapper — text path first, ViT deferred**), CPU W4A4 emulation reference + skipping greedy-parity gate landed; dense loader + single-seq `ForwardDense` + batched PAGED `Qwen3_5DenseModel::Forward` (paged==dense CPU-anchored) + `GPUModelRunner` dense route all landed CPU-green; W4A4 GPU GEMM + oracle golden pending (GPU-gated). **Ordinary plain-BF16 leaf (`LOAD-SAFETENSORS-DIRECT-DENSE`):** raw-NK stacked/tied 4B loading plus layer-bounded discrete-CUDA staging is implemented; real-weight CPU load passes 1656/1656 and local AOT-CUDA retained-host/direct-device tokens match at 1664/1664. The corrected AOT series completes 18/18 legs: ON/OFF/vLLM total **6155.10/6064.06/6730.46 tok/s**, ON=OFF 128/128, peak PSS **2.405/8.571/7.569 GiB**; current ON is 0.9316x the previous AOT result. Current-v0.25 oracle/sanitizer, strict VRAM and external 27B/35B regressions remain open, so this is not a support claim. serving M1–M3 | **T0 (the gate)** | +| **Qwen3.5/3.6 hybrid (incl. MoE)** | `Qwen3_5ForConditionalGeneration` (27B dense-hybrid, **VL multimodal** wrapper — vision_config present), `Qwen3_5MoeForConditionalGeneration` / `qwen35moe` (35B-A3B), plus the TEXT-ONLY arms `Qwen3_5ForCausalLM` and `Qwen3_5MoeForCausalLM` (`qwen3_5_text` / `qwen3_5_moe_text`; motivating checkpoint `Qwen/Qwen3.8-2.4T-A95B`) | GDN layers ×3 : 1 gated full-attn (qk-norm, partial RoPE 64d, output gate), MoE 256e top-8 + shared expert (35B) / **dense SwiGLU MLP** (27B), GemmaRMSNorm-style `(1+w)` — ✅ `25326fc` (35B forward correctness-grade, **safetensors**; 16/16 greedy on GB10 = M0 exit; GGUF k-quant load M0.10). **27B (co-equal gate):** CPU-first scaffolding started — arch/quant surveyed (`.agents/specs/qwen27b-w4a4-notes.md`: dense hybrid, W4A4, **and a VL wrapper — text path first, ViT deferred**), CPU W4A4 emulation reference + skipping greedy-parity gate landed; dense loader + single-seq `ForwardDense` + batched PAGED `Qwen3_5DenseModel::Forward` (paged==dense CPU-anchored) + `GPUModelRunner` dense route all landed CPU-green; W4A4 GPU GEMM + oracle golden pending (GPU-gated). **Ordinary plain-BF16 leaf (`LOAD-SAFETENSORS-DIRECT-DENSE`):** raw-NK stacked/tied 4B loading plus layer-bounded discrete-CUDA staging is implemented; real-weight CPU load passes 1656/1656 and local AOT-CUDA retained-host/direct-device tokens match at 1664/1664. The corrected AOT series completes 18/18 legs: ON/OFF/vLLM total **6155.10/6064.06/6730.46 tok/s**, ON=OFF 128/128, peak PSS **2.405/8.571/7.569 GiB**; current ON is 0.9316x the previous AOT result. Current-v0.25 oracle/sanitizer, strict VRAM and external 27B/35B regressions remain open, so this is not a support claim. **TEXT-ONLY arms (`MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm`, `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm`, issue #490, both `PARTIAL`):** additive registration against the existing dense/MoE factories plus a once-per-checkpoint backbone-namespace resolution (`model.language_model.` or flat `model.`, mixed REFUSED); AHEAD OF THE PIN at `ad5d29db7` / vllm#50210 and the **run gate is OWED** — no Qwen3.8 checkpoint fits GB10, so nothing about generated tokens, memory or speed is claimed. **The bf16 / 3-D-stacked MoE routed-expert arm is NOT implemented and is OWED** (the published MoE repos ship exactly that layout; only an NVFP4 requant loads), so the MoE gate does not close on a fitting checkpoint alone; such a checkpoint is refused by name (§9 deviation 17(e); [spec](specs/qwen38-text-only.md)). serving M1–M3 | **T0 (the gate)** | | Dense decoders | Llama 3.x, Qwen2/3, Mistral, Gemma 2/3, Phi | GQA + RoPE + SwiGLU + RMSNorm (subset of T0 layer set) | T1 | | MoE decoders | Mixtral, Qwen3-MoE (30B-A3B), GLM-4-MoE, OLMoE | FusedMoE 🚧 `65788b3` (correctness-grade eager; grouped-GEMM perf M2.2) | T1 | | Qwen3-Next | `Qwen3NextForCausalLM` | same stack, interleaved-GQA weight layout | T1 | @@ -1605,6 +1608,88 @@ Examples: `examples/cli` ✅ (C-API client), `examples/server` ✅ (OpenAI serve module; f32, an annotated escape — it runs once per load and its output is narrowed to the stream dtype on upload). Owner: the LTX-2.5 row. +17. **Ahead-of-pin forward port: the Qwen3.5 TEXT-ONLY arms are anchored on a + POST-PIN upstream commit (2026-08-12, + `MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm` + + `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm`, issue + [#490](https://github.com/mudler/vllm.cpp/issues/490)).** Our parity pin is + `555967922` (2026-07-26), whose `vllm/model_executor/models/registry.py` + carries only `Qwen3_5ForConditionalGeneration` and + `Qwen3_5MoeForConditionalGeneration`. The text-only arms + `Qwen3_5ForCausalLM` / `Qwen3_5MoeForCausalLM` arrived upstream AFTERWARDS, + in [vllm#50210](https://github.com/vllm-project/vllm/pull/50210) at + `ad5d29db7` (a descendant of the pin). Every `file:line` this row cites — + `registry.py:202-203`, `qwen3_5.py:296-300`, `qwen3_5.py:439-449` — therefore + points at a **post-pin head, not at the pin**, which is a deliberate + exception to "port from the pinned oracle". Same shape as deviation 16 above + and taken for the same reason: at the pin the feature does not exist at all, + so there is nothing there to mirror. It is recorded here, and argued for in + the commit that introduced it, because no checker enforces the anchor rule. + Consequences, all binding while this stands: + (a) this row does **not** advance the pin and reconciles nothing else in the + `555967922..ad5d29db7` range — the next [upstream-sync](upstream-sync.md) + cycle reconciles it deliberately, and until then a Qwen3.5 change must check + both anchors; (b) **no token or speed axis is claimable for + `Qwen/Qwen3.8-2.4T-A95B`** — 2.4T bf16 is ~4.8 TB and the released FP8 + variant ~2.4 TB against GB10's 128 GB unified, with no smaller Qwen3.8 + sibling, so the run gate is OWED (both rows are `PARTIAL`, never `DONE`, and + the gap is recorded in [BENCHMARKS](../docs/BENCHMARKS.md) §Open gaps). The + DENSE gate closes when a `Qwen3_5ForCausalLM` checkpoint that fits GB10 + appears; **the MoE one does not** — per (e) below, a fitting PUBLISHED MoE + checkpoint would still be refused at load, so the MoE gate needs a fitting + checkpoint whose routed experts are per-expert NVFP4, or (e) implemented + first; (c) **one behavior deliberately diverges from upstream, in the + strict direction.** Upstream normalizes the two weight namespaces with + `WeightsMapper(orig_to_new_prefix={"model.language_model.": "model."})`, so a + checkpoint carrying backbone tensors under BOTH spellings LOADS upstream, its + names silently rewritten; `ResolveQwen3_5BackbonePrefix` + (`src/vllm/model_executor/models/qwen3_5_weights.cpp:597-615`) REFUSES it. + The reason is that our loaders resolve the namespace ONCE and a mixed index + is the one input where a per-lookup mapper binds half a model from each + namespace and still appears to succeed — a wrong model that loads, which no + name-mapping test can see. A refusal is strictly safer than upstream's + rewrite and cannot make a checkpoint upstream rejects load here, so it is + tracked rather than reconciled; it is revisited if upstream ever publishes a + genuinely mixed index; (d) **the published config's dtype key is NOT + consumed, tree-wide.** `Qwen/Qwen3.8-2.4T-A95B` is transformers 4.57.3 and + spells the model dtype `dtype`; `hf_config.cpp:520-522` reads only the legacy + `torch_dtype`, so `HfConfig::torch_dtype` is EMPTY on this document. Inert + today — nothing in the tree reads that field — and deliberately NOT fixed on + this row, because consuming `dtype` is a behavior change on every model and + owes its own row, RED-first test and inertness proof. Pinned by an assertion + in `tests/vllm/models/test_qwen3_8_text_only.cpp` so it cannot drift + silently; (e) **the bf16 / 3-D-STACKED MoE ROUTED-EXPERT ARM IS NOT + IMPLEMENTED, and this was recorded INVERTED until 2026-08-12.** The earlier + text here, and on every other surface, said "MTP, quantized and GGUF arms for + 3.8 are NOT implemented and are recorded as owed". The QUANTIZED arm is the + only one that IS implemented: `LoadQwen3_5Moe` routes every routed expert + through `LoadMoeExpertsInto` + (`src/vllm/model_executor/models/qwen3_5_weights.cpp:519-530`) into + `LoadNvfp4Raw` (`:433-462`), which hard-requires per-expert + `experts...weight` = `U8`, `.weight_scale` = `F8_E4M3` and + `.weight_scale_2`. There is **no stacked branch and no bf16 branch** — + unlike `gemma4_weights.cpp:326`, which dispatches between layouts. Read live + 2026-08-12, the published indices have neither shape: + `Qwen/Qwen3.8-2.4T-A95B` has 93x `mlp.experts.gate_up_proj` + 93x + `.down_proj` (3-D stacked) and ZERO names matching `weight_scale` or + `input_scale`, with `lm_head.weight` alone; `Qwen/Qwen3.6-35B-A3B` is the + same under the VL prefix. Our gated 35B row reads the REQUANTIZED + `nvidia/Qwen3.6-35B-A3B-NVFP4`, so this loader **has never read a published + Qwen bf16 MoE repo**. What is owed is therefore the stacked/bf16 MoE expert + arm (with the bf16 shared expert, the FP8-less attention tower and the bf16 + `lm_head` on that path), which needs its own row, spec, RED-first test and + NVFP4 inertness proof. Until it exists such a checkpoint is REFUSED by name + (`CheckMoeExpertLayoutSupported`, same file), per AGENTS.md §Shared seams. + **The DENSE arm is NOT affected and the asymmetry is deliberate record:** + `LoadQwen3_5Dense` routes BF16 vs FP8 vs NVFP4 per projection by tensor + presence (`qwen3_5_dense_weights.cpp:354-360,472-503`) and routes the head by + dtype (`LoadDenseLmHead` / `LoadLmHeadAnyDtype`, `:215-233,515-547`), so it + may genuinely load a flat bf16 checkpoint. The whole deviation is discharged + by a pin advance that includes `ad5d29db7`, at which point (a) and the + anchors become ordinary mirrored behavior and only (c), (d) and (e) survive + as tracked items. Scope and gates: + [qwen3.8 text-only spec](specs/qwen38-text-only.md). + ## 10. E2E test suites (T0 deliverable) 1. **Op parity**: golden dumps from upstream vLLM (Python, test-time only) → diff --git a/.agents/roadmap_v1.md b/.agents/roadmap_v1.md index 198cd26be..0bc505609 100644 --- a/.agents/roadmap_v1.md +++ b/.agents/roadmap_v1.md @@ -47,6 +47,8 @@ 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 | +| [#488](https://github.com/mudler/vllm.cpp/issues/488) | `BACKEND-ROCM` | ROCm gfx1200: PagedAttnOnline is 8.1x slower per call than vLLM's paged-attention kernel (41.1us vs 5.10us) | perf | | [#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 | @@ -85,10 +87,13 @@ issue is not yet placed. Keyed record: update in place, never append. | [#475](https://github.com/mudler/vllm.cpp/issues/475) | `ENG-RELEASE-WINDOWS` | PR #446 release benchmark projection exceeds its page budget after merging current main | bug | | [#499](https://github.com/mudler/vllm.cpp/issues/499) | `ENG-RELEASE-WINDOWS` | Release binaries report project version instead of prerelease identity | bug | | [#500](https://github.com/mudler/vllm.cpp/issues/500) | `ENG-RELEASE-WINDOWS` | MSVC release build lacks ostream definition for string_view doctest diagnostics | bug | +| [#720](https://github.com/mudler/vllm.cpp/issues/720) | `ENG-RELEASE-WINDOWS` | `M_PI` is a POSIX extension MSVC's `` does not define, so `windows-msvc-*` hard-error on every PR; spec [`windows-msvc-m-pi.md`](specs/windows-msvc-m-pi.md). MEASURED, and the intake's "three TUs" is wrong both ways: the two LTX-2.5 VAEs carry their own `#define` and compile (one of them with ZERO uses), while `tests/vllm/models/test_vocoder1d.cpp` — which `git grep -- src include` never looked at and the CI log never reached — is the second real break | bug | | [#170](https://github.com/mudler/vllm.cpp/issues/170) | `ENG-RELEASE-BINARIES` | Publish container images to GHCR (cuda, vulkan, cpu) | feature | | [#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 | +| [#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 | @@ -123,6 +128,7 @@ issue is not yet placed. Keyed record: update in place, never append. | [#412](https://github.com/mudler/vllm.cpp/issues/412) | `MODEL-MM-muse-glimmer-muse-glimmer-for-conditional-generation` | Muse Glimmer config defaults are neutral values, not the architecture's constants: the released GGUF's sandwich post-norms ran at 1e-5 instead of 1e-8 | bug | | [#489](https://github.com/mudler/vllm.cpp/issues/489) | `MODEL-MM-qwen3-5-qwen3-5-moe-for-conditional-generation` | GDN `output_gate_type` is never parsed: a "sigmoid" checkpoint silently computes silu gating, and no token gate can see it | bug | | [#609](https://github.com/mudler/vllm.cpp/issues/609) | `MODEL-TEXT-bailing-moe-v3-bailing-moe-v3-for-causal-lm` | `BailingMoeV3ForCausalLM` (recipe `inclusionAI/Ling-3.0-flash`) is registered on vLLM `main` but absent at the pin and had no model-matrix row. The issue's other architecture, `Qwen3_5MoeForCausalLM`, is owned by [#490](https://github.com/mudler/vllm.cpp/issues/490) / PR #601, which registers it rather than only rowing it | bug | +| [#490](https://github.com/mudler/vllm.cpp/issues/490) | `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` | Qwen3.5/3.8 text-only checkpoints cannot load: `Qwen3_5MoeForCausalLM` unregistered and the loader hardcodes the VL `model.language_model.` prefix. Registration + one-per-checkpoint namespace resolution LANDED 2026-08-12 (sibling row `MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm`); the issue stays OPEN because the RUN GATE is owed — no text-only Qwen3.5 checkpoint fits this hardware, AND (corrected 2026-08-12) the published MoE repos ship 3-D stacked, unquantized experts that this loader does not implement, so the MoE gate does not close on a fitting checkpoint alone; that arm is owed and is refused by name | feature | | [#298](https://github.com/mudler/vllm.cpp/issues/298) | `PERF-27B-LMHEAD-DSR` | `main` is RED on the `device-leakage` DSR ratchet: `PrepareLmHeadResident` added a 33rd `vt_ifdef` to the shared layer | bug | | [#213](https://github.com/mudler/vllm.cpp/issues/213) | `PERF-27B-LMHEAD-FP4` | Qwen3.6 NVFP4 baselines (27B and 35B-A3B) must reach vLLM speed parity | perf | | [#339](https://github.com/mudler/vllm.cpp/issues/339) | `PERF-27B-LMHEAD-FP4` | 27B c1: every fp8 input projection asks for an f32 output, selecting the slower nvjet template family where vLLM emits bf16 (48 f32-out projections 18.51 ms vs 48 bf16-out 7.05 ms). The merged GDN `in_proj` arm is built DEFAULT OFF as `VT_GDN_FP8_IN_BF16`, spec [`perf-fp8-alpha-fold.md`](specs/perf-fp8-alpha-fold.md) §Attempt 4 — UNMEASURED: no committed gate loads the fp8 tower (`row/GATE-27B-FP8-TOWER-GOLDEN` builds that arm) | perf | @@ -145,9 +151,10 @@ issue is not yet placed. Keyed record: update in place, never append. | [#484](https://github.com/mudler/vllm.cpp/issues/484) | `GATE-PIN-UNPINNED-SNAPSHOTS` | The `check-snapshot-pins` CI step has never been observed executing on a real GitHub runner; the spec records this row's GPU/weights limits but not that one | bug | | [#485](https://github.com/mudler/vllm.cpp/issues/485) | `GATE-PIN-UNPINNED-SNAPSHOTS` | `_MARK_FIXPOINT_ROUNDS = 6` silently truncates the binding fixpoint: a 7-link helper chain evades `check-snapshot-pins` with no diagnostic | bug | | [#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 | -| [#644](https://github.com/mudler/vllm.cpp/issues/644) | `ROAD-V1-LTX25` | LTX-2.5 full port: close every refused conditioning arm. Row 1 `LTX25-IMAGE-COND` (spec [`ltx25-image-conditioning.md`](specs/ltx25-image-conditioning.md)) builds the video VAE ENCODER's load path — `Ltx2VideoVaeEncoderKeyRules` existed nowhere in the tree — and serves an image at latent frame 0 at `crf = 0`; keyframe / reference / non-zero-CRF stay refused by name | 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: close every refused conditioning arm. Row 0 `LTX25-PROMPT-ADALN` (spec [`ltx25-prompt-adaln.md`](specs/ltx25-prompt-adaln.md)) restored `use_prompt_adaln_single`, which the loader cleared unconditionally so every render dropped the timestep-conditioned half of the prompt K/V modulation. Row 1 `LTX25-IMAGE-COND` (spec [`ltx25-image-conditioning.md`](specs/ltx25-image-conditioning.md)) builds the video VAE ENCODER's load path — `Ltx2VideoVaeEncoderKeyRules` existed nowhere in the tree — and serves an image at latent frame 0 at `crf = 0`; keyframe / reference / non-zero-CRF stay refused by name | 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 | @@ -156,6 +163,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 | @@ -179,11 +188,23 @@ 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) | — | 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 | +| [#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 | +| [#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 | +| [#664](https://github.com/mudler/vllm.cpp/issues/664) | — | **FIXED 2026-08-13, `row/FIX-WINDOWS-POSIX-VIDEO-ENGINE`.** `windows-msvc-cpu` / `windows-msvc-vulkan` were RED on EVERY open PR (9 sampled across 5 unrelated lanes) from ONE file: `video_engine.cpp` reached Windows with ``, `::stat` and `S_ISDIR` (landed `cefacd2d0`), which `check-windows-portability.py:1675-1688` flags under `full_source_posix` — every scanned source, not only the platform-boundary set. `main` was never a denominator because the Windows jobs are PR-only and `skipped` on push (#584). Repaired at the SOURCE, not the checker: `IsDir`/`Exists` now take the `std::error_code` overloads through a file-local `NativePath`, which preserves `::stat`'s return-false-for-an-uninspectable-path behaviour that the THROWING overloads would have turned into a `filesystem_error` escaping a registry query | bug | +| [#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 | +| [#699](https://github.com/mudler/vllm.cpp/issues/699) | `MODEL-MM-dots3-note-dots3-note-for-causal-lm` | dots3-note (280B-A16B multimodal MoE, text+image+video+audio): scope the port. Upstream SUBCLASSES DeepSeek — `Dots3NoteModel(DeepseekV32Model)`, `Dots3NoteMoE(DeepseekV2MoE)` — so our gated MLA, DSA indexer, `noaux_tc` router, Qwen3-VL vision and Voxtral audio carry most of it; net-new is sliding-window MLA over 33 of 46 layers with a SECOND latent geometry (576 vs 1088 rows), the padded/heterogeneous MLA KV spec, the headwise attention gate, the pyramid MoE ViT and the `dots` audio stem. Two blockers, both recorded rather than worked around: BEYOND-PIN (vLLM `main` only, #51255, still being patched) and ORACLE-MEMORY-INFEASIBLE (~576 GB bf16 / ~290 GB fp8 against a 119-122 GiB ceiling on every host we own, and no smaller checkpoint exists) | 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 @@ -594,10 +615,10 @@ 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 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/dots3-note.md b/.agents/specs/dots3-note.md new file mode 100644 index 000000000..c344731da --- /dev/null +++ b/.agents/specs/dots3-note.md @@ -0,0 +1,513 @@ +# dots3-note — a DeepSeek-V3.2 text tower we mostly own, on hardware we do not + +**Rows:** `MODEL-MM-dots3-note-dots3-note-for-causal-lm` (`Dots3NoteForCausalLM`), +`MODEL-SPEC-dots3-note-dots3-note-mtp` (`Dots3NoteMTPModel`) — both in +[model-matrix.md](../model-matrix.md). +**Issue:** [#699](https://github.com/mudler/vllm.cpp/issues/699). +**Claim:** `CLAIM-MODEL-DOTS3-NOTE-W0`. +**Checkpoint:** `dots-studio/dots3-note-prev` (bf16) and +`dots-studio/dots3-note-prev-fp8`. +**Upstream:** vLLM `main` — added by +[vllm#51255](https://github.com/vllm-project/vllm/pull/51255) at +`9035151d6`, last touched `170592a93` (2026-08-13, +[vllm#52172](https://github.com/vllm-project/vllm/pull/52172) "Disable sequence +parallelism for Dots3 NOTE"). **NOT present at our parity pin.** +**Designated CUDA host (developer directive, 2026-08-14):** Thor +`192.168.68.23` for end-to-end verification. §6.3 records what that host can and +cannot carry for this model, measured. +**Status:** W0 — spec committed, no engine code, nothing built, nothing +downloaded, no GPU used. + +--- + +## 0. Honesty statement — what is and is not claimed + +Nothing has been ported and nothing has been measured on this model. + +**Measured here:** the checkpoint's file list and byte total (HF API, +`?blobs=true`); the geometry in §1 (its `config.json`); the presence and shape of +the upstream implementation (`git show origin/main:...` against a fetched +`${VLLM_SOURCE}`); the absence of the architecture at our pin; and the live state +of Thor in §6.3 (one read-only `ssh` probe). + +**Read, not run:** every upstream behaviour in §2. No vLLM execution of this +model has happened on this project's hardware, and §6 explains why it cannot. + +**Not established, and this is the load-bearing gap:** that any oracle for this +model runs anywhere we can reach. Until it does, no gate in §5 can bind, and no +brick past W1 may claim correctness against upstream — only against an +independent in-test reference. + +A second honesty note, because this row's shape invites the error. The text tower +is *derived from* code we have gated (DeepSeek-V2 MLA, the V3.2-family DSA +indexer, the `noaux_tc` router). Derived is not identical. §4 lists four config +fields and one norm that differ from the DeepSeek defaults our code assumes; each +one silently changes numerics rather than failing loudly. + +--- + +## 1. What the model is, measured + +`dots-studio/dots3-note-prev`, `model_type = "dots3_note"`, architecture +`Dots3NoteForCausalLM`. 280B total / 16B activated (model card), text + image + +video + audio understanding, 512K positions. + +**Checkpoint, from the HF API:** 131 language shards + `model-vision.safetensors` +(13.7 GB) + `model-audio.safetensors` (1.77 GB) = **~576 GB** repo total in bf16. +The `-fp8` sibling is ~290 GB. There is no smaller `dots3-note` variant in the +`dots-studio` org — the org's other models (`dots.llm1`, `dots.ocr`, `dots.mocr`, +the `dots.tts*` family) are different architectures, not scaled-down NOTEs. + +### 1.1 Text tower + +| Field | Value | +|---|---| +| `num_hidden_layers` | 46 | +| `hidden_size` | 5120, `intermediate_size` 13824 | +| `vocab_size` | 152064, `tie_word_embeddings` false | +| `max_position_embeddings` | 524288, `rope_theta` 8e7, `rope_scaling` null | +| MoE | 256 routed + 1 shared, `num_experts_per_tok` 8, `moe_intermediate_size` 1536, `moe_layer_freq` 1, `first_k_dense_replace` 1 | +| Router | `scoring_func` sigmoid, `topk_method` noaux_tc, `norm_topk_prob` true, `routed_scaling_factor` 1.0 | + +**Attention is hybrid, and the two halves have different geometry.** +`layer_types` is 46 entries: **13 `full_attention`** at indices +`0, 1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45`, and **33 `sliding_attention`** +filling the rest (period 4 after the first pair). + +| | full layers | sliding layers | +|---|---|---| +| heads | 128 | 64 (`swa_num_attention_heads`) | +| `q_lora_rank` | 1024 | 1024 (`swa_q_lora_rank`) | +| `kv_lora_rank` | 512 | **1024** (`swa_kv_lora_rank`) | +| `qk_nope_head_dim` | 128 | **192** (`swa_qk_nope_head_dim`) | +| `qk_rope_head_dim` | 64 | 64 | +| `v_head_dim` | 128 | 128 | +| rope | theta 8e7 | **theta 5e4**, `is_neox_style=False` | +| window | — | **513** (`sliding_window_size`) | +| gate | `headwise` | `headwise` (`swa_attention_gate_type`) | +| sparse indexer | yes | no | + +Consequence for the KV cache: the full layers want a latent row of +`512 + 64 = 576`, the sliding layers `1024 + 64 = 1088`. Upstream reconciles this +by *padding the physical row* so both share one block shape (§2.3). + +**DSA (lightning indexer)** on the full layers: `index_n_heads` 64, +`index_head_dim` 128, `index_topk` 2048. + +### 1.2 Vision tower — a MoE ViT + +`vision_config`: 42 layers, `embed_dim` 1536, `intermediate_size` 4224, +`moe_intermediate_size` 2112, 24 heads, `patch_size` 14, `temporal_patch_size` 1, +`spatial_merge_size` 2, `use_qk_norm` true, `is_causal` false, `post_norm` true, +`pre_pixel_shuffle` true, `use_bias` false. Adapter is `patch_merger`, +1536 → 5120, merge size 2. + +The MoE is a **pyramid**: `pyramid_num_routed` is `-1` (dense) for layers 0–24, +then `4, 8, 12, … 64, 64` for layers 25–41. Router `sigmoid`, `router_scale` 1.0, +`capacity_factor` 2. ~7B total / 1.2B activated (model card). + +### 1.3 Audio tower + +`audio_config.encoder_type = "dots"`, wrapping a modified Whisper: `d_model` +1280, 32 encoder layers, 20 heads, `encoder_ffn_dim` 5120, 128 mel bins, +`max_source_positions` **6000**, `activation_function` **swiglu**. Plus +`use_conv2d_stem` true, `use_rope` true (partial rotary 0.5, theta 1e4), +`use_rms_norm` true, `use_causal` false, `downsample_hidden_size` 480, +`chunk_seconds` 60, `conv_chunksize` 500, adapter 1280 → 5120, 16 kHz. ~800M +(model card). Audio placeholders are `<|audio_comp_start|>` / +`<|audio_comp_pad|>` / `<|audio_comp_end|>`. + +### 1.4 MTP + +`Dots3NoteMTPModel`. The config class defaults `num_nextn_predict_layers` to +**1** — one nextn layer, not three. The model card's "three-token speculative +decoding" therefore describes the *speculation depth* the head is driven at, not +a count of heads; that reading is inferred from the config default and is **not +verified against a checkpoint**, because the shard index has not been read. W2 +resolves it from `model.safetensors.index.json`. + +--- + +## 2. Upstream chain, `file:line` + +Paths under `${VLLM_SOURCE}` = `/home/mudler/_git/vllm` at `origin/main`. Note +the package layout: this is **not** `vllm/model_executor/models/dots3_note.py`. +It is `vllm/models/dots3_note/{common,nvidia}/`, the same platform-split shape +DeepSeek-V4 uses — 15 files, ~5.7k LoC. + +### 2.1 Registration + +- `vllm/model_executor/models/registry.py:375` — + `"Dots3NoteForCausalLM": ("vllm.models.dots3_note", "Dots3NoteForCausalLM")`. +- `registry.py:662` — `"Dots3NoteMTPModel": ("vllm.models.dots3_note", "Dots3NoteMTP")`. +- `vllm/transformers_utils/configs/dots3_note.py:7` — + `class Dots3NoteConfig(DeepseekV3Config)`. Read this file before anything else; + §4 is entirely about what it sets. + +### 2.2 The text tower is subclassed from DeepSeek + +`vllm/models/dots3_note/nvidia/model.py`: + +| dots3 class | base | line | +|---|---|---| +| `Dots3NoteMoE` | `DeepseekV2MoE` | `:76` | +| `Dots3NotePaddedMLAAttention` | `MLAAttention` | `:204` | +| `Dots3NoteFullAttention` | `DeepseekV2MLAAttention` | `:219` | +| `Dots3NoteSlidingAttention` | `nn.Module` (built directly) | `:329` | +| `Dots3NoteDecoderLayer` | `DeepseekV32DecoderLayer` | `:481` | +| `Dots3NoteModel` | `DeepseekV32Model` | `:549` | +| `Dots3NoteLanguageModelForCausalLM` | `DeepseekV32ForCausalLM` | `:681` | + +The shared attention body is the free function `_forward_note_mla` +(`model.py:135`), used by both the full and the sliding classes. Its deltas over +plain DeepSeek MLA are exactly four: + +1. `q_c = q_a_layernorm(q_c) * q_lora_scale` and + `kv_c_normed = kv_a_layernorm(kv_c) * kv_lora_scale` (`:154`, `:160`) — see §4.2. +2. `k_pe = k_rope_only_layernorm(k_pe)` (`:161`) — an **extra RMSNorm over the + 64-dim rope-only slice of k**, which DeepSeek does not have. +3. The headwise gate (`:246-262`): `gate = g_proj(hidden_states)`, + `sigmoid` in **fp32** then cast back, reshape attention output to + `[-1, num_heads, v_head_dim]`, multiply per head, flatten. +4. The indexer runs only when `attention.is_sparse` (`:186`) — i.e. never on the + sliding layers, which set `self.indexer = None` / `is_sparse = False` + (`model.py:430-432`). + +`Dots3NoteMoE` (`:76`) is `DeepseekV2MoE` with the shared expert lifted out and +rebuilt at a **block-padded** intermediate size (`_padded_mlp_size:63`) so +blockwise-FP8 weights divide across TP ranks. At TP=1 with no +`weight_block_size` this padding is the identity — record it, do not port it +speculatively. + +### 2.3 Sliding-window MLA — the new machinery + +`vllm/models/dots3_note/nvidia/attention.py`, 807 lines, and the reason this row +is not a mechanical port: + +- `_gather_swa_kv_kernel:48` — Triton kernel gathering the windowed KV rows. +- `_apply_swa_score_mask_kernel:118` — the window mask over scores. +- `_build_sliding_window_metadata:191`, `_SlidingWindowChunk:171`, + `_SlidingWindowMetadata:186` — chunked window bookkeeping. +- `Dots3NoteMLAMetadataBuilder(TritonMLAMetadataBuilder):306`, including + `_reserve_attn_logits_workspace:315`. +- `Dots3NoteTritonMLAImpl(TritonMLAImpl):438` with `_forward_swa_mqa:469`, + `forward_mha:564`, `forward_mqa:655`. +- `Dots3NoteFlashAttnPrefillBackend:257` with `run_sliding_window:278`. +- `Dots3NotePaddedSparseImpl(FlashAttnMLASparseImpl):689` — the full layers, + with `_logical_cache:692` narrowing the padded physical row back to the logical + 576 and `do_kv_cache_update:696`. + +The padding contract lives in `model.py:204-217`: `Dots3NotePaddedMLAAttention` +overrides `get_kv_cache_spec` to report `physical_head_size`, so full and sliding +layers allocate the same block while each reads its own logical width. + +### 2.4 Vision + +`nvidia/vision.py` (677) — `DotsMoEVitConfig:27`, `MoESwiGLUFFN:139`, +`MoESwiGLUFFNFP8:242`, `DotsPatchEmbed:321`, `MoEVisionBlock:352`, +`PixelShuffleAdapter:419`, `PatchMergerAdapter:464`, `DotsMoEVitModel:508` +(`get_pos_ids_by_grid:565`, `rot_pos_emb:601`, cu_seqlens builders `:609`/`:621`). +`nvidia/vision_attention.py` (477). `nvidia/vision_moe.py` (149) — +`note_vision_fused_moe_fp8`, whose docstring states the memory-format trap +directly: *"the native NOTE encoder keeps dynamic activation scales as FP32 +instead of rounding them to E8M0"*, weight scales block 128×128, activations +quantized per token/group-128. + +### 2.5 Audio + +`nvidia/audio_encoder.py` (745) — `RotaryEmbedding:47`, +`WhisperPositionalEmbedding:184`, `WhisperAttention:196`, +`WhisperEncoderLayer:310`, `DotsSpeechEncoder:437` with three stems +(`_forward_conv2d_stem:573`, `_forward_conv1d_stem:594`, +`_forward_latent_stem:607`) and `_temporal_mask:538`. `nvidia/audio.py` (305) is +the vLLM-side wrapper. + +### 2.6 Multimodal front end and MTP + +`nvidia/multimodal.py:49` `Dots3NoteForCausalLM(nn.Module, SupportsMultiModal, +SupportsPP)` — `get_placeholder_str:65`, `_process_image_input:144`, +`_process_audio_input:156`, `_process_video_input:172`, `embed_multimodal:225`, +`get_mm_mapping:300`. `common/processor.py` (811) and `common/video.py` (497) +carry the prompt-side expansion and frame sampling. +`nvidia/mtp.py:31,88,141` — `Dots3NoteMultiTokenPredictorLayer`, +`Dots3NoteMultiTokenPredictor(DeepseekV32MultiTokenPredictor)`, +`Dots3NoteMTP(DeepseekV32MTP)` with `has_own_embed_tokens = True`, +`has_own_lm_head = False`. + +--- + +## 3. Reuse versus new + +### 3.1 What we already own and gate + +| Piece | Ours | Evidence | +|---|---|---| +| MLA: fused qkv_a, q_lora branch, two RMSNorms, split RoPE, kv_b | `src/vllm/model_executor/models/deepseek_v2.cpp` | SACRED 8/8 token-exact on DeepSeek-V2-Lite | +| DSA lightning indexer + top-k | `deepseek_v4_dsa.cpp`, [dsa-topk-bounds.md](dsa-topk-bounds.md) | unit-gated | +| `noaux_tc` sigmoid router + `e_score_correction_bias` + shared experts | `deepseek_v2`, `laguna_ops.cpp`, `kimi_k3.cpp`, `nemotron_h_weights.cpp:309` | `tests/vt/test_ops_moe_router_grouped.cpp` | +| 256-expert grouped-GEMM MoE | DeepSeek-V4 MoE path | measured decode | +| MTP draft head + lossless self-spec verify | `v1/worker/gpu/spec_decode/mtp/speculator.cpp`, `deepseek_v4.cpp` | `test_deepseek_v4_mtp` 5/5 | +| ViT + 2-D RoPE + patch merger, **image and video** | `qwen3_vl_vision.cpp` | STRICT 32/32 image, 32/32 video | +| Whisper-class audio encoder + FA-2 attention | `whisper_audio.cpp`, `voxtral.cpp` | audio→text gate 16/16 | +| MM processor / placeholder expansion / OpenAI content parts | `multimodal/`, `chat_mm.cpp` | `test_chat_mm` 8/8 | + +That is the majority of the parameter count and most of the decode step. + +### 3.2 What does not exist here + +1. **Windowed MLA.** Every MLA path we have is full-attention over a paged cache + (`deepseek_v4_registry.cpp` says so explicitly: `is_hybrid = false`). 33 of 46 + layers need a 513-wide window. **Largest single brick.** +2. **Heterogeneous MLA KV spec** — two logical latent widths sharing one physical + block, plus a `_logical_cache`-equivalent narrowing on read. +3. **Headwise attention gate**, the extra `k_rope_only_layernorm`, and the two + lora rescale scalars. Individually trivial, all on the hot path, and all three + are invisible to a shape check. +4. **MoE ViT** — every vision tower we have is dense. Needs the pyramid schedule, + the sigmoid router with `capacity_factor`, and the FP32-scale FP8 MoE formula + of §2.4. +5. **`dots` audio stem** — conv2d stem, RoPE, RMSNorm, SwiGLU, 6000 positions, + 60 s chunking. Our Whisper block has none of these. +6. **`dots3_note` config parsing** in `hf_config.cpp`, including the four + defaults of §4 that the checkpoint's `config.json` does **not** carry. +7. **GGUF k-quant arm.** llama.cpp has no `dots3_note` architecture, so the + converter is ours to write and there is no quant-matched llama.cpp + comparison for this row. Per AGENTS.md the arm is owed, not optional; an + unimplemented arm refuses with a message naming the missing piece. + +--- + +## 4. Config traps — what `config.json` does not say + +`Dots3NoteConfig.__init__` (`transformers_utils/configs/dots3_note.py:12-25`) +sets four defaults that are **absent from the published `config.json`**. A port +that reads only the checkpoint gets all four wrong, and every one of them is +numerically silent. + +1. **`n_group = 1`, `topk_group = 1`.** Upstream's comment is explicit: *"Do not + inherit DeepSeek-V3's 8-group/4-group router defaults: Note was trained with + an ungrouped (1/1) noaux_tc router … A different grouping changes the selected + experts at every MoE layer."* Our `noaux_tc` router is gated at V3's grouped + dims; this row must drive it ungrouped. +2. **`indexer_rope_interleave = True`.** dots3 projects indexer RoPE coordinates + in **adjacent (GPT-J) pairs**; DeepSeek-V3.2 defaults to **split-half (NeoX)** + when the flag is absent. Our DSA indexer was ported against the V3.2 default, + so this rotates different learned coordinates. +3. **`num_nextn_predict_layers = 1`** (§1.4). +4. The base class is `DeepseekV3Config`, so anything not overridden inherits + V3's default — check each field we read rather than assuming the JSON is + complete. + +Two more that *are* in the JSON but differ from our assumptions: + +5. **`apply_mla_qkv_lora_rescale: true`** ⇒ + `q_lora_scale = sqrt(hidden_size / q_lora_rank)` and + `kv_lora_scale = sqrt(hidden_size / kv_lora_rank)` applied *after* the + respective layernorms (`model.py:154,160`). For the full layers that is + `sqrt(5120/1024)` and `sqrt(5120/512)`; for sliding, `sqrt(5120/1024)` twice. +6. **`is_neox_style=False` on the sliding rope only** (`model.py:404`), with its + own theta 5e4 against the full layers' 8e7. + +**Gate obligation:** each of the six gets a RED-first unit assertion before the +layer that consumes it is written. A wrong value here produces plausible tokens, +which is precisely the class of defect a token gate cannot catch when no oracle +is available to compare against. + +--- + +## 5. Gates + +**Correctness first, and the gate form is chosen by measurement, not in advance** +— capture K=5 greedy oracle runs; deterministic ⇒ STRICT token-exact, otherwise +the ratified near-tie distributional bar. That decision cannot be made until §6 +produces a running oracle. + +Until then, each brick gates against an **independent in-test double-precision +reference** written from the upstream source, with RED-first mutation proof. +That is a consistency gate, not a correctness gate, and the spec says so at every +brick: a shared-helper comparison proves the two arms agree, never that either is +right. + +Owed measurements when hardware exists: token gate; decode/prefill throughput and +TTFT against production-configured vLLM (never `--enforce-eager`); memory; and +the quantized arms including GGUF k-quants. + +--- + +## 6. Oracles and hardware — the blocking section + +### 6.1 The oracle is vLLM, past the pin + +vLLM is the primary oracle and implements this model, so no secondary oracle is +admissible for it. But our parity pin is `555967922` (0.26.0.dev0, 2026-07-26, +[upstream-sync.md](../upstream-sync.md)), and the checkout at that SHA contains +only `dots_ocr.py` — verified, not assumed. dots3 exists on `main` only. + +This row is therefore **beyond-pin**, alongside `KimiK3ForConditionalGeneration` +and `MuseGlimmerForConditionalGeneration`. Gating it requires a pin advance, +which is a full sync cycle with every affected row reconciled — not a version +transcription. Worse for scheduling: upstream is still changing this code +(`#52172` landed 2026-08-13, the day before this spec), so a pin advance taken +now captures a moving implementation. + +### 6.2 Memory — the model does not fit anywhere we own + +| Vehicle | Size | Fits GB10 (~119 GiB)? | Fits Thor (~122 GiB)? | +|---|---|---|---| +| `dots3-note-prev` bf16 | ~576 GB | no | no | +| `dots3-note-prev-fp8` | ~290 GB | no | no | +| hypothetical ~2 bpw GGUF (ours) | ~75–90 GiB + towers | plausible | plausible | + +Upstream's own recipe is `--tensor-parallel-size 8` on H100s. Two of our boxes +together are ~240 GiB and there is no TP-over-LAN path here, so aggregating them +is not a plan. + +**The consequence, stated plainly: there is no configuration in which the vLLM +oracle runs this model on hardware this project has.** Our own arm may well run +at ≤2 bpw — that is the ds4flash IQ2_XXS pattern — but an arm with no oracle +beside it produces no parity number. This is the same wall +[deepseek-v4-flash.md](deepseek-v4-flash.md) hit at 156.7 GiB, three times worse, +and unlike that row there is no smaller published checkpoint to retreat to. + +### 6.3 Thor as the designated e2e host — what it can carry + +Developer directive 2026-08-14: use `192.168.68.23` as the CUDA host for +end-to-end verification. Probed read-only the same day: + +``` +hostname kairos-4db2 aarch64, 14 cores +memory 122 GB total, 118 GB available +disk /home 918G, 123 GiB free (/ is a 4.4G loop, 1.3G free) +toolchain python3 present; NO nvcc, NO cmake, NO ninja, NO ~/venvs +nvidia-smi refuses under non-interactive ssh: + "NvRmMemInitNvmap failed: error Permission denied" +``` + +Three facts follow, and they are recorded rather than worked around. + +1. **Thor cannot host the oracle for this model.** 290 GB FP8 exceeds both its + 122 GB of RAM and its 123 GiB of free disk — the checkpoint will not even + land. Designating the host does not change §6.2; it fixes *where our arm and + our unit gates run*, which is a real and separate thing. +2. **Thor needs provisioning first** (W0.5): CUDA toolkit, cmake/ninja, the + `nvidia-smi` permission for the agent's ssh session, and — if any oracle work + is ever to run there — a vLLM build. Per + [environment.md](../environment.md) the box was reimaged 2026-08-11 and came + back with no host CUDA toolkit; this probe confirms that is still true. +3. **Thor's standing traps apply.** `vm.overcommit_memory=1` with zero swap: the + kernel grants memory it cannot back and touching those pages takes the whole + machine down (observed three times on 2026-08-11). Any run here is sized + conservatively and never `-j` parallel across model gates. The reimage changed + the host key. + +What Thor *is* good for on this row: sm_110 runtime coverage (it is our only +non-GB10 CUDA host), our own low-bit arm end to end, and every unit/brick gate in +§7 — none of which need the 290 GB checkpoint. + +### 6.4 The three ways this row can proceed + +Recorded as options, not chosen here; picking one is a developer decision. + +- **A — rent 8×H100.** The only path to a real parity gate. Cost and data-egress + are the developer's call; nothing in-repo authorizes it. +- **B — unit-gated bricks, e2e owed.** Port each brick against an independent + reference, ship our low-bit arm on Thor, and record the parity gate as an open + gap on the row. Honest, and it is what the row will do by default. +- **C — park at W0.** The row stays `SPIKE`, the scope is on record, and the work + waits for either a smaller checkpoint or hardware. + +Whichever is chosen, **no ceiling is declared** and the gap stays open. + +--- + +## 7. Phases + +W0 is this document. Nothing past it is dispatched until §6.4 is answered. + +- **W0 — scope (this).** Arch map, reuse-vs-new, config traps, quant/HW fit, + oracle plan, rows. **DONE.** +- **W0.5 — provision Thor.** CUDA toolkit, cmake/ninja, `nvidia-smi` under the + agent's ssh, a built vllm.cpp, and the ctest baseline green on sm_110. Gate: + the existing suite passes there before this row adds anything to it. +- **W1 — config + registry.** `dots3_note` in `hf_config.cpp` with RED-first + assertions on all six §4 traps; `dots3_note_registry.cpp` as an additive TU + registering `Dots3NoteForCausalLM` (and `Dots3NoteMTPModel` as INVENTORIED). + Forward refuses loudly. Gate: config parse unit tests; loader accounts for + 100% of tensors on a single-layer slice from the shard index. +- **W2 — weight map.** `model.safetensors.index.json` read for real: the + full/sliding split, `g_proj`, `k_rope_only_layernorm`, the indexer tensors, the + 256-expert w13/w2 mapping, the nextn tail (resolving §1.4), and the two tower + files. Gate: name-map checker, no unclaimed tensor. +- **W3 — full-attention layer.** `_forward_note_mla` over our DeepSeek MLA: + lora rescales, `k_rope_only_layernorm`, headwise gate, DSA indexer at + `indexer_rope_interleave=True`. Gate: independent double-precision reference, + RED-first, mutation-proved. +- **W4 — sliding-window MLA.** The §2.3 stack: windowed metadata, the gather, the + score mask, and the padded/heterogeneous KV spec. The largest brick; likely + splits further once W3 lands. +- **W5 — MoE.** Ungrouped `noaux_tc` at 256/8 + the shared expert. Mostly + routing our existing path at new dims. +- **W6 — vision tower.** Dense ViT half first, then the pyramid MoE and the + FP32-scale FP8 formula. Reuses `qwen3_vl_vision` structure. +- **W7 — audio tower.** The `dots` stem deltas over our Whisper encoder. +- **W8 — MM front end + ABI.** Processor, video sampling, placeholder expansion, + `<|audio_comp_*|>`, `include/vllm.h` surface, the example server as a thin + client. +- **W9 — quantized arms.** Blockwise FP8 and the owed GGUF k-quant arm + + converter. +- **W10 — MTP.** `Dots3NoteMTPModel` over the existing speculator seam. +- **W11 — gates.** Whatever §6.4 permits: full SACRED if A, the recorded-gap + form if B. + +--- + +## 8. Risks + +- **R1 — the oracle never becomes reachable.** Primary risk; §6.4 is its only + mitigation. Everything else is downstream of it. +- **R2 — upstream is still moving.** `#52172` landed the day before this spec. + Pin against a specific `main` SHA per brick and record it, or the "ported from" + anchors rot. +- **R3 — the four silent config traps** (§4). Mitigation: RED-first assertion per + trap, before the consuming layer exists. +- **R4 — windowed MLA is genuinely new** and upstream needed two Triton kernels + plus its own metadata builder to get it. Do not scope W4 as an increment on our + paged full-attention MLA. +- **R5 — the vision MoE's FP32 activation scales** (§2.4) are the exact shape of + a too-wide/too-narrow dtype defect that a token gate cannot see. Check the + memory format against upstream explicitly, per + [porting.md](../porting.md). +- **R6 — no llama.cpp comparison** for the GGUF arm, so the quantized floor has + no external reference. Record it as a gap rather than substituting a different + model's number. +- **R7 — Thor's overcommit + zero swap** takes the box down on an oversized run + (§6.3). Size every run; never stack them. + +## 8.1 A record constraint discovered while landing W0 + +Advancing this row to `ACTIVE` requires this spec to grow the **structured +headings** `check-agent-record.py` enforces for an active row: Scope, Upstream +chain, Our baseline, Port map, Tests to port, Gates, Dependencies. The W0 shape +above deliberately does not have them, and the checker accepts that at `SPIKE`. +Measured by mutating the row's state to `ACTIVE` in a scratch copy and reading +the seven errors it produced. Whoever takes W1 owes that restructure in the same +change as the lifecycle move, not afterwards. + +## 9. Stop conditions + +- Any brick whose only available comparison is a shared helper stops and says so + rather than claiming correctness. +- If W2's tensor accounting disagrees with §1 geometry, stop and reconcile this + spec before writing a layer. +- If a pin advance is attempted and any affected row cannot be reconciled, the + advance stops; this row waits rather than moving the pin around it. +- No throughput number is quoted for this model until a same-tool trace exists + for both arms on identical workloads. + +## Now + +W0 — spec committed, rows added, no engine code and no build. The row is +`SPIKE` and **blocked on §6.4**: there is no hardware here that runs the oracle, +and the architecture is past our parity pin. Next action is a developer decision +between options A, B and C, not an implementation brick. If B is chosen, W0.5 +(provision Thor at `192.168.68.23`) is the first dispatchable task. 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/.agents/specs/indextts-2-5.md b/.agents/specs/indextts-2-5.md new file mode 100644 index 000000000..dc90839ff --- /dev/null +++ b/.agents/specs/indextts-2-5.md @@ -0,0 +1,420 @@ +# 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). + +## 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. + +## What the SHIPPED checkpoint actually contains + +Read from the repository manifest and `config.yaml` itself (not the recipe page, +not the paper). Four findings change the plan, and two of them settle questions +this spec previously left open. + +**The model runs at TWO sample rates.** The talker's mel front end is 24 kHz with +100 mel bins; S2Mel and the vocoder work at 22.05 kHz with 80. The OUTPUT is +22.05 kHz. Conflating them yields audio at the wrong speed rather than an error, +so both are pinned in `indextts2_config.h` and gated. + +**The language question is SETTLED, and the recipe page was wrong.** The shipped +tokenizer is `multilingual_zh_ja_yue_char_del.tiktoken` — zh, ja, yue. That +matches the vLLM-Omni docs (zh/en/zhen/ja/yue) and contradicts the recipe page's +zh/en/ja/es/ar. Nothing may claim Spanish or Arabic. + +**The text tokenizer is TIKTOKEN, not a HuggingFace `tokenizer.json`.** This lane +therefore inherits the constraint already recorded for Kimi-Linear: a tiktoken-only +checkpoint has no `tokenizer.json`, so any path that assumes one is unavailable. + +**A Qwen-0.6B EMOTION MODEL ships inside the checkpoint** (`qwen0.6bemo4-merge/`, +with its own `model.safetensors`, tokenizer and config), alongside `feat1.pt` / +`feat2.pt` speaker and emotion matrices and an `emo_condition_module`. + +*Corrected from the first reading.* It was recorded here as unscoped work, on the +assumption that a second language model inside a TTS lane implied a second port. +Its safetensors header says otherwise. Read by HTTP range request — 2 MB, no +weights — it is **310 BF16 tensors of stock `Qwen3ForCausalLM`**: hidden 1024, 28 +layers, GQA 16 query heads over 8 KV heads at `head_dim` 128, `intermediate_size` +3072, vocab 151936, `tie_word_embeddings: true`. `config.json` names that +architecture literally, this tree registers it in +`src/vllm/model_executor/models/qwen3_dense.cpp`, and +`src/vllm/model_executor/models/qwen3_weights.cpp:168` already has the tying +branch that explains the absent `lm_head.weight`. + +So the emotion *language model* needs **no port**. That reduction is pinned by +`tests/scripts/test_indextts2_emotion_arch_covered.py` against a committed +manifest, so if it is re-exported under another name, or our loader is renamed, +or tying is dropped, the claim fails there rather than rotting in this paragraph. + +**But the emotion PATH is much larger than that model, and the same day's first +correction understated it.** It was written here as "surrounding wiring: +`feat1.pt` / `feat2.pt` and `emo_condition_module`". Reading `gpt.pth`'s own +pickle header (below) shows the emotion path is two unported networks living +inside the talker checkpoint: + +| Group | What it is | Evidence | +|---|---|---| +| `emo_conditioning_encoder` | A **Conformer** encoder at width 512: relative-position MHA carrying `pos_bias_u` / `pos_bias_v` `[4, 128]`, macaron feed-forwards, a conv module with depthwise kernel 15, and a Conv2d-subsampling front end whose `embed.out` is `[512, 261632]` | 38 name patterns in `gpt.pth` | +| `emo_perceiver_encoder` | A **Perceiver resampler**: learned `latents [1, 1024]`, `to_q` / `to_kv` / `to_out`, a GEGLU feed-forward at 2730, `proj_context [1024, 512]` | 9 patterns | +| `emo_layer`, `emovec_layer` | The two projections into the talker: `[1280, 1280]` and `[1280, 1024]` | 4 patterns | + +Neither network is ported, and neither is one this tree already has. The lesson +is the one this campaign keeps re-learning: an architecture name settles what a +*model* costs, and settles nothing about what a *checkpoint* contains. + +### Loading them: convert offline, never read pickle in the engine + +Upstream ships `.pth`, which is a ZIP around a Python pickle. This tree has no +torch-pickle reader, and deliberately does not grow one. Pickle executes +arbitrary code by construction, so a reader in the engine would run a +attacker-controllable program inside the process that serves users, and every +other lane here already loads safetensors or GGUF. + +So the conversion is OFFLINE and once: +`scripts/convert-indextts2-checkpoint.py` flattens the nested state dicts with +'.' -- which is exactly the naming the manifest above records, so the converted +names ARE the manifest's names and the manifest checks the conversion -- and +writes safetensors the existing reader can open. Measured on the shipped +checkpoint: + +| Source | Tensors kept | Dropped | .pth | .safetensors | +|---|---|---|---|---| +| `gpt.pth` | 456 | 0 | 3108.60 MiB | 3108.45 MiB | +| `codec.pth` | 243 | **729** | 579.16 MiB | **192.99 MiB** | +| `s2mel.pth` | 284 | 0 | 395.69 MiB | 395.63 MiB | + +`codec.pth` is **75% optimizer state**: 729 of its 972 tensors are training +residue, and dropping them takes the file from 579 MiB to 193 MiB. That is +dropped loudly, with a count, and the drop prefix is gated from both sides -- +every optimizer key must match it, and no weight in `gpt.pth` or `s2mel.pth` may. + +The conversion needs torch and 4 GiB of weights, so CI cannot run it. +`tests/scripts/test_indextts2_convert.py` holds the part where a silent mistake +would be unrecoverable -- which tensors survive, under which names -- with fakes +and no torch, because a dropped weight looks exactly like a weight that was +never there. + +### What the three .pth checkpoints actually hold + +`gpt.pth`, `codec.pth` and `s2mel.pth` are torch ZIPs: one small pickle names +every tensor, and the gigabytes are separate blobs. `scripts/read-torch-manifest.py` +fetches the central directory and that pickle by range request and unpickles it +with a stub Unpickler, so the full manifest of **1712 tensors across 4.0 GiB** +costs a few hundred KB and needs no torch. The record is committed at +`tests/vllm/models/indextts2_pth_manifest.json`. + +It confirms four constants in `indextts2_config.h` from a source independent of +`config.yaml`: `kTalkerDim` 1280 is `emo_layer.weight`'s square, `kStyleDim` 192 +is `spk_emb_proj.weight`'s input, `kVocosDim` 384 and `kVocosIntermediateDim` +2048 are the codec decoder's ConvNeXt widths, and `kCodecHiddenSize` 1024 is that +decoder's input. `test_indextts2_config_contract.py` compares the header to the +config, which shares its source; `test_indextts2_pth_manifest.py` compares it to +the weights, which does not. + +It also names what our ports do NOT model. The reduced-dim gates all pass, and +they pass over a smaller network than the checkpoint holds: + +| Where | Unported | Note | +|---|---|---| +| ~~`s2mel.pth` `net.cfm.estimator`~~ | ~~`wavenet.*`~~ | **PORTED** in `wavenet.cpp`, gated against upstream `WN` at reduced dims (3 cases / 133 assertions, 6 mutations caught). Not a conditioning stack but the DiT's FINAL LAYER: the config sets `final_layer_type: wavenet`, which is also what `t_embedder2`, `conv1` and `conv2` belong to | +| ~~same~~ | ~~`skip_linear`, `layers.N.skip_in_linear`~~ | BOTH **PORTED**: the long skip in `dit_tail.cpp`, the per-layer U-Net skip in `dit_skip.cpp`. The routing was RECORDED from upstream's own Transformer rather than read off the formula (`scripts/gen-dit-skip-schedule.py`): at the shipped depth 13, layers 0-5 emit, 7-12 receive LIFO so layer 7 takes layer 5's output, and layer 6 does neither. At EVEN depth there is one more emitter than receiver and the earliest skip is never consumed; we report that rather than correct it | +| ~~same~~ | ~~`t_embedder2`, `conv1`, `conv2`~~ | **PORTED** in `dit_tail.cpp` together with `skip_linear`, `res_projection` and `final_layer`, gated against upstream's own DiT modules end to end (4 cases, 6 mutations caught). Note the coupling upstream hides by setting both to 512: `final_layer` is sized at the WAVENET width but conditioned on `t1` at the DiT width, so the two must be equal. We refuse unequal widths by name | +| ~~same~~ | ~~`cond_projection`, `cond_x_merge_linear`~~; `content_mask_embedder` | **PORTED** in `dit_front.cpp`, both the conditional and the CFG unconditional branch. **`cond_embedder` is DEAD in 2.5**: upstream forces `cond_in_module = cond_projection` and the `content_type` switch that would have selected it is commented out, so the tensor ships and is never read. A port that restored the switch would read a tensor this model does not use | +| `s2mel.pth` `net.length_regulator` | `mask_token`, `embedding`, `content_in_proj` | Our `lenreg` port has the interpolate/GroupNorm/Mish stack and none of these | +| `s2mel.pth` | `net.gpt_layer` | Three weight/bias pairs; unmodeled and unexplained | +| `codec.pth` | `model.encoder.*`, `model.down`, `model.up` | Only the quantizer (`fvq`) and the Vocos-shaped decoder are ported | + +`codec.pth` also ships `optimizer.state`, so part of its 0.57 GiB is training +residue rather than weights. + +**Two components are NOT in this repository at all**: BigVGAN +(`bigvgan_generator.pt`, fetched into `hf_cache/bigvgan`) and w2v-bert-2.0. They +download separately at first run, so a byte count of this repo understates what a +render needs. + +The full manifest is 22 files: `gpt.pth`, `codec.pth`, `s2mel.pth`, +`wav2vec2bert_stats.pt`, `feat1.pt`, `feat2.pt`, the tiktoken vocabulary, the +Qwen emotion directory, and `config.yaml`. + +## 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 (13 blocks, hidden 512, 8 heads, in_channels 80) | 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`, 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/.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/.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/.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/.agents/specs/minimax-music3.md b/.agents/specs/minimax-music3.md new file mode 100644 index 000000000..8281c71c8 --- /dev/null +++ b/.agents/specs/minimax-music3.md @@ -0,0 +1,386 @@ +# 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 DONE, W1 LANDED.** Spec committed, both oracles pinned, §1.1 resolved and confirmed at runtime, the diffusers oracle gateable against committed goldens, and the modular loader in the tree. W2/W3 dispatched. +**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. **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 +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 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. + +--- + +## 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 are recorded** — [`../oracles/diffusers.md`](../oracles/diffusers.md) +and [`../oracles/sglang-omni.md`](../oracles/sglang-omni.md), landed in #679 and +advanced in #708. SGLang-Omni has 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. (An earlier revision said the pins "go into `.agents/oracles/` in +W0", which read as future work and misled two implementers into reporting the +SGLang-Omni record as owed after it existed. Present tense, because the record +is a fact and not a plan.) + +--- + +## 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 | +| **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 +LLM→diffusion handoff on *continuous hidden states* rather than discrete tokens. + +--- + +## 4G. The row's structured record + +| Field | Value | +|---|---| +| Scope | IN: the diffusers-arm six-component checkpoint, all five modules, load through waveform; lyrics + structured description in, 44100 Hz stereo out; registration as a `SpeechRegistry` family with the `vllm_speech_*` ABI and the example HTTP server as a thin client (§4.1); quantized arms incl. GGUF k-quants (W7). OUT: the native `AbabForCausalLM` + `.pth` arm, refused by name (§2); streaming, which upstream does not support and which is refused rather than faked; a 32 kHz delivery arm, which is a downstream resample gated separately (§1.1); any change to `SpeechEngine` behaviour for IndexTTS-2.5. | +| Upstream chain | `minimax_music3` is ABSENT from the pinned vLLM, from vLLM `main` and from `vllm-omni` — this row is why AGENTS.md §"When vLLM has no implementation" exists. Primary oracle `diffusers` PR [#14456](https://github.com/huggingface/diffusers/pull/14456) head `c6da9936` (OPEN), [`../oracles/diffusers.md`](../oracles/diffusers.md) `gateable = yes`. Cross-check SGLang-Omni `748a0b43`, [`../oracles/sglang-omni.md`](../oracles/sglang-omni.md) `gateable = no`, which serves the NATIVE layout (§2). `transformers` 5.14.1 for the `Qwen3ForCausalLM` half. Checkpoint `MiniMaxAI/MiniMax-Music3` diffusers arm, 27 GB, at `/mnt/nas_share/checkpoints/minimax-music3`. | +| Our baseline | LANDED for this row: the modular loader `minimax_music3_loader.{h,cpp}` (#714, 1413/1413 assertions against the real tree, all 1012 tensors accounted, native arm refused by name) and the gateable oracle `tools/oracle/music3_oracle.py` with 13 per-stage goldens (#708). REUSED rather than rebuilt: the token-exact Qwen3 dense forward and paged KV, the `vocoder1d` primitives, `multimodal::SpeechEngine`, and the H3 / LTX-2.5 flow-matching and audio-VAE precedent (§4, §4.1). Before this row there was no music generation and no text-to-audio path of any kind. | +| Port map | loader -> `src/vllm/model_executor/models/minimax_music3_loader.cpp` (LANDED, from `scripts/convert_minimax_music3_to_diffusers.py`). `language_model` -> the landed Qwen3 dense path (W2). `condition_embedder_minimax_music3.py` -> W3. `minimax_music3_rvq_depth_decoder.py` -> W3. `transformer_minimax_music3.py` + `FlowMatchEulerDiscreteScheduler` -> W4. `minimax_music3_vocoder.py` -> W5, over the shared `vocoder1d` primitives. `modular_pipelines/minimax_music3/{encoders,before_denoise,denoise,decoders}.py` -> W6. | +| Tests to port | Upstream ships NO unit tests for this model at the pinned SHA — the PR carries docs, a conversion script and the modules, and nothing test-shaped was found in the seven PR files fetched. So the references are CAPTURED, not ported, and this spec says so rather than implying a port that never happened: `tests/parity/goldens/minimax_music3_oracle/` holds per-stage tensors with a manifest recording shape, dtype, sha256 and min/max/mean per entry. Each phase gates against its own stage's entry. If upstream later adds tests, they are ported in the same change that touches the corresponding module. | +| Gates | Split by half, and conflating them is the failure mode §0 warns about. LLM half: TOKEN-EXACT against `rvq_codes.npy` `[26,8]` int32, where row 0 is the priming decode that emits no frame so `rows[1:]` align with the 25 frames. Acoustic half: per-stage tensor parity at fixed seed and reduced dimensions against `condition_chunk0`, `denoise_{first,last}_*`, `vocoder_input_chunk0`, `waveform` — no logits exist, so no token gate does either. A correlation coefficient is NOT a gate here: Pearson is scale-invariant and cannot see a uniformly scaled latent. Speed is measured against SGLang-Omni in its production configuration (both CUDA graphs, compiled DIT and DAV, batched seeded sampling), never with those disabled. | +| Dependencies | `multimodal::SpeechEngine` + `SpeechRegistry` for W6, extended additively per §4.1 with IndexTTS-2.5 left byte-identical. The landed Qwen3 dense forward and paged KV for W2. The `vocoder1d` primitives for W5. The diffusers oracle staying gateable at its pin, for every phase. NO dependency on vLLM-Omni, and none on `dgx.casa`, which was down throughout W0 — the correctness gate runs on CPU by design. | +| Work breakdown | §6. W0 spec + both oracle pins + §1.1 + oracle stand-up (DONE). W1 modular loader, weight-norm folding, dtype invariant, native-arm refusal (DONE). W2 global LLM. W3 condition mix + RVQ depth decoder. W4 flow-matching DiT + scheduler. W5 vocoder over `vocoder1d`. W6 speech-family registration + `vllm_speech_*` ABI + example HTTP server. W7 quantized arms, anything unimplemented refused by name. | +| Risks/decisions | The primary oracle is an OPEN PR: it may be rebased or refactored in review, so the pin is the head SHA and the W1 tensor mapping is re-checked at merge. The on-disk dtype set is NOT runnable (§2.1) — an early revision of this spec asserted the opposite, and the correction is why the loader enforces `dtype(LM) == dtype(rvq) == dtype(cond)` and refuses violations by name. fp32 on the acoustic half is upstream's choice, mirrored, and sets a speed baseline in a regime this project has not optimised for — W7 is where that becomes interesting. The 5000-token prompt and 9000-frame ceilings are enforced, not discovered. Non-streaming is refused by name rather than buffered and called streaming. | + +## 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 (work breakdown) + +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**) 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 | +| **W4** | Flow-matching DiT + `FlowMatchEulerDiscreteScheduler` with `invert_sigmas` | per-step latent parity against the oracle at a fixed seed | +| **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 +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 DONE, W1 LANDED, row `ACTIVE`.** The diffusers oracle generates audio and is +`gateable = yes` against 13 committed per-stage goldens; both oracles are pinned; +§1.1 is resolved and confirmed at runtime; the modular loader is in the tree with +the dtype invariant §2.1 enforced and the native arm refused by name. + +W2/W3 (the autoregressive half) are dispatched. Then W4/W5 acoustic, W6 the +speech-family registration plus the `vllm_speech_*` ABI and the example HTTP +server, W7 the quantized arms. + +Two things are owed and neither is W0's to close: **no speed number exists** — the +oracle capture was CPU-only because `dgx.casa` was down, so nothing here touches +the speed axis — and SGLang-Omni remains `gateable = no`, read but never executed. diff --git a/.agents/specs/mm-serving.md b/.agents/specs/mm-serving.md index e764ed0be..ab7bdc215 100644 --- a/.agents/specs/mm-serving.md +++ b/.agents/specs/mm-serving.md @@ -125,8 +125,20 @@ chat request content-part array [MM-SERVE-PARSE — CPU, THIS BRICK] overloads via `InputProcessor::process_inputs_mm` carry the expanded prompt + `mm_features` onto `EngineCoreRequest`/`Request`; serving_chat routes mm requests through them via the `MultiModalChatFn` seam. See "Brick 2 — landed". -- **Streaming mm, multiple images, video parts, image_embeds/prompt_embeds** — the +- **Streaming mm, video parts, image_embeds/prompt_embeds** — the parse tolerates them (empty payload under their `type`) but they are not routed. +- **Multiple images — NOT "not routed": SILENTLY TRUNCATED** (#686). This line + previously grouped them with the above, which understated it. + `entrypoints/openai/chat_mm.cpp:256-262` locates the **first** `image_url` part + and `break`s, so a three-image request is *served with one*, with no error and no + warning. "Not routed" is accurate for video, audio and `*_embeds` — nothing is + sent and the user notices. Truncation is the case where they do not. + AGENTS.md requires an unimplemented arm to be "refused with a message naming the + missing piece", and upstream does exactly that: + `MultiModalProcessingInfo.validate_num_items` + (`vllm/multimodal/processing/context.py:409-428`) raises `VLLMValidationError`. + #607's L1 has now ported that refusal (`BaseProcessingInfo::ValidateNumItems`); + wiring this call site to it is L2, and this is the reason L2 exists. ## Brick 2 (`MM-SERVE-ENGINE`) — landed diff --git a/.agents/specs/multimodal-track.md b/.agents/specs/multimodal-track.md index 2c58abd65..2558f6491 100644 --- a/.agents/specs/multimodal-track.md +++ b/.agents/specs/multimodal-track.md @@ -205,6 +205,138 @@ 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 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+ +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. + + **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. +- **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 diff --git a/.agents/specs/nemotron-h-model.md b/.agents/specs/nemotron-h-model.md index 515abdf7f..6dfe71a2f 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,9 +179,9 @@ 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 | +| **W4** | `nemotron_h.cpp` forward: hybrid layer loop, Mamba2 mixer wiring, 6 attention layers, MoE layers. **DONE — §6b is the authority on what was gated and how** | CPU forward runs; every block compared ELEMENTWISE against a reference written independently in `double` from the upstream formulas, both dtype arms, short AND long prompts. NOT against a dumped oracle activation reference as this row originally planned: that needs the WEIGHT LOADER, which does not exist yet, so it is owed to W6 alongside the token gate | #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 | | **W6** | **GB10 e2e token gate vs the pinned oracle** | token-exact greedy, identical prompts/counts/batching/sampling; oracle identity asserted | #496 W2 (CUDA), W4, W5 | | **W7** | GGUF k-quant / i-quant arm through the shared GGUF loader (see §5b); refused by name until it lands | quant-matched load + token gate | W4 | @@ -879,15 +879,466 @@ 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. + +## 6b. W4 result — the forward COMPUTES, and the gate around it did not (2026-08-14) + +W4 was built on `row/MODEL-NEMOTRON-H-W4B` from the rescued WIP commit +`d8c0d13f2` — 2411 lines committed by a session that died mid-build with no gate +ever executed — re-merged onto `origin/main`. + +**The inherited forward compiles and is numerically RIGHT.** First clean Release +`-Werror` build: 416/416 targets, **0 `warning:` lines**, ninja exit 0. First run +of its gate: **13 cases, 10 passed, 3 failed, 161 assertions, `Status: FAILURE!`, +exit 1** — and every one of the POSITIVE comparisons against the independently +written `double` references passed on that first run: the Mamba2 mixer, SSD +chunk-size invariance, the carried two-leg state, GQA attention, the non-gated +relu² MoE, the dense MLP, the whole hybrid stack (short + long, f32 + bf16), the +single-branch residual structure, the refusals, and greedy determinism. + +### The three failures were all anti-vacuity guards, and chasing them found the real defect + +Each `AnyDiffers(...)` guard asserts "the mis-port is a DIFFERENT answer". All +three returned false. Measured separations: + +| Guard | max_abs | max_rel | bitwise differing | Verdict | +|---|---|---|---|---| +| bf16 SSM state vs f32 | 1.42e-6 | 2.38e-2 | 192/192 | REAL, but judged by an ABSOLUTE band 140x larger than the largest difference the defect can produce | +| RoPE-rotated attention | 9.41e-6 | 3.18 | 383/384 | attention was DEGENERATE in the fixture (below) | +| routed scale folded into router weights | 2.09e-8 | 3.68e-5 | 245/288 | the fold is EXACT-ARITHMETIC-EQUAL; the test's claim was FALSE | +| routed scale folded into logits | 8.55e-2 | 25.3 | 288/288 | genuinely different — this guard was sound | + +RopeNeox demonstrably DID rotate q (360 of 384 elements moved), so no guard +failed because its own instrument was dead +([[absent-hook-looks-like-armed-instrument]]). + +**What that exposed: the bf16 arms could not fail at all.** `TolFor(bf16)` was a +flat `{atol 6e-2, rtol 6e-2}` applied to references whose own magnitude nobody +had measured: + +| Comparison | max abs(want) | mean abs(want) | atol | atol/mean | all-zeros answer | +|---|---|---|---|---|---| +| attention T=48 bf16 | 0.0109 | 3.55e-4 | 0.06 | **169** | PASSES | +| attention T=6 bf16 | 0.0109 | 1.85e-3 | 0.06 | **32.4** | PASSES | +| mamba2 mixer bf16 | 0.0447 | 1.69e-2 | 0.06 | **3.55** | PASSES | +| logits T=6 bf16 | 0.950 | 0.377 | 0.6 | **1.59** | PASSES | +| logits T=40 bf16 | 1.03 | 0.425 | 0.6 | **1.41** | PASSES | +| attention T=48 f32 | 0.0106 | 3.60e-4 | 2e-4 | 0.556 | weak | + +bf16 is the RELEASED checkpoint's model dtype. On that arm a mixer returning all +zeros passed. The f32 arms carried all the gating there ever was. This is +[[gate-comparing-shared-helper-proves-consistency-not-correctness]] in a new +shape: not a shared helper, but a band larger than the signal. + +### Five repairs + +1. **Bands are RELATIVE to the reference's own peak**, not flat absolutes. +2. **Every comparison SELF-CERTIFIES.** `ExpectCloseRel` REQUIREs that its own + band REJECTS an all-zeros answer before it accepts the real one. A gate that + cannot fail is now itself a test failure, asserted at run time rather than + left for the next reader to re-derive. M15 below proves it works. +3. **The attention fixture was degenerate.** At q/k weight scale 0.2 the tiny + model's logits were ~0.09, so the softmax was near-UNIFORM and the block was + an unweighted mean of v — tiny by cancellation, and nearly blind to anything + that only moves attention WEIGHTS (RoPE, the scale factor, causality). The + real checkpoint is not in that regime: `head_dim=128` and `hidden_size=2688` + put it in the selective one by construction. Raised to 0.95 to restore it. +4. **The routed-scale case asserted something false.** `routed_scaling_factor` + is applied AFTER the top-k renormalisation, so folding it into the router + weights and scaling the assembled routed sum are the SAME expression; the + separation is floating-point association only. The bitwise instrument for it + is at the op level (§6a M6). The case now gates the two things that ARE + observable here — scaling the SHARED term (which + `apply_routed_scale_to_output=True` exists to prevent, and which a + shared-expert-free architecture like Laguna cannot expose) and scaling the + LOGITS — and pins the fold as arithmetically indistinguishable. +5. **The SSM-cache-dtype guard asserted in the wrong place.** Downstream, the + separation is 3.16e-5 of the signal peak, BELOW the f32 arm's own 2e-4 band, + because `A = -exp(A_log)` decays a carried state within a few tokens. It now + gates the STORED STATE, where the dtype actually lives: the dtype itself, the + exact 2x byte-count ratio (the assertion that caught the shared-resolver + defect in §5c), and a relative separation above bf16's resolution. + +### Seams + +- **`vt::FusedChain`** — the two residual add+RMSNorm sites now route through + `kFusedAddRmsNormStd` behind `VT_FUSED_CHAIN_ADOPT`, per AGENTS.md. The + inherited code hand-called `vt::RmsNorm(..., &residual)` and + `scripts/check-fusion-consistency.py` was RED on it. Both arms are green and + the Tier-0 composite dispatches to the same primitive. +- **`ModelRegistry::Forward`** — `ForwardNemotronHForCausalLM` reaches the + forward through the shared seam (inherited from the WIP, kept). +- **`dense_attn::AttnBlock` — NOT APPLICABLE AT W4, recorded rather than + skipped.** That seam is the DEVICE/PAGED full-attention block: it requires a + `PagedKvCache`, a `slot_mapping`, a `block_table`, `StepInputs`, and a + `Qwen3DenseAttnWeights` built from `OwnedTensor`s a loader produced. W4 is the + HOST reference forward and owns none of those. This is the same boundary + Kimi-Linear's `kimi_linear_forward.cpp` and DeepSeek-V4's + `DeepseekV4ForwardHost` sit on, and W6 is where `AttnBlock` applies. +- **`layers::MlpGateUpMethodBase` / `vt::MergedGemmGroup`** — not applicable; + §6a already settled that a non-gated expert has no pair to merge. +- **`scripts/runner-routing-allowlist.txt`** — a DEVIATION from the W4 task's + stated authority, argued in the commit that makes it. Wiring the registry + forward to the host reference makes the model visible to + `check-runner-routing-consistency` as off-framework host logits. It cannot + return device-resident logits because there is no NemotronH weight loader at + all; the entry names W6 as what removes it. + +### RED-first + +With the five forward entry points reverted to W3's refusal, the suite is +**12 of 13 cases FAILING, `Status: FAILURE!`, exit 1**. Note the instrument +trap: the assertion count collapses **254 -> 16** and prints +`14 passed | 2 failed`, because the cases THREW — `grep 'assertions:'` reads a +fully red gate as nearly clean ([[doctest-assertions-line-hides-thrown-cases]]). +Read `Status:`. Tree restored, SHA-256 re-verified, worktree proven clean, +rebuilt back to green. + +### Mutation proof (IMP-MUTATE) — 15 applied alone, rebuilt, run, restored + +Every one RED; after each, the file was restored, its SHA-256 re-verified against +the baseline, and the whole worktree proven clean before the next. + +| Mutation | Result | +|---|---| +| M1 `routed_scale` dropped from `MoeCombine` | FAILURE 3 cases / 14 assertions | +| M2 routed factor ALSO folded into the router weights (double-scale) | FAILURE 3 / 14 | +| M3 router weight dtype inherited from the model instead of f32 | FAILURE 3 cases, assertions 254 -> **188** (threw) | +| M4 attention scale `1/Dh` instead of `1/sqrt(Dh)` | FAILURE 3 / 9 | +| M5 attention causality dropped | FAILURE 3 / 11 | +| M6 conv silu activation dropped | FAILURE 3 / 13 | +| M7 `dt_softplus` dropped | FAILURE 3 / 11 | +| M8 `D` skip-connection dropped from the SSD scan | FAILURE 3 / 13 | +| M9 `dt_bias` dropped from the SSD scan | FAILURE 3 / 8 | +| M10 gated group norm collapsed to `n_groups=1` | FAILURE 3 / 8 | +| M11 `A = +exp(A_log)` (decay sign inverted) | FAILURE 6 cases, assertions 254 -> **87** (threw) | +| M12 relu² bypassed in the non-gated expert | FAILURE 4 / 15 | +| M13 B and C swapped in the conv-output split | FAILURE 3 / 10 | +| M14 final `norm_f` loses its residual fold | FAILURE 1 / 8 | +| M15 **self-certification**: bf16 band widened to 3.0 (makes the gate vacuous) | FAILURE 4 cases / 4 assertions — the non-vacuity REQUIREs fire | + +M15 is the one that keeps the others honest: it proves the self-certification +added above actually detects a band that can no longer fail. M3 and M11 are the +instrument trap again — their assertion COUNTS fall because cases threw. + +M8, M9 and M14 additionally could not be COMPILED in their first form: each left +a `Tensor` unused and `-Werror` rejected it. That is a real, if accidental, +second gate; the mutations were re-run with the variable consumed. + +### Gate evidence + +Local x86_64 CPU-only Release `-Werror` (GNU 13.3, Ninja), plus a Debug arm with +asserts unmasked. Disk is recorded beside every number because this box hit +**100% mid-run**: the first full build reported `FULL_BUILD_EXIT=1` with +`fatal error: error writing to /tmp/ccvKMX4g.s: No space left on device`, while +the harness notification for that same job read "exit code 0" — the wrapper's +status, not ninja's ([[unit-success-is-not-script-success]]). Space was reclaimed +and every number below comes from a re-run with headroom. + +| Arm | Result | disk free | +|---|---|---| +| Release `-Werror`, clean full build | **exit 0, 0 `warning:` lines, 0 ENOSPC lines** | 8.9G / 98% | +| `test_nemotron_h_forward` (Release) | **13/13 cases, 254/254 assertions, `Status: SUCCESS!`** | 8.9G | +| same, `VT_FUSED_CHAIN_ADOPT=0` A/B | **13/13, 254/254, `Status: SUCCESS!`** | 8.9G | +| `test_nemotron_h_scaffold` (Release) | **12/12, 38285/38285, `Status: SUCCESS!`** | 9.1G | +| Debug (`-g0`, asserts unmasked) forward | **13/13, 254/254, `Status: SUCCESS!`** | 5.0G | +| Debug (`-g0`) scaffold | **12/12, 38285/38285, `Status: SUCCESS!`** | 5.0G | +| full `ctest -j4` | **100% tests passed, 0 failed out of 430** (skipped: `test_modelopt_mixed_precision_checkpoint`, `test_voxtral_e2e` — neither asset present) | 5.6G / 99% | + +**The CUDA arm, on Jetson Thor (`kairos-4db2`, aarch64, sm_110).** Transferred by +`git archive` and md5-verified on both ends. Container `vllmcpp-build:aarch64`, +`--runtime=nvidia`, `NVIDIA_DISABLE_REQUIRE=1`; GPU visible inside as **NVIDIA +Thor, compute_cap 11.0**, nvcc **13.0.88**. Configured `-DVLLM_CPP_CUDA=ON +-DVLLM_CPP_CUDA_ARCHITECTURES=110 -DVLLM_CPP_TRITON=OFF`, no CUTLASS — the log's +`CUTLASS not found ... NVFP4 GEMM + FA2 disabled` and three `DISABLED (no +requested arch in [110] provides it)` lines are CORRECT for sm_110, not a silent +fallback. Disk 472-476G free / 46% throughout. + +| Thor arm | Result | +|---|---| +| build, 455 targets | **`BUILD_EXIT=0`, 0 `warning:` lines** (CUDA TUs incl. `cuda_ops.cu`, `cuda_paged_attn.cu`, `cuda_gdn.cu`, Marlin) | +| `test_nemotron_h_forward` | **13/13, 254/254, `Status: SUCCESS!`** — IDENTICAL counts to x86_64 | +| `test_nemotron_h_scaffold` | **12/12, 38285/38285, `Status: SUCCESS!`** — IDENTICAL to x86_64 | +| `test_ops_mamba2_ssd` | 12/12, **2095** assertions, SUCCESS | +| `test_ops_mamba2_gated_norm` | 12/12, **3723**, SUCCESS | +| `test_ops_mamba2_state_update` | 10/10, **5965**, SUCCESS | +| `test_ops_moe_nongated_relu2` | 12/12, 81, SUCCESS | + +The three Mamba2 counts reproduce the GB10 precedent (12/2095, 10/5965, 12/3723) +exactly, on a different architecture and toolchain. + +**What Thor does NOT prove, stated plainly.** `NemotronHForward` asserts a CPU +queue by design — the device/paged path is W6 — so Thor's value here is a +CUDA-ENABLED BUILD and a second architecture executing the suite, not GPU +execution of the forward. The vt primitives the forward composes +(`Mamba2ChunkScan`, `RmsNormGatedGroup`, `Mamba2StateUpdate`, `MoeRelu2`) DO have +CUDA arms and are gated above. + +### Still owed after W4 + +The **weight loader** — nothing materializes the 18487 enumerated tensors, so no +checkpoint can be run and the forward refuses by name on every load. Then the MTP +head (W5), the e2e token gate against the committed goldens (W6), and the GGUF +arm (W7). The committed `nemotron_35_lightning_greedy/oracle.json` goldens are +W6's gate and were deliberately NOT consumed here. No speed claim is made or +implied by this W. + +### 6c. Fresh-review residuals carried forward (2026-08-14) + +W4's fresh review returned **PASS** and proved its central claim by experiment: +it reconstructed the inherited gate, made an attention block return all zeros, +and watched **both bf16 arms accept it** — the released checkpoint's dtype. The +repair holds; the same mutant now fails on all four arms. Four residuals are +recorded rather than left in a reviewer's report: + +**R1 — the bf16 band is coarser than the defect class this file targets, and +the f32 arm must never be dropped as redundant.** `test_nemotron_h_forward.cpp` +compares bf16 at 3e-2 of peak. Demonstrated: a 2% attention-scale error +(`args.scale *= 1.02`) fails both f32 arms and **passes both bf16 arms**. +Corroborating: this file's own no-RoPE separation is **0.0210891**, *below* the +bf16 band, so a RoPE mis-port would pass the bf16 comparison. The f32 arm +(2e-4) and the dedicated no-RoPE guard catch both, which is why this is a +residual and not a defect — but the asymmetry is now on the record so nobody +prunes the f32 arm as duplicated coverage. + +**R2 — the self-certification is a structural identity, not a tightness +measure.** Each comparison REQUIREs that its band reject all-zeros, which with +a peak-relative band reduces exactly to `rel < 0.5`. It eliminates the precise +defect it was written for and nothing more; at `rel = 0.49` it still passes +everywhere. Honestly scoped, not a general guarantee of band quality. + +**R3 — the routed-scale CALL SITE is genuinely ungated.** Folding +`routed_scaling_factor` into the router instead of `MoeCombine`'s +`routed_scale` gives **13/13 cases, 254/254 assertions, SUCCESS** — the +model-level gate cannot see it (peak-relative separation 1.91e-07). It is +arithmetically equal post-renormalisation, which `layer.py:291-300` states +outright by forcing the router factor to 1.0 "so it ends up being a nop". The +bitwise instrument at `tests/vt/test_ops_moe_nongated_relu2.cpp:270` gates +`vt::MoeCombine`'s own semantics, **not** this call site's choice — a +distinction the earlier write-up blurred. Unavoidable here (the model-level +reference is `double`, so no bitwise comparison exists), and recorded as +uncovered rather than implied to be covered. + +**R4 — two comment magnitudes state no denominator.** "3.7e-5 relative" for the +fold measures 1.91e-07 peak-relative; "25.3x the signal" for `scale_logits` +measures 0.568. The qualitative claims are right and were independently +verified; the numbers appear to be mean-relative or from an earlier fixture. + +## 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:** **W4 is written and gated on `row/MODEL-NEMOTRON-H-W4B` (§6b) +and needs a FRESH REVIEW** — never the agent that wrote it — which should mutate +the self-certification (§6b M15) and the two anti-vacuity guards that were +rewritten, because those are the claims this W changes rather than adds. + +After W4 lands, the next brick is **the WEIGHT LOADER**, which §4's table does +not name as a W of its own and should: W5 (MTP), W6 (the e2e token gate) and W7 +(GGUF) all sit behind it, and until it exists the forward refuses by name on +every checkpoint load. Also carried forward, not resolved: 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). + +The row stays `INVENTORIED`: W4 changes no lifecycle state, because nothing runs +end to end yet. ## 8. Stop conditions 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/.agents/specs/qwen38-text-only.md b/.agents/specs/qwen38-text-only.md new file mode 100644 index 000000000..0fa9baa0f --- /dev/null +++ b/.agents/specs/qwen38-text-only.md @@ -0,0 +1,436 @@ +# Qwen3.5/3.8 text-only arms: `Qwen3_5MoeForCausalLM`, `Qwen3_5ForCausalLM` + +**Rows:** `MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm`, +`MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` (both NEW beyond-pin rows; this row +adds them to [`model-matrix.md`](../model-matrix.md) with the inventory counts +bumped, mirroring the `MuseGlimmer`/`KimiK3` beyond-pin precedent) +**Issue:** [#490](https://github.com/mudler/vllm.cpp/issues/490) +**Also closes:** [#627](https://github.com/mudler/vllm.cpp/issues/627) — the +pre-existing misaligned-load UB this row's new test was the first gate to reach +(see `## Outcome` → "The safetensors alignment class"). +**Lifecycle:** `READY` +**Owner:** unassigned + +## Scope + +Load text-only checkpoints of the Qwen3.5-family GDN-hybrid backbone we already +run — the arms upstream calls `Qwen3_5ForCausalLM` and `Qwen3_5MoeForCausalLM`. +`Qwen/Qwen3.8-2.4T-A95B` is the motivating checkpoint; it is the same +architecture at larger scale, not a new one. + +In scope: + +- register the two text-only architecture strings against the existing dense and + MoE factories; +- accept both the VL-prefixed (`model.language_model.`) and clean (`model.`) + weight namespaces in the Qwen3.5 dense and MoE loaders; +- resolve a **flat** (non-nested, no `vision_config`) text config through the + existing path; +- prove the 27B / 35B / Coder gates stay byte-identical. + +Out of scope: any speed claim, any GGUF arm for 3.8, the vision tower +(a text-only checkpoint has none), MTP weights for 3.8, advancing the parity pin, +the **bf16 / 3-D-stacked MoE routed-expert arm** (owed, and refused by name +here — see [What this row does NOT make loadable](#what-this-row-does-not-make-loadable)), +and **any support claim for the 2.4T checkpoint itself**, which this hardware +cannot execute (see Gates). + +## Why this is not a new port + +`config.json` for `Qwen/Qwen3.8-2.4T-A95B` declares `Qwen3_5MoeForCausalLM` / +`model_type: qwen3_5_moe_text`. Against Qwen3.6-35B-A3B, which we run token-exact +315/315, every structural knob is identical — `head_dim` 256, +`linear_key/value_head_dim` 128, `linear_num_key_heads` 16, +`full_attention_interval` 4, `attn_output_gate` true, `partial_rotary_factor` +0.25, `rope_theta` 1e7, `mtp_num_hidden_layers` 1, `linear_conv_kernel_dim` 4, +and `vocab_size` 248320 (the same tokenizer). The differences are scale only: +hidden 2048->8192, layers 40->92, attention heads 16->64, KV heads 2->4, linear +V-heads 32->128, experts 256->512, top-k 8->10, moe/shared intermediate +512->2048. All of these are read from config, not hardcoded +(`qwen3_5_common.cpp:40-47`; the only expert constraint is `num_experts > 0` at +`qwen3_5_weights.cpp:618`). + +The 3.8 config also carries `output_gate_type: "swish"`, which normalizes to +silu. That key is handled by its own row (issue #489) and is not re-litigated +here. + +## Upstream chain + +| Upstream anchor | Contract to mirror | +|---|---| +| upstream `vllm/model_executor/models/registry.py:202-203` @ `ad5d29db7` | `Qwen3_5ForCausalLM` and `Qwen3_5MoeForCausalLM` are registered text-only arms of the same `qwen3_5` module. | +| upstream `vllm/model_executor/models/qwen3_5.py:439-449` @ `ad5d29db7` | `Qwen3_5ForCausalLM` is the shared base unchanged; `Qwen3_5MoeForCausalLM` is that base plus the MoE hyperparameters — no separate backbone. | +| upstream `vllm/model_executor/models/qwen3_5.py:296-300` @ `ad5d29db7` | `WeightsMapper(orig_to_new_prefix={"model.language_model.": "model."})` — the canonical namespace is `model.`, and the VL-prefixed form is accepted and rewritten. | + +**Ahead-of-pin, stated as such.** Our parity pin is `555967922`, whose registry +carries only the `ForConditionalGeneration` entries. The text-only arms arrived +upstream in **PR #50210 / `ad5d29db7`**, which is post-pin. This row is a +deliberate forward port of one upstream PR, not a mirror of the pin, and it does +not advance the pin or reconcile anything else in that range. That is visible +debt argued here and in the commit, not a silent divergence. + +## Design + +Weight-name evidence, read from the published indices of both checkpoints: + +| | Qwen3.6-35B-A3B (published) | Qwen3.8-2.4T-A95B (published) | +|---|---|---| +| embed | `model.language_model.embed_tokens.weight` | `model.embed_tokens.weight` | +| layer | `model.language_model.layers.0.linear_attn.*` | `model.layers.0.linear_attn.*` | +| experts | `...mlp.experts.gate_up_proj` + `.down_proj` (3D STACKED, 41x) | `...mlp.experts.gate_up_proj` + `.down_proj` (3D STACKED, 93x) | +| shared | `...mlp.shared_expert_gate.weight` | `...mlp.shared_expert_gate.weight` | +| head | `lm_head.weight` | `lm_head.weight` | +| quant scales | **NONE** — 0 `weight_scale`, 0 `input_scale` | **NONE** — 0 `weight_scale`, 0 `input_scale` | + +The **backbone** names are identical modulo the prefix, so the namespace +decision is the whole of what this row changes in the loader. **It is not the +whole of what either checkpoint needs**, and an earlier revision of this spec — +and of the two commits below it — wrongly said it was. See +[What this row does NOT make loadable](#what-this-row-does-not-make-loadable). + +1. **One prefix decision, resolved once.** The Qwen3.5 loaders currently + concatenate the literal `model.language_model.` in 4 places + (`qwen3_5_weights.cpp:560,632,633,659`) and 3 more in + `qwen3_5_dense_weights.cpp`. Replace the literal with a single resolved + backbone prefix, chosen once per checkpoint by probing which namespace the + shard index actually contains, then used everywhere. Mirrors upstream's single + `WeightsMapper` rather than scattering a fallback into each lookup — a + per-lookup fallback would let a checkpoint load half from one namespace and + half from the other and still appear to succeed. +2. **Registration is additive.** Two `REGISTER_VLLM_MODEL` entries pointing at + the existing dense and MoE factories. No factory, forward, or KV-cache change: + `ModelRegistry::Resolve` is exact-match with no aliasing + (`model_registry.cpp:217-231`), so the strings must be present literally. +3. **Config resolution already works.** `ResolveTextConfig` falls through to the + top-level document when there is no `text_config` + (`hf_config.cpp:113-122`), and `qwen3_5_moe_text` is already in + `IsQwen35Family` (`:128-132`), so the `partial_rotary_factor` 0.25 default + applies to a flat config. MRoPE is mm-path-only and every text caller passes + `nullptr` (`qwen3_5.cpp:7540`), so a config without `mrope_section` is + unaffected. Both facts get a test rather than an assumption. + +## What this row does NOT make loadable + +Corrected 2026-08-12 after an independent review returned FAIL on records +honesty. The registration and the namespace resolution are sound; the claim +built on top of them was not. + +**The MoE arm cannot read a published Qwen3.5-family MoE checkpoint, in either +namespace.** `LoadQwen3_5Moe` routes every routed expert through +`LoadMoeExpertsInto` (`qwen3_5_weights.cpp:519-530`) into `LoadNvfp4Raw` +(`:433-462`), which hard-requires per-expert `experts...weight` = `U8`, +`.weight_scale` = `F8_E4M3` and `.weight_scale_2`. There is **no stacked branch +and no bf16 branch** — unlike `gemma4_weights.cpp:326`, which dispatches between +layouts. Against that, the published indices (read live 2026-08-12): + +- `Qwen/Qwen3.8-2.4T-A95B`: 1609 tensors, 93x `mlp.experts.gate_up_proj` + + 93x `.down_proj` (3-D stacked, 92 backbone layers + 1 MTP), **zero** names + matching `weight_scale` or `input_scale`, `lm_head.weight` alone. +- `Qwen/Qwen3.6-35B-A3B`: 1045 tensors, the same stacked spelling under + `model.language_model.`, **zero** `weight_scale`. + +So the 2.4T load would die at `w.lm_head_fp4 = LoadNvfp4Raw(get, "lm_head")` +(`:679`) before the experts are even reached, and would die again at the FP8 +attention, the routed experts and the shared expert. Our gated 35B row reads the +REQUANTIZED `nvidia/Qwen3.6-35B-A3B-NVFP4`; this loader **has never read a +published Qwen bf16 MoE repo**. + +**The dense/MoE asymmetry is real and must not be flattened.** +`LoadQwen3_5Dense` DOES route BF16 vs FP8 vs NVFP4 per projection by tensor +presence (`qwen3_5_dense_weights.cpp:354-360,472-503`, and +`LoadDenseLmHead`/`LoadLmHeadAnyDtype` at `:215-233,515-547`), so the DENSE +text-only arm may genuinely load a flat bf16 checkpoint. Only the MoE arm +cannot. Any statement about "the text-only arms" that does not make that +distinction is wrong. + +**What is therefore OWED, named:** the **bf16 / 3-D-stacked MoE routed-expert +arm** (plus the bf16 shared expert, the bf16 FP8-less attention tower, and the +bf16 `lm_head`, all on the MoE path). That is a real port with its own spec, +RED-first test and NVFP4 inertness proof — it is not this row. Until it exists, +this row ships a **REFUSAL that names the missing piece** +(`CheckMoeExpertLayoutSupported`, `qwen3_5_weights.cpp`), because AGENTS.md +requires an unimplemented arm be refused by name rather than discovered later, +and this spec's own stop conditions said the same. + +**Consequence for the run gate.** "It closes when a text-only +`Qwen3_5[Moe]ForCausalLM` checkpoint that fits GB10 appears" is FALSE for the +MoE arm: a fitting *published* (bf16/stacked) MoE checkpoint would still be +refused at load. The MoE run gate needs a fitting checkpoint **whose routed +experts are per-expert NVFP4**, or the owed stacked/bf16 arm implemented first. +For the DENSE arm a fitting bf16 checkpoint is sufficient. + +## Risks + +- **Regression on gated rows.** These loaders serve 27B/35B/Coder. A prefix bug + breaks checkpoints we currently gate. Mitigated by byte-identical golden md5, + not by a green suite. +- **Half-resolved namespace.** Probing per lookup instead of once could load a + mixture. Mitigated by design point 1 and a test with a deliberately mixed + index, which must be refused. Refusing where upstream's `WeightsMapper` + NORMALIZES is a deliberate divergence in the strict direction and is recorded + as such in [porting-inventory](../porting-inventory.md) §9 deviation 17(c). +- **Untestable scale.** 92 layers / 512 experts is far past anything we can + instantiate. Mitigated by testing config resolution and name mapping directly, + and by *not* claiming the checkpoint runs. +- **Ahead-of-pin drift.** The forward-ported arm could diverge if upstream + changes it before our next sync. Recorded in + [porting-inventory](../porting-inventory.md) §9 deviation 17 as ahead-of-pin, + with the two arms added to its §5 Qwen3.5 row, so the next sync cycle + reconciles it deliberately. + +## Tests + +1. Architecture dispatch: a flat Qwen3.8-shaped config resolves + `Qwen3_5MoeForCausalLM` to the MoE registration, and `Qwen3_5ForCausalLM` to + the dense one. RED first — today both raise unsupported. +2. Config resolution on the real 3.8 shape: flat doc, no `vision_config`, no + `mrope_section`; assert the scale fields and the 0.25 rotary default. +3. Weight-name mapping: a clean (`model.`) index and a VL-prefixed + (`model.language_model.`) index both resolve every expected backbone tensor + name; a mixed index is refused. +4. Loader byte-equality, DENSE and MoE. Two synthetic one-layer checkpoints + with byte-identical payloads and only the namespace differing must load to + byte-identical weights through the production `LoadQwen3_5Dense` and + `LoadQwen3_5Moe`. The MoE case runs on BOTH expert-residency paths — + `shards_owner == nullptr` (eager) and non-null (deferred), with + `load_layer_experts` actually driven — because the deferred closure captures + the resolved prefix by value and executes after the resolving frame returns, + which is a third prefix site the dense loader has no analogue of. +4c. Refusal (added 2026-08-12): a synthetic checkpoint in the PUBLISHED shape — + 3-D stacked `mlp.experts.gate_up_proj` / `.down_proj`, bf16, no scale tensors + — must be refused by `LoadQwen3_5Moe` with a message that NAMES the offending + tensor and the required per-expert NVFP4 layout, in both namespaces; likewise + a per-expert-but-unquantized index and an NVFP4 index with a bf16 `lm_head`. + The supported per-expert NVFP4 layout must still load unchanged, asserted in + the same case so the gate cannot be satisfied by refusing everything. +5. Inertness: 27B/35B/Coder suites unchanged, golden md5 unchanged. The + per-layer seam DEFAULT is pinned by DRIVING `LoadQwen3_5MoeLayer` / + `LoadQwen3_5DenseLayer` with the prefix argument OMITTED — asserting the two + named constants does not pin it, and flipping both defaults VL->flat left the + original case green (review finding F7). +6. Record count (added 2026-08-13): the `MODEL` ratchet bump 362 -> 364 in + `scripts/check-agent-record.py` is tied to the two rows behind it — + `MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm` and + `MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` each appear exactly once in + `.agents/model-matrix.md`, and the pin equals the MODEL rows that file + carries. The existing ratchet test moves the pin by one, which holds for ANY + pin value and so cannot say whether THIS value is right; these do. + +## Gates + +- Focused: the targets above plus the Qwen3.5 dense/MoE suites. +- Full gate on the row before push. +- **The run gate is OWED and must be recorded as owed.** 2.4T bf16 is ~4.8 TB; + the only other released variant is `Qwen/Qwen3.8-2.4T-A95B-FP8` at ~2.4 TB; + GB10 has 128 GB unified and no smaller Qwen3.8 sibling exists. There is + therefore **no token-exact oracle run for this checkpoint**, and the row may + not reach `DONE` on the strength of dispatch and mapping tests. What this row + can honestly claim is that the architecture is registered and the weight + namespace resolves — nothing about generated tokens. + +If a `Qwen3_5ForCausalLM` checkpoint small enough to execute appears, that +becomes the DENSE run gate and closes that axis. **It does not close the MoE +axis**: a fitting *published* (bf16/stacked) MoE checkpoint would still be +refused at load, so the MoE gate needs one whose routed experts are per-expert +NVFP4, or the owed stacked/bf16 arm implemented first (see +[What this row does NOT make loadable](#what-this-row-does-not-make-loadable)). + +## Evidence required + +- RED capture of the dispatch test before registration. +- A mutation capture per prefix site — dense and MoE, including the deferred + expert closure — showing the hardcoded VL literal makes the flat load throw. +- Green focused + full gate after. +- RED capture of the refusal case before `CheckMoeExpertLayoutSupported` exists + (the literal `qwen3_5 weights: expected U8 for lm_head.weight`), plus a + mutation per refusal branch and one that refuses unconditionally, which must + turn the SUPPORTED-layout assertions red. +- Golden md5 before/after for 27B/35B/Coder showing no drift. +- The owed run gate recorded explicitly in the row and in `docs/STATUS.md`. +- Executable mutation evidence for the checker change, in + `tests/scripts/test_agent_record.py`, which is what `scripts/check-pr-size.py` + runs BASE-checker-against-HEAD-tree: the literal RED + `AssertionError: 364 != 362 : the MODEL pin must equal the MODEL rows + model-matrix.md carries` with the pin at its BASE value, a second RED from + deleting one of the two new matrix rows, and green after both restorations. + +## Stop conditions + +- If the prefix cannot be resolved once per checkpoint without touching the + per-tensor lookup contract, stop and return `NEEDS_DECISION` rather than + scattering fallbacks through the loader. +- If any 27B/35B/Coder golden md5 moves, stop — that is a regression on a gated + row, and this row carries no evidence that could justify it. +- Do not implement an MTP arm, a stacked/bf16 MoE expert arm, or a GGUF arm for + 3.8 on speculation; refuse them with a message naming the missing piece and + record them as owed. (The QUANTIZED arm is the one that IS implemented — the + earlier wording here had this inverted.) + +## Now + +Both rows are `PARTIAL` (2026-08-12). Registration, the once-per-checkpoint +backbone-namespace resolution and the tests above are landed on +`row/MODEL-QWEN38-TEXT-ONLY`; full CPU gate green (396/396, 1 skipped: +`test_voxtral_e2e`, fixture absent) and `tests/parity/goldens` md5-unchanged. + +**Next step is the OWED run gate, and nothing else advances these rows.** The +DENSE one needs a `Qwen3_5ForCausalLM` checkpoint that fits GB10; the MoE one +needs a fitting checkpoint whose routed experts are PER-EXPERT NVFP4, because a +published (stacked/bf16) MoE checkpoint is refused at load. Neither exists +today. Until they do, the honest claim stays "the architecture is registered, +the weight namespace resolves, and an unimplemented expert layout is refused by +name". + +Also owed, and deliberately NOT implemented on speculation: the **bf16 / +3-D-stacked MoE routed-expert arm** (this was recorded INVERTED as "the +quantized arm is owed" until 2026-08-12 — the quantized arm is the only one +implemented), and the MTP and GGUF arms for 3.8. + +## Outcome + +**Measured.** Architecture dispatch for both strings, config resolution on the +PUBLISHED `Qwen/Qwen3.8-2.4T-A95B` `config.json` (committed verbatim as +`tests/vllm/models/fixtures/qwen3_8_2_4t_a95b/config.json`, md5 +`303dc59227f1d03afc941646e8df3132`) — the scale fields, the 92-entry +`layer_types` list and its `[linear,linear,linear,full] x 23` pattern, the NESTED +`rope_parameters` block both loaders' rope actually reads, and the absence of +`text_config` / `vision_config` / `mrope_section` — and weight-namespace +resolution on a clean index, a VL-prefixed index, a vision-inclusive VL index, an +index carrying `mtp.*`, a mixed index and an empty one. The strongest of these is +not a name-mapping assertion: two synthetic one-layer checkpoints with +byte-identical payloads and only the namespace differing load to byte-identical +weights through the production `LoadQwen3_5Dense` — and, on the MoE arm this row +exists for, through the production `LoadQwen3_5Moe` on BOTH expert-residency +paths, the deferred `load_layer_experts` closure included. Each of the three MoE +prefix sites was reverted to the hardcoded VL literal in turn and each RED is the +flat checkpoint failing to bind: `layers.0.input_layernorm.weight` (per-layer +base), `embed_tokens.weight` (top level) and `layers.0.mlp.experts.0.gate_proj +.weight` (the deferred closure). + +**Rejected.** A per-lookup namespace fallback — it would let a checkpoint bind +half its tensors from each namespace and still appear to load, which is exactly +the failure a name-mapping test cannot see. Also rejected: a blanket +"starts with `model.`" probe, because `model.visual.*` on a vision-inclusive 27B +checkpoint would have made it look like a flat text checkpoint and turned a +checkpoint we gate today into a refusal. Only the three structural backbone +spellings vote. + +**Why the defaults are what they are.** The per-layer public seams +(`LoadQwen3_5MoeLayer`, `LoadQwen3_5DenseLayer`) default `backbone_prefix` to the +VL spelling, so every 27B/35B/Coder caller is byte-identical by construction +rather than by re-measurement. The text-only arms register with +`kQwen3_5TextInfo` (hybrid YES, multimodal NO) because upstream's +`Qwen3_5ForCausalLMBase` inherits `IsHybrid` but not `SupportsMultiModal`; the +`ForConditionalGeneration` wrappers remain the multimodal registrations. + +**What was NOT established, and what an earlier revision wrongly claimed.** Any +claim about generated tokens, memory or speed for `Qwen/Qwen3.8-2.4T-A95B` — +that checkpoint cannot be executed on this hardware and was never run. And, +corrected 2026-08-12 after a review FAIL, **any claim that hardware size is the +only thing between this code and that checkpoint**: it is not, because the MoE +loader reads only per-expert NVFP4 experts and both published Qwen MoE repos +ship 3-D stacked, unquantized ones. That arm is OWED and is now refused by a +message naming it (`CheckMoeExpertLayoutSupported`), with the fixture and the +literal RED in `tests/vllm/models/test_qwen3_8_text_only.cpp`. The DENSE loader +routes BF16/FP8/NVFP4 by tensor presence and is not subject to that gap — the +asymmetry is deliberate record, not an oversight. + +**The refusal was verified against the REAL gated checkpoint, and its `mtp.` +exclusion is LOAD-BEARING.** Added 2026-08-12 after an independent review. The +published `nvidia/Qwen3.6-35B-A3B-NVFP4` safetensors index was fetched and read +directly (`model.safetensors.index.json`, **124,468 tensors**): it **does** +contain the exact 3-D stacked spelling `CheckMoeExpertLayoutSupported` refuses, +as `mtp.layers.0.mlp.experts.gate_up_proj` and `mtp.layers.0.mlp.experts +.down_proj` — and only there. Under the resolved backbone +(`model.language_model.`) there are **zero** stacked expert names, **zero** +expert `.weight` without a `_scale` sibling, and both `lm_head.weight_scale` and +`lm_head.weight_scale_2` are present. So the checkpoint we gate today is **not** +refused — but only because the scan is anchored at `layers.` +(`qwen3_5_weights.cpp:633,638`) and `mtp.` is under neither backbone spelling. + +That exclusion was pinned by nothing in-tree. Broadening the scan to every +`.mlp.experts.` name would refuse the one checkpoint this arm is gated on, on a +**CUDA-only load path**, with the entire CPU suite still green. The supported +fixture (`MoeOneLayerSpecs`) therefore now carries those two `mtp.` names, so +the shape of the real index is what the inertness assertions run against, and +case 4c gains a subcase that both re-asserts the fixture still carries them +(count `== 1`, and zero under either backbone prefix) and that the load stays +clean. RED-first: with the `layers.` filter dropped, the new subcase +fails with `3-D stacked routed experts are not implemented ... found +"mtp.layers.0.mlp.experts.gate_up_proj"` (2 cases failed, `Status: FAILURE!`, +assertion count 747 → 277 as the thrown cases abort); `src/` restored +byte-for-byte afterwards and back to 7/7, 747/747. + +**What the "refused by name" guarantee does and does not cover.** It covers the +routed experts and `lm_head` only: the stacked spelling, a per-expert `.weight` +with no `.weight_scale` beside it, and an unquantized `lm_head` that is present +(a checkpoint with no `lm_head.weight` at all is the tied-head case and is +deliberately not this refusal). Everything else on the MoE path still surfaces +its raw loader error — a bf16 **shared** expert, a bf16 (FP8-less) attention +tower, the compressed-tensors `weight_packed` spelling, and a tied-head MoE +checkpoint. No surface claims otherwise, so this is a clarification of scope +rather than a gap; widening the refusal belongs with the owed stacked/bf16 arm, +which has to read those layouts anyway. + +**Recorded as tracked debt** in [porting-inventory](../porting-inventory.md) §9 +deviation 17, with the two arms carried on its §5 Qwen3.5 row and the owed run +gate on [BENCHMARKS](../../docs/BENCHMARKS.md) §Open gaps: the ahead-of-pin +anchor `ad5d29db7` (17a/b), the deliberate REFUSAL of a mixed namespace where +upstream's `WeightsMapper` would normalize it (17c), the published config's +transformers-4.57.3 `dtype` key, which `hf_config.cpp:520-522` does not consume +(17d — inert, no reader, and a fix would touch every model, so it is pinned by +an assertion rather than smuggled in here), and the **unimplemented bf16 / +3-D-stacked MoE routed-expert arm** (17e, added 2026-08-12 — it was previously +recorded inverted, as the quantized arm being the owed one). + +**The safetensors alignment class** ([#627](https://github.com/mudler/vllm.cpp/issues/627), +fixed 2026-08-13). This row's `test_qwen3_8_text_only` was the first gate ever to +run a safetensors weight loader under UBSan, and it went RED on +`qwen3_5_weights.cpp:298` — `load of misaligned address ... for type +'const uint16_t', which requires 2 byte alignment`. **The defect is pre-existing +(`8ee2c0766`), not this row's**; what this row supplied is the first synthetic +checkpoint whose tensor offsets are not all even, which is a legitimate shape a +real file can have because a safetensors offset is just the running byte total of +everything ahead of it. + +The observed site was one instance of a class. A sweep of every +`reinterpret_cast(.data)` in `src/vllm/model_executor/models/` +found **fifteen** across nine `*_weights.cpp` loaders, including two with +stricter-than-2-byte requirements: `olmo2_weights.cpp` forms a `const float*` +(4-byte) and `qwen3_dspark_weights.cpp` a `const int64_t*` (8-byte). All fifteen +now go through `vt::LoadUnaligned` — the seam `ea4deb203` introduced and that +`dense_loaders::TransposeBf16` and `minimax_h3_vae_loader.cpp:87` already used; +the local `qwen3_5_weights.cpp` copy of `TransposeBf16` had simply never been +migrated to it. Two sites could not take a byte pointer and were handled in kind: +`internlm2_weights.cpp` only ever bulk-`memcpy`s from its source, so it keeps a +`const uint8_t*` and scales its offsets, and `gemma4_weights.cpp` feeds a typed +scale pointer to `DequantFp8ChannelToBf16` (whose header is outside this row's +authority), so it copies the N-element scale row into an aligned buffer first. + +**Inertness is proven three ways, not asserted.** (1) `vt::LoadUnaligned` is +`memcpy`, which is bit-identical to `*(const T*)p` on every input the old code +was *allowed* to read — a scratch harness ran the original and rewritten form of +all six loops over one payload at an aligned base and got byte-identical output, +then reproduced those same bytes from the rewritten form at misaligned bases 1 +through 8. (2) `tests/parity/goldens` is untouched and every golden-comparing +suite passes. (3) Full CPU gate 404/404 (2 skipped: `test_voxtral_e2e` and +`test_modelopt_mixed_precision_checkpoint`, fixtures absent) and — the gate that +was red — full ASan+UBSan 404/404, `test_qwen3_8_text_only` back to 7/7 and +747/747 with zero runtime errors. + +**No load-time regression, checked rather than assumed.** `TransposeBf16` is a +hot load-time loop and a naive per-element `memcpy` is exactly the kind of change +that can turn one load into a call. It does not here: at `-O2` the old and new +inner loops are instruction-for-instruction identical — the same six +instructions, the same `movzx REG, WORD PTR [rax]`, no call emitted. The whole +delta is five prologue instructions (one callee-saved push/pop pair and two +address setups), paid once per call, not per element. + +**Still owed, and deliberately not touched here** because they fall outside this +row's authority (`*_weights.cpp`): the same cast survives at +`voxtral.cpp:51,347`, `qwen3_vl.cpp:78` and `qwen3_5_mtp.cpp:71`. The first three +are genuine misaligned *loads* of the same severity as the one UBSan caught; the +`qwen3_5_mtp.cpp` one only forms the pointer and then `memcpy`s through it, so it +is UB but will not fire the `alignment` check. None is reached by any suite that +runs under sanitizers today, which is precisely why they need a follow-up rather +than a grep. diff --git a/.agents/specs/rocm-decode-attn-d128.md b/.agents/specs/rocm-decode-attn-d128.md new file mode 100644 index 000000000..8b964aa57 --- /dev/null +++ b/.agents/specs/rocm-decode-attn-d128.md @@ -0,0 +1,330 @@ +# ROCm decode paged-attention at head_dim=128 — the ROCm arm of #382 + +**Row:** `BACKEND-ROCM` (backend-matrix, `ACTIVE`) — the row this change's code +lives in. Its issue, #382, is filed against `KERNEL-ATTN-PAGED`, the +cross-backend kernel row (state `ANCHOR-BACKFILL`). The two differ deliberately: +the defect is cross-backend, this change is the ROCm arm of it, and +`check-agent-record` requires an active claim to name a `SPIKE`/`ACTIVE` row. +**Claim:** `CLAIM-ROCM-DECODE-ATTN-D128`. +**Issue:** [#382](https://github.com/mudler/vllm.cpp/issues/382) — "decode-opt +attention kernel is head_dim-256 only; head_dim 128 (Qwen3-dense, Llama, +Mistral) falls to the block kernel." This spec is the **ROCm half** of that +issue. The CUDA half already landed as +[PR #425](https://github.com/mudler/vllm.cpp/pull/425) (`66399617`); #382 stays +open because the flip to default-ON is still owed and only CUDA was covered. +**Motivating measurement:** [#488](https://github.com/mudler/vllm.cpp/issues/488) +— the ROCm per-call decode-attention gap (`PagedAttnOnline` 41.1us vs vLLM's +5.10us fallback kernel, 8.1x) on gfx1200. #488 asserts no cause; this spec +supplies one of them and does not close it. +**Base:** `origin/main` `fafa16f0` after rebase (the claim file records the +same), fresh branch (not stacked — this touches +`rocm_paged_attn.hip`/its cross-device test, disjoint from #506's +`rocm_skinny_gemm.hip`/`rocm_matmul_hipblaslt.hip`). +**Board:** AMD Radeon RX 9060 XT (`gfx1200`, Navi 44, RDNA4, discrete, 32 +CUs), ROCm 7.2.3, hipClang/Clang 22.0.0. +**Reference checkout:** `${VLLM_SOURCE}` pinned at `5559679229bc` (parity pin +`555967922`). + +--- + +## 1. Reconciliation: this gap already had an issue and a landed CUDA fix + +Per the protocol's re-verify-before-claiming rule, this section records what +already existed, because the answer changed the shape of the change. + +[#382](https://github.com/mudler/vllm.cpp/issues/382) — filed by an outside +contributor against `KERNEL-ATTN-PAGED` — names this defect exactly, on the +CUDA file, and proposes the same fix this spec implements: template the +decode-opt kernel on elements-per-lane and add an `EPL=4` instantiation. +[PR #425](https://github.com/mudler/vllm.cpp/pull/425) landed that for CUDA. + +So the ROCm change is a **mirror of merged work**, not new design. It adopts +the merged arm's flag, default, and stated reason verbatim +(`cuda_paged_attn.cu`, `DecodeD128Enabled`). Where the two backends' facts +differ — and they do, sharply in §5's measurement and materially in §7's dtype +coverage, where this arm is **narrower** than the CUDA one — the difference is +recorded rather than averaged away. + +## 2. Why `PagedAttnOnline` was what ran + +Reading the ROCm dispatch before this change: + +```c +const bool bf16_decode_opt = + decode_opt && (d == 256 || d == 512) && query.dtype == DType::kBF16 && ... +``` + +Every fast decode kernel this file has (`PagedAttnDecodeOptBf16T`, +`PagedAttnDecodeGqaBf16`) was gated to `d == 256 || d == 512`. Qwen3-0.6B's +`head_dim` is **128** — the standard value for the great majority of dense/GQA +models this project supports (Qwen3 0.6B/1.7B/4B, Llama, Mistral). So +`bf16_decode_opt` was false for every one of them and every call fell straight +through to `PagedAttnOnline`. Not a gfx1200-specific gap, nor a WMMA-specific +one: the file had no fast path at all for the head size these models use. This +project's own Metal test (`test_metal_backend.cpp:915`, "Qwen3 geometry … +head_dim 128") independently names `d=128` as the real-model shape. + +Cross-checked against the pinned oracle (`csrc/rocm/attention.cu` @ +`555967922`): its `CALL_CUSTOM_LAUNCHER_BLK_HEAD` dispatch switches on +`head_size` with cases **64 and 128** (`attention.cu:3609-3620`), including on +RDNA4 via the `is_navi_gpu()` (`arch.find("gfx11")==0 || arch.find("gfx12")==0`) +launcher variant. Upstream's fast kernel covers `d=128`; ours didn't. + +## 3. Why this is a small change, not a new kernel + +`PagedAttnDecodeOptBf16T` and `PagedAttnDecodeGqaBf16` are +already generic over head_dim via `EPL` (elements-per-lane = `d/32`, one +wavefront's lanes splitting a `d`-wide row). Every online-softmax computation, +shared-memory sizing and warp-shuffle reduction already parametrizes on +`d`/`EPL` with no 256/512-specific assumption in the kernel body. The **only** +hardcoded constraint was in the vectorized load/store helpers: + +```c +template +__device__ inline void LoadRowEplBf16(...) { + static_assert(EPL == 8 || EPL == 16, "EPL"); // uint4-sized loads only +``` + +`EPL=4` (`d=128`) needs an 8-byte (`uint2`) load instead of a 16-byte (`uint4`) +one — the same pattern, one size down. No new algorithm, tiling, or +synchronization. This is the same observation #382 made about the CUDA file. + +## 4. What will change + +`src/vt/rocm/rocm_paged_attn.hip`: + +1. `LoadRowEplBf16<4>`/`StoreRowEplBf16<4>` — `uint2`-sized vectorized + load/store, same shape as the existing `EPL==8` branch. +2. A `VT_ATTN_DECODE_D128` gate, **default OFF**, read once beside the existing + `decode_opt`/`decode_gqa` flags — the same env var, default and rationale as + the merged CUDA arm. +3. `bf16_decode_opt` gate: `d == 256 || d == 512` → `(d == 128 && (decode_d128 + || decode_wmma)) || d == 256 || d == 512`. The `decode_wmma` disjunct is + deliberate: the rocWMMA arm (separate spec) is a second, independently + opt-in kernel for the same head size, and without it a bare + `VT_ATTN_DECODE_WMMA=1` would be a silent no-op. **Forward reference:** + neither `VT_ATTN_DECODE_WMMA` nor `rocm-decode-attn-d128-wmma.md` exists yet + — in the tree or in this PR — so an implementer working from this section + must land the flag with the rocWMMA arm, not cite it from here. +4. `decode_gqa` fused-head condition extended to `qg == 2 && (d == 128 || d == + 256 || d == 512)` (Qwen3-0.6B/1.7B are `qg=2`; Qwen3-4B is `qg=4`, not fused + at any `d` today — falls to per-head `PagedAttnDecodeOptBf16T`, still off + `PagedAttnOnline`). +5. Two launch switches (`DecodeGqa`, `DecodeOptBf16T`) extended with a + `d == 128` case. + +No change to any prefill path — those stay gated to `d==256||512` and are out +of scope (§7); a `d=128` prefill call already falls through their internal +`else { goto flash_fallback; }` guards to the decode-shaped launch. + +### Why default OFF + +Verbatim from the merged CUDA arm's reason, which applies identically here: the +arm is correctness-complete but **not byte-exact** against the kernel it +replaces. Warp-strided online softmax reduces the KV sequence in a different +**order** than `PagedAttnOnline`'s per-tile loop, so a greedy anchor can move +at an exact bf16 tie. Shipping OFF keeps every existing golden byte-identical. +The flip owes the near-tie razor, a distributional gate, and regen under the +ratified-tie rule — on **both** backends, and is named as owed in §7. + +### Test coverage + +`tests/vt/test_backend_cross_device.cpp`: new case, "paged attention at Qwen3 +geometry (bf16, GQA 2, head_dim 128) matches the CPU oracle" — mirrors the +Metal "Qwen3 geometry" test's shape (`nblocks=24, bsz=16, hq=16, hkv=8, +dh=128`, 2 requests mixing a 40-token prefill and a 5-token decode-with-context, +so both the prefill fallthrough and the decode dispatch are exercised in one +call), looped over `RegisteredDevices()` so it also covers Metal/CUDA/CPU. +Checks NMSE ≤ 5e-4 against the CPU oracle and `OpProviderStats::declines == 0`. +Genuinely new coverage: the existing generic paged-attention cross-device test +at `d=8, f32` never reached any bf16 `EPL`-templated kernel, so none of them had +bf16 correctness coverage in this suite. + +`tests/CMakeLists.txt`: because the arm ships OFF **and** its flag is read into +a `static const bool` — once per process — the default registration only ever +gates the `PagedAttnOnline` fallback. Two extra ctest registrations re-run the +same binary filtered to this case with `VT_ATTN_DECODE_D128=1` and +`VT_ATTN_DECODE_WMMA=1`, so the arms that actually run the new kernels are +gated. Same shape as the existing `test_dense_gateup_fused_marlin_off_*` pair. +Verified non-vacuous (the trap `SKIP_RETURN_CODE 77` exists for, issue #463): +the filter resolves to `test cases: 1 | 1 passed`, `assertions: 6 | 6 passed`, +not zero. + +## 5. Evidence + +**The gate is live in both directions.** Same binary, no rebuild — an A/B that +was not possible before this change, because the arm was unconditional. +Qwen3-0.6B, 1024-token input, 32 output, concurrency 1, seed 0, GPU lock held: + +| `VT_ATTN_DECODE_D128` | TPOT rep1 | TPOT rep2 | +|---|---|---| +| unset (default — `PagedAttnOnline`) | 44.82 ms | 44.82 ms | +| `=1` | 12.80 ms | 12.60 ms | +| | **3.53x** | | + +**Decode throughput**, in-engine, 128in/128out, concurrency 1, isolated +same-binary A/B, back-to-back: + +| Model | Off (tok/s) | On (tok/s) | Speedup | TPOT off → on | +|---|---|---|---|---| +| Qwen3-0.6B (GQA=2, fused) | 53.84 | 76.84 | **+42.7%** | 13.80 → 8.12 ms | +| Qwen3-1.7B (GQA=2, fused) | 32.23 | 40.30 | **+25.0%** | 26.06 → 19.82 ms | +| Qwen3-4B (GQA=4, per-head only) | 20.70 | 24.38 | **+17.8%** | 42.53 → 35.44 ms | + +Single run per cell on a board that may also drive a display — indicative, not +the 2-3x-idle-reproduced standard; matches the caveat already carried by +[rocm-gfx1200-m2-correctness.md](rocm-gfx1200-m2-correctness.md) for this +board. The 1024-token table above is 2 reps and is the tighter number. GQA=2 +models (fused kernel) win more than the GQA=4 model (per-head only) — +consistent with the fused kernel halving K/V reloads per its own header +comment, though this run does not isolate that mechanism via a trace. + +**The cross-architecture reversal is the most important row in this spec.** +#382 measured the same `EPL=4` arm on sm_110 / Jetson AGX Thor as **1.6x +slower** than the block kernel it replaces (81.6 → 131.0 at c=1, and worse at +c=8). We measure **3.53x faster** on gfx1200. Both can be true: they are +different kernels (`PagedAttentionDecodeOptKernel` vs `PagedAttnDecodeGqaBf16`), +different fallbacks (CUDA's generic *block* kernel vs ROCm's `PagedAttnOnline`), +different wave widths (32 on both, but different occupancy and LDS budgets), and +different memory systems. **What this reversal forbids is a shared default.** +Any future flip to default-ON must be argued per backend with per-backend +measurement; the fact that the ROCm arm is a large win is not evidence for the +CUDA arm, and #382's sm_110 regression is not evidence against this one. + +**Correctness**, gfx1200, real hardware: `ctest -R 'rocm|cross_device'` **6/6 +pass**, including both new flag-on registrations. Full `ctest` 393 tests, +385 passed / 8 failed; all 8 reproduce identically (same tests, same root cause +`vt: no kernel for op 63 on device type 5`, an unrelated pre-existing ROCm +op-registration gap) on an isolated build of this branch **without** this +change — confirmed not caused by it. + +## 6. What this does not claim + +- **It does not close #488.** #488 reports a per-call gap against vLLM and + asserts no cause. This removes one cause. No same-tool per-call re-measure + against the oracle was run after the change (blocked on the container/glibc + issue recorded in the WMMA spec), so the residual gap is unquantified. +- **No ceiling.** The next traceable hypothesis is the `qg=4`/`qg=8` fusion gap + (§7) and, above it, the skinny-GEMM lever in #487, which #488 itself notes is + the larger share of ROCm decode time. + +## 7. Scope + +**In scope.** The `rocm_paged_attn.hip` edits in §4, the new bf16 `d=128` +cross-device test and its two flag-on ctest registrations, and this spec. + +**Out of scope, named and owed.** +- **The flip to default-ON, on both backends.** Owes the near-tie razor, a + distributional gate, and golden regen under the ratified-tie rule. Per §5 it + must be argued per backend, not once. This is what keeps #382 open. +- **rocWMMA for `d=128`.** A second, independently-flagged kernel for the same + shape — its own spec (`rocm-decode-attn-d128-wmma.md`, landing separately), + its own claim, its own issue. +- **Every dtype combination except all-`bf16` — and here the ROCm arm is + narrower than the CUDA arm it mirrors.** `bf16_decode_opt` requires `query`, + `k_cache`, `v_cache` and `out` to *all* be `kBF16`, and the vectorized helpers + are bf16 by construction (`LoadRowEplBf16`/`StoreRowEplBf16`). The fallback + dispatch supports **five** combinations — `bf16/bf16/bf16`, `f32/f32/f32`, + `bf16/bf16/f32`, `bf16/f32/bf16`, `f32/bf16/f32` — so **four of the five still + fall to `PagedAttnOnline` at `d=128`**, which is the exact fallback this arm + exists to get off. + + The CUDA arm does not have this limitation. It added `LoadRowN<4, float>` + *beside* `LoadRowN<4, __nv_bfloat16>`, and its `d == 32 * 4` launch branch + carries no dtype gate at all, so f32 reaches the decode-opt kernel there + (`cuda_paged_attn.cu:2796` dispatches `LaunchDecode` for + `out.dtype == kF32`). §1 calls this change a mirror of merged work; **on dtype + coverage it is not one**, and that is recorded here rather than left to be + discovered. + + The limitation is pre-existing, not introduced: ROCm's decode-opt has been + bf16-only at *every* head_dim, so this is a gap the `d=128` arm inherits + rather than creates. It is owed, not intended, and it is not waived — an f32 + `d=128` decode on ROCm is silently slow rather than refused, which is the + weaker of the two failure modes AGENTS.md allows. +- **`qg=4`/`qg=8` GQA fusion at any `d`.** `PagedAttnDecodeGqaBf16`'s fused + condition only ever covered `qg==2` and `qg==8 && d==512`; `qg=4` (Qwen3-4B) + was never fused at 256/512 either. Real, but head-dim-independent — its own + follow-on. +- **Prefill at `d=128`.** Prefill kernels stay `d==256||512`-only. +- **Explaining why GQA=2 wins more than GQA=4** via kernel tracing. Named, not + run. + +## 8. Risks and decisions + +| Risk | Assessment | +|---|---| +| The reduction-order change moves a greedy anchor at a bf16 tie | This is why the arm ships **default OFF**, adopting the merged CUDA arm's flag, default and stated reason verbatim rather than inventing new ones. A default-ON flip is a separate, per-backend argument and is explicitly out of scope here. | +| The 3.53x is a single-board, single-run figure | Measured on one gfx1200 that may also drive a display. It is indicative, not the idle-box reproduced standard AGENTS.md requires for a binding number, and §5 says so. It justifies building the arm; it does not license a BENCHMARKS entry or a default flip. | +| The same arm measured 1.6x SLOWER on sm_110 (#382) | Recorded, deliberately not reconciled. It is the reason the default stays OFF and the reason the flip must be argued per backend rather than once. Treating the ROCm number as settling the question for all boards is the error this row is guarding against. | +| The spec lands before its code | Intended, and required — AGENTS.md puts the spec before implementation. The consequence is that §4 and the result section describe an unmerged branch, which the banner above the result section states outright so no reader mistakes it for landed work. | +| `VT_ATTN_DECODE_WMMA` is cited but does not exist | A forward reference to a sibling arm whose spec and issue are not yet filed (§4). An implementer must land the flag alongside the rocWMMA arm; taking §4 literally today produces a reference to an undefined symbol. | +| The residual #488 gap is unquantified | No post-change per-call oracle re-measure was run, so how much of #488 this closes is unknown. Named here rather than left implicit; it is owed before #488 can be judged. | + +## 9. Stop conditions + +- **Stop if the token-exact gate moves with the flag OFF.** The arm is opt-in; + flag-off behaviour must be byte-identical to today. Any drift means the gate + predicate is wrong, not that the golden needs refreshing. +- **Stop if the flag-ON arm cannot be shown to reach the new kernel.** A green + gate that never entered `PagedAttnDecodeOptBf16T` proves nothing — the ctest + registrations in §4 exist precisely so the opt-in arms are gated rather than + silently skipped. Confirm selection counts, not just tokens. +- **Stop before flipping the default.** That needs its own row, and needs the + sm_110 reversal in §6 reconciled rather than out-voted by one board. +- **Stop if the measured speedup on a second board contradicts the first.** + Two boards disagreeing is a finding about the kernel, not noise to average. +- **Do not extend this row to the rocWMMA arm.** It is a separate, independently + flagged kernel with its own spec and its own issue, both still unfiled. + +## 10. Reproduction + +```sh +nix develop .#rocm-shell --command bash -c ' + cmake -S . -B build-hip -G Ninja -DVLLM_CPP_HIP=ON \ + -DVLLM_CPP_HIP_ARCHITECTURES=gfx1200 -DROCM_PATH=$ROCM_PATH \ + -DCMAKE_BUILD_TYPE=Release + cmake --build build-hip -j"${JOBS:-8}" +' +flock "$HOME/gpu.lock" -c ' + nix develop .#rocm-shell --command ctest --test-dir build-hip \ + -R "rocm|cross_device" --output-on-failure +' +# Flag A/B, same binary: +flock "$HOME/gpu.lock" -c ' + build-hip/examples/vllm-bench --model --num-prompts 2 \ + --input-len 1024 --output-len 32 --concurrency 1 --seed 0 + VT_ATTN_DECODE_D128=1 build-hip/examples/vllm-bench --model \ + --num-prompts 2 --input-len 1024 --output-len 32 --concurrency 1 --seed 0 +' +``` + +## Result on the implementation branch (2026-08-12) + +> **Not landed.** This section records what was built and measured on the +> unmerged implementation branch. No `VT_ATTN_DECODE_D128` exists in +> `src/vt/rocm/` on `main` — `git log -S'VT_ATTN_DECODE_D128' -- src/vt/rocm/` +> is empty, and `rocm_paged_attn.hip` still gates on `d == 256 || d == 512`. +> This spec is committed BEFORE its implementation, per AGENTS.md; the section +> becomes `## Outcome` when the code merges and the row reaches `DONE`. + +**Built the ROCm `d=128` decode arm, default OFF, mirroring the merged CUDA +arm of the same issue.** Root cause for the ROCm decode-attention gap #488 +measured was neither architecture- nor WMMA-specific: no fast decode kernel +existed for `head_dim=128`, the size every locally-tested model uses, on any +board. #382 had already named this and PR #425 had already fixed the CUDA half; +this is the mirror, adopting that arm's flag (`VT_ATTN_DECODE_D128`), default +(OFF) and reason (reduction-order change can move a greedy anchor at a bf16 +tie) rather than inventing new ones. + +Measured on gfx1200 with the gate exercised both directions on one binary: +**3.53x** TPOT at 1024-token context, and +42.7% / +25.0% / +17.8% decode +throughput on Qwen3-0.6B / 1.7B / 4B. New bf16 `d=128` GQA correctness coverage +where none existed, plus two flag-on ctest registrations so the opt-in arms are +actually gated rather than silently skipped. + +**The finding worth carrying forward is the reversal:** #382 measured this same +arm 1.6x *slower* on sm_110, where we measure it 3.5x *faster*. That is +recorded, not reconciled, and it is the reason the default-ON flip must be +argued per backend. Rejected: landing default-ON on the strength of the ROCm +number alone. 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/.agents/specs/tool-parser-breadth.md b/.agents/specs/tool-parser-breadth.md index 513083d93..25e59f691 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,64 @@ 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: +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 | -|---|---|---:| -| `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** | +| 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. **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 + `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 @@ -56,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 @@ -69,20 +113,87 @@ 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. **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 + (`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. + + **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 + 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 +212,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 +246,297 @@ 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 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 +`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. + +**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/.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/specs/windows-msvc-m-pi.md b/.agents/specs/windows-msvc-m-pi.md new file mode 100644 index 000000000..8a88d3b41 --- /dev/null +++ b/.agents/specs/windows-msvc-m-pi.md @@ -0,0 +1,149 @@ +# Native MSVC `M_PI` repair — the LTX-2.5 pi spellings + +Identity: `ENG-RELEASE-WINDOWS` + +Issue: [#720](https://github.com/mudler/vllm.cpp/issues/720) + +Parent specification: [windows-binary-release.md](windows-binary-release.md) + +Predecessor repair: +[windows-msvc-strict-build.md](windows-msvc-strict-build.md), whose Design +already ruled: "Use standard C++20 math constants rather than enabling +non-standard `M_PI`." This repair applies that ruling to the LTX-2.5 sources +that landed after it. + +Status: `ACTIVE`. Base `a4313641413394e48e6e7a2f05d8e13d541f8be0`. + +## Scope + +Remove every `M_PI` from `src/` and `tests/` and replace it with +`std::numbers::pi_v`, the spelling this repository already uses at four +existing sites. Delete the two hand-rolled `#ifndef M_PI / #define M_PI` blocks +in the LTX-2.5 VAEs. Change no value, no expression structure, no tolerance, no +golden, and no runtime behavior. + +Explicitly excluded: `_USE_MATH_DEFINES` in any form; any new named pi constant +of our own; `src/vllm/multimodal/video_engine.cpp` (issue #664, PR #677); the +second, unrelated failure in the same portability suite (issue #680). + +## Observed baseline and root cause + +`M_PI` is a POSIX extension, not standard C++. MSVC's `` defines it only +when `_USE_MATH_DEFINES` precedes the include, so every unguarded use is a hard +C2065. `windows-msvc-cpu` on PR #677 reported: + +``` +src\vllm\model_executor\models\ltx2.cpp(72,58): error C2065: 'M_PI': undeclared identifier +src\vllm\model_executor\models\ltx2.cpp(72,15): error C2737: 'kBeta': const object must be initialized +src\vllm\model_executor\models\ltx2.cpp(557,69): error C2065: 'M_PI': undeclared identifier +src\vllm\model_executor\models\ltx2.cpp(572,76): error C2065: 'M_PI': undeclared identifier +``` + +The intake counted "three LTX-2.5 TUs", from `git grep -l M_PI -- src include`. +**That count is wrong in both directions**, and measurement rather than grep is +what separates them: + +- `ltx2_audio_vae.cpp` and `ltx2_video_vae.cpp` carry their own + `#ifndef M_PI / #define M_PI 3.14159265358979323846` guard, so both compile + under MSVC today. They are not build breaks. They are two extra spellings of + pi — the defect [#687](https://github.com/mudler/vllm.cpp/issues/687) names — + and `ltx2_video_vae.cpp`'s guard has **zero uses**: it is dead. +- `tests/vllm/models/test_vocoder1d.cpp` has two unguarded uses and **is** a + hard MSVC break. `git grep -- src include` never looked at `tests/`, and the + CI log never named it because the library build stops at `ltx2.cpp` first. + +So the real MSVC break is two TUs, not three, and one of the two was invisible +to both the CI message and the intake grep. + +## Design + +`std::numbers::pi_v` from ``, available at this project's +`CMAKE_CXX_STANDARD 20` (`CMakeLists.txt:35`), and already the spelling at +`mla_attention.cpp:82`, `deepseek_v4.cpp:602`, `minimax_h3_video_vae.cpp:258`, +and `vocoder1d.cpp:35`. One definition — the standard's — reached by an include +rather than by a macro whose correctness depends on preceding every transitive +``. No fifth spelling is introduced. + +`_USE_MATH_DEFINES` is rejected: it is order-dependent across transitive +includes, and it re-declares the POSIX extension instead of removing the +dependency on it. + +## Tests and RED evidence + +No checker changes. `tests/scripts/test_check_windows_portability.py`'s +`test_real_tree_uses_portable_windows_allocation_and_math` **already** asserts +that no file under `src/` or `tests/` contains `\bM_PI\b`, and it is already RED +on `main` for exactly these four files. That committed assertion is the RED, and +it is also why the `tests/` TU is in scope: the existing contract always covered +it. Because that suite runs in no workflow (#646, #680), the assertion had gone +unheld while the LTX-2.5 lane landed. + +The MSVC diagnostic itself is additionally reproduced on Linux by compiling the +real TUs with `-U_GNU_SOURCE -D_ISOC99_SOURCE`, which removes glibc's `M_PI` +from `` and so recreates MSVC's header condition on g++. + +Value identity is asserted, not assumed, three ways: the bit pattern of both +spellings, a token-sequence equivalence proof that the diff restructures no +expression, and a before/after run of the seven LTX-2.5 and vocoder golden +suites. + +## Gates + +1. RED: `test_real_tree_uses_portable_windows_allocation_and_math` fails at base + naming all four files; the Linux MSVC-condition probe reproduces the C2065 + sites at matching line and column. +2. GREEN: the same test passes; the suite's case count is unchanged. +3. Before/after golden equality across `test_vocoder1d`, `test_ltx2`, + `test_ltx2_vae`, `test_ltx2_device`, `test_ltx2_loader`, + `test_ltx2_text_encoder`, `test_ltx2_pipeline`. +4. Mutation: reverting the constant re-reds the gate; perturbing pi's value reds + the goldens, proving arm 3 is not a vacuous green. +5. Full `scripts/agent-preflight.sh --staged` and full `ctest`. +6. Native `windows-msvc-cpu` and `windows-msvc-vulkan` on the PR head. Linux + cannot substitute for the MSVC compiler gate. + +## Risks and stop conditions + +The risk is a silent value or associativity change hiding behind a mechanical +substitution — a token gate cannot see a constant that is merely slightly wrong. +The token-equivalence proof and the value-perturbation mutation exist for that. +Stop with `NEEDS_DECISION` if any golden moves; a moved value would mean the +constant is not what it is claimed to be. + +## Outcome + +The base tree's own committed portability test was already RED, naming +`ltx2.cpp`, `ltx2_video_vae.cpp`, `ltx2_audio_vae.cpp` and +`tests/vllm/models/test_vocoder1d.cpp` — four files, not the three the issue +title counts, and 71 cases ran in both arms with failures going 2 to 1. The +surviving failure, `test_real_unsupported_tier_helper_is_structurally_scoped`, +is pre-existing and owned by #680; it was captured failing on the pristine base +before any edit. + +The Linux `-U_GNU_SOURCE -D_ISOC99_SOURCE` probe reproduced the hosted +diagnostic at `ltx2.cpp` 72:58, 557:69 and 572:76 — the same line **and column** +as the MSVC log — and additionally at `test_vocoder1d.cpp` 106:21 and 121:21, +which no MSVC log had yet reached. It emitted nothing for the two VAEs, +confirming by measurement that their guards make them compile. + +Value identity holds three ways. `M_PI`, `std::numbers::pi_v` and the +hand-rolled `3.14159265358979323846` are the same double, `400921fb54442d18`, +and the three derived quantities (`sqrt(2/pi)` as float, `pi/2` as float and as +double) are bit-equal. A token-sequence comparison over all four files, 41,191 +tokens with the two pi spellings unified, reports pure token substitution: the +only differences are the added `` includes and the two removed +hand-rolled defines, so no operator order, cast or operand moved. The seven +golden suites report identical counts before and after — 185 cases and 17,378 +assertions, all passing in both arms. + +That third arm is not vacuous. Perturbing pi to `3.14159` at the three +`ltx2.cpp` sites builds clean and reds 60 assertions in `test_ltx2` and 5 in +`test_ltx2_pipeline`, so those goldens do see this constant. `test_ltx2_vae` +stays green under that mutation — it does not reach these sites, and its +unchanged result is therefore evidence of nothing in particular. + +Reverting the constant to `M_PI` in `ltx2.cpp` still **builds clean on Linux** +(exit 0) while re-reding the portability gate and the MSVC-condition probe. That +is the whole shape of this bug: the Linux build is structurally blind to it, and +only a source-pattern gate or the MSVC compiler can see it. Every mutation was +restored from the index and verified by `sha256sum -c`, never by `git status`. 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/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dbf80e43..726717ce0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,6 +155,17 @@ jobs: # so this needs no model, no GPU and no quiet box. run: | python3 tests/scripts/test_cpu_x86_llamacpp_floor.py + - name: The IndexTTS-2.5 port still matches the shipped checkpoint (#634) + # These three read committed copies of the checkpoint's own config and + # safetensors header, so they need no network, no weights and no GPU. + # They landed in #727 and earlier WITHOUT a CI line, which made them + # run-if-you-remember rather than gates; wiring them is the point. + run: | + python3 tests/scripts/test_indextts2_config_contract.py + python3 tests/scripts/test_indextts2_emotion_arch_covered.py + python3 tests/scripts/test_indextts2_pth_manifest.py + python3 tests/scripts/test_indextts2_convert.py + python3 tests/scripts/test_vocoder1d_single_home.py - name: Critical regression tests remain executable and CTest-registered run: | python3 scripts/check-test-registration.py @@ -412,10 +423,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/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/CMakeLists.txt b/CMakeLists.txt index 5ec949705..91fb92620 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -766,6 +766,27 @@ 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/minimax_music3_loader.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/campplus.cpp + src/vllm/model_executor/models/w2vbert.cpp + src/vllm/model_executor/models/fvq.cpp + src/vllm/model_executor/models/wavenet.cpp + src/vllm/model_executor/models/dit_tail.cpp + src/vllm/model_executor/models/dit_skip.cpp + src/vllm/model_executor/models/dit_front.cpp + src/vllm/model_executor/models/dit_stack.cpp + src/vllm/model_executor/models/indextts2_s2mel_loader.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/adaln.cpp + src/vllm/model_executor/models/dit.cpp + src/vllm/model_executor/models/talker.cpp + src/vllm/model_executor/models/indextts2_pipeline.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 @@ -827,6 +848,7 @@ add_library(vllm STATIC # MODEL-NEMOTRON-H W3 (#517): the additive NemotronH structural bring-up — # config descent, the on-disk name map, the registry entry and the het-KV # topology. No forward: nemotron_h.cpp is W4. + src/vllm/model_executor/models/nemotron_h.cpp src/vllm/model_executor/models/nemotron_h_registry.cpp src/vllm/model_executor/models/nemotron_h_weights.cpp src/vllm/model_executor/models/glm4_registry.cpp @@ -892,6 +914,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 @@ -1101,6 +1126,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/BENCHMARKS.md b/docs/BENCHMARKS.md index 8ce7e32ae..206cfc704 100644 --- a/docs/BENCHMARKS.md +++ b/docs/BENCHMARKS.md @@ -424,6 +424,7 @@ built on it rather than keeping the flattering one. | CPU keep-quant MoE decode | **No number owed**: correctness-only P0. The grouped keep-quant GEMM read activations as f32 whatever their dtype, so CPU MoE decode emitted token-0 garbage from `b4f5610a` (2026-07-31) | Speed unmeasured and unclaimed; `test_ops_quant_dot` GREEN (150224 assertions) | | Accepted-and-inert serve args (`SERVE-RECIPE-ARGS`, #606) | **No number owed**: argument parsing only, so nothing to time and no oracle leg. Correctness gate 4 cases / 58 asserts GREEN, RED-first, mutation-proven | None. A speed axis would be fabricated; closes on review plus the operator gate rerun | | DeepSeek-V2-Lite MLA | Attributed miss, `ACTIVE` | Throughput at every concurrency | +| Qwen3.5 text-only arms (#490) | **No number; gates OWED**, both `PARTIAL` | No fitting ckpt: no denominator; MoE needs NVFP4 experts: published REFUSED (§9 17e) | | Qwen3.5 upstream throughput levers (roadmap C10) | NOT MEASURED. vLLM's 2026-08-06 25K tok/s/GPU is a GB200/NVLink72 disaggregated cluster result, not comparable to one GB10, and is NOT adopted as our bar | Advance the parity pin past `555967922` so the referenced PRs exist, re-capture goldens at zero drift, then port the GDN prefill kernel | | DeepSeek-V4-Flash | **Parity with ds4 (0.997x)** | Optional beat-path: f16 tensor-core DSA/router (near-tie class) | | DeepSeek-V4-Flash vs vLLM | Infeasible on one Spark | 2x GB10 with TP2 over the NCCL seam | @@ -436,6 +437,7 @@ built on it rather than keeping the flattering one. | vLLM 0.26 re-benchmark | Pending | Re-run the binding grids on the advanced pin | | MiniMax-H3 FP4 speed (W-FP4a) | **Measured GB10 (`row/H3-FP4-GPU-E2E`).** Marlin W4A16 byte-exact vs bf16; fp4 a memory win, 0.8x bf16/forward. Real-ckpt fp4-resident e2e RUNS (mp4/wav) | fp4 speed CLOSED. bf16-vs-quant A/B: ENCODER half MEASURED (§8.15), DiT half NOT (no bf16 render exists). Detail: benchmark-record + spec §8 | | LTX-2.5 axes | Speed `PENDING` (vllm-omni#6066 has no native 2.5), binding oracle too. **SIZE: 320x192/25f completes on GB10, 448x256 does not**; that render was REGISTER-conditioned, not prompted | Wall is the HOST VAE decode, not the pool: drain returns 0.11 GiB, byte-inert. 2 baselines UNRESOLVED (lock). A real-checkpoint PROMPTED render is OWED | +| MiniMax-Music3 (`MiniMaxMusic3ForConditionalGeneration`) | **Every axis `PENDING`; none owed yet.** W0/W1 are correctness scaffolding; no forward pass exists to time | Denominator when it does: SGLang-Omni `748a0b43` in its production configuration. First number owed at W6 | | MiniMax-H3 render coherence (`row/H3-RENDER-CLOSE` #77) | **CLOSED: a COHERENT scene on GB10.** #70/#74 white was wrong-PARTITION usage (t2va on the ref2va ckpt); t2va on the FL2VA GGUF renders a prompt-matched orange cat (adj-cos 0.95 vs 0.06, no patch-grid) | Verified first: t2va inputs byte-exact vs upstream; CUDA device==host at seq 1920. Follow-up `H3-TASK-PARTITION-GUARD`: the task/partition mismatch now RAISES 1:1 with `_resolve_task` (spec §8.6-8.7) | | MiniMax-H3 image conditioning (`row/H3-CONDITIONED-E2E`, `row/H3-VISION-SCATTER`, `row/H3-REF2VA-ASSEMBLY`) | **fl2va COHERENT; ref2va assembly bug FIXED+gated.** vision→cond scatter gated; ref2va block-dim double-division fixed + RED-first gated (128 vs 512) + a permanent ref2va DiT-forward rung (§8.10) | grid RE-ATTRIBUTED: with the fix ref2va grids in fp4 AND bf16, and t2va with no refs on the ref2va NVFP4 also grids while FL2VA-GGUF renders, so it is the **NVFP4 checkpoint/loader**, NOT assembly/fp4 (§8.10) | | MiniMax-H3 Thor render speed (sm_110, no FA2) | **34.6 s/step** at 864x480/124f/50 steps on Q4_K_M, **16.6x** off 574.5 (render ~28 min, was ~8 h). Landed: warp-per-query, chunked warp reduce-scatter (1.76x), bf16 `mma.sync` (9.82x) | Shared-memory K/V tiling (23% SLOWER) and register Q-blocking (-0.8%) both measured and REVERTED: memory traffic is not the bound (one head's K+V is 3.9 MB against 32 MB of L2) | 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/FEATURES.md b/docs/FEATURES.md index 1b7e6b4e3..7e929f7a8 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -27,7 +27,7 @@ are our reading of their documented behavior, not measurements. | Correctness gate | token-exact vs vLLM | reference | own | own | | Architectures | 38 registered, 27 gated | 130+ | 100+ | 100+ | | Downloadable server binaries | ✅ v0.0.2: eight indexed archives with checksums, provenance, manifests, and SBOMs. Windows ZIP downloads do not exist; native CPU/Vulkan lanes await hosted runtime, dry-run, prerelease, and authenticated audit gates | ✅ wheels/containers | ✅ wheels/containers | ✅ host-specific binaries | -| Native Windows builds | ◐ CPU/Vulkan: `/MT /W4 /WX`, central `NOMINMAX`, UTF-8, aligned allocation, runtime ISA dispatch. Local closure includes the float-domain DeepSeek probe; hosted compile/runtime/release pending | ✅ | ✅ | ✅ | +| Native Windows builds | ◐ CPU/Vulkan: `/MT /W4 /WX`, central `NOMINMAX`, UTF-8, aligned allocation, C++20 `std::numbers` pi, runtime ISA dispatch. Local closure includes the float-domain DeepSeek probe; hosted compile/runtime/release pending | ✅ | ✅ | ✅ | ## Serving and scheduling @@ -80,7 +80,7 @@ are our reading of their documented behavior, not measurements. | Merged fp8 projection folds per-column alpha in the GEMM epilogue | ◐ `VT_FP8_ALPHA_VEC_EPILOGUE`, CUDA only, default off, ungated; refuses split-K under a bf16-D equivalence claim (`claims_splitk1_premise`, default off) | n/a | n/a | n/a | | `vt::MulColVecF32` carries a bf16 store width | ✅ f32 arm byte-identical; bf16 arm rounds once; CPU + CUDA | n/a | ☐ | ☐ | | bf16 / fp16 | ✅ | ✅ | ✅ | ✅ | -| Safetensors direct load, no conversion | ✅ | ✅ | ✅ | ☐ | +| Safetensors direct load, no conversion | ✅ at ANY tensor byte offset: the format aligns nothing, so the `*_weights.cpp` loaders never form a typed pointer into the mapping (#627). `voxtral.cpp`, `qwen3_vl.cpp`, `qwen3_5_mtp.cpp` still do and are OWED | ✅ | ✅ | ☐ | | Weights uploaded straight from the file mapping (no host copy first) | ◐ verbatim tensors only (37.8% of 27B BF16); arbitrary-offset reads are defined, including Laguna graph staging. Merged/transposed and merged FP4 weights still copy | ✅ | ✅ | ✅ mmap | ## Model coverage @@ -111,6 +111,7 @@ speed-pending, which [BENCHMARKS.md](BENCHMARKS.md) tracks. |---|---|---|---| | `Qwen3_5ForConditionalGeneration` | Qwen3.6-27B NVFP4 (`unsloth` @`890bdef7`, `nvidia` @`0893e160`); Qwen3.5-4B BF16 | 27B strict 235/235 text + 32/32 image/video; 4B cached 3/3 | `unsloth` 27B at/above vLLM, `nvidia` ModelOpt 0.85x; 4B throughput 1.021x. Loads BF16, FP8 and NVFP4 (CT + ModelOpt naming); a `modelopt_mixed` FP8 tower stays NATIVE (#164), GDN `in_proj_qkvz` merged. CUDA/CPU only | | `Qwen3_5MoeForConditionalGeneration` | Qwen3.6-35B-A3B (NVFP4, GDN MoE) | strict 315/315 text vs vLLM 0.25.0 | gate model: 0.93x to 1.03x grid | +| `Qwen3_5ForCausalLM`, `Qwen3_5MoeForCausalLM` | none: no text-only Qwen3.5 checkpoint fits this hardware | **NO RUN GATE, OWED.** Dispatch/config/namespace gated on `test_qwen3_8_text_only.cpp`; NO token claim. MoE reads ONLY per-expert NVFP4: the published stacked/bf16 layout is unimplemented, OWED, refused by name | not measured | | `Qwen3ForCausalLM` | Qwen3 dense 0.6B/1.7B/4B/32B, NVFP4A16 | near-tie strict 16/16 vs vLLM 0.25.0 | c1 every-axis parity, c8 decode residual | | `Qwen3MoeForCausalLM` | Qwen3-Coder-30B-A3B | strict 6/6 vs vLLM 0.25.0 | 11/16 grid cells at or above graphed vLLM | | `Qwen3VLForConditionalGeneration` | Qwen3-VL-4B-Instruct (image + video) | image strict 32/32, video near-tie vs vLLM 0.25.0 | vision tower 0.57x vs vLLM encode; umbrella pending | @@ -137,7 +138,7 @@ speed-pending, which [BENCHMARKS.md](BENCHMARKS.md) tracks. | `LagunaForCausalLM` | poolside/Laguna-S-2.1-NVFP4, GGUF-Q4_K, Laguna-XS | byte-exact near-tie (distributional vs vLLM) | vLLM parity+ 1.03x, default on, via the `laguna-gen` CLI; the registered engine forward VT_CHECKs non-bf16 (`ARCH-ONE-SURFACE` fold) | | `KimiLinearForCausalLM` | Kimi-Linear-48B-A3B (KDA + NoPE-MLA + MoE) | **Folded onto the shared paged runner (ROW 7 §21, #122): engine==CLI 128/128 byte-identical; vs golden 122/128 (the intrinsic near-tie profile); FA2 paged MLA default-ON; SACRED post-fold green** | Served via `vllm_engine_load` + `vllm_complete_tokens` (ABI v13); server 19.0 tok/s wall vs vLLM ~21 (~0.90×), speed residual open | | `KimiK3ForConditionalGeneration` | Kimi-K3 (2.8T MoE) | scaffold: registry+config+enumeration gated, forward refuses | HW-infeasible (~1.56 TB); no run | -| `NemotronHForCausalLM` | Nemotron-3.5-Lightning-30B-A3B-NVFP4 (`nvidia` @`29f2d174`) | scaffold: registry+config+enumeration+KV-shape gated, forward refuses. 18487/18487 tensors claimed; a bf16 config claims that set minus its scale companions. Nothing runs yet (spec #517, blocked on #496) | no run; GGUF k-quants refuse by name and are owed | +| `NemotronHForCausalLM` | Nemotron-3.5-Lightning-30B-A3B-NVFP4 (`nvidia` @`29f2d174`) | registry+config+enumeration+KV-shape gated; the hybrid Mamba2/GQA/relu2-MoE forward COMPUTES, gated elementwise vs independent double references. No weight loader, so nothing runs end to end (spec #517 W4) | no run; GGUF k-quants refuse by name and are owed | | `MuseGlimmerForCausalLM` | real tensors, **bf16 depth 4/52 only**: 5 prefill argmax positions match a torch transcription of vllm#51655 and HF. GGUF full depth generates coherently (#347, #359) but is **NOT token-exact** | text forward + loader vs an fp32 reference, per-mechanism property tests, scaffold 11/11, GGUF gate 17/17. An ABSENT config key now takes the architecture's constant (#412): GGUF post-norms ran at 1e-5, not 1e-8 | no vLLM denominator (pin cannot load it); SECONDARY llama.cpp, same GGUF, GB10 CPU: prefill tie **0.997x**, decode 0.232x, RSS 1.92x (#333) | | `MuseGlimmerForConditionalGeneration` | vision: **no reference run of any kind**; enumeration gated vs the released 30B index (1436/1436). Image/video need bf16 safetensors: `mmproj-kquant.gguf` is refused by name | perception encoder loaded and wired, so an image or video prompt runs; `perception_emb_norm` now armed by default (#405). Reachability plus placeholder scatter only, no image or video correctness | not measurable; anchored to open vllm#51655 | | `LlamaModel` | landed tiny synthetic embedding fixture (engine path == direct pooler path, identical vectors; f64 LAST+normalize reference); real checkpoint (e5-mistral class) is a NAMED residual | pooling/embed only, text paths refuse by task; `vllm_embed` + `/v1/embeddings` | n/a (CPU correctness-grade embeddings) | @@ -161,7 +162,8 @@ 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. Image chain PPM->resize->encode->place->noise vs executed upstream. Prompt->Gemma-4->cross-attn FIXTURE-gated; render OWED | Family `ltx-2.5` via `ltx2-gen`. ~29 GB NVFP4/GB10, FP8 ~44 GB, +~24 GB tower. FP8/torchao/NVFP4 load. IMAGE cond SERVED at image_crf=0 (out of distribution; resolved 18 refuses). DiffVAE, LoRA, keyframe/ref refused | +| 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 AdaLN host+device; prompt->Gemma-4->cross-attn FIXTURE-gated. Image chain PPM->resize->encode->place->noise. Render 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 SERVED `crf=0`; DiffVAE/LoRA/keyframe/ref 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 | `ACTIVE`. Oracle gateable with 13 per-stage goldens (#708); modular loader gated 1413/1413 on the real 27 GB checkpoint, all 1012 tensors accounted (#714). Output 44100 Hz stereo. No stage runs yet: W2-W7 owed | 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 | @@ -175,6 +177,7 @@ Enumerated in `.agents/model-matrix.md`, not registered, no runnable GB10 gate: | `DeepseekV3ForCausalLM`, `DeepseekV32ForCausalLM` | DeepSeek-V3 / V3.2 | 671B, ~642 GiB fp8 vs 119 GiB unified; V3.2 also DSA-indexer dep-blocked | | `GlmMoeDsaForCausalLM` | GLM-5 (DSA) | ~1404 GiB bf16; dep-blocked (GLM-5.x is DeepSeek-V3.2 verbatim) | | `MiniMaxM2ForCausalLM` | MiniMax-M2 | ~230B, ~428 GiB bf16, ~4x over the unified pool | +| `Dots3NoteForCausalLM`, `Dots3NoteMTPModel` | dots3-note (280B-A16B multimodal MoE) | ~576 GB bf16 / ~290 GB fp8 vs a 119-122 GiB ceiling on every host, no smaller checkpoint; also beyond-pin (vLLM `main` only). Scoped ([spec](../.agents/specs/dots3-note.md), #699) | 27 of the 32 registered text-generation architectures carry a passing correctness gate today; the rest are honestly marked scaffold or blocked above. @@ -192,8 +195,9 @@ 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) | ☐ | ☐ | -| 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 | +| 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) | ◐ IndexTTS-2.5 only: the S2Mel DiT is COMPLETE front to tail and gated against upstream; its tail runs on REAL shipped weights. Other stages gated at reduced dims. No render, no route (#634) | ✅ (vllm-omni: MOSS-TTS, Qwen3-TTS, Higgs Audio v3, Voxtral TTS, IndexTTS-2.5) | not assessed | not assessed | +| MUSIC generation (MiniMax-Music3) | ☐ not generating. The W1 checkpoint LOADER has landed ([spec](../.agents/specs/minimax-music3.md), #672); no stage runs yet. 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 @@ -201,9 +205,9 @@ API the image **request** path is wired end to end (`ROAD-V1-MM` W1-W3): the production server attaches the seam at `server_main.cpp:826`. Two residuals keep it from ✅: the model runner has no mm-forward consuming `Request.mm_features`, and no image codec is vendored (raw RGB only). Video, audio and multi-image over -HTTP are not started. Audio **in** is gated; audio **out** does not exist: we -ship no TTS or speech-generation path on any surface, which is why that row is -the only ☐ in our column here. +HTTP are not started. Audio **in** is gated; audio **out** reaches no surface: +the IndexTTS-2.5 ◐ reads "assembled, never run", so asking for speech today +gets a refusal naming what is missing. ## Speculative decoding @@ -227,7 +231,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 2acbe72f1..3038b35f9 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` @@ -140,6 +140,7 @@ token-for-token correctness against the pinned oracle. | InternLM2 dense (fused-`wqkv` interleaved split) | Correctness-complete, speed-pending | Token-exact 16/16 (internlm2-chat-1_8b): 12/16 strict + 4/16 bf16 near-tie (max gap 0.0 nats), 0 divergent; first InternLM model; ZERO new compute kernel (reuses the Llama dense forward; the only delta is a loader-side de-interleave of the fused `wqkv`, which packs q/k/v interleaved by KV-group) | | MiniMax-H3 (`MiniMaxH3DiTModel`, video+audio DIFFUSION) | **ABI v12 ONE SURFACE; device selector uses generic `DeviceType`; DSR 32.** t2va+fl2va COHERENT; bf16 shards STREAM | ref2va ckpt fidelity §8.12; encoder A/B §8.15; GB10 re-verify residual; CPU fold 6/137 (one queue + device provenance mutation-gated) | | LTX-2.5 (`LTX2VideoTransformer3DModel`, video+audio DIFFUSION) | **L1-L9c landed (#435).** 21.00B / 48 blocks. `VideoEngine` seam + ABI **v18**, DiT forward (CPU f32 parity, bf16 device-resident), Gemma-4 TE, both VAEs, the embeddings connector, pipeline, NVFP4/FP8 arms, `/v1/videos` | A shipped 21.00B FP8 DiT runs device-resident on GB10. The 320x192/25f frames ARE a scene, register-conditioned. L13 encodes a typed prompt, FIXTURE-gated; a prompted render is OWED. Speed and oracle parity `PENDING` | +| MiniMax-Music3 (`MiniMaxMusic3ForConditionalGeneration`, text-to-MUSIC) | **`ACTIVE`: W0 + W1 landed (#672).** First row whose oracle is not vLLM: the OPEN diffusers PR #14456 `c6da9936`, which generates audio here | W2-W7 owed. Loader gated 1413/1413 on the real checkpoint; no speed number exists or is claimed | | Command-R / Cohere dense (`CohereForCausalLM`) | Implemented, gate-blocked | ZERO-new-kernel port grounded in vLLM `commandr.py`: weight-only Cohere LayerNorm + GPT-J full-width RoPE + PARALLEL residual + `logit_scale` + tied embeddings, all reuse; compiles, links, self-registers. No SACRED gate yet (real checkpoints HF-gated, ungated ones tiny-random, GPU box disk-full); oracle run-verified at W0. See docs/BENCHMARKS.md | | Phi-1 / Phi-2 dense (`PhiForCausalLM`, parallel residual) | Correctness-complete, speed-pending | Token-exact 16/16 (microsoft/phi-2): 9/16 strict + 7/16 bf16 near-ties (max gap 0.25 nats), 0 forward-divergent; the OLDER Microsoft Phi arch, DISTINCT from Phi-3/Phi-4; ZERO new compute kernel (GPT-J parallel residual, LayerNorm-with-bias, biased qkv/dense, partial NeoX rope 32/80, non-gated NewGELU MLP reusing `vt::GeluTanh`, untied biased lm_head); F16 dtype-aware loader | | MiniCPM dense (`MiniCPMForCausalLM`, three scalars) | Correctness-complete, speed-pending | Token-exact 16/16 (openbmb/MiniCPM-2B-sft-bf16): 10/16 strict + 6/16 bf16 near-ties (max gap 0.0 nats), 0 forward-divergent; first OpenBMB MiniCPM model; ZERO new compute kernel (the Llama/Granite dense forward plus three scalars: scale_emb, scale_depth/sqrt(layers) residual, dim_model_base logit scaling), tied lm_head; `.bin`-only weights converted to safetensors via trusted torch | @@ -162,7 +163,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) | 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 | @@ -698,6 +699,52 @@ oracle-blocked for a gate (see the capability table above). ### Frontier and hardware-blocked families +**Qwen3.5 text-only arms (`Qwen3_5ForCausalLM`, `Qwen3_5MoeForCausalLM`) — +REGISTERED 2026-08-12, RUN GATE OWED (#490).** An ahead-of-pin forward port of +upstream PR vllm#50210 (`ad5d29db7`), which registers both arms against the same +`qwen3_5` module our gated `ForConditionalGeneration` wrappers already use. Two +additive `REGISTER_VLLM_MODEL` lines against the EXISTING dense and MoE +factories: no forward, no KV-cache spec, no loader fork. + +The other half is ONE backbone weight-namespace decision per checkpoint: +`model.` for a text-only arm, `model.language_model.` for the wrappers, and a +MIXED index refused rather than half-bound. `Qwen/Qwen3.8-2.4T-A95B` declares +`Qwen3_5MoeForCausalLM` / `qwen3_5_moe_text` and is the token-exact +Qwen3.6-35B-A3B GDN-hybrid MoE backbone at larger scale, every knob +config-driven, with the BACKBONE weight names identical modulo that prefix. + +**CORRECTED 2026-08-12: the prefix is NOT the only thing between this code and +`Qwen/Qwen3.8-2.4T-A95B`, and the first two commits of this row said it was.** +The MoE loader reads only PER-EXPERT NVFP4 routed experts (`LoadMoeExpertsInto` +-> `LoadNvfp4Raw`: `U8` weight + `F8_E4M3` `.weight_scale` + `.weight_scale_2`), +with no stacked and no bf16 branch. The published indices (read live 2026-08-12) +have neither: `Qwen/Qwen3.8-2.4T-A95B` carries 93x `mlp.experts.gate_up_proj` + +93x `.down_proj` (3-D STACKED) and **zero** `weight_scale` / `input_scale` +tensors, and `Qwen/Qwen3.6-35B-A3B` is the same under the VL prefix — our gated +35B row reads the REQUANTIZED `nvidia/Qwen3.6-35B-A3B-NVFP4`. + +So the **bf16 / 3-D-stacked MoE routed-expert arm is NOT implemented and is +OWED**, and a published MoE checkpoint is now REFUSED by a message naming it +rather than dying on `expected U8 for lm_head.weight`. The DENSE arm is not +affected: `LoadQwen3_5Dense` routes BF16 vs FP8 vs NVFP4 per projection by +tensor presence, so it may genuinely load a flat bf16 checkpoint. That asymmetry +is the record. + +What is claimed is dispatch, flat-config resolution, namespace resolution and +the refusal, gated by `tests/vllm/models/test_qwen3_8_text_only.cpp`, with +27B/35B/Coder inert and parity goldens md5-unchanged. **What is NOT claimed is a +single generated token.** 2.4T bf16 is ~4.8 TB and the released FP8 variant +~2.4 TB against 128 GB of unified memory, and no smaller Qwen3.8 sibling exists, +so there is no token-exact oracle run and no speed number. + +Both rows therefore stay `PARTIAL`. The owed **DENSE** run gate closes when a +`Qwen3_5ForCausalLM` checkpoint that fits GB10 appears. The **MoE** run gate +needs more: a fitting *published* (bf16/stacked) MoE checkpoint would still be +refused at load, so it needs a fitting checkpoint whose routed experts are +per-expert NVFP4, or the owed stacked/bf16 arm implemented first. Also NOT +implemented and recorded as owed: that stacked/bf16 MoE expert arm, and the MTP +and GGUF arms for 3.8. This does not advance the parity pin. + Larger DeepSeek / GLM / MiniMax / Gemma-4 variants are recorded as **hardware-blocked** (they do not fit 119 GiB of unified memory on this box) or **spiked-only**, per the [model matrix](../.agents/model-matrix.md). diff --git a/docs/USAGE.md b/docs/USAGE.md index 41b5d50d5..7f4c8ee58 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -198,6 +198,26 @@ build/examples/vllm-cli \ | `--repeat N` | `1` | Load once, then run N blocking completions. Use it to read a warm decode tok/s without paying model load each time. Not supported with `--stream`, which falls back to 1 | | `-h`, `--help` | | Print usage and exit | +`--model` resolves a Qwen3.5-family checkpoint's backbone under EITHER weight +namespace. The multimodal wrappers (`Qwen3_5ForConditionalGeneration`, +`Qwen3_5MoeForConditionalGeneration`) publish the text backbone nested under +`model.language_model.`; the text-only arms (`Qwen3_5ForCausalLM`, +`Qwen3_5MoeForCausalLM`) publish it flat under `model.`. The loader decides which +ONCE per checkpoint from the shard index, and REFUSES a checkpoint that carries +backbone tensors under both rather than binding half the model from each. + +**Resolving the namespace is not the same as loading the checkpoint, and the +MoE and dense arms differ.** The dense loader routes each projection to BF16, +FP8 or NVFP4 by tensor presence, so a flat bf16 `Qwen3_5ForCausalLM` checkpoint +is expected to load. The **MoE** loader reads only PER-EXPERT NVFP4 routed +experts, while the published MoE repos (`Qwen/Qwen3.8-2.4T-A95B`, +`Qwen/Qwen3.6-35B-A3B`) ship 3-D stacked, unquantized experts — that arm is +**not implemented**, and such a checkpoint is refused at load with a message +naming what is missing. Use an NVFP4 requant (e.g. +`nvidia/Qwen3.6-35B-A3B-NVFP4`) for the MoE path. No text-only Qwen3.5 +checkpoint has been RUN here at all — see [STATUS.md](STATUS.md) for the owed +run gates. + GGUF and safetensors mapped-payload paths, plus safetensors index paths, use the host's native filesystem encoding, including Unicode paths on Windows. Native Windows release artifacts are not published yet; they will remain unavailable @@ -368,7 +388,7 @@ tokens quietly. | Architecture | Why it refuses | |---|---| | `KimiK3ForConditionalGeneration` | Needs ~1.56 TB (MXFP4); no host here can run it | -| `NemotronHForCausalLM` | The Mamba2 forward is not ported yet (#517 W4, blocked on #496). Safetensors resolve and parse; a GGUF file is refused by name, since no GGUF arm exists for it | +| `NemotronHForCausalLM` | The hybrid forward is ported (#517 W4) but there is no weight LOADER yet, so a checkpoint still cannot be run: loading leaves the weights unmaterialized and the forward refuses by name. Safetensors resolve and parse; a GGUF file is refused by name, since no GGUF arm exists for it | This is a deliberate state, not a bug: registering the architecture is what lets the config parse and weight-name mapping be tested before the forward exists. @@ -404,15 +424,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 @@ -1066,6 +1096,14 @@ Registered in | GET | `/v1/videos/{id}` | Job status | | GET | `/v1/videos/{id}/content` | The finished MP4 (`video/mp4`) | +There is **no `/v1/audio/speech`**. Text to speech is not servable: the +IndexTTS-2.5 stages are ported and gated at reduced dimensions, with further +stages named as missing by the checkpoint's own manifest, and no route is +registered, the public ABI carries no synthesis entry point, and loading the +family refuses with a message naming the missing pieces (#634). Asking a running server for speech +today is a 404 at the route table, not a runtime error, and that is the accurate +signal: the capability does not reach any surface yet. + `prompt_logprobs` is accepted on `/v1/completions` and `/v1/chat/completions` and the engine computes it — every prompt position is scored against the token that followed it, accumulated across chunked prefill — but the **response body @@ -1145,7 +1183,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) | @@ -1824,6 +1862,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 @@ -2095,11 +2153,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`, @@ -2195,3 +2256,146 @@ NVFP4) and misses `.bias` (BF16, so a different unpack path) while the config still says the projection is biased. Without the refusal that renders a plausible video for the wrong prompt: every conditioning row is shifted by the missing bias and every padded row projects to 0 instead of to the bias. + +## MiniMax-Music3: the checkpoint loader + +**It loads, it does not generate.** `include/vllm/model_executor/models/` +`minimax_music3_loader.h` is phase W1 of #672 — it resolves the shipped +`diffusers` layout, parses the six component configs, and accounts every tensor +in the files against what those configs owe. No forward, no scheduler step and +no audio; those are W2-W7, and nothing below produces a song. + +Point it at the **diffusers arm**, the six-component tree: + +``` +minimax-music3/ + modular_model_index.json + transformer/ config.json + 2 shards + index 441 tensors F32 + condition_encoder/ config.json + 1 file 4 tensors F32 + rvq_depth_decoder/ config.json + 1 file 47 tensors BF16 + vocoder/ config.json + 1 file 121 tensors F32 + language_model/ config.json + 4 shards + index 399 tensors BF16 + scheduler/scheduler_config.json + tokenizer/ +``` + +`MiniMaxMusic3ResolveCheckpoint` refuses anything else **by name**, and the +refusal you are most likely to hit is the useful one. The same repository also +ships a **native** arm — `qwen_7B/qwen_7B/`, `flowmatching_vae.pth`, `dav.pth` — +which SGLang-Omni serves and which holds every weight this port needs in a layout +nothing here reads. Pointed at that tree the loader names it as the native arm, +lists the diffusers components it lacks, and tells you to convert it with +diffusers' `scripts/convert_minimax_music3_to_diffusers.py`. It is never +silently mis-loaded. + +Two things the loader enforces that a correctness gate later could not catch: + +**On-disk dtype and runtime dtype are different things, and the loader keeps +them apart.** The files store F32 for the transformer, condition encoder and +vocoder and BF16 for the RVQ depth decoder and language model, and +`MiniMaxMusic3AccountTensors` refuses a file that disagrees. That set is *not* a +runnable configuration. Upstream casts in exactly two places, `denoise.py:83` +(condition encoder output into the transformer) and `decoders.py:84` (latents +into the vocoder), and never on the way in: `denoise.py:82` hands the language +model's hidden states to the condition encoder with a device move and no dtype +move. So the autoregressive half must share one dtype, and loading the on-disk +set raises `Input type (c10::BFloat16) and bias type (float) should be the same` +from `condition_embedder_minimax_music3.py:64`. + +`MiniMaxMusic3ResolveRuntimeDtypes` answers the runtime question. +`kBf16ArFp32Acoustic` is the gated configuration: language model, depth decoder +and condition encoder in bf16, transformer and vocoder in fp32. +`MiniMaxMusic3CheckRuntimeDtypes` refuses a violation by name, listing all three +autoregressive components with their dtypes, because upstream's own error names +a bias dtype and never says which component disagreed with which. +`kAsStored` is kept selectable so that failure stays reproducible; it is +reported as not runnable rather than quietly repaired. + +**The vocoder's weight norm is folded at load.** Its 30 weight-normed +convolutions ship as torch's legacy `weight_g`/`weight_v` pairs; +`MiniMaxMusic3LoadVocoderWeights` collapses each to a single `.weight` +through `vocoder1d::MaterializeWeightNorm`, so no `_g`/`_v` name survives and +nothing downstream can read the direction `v` as if it were the weight. Four of +the thirty are `ConvTranspose1d`, whose weight is `[C_in, C_out, K]` — torch +reduces over dimension 0 either way, which for those four is the *input* channel. + +### Running its gate + +The suite needs no checkpoint. `tests/vllm/models/minimax_music3_manifest.inc` +carries the real checkpoint's own safetensors headers — 1012 entries of names, +dtypes and shapes, no weight bytes — and every geometry claim is asserted +against it: + +```sh +cmake -S . -B build -DVLLM_CPP_BUILD_TESTS=ON +cmake --build build -j 8 --target test_minimax_music3_loader +./build/tests/test_minimax_music3_loader +``` + +One test case additionally exercises the real 27 GB tree when you name it, and +loudly skips when you do not: + +```sh +VLLM_CPP_MUSIC3_CHECKPOINT=/path/to/minimax-music3 \ + ./build/tests/test_minimax_music3_loader +``` + +Regenerate the manifest after a checkpoint revision moves — it reads headers +only, so it does not stream the weights: + +```sh +python3 scripts/gen-minimax-music3-manifest.py \ + --checkpoint /path/to/minimax-music3 \ + --output tests/vllm/models/minimax_music3_manifest.inc +``` + +### IndexTTS-2.5 goldens and checkpoint manifests + +The speech lane is not servable yet (see `/v1/audio/speech` above); these +regenerate its gates. `read-torch-manifest.py` reads a torch `.pth`'s tensor +names and shapes from its pickle header over HTTP range requests, so it inspects +a multi-GB checkpoint without downloading the weights: + +```sh +python3 scripts/read-torch-manifest.py \ + https://huggingface.co/IndexTeam/IndexTTS-2.5/resolve/main/s2mel.pth +``` + +The stage goldens need the upstream source checked out, and emit `.inc` files +that carry no weight bytes: both sides rebuild parameters from one shared +pseudo-random stream. + +```sh +WAVENET_SRC=/path/to/index-tts/indextts/s2mel/modules \ + python3 scripts/gen-wavenet-goldens.py --out tests/vllm/models/wavenet_goldens.inc + +DIT_SRC=/path/to/index-tts/indextts/s2mel/modules \ + python3 scripts/gen-dit-tail-goldens.py --out tests/vllm/models/dit_tail_goldens.inc + +DIT_SRC=/path/to/index-tts/indextts/s2mel/modules \ + python3 scripts/gen-dit-front-goldens.py --out tests/vllm/models/dit_front_goldens.inc + +DIT_SRC=/path/to/index-tts/indextts/s2mel/modules \ + python3 scripts/gen-dit-stack-goldens.py --out tests/vllm/models/dit_stack_goldens.inc +``` + +The U-Net skip routing is recorded rather than generated into an `.inc`: this +prints the schedule upstream's own Transformer actually performs, at several +depths, and the expected values are quoted in `tests/vllm/models/test_dit_skip.cpp`. + +```sh +python3 scripts/gen-dit-skip-schedule.py /path/to/index-tts/indextts/s2mel/modules +``` + +Convert the checkpoints once, then point the loader gate at the result to check +the real weights (it is skipped, loudly, when the variable is unset): + +```sh +python3 scripts/convert-indextts2-checkpoint.py \ + --checkpoint $CHECKPOINT_ROOT/IndexTTS-2.5 \ + --out $CHECKPOINT_ROOT/IndexTTS-2.5-safetensors \ + --manifest tests/vllm/models/indextts2_pth_manifest.json + +VLLM_CPP_INDEXTTS2_S2MEL=$CHECKPOINT_ROOT/IndexTTS-2.5-safetensors/s2mel.safetensors \ + ./build/tests/test_indextts2_s2mel_loader +``` 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/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/model_executor/models/adaln.h b/include/vllm/model_executor/models/adaln.h new file mode 100644 index 000000000..f3c68518c --- /dev/null +++ b/include/vllm/model_executor/models/adaln.h @@ -0,0 +1,45 @@ +// Adaptive layer norm — how S2Mel's DiT is conditioned (#634). +// +// Every DiT block, and the final layer, condition on the timestep/style vector +// through adaLN rather than through cross-attention. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace adaln { + +// modulate(x, shift, scale) = x * (1 + scale) + shift, broadcast over frames +// (diffusion_transformer.py:11-12). +// +// THE `1 +` IS THE DETAIL. Without it the modulation is centred on 0 instead of +// 1, which still trains and still generates -- it is simply a different model, +// and no shape or finiteness check can see it. +std::vector Modulate(const std::vector& x, int64_t frames, int64_t hidden, + const std::vector& shift, const std::vector& scale); + +// LayerNorm with elementwise_affine = FALSE: normalize only, NO gamma or beta. +// The checkpoint contains no such tensors, so a port that applies them is +// reading parameters that do not exist. +std::vector LayerNormNoAffine(const std::vector& x, int64_t frames, int64_t hidden, + double eps); + +struct FinalLayerWeights { + // adaLN_modulation is SiLU -> Linear(hidden, 2*hidden); the output CHUNKS into + // [shift, scale] in that order. + std::vector ada_w, ada_b; + // The output projection is weight-normed: (g, v) with w = g * v / ||v||. + std::vector linear_g, linear_v, linear_bias; +}; + +// FinalLayer::forward — norm (no affine) -> modulate -> weight-normed linear. +// Returns [frames, out_channels]. +std::vector FinalLayer(const std::vector& x, int64_t frames, int64_t hidden, + int64_t out_channels, const std::vector& cond, + const FinalLayerWeights& weights, double eps); + +} // namespace adaln +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/campplus.h b/include/vllm/model_executor/models/campplus.h new file mode 100644 index 000000000..371e3f2e2 --- /dev/null +++ b/include/vllm/model_executor/models/campplus.h @@ -0,0 +1,181 @@ +// 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 +#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); + + +// 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); + + +// ── 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); + + +// ── 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/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/include/vllm/model_executor/models/dit.h b/include/vllm/model_executor/models/dit.h new file mode 100644 index 000000000..1f9c92a9a --- /dev/null +++ b/include/vllm/model_executor/models/dit.h @@ -0,0 +1,78 @@ +// S2Mel DiT block primitives (#634), from gpt-fast as the DiT wraps it. +// +// TWO CONVENTIONS THAT DIFFER FROM THEIR NEIGHBOURS IN THIS SAME MODEL: +// +// * `AdaptiveLayerNorm` here is `weight * norm(x) + bias` -- NO `1 +`, unlike +// `adaln::Modulate` used by the DiT's FinalLayer. Two adaLN conventions +// coexist in one model, so porting one over the other is silent and easy. +// * The rotary embedding pairs ADJACENT components (`reshape(..., -1, 2)`), +// not halves of the vector. The half-split convention is far more common and +// yields a rotation that is smooth, norm-preserving and wrong. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace dit { + +// RMSNorm: x * rsqrt(mean(x^2) + eps) * weight. NO mean subtraction -- that is +// what separates it from LayerNorm, and subtracting anyway still normalizes. +std::vector RmsNorm(const std::vector& x, int64_t frames, int64_t dim, + const std::vector& weight, double eps); + +// AdaptiveLayerNorm: split(project(embedding)) -> (weight, bias), then +// `weight * rms_norm(x) + bias`. The split is [weight, bias] in that order. +std::vector AdaptiveLayerNorm(const std::vector& x, int64_t frames, int64_t dim, + const std::vector& embedding, + const std::vector& proj_w, + const std::vector& proj_b, + const std::vector& norm_weight, double eps); + +// apply_rotary_emb over [frames, heads, head_dim], with `freqs` laid out +// [frames, head_dim/2, 2] as (cos, sin). +// +// out[2i] = x[2i] * cos - x[2i+1] * sin +// out[2i+1] = x[2i+1] * cos + x[2i] * sin +// +// The pairs are (0,1), (2,3), ... -- ADJACENT. Pairing i with i + head_dim/2 is +// the other common convention and is wrong here. +std::vector ApplyRotary(const std::vector& x, int64_t frames, int64_t heads, + int64_t head_dim, const std::vector& freqs); + + +// SwiGLU feed-forward: w2(silu(w1(x)) * w3(x)). +// +// W1 IS THE GATE (it takes the SiLU), w3 is the up-projection. Swapping them +// yields a network of identical shape that trains and generates, and is a +// different function -- the checkpoint stores them as separate tensors, so +// nothing but the values can tell. +std::vector SwiGlu(const std::vector& x, int64_t frames, int64_t dim, + int64_t intermediate, const std::vector& w1, + const std::vector& w3, const std::vector& w2); + +struct BlockWeights { + std::vector wqkv; // [3*heads*head_dim, dim], fused q|k|v + std::vector wo; // [dim, dim] + std::vector w1, w3, w2; // SwiGLU + std::vector attn_proj_w, attn_proj_b, attn_norm_w; // attention_norm + std::vector ffn_proj_w, ffn_proj_b, ffn_norm_w; // ffn_norm +}; + +// TransformerBlock::forward (gpt_fast/model.py:221-239): +// +// h = x + attention(attention_norm(x, c)) +// out = h + feed_forward(ffn_norm(h, c)) +// +// Both norms are AdaptiveLayerNorm conditioned on `c`, and both residuals are +// FULL (no scaling) -- unlike the macaron halves in the w2v-bert Conformer, +// which is a different block type in the same lane. +std::vector Block(const std::vector& x, const std::vector& cond, + int64_t frames, int64_t dim, int64_t heads, int64_t head_dim, + int64_t intermediate, const std::vector& freqs, + const BlockWeights& weights, double eps); + +} // namespace dit +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/dit_front.h b/include/vllm/model_executor/models/dit_front.h new file mode 100644 index 000000000..ac075d00f --- /dev/null +++ b/include/vllm/model_executor/models/dit_front.h @@ -0,0 +1,63 @@ +// The S2Mel DiT FRONT END: how the conditioned input is built (#634). +// +// Upstream `indextts/s2mel/modules/diffusion_transformer.py:206-226`, index-tts +// @4f8792ff120cd3ea470dd511e997a17c86cddd10, under the shipped config +// (`style_condition: true`, `style_as_token: false`): +// +// cond = cond_projection(cond) +// x_in = cat([x^T, prompt_x^T, cond], -1) // 80 + 80 + 512 = 672 +// x_in = cat([x_in, style repeated over T], -1) // + 192 = 864 +// if class_dropout: x_in[..., in_channels:] *= 0 +// x_in = cond_x_merge_linear(x_in) // 864 -> hidden +// +// `cond_x_merge_linear.weight` is [512, 864] in the shipped checkpoint, and 864 +// is exactly 512 + 80 * 2 + 192, so the concatenation order and widths are +// pinned by the weight itself. +// +// TWO THINGS CONTRADICT WHAT A READER EXPECTS. +// +// `cond_in_module` is FORCED to `cond_projection` upstream: the `content_type` +// switch that would have chosen `cond_embedder` is commented out. So +// `cond_embedder` is present in `s2mel.pth` and DEAD in 2.5. A port that +// "restored" the switch would read a tensor this model never uses. +// +// `class_dropout` zeroes everything AFTER the first `in_channels` columns, +// keeping x and dropping prompt, cond and style. That is not a training-only +// path: `mask_content` sets it at inference, so it IS the classifier-free +// guidance unconditional branch that `cfm::EulerStepCfg` consumes. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace dit_front { + +struct Weights { + std::vector cond_proj_w; // [hidden, hidden] + std::vector cond_proj_b; // [hidden] + std::vector merge_w; // [hidden, in_channels * 2 + hidden + style] + std::vector merge_b; // [hidden] +}; + +struct Config { + int64_t hidden = 0; + int64_t in_channels = 0; + int64_t style = 0; + int64_t frames = 0; +}; + +// x and prompt_x are [in_channels, frames] CHANNEL-major, as upstream holds +// them before its transpose. cond is [frames, hidden]. style is [style]. +// `unconditional` selects the CFG branch that zeroes everything past +// in_channels. Returns [frames, hidden]. +std::vector BuildXIn(const Config& cfg, const Weights& w, + const std::vector& x, + const std::vector& prompt_x, + const std::vector& cond, + const std::vector& style, bool unconditional); + +} // namespace dit_front +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/dit_skip.h b/include/vllm/model_executor/models/dit_skip.h new file mode 100644 index 000000000..93cc3f534 --- /dev/null +++ b/include/vllm/model_executor/models/dit_skip.h @@ -0,0 +1,55 @@ +// The DiT's U-Net (uvit) skip connections across transformer depth (#634). +// +// Upstream `indextts/s2mel/modules/gpt_fast/model.py:152-190` and `:213-232`, +// index-tts @4f8792ff120cd3ea470dd511e997a17c86cddd10. The shipped config sets +// `s2mel.DiT.uvit_skip_connection: true`, and the checkpoint carries one +// `layers.N.skip_in_linear` per receiving layer. +// +// The first half of the stack pushes its OUTPUT onto a stack; the second half +// pops one before running. So at the shipped depth 13 layer 7 receives layer 5's +// output, layer 8 receives layer 4's, and layer 6 in the middle neither emits +// nor receives. +// +// Every plausible variant of this routing still produces a running model: FIFO +// instead of LIFO, `>=` instead of `>`, pushing the layer's INPUT rather than +// its output. `scripts/gen-dit-skip-schedule.py` records what upstream actually +// does by driving its own Transformer, and the gate compares against that. +// +// One upstream asymmetry is preserved deliberately: at EVEN depth there is one +// more emitter than receiver, so the earliest emitted skip is never consumed. +// It is reported rather than "corrected". +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace dit_skip { + +struct Schedule { + std::vector emit; // layer indices that push their output + std::vector receive; // layer indices that pop one before running + // source[i] is the layer whose output layer i receives, or -1 for none. + std::vector source; + int64_t orphaned = 0; // emitted but never consumed; non-zero at even depth +}; + +// The routing for a stack of `layers` transformer blocks. +Schedule Plan(int64_t layers); + +// skip_in_linear(cat([x, skip], dim=-1)). +// +// THE CONCATENATION ORDER IS THE DETAIL: x first, then the skip. Reversing it +// reads the same weights against the wrong halves and still returns a tensor of +// the right shape. +// +// x and skip are [frames, dim]; weight is [dim, 2 * dim]; returns [frames, dim]. +std::vector ApplySkip(const std::vector& x, const std::vector& skip, + int64_t frames, int64_t dim, + const std::vector& weight, + const std::vector& bias); + +} // namespace dit_skip +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/dit_stack.h b/include/vllm/model_executor/models/dit_stack.h new file mode 100644 index 000000000..4bc50d741 --- /dev/null +++ b/include/vllm/model_executor/models/dit_stack.h @@ -0,0 +1,60 @@ +// The S2Mel DiT transformer STACK: what sits between front end and tail (#634). +// +// Upstream `indextts/s2mel/modules/gpt_fast/model.py:161-191` +// (Transformer.forward), index-tts @4f8792ff120cd3ea470dd511e997a17c86cddd10. +// N blocks, the U-Net skip routing across them, and a final AdaptiveLayerNorm. +// +// This is composition only: the block is `dit::Block`, the routing is +// `dit_skip::Plan`, and the per-layer skip merge is `dit_skip::ApplySkip`. +// Nothing here reimplements any of them. +// +// The rotary table is an INPUT. Upstream precomputes `freqs_cis` once for the +// whole model and indexes it by position, so passing it in keeps this a gate on +// composition rather than on a second copy of that computation. +// +// Upstream builds a `skip_in_linear` on EVERY layer when uvit_skip_connection is +// set, even the layers that never receive one, so the checkpoint carries +// unused ones. They are loaded and left alone rather than treated as an error. +#pragma once + +#include +#include + +#include "vllm/model_executor/models/dit.h" + +namespace vllm { +namespace models { +namespace dit_stack { + +struct LayerWeights { + dit::BlockWeights block; + // Present on every layer upstream; consulted only on receiving layers. + std::vector skip_in_w; // [dim, 2 * dim] + std::vector skip_in_b; // [dim] +}; + +struct Weights { + std::vector layers; + // transformer.norm, an AdaptiveLayerNorm like the per-block ones. + std::vector norm_proj_w, norm_proj_b, norm_w; +}; + +struct Config { + int64_t dim = 0; + int64_t heads = 0; + int64_t head_dim = 0; + int64_t intermediate = 0; + int64_t frames = 0; + double eps = 1e-5; +}; + +// x is [frames, dim]; cond is [dim] (one conditioning vector, as upstream passes +// t1 unsqueezed); freqs is the rotary table for these positions. +// Returns [frames, dim]. +std::vector Forward(const Config& cfg, const Weights& w, + const std::vector& x, const std::vector& cond, + const std::vector& freqs); + +} // namespace dit_stack +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/dit_tail.h b/include/vllm/model_executor/models/dit_tail.h new file mode 100644 index 000000000..d0b756723 --- /dev/null +++ b/include/vllm/model_executor/models/dit_tail.h @@ -0,0 +1,70 @@ +// The S2Mel DiT TAIL — everything after the transformer stack (#634). +// +// Upstream `indextts/s2mel/modules/diffusion_transformer.py:243-253`, index-tts +// @4f8792ff120cd3ea470dd511e997a17c86cddd10, under the shipped config +// (`long_skip_connection: true`, `final_layer_type: wavenet`): +// +// x_res = skip_linear(cat([x_res, x], -1)) // the LONG skip +// h = conv1(x_res) // Linear D -> wavenet H +// t2 = t_embedder2(t) // a SECOND embedder +// h = wavenet(h^T, mask, g=t2)^T + res_projection(x_res) +// h = final_layer(h, t1) +// out = conv2(h^T) // Conv1d H -> in_channels +// +// This composes `wavenet::Forward`, `cfm::TimestepFeatures` and +// `adaln::FinalLayer` rather than reimplementing any of them. +// +// One coupling is NOT obvious from the config and is asserted here: the DiT's +// hidden width must equal the wavenet width, because `final_layer` is built at +// the wavenet width but conditioned on `t1`, which the DiT embeds at its own. +// Both are 512 upstream, so the constraint never shows there. +#pragma once + +#include +#include + +#include "vllm/model_executor/models/adaln.h" +#include "vllm/model_executor/models/wavenet.h" + +namespace vllm { +namespace models { +namespace dit_tail { + +struct Linear { + std::vector weight; // [out, in], torch row-major + std::vector bias; // [out] +}; + +struct Weights { + Linear skip_linear; // [hidden, hidden + in_channels] + Linear conv1; // [wn_hidden, hidden] + Linear res_projection; // [wn_hidden, hidden] + Linear conv2; // Conv1d kernel 1: [in_channels, wn_hidden] + Linear t_embedder2_mlp0; // [wn_hidden, freq_size] + Linear t_embedder2_mlp2; // [wn_hidden, wn_hidden] + wavenet::Weights wn; + adaln::FinalLayerWeights final_layer; +}; + +struct Config { + int64_t hidden = 0; + int64_t wn_hidden = 0; // must equal `hidden`; see the header comment + int64_t in_channels = 0; + int64_t frames = 0; + int64_t freq_size = 256; // TimestepEmbedder's frequency_embedding_size + wavenet::Config wn; +}; + +// x_res is [frames, hidden] (the transformer output, frame-major). +// x is [frames, in_channels] (the noisy input at this step, frame-major). +// t1 is [hidden] (the DiT's own timestep embedding), t is the raw timestep. +// mask is [frames] or empty. +// Returns [in_channels, frames], CHANNEL-major, as upstream's conv2 does. +std::vector Forward(const Config& cfg, const Weights& w, + const std::vector& x_res, const std::vector& x, + float t, const std::vector& t1, + const std::vector& mask); + +} // namespace dit_tail +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/fvq.h b/include/vllm/model_executor/models/fvq.h new file mode 100644 index 000000000..a7eeee4cb --- /dev/null +++ b/include/vllm/model_executor/models/fvq.h @@ -0,0 +1,53 @@ +// 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] +}; + +// The fold w = g * v / ||v|| is NOT declared here. It has one home, +// `vocoder1d::MaterializeWeightNorm`, and this file forked it: the copy that +// lived here was byte-equivalent but skipped that one's check that `g` carries +// one magnitude per dim-0 slice. Call the shared one. + +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/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/indextts2_config.h b/include/vllm/model_executor/models/indextts2_config.h new file mode 100644 index 000000000..7a2f08561 --- /dev/null +++ b/include/vllm/model_executor/models/indextts2_config.h @@ -0,0 +1,57 @@ +// IndexTTS-2.5 shape contract, read from the SHIPPED config.yaml (#634). +// +// Every value here was taken from +// huggingface.co/IndexTeam/IndexTTS-2.5/resolve/main/config.yaml, not inferred +// from the paper or the recipe page. `tests/vllm/models/test_indextts2_config` +// pins them, so a port built against the wrong dimensions fails at the contract +// rather than at a tensor. +#pragma once + +#include + +namespace vllm { +namespace models { +namespace indextts2 { + +// ── THE MODEL RUNS AT TWO SAMPLE RATES, and conflating them is the first +// mistake available. The talker's mel front end is 24 kHz with 100 mel bins; +// S2Mel and the vocoder work at 22.05 kHz with 80. The OUTPUT is 22.05 kHz. +inline constexpr int64_t kTalkerMelSampleRate = 24000; +inline constexpr int64_t kTalkerMelBins = 100; +inline constexpr int64_t kOutputSampleRate = 22050; +inline constexpr int64_t kS2MelMelBins = 80; +inline constexpr int64_t kHopLength = 256; +inline constexpr int64_t kNFft = 1024; + +// ── talker (gpt.pth) +inline constexpr int64_t kTalkerDim = 1280; +inline constexpr int64_t kTalkerLayers = 24; +inline constexpr int64_t kTalkerHeads = 20; +inline constexpr int64_t kNumberTextTokens = 60509; +inline constexpr int64_t kNumberMelCodes = 8194; +inline constexpr int64_t kStartMelToken = 8192; +inline constexpr int64_t kStopMelToken = 8193; +inline constexpr int64_t kMaxMelTokens = 1815; +inline constexpr int64_t kMaxTextTokens = 600; +inline constexpr int64_t kMelLengthCompression = 1024; + +// ── semantic codec (codec.pth) +inline constexpr int64_t kCodecCodebookSize = 8192; +inline constexpr int64_t kCodecHiddenSize = 1024; +inline constexpr int64_t kCodecCodebookDim = 8; +inline constexpr int64_t kVocosDim = 384; +inline constexpr int64_t kVocosIntermediateDim = 2048; +inline constexpr int64_t kVocosNumLayers = 12; + +// ── S2Mel (s2mel.pth) +inline constexpr int64_t kStyleDim = 192; // CAMPPlus embedding +inline constexpr int64_t kLengthRegulatorChannels = 512; +inline constexpr int64_t kLengthRegulatorInChannels = 1024; +inline constexpr int64_t kDitHiddenDim = 512; +inline constexpr int64_t kDitNumHeads = 8; +inline constexpr int64_t kDitDepth = 13; +inline constexpr int64_t kDitInChannels = 80; + +} // namespace indextts2 +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/indextts2_pipeline.h b/include/vllm/model_executor/models/indextts2_pipeline.h new file mode 100644 index 000000000..9270e05b0 --- /dev/null +++ b/include/vllm/model_executor/models/indextts2_pipeline.h @@ -0,0 +1,68 @@ +// IndexTTS-2.5 pipeline composition (#634). +// +// Every stage's numerics are ported and gated individually. This wires them into +// ONE path in upstream's order (infer_v2_5.py:569-660): +// +// 1. w2v-bert-2.0 reference clip -> semantic features +// 2. EnhancedCodec features -> semantic codes + quantized feat +// 3. CAMPPlus feat -> 192-d style vector +// 4. length regulator codes -> prompt condition at mel rate +// 5. GPT-2 talker text + style -> mel codes +// 6. S2Mel + BigVGAN mel codes -> 22.05 kHz waveform +// +// WHAT THIS IS AND IS NOT. This is a STRUCTURAL composition: it proves the +// stages connect, that each one's output shape is the next one's input shape, +// and that a change at the front propagates to the back. It runs at reduced +// dimensions on synthetic weights and is NOT a quality result, NOT a parity +// result, and NOT a render. The same distinction the MiniMax-H3 lane drew when +// it composed t2va before real weights existed. +// +// A real render additionally needs the checkpoint loader and the vLLM-Omni +// oracle (#633), neither of which exists yet. +#pragma once + +#include +#include +#include + +namespace vllm { +namespace models { +namespace indextts2 { + +// Reduced-dimension shape contract. The real model's values come from +// `config.yaml`; these are the knobs the composition needs to agree on. +struct PipelineDims { + int64_t ref_frames = 0; // reference-clip frames into w2v-bert + int64_t semantic_dim = 0; // w2v-bert hidden + int64_t codec_dim = 0; // EnhancedCodec input width + int64_t codebook_dim = 0; + int64_t codebook_size = 0; + int64_t style_feat_dim = 0; // CAMPPlus feat_dim + int64_t style_dim = 0; // CAMPPlus embedding (192 in the real model) + int64_t mel_frames = 0; // target mel length + int64_t mel_channels = 0; + int64_t talker_dim = 0; + int64_t talker_vocab = 0; +}; + +struct PipelineResult { + std::vector semantic_codes; // stage 2 + std::vector style; // stage 3, [style_dim] + std::vector quantized; // stage 2 output, [codec_dim, ref_frames] + std::vector prompt_condition; // stage 4, [codec_dim, mel_frames] + std::vector mel; // stage 5+6 input, [mel_channels, mel_frames] + std::vector waveform; // stage 6 output + int64_t sample_rate = 22050; +}; + +// Run the composed path over a reference clip and a text token sequence. +// +// Throws std::runtime_error naming the stage when a shape does not line up -- +// which is the failure this seam exists to make loud, since a silent reshape +// between stages is how a pipeline produces audio from the wrong tensor. +PipelineResult RunReduced(const PipelineDims& dims, const std::vector& reference_clip, + const std::vector& text_tokens, uint64_t seed); + +} // namespace indextts2 +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/indextts2_s2mel_loader.h b/include/vllm/model_executor/models/indextts2_s2mel_loader.h new file mode 100644 index 000000000..4d61c4aea --- /dev/null +++ b/include/vllm/model_executor/models/indextts2_s2mel_loader.h @@ -0,0 +1,45 @@ +// Bind the converted S2Mel checkpoint to the ported DiT tail (#634). +// +// Reads `s2mel.safetensors`, produced offline by +// `scripts/convert-indextts2-checkpoint.py` from upstream's `s2mel.pth`. The +// engine never reads pickle; see that script's header for why. +// +// Names are the manifest's names: the converter joins nested state dicts with +// '.', so `net.cfm.estimator.conv1.weight` here is the same string +// `tests/vllm/models/indextts2_pth_manifest.json` records. +// +// DIMENSIONS ARE RESOLVED FROM THE TENSORS, not from a config. The shipped +// checkpoint says hidden 512, in_channels 80, wavenet 512 wide over 8 layers of +// kernel 5, and `t_embedder2.mlp.0.weight` is [512, 256], so the sinusoidal +// feature width is 256. A config that disagreed with the weights would be the +// config that is wrong. +// +// The shipped S2Mel tower is F32 throughout, which is recorded rather than +// assumed: this is one of the rare places where f32 is what upstream ACTUALLY +// stores, not a widening we introduced. +#pragma once + +#include + +#include "vllm/model_executor/model_loader/safetensors_reader.h" +#include "vllm/model_executor/models/dit_tail.h" + +namespace vllm { +namespace models { +namespace indextts2 { + +struct S2MelTail { + dit_tail::Config config; + dit_tail::Weights weights; +}; + +// Throws std::runtime_error naming the missing or misshapen tensor. A checkpoint +// that is merely INCOMPLETE must not load as if it were whole. +S2MelTail LoadS2MelTail(const SafetensorsFile& file); + +// Convenience: open the file and load. `path` is the converted safetensors. +S2MelTail LoadS2MelTail(const std::string& path); + +} // namespace indextts2 +} // namespace models +} // namespace vllm 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/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/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/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/include/vllm/model_executor/models/minimax_h3.h b/include/vllm/model_executor/models/minimax_h3.h index bce4e8934..26f1c4586 100644 --- a/include/vllm/model_executor/models/minimax_h3.h +++ b/include/vllm/model_executor/models/minimax_h3.h @@ -487,96 +487,11 @@ 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, - const std::vector& v, - int64_t out_channels); +// Every conv in this decoder is weight-normalized, so the checkpoint stores +// (g, v). The fold `w = g * v / ||v||` moved to `vocoder1d::MaterializeWeightNorm` +// when MiniMax-Music3's vocoder became its second consumer: a model's header is +// not a home for something two lanes share, and a second copy of the reduction +// axis is the duplicate that goes wrong quietly. struct StTensor; class SafetensorsFile; @@ -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/minimax_music3_loader.h b/include/vllm/model_executor/models/minimax_music3_loader.h new file mode 100644 index 000000000..c6c5c3218 --- /dev/null +++ b/include/vllm/model_executor/models/minimax_music3_loader.h @@ -0,0 +1,490 @@ +// MiniMax-Music3 — the modular six-component checkpoint loader (W1 of #672). +// +// Row MODEL-MUSIC-minimax-music3-mini-max-music3-for-conditional-generation, +// .agents/specs/minimax-music3.md phase W1. Issue #672. +// +// ─── WHAT THIS PHASE IS, AND WHAT IT IS NOT ───────────────────────────────── +// +// It resolves the shipped `diffusers` layout, parses each component's config, +// enumerates the tensors that config OWES, and accounts every tensor the files +// actually carry against that enumeration. No forward, no scheduler step, no +// generation: those are W2..W6. What it buys is that a wrong-shaped or +// wrong-dtype component is a LOAD-TIME refusal naming the component and the +// tensor, instead of a plausible, finite, wrong song several phases later. +// +// ─── TWO PACKAGINGS, AND ONLY ONE IS LOADED ───────────────────────────────── +// +// `MiniMaxAI/MiniMax-Music3` ships the same weights twice (spec section 2). The +// NATIVE arm is `qwen_7B/qwen_7B/` + `flowmatching_vae.pth` + `dav.pth` +// (convert_minimax_music3_to_diffusers.py:30,34,38) and is what SGLang-Omni +// serves; the DIFFUSERS arm is the six safetensors components this port loads. +// +// The native arm is REFUSED BY NAME. It is not silently mis-loaded, and the +// refusal names the diffusers components that are missing rather than saying +// "unsupported": a `qwen_7B/` tree pointed at this loader has to be told which +// artifact it is and which one this port wants, or the next person re-derives +// it. `MiniMaxMusic3ResolveCheckpoint` is where that happens. +// +// ─── ON-DISK DTYPE AND RUNTIME DTYPE ARE DIFFERENT THINGS HERE ────────────── +// +// WHAT THE FILES STORE, measured from the headers and not in dispute: +// `transformer`, `condition_encoder` and `vocoder` are F32, +// `rvq_depth_decoder` and `language_model` are BF16. That is what +// `convert_minimax_music3_to_diffusers.py` writes — `:267` defaults +// `--dtype float32`, `:208-211` applies it to three components, `:214` forces +// the depth decoder to bf16 — and `EnumerateMiniMaxMusic3*Tensors` states it +// per component so `MiniMaxMusic3AccountTensors` can refuse a file that +// disagrees. Those dtypes are FACTS ABOUT THE FILES and they stay. +// +// WHAT WILL ACTUALLY RUN IS NOT THAT SET, and this correction is the reason +// this section was rewritten. The spec's section 2.1 read the converter's +// output as upstream's resolved RUNTIME policy. Running the oracle refuted it: +// the on-disk set is NOT RUNNABLE through upstream's own pipeline. +// +// Upstream casts in exactly two places and nowhere else: +// +// denoise.py:83 the condition encoder's OUTPUT -> transformer.dtype +// decoders.py:84 the latents -> vocoder.dtype +// +// Nothing casts on the way IN. `denoise.py:82` hands +// `block_state.frame_hiddens[...].to(device)` to the condition encoder with a +// device move and no dtype move, so the encoder and the depth decoder consume +// the LANGUAGE MODEL's hidden states at the language model's dtype. Load the +// on-disk set and `condition_embedder_minimax_music3.py:64` raises +// +// RuntimeError: Input type (c10::BFloat16) and bias type (float) +// should be the same +// +// because `self.proj` is an fp32 `nn.Conv1d` being fed bf16. The two lines +// above it are the tell: `:61` and `:63` cast `layer_weight_logits` and +// `layer_scale` with `.to(hidden_states.dtype)`, so the learned MIX follows the +// hidden states — but `proj` is a MODULE and is never cast, so it cannot. +// +// THE INVARIANT THAT ACTUALLY HOLDS, and what this loader enforces: +// +// dtype(language_model) == dtype(rvq_depth_decoder) == dtype(condition_encoder) +// +// The gated configuration is `kBf16ArFp32Acoustic`: the AR half (language +// model, depth decoder, condition encoder) in bf16 and the acoustic half +// (transformer, vocoder) in fp32. That is also the converter's default for the +// DiT and the vocoder, and what SGLang-Omni states it runs. +// +// `kAsStored` is kept SELECTABLE rather than deleted, so the failure stays +// reproducible against the oracle's own `--dtype-policy on-disk`. It is +// reported as not-runnable rather than quietly repaired. +// +// So the fp32 on the acoustic half still needs no apology under AGENTS.md's +// too-wide rule — the oracle runs fp32 there too. What is NOT true, and what +// this file previously said, is that the whole on-disk set is the runtime +// policy. `MiniMaxMusic3CheckRuntimeDtypes` refuses a violating configuration +// BY NAME, naming all three AR components and their dtypes, because a refusal +// that says which three disagree is worth more than the torch type error +// upstream gives from inside a forward pass. +// +// ─── WEIGHT NORM IS FOLDED AT LOAD, NOT REPRODUCED ────────────────────────── +// +// The vocoder ships torch's LEGACY `weight_g`/`weight_v` pair for all 30 of its +// weight-normed convolutions — MEASURED from the header, not counted by eye: +// `conv_in` and `conv_out`, plus per block one `conv_t1` and three residual +// units of two convs each (7 x 4 = 28). With the 2 `dec_in_proj` tensors and +// the 29 snake alphas that is 2 + 30*3 + 29 = 121, the component's whole tensor +// count. (`weight_norm(...)` at minimax_music3_vocoder.py:42,44,55,89,98.) +// `w = g * v / ||v||` with the norm taken over every dimension but dim 0 — a +// pure function of the stored parameters, constant for the whole run. +// +// DECISION: FOLD, do not reproduce the parameterization. Three reasons, in +// order. (1) It is what torch itself computes once and caches; reproducing it +// per forward would recompute a constant every denoise step. (2) The forward +// then consumes ONE weight per convolution, so W5's decoder is the same shape +// as every other conv decoder in this tree and cannot accidentally read `v` as +// if it were `w` — which is precisely the mis-load minimax_h3_vae_loader.cpp:11 +// records catching. (3) Folding is checkable in isolation, which is what the +// mutation gate in test_minimax_music3_loader.cpp does; a parameterization +// spread across a forward is not. +// +// The fold itself is NOT a new function. `vocoder1d::MaterializeWeightNorm` is +// the one home, shared with MiniMax-H3's audio VAE, and +// tests/scripts/test_vocoder1d_single_home.py asserts it stays that way. +// +// ─── WHAT THE SHARED SEAMS DO AND DO NOT ALREADY COVER ────────────────────── +// +// `vocoder1d` is a PRIMITIVE library — Conv1d, ConvTranspose1d, Pad1d, +// SnakeActivation, AliasFreeActivation1d, KaiserSincFilter1d — not an +// instantiable decoder. There is no `Vocoder1D` class for Music3's vocoder to +// be a configuration of, so this loader materializes weights and W5 will +// compose the primitives. Two of them fit unchanged and are recorded here so +// W5 does not re-derive it: Music3's `MiniMaxMusic3Snake1d` +// (minimax_music3_vocoder.py:25-34) is `x + (alpha + 1e-9)^-1 * sin^2(alpha*x)`, +// which is EXACTLY `vocoder1d::SnakeActivation` with a null `beta` and +// `logscale=false`, down to `kSnakeEps`; and Music3 uses PLAIN snake with no +// up/downsample, so `AliasFreeActivation1d` does not apply to it. +// +// `multimodal::SpeechEngine` is the TTS engine seam (text + reference clip -> +// mono waveform). It is the right home for W6's end-to-end surface and it is +// not a loader seam: it has no notion of a component set, and nothing in it is +// reachable from a phase that only reads headers. W1 therefore mirrors +// `ltx2_loader.h` — the other multi-component diffusion checkpoint — and adds +// nothing to `SpeechEngine`. +#pragma once + +#include +#include +#include +#include + +namespace vllm { + +class SafetensorsFile; + +// --------------------------------------------------------------------------- +// Which packaging is on disk +// --------------------------------------------------------------------------- + +// The three files/directories that IDENTIFY the native arm, exactly as +// convert_minimax_music3_to_diffusers.py:30,34,38 resolves them and as +// sglang_omni/models/minimax_music3/checkpoint.py:35-56 serves them. +inline constexpr const char* kMusic3NativeDitFile = "flowmatching_vae.pth"; +inline constexpr const char* kMusic3NativeVaeFile = "dav.pth"; +inline constexpr const char* kMusic3NativeQwenDir = "qwen_7B"; + +// The six components of the diffusers arm, in `modular_model_index.json` order. +inline constexpr const char* kMusic3Components[] = { + "condition_encoder", "language_model", "rvq_depth_decoder", + "scheduler", "tokenizer", "transformer", +}; +inline constexpr int kMusic3ComponentCount = 6; +// `vocoder` is a seventh directory that `modular_model_index.json` also lists; +// the array above is the index's own key order and the vocoder sorts last. +inline constexpr const char* kMusic3VocoderComponent = "vocoder"; + +// Every resolved path a load needs. Produced only for a checkpoint that IS the +// diffusers arm. +struct MiniMaxMusic3Paths { + std::string root; + std::string modular_index; // modular_model_index.json + std::string transformer_dir; + std::string condition_encoder_dir; + std::string rvq_depth_decoder_dir; + std::string vocoder_dir; + std::string language_model_dir; + std::string scheduler_config; // scheduler/scheduler_config.json + std::string tokenizer_dir; + // Shard files, in index order, for the two sharded components. + std::vector transformer_shards; + std::vector language_model_shards; +}; + +// Resolve `root` as the diffusers arm, or THROW naming what is wrong. +// +// Three refusals, and they are distinguishable on purpose: +// +// * a NATIVE-arm tree (`qwen_7B/` + `flowmatching_vae.pth` + `dav.pth`, or +// any of the three) is named as the native arm, told that only the +// diffusers arm is supported, and told which diffusers components it lacks; +// * a tree that is neither is refused for the components it lacks; +// * a diffusers tree missing one component names that component. +// +// The native-arm case is separated because it is the one that would otherwise +// LOOK loadable: it holds every weight this port needs, in a layout nothing +// here reads. Recorded as owed in the spec rather than discovered at W6. +MiniMaxMusic3Paths MiniMaxMusic3ResolveCheckpoint(const std::string& root); + +// True when `root` carries any native-arm marker. Never throws: used by the +// refusal to decide WHICH message to raise, and exposed so a caller can ask. +bool MiniMaxMusic3IsNativeArm(const std::string& root); + +// --------------------------------------------------------------------------- +// The configs, as the released checkpoint states them +// --------------------------------------------------------------------------- + +// transformer/config.json. F32 on disk (spec section 2.1: upstream's resolved +// `--dtype float32`, convert_minimax_music3_to_diffusers.py:208,267). +struct MiniMaxMusic3TransformerConfig { + int64_t in_channels = 128; + int64_t condition_dim = 2048; + int64_t num_layers = 36; + int64_t num_attention_heads = 32; + int64_t attention_head_dim = 64; + int64_t ff_inner_dim = 8192; + int64_t rotary_dim = 32; + int64_t fourier_embedding_dim = 256; + + // transformer_minimax_music3.py:177 — `num_attention_heads * attention_head_dim`. + int64_t inner_dim() const { return num_attention_heads * attention_head_dim; } + // transformer_minimax_music3.py:178 — the input concatenates + // [latent, zeros(in_channels), condition] along channels. + int64_t concat_channels() const { return 2 * in_channels + condition_dim; } +}; + +// condition_encoder/config.json. F32 on disk (spec section 2.1). +// +// FOUR TENSORS, and that is the finding rather than an omission: the module is +// a learned weighted MIX over `num_condition_layers` language-model hidden +// layers plus one Conv1d, not an encoder tower +// (condition_embedder_minimax_music3.py:44-46). +struct MiniMaxMusic3ConditionEncoderConfig { + int64_t condition_hidden_dim = 4096; + int64_t num_condition_layers = 8; + int64_t out_dim = 2048; + int64_t input_sampling_rate = 24000; + int64_t input_hop_length = 960; + int64_t output_sampling_rate = 44100; + int64_t output_hop_length = 512; +}; + +// rvq_depth_decoder/config.json. BF16 on disk — forced regardless of `--dtype` +// (convert_minimax_music3_to_diffusers.py:214), so it is the one component +// whose narrow dtype is upstream's explicit choice rather than the default. +struct MiniMaxMusic3RvqDepthDecoderConfig { + int64_t hidden_size = 4096; + int64_t num_layers = 4; + int64_t num_attention_heads = 16; + int64_t intermediate_size = 6144; + int64_t audio_vocab_size = 1024; + int64_t num_codebooks = 8; + int64_t max_position_embeddings = 16; + + // minimax_music3_rvq_depth_decoder.py:113,124 — the embedding table covers + // the RESIDUAL codebooks only, and there is one head per residual codebook. + int64_t residual_codebooks() const { return num_codebooks - 1; } +}; + +// vocoder/config.json. F32 on disk (spec section 2.1). +struct MiniMaxMusic3VocoderConfig { + int64_t latent_channels = 128; + int64_t decoder_input_dim = 1024; + int64_t decoder_hidden_dim = 1536; + std::vector upsampling_ratios{8, 8, 4, 2}; + int64_t sampling_rate = 44100; + + // minimax_music3_vocoder.py:110,115 — the 128 latent channels are folded into + // TWO streams of 64, which is where the stereo pair comes from. + int64_t stream_channels() const { return latent_channels / 2; } + // 8*8*4*2 = 512 waveform samples per latent frame; 44100/512 = 86.133 Hz is + // the latent frame rate the condition encoder sets (spec section 1.1). + int64_t hop_length() const; +}; + +// language_model/config.json — a stock `Qwen3ForCausalLM`, retrained on a +// 200 000-entry music vocabulary. BF16 on disk (its own `"dtype": "bfloat16"`). +struct MiniMaxMusic3LanguageModelConfig { + int64_t hidden_size = 4096; + int64_t intermediate_size = 12288; + int64_t num_hidden_layers = 36; + int64_t num_attention_heads = 32; + int64_t num_key_value_heads = 8; + int64_t head_dim = 128; + int64_t vocab_size = 200000; + int64_t max_position_embeddings = 10240; + double rope_theta = 1000000.0; + double rms_norm_eps = 1e-6; + bool tie_word_embeddings = false; +}; + +// scheduler/scheduler_config.json — `FlowMatchEulerDiscreteScheduler`. +// +// `num_train_timesteps: 1` is not a typo and is read rather than defaulted: the +// conversion script's own comment (convert_minimax_music3_to_diffusers.py:220) +// says it keeps `scheduler.timesteps` equal to the flow-matching time in [0, 1] +// that the transformer's Fourier embedding expects. +struct MiniMaxMusic3SchedulerConfig { + int64_t num_train_timesteps = 1; + double shift = 1.0; + bool invert_sigmas = true; + bool use_dynamic_shifting = false; + std::string time_shift_type = "exponential"; +}; + +struct MiniMaxMusic3Config { + MiniMaxMusic3TransformerConfig transformer; + MiniMaxMusic3ConditionEncoderConfig condition_encoder; + MiniMaxMusic3RvqDepthDecoderConfig rvq_depth_decoder; + MiniMaxMusic3VocoderConfig vocoder; + MiniMaxMusic3LanguageModelConfig language_model; + MiniMaxMusic3SchedulerConfig scheduler; +}; + +// Parse all six configs. Every `_class_name` / `architectures` entry is checked +// against the class this port implements and refused BY NAME on a mismatch: a +// config that silently deserializes to all-defaults is a wrong-shaped model +// with no error, which is exactly what .agents/porting-a-model.md section 1 +// forbids. +MiniMaxMusic3Config MiniMaxMusic3LoadConfig(const MiniMaxMusic3Paths& paths); + +// --------------------------------------------------------------------------- +// RUNTIME dtype — what each component will RUN in, not what its file stores +// --------------------------------------------------------------------------- + +// The five weight-bearing components, in AR-then-acoustic order, so a refusal +// can name them in the order the pipeline uses them. +struct MiniMaxMusic3RuntimeDtypes { + // The AR half. These three MUST agree: upstream never casts between them + // (denoise.py:82 moves device only), so a disagreement is a torch type error + // from inside `condition_embedder_minimax_music3.py:64`. + std::string language_model; + std::string rvq_depth_decoder; + std::string condition_encoder; + // The acoustic half. Each is reached through an explicit cast, so each may + // differ from the AR half and from the other freely. + std::string transformer; // denoise.py:83 casts the condition into it + std::string vocoder; // decoders.py:84 casts the latents into it +}; + +enum class MiniMaxMusic3DtypePolicy { + // GATED. AR half bf16, acoustic half fp32. The converter's default for the + // DiT and the vocoder, and what SGLang-Omni states it runs. + kBf16ArFp32Acoustic, + // The dtypes the FILES carry. NOT RUNNABLE — kept selectable so the failure + // stays reproducible against the oracle's `--dtype-policy on-disk`, never as + // a default and never silently repaired. + kAsStored, +}; + +MiniMaxMusic3RuntimeDtypes MiniMaxMusic3ResolveRuntimeDtypes(MiniMaxMusic3DtypePolicy policy); + +// True when the three AR components agree, i.e. when the configuration can run +// at all. Never throws, so a caller can ask before committing to a load. +bool MiniMaxMusic3RuntimeDtypesAreRunnable(const MiniMaxMusic3RuntimeDtypes& dtypes); + +// Enforce the invariant, or THROW naming all three AR components with their +// dtypes and the upstream line that would otherwise fail. Refusing here rather +// than at the first Conv1d is the whole point: upstream's message names a bias +// dtype, not which component disagreed with which. +void MiniMaxMusic3CheckRuntimeDtypes(const MiniMaxMusic3RuntimeDtypes& dtypes); + +// The dtype each component's FILE stores. A fact about the artifact, and +// deliberately NOT a runtime policy — see the header note. +MiniMaxMusic3RuntimeDtypes MiniMaxMusic3OnDiskDtypes(); + +// --------------------------------------------------------------------------- +// What each component OWES, derived from its config +// --------------------------------------------------------------------------- + +// One required tensor: the name it ships under on disk, the dtype spec section +// 2.1 fixes for its component, and the exact shape its config implies. +struct MiniMaxMusic3TensorSpec { + std::string name; + std::string dtype; // the safetensors header spelling: "F32" / "BF16" + std::vector shape; +}; + +// Enumerated by walking upstream's own `__init__`, module for module, so the +// enumeration is traceable rather than transcribed from a header dump: +// +// transformer transformer_minimax_music3.py:179-192, :134-138 +// condition_encoder condition_embedder_minimax_music3.py:44-46 +// rvq_depth_decoder minimax_music3_rvq_depth_decoder.py:113-124, :78-83 +// vocoder minimax_music3_vocoder.py:88-98, :54-62, :41-44 +// language_model transformers `Qwen3ForCausalLM`, with `q_norm`/`k_norm` +// per head_dim (Qwen3's per-head RMSNorm) +// +// Returned in a stable, sorted-by-name order so two callers cannot disagree. +std::vector EnumerateMiniMaxMusic3TransformerTensors( + const MiniMaxMusic3TransformerConfig& config); +std::vector EnumerateMiniMaxMusic3ConditionEncoderTensors( + const MiniMaxMusic3ConditionEncoderConfig& config); +std::vector EnumerateMiniMaxMusic3RvqDepthDecoderTensors( + const MiniMaxMusic3RvqDepthDecoderConfig& config); +std::vector EnumerateMiniMaxMusic3VocoderTensors( + const MiniMaxMusic3VocoderConfig& config); +std::vector EnumerateMiniMaxMusic3LanguageModelTensors( + const MiniMaxMusic3LanguageModelConfig& config); + +// Every component's enumeration, keyed by component directory name. +std::map> +EnumerateMiniMaxMusic3Tensors(const MiniMaxMusic3Config& config); + +// --------------------------------------------------------------------------- +// Accounting: enumerated == present, zero unaccounted +// --------------------------------------------------------------------------- + +// One tensor as a FILE declares it. Header metadata only — this is what a +// manifest gate consumes and what a real `SafetensorsFile` is reduced to, so +// one accounting implementation serves both and they cannot disagree. +struct MiniMaxMusic3ManifestEntry { + std::string name; + std::string dtype; + std::vector shape; +}; + +// What the account examined. A gate that cannot say HOW MANY things it looked +// at has not reported, so these are returned rather than logged. +struct MiniMaxMusic3AccountReport { + int64_t required = 0; + int64_t present = 0; + int64_t matched = 0; +}; + +// Account `present` against `required` for one component, or THROW naming the +// component and the first offending tensor. +// +// FOUR failure modes, each named separately, because collapsing them is how a +// refusal stops being evidence: a required tensor MISSING, a present tensor +// UNACCOUNTED for, a SHAPE mismatch (with both shapes printed), and a DTYPE +// mismatch (with both dtypes printed, and the spec section that fixes it). +// +// Returns counts on success; `matched == required == present` always holds when +// it returns, which is what makes a silent partial match impossible. +MiniMaxMusic3AccountReport MiniMaxMusic3AccountTensors( + const std::string& component, const std::vector& required, + const std::vector& present); + +// The same account against a real file's header. +MiniMaxMusic3AccountReport MiniMaxMusic3AccountFile( + const std::string& component, const std::vector& required, + const SafetensorsFile& file); + +// Reduce a file's header to manifest entries. No payload is touched. +std::vector MiniMaxMusic3ReadManifest( + const SafetensorsFile& file); + +// --------------------------------------------------------------------------- +// Materialization +// --------------------------------------------------------------------------- + +// One host-resident tensor with the dtype the FILE carries preserved. Nothing +// on this path widens or narrows: spec section 2.1's policy is upstream's, and +// a loader that "helpfully" converted would be making the measured change the +// spec reserves for its own evidence. +struct MiniMaxMusic3Tensor { + std::string dtype; + std::vector shape; + std::vector bytes; + + int64_t numel() const; +}; + +struct MiniMaxMusic3ComponentWeights { + std::string component; + std::map tensors; +}; + +// Materialize one component, accounting it first. Every enumerated tensor must +// exist at its enumerated shape AND dtype before a byte is copied, so a missing +// tensor can never read as zeros. +MiniMaxMusic3ComponentWeights MiniMaxMusic3LoadComponent( + const std::string& component, const std::vector& required, + const SafetensorsFile& file); + +// The vocoder, with every `weight_g`/`weight_v` pair FOLDED to a single +// `.weight` and no `_g`/`_v` name surviving. F32 throughout — the +// checkpoint's own dtype (spec section 2.1), not a widening. +// +// `folded` counts the pairs collapsed; 20 for the shipped checkpoint, and it is +// returned rather than assumed so a file that lost a pair is visible. +struct MiniMaxMusic3VocoderWeights { + std::map> tensors; + std::map> shapes; + int64_t folded = 0; +}; + +MiniMaxMusic3VocoderWeights MiniMaxMusic3LoadVocoderWeights( + const MiniMaxMusic3VocoderConfig& config, const SafetensorsFile& file); + +// The `` names whose weight this checkpoint stores weight-normed, in +// enumeration order. Derived from the same walk the enumeration uses, so the +// two cannot drift. +std::vector MiniMaxMusic3WeightNormedModules( + const MiniMaxMusic3VocoderConfig& config); + +} // namespace vllm diff --git a/include/vllm/model_executor/models/qwen3_5_dense.h b/include/vllm/model_executor/models/qwen3_5_dense.h index d2a44f71f..888a91dbd 100644 --- a/include/vllm/model_executor/models/qwen3_5_dense.h +++ b/include/vllm/model_executor/models/qwen3_5_dense.h @@ -184,11 +184,15 @@ GdnLayerWeights LoadQwen3_5DenseGdn(const TensorResolver& get, const std::string& layer_base); // Load one dense decoder layer. `layer_type` is "linear_attention" or -// "full_attention". Prefix is "model.language_model.layers.{layer_idx}.". Routes -// each Linear to ordinary BF16 or compressed NVFP4 based on tensor presence. -Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer(const TensorResolver& get, - const std::string& layer_type, - int64_t layer_idx); +// "full_attention". Prefix is "{backbone_prefix}layers.{layer_idx}.", defaulting +// to the VL spelling every checkpoint we gate today uses, so this seam is +// byte-identical for the 27B/35B/Coder callers; `LoadQwen3_5Dense` passes the +// prefix it resolved ONCE from the shard index +// (`ResolveQwen3_5BackbonePrefix`). Routes each Linear to ordinary BF16 or +// compressed NVFP4 based on tensor presence. +Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer( + const TensorResolver& get, const std::string& layer_type, int64_t layer_idx, + const std::string& backbone_prefix = std::string(kQwen3_5VlBackbonePrefix)); // The same load with an EXPLICIT presence probe — what `LoadQwen3_5Dense` calls // per layer. The resolver-only overload above answers `has` with a constant @@ -199,7 +203,8 @@ Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer(const TensorResolver& get, // production takes. Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer( const TensorResolver& get, const std::function& has, - const std::string& layer_type, int64_t layer_idx); + const std::string& layer_type, int64_t layer_idx, + const std::string& backbone_prefix = std::string(kQwen3_5VlBackbonePrefix)); // Full dense-model load across the given shards. Uses config.num_hidden_layers // and config.layer_types. Text path only — the vision tower (model.visual.*) diff --git a/include/vllm/model_executor/models/qwen3_5_weights.h b/include/vllm/model_executor/models/qwen3_5_weights.h index 512e470b2..82cdff607 100644 --- a/include/vllm/model_executor/models/qwen3_5_weights.h +++ b/include/vllm/model_executor/models/qwen3_5_weights.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "vllm/model_executor/model_loader/safetensors_reader.h" @@ -488,6 +489,52 @@ struct Qwen3_5MoeWeights { // Resolves a tensor name to its StTensor (across shards). Throws if absent. using TensorResolver = std::function; +// --- Backbone weight namespace (MODEL-TEXT-qwen3-5-*-for-causal-lm) ----------- +// +// A Qwen3.5-family checkpoint publishes its text backbone under ONE of two +// spellings. The multimodal wrappers we already gate (Qwen3.6-27B / 35B-A3B / +// Coder) nest it under `model.language_model.`; the TEXT-ONLY arms +// (`Qwen3_5ForCausalLM` / `Qwen3_5MoeForCausalLM`, e.g. +// `Qwen/Qwen3.8-2.4T-A95B`) publish it flat under `model.`. The BACKBONE names +// are otherwise identical — same `mlp.shared_expert_gate.weight`, same +// top-level `lm_head`. +// +// THE PREFIX IS NOT THE ONLY THING BETWEEN THIS LOADER AND A PUBLISHED +// CHECKPOINT, and an earlier revision of this comment wrongly implied it was. +// The published Qwen3.5-family MoE repos ship 3-D STACKED routed experts +// (`...mlp.experts.gate_up_proj` / `.down_proj`) and carry no quantization +// scales at all, while `LoadQwen3_5Moe` reads ONLY per-expert NVFP4. That arm +// is OWED and is refused by name (`CheckMoeExpertLayoutSupported`, +// `qwen3_5_weights.cpp`). The DENSE loader is different: it routes BF16 vs FP8 +// vs NVFP4 per projection by tensor presence, so it may genuinely read a flat +// bf16 checkpoint. Resolving the namespace is what THIS seam does; it is not a +// support claim for either published checkpoint. +// +// Upstream normalizes the two with ONE mapper — +// WeightsMapper(orig_to_new_prefix={"model.language_model.": "model."}) +// (vllm/model_executor/models/qwen3_5.py:296-300 @ `ad5d29db7`, PR #50210, +// which is AHEAD OF our `555967922` parity pin and recorded as such) — +// so `model.` is canonical and the VL spelling is its accepted alias. +inline constexpr std::string_view kQwen3_5VlBackbonePrefix = + "model.language_model."; +inline constexpr std::string_view kQwen3_5TextBackbonePrefix = "model."; + +// Decides which of the two the checkpoint uses, ONCE, from the shard index, so +// every subsequent lookup in a load uses one namespace. Deliberately NOT a +// per-lookup fallback: a fallback would let a checkpoint bind half its tensors +// from one namespace and half from the other and still appear to load. +// +// Only BACKBONE spellings vote — `embed_tokens.weight`, +// `norm.weight` and `layers.`. `model.visual.*` (the +// vision-inclusive 27B/35B towers) and the top-level `lm_head.*` / `mtp.*` +// therefore cast no vote, which is what keeps a vision checkpoint from looking +// like a flat text one. +// +// Throws std::runtime_error when BOTH namespaces carry backbone tensors (a +// mixed index is refused, never half-loaded) and when NEITHER does. +std::string ResolveQwen3_5BackbonePrefix( + const std::vector& tensor_names); + // --- ENG-LOAD-DIRECT-UPLOAD (issue #150) ------------------------------------- // // THE DEFECT THIS CLOSES. Loading a checkpoint copies the weights TWICE: the @@ -532,11 +579,14 @@ void SetLoadDirectUploadOverrideForTesting(std::optional value); // Load one decoder layer's weights from real tensors. `layer_type` is // "linear_attention" or "full_attention"; `num_experts` drives the expert loop. // Exercised on real data by the Task 3 unit test (both layer types live in -// shard 1). Prefix is "model.language_model.layers.{layer_idx}.". -Qwen3_5MoeLayerWeights LoadQwen3_5MoeLayer(const TensorResolver& get, - const std::string& layer_type, - int64_t layer_idx, - int64_t num_experts); +// shard 1). Prefix is "{backbone_prefix}layers.{layer_idx}.", and the default +// is the VL spelling every checkpoint we gate today uses, so this seam is +// byte-identical for the 27B/35B/Coder callers. `LoadQwen3_5Moe` passes the +// prefix it resolved once from the shard index. +Qwen3_5MoeLayerWeights LoadQwen3_5MoeLayer( + const TensorResolver& get, const std::string& layer_type, int64_t layer_idx, + int64_t num_experts, + const std::string& backbone_prefix = std::string(kQwen3_5VlBackbonePrefix)); // Full-model load: resolves every param across the given shards (name -> shard // looked up from each file's own header), dequantizes/transposes, and returns diff --git a/include/vllm/model_executor/models/talker.h b/include/vllm/model_executor/models/talker.h new file mode 100644 index 000000000..040d10638 --- /dev/null +++ b/include/vllm/model_executor/models/talker.h @@ -0,0 +1,41 @@ +// IndexTTS-2.5 talker embedding scaffolding (#634). +// +// The talker (`UnifiedVoice`, model_v2.py) runs a GPT-2 backbone -- already +// ported -- over a sequence assembled from text tokens, mel codes and a speaker +// conditioning latent. This header covers that assembly. +// +// ITS POSITION EMBEDDINGS ARE NOT GPT-2's `wpe`. `LearnedPositionEmbeddings` +// (model_v2.py:244-256) is a SEPARATE learned table, added on top of the token +// embedding before the backbone runs -- so the backbone's own wpe applies as +// well. Treating them as one table silently halves the positional signal. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace talker { + +// LearnedPositionEmbeddings::forward — rows 0 .. seq_len-1 of the table, i.e. +// the positions a FULL sequence occupies. +std::vector PositionRows(const std::vector& table, int64_t dim, int64_t seq_len); + +// LearnedPositionEmbeddings::get_fixed_embedding — the SINGLE row at `index`. +// +// This is the incremental-decode path: at generation step n the position is n, +// not 0. Returning row 0 每 step makes every generated frame believe it is the +// first, which still decodes to audio and destroys the prosody. The distinction +// cannot be seen from shapes. +std::vector PositionRowAt(const std::vector& table, int64_t dim, int64_t index); + +// Token embedding lookup plus the learned position row, the assembly the talker +// performs for both its text and mel streams. +std::vector EmbedWithPositions(const std::vector& tokens, + const std::vector& token_table, + const std::vector& pos_table, int64_t dim, + int64_t vocab_size); + +} // namespace talker +} // namespace models +} // namespace vllm diff --git a/include/vllm/model_executor/models/vocoder1d.h b/include/vllm/model_executor/models/vocoder1d.h new file mode 100644 index 000000000..49aa9b5a8 --- /dev/null +++ b/include/vllm/model_executor/models/vocoder1d.h @@ -0,0 +1,114 @@ +// 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); + +// torch `weight_norm`: w = g * v / ||v||, the norm taken over every dimension +// EXCEPT dim 0. A pure function of the stored parameters, so every consumer +// folds it once at load rather than reproducing the parameterization per +// forward — which is what torch itself caches. +// +// `dim0` is the size of dimension 0 of `v`, and naming it that rather than +// `out_channels` is deliberate: for an `nn.Conv1d` weight [C_out, C_in, K] dim 0 +// IS the output channel, but for the `nn.ConvTranspose1d` weight +// [C_in, C_out, K] it is the INPUT channel, and torch normalizes over dim 0 +// either way. MiniMax-Music3's vocoder carries both spellings in one file +// (minimax_music3_vocoder.py:55 transpose, :42/:44/:89/:98 conv), so a helper +// that assumed "out channels" would fold four of its thirty convolutions over +// the wrong axis while still producing finite, correctly shaped weights. +// +// Second consumer, hence its home here rather than in a model's header: +// MiniMax-H3's audio VAE (`parametrizations.weight.original0/1`, the modern +// spelling) and MiniMax-Music3's vocoder (`weight_g`/`weight_v`, the legacy +// one). Same arithmetic, different era. +std::vector MaterializeWeightNorm(const std::vector& g, + const std::vector& v, int64_t dim0); + +} // namespace vocoder1d +} // namespace vllm 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/include/vllm/model_executor/models/w2vbert.h b/include/vllm/model_executor/models/w2vbert.h new file mode 100644 index 000000000..74556ca0e --- /dev/null +++ b/include/vllm/model_executor/models/w2vbert.h @@ -0,0 +1,134 @@ +// 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); + + +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); + + +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); + + +// 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/include/vllm/model_executor/models/wavenet.h b/include/vllm/model_executor/models/wavenet.h new file mode 100644 index 000000000..3531fe427 --- /dev/null +++ b/include/vllm/model_executor/models/wavenet.h @@ -0,0 +1,62 @@ +// WaveNet stack — the S2Mel DiT's FINAL LAYER (#634). +// +// Upstream `indextts/s2mel/modules/wavenet.py` class `WN`, index-tts +// @4f8792ff120cd3ea470dd511e997a17c86cddd10. The shipped config sets +// `s2mel.DiT.final_layer_type: wavenet`, so this is the path the model takes, +// not an alternative branch: `net.cfm.estimator.wavenet.*` is in `s2mel.pth`. +// +// Three details are easy to get wrong and are gated: +// - the convolutions are upstream `SConv1d`, whose default `pad_mode` is +// REFLECT, not zero. With stride 1 the extra-padding term is always 0 and +// the padding is symmetric, but it is still a reflection. +// - the gate is `tanh(first half) * sigmoid(second half)` of `x_in + g_l`, +// where `g_l` is this layer's slice of ONE conditioning projection. +// - the mask multiplies the residual update and the final output, but NOT the +// input to `in_layers`, so a masked run is not a shorter run. +// +// Layout is [channels, frames] (channel-major), matching torch's [B, C, T]. +#pragma once + +#include +#include + +namespace vllm { +namespace models { +namespace wavenet { + +// One weight-normed SConv1d. The checkpoint carries the LEGACY (g, v) spelling, +// `...conv.conv.weight_g` / `weight_v`; the fold itself has one home, +// `vocoder1d::MaterializeWeightNorm`. +struct ConvWeights { + std::vector g; // [out_channels] + std::vector v; // [out_channels, in_channels, kernel] + std::vector bias; // [out_channels] +}; + +struct Config { + int64_t hidden = 0; + int64_t kernel = 0; // must be odd, as upstream asserts + int64_t dilation_rate = 1; // layer i dilates by dilation_rate^i + int64_t layers = 0; + int64_t gin = 0; // 0 disables conditioning and `cond` is then unused +}; + +struct Weights { + ConvWeights cond; // [gin -> 2 * hidden * layers], kernel 1 + std::vector in_layers; // [hidden -> 2 * hidden], kernel + // [hidden -> 2 * hidden], except the LAST which is [hidden -> hidden]: + // upstream notes the extra half would never be read. + std::vector res_skip_layers; +}; + +// x is [hidden, frames]; g is [gin] (upstream passes [B, gin, 1], one frame); +// mask is [frames] and may be empty for an all-ones mask. +// Returns [hidden, frames]. +std::vector Forward(const Config& cfg, const Weights& w, + const std::vector& x, int64_t frames, + const std::vector& g, + const std::vector& mask); + +} // namespace wavenet +} // namespace models +} // namespace vllm diff --git a/include/vllm/multimodal/ltx2_video.h b/include/vllm/multimodal/ltx2_video.h index 44877d8df..b74f38d0f 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/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/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/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/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/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/scripts/check-agent-record.py b/scripts/check-agent-record.py index 8dc1b001c..1eb039b60 100644 --- a/scripts/check-agent-record.py +++ b/scripts/check-agent-record.py @@ -65,7 +65,55 @@ # 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. + # 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. + # 375 since 2026-08-14: +`MODEL-TEXT-qwen3-5-qwen3-5-for-causal-lm` and + # +`MODEL-TEXT-qwen3-5-qwen3-5-moe-for-causal-lm` (issue #490), the TEXT-ONLY + # arms of the Qwen3.5 backbone — the eighth architecture the #609/#610 audit + # found and deliberately left to its owner, plus its dense sibling. Both are + # beyond-pin: they are not among the 355 registry architectures at + # `555967922` because they landed upstream afterwards (PR vllm#50210 @ + # `ad5d29db7`), exactly like the Muse Glimmer row above. Their Upstream cells + # deliberately carry no pinned module/class target, so the pin-derived static + # invariants in check_model_invariants are UNCHANGED (324/373/356/310/261) — + # this is the row-EXISTS count only, bumped because two new rows exist, never + # to make a transition pass. This row was authored against 362 -> 364, then + # re-derived to 370 -> 372, and is now RE-DERIVED AGAIN to 373 -> 375: the + # #609/#610 backfill, LTX-2.5, IndexTTS-2.5 and MiniMax-Music3 all landed + # while it was in review, and every one of them moved this pin. The number is + # counted off the matrix as it stands after the merge, never carried forward + # from the branch — a justification framed against a number this file no + # longer carries would be false about the file it sits in, and + # `Qwen35TextOnlyRowsAreCounted` is what ties this value to the two rows the + # matrix actually holds. + # 377 since 2026-08-14, and RE-DERIVED rather than carried forward: +2 for + # dots3-note, which vLLM registers as TWO architectures + # (`Dots3NoteForCausalLM` and its speculative head `Dots3NoteMTPModel`), + # landing `SPIKE` and `INVENTORIED` respectively with the spec committed + # (#699). This is the collision the Music3/IndexTTS comment above warns + # about, happening again on the same day: the #490 branch took 373 -> 375 + # for the Qwen3.5 text-only arms while the dots3 branch took 373 -> 375 for + # its own two rows. BOTH read 375 and neither was right -- the merged tree + # holds four new rows, so it is 377. An auto-merge keeping either side would + # have left this file internally consistent while silently short two real + # architectures, which is why the number is counted off the matrix AFTER the + # merge and why `test_dots3_rows_are_inside_the_model_ratchet` names the rows + # instead of trusting the count. dots3-note is beyond-pin (vLLM `main` only, + # vllm#51255, still being patched), carries no pinned-registry target, and + # leaves the at-the-pin inventory (324/373/356/310/261) unchanged. Bumped + # because two rows EXIST, never to make a transition pass. + "MODEL": (AGENTS / "model-matrix.md", 377), # 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 @@ -411,7 +459,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/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/scripts/convert-indextts2-checkpoint.py b/scripts/convert-indextts2-checkpoint.py new file mode 100644 index 000000000..3ebfa80e8 --- /dev/null +++ b/scripts/convert-indextts2-checkpoint.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +"""Convert IndexTTS-2.5's torch `.pth` checkpoints to safetensors. + +This tree has no torch-pickle reader and deliberately does not grow one: every +other lane loads safetensors or GGUF, and adding a pickle interpreter to the +engine would mean executing a serialization format designed to run arbitrary +code, inside the process that serves users. Upstream ships `.pth`, so the +conversion happens OFFLINE, once, and the engine loads the result through the +reader it already has. + +The mapping is flat and lossless: nested state dicts are joined with '.', which +is exactly the naming `tests/vllm/models/indextts2_pth_manifest.json` records, +so the converted names are the manifest's names and can be checked against it. + +Usage: + python3 scripts/convert-indextts2-checkpoint.py \ + --checkpoint $CHECKPOINT_ROOT/IndexTTS-2.5 --out +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +SOURCES = ("gpt.pth", "codec.pth", "s2mel.pth") + +# `codec.pth` ships optimizer state alongside the weights. It is training +# residue, cannot be loaded by anything here, and is a large fraction of the +# file, so it is dropped -- loudly, with a count, never silently. +DROP_PREFIXES = ("optimizer.",) + + +def flatten(obj, prefix: str = ""): + """Yield (dotted_name, tensor) for every tensor in a nested state dict.""" + if hasattr(obj, "shape") and hasattr(obj, "dtype"): + yield prefix, obj + return + if isinstance(obj, dict): + for key, value in obj.items(): + child = f"{prefix}.{key}" if prefix else str(key) + yield from flatten(value, child) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--checkpoint", required=True, type=Path) + ap.add_argument("--out", required=True, type=Path) + ap.add_argument("--manifest", type=Path, default=None, + help="optional indextts2_pth_manifest.json to verify against") + a = ap.parse_args() + + import torch + from safetensors.torch import save_file + + a.out.mkdir(parents=True, exist_ok=True) + summary = {} + + for name in SOURCES: + src = a.checkpoint / name + if not src.exists(): + raise SystemExit(f"missing {src}") + state = torch.load(src, map_location="cpu", weights_only=False) + + kept, dropped = {}, 0 + for key, tensor in flatten(state): + if key.startswith(DROP_PREFIXES): + dropped += 1 + continue + # safetensors refuses shared storage; contiguous clones are the + # documented fix and keep the values identical. + kept[key] = tensor.detach().cpu().contiguous().clone() + + dst = a.out / (name.replace(".pth", ".safetensors")) + save_file(kept, str(dst), metadata={"source": name, "model": "IndexTTS-2.5"}) + summary[name] = {"kept": len(kept), "dropped": dropped, + "out": dst.name, "bytes": dst.stat().st_size} + print(f"{name:<12} -> {dst.name:<22} {len(kept):5d} tensors" + f" {dropped:4d} dropped (optimizer state)") + + if a.manifest: + man = json.loads(a.manifest.read_text()) + for name in SOURCES: + expected = man[name]["tensors"] + got = summary[name]["kept"] + summary[name]["dropped"] + status = "MATCH" if got == expected else "MISMATCH" + print(f" manifest {name:<12} recorded={expected:5d} seen={got:5d} {status}") + if got != expected: + raise SystemExit( + f"{name}: converted {got} tensors but the manifest records " + f"{expected}; the checkpoint changed under the record") + + (a.out / "conversion.json").write_text(json.dumps(summary, indent=2) + "\n") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/gen-adaln-goldens.py b/scripts/gen-adaln-goldens.py new file mode 100644 index 000000000..554a51fe8 --- /dev/null +++ b/scripts/gen-adaln-goldens.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/adaln_goldens.inc — S2Mel's adaLN / FinalLayer oracle. + +The DiT conditions every block through adaptive layer norm: +`modulate(x, shift, scale) = x * (1 + scale) + shift`, with shift and scale +produced by `SiLU -> Linear(hidden, 2*hidden)` from the conditioning vector. + +THE `1 +` IS THE DETAIL. Dropping it leaves a model that still trains and still +generates; it simply centres the modulation on 0 instead of 1. + +The FinalLayer's norm is `elementwise_affine=False` -- it has NO gamma/beta, so a +port that applies them is reading tensors that do not exist in the checkpoint. + +Usage: DIT_SRC= python3 scripts/gen-adaln-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"]) + sys.path.insert(0, str(src.parents[3])) + 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); spec.loader.exec_module(m) + + torch.manual_seed(0) + HIDDEN, OUT_CH, T = 12, 5, 6 + fl = m.FinalLayer(hidden_size=HIDDEN, patch_size=1, out_channels=OUT_CH).eval() + manifest = [] + with torch.no_grad(): + for n, p in list(fl.named_parameters()) + list(fl.named_buffers()): + p.copy_(P(n, tuple(p.shape) if p.dim() else (1,)).reshape(p.shape)) + manifest.append((n, list(p.shape))) + x = P("ax", (1, T, HIDDEN), 1.0) + c = P("ac", (1, HIDDEN), 1.0) + with torch.no_grad(): + out = fl(x, c) + shift, scale = fl.adaLN_modulation(c).chunk(2, dim=1) + modulated = m.modulate(fl.norm_final(x), shift, scale) + + 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-adaln-goldens.py -- do not edit.\n") + f.write("// S2Mel FinalLayer executed DIRECTLY. norm_final has\n") + f.write("// elementwise_affine=False: NO gamma/beta exist in the checkpoint.\n") + f.write("#pragma once\n\n#include \n\nnamespace adaln_goldens {\n\n") + f.write(f"inline constexpr int64_t kHidden = {HIDDEN};\n") + f.write(f"inline constexpr int64_t kOutCh = {OUT_CH};\n") + f.write(f"inline constexpr int64_t kFrames = {T};\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, 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, "kX", x); emit(f, "kC", c) + emit(f, "kModulated", modulated); emit(f, "kFinalOut", out) + f.write("} // namespace adaln_goldens\n") + print(f"wrote {p}: {len(manifest)} tensors, hidden={HIDDEN}, out_ch={OUT_CH}") + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/gen-campplus-goldens.py b/scripts/gen-campplus-goldens.py new file mode 100644 index 000000000..a31f95153 --- /dev/null +++ b/scripts/gen-campplus-goldens.py @@ -0,0 +1,162 @@ +import importlib.util, sys, types, numpy as np, torch +from pathlib import Path +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) + +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) + + +# ---- 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) + + +# ---- 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) + + +# ---- 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") + 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) + 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) + 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) + 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/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/scripts/gen-dit-front-goldens.py b/scripts/gen-dit-front-goldens.py new file mode 100644 index 000000000..29494873e --- /dev/null +++ b/scripts/gen-dit-front-goldens.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +"""Emit C++ goldens for the S2Mel DiT FRONT END: how x_in is built. + +Upstream `indextts/s2mel/modules/diffusion_transformer.py:243-253`, index-tts +@4f8792ff120cd3ea470dd511e997a17c86cddd10, under the shipped config +(`long_skip_connection: true`, `final_layer_type: wavenet`): + + if long_skip_connection: x_res = skip_linear(cat([x_res, x], dim=-1)) + x = conv1(x_res) # Linear D -> wavenet hidden + x = x.transpose(1, 2) # [B, H, T] + t2 = t_embedder2(t) + x = wavenet(x, x_mask, g=t2.unsqueeze(2)).transpose(1, 2) + res_projection(x_res) + x = final_layer(x, t1).transpose(1, 2) + x = conv2(x) # Conv1d H -> in_channels, kernel 1 + +The DiT is constructed for real at reduced dims, so every module here is +upstream's own; only the SEQUENCE is restated, and it is restated once, next to +the upstream line numbers it copies. + +Usage: DIT_SRC= python3 \ + scripts/gen-dit-front-goldens.py --out tests/vllm/models/dit_front_goldens.inc +""" + +from __future__ import annotations + +import argparse +import importlib.util +import os +import sys +import types +from pathlib import Path +from types import SimpleNamespace + +import torch + + +def rnd(name: str, n: int, scale: float = 1.0) -> list: + h = 0xCBF29CE484222325 + for ch in name.encode(): + h = ((h ^ ch) * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF + out = [] + for _ in range(n): + h = (h + 0x9E3779B97F4A7C15) & 0xFFFFFFFFFFFFFFFF + z = h + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & 0xFFFFFFFFFFFFFFFF + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & 0xFFFFFFFFFFFFFFFF + z ^= z >> 31 + out.append(((z >> 11) * (1.0 / 9007199254740992.0) * 2.0 - 1.0) * scale) + return out + + +def tensor(name: str, shape, scale: float = 1.0) -> torch.Tensor: + n = 1 + for d in shape: + n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + + +def load_dit(src: Path): + sys.path.insert(0, str(src.parents[2])) + 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( + "indextts.s2mel.modules.diffusion_transformer", src / "diffusion_transformer.py" + ) + m = importlib.util.module_from_spec(spec) + spec.loader.exec_module(m) + return m + + +def fmt(values) -> str: + lines, row = [], [] + for v in values: + row.append(f"{float(v):.9e}F") + if len(row) == 6: + lines.append(" " + ", ".join(row) + ",") + row = [] + if row: + lines.append(" " + ", ".join(row) + ",") + return "\n".join(lines) + + +# Reduced dims. The shipped model is hidden 512 / wavenet 512 / in_channels 80 / +# 8 wavenet layers; the RATIOS that matter (skip_linear takes hidden + in_channels, +# conv2 maps wavenet hidden -> in_channels) are preserved. +# +# WN_HIDDEN MUST EQUAL HIDDEN. `final_layer` is built at the wavenet width but is +# called with `t1`, which the DiT embeds at ITS hidden width, so the wavenet +# final-layer path only composes when the two are equal. They both happen to be +# 512 upstream, which hides the coupling; setting them differently here raised +# `mat1 and mat2 shapes cannot be multiplied (1x8 and 6x12)` from upstream's own +# module. The C++ port asserts it rather than inheriting a silent coincidence. +HIDDEN, WN_HIDDEN, IN_CH, HEADS, DEPTH = 8, 8, 4, 2, 1 +STYLE = 6 +WN_LAYERS, WN_KERNEL, WN_DILATION, FRAMES = 2, 3, 1, 7 + + +def build_args(): + dit = SimpleNamespace( + time_as_token=False, style_as_token=False, uvit_skip_connection=True, + depth=DEPTH, num_heads=HEADS, hidden_dim=HIDDEN, block_size=128, + in_channels=IN_CH, content_type="discrete", content_codebook_size=16, + content_dim=HIDDEN, is_causal=False, final_layer_type="wavenet", + style_condition=True, class_dropout_prob=0.0, long_skip_connection=True, + target="mel", f0_condition=False, n_f0_bins=8, content_codebooks=1, + zero_prompt_speech_token=False, add_resblock_in_transformer=False, + ) + wavenet = SimpleNamespace( + hidden_dim=WN_HIDDEN, num_layers=WN_LAYERS, kernel_size=WN_KERNEL, + dilation_rate=WN_DILATION, p_dropout=0.0, style_condition=True, + ) + style_encoder = SimpleNamespace(dim=STYLE) + return SimpleNamespace(DiT=dit, wavenet=wavenet, style_encoder=style_encoder) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--out", required=True) + a = ap.parse_args() + m = load_dit(Path(os.environ["DIT_SRC"])) + + torch.manual_seed(0) + dit = m.DiT(build_args()).eval() + + # Every parameter on the tail comes from the shared stream, so the C++ side + # rebuilds them without a fixture. + tail_prefixes = ("cond_projection.", "cond_x_merge_linear.") + with torch.no_grad(): + for pname, p in sorted(dit.named_parameters()): + if pname.startswith(tail_prefixes): + p.copy_(tensor("front." + pname, list(p.shape), 0.5)) + + x = tensor("front.x", [1, IN_CH, FRAMES]) # channel-major, as upstream + prompt_x = tensor("front.prompt_x", [1, IN_CH, FRAMES]) + cond = tensor("front.cond", [1, FRAMES, HIDDEN]) + style = tensor("front.style", [1, STYLE]) + + with torch.no_grad(): + # ---- upstream diffusion_transformer.py:206-226, verbatim order ---- + cond_p = dit.cond_projection(cond) + xt = x.transpose(1, 2) + pt = prompt_x.transpose(1, 2) + x_in = torch.cat([xt, pt, cond_p], dim=-1) + x_in = torch.cat([x_in, style[:, None, :].repeat(1, FRAMES, 1)], dim=-1) + cat864 = x_in.clone() + merged = dit.cond_x_merge_linear(x_in) + + # the CFG unconditional branch: everything past in_channels zeroed + x_in_u = cat864.clone() + x_in_u[..., IN_CH:] = x_in_u[..., IN_CH:] * 0 + merged_u = dit.cond_x_merge_linear(x_in_u) + # ------------------------------------------------------------------- + + names = sorted(n for n, _ in dit.named_parameters() if n.startswith(tail_prefixes)) + + body = [ + "// GENERATED by scripts/gen-dit-front-goldens.py -- do not edit.", + "// Oracle: diffusion_transformer.py:243-253 (DiT tail), index-tts", + "// @4f8792ff120cd3ea470dd511e997a17c86cddd10, under the SHIPPED config", + "// long_skip_connection: true, final_layer_type: wavenet.", + "#pragma once", + "", + "#include ", + "", + "namespace dit_front_goldens {", + "", + f"inline constexpr int64_t kHidden = {HIDDEN};", + f"inline constexpr int64_t kInChannels = {IN_CH};", + f"inline constexpr int64_t kStyle = {STYLE};", + f"inline constexpr int64_t kFrames = {FRAMES};", + "", + "inline constexpr const char* kParamNames[] = {", + ] + body += [f' "front.{n}",' for n in names] + body += [ + "};", + "", + "// The 864-wide concatenation before the merge -- [kFrames, 864].", + "inline constexpr float kCat[] = {", + fmt(cat864.reshape(-1).tolist()), + "};", + "", + "// cond_x_merge_linear(cat) -- [kFrames, kHidden].", + "inline constexpr float kMerged[] = {", + fmt(merged.reshape(-1).tolist()), + "};", + "", + "// The CFG UNCONDITIONAL branch: columns past kInChannels zeroed first.", + "inline constexpr float kMergedUncond[] = {", + fmt(merged_u.reshape(-1).tolist()), + "};", + "", + "} // namespace dit_front_goldens", + "", + ] + + Path(a.out).write_text("\n".join(body)) + print(f"wrote {a.out}: {len(names)} front params, merged {tuple(merged.shape)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/gen-dit-goldens.py b/scripts/gen-dit-goldens.py new file mode 100644 index 000000000..e378354c6 --- /dev/null +++ b/scripts/gen-dit-goldens.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/dit_goldens.inc — the S2Mel DiT block primitives. + +The DiT's transformer is gpt-fast's, wrapped so every block is conditioned by an +AdaptiveLayerNorm around an RMSNorm, with rotary position embeddings. + +TWO CONVENTIONS THAT DIFFER FROM THEIR NEIGHBOURS IN THE SAME MODEL: + + * This AdaptiveLayerNorm is `weight * norm(x) + bias` -- NO `1 +`, unlike the + `modulate()` used by the DiT's FinalLayer. Two adaLN conventions coexist, so + porting one over the other is an easy and silent error. + * The rotary embedding pairs ADJACENT components (`reshape(..., -1, 2)`), not + halves of the vector. The half-split convention is far more common and + produces a rotation that is smooth, norm-preserving and wrong. + +Usage: GPTFAST_SRC= python3 ... --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["GPTFAST_SRC"]) + sys.path.insert(0, str(src.parents[4])) + spec = importlib.util.spec_from_file_location("gf", src) + m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m) + + torch.manual_seed(0) + DIM, T, HEADS = 8, 5, 2 + HEAD_DIM = DIM // HEADS + + rms = m.RMSNorm(DIM, eps=1e-5).eval() + with torch.no_grad(): + rms.weight.copy_(P("rms.weight", (DIM,), 0.5) + 1.0) + x = P("dx", (1, T, DIM), 1.5) + rms_out = rms(x) + + aln = m.AdaptiveLayerNorm(DIM, m.RMSNorm(DIM, eps=1e-5)).eval() + with torch.no_grad(): + aln.project_layer.weight.copy_(P("aln.project_layer.weight", (2 * DIM, DIM))) + aln.project_layer.bias.copy_(P("aln.project_layer.bias", (2 * DIM,))) + aln.norm.weight.copy_(P("aln.norm.weight", (DIM,), 0.5) + 1.0) + emb = P("aemb", (1, 1, DIM), 1.0) + aln_out = aln(x, emb) + + # rotary: freqs_cis is [T, head_dim/2, 2] + freqs = m.precompute_freqs_cis(T, HEAD_DIM, base=10000) + q = P("dq", (1, T, HEADS, HEAD_DIM), 1.0) + rot = m.apply_rotary_emb(q, freqs) + + + # ---- SwiGLU feed-forward and the whole TransformerBlock ---- + INTER = 16 + cfg = m.ModelArgs(block_size=64, n_layer=1, n_head=HEADS, dim=DIM, + head_dim=HEAD_DIM, vocab_size=32, intermediate_size=INTER) + ff = m.FeedForward(cfg).eval() + with torch.no_grad(): + for n, par in ff.named_parameters(): + par.copy_(P("ff." + n, tuple(par.shape)).reshape(par.shape)) + ff_in = P("ffin", (1, T, DIM), 1.0) + ff_out = ff(ff_in) + + blk = m.TransformerBlock(cfg).eval() + bman = [] + with torch.no_grad(): + for n, par in list(blk.named_parameters()) + list(blk.named_buffers()): + if n.endswith("num_batches_tracked"): continue + par.copy_(P("blk." + n, tuple(par.shape) if par.dim() else (1,)).reshape(par.shape)) + bman.append((n, list(par.shape))) + bx = P("bx", (1, T, DIM), 1.0) + bc = P("bc", (1, 1, DIM), 1.0) + input_pos = torch.arange(T) + mask = torch.ones(1, 1, T, T, dtype=torch.bool) + with torch.no_grad(): + blk_out = blk(bx, bc, input_pos, freqs, mask) + + def emit(f, name, t): + # .float() first: precompute_freqs_cis returns BFLOAT16 upstream, so the + # rotary table already carries bf16 precision before any rotation runs. + arr = np.asarray(t.detach().float().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-dit-goldens.py -- do not edit.\n") + f.write("// gpt-fast primitives as S2Mel's DiT uses them. NOTE the AdaptiveLayerNorm\n") + f.write("// here is `weight * norm(x) + bias` -- NO `1 +`, unlike modulate() in the\n") + f.write("// FinalLayer -- and the rotary pairs ADJACENT components, not halves.\n") + f.write("#pragma once\n\n#include \n\nnamespace dit_goldens {\n\n") + for n, v in (("kDim", DIM), ("kFrames", T), ("kHeads", HEADS), ("kHeadDim", HEAD_DIM)): + f.write(f"inline constexpr int64_t {n} = {v};\n") + f.write("\n") + emit(f, "kX", x); emit(f, "kRmsOut", rms_out) + emit(f, "kEmb", emb); emit(f, "kAlnOut", aln_out) + emit(f, "kFreqs", freqs); emit(f, "kQ", q); emit(f, "kRotOut", rot) + f.write(f"inline constexpr int64_t kInter = {INTER};\n\n") + emit(f, "kFfIn", ff_in); emit(f, "kFfOut", ff_out) + f.write(f"inline constexpr int64_t kBlockManifestSize = {len(bman)};\n") + f.write("struct BlockEntry { const char* name; int64_t rank; int64_t d0, d1; };\n") + f.write("inline constexpr BlockEntry kBlockManifest[] = {\n") + for nm, sh in bman: + d = list(sh) + [1, 1] + f.write(f' {{"{nm}", {len(sh)}, {d[0]}, {d[1]}}},\n') + f.write("};\n\n") + emit(f, "kBx", bx); emit(f, "kBc", bc); emit(f, "kBlockOut", blk_out) + f.write("} // namespace dit_goldens\n") + print(f"wrote {p}: dim={DIM} heads={HEADS} head_dim={HEAD_DIM} T={T}") + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/gen-dit-skip-schedule.py b/scripts/gen-dit-skip-schedule.py new file mode 100644 index 000000000..c53330a63 --- /dev/null +++ b/scripts/gen-dit-skip-schedule.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Record the DiT's U-Net skip SCHEDULE from upstream, for several depths. + +`uvit_skip_connection: true` in the shipped config makes the first half of the +transformer emit its outputs onto a stack and the second half pop them, so layer +7 receives layer 5's output, layer 8 receives layer 4's, and so on. The routing +is pure index logic and every plausible variant (FIFO instead of LIFO, `>=` +instead of `>`, emitting before the layer instead of after) produces a model +that still runs. + +This drives upstream's own Transformer and records which layer actually received +which layer's output, rather than restating the formula. +""" + +import importlib.util +import sys +import types +from pathlib import Path +from types import SimpleNamespace + +import torch + +SRC = Path(sys.argv[1]) # .../indextts/s2mel/modules +sys.path.insert(0, str(SRC.parents[2])) +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( + "indextts.s2mel.modules.gpt_fast.model", SRC / "gpt_fast" / "model.py" +) +gm = importlib.util.module_from_spec(spec) +spec.loader.exec_module(gm) + + +def schedule(depth: int): + """Return [(receiver_layer, emitter_layer), ...] as upstream actually routes.""" + args = gm.ModelArgs( + block_size=64, n_layer=depth, n_head=2, dim=8, head_dim=4, + vocab_size=16, uvit_skip_connection=True, time_as_token=False, + ) + tr = gm.Transformer(args) + tr.setup_caches(1, 16, use_kv_cache=False) + tr.eval() + + tag = {} # id(tensor) -> layer index that produced it + received = [] # (receiver, emitter) + + for i, layer in enumerate(tr.layers): + orig = layer.forward + + def wrapped(*a, _i=i, _orig=orig, **kw): + skip = kw.get("skip_in_x", a[8] if len(a) > 8 else None) + if skip is not None: + received.append((_i, tag.get(id(skip), -1))) + out = _orig(*a, **kw) + tag[id(out)] = _i + return out + + layer.forward = wrapped + + x = torch.randn(1, 5, 8) + c = torch.randn(1, 1, 8) + input_pos = torch.arange(5) + mask = torch.ones(1, 1, 5, 5, dtype=torch.bool) + with torch.no_grad(): + tr(x, c, input_pos, mask) + return received, tr.layers_emit_skip, tr.layers_receive_skip + + +for d in (2, 3, 4, 5, 12, 13): + rec, emit, recv = schedule(d) + print(f"depth={d:>3} emit={emit} receive={recv}") + print(f" pairs(receiver<-emitter)={rec}") diff --git a/scripts/gen-dit-stack-goldens.py b/scripts/gen-dit-stack-goldens.py new file mode 100644 index 000000000..447c50a3d --- /dev/null +++ b/scripts/gen-dit-stack-goldens.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +"""Emit C++ goldens for the whole S2Mel DiT transformer STACK. + +Upstream `indextts/s2mel/modules/gpt_fast/model.py:161-191` (Transformer.forward) +plus its TransformerBlock, index-tts @4f8792ff120cd3ea470dd511e997a17c86cddd10. +This is what sits BETWEEN the ported front end and the ported tail: N blocks, +the U-Net skip routing across them, and a final AdaptiveLayerNorm. + +The rotary table is emitted as an INPUT rather than recomputed on our side, so +this gates the stack's composition and not a second implementation of +`precompute_freqs_cis`. + +Usage: DIT_SRC= python3 \ + scripts/gen-dit-stack-goldens.py --out tests/vllm/models/dit_stack_goldens.inc +""" + +from __future__ import annotations + +import argparse +import importlib.util +import os +import sys +import types +from pathlib import Path + +import torch + + +def rnd(name: str, n: int, scale: float = 1.0) -> list: + h = 0xCBF29CE484222325 + for ch in name.encode(): + h = ((h ^ ch) * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF + out = [] + for _ in range(n): + h = (h + 0x9E3779B97F4A7C15) & 0xFFFFFFFFFFFFFFFF + z = h + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & 0xFFFFFFFFFFFFFFFF + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & 0xFFFFFFFFFFFFFFFF + z ^= z >> 31 + out.append(((z >> 11) * (1.0 / 9007199254740992.0) * 2.0 - 1.0) * scale) + return out + + +def tensor(name: str, shape, scale: float = 1.0) -> torch.Tensor: + n = 1 + for d in shape: + n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + + +def fmt(values) -> str: + lines, row = [], [] + for v in values: + row.append(f"{float(v):.9e}F") + if len(row) == 6: + lines.append(" " + ", ".join(row) + ",") + row = [] + if row: + lines.append(" " + ", ".join(row) + ",") + return "\n".join(lines) + + +DIM, HEADS, HEAD_DIM, DEPTH, FRAMES = 8, 2, 4, 5, 6 +INTERMEDIATE = None # taken from the constructed model + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--out", required=True) + a = ap.parse_args() + src = Path(os.environ["DIT_SRC"]) + sys.path.insert(0, str(src.parents[2])) + 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( + "indextts.s2mel.modules.gpt_fast.model", src / "gpt_fast" / "model.py" + ) + gm = importlib.util.module_from_spec(spec) + spec.loader.exec_module(gm) + + torch.manual_seed(0) + args = gm.ModelArgs(block_size=64, n_layer=DEPTH, n_head=HEADS, dim=DIM, + head_dim=HEAD_DIM, vocab_size=16, + uvit_skip_connection=True, time_as_token=False) + tr = gm.Transformer(args) + tr.setup_caches(1, 32, use_kv_cache=False) + tr.eval() + + with torch.no_grad(): + for pname, p in sorted(tr.named_parameters()): + p.copy_(tensor("stack." + pname, list(p.shape), 0.5)) + + x = tensor("stack.x", [1, FRAMES, DIM]) + c = tensor("stack.c", [1, 1, DIM]) + input_pos = torch.arange(FRAMES) + mask = torch.ones(1, 1, FRAMES, FRAMES, dtype=torch.bool) + + with torch.no_grad(): + out = tr(x, c, input_pos, mask) + freqs = tr.freqs_cis[input_pos] # [FRAMES, head_dim/2, 2] + + inter = tr.layers[0].feed_forward.w1.weight.shape[0] + names = sorted(n for n, _ in tr.named_parameters()) + + body = [ + "// GENERATED by scripts/gen-dit-stack-goldens.py -- do not edit.", + "// Oracle: gpt_fast/model.py Transformer.forward + TransformerBlock,", + "// index-tts @4f8792ff120cd3ea470dd511e997a17c86cddd10, with", + "// uvit_skip_connection: true as the shipped config sets.", + "#pragma once", + "", + "#include ", + "", + "namespace dit_stack_goldens {", + "", + f"inline constexpr int64_t kDim = {DIM};", + f"inline constexpr int64_t kHeads = {HEADS};", + f"inline constexpr int64_t kHeadDim = {HEAD_DIM};", + f"inline constexpr int64_t kDepth = {DEPTH};", + f"inline constexpr int64_t kFrames = {FRAMES};", + f"inline constexpr int64_t kIntermediate = {int(inter)};", + f"inline constexpr double kEps = {float(args.norm_eps):.9e};", + "", + "inline constexpr const char* kParamNames[] = {", + ] + body += [f' "stack.{n}",' for n in names] + body += [ + "};", + "", + "// The rotary table upstream used, [kFrames, kHeadDim/2, 2]. Emitted as", + "// an INPUT so this gates composition, not a second rotary implementation.", + "inline constexpr float kFreqs[] = {", + fmt(freqs.reshape(-1).tolist()), + "};", + "", + "// Transformer.forward output -- [kFrames, kDim].", + "inline constexpr float kOut[] = {", + fmt(out.reshape(-1).tolist()), + "};", + "", + "} // namespace dit_stack_goldens", + "", + ] + Path(a.out).write_text("\n".join(body)) + print(f"wrote {a.out}: depth {DEPTH}, intermediate {int(inter)}, " + f"{len(names)} params, out {tuple(out.shape)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/gen-dit-tail-goldens.py b/scripts/gen-dit-tail-goldens.py new file mode 100644 index 000000000..3410fce27 --- /dev/null +++ b/scripts/gen-dit-tail-goldens.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +"""Emit C++ goldens for the S2Mel DiT TAIL: everything after the transformer. + +Upstream `indextts/s2mel/modules/diffusion_transformer.py:243-253`, index-tts +@4f8792ff120cd3ea470dd511e997a17c86cddd10, under the shipped config +(`long_skip_connection: true`, `final_layer_type: wavenet`): + + if long_skip_connection: x_res = skip_linear(cat([x_res, x], dim=-1)) + x = conv1(x_res) # Linear D -> wavenet hidden + x = x.transpose(1, 2) # [B, H, T] + t2 = t_embedder2(t) + x = wavenet(x, x_mask, g=t2.unsqueeze(2)).transpose(1, 2) + res_projection(x_res) + x = final_layer(x, t1).transpose(1, 2) + x = conv2(x) # Conv1d H -> in_channels, kernel 1 + +The DiT is constructed for real at reduced dims, so every module here is +upstream's own; only the SEQUENCE is restated, and it is restated once, next to +the upstream line numbers it copies. + +Usage: DIT_SRC= python3 \ + scripts/gen-dit-tail-goldens.py --out tests/vllm/models/dit_tail_goldens.inc +""" + +from __future__ import annotations + +import argparse +import importlib.util +import os +import sys +import types +from pathlib import Path +from types import SimpleNamespace + +import torch + + +def rnd(name: str, n: int, scale: float = 1.0) -> list: + h = 0xCBF29CE484222325 + for ch in name.encode(): + h = ((h ^ ch) * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF + out = [] + for _ in range(n): + h = (h + 0x9E3779B97F4A7C15) & 0xFFFFFFFFFFFFFFFF + z = h + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & 0xFFFFFFFFFFFFFFFF + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & 0xFFFFFFFFFFFFFFFF + z ^= z >> 31 + out.append(((z >> 11) * (1.0 / 9007199254740992.0) * 2.0 - 1.0) * scale) + return out + + +def tensor(name: str, shape, scale: float = 1.0) -> torch.Tensor: + n = 1 + for d in shape: + n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + + +def load_dit(src: Path): + sys.path.insert(0, str(src.parents[2])) + 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( + "indextts.s2mel.modules.diffusion_transformer", src / "diffusion_transformer.py" + ) + m = importlib.util.module_from_spec(spec) + spec.loader.exec_module(m) + return m + + +def fmt(values) -> str: + lines, row = [], [] + for v in values: + row.append(f"{float(v):.9e}F") + if len(row) == 6: + lines.append(" " + ", ".join(row) + ",") + row = [] + if row: + lines.append(" " + ", ".join(row) + ",") + return "\n".join(lines) + + +# Reduced dims. The shipped model is hidden 512 / wavenet 512 / in_channels 80 / +# 8 wavenet layers; the RATIOS that matter (skip_linear takes hidden + in_channels, +# conv2 maps wavenet hidden -> in_channels) are preserved. +# +# WN_HIDDEN MUST EQUAL HIDDEN. `final_layer` is built at the wavenet width but is +# called with `t1`, which the DiT embeds at ITS hidden width, so the wavenet +# final-layer path only composes when the two are equal. They both happen to be +# 512 upstream, which hides the coupling; setting them differently here raised +# `mat1 and mat2 shapes cannot be multiplied (1x8 and 6x12)` from upstream's own +# module. The C++ port asserts it rather than inheriting a silent coincidence. +HIDDEN, WN_HIDDEN, IN_CH, HEADS, DEPTH = 8, 8, 4, 2, 1 +WN_LAYERS, WN_KERNEL, WN_DILATION, FRAMES = 2, 3, 1, 7 + + +def build_args(): + dit = SimpleNamespace( + time_as_token=False, style_as_token=False, uvit_skip_connection=True, + depth=DEPTH, num_heads=HEADS, hidden_dim=HIDDEN, block_size=128, + in_channels=IN_CH, content_type="discrete", content_codebook_size=16, + content_dim=HIDDEN, is_causal=False, final_layer_type="wavenet", + style_condition=True, class_dropout_prob=0.0, long_skip_connection=True, + target="mel", f0_condition=False, n_f0_bins=8, content_codebooks=1, + zero_prompt_speech_token=False, add_resblock_in_transformer=False, + ) + wavenet = SimpleNamespace( + hidden_dim=WN_HIDDEN, num_layers=WN_LAYERS, kernel_size=WN_KERNEL, + dilation_rate=WN_DILATION, p_dropout=0.0, style_condition=True, + ) + style_encoder = SimpleNamespace(dim=IN_CH) + return SimpleNamespace(DiT=dit, wavenet=wavenet, style_encoder=style_encoder) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--out", required=True) + a = ap.parse_args() + m = load_dit(Path(os.environ["DIT_SRC"])) + + torch.manual_seed(0) + dit = m.DiT(build_args()).eval() + + # Every parameter on the tail comes from the shared stream, so the C++ side + # rebuilds them without a fixture. + tail_prefixes = ( + "skip_linear.", "conv1.", "conv2.", "t_embedder2.", "wavenet.", + "final_layer.", "res_projection.", + ) + with torch.no_grad(): + for pname, p in sorted(dit.named_parameters()): + if pname.startswith(tail_prefixes): + p.copy_(tensor("tail." + pname, list(p.shape), 0.5)) + + x_res = tensor("tail.x_res", [1, FRAMES, HIDDEN]) + x = tensor("tail.x", [1, FRAMES, IN_CH]) + t = torch.tensor([0.37], dtype=torch.float32) + t1 = tensor("tail.t1", [1, HIDDEN]) + x_mask = torch.ones(1, 1, FRAMES) + + with torch.no_grad(): + # ---- upstream diffusion_transformer.py:243-253, verbatim order ---- + xr = dit.skip_linear(torch.cat([x_res, x], dim=-1)) + h = dit.conv1(xr) + after_conv1 = h.reshape(-1).tolist() + h = h.transpose(1, 2) + t2 = dit.t_embedder2(t) + h = dit.wavenet(h, x_mask, g=t2.unsqueeze(2)).transpose(1, 2) + dit.res_projection(xr) + h = dit.final_layer(h, t1).transpose(1, 2) + out = dit.conv2(h) + # ------------------------------------------------------------------- + + names = sorted(n for n, _ in dit.named_parameters() if n.startswith(tail_prefixes)) + + body = [ + "// GENERATED by scripts/gen-dit-tail-goldens.py -- do not edit.", + "// Oracle: diffusion_transformer.py:243-253 (DiT tail), index-tts", + "// @4f8792ff120cd3ea470dd511e997a17c86cddd10, under the SHIPPED config", + "// long_skip_connection: true, final_layer_type: wavenet.", + "#pragma once", + "", + "#include ", + "", + "namespace dit_tail_goldens {", + "", + f"inline constexpr int64_t kHidden = {HIDDEN};", + f"inline constexpr int64_t kWnHidden = {WN_HIDDEN};", + f"inline constexpr int64_t kInChannels = {IN_CH};", + f"inline constexpr int64_t kWnLayers = {WN_LAYERS};", + f"inline constexpr int64_t kWnKernel = {WN_KERNEL};", + f"inline constexpr int64_t kWnDilation = {WN_DILATION};", + f"inline constexpr int64_t kFrames = {FRAMES};", + f"inline constexpr float kT = {float(t.item()):.9e}F;", + "", + "inline constexpr const char* kParamNames[] = {", + ] + body += [f' "tail.{n}",' for n in names] + body += [ + "};", + "", + "// conv1(skip_linear(cat)) BEFORE the wavenet -- [kFrames, kWnHidden].", + "inline constexpr float kAfterConv1[] = {", + fmt(after_conv1), + "};", + "", + "// The tail's output -- [kInChannels, kFrames], channel-major.", + "inline constexpr float kOut[] = {", + fmt(out.reshape(-1).tolist()), + "};", + "", + "} // namespace dit_tail_goldens", + "", + ] + + Path(a.out).write_text("\n".join(body)) + print(f"wrote {a.out}: {len(names)} tail params, out {tuple(out.shape)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) 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/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/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/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/scripts/gen-minimax-music3-manifest.py b/scripts/gen-minimax-music3-manifest.py new file mode 100644 index 000000000..441c1cc34 --- /dev/null +++ b/scripts/gen-minimax-music3-manifest.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Emit tests/vllm/models/minimax_music3_manifest.inc from the REAL checkpoint. + +Names, dtypes and shapes only -- not one weight byte. Every geometry claim +.agents/specs/minimax-music3.md section 1 makes is asserted against this +manifest, so a claim cannot drift from the artifact and CI never needs the +28.5 GB asset. + +The four diffusers-only components (`transformer`, `condition_encoder`, +`vocoder`, `rvq_depth_decoder`) are read from their safetensors headers. The +`language_model` is read from its headers too when the shards are present; its +NAME SET comes from `model.safetensors.index.json`, which is what actually +pins which tensors the component owes. + +Usage: + scripts/gen-minimax-music3-manifest.py \ + --checkpoint /mnt/nas_share/checkpoints/minimax-music3 \ + --output tests/vllm/models/minimax_music3_manifest.inc +""" + +from __future__ import annotations + +import argparse +import json +import struct +from pathlib import Path + +# One entry per component: (component name, subdirectory, shard glob, index file). +COMPONENTS = ( + ("transformer", "transformer", "diffusion_pytorch_model*.safetensors", + "diffusion_pytorch_model.safetensors.index.json"), + ("condition_encoder", "condition_encoder", "diffusion_pytorch_model.safetensors", None), + ("rvq_depth_decoder", "rvq_depth_decoder", "diffusion_pytorch_model.safetensors", None), + ("vocoder", "vocoder", "diffusion_pytorch_model.safetensors", None), + ("language_model", "language_model", "model-*.safetensors", "model.safetensors.index.json"), +) + +MAX_RANK = 3 + + +def read_header(path: Path) -> dict: + with path.open("rb") as handle: + (length,) = struct.unpack(" tuple[dict[str, list[tuple[str, str, list[int]]]], list[str]]: + """Return {component: [(name, dtype, shape)]} plus the components read from an index only.""" + tensors: dict[str, list[tuple[str, str, list[int]]]] = {} + index_only: list[str] = [] + for component, subdir, pattern, index_name in COMPONENTS: + directory = root / subdir + if not directory.is_dir(): + raise SystemExit(f"missing component directory: {directory}") + shards = sorted(directory.glob(pattern)) + merged: dict[str, tuple[str, list[int]]] = {} + for shard in shards: + for name, entry in read_header(shard).items(): + if name in merged: + raise SystemExit(f"{component}: {name} appears in more than one shard") + merged[name] = (entry["dtype"], list(entry["shape"])) + if index_name is not None: + index = json.loads((directory / index_name).read_text()) + declared = set(index["weight_map"]) + if not merged: + raise SystemExit( + f"{component}: no shard is present; " + f"the index declares {len(declared)} tensors but none can be measured" + ) + if set(merged) != declared: + missing = sorted(declared - set(merged)) + extra = sorted(set(merged) - declared) + raise SystemExit( + f"{component}: header/index disagree; " + f"missing from shards: {missing[:5]} ({len(missing)}), " + f"absent from index: {extra[:5]} ({len(extra)})" + ) + for name, (dtype, shape) in merged.items(): + if len(shape) > MAX_RANK: + raise SystemExit(f"{component}: {name} has rank {len(shape)} > {MAX_RANK}") + tensors[component] = sorted((n, d, s) for n, (d, s) in merged.items()) + return tensors, index_only + + +def emit(tensors: dict[str, list[tuple[str, str, list[int]]]], checkpoint: Path) -> str: + lines: list[str] = [] + add = lines.append + add("// GENERATED by scripts/gen-minimax-music3-manifest.py --- DO NOT EDIT BY HAND.") + add("//") + add("// Tensor manifest of the REAL MiniMaxAI/MiniMax-Music3 DIFFUSERS-arm checkpoint:") + add("// names, dtypes and shapes only, read from each safetensors header. No weight") + add("// bytes, so the gate that consumes this needs no checkpoint and CI never") + add("// downloads 28.5 GB. See .agents/specs/minimax-music3.md section 1.") + add("#pragma once") + add("") + add("#include ") + add("") + add("namespace vllm_test {") + add("") + add("struct Music3ManifestTensor {") + add(" const char* component;") + add(" const char* name;") + add(" const char* dtype;") + add(f" int64_t shape[{MAX_RANK}]; // 0-padded") + add(" int32_t rank;") + add("};") + add("") + total = 0 + for component, _, _, _ in COMPONENTS: + entries = tensors[component] + params = sum(_numel(shape) for _, _, shape in entries) + dtypes = sorted({dtype for _, dtype, _ in entries}) + symbol = _symbol(component) + add(f"// {component}: {len(entries)} tensors, {params} parameters, dtype(s) {'/'.join(dtypes)}.") + add(f"inline constexpr int64_t kMusic3{symbol}TensorCount = {len(entries)};") + add(f"inline constexpr int64_t kMusic3{symbol}Parameters = {params};") + total += len(entries) + add("") + add(f"inline constexpr int64_t kMusic3ManifestTensorCount = {total};") + add("") + add("inline constexpr Music3ManifestTensor kMusic3Manifest[] = {") + for component, _, _, _ in COMPONENTS: + for name, dtype, shape in tensors[component]: + padded = list(shape) + [0] * (MAX_RANK - len(shape)) + dims = ", ".join(str(d) for d in padded) + add(f' {{"{component}", "{name}", "{dtype}", {{{dims}}}, {len(shape)}}},') + add("};") + add("") + add("} // namespace vllm_test") + add("") + return "\n".join(lines) + + +def _numel(shape: list[int]) -> int: + out = 1 + for dim in shape: + out *= dim + return out + + +def _symbol(component: str) -> str: + return "".join(part.capitalize() for part in component.split("_")) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--checkpoint", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + tensors, _ = collect(args.checkpoint) + args.output.write_text(emit(tensors, args.checkpoint), encoding="utf-8") + total = sum(len(v) for v in tensors.values()) + print(f"wrote {args.output} with {total} tensors across {len(tensors)} components") + for component, entries in tensors.items(): + print(f" {component}: {len(entries)} tensors") + + +if __name__ == "__main__": + main() 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/scripts/gen-w2vbert-goldens.py b/scripts/gen-w2vbert-goldens.py new file mode 100644 index 000000000..db0748708 --- /dev/null +++ b/scripts/gen-w2vbert-goldens.py @@ -0,0 +1,165 @@ +#!/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, Wav2Vec2BertFeatureProjection, Wav2Vec2BertEncoder) + +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())) + 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 + # 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) + + # ---- 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") + 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") + 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"]) + 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 + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/gen-wavenet-goldens.py b/scripts/gen-wavenet-goldens.py new file mode 100644 index 000000000..b930f9b3f --- /dev/null +++ b/scripts/gen-wavenet-goldens.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +"""Emit C++ goldens for the S2Mel DiT's WaveNet final layer. + +The shipped `s2mel.pth` carries `net.cfm.estimator.wavenet.*`, and the config +sets `DiT.final_layer_type: wavenet`, so this stack is on the render path, not an +alternative branch. It was missing from the port map entirely until the +checkpoint manifest named it (#634). + +Usage: WAVENET_SRC= python3 \ + scripts/gen-wavenet-goldens.py --out tests/vllm/models/wavenet_goldens.inc + +Both sides rebuild the same weights from one FNV-1a -> splitmix64 stream, so no +weight bytes are committed and the C++ test needs no fixture file. +""" + +from __future__ import annotations + +import argparse +import importlib.util +import os +import sys +import types +from pathlib import Path + +import torch + + +def rnd(name: str, n: int, scale: float = 1.0) -> list: + """FNV-1a over the name seeds splitmix64; the C++ side repeats this.""" + h = 0xCBF29CE484222325 + for ch in name.encode(): + h = ((h ^ ch) * 0x100000001B3) & 0xFFFFFFFFFFFFFFFF + out = [] + for _ in range(n): + h = (h + 0x9E3779B97F4A7C15) & 0xFFFFFFFFFFFFFFFF + z = h + z = ((z ^ (z >> 30)) * 0xBF58476D1CE4E5B9) & 0xFFFFFFFFFFFFFFFF + z = ((z ^ (z >> 27)) * 0x94D049BB133111EB) & 0xFFFFFFFFFFFFFFFF + z ^= z >> 31 + out.append(((z >> 11) * (1.0 / 9007199254740992.0) * 2.0 - 1.0) * scale) + return out + + +def tensor(name: str, shape, scale: float = 1.0) -> torch.Tensor: + n = 1 + for d in shape: + n *= d + return torch.tensor(rnd(name, n, scale), dtype=torch.float64).reshape(shape).float() + + +def load_wn(src: Path): + """Import upstream `wavenet.py` with its siblings stubbed.""" + root = src.parents[2] # ...//indextts/s2mel/modules -> + sys.path.insert(0, str(root)) + 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( + "indextts.s2mel.modules.wavenet", src / "wavenet.py" + ) + m = importlib.util.module_from_spec(spec) + spec.loader.exec_module(m) + return m.WN + + +def fmt(values) -> str: + lines, row = [], [] + for v in values: + row.append(f"{float(v):.9e}F") + if len(row) == 6: + lines.append(" " + ", ".join(row) + ",") + row = [] + if row: + lines.append(" " + ", ".join(row) + ",") + return "\n".join(lines) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--out", required=True) + a = ap.parse_args() + src = Path(os.environ["WAVENET_SRC"]) + WN = load_wn(src) + + # Reduced dims. kernel_size must be odd (upstream asserts it); dilation_rate + # is 1 in the shipped config, so a value > 1 here is DELIBERATE: it exercises + # the dilated padding arithmetic the shipped model happens not to reach. + HIDDEN, KERNEL, DILATION, LAYERS, GIN, FRAMES = 6, 3, 2, 3, 4, 9 + + torch.manual_seed(0) + wn = WN( + hidden_channels=HIDDEN, + kernel_size=KERNEL, + dilation_rate=DILATION, + n_layers=LAYERS, + gin_channels=GIN, + p_dropout=0.0, + causal=False, + ).eval() + + # Overwrite every parameter from the shared stream so the C++ side can + # rebuild them without a fixture. Weight-norm parametrisation keeps (g, v). + with torch.no_grad(): + for pname, p in sorted(wn.named_parameters()): + p.copy_(tensor("wn." + pname, list(p.shape), 0.5)) + + x = tensor("wn.x", [1, HIDDEN, FRAMES]) + g = tensor("wn.g", [1, GIN, 1]) + mask = torch.ones(1, 1, FRAMES) + + # A mask with trailing zeros. Upstream masks the residual update and the + # final output but NOT the input to `in_layers`, so a masked run is not the + # same as a shorter run, and that asymmetry is what this pins. + masked = torch.ones(1, 1, FRAMES) + masked[0, 0, FRAMES - 3 :] = 0.0 + + with torch.no_grad(): + out_cond = wn(x, mask, g=g) + out_masked = wn(x, masked, g=g) + out_nocond = wn(x, mask, g=None) if wn.gin_channels == 0 else None + + names = sorted(n for n, _ in wn.named_parameters()) + + body = [ + "// GENERATED by scripts/gen-wavenet-goldens.py -- do not edit.", + "// Oracle: indextts/s2mel/modules/wavenet.py WN, upstream index-tts", + "// @4f8792ff120cd3ea470dd511e997a17c86cddd10. The DiT selects this stack", + "// via config `DiT.final_layer_type: wavenet`, so it is on the render path.", + "#pragma once", + "", + "#include ", + "", + "namespace wavenet_goldens {", + "", + f"inline constexpr int64_t kHidden = {HIDDEN};", + f"inline constexpr int64_t kKernel = {KERNEL};", + f"inline constexpr int64_t kDilationRate = {DILATION};", + f"inline constexpr int64_t kLayers = {LAYERS};", + f"inline constexpr int64_t kGin = {GIN};", + f"inline constexpr int64_t kFrames = {FRAMES};", + "", + "// Parameter names, in the order the C++ side must rebuild them.", + "inline constexpr const char* kParamNames[] = {", + ] + body += [f' "wn.{n}",' for n in names] + body += [ + "};", + "", + "// Output of WN(x, ones_mask, g) -- [kHidden, kFrames], channel-major.", + "inline constexpr float kOutCond[] = {", + fmt(out_cond.reshape(-1).tolist()), + "};", + "", + ] + body += [ + "// The same run under a mask whose last three frames are zero.", + "inline constexpr float kMask[] = {", + fmt(masked.reshape(-1).tolist()), + "};", + "", + "inline constexpr float kOutMasked[] = {", + fmt(out_masked.reshape(-1).tolist()), + "};", + "", + ] + if out_nocond is not None: + body += [ + "inline constexpr float kOutNoCond[] = {", + fmt(out_nocond.reshape(-1).tolist()), + "};", + "", + ] + body += ["} // namespace wavenet_goldens", ""] + + Path(a.out).write_text("\n".join(body)) + print(f"wrote {a.out}: {len(names)} params, out {tuple(out_cond.shape)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/read-torch-manifest.py b/scripts/read-torch-manifest.py new file mode 100644 index 000000000..02bafdb6a --- /dev/null +++ b/scripts/read-torch-manifest.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Read a torch .pth's tensor manifest by HTTP range request, no weights. + +A torch save file is a ZIP holding one small pickle (`*/data.pkl`) that names +every tensor and its shape/dtype, plus one uncompressed blob per storage. The +pickle is kilobytes; the blobs are gigabytes. Fetching the central directory and +then just the pickle reads the whole manifest for a few hundred KB. +""" + +import io +import pickle +import struct +import subprocess +import sys + + +def get(url: str, start: int, end: int) -> bytes: + out = subprocess.run( + ["curl", "-sL", "-r", f"{start}-{end}", url], + capture_output=True, check=True, timeout=180, + ) + return out.stdout + + +def size(url: str) -> int: + out = subprocess.run( + ["curl", "-sIL", url], capture_output=True, check=True, text=True, timeout=60 + ) + lengths = [l for l in out.stdout.splitlines() if l.lower().startswith("content-length")] + return int(lengths[-1].split(":")[1]) + + +class _StatefulDict(dict): + """A dict that tolerates pickle BUILD, which plain dict does not.""" + + def __setstate__(self, state): + if isinstance(state, dict): + self.update(state) + + +class ShapeRecorder(pickle.Unpickler): + """Unpickle the manifest without torch: every rebuild becomes a record.""" + + def find_class(self, module, name): + def stub(*args, **kwargs): + if name == "_rebuild_from_type_v2": + # (func, new_type, args, state) -> func(*args) + func, _new_type, fargs = args[0], args[1], args[2] + return func(*fargs) if callable(func) else f"<{func}>" + if name.startswith("_rebuild_tensor"): + # (storage, offset, size, stride, ...) + storage = args[0] + shape = list(args[2]) + return {"shape": shape, "dtype": storage[1] if isinstance(storage, tuple) else "?"} + if name in ("OrderedDict", "dict"): + return dict(*args, **kwargs) + return f"<{module}.{name}>" + if name == "OrderedDict": + return _StatefulDict + return stub + + def persistent_load(self, pid): + # ('storage', , key, location, numel) + if isinstance(pid, tuple) and len(pid) >= 2: + dt = pid[1] + return ("storage", getattr(dt, "__name__", str(dt))) + return ("storage", "?") + + +def manifest(url: str) -> dict: + total = size(url) + tail = get(url, max(0, total - 200_000), total - 1) + # Locate the End Of Central Directory record in the tail we fetched. + eocd = tail.rfind(b"PK\x05\x06") + if eocd < 0: + raise SystemExit("no EOCD; zip64 or a bigger tail needed") + cd_size, cd_off = struct.unpack(" pending framework-routing (see AGENTS.md decode/runtime seam) qwen3_vl # ForwardQwen3VLForConditionalGeneration returns HostLogits + private VLGenerateCore host ArgMax loop (MM-ENGINE-FORWARD partial seam) bypasses the runner -> pending framework-routing (see AGENTS.md decode/runtime seam) +nemotron_h # W4 (#517) ports the hybrid forward as the HOST CPU reference (nemotron_h.cpp asserts a CPU queue); ForwardNemotronHForCausalLM reaches it through ModelRegistry::Forward and returns HostLogits. It cannot yet return a device-resident ForwardLogits because there is no NemotronH weight LOADER at all — every checkpoint load leaves the host weights unmaterialized and the forward REFUSES BY NAME (spec .agents/specs/nemotron-h-model.md §5b). The device/paged runner path is W6, which is what removes this entry -> pending framework-routing (see AGENTS.md decode/runtime seam) 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/model_executor/models/adaln.cpp b/src/vllm/model_executor/models/adaln.cpp new file mode 100644 index 000000000..c8a6d85a3 --- /dev/null +++ b/src/vllm/model_executor/models/adaln.cpp @@ -0,0 +1,129 @@ +// Adaptive layer norm. See adaln.h for the upstream anchors. +#include "vllm/model_executor/models/adaln.h" + +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace adaln { + +std::vector Modulate(const std::vector& x, int64_t frames, int64_t hidden, + const std::vector& shift, const std::vector& scale) { + VT_CHECK(x.size() == static_cast(frames * hidden), "adaln: modulate shape"); + VT_CHECK(shift.size() == static_cast(hidden) && + scale.size() == static_cast(hidden), + "adaln: shift/scale must be one per hidden unit"); + std::vector out(x.size()); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t h = 0; h < hidden; ++h) { + const size_t i = static_cast(t * hidden + h); + // 1 + scale, not scale. + out[i] = static_cast(static_cast(x[i]) * + (1.0 + static_cast(scale[static_cast(h)])) + + static_cast(shift[static_cast(h)])); + } + } + return out; +} + +std::vector LayerNormNoAffine(const std::vector& x, int64_t frames, int64_t hidden, + double eps) { + VT_CHECK(x.size() == static_cast(frames * hidden), "adaln: layernorm shape"); + std::vector out(x.size()); + for (int64_t t = 0; t < frames; ++t) { + const float* row = x.data() + static_cast(t * hidden); + double mean = 0.0; + for (int64_t h = 0; h < hidden; ++h) mean += static_cast(row[h]); + mean /= static_cast(hidden); + double var = 0.0; + for (int64_t h = 0; h < hidden; ++h) { + const double d = static_cast(row[h]) - mean; + var += d * d; + } + var /= static_cast(hidden); + const double inv = 1.0 / std::sqrt(var + eps); + for (int64_t h = 0; h < hidden; ++h) { + // No gamma, no beta: elementwise_affine is false. + out[static_cast(t * hidden + h)] = + static_cast((static_cast(row[h]) - mean) * inv); + } + } + return out; +} + +namespace { + +std::vector WeightNorm(const std::vector& g, const std::vector& v, + int64_t out_dim) { + const int64_t per = static_cast(v.size()) / out_dim; + std::vector w(v.size()); + for (int64_t o = 0; o < out_dim; ++o) { + double norm = 0.0; + for (int64_t i = 0; i < per; ++i) { + const double q = static_cast(v[static_cast(o * per + i)]); + norm += q * q; + } + norm = std::sqrt(norm); + const double s = (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)]) * s); + } + } + return w; +} + +double Silu(double x) { return x / (1.0 + std::exp(-x)); } + +} // namespace + +std::vector FinalLayer(const std::vector& x, int64_t frames, int64_t hidden, + int64_t out_channels, const std::vector& cond, + const FinalLayerWeights& w, double eps) { + VT_CHECK(cond.size() == static_cast(hidden), "adaln: cond shape"); + + // SiLU then Linear(hidden -> 2*hidden); the result chunks into [shift, scale]. + std::vector activated(static_cast(hidden)); + for (int64_t h = 0; h < hidden; ++h) { + activated[static_cast(h)] = Silu(static_cast(cond[static_cast(h)])); + } + std::vector shift(static_cast(hidden)), scale(static_cast(hidden)); + for (int64_t o = 0; o < 2 * hidden; ++o) { + double acc = static_cast(w.ada_b[static_cast(o)]); + for (int64_t h = 0; h < hidden; ++h) { + acc += static_cast(w.ada_w[static_cast(o * hidden + h)]) * + activated[static_cast(h)]; + } + // chunk(2): the FIRST half is shift, the second is scale. + if (o < hidden) { + shift[static_cast(o)] = static_cast(acc); + } else { + scale[static_cast(o - hidden)] = static_cast(acc); + } + } + + const std::vector normed = LayerNormNoAffine(x, frames, hidden, eps); + const std::vector modulated = Modulate(normed, frames, hidden, shift, scale); + + const std::vector lin = WeightNorm(w.linear_g, w.linear_v, out_channels); + std::vector out(static_cast(frames * out_channels)); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t o = 0; o < out_channels; ++o) { + double acc = static_cast(w.linear_bias[static_cast(o)]); + for (int64_t h = 0; h < hidden; ++h) { + acc += static_cast(lin[static_cast(o * hidden + h)]) * + static_cast(modulated[static_cast(t * hidden + h)]); + } + out[static_cast(t * out_channels + o)] = static_cast(acc); + } + } + return out; +} + +} // namespace adaln +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/campplus.cpp b/src/vllm/model_executor/models/campplus.cpp new file mode 100644 index 000000000..13aba1025 --- /dev/null +++ b/src/vllm/model_executor/models/campplus.cpp @@ -0,0 +1,467 @@ +// CAMPPlus primitives. See campplus.h for the upstream anchors. +#include "vllm/model_executor/models/campplus.h" + +#include +#include +#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; +} + +// 1-D convolution over [C, T] with `same` padding, as every CAMPPlus conv uses. +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; + 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; +} + +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; +} + + +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 { + +// 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; +} + + +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/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/src/vllm/model_executor/models/dit.cpp b/src/vllm/model_executor/models/dit.cpp new file mode 100644 index 000000000..be5cbc296 --- /dev/null +++ b/src/vllm/model_executor/models/dit.cpp @@ -0,0 +1,202 @@ +// S2Mel DiT block primitives. See dit.h for the conventions this pins. +#include "vllm/model_executor/models/dit.h" + +#include +#include +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace dit { + +std::vector RmsNorm(const std::vector& x, int64_t frames, int64_t dim, + const std::vector& weight, double eps) { + VT_CHECK(x.size() == static_cast(frames * dim), "dit: rmsnorm shape"); + std::vector out(x.size()); + for (int64_t t = 0; t < frames; ++t) { + const float* row = x.data() + static_cast(t * dim); + double sq = 0.0; + for (int64_t i = 0; i < dim; ++i) { + const double v = static_cast(row[i]); + sq += v * v; // NO mean subtraction: this is RMS, not variance. + } + const double inv = 1.0 / std::sqrt(sq / static_cast(dim) + eps); + for (int64_t i = 0; i < dim; ++i) { + out[static_cast(t * dim + i)] = + static_cast(static_cast(row[i]) * inv * + static_cast(weight[static_cast(i)])); + } + } + return out; +} + +std::vector AdaptiveLayerNorm(const std::vector& x, int64_t frames, int64_t dim, + const std::vector& embedding, + const std::vector& proj_w, + const std::vector& proj_b, + const std::vector& norm_weight, double eps) { + VT_CHECK(embedding.size() == static_cast(dim), "dit: adaln embedding shape"); + // project(embedding) -> 2*dim, split into [weight, bias]. + std::vector proj(static_cast(2 * dim)); + for (int64_t o = 0; o < 2 * dim; ++o) { + double acc = static_cast(proj_b[static_cast(o)]); + for (int64_t i = 0; i < dim; ++i) { + acc += static_cast(proj_w[static_cast(o * dim + i)]) * + static_cast(embedding[static_cast(i)]); + } + proj[static_cast(o)] = acc; + } + + const std::vector normed = RmsNorm(x, frames, dim, norm_weight, eps); + std::vector out(normed.size()); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t i = 0; i < dim; ++i) { + const size_t k = static_cast(t * dim + i); + // weight * norm + bias. NOT (1 + weight) -- that is the FinalLayer's + // convention, and this is a different one in the same model. + out[k] = static_cast(proj[static_cast(i)] * static_cast(normed[k]) + + proj[static_cast(dim + i)]); + } + } + return out; +} + +std::vector ApplyRotary(const std::vector& x, int64_t frames, int64_t heads, + int64_t head_dim, const std::vector& freqs) { + VT_CHECK(x.size() == static_cast(frames * heads * head_dim), "dit: rotary shape"); + VT_CHECK(head_dim % 2 == 0, "dit: head_dim must be even"); + const int64_t pairs = head_dim / 2; + VT_CHECK(freqs.size() == static_cast(frames * pairs * 2), "dit: freqs shape"); + + std::vector out(x.size()); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t h = 0; h < heads; ++h) { + for (int64_t p = 0; p < pairs; ++p) { + // ADJACENT pairing: components 2p and 2p+1. + const size_t lo = static_cast((t * heads + h) * head_dim + 2 * p); + const size_t hi = lo + 1; + const double cos_v = static_cast(freqs[static_cast((t * pairs + p) * 2)]); + const double sin_v = + static_cast(freqs[static_cast((t * pairs + p) * 2 + 1)]); + const double a = static_cast(x[lo]); + const double b = static_cast(x[hi]); + out[lo] = static_cast(a * cos_v - b * sin_v); + out[hi] = static_cast(b * cos_v + a * sin_v); + } + } + } + return out; +} + + +namespace { + +std::vector Linear(const std::vector& x, int64_t frames, int64_t in_dim, + int64_t out_dim, const std::vector& w) { + 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 = 0.0; + 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; +} + +double Silu(double x) { return x / (1.0 + std::exp(-x)); } + +} // namespace + +std::vector SwiGlu(const std::vector& x, int64_t frames, int64_t dim, + int64_t intermediate, const std::vector& w1, + const std::vector& w3, const std::vector& w2) { + const std::vector gate = Linear(x, frames, dim, intermediate, w1); + const std::vector up = Linear(x, frames, dim, intermediate, w3); + std::vector mid(gate.size()); + for (size_t i = 0; i < gate.size(); ++i) { + // SiLU applies to W1's output only. + mid[i] = static_cast(Silu(static_cast(gate[i])) * static_cast(up[i])); + } + return Linear(mid, frames, intermediate, dim, w2); +} + +std::vector Block(const std::vector& x, const std::vector& cond, + int64_t frames, int64_t dim, int64_t heads, int64_t head_dim, + int64_t intermediate, const std::vector& freqs, + const BlockWeights& w, double eps) { + VT_CHECK(x.size() == static_cast(frames * dim), "dit: block shape"); + + // ── attention half ──────────────────────────────────────────────────────── + const std::vector normed = + AdaptiveLayerNorm(x, frames, dim, cond, w.attn_proj_w, w.attn_proj_b, w.attn_norm_w, eps); + + const int64_t qkv_dim = heads * head_dim; + const std::vector qkv = Linear(normed, frames, dim, 3 * qkv_dim, w.wqkv); + std::vector q(static_cast(frames * qkv_dim)); + std::vector k(q.size()), v(q.size()); + for (int64_t t = 0; t < frames; ++t) { + for (int64_t i = 0; i < qkv_dim; ++i) { + const size_t base = static_cast(t * 3 * qkv_dim); + q[static_cast(t * qkv_dim + i)] = qkv[base + static_cast(i)]; + k[static_cast(t * qkv_dim + i)] = qkv[base + static_cast(qkv_dim + i)]; + v[static_cast(t * qkv_dim + i)] = qkv[base + static_cast(2 * qkv_dim + i)]; + } + } + // Rotary applies to q and k, never to v. + const std::vector qr = ApplyRotary(q, frames, heads, head_dim, freqs); + const std::vector kr = ApplyRotary(k, frames, heads, head_dim, freqs); + + const double scale = 1.0 / std::sqrt(static_cast(head_dim)); + std::vector ctx(static_cast(frames * qkv_dim)); + for (int64_t hd = 0; hd < heads; ++hd) { + for (int64_t i = 0; i < frames; ++i) { + std::vector scores(static_cast(frames)); + double best = -std::numeric_limits::infinity(); + for (int64_t j = 0; j < frames; ++j) { + double dot = 0.0; + for (int64_t d = 0; d < head_dim; ++d) { + dot += static_cast(qr[static_cast(i * qkv_dim + hd * head_dim + d)]) * + static_cast(kr[static_cast(j * qkv_dim + hd * head_dim + d)]); + } + dot *= scale; + scores[static_cast(j)] = dot; + best = std::max(best, dot); + } + double denom = 0.0; + for (double& sc : scores) { sc = std::exp(sc - best); denom += sc; } + 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 * qkv_dim + hd * head_dim + d)]); + } + ctx[static_cast(i * qkv_dim + hd * head_dim + d)] = + static_cast(acc / denom); + } + } + } + const std::vector attn_out = Linear(ctx, frames, qkv_dim, dim, w.wo); + + std::vector h(x.size()); + for (size_t i = 0; i < h.size(); ++i) h[i] = x[i] + attn_out[i]; // FULL residual + + // ── feed-forward half ───────────────────────────────────────────────────── + const std::vector ffn_normed = + AdaptiveLayerNorm(h, frames, dim, cond, w.ffn_proj_w, w.ffn_proj_b, w.ffn_norm_w, eps); + const std::vector ffn = SwiGlu(ffn_normed, frames, dim, intermediate, w.w1, w.w3, w.w2); + + std::vector out(h.size()); + for (size_t i = 0; i < out.size(); ++i) out[i] = h[i] + ffn[i]; // FULL residual + return out; +} + +} // namespace dit +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/dit_front.cpp b/src/vllm/model_executor/models/dit_front.cpp new file mode 100644 index 000000000..1d4a80762 --- /dev/null +++ b/src/vllm/model_executor/models/dit_front.cpp @@ -0,0 +1,87 @@ +// S2Mel DiT front end. See dit_front.h for the upstream anchors. +#include "vllm/model_executor/models/dit_front.h" + +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace dit_front { + +std::vector BuildXIn(const Config& cfg, const Weights& w, + const std::vector& x, + const std::vector& prompt_x, + const std::vector& cond, + const std::vector& style, bool unconditional) { + VT_CHECK(cfg.hidden > 0 && cfg.in_channels > 0 && cfg.frames > 0, + "dit_front: hidden, in_channels and frames must be positive"); + const int64_t T = cfg.frames; + const int64_t H = cfg.hidden; + const int64_t C = cfg.in_channels; + const int64_t S = cfg.style; + const int64_t wide = C * 2 + H + S; + + VT_CHECK(x.size() == static_cast(C * T), "dit_front: x must be [in_channels, frames]"); + VT_CHECK(prompt_x.size() == static_cast(C * T), + "dit_front: prompt_x must be [in_channels, frames]"); + VT_CHECK(cond.size() == static_cast(T * H), "dit_front: cond must be [frames, hidden]"); + VT_CHECK(style.size() == static_cast(S), "dit_front: style must be [style]"); + VT_CHECK(w.merge_w.size() == static_cast(H * wide), + "dit_front: cond_x_merge_linear must be [hidden, 2*in_channels + hidden + style]"); + + // cond_projection FIRST: the concatenation takes the PROJECTED cond, not the + // raw one. cond_embedder is not consulted -- see the header. + std::vector cond_p(static_cast(T * H)); + for (int64_t t = 0; t < T; ++t) { + for (int64_t o = 0; o < H; ++o) { + double acc = w.cond_proj_b.empty() + ? 0.0 + : static_cast(w.cond_proj_b[static_cast(o)]); + for (int64_t i = 0; i < H; ++i) { + acc += static_cast(cond[static_cast(t * H + i)]) * + static_cast(w.cond_proj_w[static_cast(o * H + i)]); + } + cond_p[static_cast(t * H + o)] = static_cast(acc); + } + } + + // cat([x^T, prompt_x^T, cond, style-over-T], -1), then optionally zero + // everything past in_channels for the unconditional branch. + std::vector cat(static_cast(T * wide)); + for (int64_t t = 0; t < T; ++t) { + const size_t row = static_cast(t * wide); + for (int64_t c = 0; c < C; ++c) { + cat[row + static_cast(c)] = x[static_cast(c * T + t)]; + cat[row + static_cast(C + c)] = + unconditional ? 0.0F : prompt_x[static_cast(c * T + t)]; + } + for (int64_t i = 0; i < H; ++i) { + cat[row + static_cast(2 * C + i)] = + unconditional ? 0.0F : cond_p[static_cast(t * H + i)]; + } + for (int64_t i = 0; i < S; ++i) { + cat[row + static_cast(2 * C + H + i)] = + unconditional ? 0.0F : style[static_cast(i)]; + } + } + + std::vector out(static_cast(T * H)); + for (int64_t t = 0; t < T; ++t) { + for (int64_t o = 0; o < H; ++o) { + double acc = + w.merge_b.empty() ? 0.0 : static_cast(w.merge_b[static_cast(o)]); + for (int64_t i = 0; i < wide; ++i) { + acc += static_cast(cat[static_cast(t * wide + i)]) * + static_cast(w.merge_w[static_cast(o * wide + i)]); + } + out[static_cast(t * H + o)] = static_cast(acc); + } + } + return out; +} + +} // namespace dit_front +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/dit_skip.cpp b/src/vllm/model_executor/models/dit_skip.cpp new file mode 100644 index 000000000..7cae194ed --- /dev/null +++ b/src/vllm/model_executor/models/dit_skip.cpp @@ -0,0 +1,86 @@ +// DiT U-Net skip connections. See dit_skip.h for the upstream anchors. +#include "vllm/model_executor/models/dit_skip.h" + +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace dit_skip { + +Schedule Plan(int64_t layers) { + VT_CHECK(layers > 0, "dit_skip: a transformer needs at least one layer"); + Schedule s; + s.source.assign(static_cast(layers), -1); + + // model.py:154-155. Note the ASYMMETRY: emitters use `<` and receivers use + // `>`, both against `layers / 2` with integer division, so at odd depth the + // middle layer is on neither list and the two halves balance, while at even + // depth there is one more emitter than receiver. + const int64_t half = layers / 2; + for (int64_t i = 0; i < layers; ++i) { + if (i < half) { + s.emit.push_back(i); + } + if (i > half) { + s.receive.push_back(i); + } + } + + // model.py:181-190: emitters append AFTER their layer runs, receivers pop the + // most recent one, so the pairing is LIFO. + std::vector stack; + for (int64_t i = 0; i < layers; ++i) { + // Receive happens BEFORE this layer runs... + if (i > half) { + VT_CHECK(!stack.empty(), + "dit_skip: a receiving layer found the skip stack empty"); + s.source[static_cast(i)] = stack.back(); + stack.pop_back(); + } + // ...and emit happens AFTER, pushing this layer's own output. + if (i < half) { + stack.push_back(i); + } + } + s.orphaned = static_cast(stack.size()); + return s; +} + +std::vector ApplySkip(const std::vector& x, const std::vector& skip, + int64_t frames, int64_t dim, + const std::vector& weight, + const std::vector& bias) { + VT_CHECK(frames > 0 && dim > 0, "dit_skip: frames and dim must be positive"); + VT_CHECK(x.size() == static_cast(frames * dim) && + skip.size() == static_cast(frames * dim), + "dit_skip: x and skip must both be [frames, dim]"); + VT_CHECK(weight.size() == static_cast(dim * 2 * dim), + "dit_skip: skip_in_linear weight must be [dim, 2 * dim]"); + VT_CHECK(bias.empty() || bias.size() == static_cast(dim), + "dit_skip: skip_in_linear bias must be [dim]"); + + std::vector out(static_cast(frames * dim)); + for (int64_t f = 0; f < frames; ++f) { + for (int64_t o = 0; o < dim; ++o) { + double acc = bias.empty() ? 0.0 : static_cast(bias[static_cast(o)]); + // cat([x, skip], -1): x occupies columns [0, dim), skip [dim, 2 * dim). + for (int64_t i = 0; i < dim; ++i) { + acc += static_cast(x[static_cast(f * dim + i)]) * + static_cast(weight[static_cast(o * 2 * dim + i)]); + } + for (int64_t i = 0; i < dim; ++i) { + acc += static_cast(skip[static_cast(f * dim + i)]) * + static_cast(weight[static_cast(o * 2 * dim + dim + i)]); + } + out[static_cast(f * dim + o)] = static_cast(acc); + } + } + return out; +} + +} // namespace dit_skip +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/dit_stack.cpp b/src/vllm/model_executor/models/dit_stack.cpp new file mode 100644 index 000000000..9bf970bd0 --- /dev/null +++ b/src/vllm/model_executor/models/dit_stack.cpp @@ -0,0 +1,67 @@ +// S2Mel DiT transformer stack. See dit_stack.h for the upstream anchors. +#include "vllm/model_executor/models/dit_stack.h" + +#include +#include + +#include "vllm/model_executor/models/dit_skip.h" +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace dit_stack { + +std::vector Forward(const Config& cfg, const Weights& w, + const std::vector& x, const std::vector& cond, + const std::vector& freqs) { + const int64_t layers = static_cast(w.layers.size()); + VT_CHECK(layers > 0, "dit_stack: no layers"); + VT_CHECK(cfg.dim > 0 && cfg.frames > 0, "dit_stack: dim and frames must be positive"); + VT_CHECK(x.size() == static_cast(cfg.frames * cfg.dim), + "dit_stack: x must be [frames, dim]"); + + const dit_skip::Schedule plan = dit_skip::Plan(layers); + + std::vector cur = x; + // The stack holds the OUTPUTS of emitting layers, in order, and receivers pop + // the most recent. `plan.source` already says which layer each receiver takes, + // so the stack here only has to carry the values. + std::vector> stack; + + for (int64_t i = 0; i < layers; ++i) { + const LayerWeights& layer = w.layers[static_cast(i)]; + + // Receive BEFORE the layer runs, merging with skip_in_linear. + if (plan.source[static_cast(i)] >= 0) { + VT_CHECK(!stack.empty(), "dit_stack: a receiving layer found no skip"); + VT_CHECK(!layer.skip_in_w.empty(), + "dit_stack: a receiving layer has no skip_in_linear"); + const std::vector skip = stack.back(); + stack.pop_back(); + cur = dit_skip::ApplySkip(cur, skip, cfg.frames, cfg.dim, layer.skip_in_w, + layer.skip_in_b); + } + + cur = dit::Block(cur, cond, cfg.frames, cfg.dim, cfg.heads, cfg.head_dim, + cfg.intermediate, freqs, layer.block, cfg.eps); + + // Emit AFTER, pushing this layer's own output. + bool emits = false; + for (const int64_t e : plan.emit) { + if (e == i) { + emits = true; + } + } + if (emits) { + stack.push_back(cur); + } + } + + // transformer.norm: the same AdaptiveLayerNorm shape as the per-block norms. + return dit::AdaptiveLayerNorm(cur, cfg.frames, cfg.dim, cond, w.norm_proj_w, + w.norm_proj_b, w.norm_w, cfg.eps); +} + +} // namespace dit_stack +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/dit_tail.cpp b/src/vllm/model_executor/models/dit_tail.cpp new file mode 100644 index 000000000..fdbbda1cc --- /dev/null +++ b/src/vllm/model_executor/models/dit_tail.cpp @@ -0,0 +1,144 @@ +// The S2Mel DiT tail. See dit_tail.h for the upstream anchors. +#include "vllm/model_executor/models/dit_tail.h" + +#include +#include +#include + +#include "vllm/model_executor/models/cfm.h" +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace dit_tail { +namespace { + +// y[row, out] = sum_in x[row, in] * W[out, in] + b[out], frame-major both sides. +std::vector Dense(const std::vector& x, int64_t rows, int64_t in_dim, + const Linear& l, int64_t out_dim) { + VT_CHECK(l.weight.size() == static_cast(out_dim * in_dim), + "dit_tail: dense weight must be [out, in]"); + VT_CHECK(l.bias.empty() || l.bias.size() == static_cast(out_dim), + "dit_tail: dense bias must be [out]"); + std::vector y(static_cast(rows * out_dim)); + for (int64_t r = 0; r < rows; ++r) { + for (int64_t o = 0; o < out_dim; ++o) { + double acc = l.bias.empty() ? 0.0 : static_cast(l.bias[static_cast(o)]); + for (int64_t i = 0; i < in_dim; ++i) { + acc += static_cast(x[static_cast(r * in_dim + i)]) * + static_cast(l.weight[static_cast(o * in_dim + i)]); + } + y[static_cast(r * out_dim + o)] = static_cast(acc); + } + } + return y; +} + +// TimestepEmbedder: sinusoidal features -> Linear -> SiLU -> Linear. +std::vector TimestepEmbedder(float t, int64_t freq_size, const Linear& mlp0, + const Linear& mlp2, int64_t out_dim) { + const std::vector feats = + cfm::TimestepFeatures({t}, freq_size, 10000.0, 1000.0); + std::vector h = Dense(feats, 1, freq_size, mlp0, out_dim); + for (float& v : h) { + v = static_cast(static_cast(v) / + (1.0 + std::exp(-static_cast(v)))); // SiLU + } + return Dense(h, 1, out_dim, mlp2, out_dim); +} + +} // namespace + +std::vector Forward(const Config& cfg, const Weights& w, + const std::vector& x_res, const std::vector& x, + float t, const std::vector& t1, + const std::vector& mask) { + VT_CHECK(cfg.hidden > 0 && cfg.in_channels > 0 && cfg.frames > 0, + "dit_tail: hidden, in_channels and frames must be positive"); + // The coupling upstream hides by setting both to 512. `final_layer` is sized + // at the wavenet width and conditioned on `t1`, which arrives at the DiT + // width, so unequal widths do not compose at all -- upstream itself raises a + // shape error. Refuse with a message that says which two numbers disagree. + VT_CHECK(cfg.wn_hidden == cfg.hidden, + "dit_tail: the wavenet width must equal the DiT hidden width, because " + "final_layer is built at the former and conditioned at the latter"); + VT_CHECK(x_res.size() == static_cast(cfg.frames * cfg.hidden), + "dit_tail: x_res must be [frames, hidden]"); + VT_CHECK(x.size() == static_cast(cfg.frames * cfg.in_channels), + "dit_tail: x must be [frames, in_channels]"); + VT_CHECK(t1.size() == static_cast(cfg.hidden), "dit_tail: t1 must be [hidden]"); + + const int64_t frames = cfg.frames; + const int64_t hidden = cfg.hidden; + const int64_t wn_h = cfg.wn_hidden; + + // The LONG skip: concatenate the transformer output with the step's input + // along the feature axis, then project back down. + std::vector cat(static_cast(frames * (hidden + cfg.in_channels))); + for (int64_t f = 0; f < frames; ++f) { + const size_t dst = static_cast(f * (hidden + cfg.in_channels)); + for (int64_t i = 0; i < hidden; ++i) { + cat[dst + static_cast(i)] = x_res[static_cast(f * hidden + i)]; + } + for (int64_t i = 0; i < cfg.in_channels; ++i) { + cat[dst + static_cast(hidden + i)] = + x[static_cast(f * cfg.in_channels + i)]; + } + } + const std::vector xr = + Dense(cat, frames, hidden + cfg.in_channels, w.skip_linear, hidden); + + // conv1 is a Linear over the FEATURE axis, then the tensor is transposed into + // the channel-major layout the wavenet wants. + const std::vector h_fm = Dense(xr, frames, hidden, w.conv1, wn_h); + std::vector h_cm(static_cast(wn_h * frames)); + for (int64_t f = 0; f < frames; ++f) { + for (int64_t c = 0; c < wn_h; ++c) { + h_cm[static_cast(c * frames + f)] = h_fm[static_cast(f * wn_h + c)]; + } + } + + // The SECOND timestep embedding, at the wavenet width, conditioning the + // wavenet. It is not the same vector as `t1`, which conditions final_layer. + const std::vector t2 = + TimestepEmbedder(t, cfg.freq_size, w.t_embedder2_mlp0, w.t_embedder2_mlp2, wn_h); + + const std::vector wn_out = + wavenet::Forward(cfg.wn, w.wn, h_cm, frames, t2, mask); + + // Long residual: the wavenet output (back to frame-major) plus a projection of + // the post-skip transformer output. + const std::vector res = Dense(xr, frames, hidden, w.res_projection, wn_h); + std::vector merged(static_cast(frames * wn_h)); + for (int64_t f = 0; f < frames; ++f) { + for (int64_t c = 0; c < wn_h; ++c) { + merged[static_cast(f * wn_h + c)] = + wn_out[static_cast(c * frames + f)] + + res[static_cast(f * wn_h + c)]; + } + } + + const std::vector fin = + adaln::FinalLayer(merged, frames, wn_h, wn_h, t1, w.final_layer, 1e-6); + + // conv2 is a kernel-1 Conv1d, so it is a dense map over channels, and its + // output stays CHANNEL-major: [in_channels, frames]. + std::vector out(static_cast(cfg.in_channels * frames)); + for (int64_t o = 0; o < cfg.in_channels; ++o) { + const double b = + w.conv2.bias.empty() ? 0.0 : static_cast(w.conv2.bias[static_cast(o)]); + for (int64_t f = 0; f < frames; ++f) { + double acc = b; + for (int64_t c = 0; c < wn_h; ++c) { + acc += static_cast(fin[static_cast(f * wn_h + c)]) * + static_cast(w.conv2.weight[static_cast(o * wn_h + c)]); + } + out[static_cast(o * frames + f)] = static_cast(acc); + } + } + return out; +} + +} // namespace dit_tail +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/fvq.cpp b/src/vllm/model_executor/models/fvq.cpp new file mode 100644 index 000000000..3f64a9ab0 --- /dev/null +++ b/src/vllm/model_executor/models/fvq.cpp @@ -0,0 +1,107 @@ +// FactorizedVectorQuantize. See fvq.h for the upstream anchors. +#include "vllm/model_executor/models/fvq.h" + +#include +#include +#include +#include + +#include "vllm/model_executor/models/vocoder1d.h" +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace fvq { + +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 = vocoder1d::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 = vocoder1d::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/src/vllm/model_executor/models/gemma4_weights.cpp b/src/vllm/model_executor/models/gemma4_weights.cpp index 4b406fc18..dea8052bb 100644 --- a/src/vllm/model_executor/models/gemma4_weights.cpp +++ b/src/vllm/model_executor/models/gemma4_weights.cpp @@ -135,7 +135,16 @@ OwnedTensor LoadFp8ChannelToBf16RawNk(const TensorResolver& get, const std::stri VT_CHECK(s.shape[0] == N, "gemma4 fp8: scale N"); OwnedTensor o = MakeOwned(vt::DType::kBF16, {N, K}); o.nk = true; - DequantFp8ChannelToBf16(w.data, reinterpret_cast(s.data), N, K, + // `s.data` points into the mmap'd safetensors payload at an arbitrary byte + // offset, so a `const uint16_t*` onto it is undefined to form or load through + // (issue #627), and DequantFp8ChannelToBf16 takes a typed scale pointer. Copy + // the N-element scale row into an aligned buffer first — N is the output + // channel count, negligible next to the N*K dequant it feeds. + std::vector scale(static_cast(N)); + VT_CHECK(s.nbytes >= scale.size() * sizeof(uint16_t), + "gemma4 fp8: scale tensor too small for " + base); + std::memcpy(scale.data(), s.data, scale.size() * sizeof(uint16_t)); + DequantFp8ChannelToBf16(w.data, scale.data(), N, K, reinterpret_cast(o.bytes.data())); MaybeReleaseSourcePages(w.data, w.nbytes); MaybeReleaseSourcePages(s.data, s.nbytes); 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/indextts2_pipeline.cpp b/src/vllm/model_executor/models/indextts2_pipeline.cpp new file mode 100644 index 000000000..c7d3f916e --- /dev/null +++ b/src/vllm/model_executor/models/indextts2_pipeline.cpp @@ -0,0 +1,175 @@ +// IndexTTS-2.5 pipeline composition. See indextts2_pipeline.h for what this +// does and does not claim. +#include "vllm/model_executor/models/indextts2_pipeline.h" + +#include +#include +#include +#include +#include + +#include "vllm/model_executor/models/campplus.h" +#include "vllm/model_executor/models/fvq.h" +#include "vllm/model_executor/models/lenreg.h" +#include "vllm/model_executor/models/talker.h" +#include "vllm/model_executor/models/vocoder1d.h" +#include "vllm/model_executor/models/w2vbert.h" +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace indextts2 { +namespace { + +// Deterministic synthetic weights: this composition runs on generated tensors, +// never on a checkpoint. Named so a stage's inputs are reproducible. +std::vector Synth(const std::string& name, int64_t count, double scale, uint64_t seed) { + uint64_t s = 0xCBF29CE484222325ULL ^ seed; + for (const char c : name) { + s ^= static_cast(c); + s *= 0x100000001B3ULL; + } + std::vector out(static_cast(count)); + for (int64_t i = 0; i < count; ++i) { + uint64_t x = s + static_cast(i); + x += 0x9E3779B97F4A7C15ULL; + uint64_t z = x; + z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; + z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; + z ^= z >> 31; + out[static_cast(i)] = + static_cast(((static_cast(z >> 11) * 0x1.0p-53) * 2.0 - 1.0) * scale); + } + return out; +} + +void Expect(bool ok, const std::string& stage, const std::string& what) { + if (!ok) throw std::runtime_error("indextts2 pipeline: " + stage + ": " + what); +} + +} // namespace + +PipelineResult RunReduced(const PipelineDims& d, const std::vector& reference_clip, + const std::vector& text_tokens, uint64_t seed) { + Expect(d.ref_frames > 0 && d.semantic_dim > 0 && d.mel_frames > 0, "dims", + "reference frames, semantic dim and mel frames must be positive"); + Expect(reference_clip.size() == static_cast(d.ref_frames * d.semantic_dim), + "stage 1 (w2v-bert)", "reference clip does not match [ref_frames, semantic_dim]"); + + PipelineResult r; + + // ── stage 1: semantic features. The reference clip stands in for the + // Conformer's output; the encoder itself is gated in test_w2vbert. + std::vector features = reference_clip; + + // ── stage 2: EnhancedCodec quantization -> semantic codes. + // The codec works [dim, frames]; the features arrive [frames, dim]. + Expect(d.codec_dim == d.semantic_dim, "stage 2 (EnhancedCodec)", + "codec input width must equal the semantic dim"); + std::vector codec_in(features.size()); + for (int64_t t = 0; t < d.ref_frames; ++t) { + for (int64_t c = 0; c < d.codec_dim; ++c) { + codec_in[static_cast(c * d.ref_frames + t)] = + features[static_cast(t * d.semantic_dim + c)]; + } + } + fvq::Weights qw; + qw.in_g = Synth("q.in_g", d.codebook_dim, 0.5, seed); + qw.in_v = Synth("q.in_v", d.codebook_dim * d.codec_dim, 0.3, seed); + qw.in_bias = Synth("q.in_b", d.codebook_dim, 0.2, seed); + qw.out_g = Synth("q.out_g", d.codec_dim, 0.5, seed); + qw.out_v = Synth("q.out_v", d.codec_dim * d.codebook_dim, 0.3, seed); + qw.out_bias = Synth("q.out_b", d.codec_dim, 0.2, seed); + qw.codebook = Synth("q.cb", d.codebook_size * d.codebook_dim, 0.6, seed); + const fvq::QuantizeResult q = + fvq::Quantize(codec_in, d.ref_frames, d.codec_dim, d.codebook_dim, d.codebook_size, qw); + Expect(static_cast(q.indices.size()) == d.ref_frames, "stage 2 (EnhancedCodec)", + "one semantic code per reference frame"); + r.semantic_codes = q.indices; + r.quantized = q.z_q; + + // ── stage 3: CAMPPlus style vector from the quantized feature. + // CAMPPlus consumes (T, F); the quantized output is [dim, frames]. + Expect(d.style_feat_dim > 0 && d.style_dim > 0, "stage 3 (CAMPPlus)", + "style dims must be positive"); + // CHANNEL-MAJOR [feat, frames] -- StatsPool's layout. Building this + // frame-major silently transposes the view, so the pooled "channels" mix + // features and frames; the statistics still look plausible and are wrong. + // Caught by the permutation case below, which requires a frame permutation to + // leave the style untouched. + std::vector style_in(static_cast(d.style_feat_dim * d.ref_frames)); + for (int64_t f = 0; f < d.style_feat_dim; ++f) { + // Fold the codec width onto the style feature width deterministically; the + // real model feeds CAMPPlus its own 80-bin fbank. + const int64_t src = f % d.codec_dim; + for (int64_t t = 0; t < d.ref_frames; ++t) { + style_in[static_cast(f * d.ref_frames + t)] = + q.z_q[static_cast(src * d.ref_frames + t)]; + } + } + // StatsPool alone stands in for the full encoder here; CAMPPlus::Forward is + // gated whole in test_campplus, and re-running its 52 layers would make this + // seam a duplicate of that gate rather than a composition check. + const std::vector stats = + campplus::StatsPool(style_in, d.style_feat_dim, d.ref_frames); + Expect(static_cast(stats.size()) == 2 * d.style_feat_dim, "stage 3 (CAMPPlus)", + "stats pooling emits mean and std"); + r.style.assign(stats.begin(), stats.begin() + static_cast(d.style_dim)); + + // ── stage 4: length regulator -> prompt condition at the MEL rate. + const std::vector regulated = + lenreg::InterpolateNearest(q.z_q, d.codec_dim, d.ref_frames, d.mel_frames); + Expect(static_cast(regulated.size()) == d.codec_dim * d.mel_frames, + "stage 4 (length regulator)", "prompt condition must be at the mel frame rate"); + r.prompt_condition = regulated; + + // ── stage 5: talker embeddings -> mel codes. + Expect(!text_tokens.empty(), "stage 5 (talker)", "text tokens must not be empty"); + const std::vector tok_table = + Synth("t.tok", d.talker_vocab * d.talker_dim, 0.3, seed); + const std::vector pos_table = + Synth("t.pos", d.talker_vocab * d.talker_dim, 0.1, seed); + const std::vector talker_in = talker::EmbedWithPositions( + text_tokens, tok_table, pos_table, d.talker_dim, d.talker_vocab); + Expect(talker_in.size() == static_cast(text_tokens.size()) * + static_cast(d.talker_dim), + "stage 5 (talker)", "embedded text must be [tokens, talker_dim]"); + + // ── stage 6: mel -> waveform through the shared vocoder core. + // The mel is built from the regulated condition and the style, so a change to + // EITHER propagates to the waveform -- which is what this seam must prove. + std::vector mel(static_cast(d.mel_channels * d.mel_frames)); + for (int64_t c = 0; c < d.mel_channels; ++c) { + for (int64_t t = 0; t < d.mel_frames; ++t) { + const double cond = static_cast( + regulated[static_cast((c % d.codec_dim) * d.mel_frames + t)]); + const double sty = static_cast(r.style[static_cast(c % d.style_dim)]); + const double txt = static_cast( + talker_in[static_cast((t % static_cast(text_tokens.size())) * + d.talker_dim + (c % d.talker_dim))]); + mel[static_cast(c * d.mel_frames + t)] = + static_cast(std::tanh(cond + 0.5 * sty + 0.25 * txt)); + } + } + r.mel = mel; + + // One depthwise-ish pass through the ported vocoder primitive, so the final + // hop is real code rather than a copy. + int64_t out_len = 0; + const std::vector padded = + vocoder1d::Pad1d(mel, d.mel_channels, d.mel_frames, 1, 1, /*replicate=*/true, &out_len); + const std::vector kernel = + Synth("v.k", d.mel_channels * 3, 0.4, seed); + int64_t wav_len = 0; + r.waveform = vocoder1d::Conv1d(padded, d.mel_channels, out_len, kernel, /*bias=*/nullptr, + d.mel_channels, /*kernel=*/3, /*stride=*/1, /*dilation=*/1, + /*groups=*/d.mel_channels, &wav_len); + Expect(wav_len == d.mel_frames, "stage 6 (vocoder)", + "waveform frame count must match the mel length"); + r.sample_rate = 22050; + return r; +} + +} // namespace indextts2 +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/indextts2_s2mel_loader.cpp b/src/vllm/model_executor/models/indextts2_s2mel_loader.cpp new file mode 100644 index 000000000..3289edb04 --- /dev/null +++ b/src/vllm/model_executor/models/indextts2_s2mel_loader.cpp @@ -0,0 +1,193 @@ +// S2Mel checkpoint binding. See indextts2_s2mel_loader.h for the anchors. +#include "vllm/model_executor/models/indextts2_s2mel_loader.h" + +#include +#include +#include +#include +#include + +namespace vllm { +namespace models { +namespace indextts2 { +namespace { + +constexpr const char* kPrefix = "net.cfm.estimator."; + +[[noreturn]] void Fail(const std::string& what) { + throw std::runtime_error("IndexTTS-2.5 s2mel: " + what); +} + +int64_t Elems(const std::vector& shape) { + int64_t n = 1; + for (const int64_t d : shape) { + n *= d; + } + return n; +} + +// Every S2Mel tensor is F32 in the shipped checkpoint. Refuse anything else by +// name rather than reinterpreting bytes: a silently misread dtype is finite, +// plausible and wrong. +std::vector Read(const SafetensorsFile& file, const std::string& suffix) { + const std::string name = kPrefix + suffix; + const StTensor* t = nullptr; + try { + t = &file.Get(name); + } catch (const std::exception&) { + Fail("missing tensor '" + name + "'"); + } + if (t->dtype != "F32") { + Fail("tensor '" + name + "' is " + t->dtype + ", expected F32"); + } + const int64_t n = Elems(t->shape); + if (t->nbytes != static_cast(n) * sizeof(float)) { + Fail("tensor '" + name + "' byte length disagrees with its shape"); + } + std::vector out(static_cast(n)); + std::memcpy(out.data(), t->data, t->nbytes); + return out; +} + +// Returned BY VALUE: a shape is a handful of ints, and returning a reference +// out of a try/catch is what -Werror=dangling-reference exists to stop. +std::vector Shape(const SafetensorsFile& file, const std::string& suffix) { + const std::string name = kPrefix + suffix; + try { + return file.Get(name).shape; + } catch (const std::exception&) { + Fail("missing tensor '" + name + "'"); + } +} + +dit_tail::Linear ReadLinear(const SafetensorsFile& file, const std::string& suffix) { + dit_tail::Linear l; + l.weight = Read(file, suffix + ".weight"); + l.bias = Read(file, suffix + ".bias"); + return l; +} + +wavenet::ConvWeights ReadWeightNormConv(const SafetensorsFile& file, + const std::string& suffix) { + wavenet::ConvWeights c; + // torch's legacy weight_norm stores g with the reduced dims kept, so the + // shipped `weight_g` is [out, 1, 1] rather than [out]. Flattening is correct + // precisely because every trailing dim is 1; assert that instead of assuming. + const std::vector gs = Shape(file, suffix + ".weight_g"); + for (size_t i = 1; i < gs.size(); ++i) { + if (gs[i] != 1) { + Fail("weight_g for '" + suffix + "' is not one magnitude per output channel"); + } + } + c.g = Read(file, suffix + ".weight_g"); + c.v = Read(file, suffix + ".weight_v"); + c.bias = Read(file, suffix + ".bias"); + return c; +} + +int64_t CountWavenetLayers(const SafetensorsFile& file) { + int64_t n = 0; + const std::string stem = std::string(kPrefix) + "wavenet.in_layers."; + for (const std::string& name : file.Names()) { + if (name.rfind(stem, 0) == 0 && name.size() > stem.size() && + name.find(".conv.conv.weight_v") != std::string::npos) { + ++n; + } + } + return n; +} + +} // namespace + +S2MelTail LoadS2MelTail(const SafetensorsFile& file) { + S2MelTail out; + + // Resolve the dimensions from the weights themselves. + const std::vector skip_w = Shape(file, "skip_linear.weight"); + if (skip_w.size() != 2) { + Fail("skip_linear.weight must be 2-D"); + } + const int64_t hidden = skip_w[0]; + const int64_t in_channels = skip_w[1] - hidden; + if (in_channels <= 0) { + Fail("skip_linear.weight is not [hidden, hidden + in_channels]"); + } + + const std::vector conv1_w = Shape(file, "conv1.weight"); + if (conv1_w.size() != 2) { + Fail("conv1.weight must be 2-D"); + } + const int64_t wn_hidden = conv1_w[0]; + + const std::vector te2 = Shape(file, "t_embedder2.mlp.0.weight"); + if (te2.size() != 2) { + Fail("t_embedder2.mlp.0.weight must be 2-D"); + } + const int64_t freq_size = te2[1]; + + const int64_t layers = CountWavenetLayers(file); + if (layers <= 0) { + Fail("no wavenet in_layers found; this is not an S2Mel checkpoint"); + } + + // in_layers.0 is [2 * wn_hidden, wn_hidden, kernel]. + const std::vector in0 = Shape(file, "wavenet.in_layers.0.conv.conv.weight_v"); + if (in0.size() != 3) { + Fail("wavenet in_layer weight_v must be 3-D"); + } + const int64_t kernel = in0[2]; + + // dilation_rate is not stored. Upstream ships 1, and the shapes cannot + // distinguish it, so it is recorded as an ASSUMPTION here rather than + // presented as something the checkpoint proved. + out.config.hidden = hidden; + out.config.wn_hidden = wn_hidden; + out.config.in_channels = in_channels; + out.config.freq_size = freq_size; + out.config.frames = 0; // per call + out.config.wn.hidden = wn_hidden; + out.config.wn.kernel = kernel; + out.config.wn.dilation_rate = 1; + out.config.wn.layers = layers; + out.config.wn.gin = wn_hidden; + + out.weights.skip_linear = ReadLinear(file, "skip_linear"); + out.weights.conv1 = ReadLinear(file, "conv1"); + out.weights.res_projection = ReadLinear(file, "res_projection"); + out.weights.conv2 = ReadLinear(file, "conv2"); // [in_channels, wn_hidden, 1] + out.weights.t_embedder2_mlp0 = ReadLinear(file, "t_embedder2.mlp.0"); + out.weights.t_embedder2_mlp2 = ReadLinear(file, "t_embedder2.mlp.2"); + + out.weights.wn.cond = ReadWeightNormConv(file, "wavenet.cond_layer.conv.conv"); + for (int64_t i = 0; i < layers; ++i) { + const std::string idx = std::to_string(i); + out.weights.wn.in_layers.push_back( + ReadWeightNormConv(file, "wavenet.in_layers." + idx + ".conv.conv")); + out.weights.wn.res_skip_layers.push_back( + ReadWeightNormConv(file, "wavenet.res_skip_layers." + idx + ".conv.conv")); + } + + out.weights.final_layer.ada_w = Read(file, "final_layer.adaLN_modulation.1.weight"); + out.weights.final_layer.ada_b = Read(file, "final_layer.adaLN_modulation.1.bias"); + out.weights.final_layer.linear_g = Read(file, "final_layer.linear.weight_g"); + out.weights.final_layer.linear_v = Read(file, "final_layer.linear.weight_v"); + out.weights.final_layer.linear_bias = Read(file, "final_layer.linear.bias"); + + // The coupling the shipped config hides; dit_tail refuses it too, but failing + // at LOAD names the checkpoint rather than the call. + if (wn_hidden != hidden) { + Fail("this checkpoint has a wavenet width (" + std::to_string(wn_hidden) + + ") different from its DiT hidden width (" + std::to_string(hidden) + + "), which the wavenet final layer cannot compose"); + } + return out; +} + +S2MelTail LoadS2MelTail(const std::string& path) { + const SafetensorsFile file = SafetensorsFile::Open(path); + return LoadS2MelTail(file); +} + +} // namespace indextts2 +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/internlm2_weights.cpp b/src/vllm/model_executor/models/internlm2_weights.cpp index ab60dee10..3e8fdd5c1 100644 --- a/src/vllm/model_executor/models/internlm2_weights.cpp +++ b/src/vllm/model_executor/models/internlm2_weights.cpp @@ -98,13 +98,19 @@ OwnedTensor DeinterleaveWqkv(const StTensor& wqkv, const HfConfig& cfg, VT_CHECK(wqkv.nbytes == expected, "internlm2: byte-size mismatch for " + name); OwnedTensor merged = MakeOwned(vt::DType::kBF16, {out_rows, H}); - const auto* src = reinterpret_cast(wqkv.data); + // `src` stays BYTE-typed: it points into the mmap'd safetensors payload, whose + // per-tensor offset is the running total of everything ahead of it and so need + // not be even, and a `const uint16_t*` onto an odd byte is undefined to form + // (issue #627). Every read below is a bulk memcpy, so byte offsets — the same + // arithmetic scaled by sizeof(uint16_t) — copy the identical bytes. + const uint8_t* src = wqkv.data; auto* dst = reinterpret_cast(merged.bytes.data()); - const size_t row = static_cast(H); // elems per output row + const size_t row = static_cast(H); // elems per output row + const size_t row_bytes = row * sizeof(uint16_t); // bytes per output row if (WrongSplitRed()) { // RED path: copy wqkv straight through (naive [q|k|v] concat, NO // de-interleave) — the WRONG split; heads end up scrambled. - std::memcpy(dst, src, static_cast(out_rows) * row * sizeof(uint16_t)); + std::memcpy(dst, src, static_cast(out_rows) * row_bytes); MaybeReleaseSourcePages(wqkv.data, wqkv.nbytes); merged.nk = true; return merged; @@ -113,16 +119,16 @@ OwnedTensor DeinterleaveWqkv(const StTensor& wqkv, const HfConfig& cfg, const int64_t block = g * (groups + 2) * Dh; // first wqkv row of kv-group g // q: groups*Dh rows -> q section at g*groups*Dh std::memcpy(dst + static_cast(g * groups * Dh) * row, - src + static_cast(block) * row, - static_cast(groups * Dh) * row * sizeof(uint16_t)); + src + static_cast(block) * row_bytes, + static_cast(groups * Dh) * row_bytes); // k: Dh rows -> k section (after q_rows) at g*Dh std::memcpy(dst + static_cast(q_rows + g * Dh) * row, - src + static_cast(block + groups * Dh) * row, - static_cast(Dh) * row * sizeof(uint16_t)); + src + static_cast(block + groups * Dh) * row_bytes, + static_cast(Dh) * row_bytes); // v: Dh rows -> v section (after q_rows+kv_rows) at g*Dh std::memcpy(dst + static_cast(q_rows + kv_rows + g * Dh) * row, - src + static_cast(block + (groups + 1) * Dh) * row, - static_cast(Dh) * row * sizeof(uint16_t)); + src + static_cast(block + (groups + 1) * Dh) * row_bytes, + static_cast(Dh) * row_bytes); } MaybeReleaseSourcePages(wqkv.data, wqkv.nbytes); merged.nk = true; // raw [N=out_rows, K=H] for vt::MatmulBT diff --git a/src/vllm/model_executor/models/laguna_weights.cpp b/src/vllm/model_executor/models/laguna_weights.cpp index f845351c9..03dd69267 100644 --- a/src/vllm/model_executor/models/laguna_weights.cpp +++ b/src/vllm/model_executor/models/laguna_weights.cpp @@ -37,6 +37,7 @@ #include "vllm/model_executor/layers/quantization/compressed_tensors/nvfp4_emulation.h" // DequantCtNvfp4WeightToF32 #include "vllm/model_executor/models/qwen3_5_gguf_weights.h" // OwnGgufQuantBlocks #include "vt/dtype.h" +#include "vt/unaligned.h" namespace vllm { namespace { @@ -264,10 +265,12 @@ OwnedTensor LnLoadF32Direct(const TensorResolver& get, const std::string& name) } else if (t.dtype == "BF16") { const size_t n = t.nbytes / 2; // bf16 = 2 bytes/elem VT_CHECK(o.bytes.size() == n * 4, "laguna nvfp4: BF16->F32 size mismatch " + name); - const auto* src = reinterpret_cast(t.data); + // Unaligned: `t.data` is an arbitrary byte offset into the mmap (#627). auto* dst = reinterpret_cast(o.bytes.data()); for (size_t i = 0; i < n; ++i) { - const uint32_t bits = static_cast(src[i]) << 16; // bf16 -> high 16 bits of f32 + const uint32_t bits = static_cast( + vt::LoadUnaligned(t.data + i * 2)) + << 16; // bf16 -> high 16 bits of f32 std::memcpy(&dst[i], &bits, 4); } } else { 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/src/vllm/model_executor/models/ltx2.cpp b/src/vllm/model_executor/models/ltx2.cpp index 7e5a4cffc..f4ec1202f 100644 --- a/src/vllm/model_executor/models/ltx2.cpp +++ b/src/vllm/model_executor/models/ltx2.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -69,7 +70,7 @@ float Silu(float x) { return x / (1.0f + std::exp(-x)); } // torch.nn.functional.gelu(..., approximate="tanh"), the activation // `activation_fn="gelu-approximate"` selects (gelu_approx.py:10). float GeluTanh(float x) { - const float kBeta = static_cast(std::sqrt(2.0 / M_PI)); + const float kBeta = static_cast(std::sqrt(2.0 / std::numbers::pi_v)); const float kKappa = 0.044715f; const float inner = kBeta * (x + kKappa * x * x * x); return 0.5f * x * (1.0f + std::tanh(inner)); @@ -268,17 +269,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 +487,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"); @@ -544,7 +555,8 @@ std::vector FreqGridPytorch(double theta, int64_t n_pos_dims, int64_t dim const float t = i < halfway ? step * static_cast(i) : 1.0f - step * static_cast(n - 1 - i); out[static_cast(i)] = - std::pow(static_cast(theta), t) * static_cast(M_PI / 2.0); + std::pow(static_cast(theta), t) * + static_cast(std::numbers::pi_v / 2.0); } return out; } @@ -559,7 +571,8 @@ std::vector FreqGridNumpy(double theta, int64_t n_pos_dims, int64_t dim) for (int64_t i = 0; i < n; ++i) { // numpy's linspace is arange(n) * step, with the final sample forced to `stop`. const double t = (i == n - 1) ? 1.0 : step * static_cast(i); - out[static_cast(i)] = static_cast(std::pow(theta, t) * (M_PI / 2.0)); + out[static_cast(i)] = + static_cast(std::pow(theta, t) * (std::numbers::pi_v / 2.0)); } return out; } diff --git a/src/vllm/model_executor/models/ltx2_audio_vae.cpp b/src/vllm/model_executor/models/ltx2_audio_vae.cpp index 6c9251319..c84d591da 100644 --- a/src/vllm/model_executor/models/ltx2_audio_vae.cpp +++ b/src/vllm/model_executor/models/ltx2_audio_vae.cpp @@ -24,16 +24,14 @@ #include #include #include +#include #include #include #include "vllm/model_executor/models/minimax_h3.h" +#include "vllm/model_executor/models/vocoder1d.h" #include "vt/dtype.h" -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - namespace vllm { const std::vector& Ltx2VaeWeights::Get(const std::string& name) const { @@ -405,7 +403,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 @@ -432,9 +430,14 @@ std::vector Ltx2HannSincResampleFilter1d(int64_t ratio, int64_t* kernel_s const double clamped = std::max(-static_cast(lowpass_filter_width), std::min(static_cast(lowpass_filter_width), t)); - const double window = - std::pow(std::cos(clamped * M_PI / static_cast(lowpass_filter_width) / 2.0), 2.0); - const double sinc = t == 0.0 ? 1.0 : std::sin(M_PI * t) / (M_PI * t); + const double window = std::pow( + std::cos(clamped * std::numbers::pi_v / + static_cast(lowpass_filter_width) / 2.0), + 2.0); + const double sinc = + t == 0.0 ? 1.0 + : std::sin(std::numbers::pi_v * t) / + (std::numbers::pi_v * t); filter[static_cast(i)] = static_cast(sinc * window * rolloff / static_cast(ratio)); } @@ -545,7 +548,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 +576,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 +629,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 +651,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 +695,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 +763,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 +789,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 +832,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; @@ -1042,7 +1045,8 @@ std::vector Ltx2WaveformToLogMel(const Ltx2AudioProcessorConfig& config, std::vector window(static_cast(n_fft)); for (int64_t i = 0; i < n_fft; ++i) { window[static_cast(i)] = - 0.5 - 0.5 * std::cos(2.0 * M_PI * static_cast(i) / static_cast(n_fft)); + 0.5 - 0.5 * std::cos(2.0 * std::numbers::pi_v * + static_cast(i) / static_cast(n_fft)); } // `center=True, pad_mode="reflect"`: pad n_fft/2 on BOTH sides, so frame 0 is @@ -1080,7 +1084,8 @@ std::vector Ltx2WaveformToLogMel(const Ltx2AudioProcessorConfig& config, for (int64_t f = 0; f < n_freqs; ++f) { double real = 0.0; double imag = 0.0; - const double omega = -2.0 * M_PI * static_cast(f) / static_cast(n_fft); + const double omega = -2.0 * std::numbers::pi_v * + static_cast(f) / static_cast(n_fft); for (int64_t i = 0; i < n_fft; ++i) { const double angle = omega * static_cast(i); real += frame[static_cast(i)] * std::cos(angle); 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/src/vllm/model_executor/models/ltx2_video_vae.cpp b/src/vllm/model_executor/models/ltx2_video_vae.cpp index c7cb88825..592f5b8b4 100644 --- a/src/vllm/model_executor/models/ltx2_video_vae.cpp +++ b/src/vllm/model_executor/models/ltx2_video_vae.cpp @@ -55,10 +55,6 @@ #include "vllm/model_executor/models/minimax_h3.h" #include "vt/dtype.h" -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - namespace vllm { namespace { 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/model_executor/models/minimax_h3_audio_vae.cpp b/src/vllm/model_executor/models/minimax_h3_audio_vae.cpp index cb28d0437..aab353445 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,294 +54,14 @@ 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) { - VT_CHECK(out_channels > 0 && v.size() % static_cast(out_channels) == 0, - "minimax_h3 audio vae: weight-norm direction does not divide by out_channels"); - const int64_t per_out = static_cast(v.size()) / out_channels; - VT_CHECK(static_cast(g.size()) == out_channels, - "minimax_h3 audio vae: weight-norm magnitude must have one value per output channel"); - std::vector out(v.size()); - for (int64_t c = 0; c < out_channels; ++c) { - double norm = 0.0; - for (int64_t i = 0; i < per_out; ++i) { - const double value = v[static_cast(c * per_out + i)]; - norm += value * value; - } - norm = std::sqrt(norm); - const double scale = norm > 0.0 ? static_cast(g[static_cast(c)]) / norm : 0.0; - for (int64_t i = 0; i < per_out; ++i) { - out[static_cast(c * per_out + i)] = - static_cast(v[static_cast(c * per_out + i)] * scale); - } - } - return out; -} +// The weight-norm fold now lives in `vocoder1d::MaterializeWeightNorm`. // The anti-aliased activation, `Activation1d`: upsample by `ratio` -> Snake(Beta) // -> downsample by `ratio` (MiniMax-H3: dac_alias_free_act.py + // 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,16 +83,16 @@ 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) { - return MiniMaxH3MaterializeWeightNorm(weights.Get(prefix + ".parametrizations.weight.original0"), + return vocoder1d::MaterializeWeightNorm(weights.Get(prefix + ".parametrizations.weight.original0"), weights.Get(prefix + ".parametrizations.weight.original1"), 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 +108,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 +116,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 +130,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 +143,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 +178,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 +193,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 +229,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. @@ -573,7 +294,7 @@ std::vector WnConv1d(const MiniMaxH3AudioVaeWeights& weights, const std:: int64_t out_channels, int64_t kernel, int64_t stride, int64_t dilation, int64_t padding, int64_t* out_len) { const std::vector w = - MiniMaxH3MaterializeWeightNorm(weights.Get(prefix + ".parametrizations.weight.original0"), + vocoder1d::MaterializeWeightNorm(weights.Get(prefix + ".parametrizations.weight.original0"), weights.Get(prefix + ".parametrizations.weight.original1"), out_channels); VT_CHECK(static_cast(w.size()) == out_channels * in_channels * kernel, @@ -581,7 +302,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 +660,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 +668,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/minimax_music3_loader.cpp b/src/vllm/model_executor/models/minimax_music3_loader.cpp new file mode 100644 index 000000000..dea891f41 --- /dev/null +++ b/src/vllm/model_executor/models/minimax_music3_loader.cpp @@ -0,0 +1,896 @@ +// MiniMax-Music3 — the modular six-component checkpoint loader. See +// minimax_music3_loader.h for the decisions; this file is the mechanism. +// +// The enumerations below walk upstream's own `__init__` module for module, at +// diffusers PR #14456 head c6da9936, so a reviewer can diff them by eye against +// the Python rather than against a header dump. Every `file:line` cited is that +// revision. +#include "vllm/model_executor/models/minimax_music3_loader.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "vllm/model_executor/model_loader/safetensors_reader.h" +#include "vllm/model_executor/models/vocoder1d.h" +#include "vt/dtype.h" + +namespace vllm { +namespace { + +namespace fs = std::filesystem; + +// The safetensors header spelling of each component's ON-DISK dtype, as +// convert_minimax_music3_to_diffusers.py writes it (`--dtype float32` default +// at :267, applied at :208-211; bf16 forced on the depth decoder at :214) and +// as the headers measure. +// +// THIS IS WHAT THE FILES STORE, NOT WHAT WILL RUN. The two differ here and the +// distinction is load-bearing -- see the header's dtype section. The on-disk +// set is not a runnable configuration: running the oracle showed the condition +// encoder must match the LANGUAGE MODEL's dtype, because upstream never casts +// on the way in (denoise.py:82 moves device only) and +// condition_embedder_minimax_music3.py:64 then feeds bf16 hidden states to an +// fp32 Conv1d. `MiniMaxMusic3ResolveRuntimeDtypes` owns the runtime answer. +// +// F32 on the ACOUSTIC half still needs no apology under AGENTS.md's too-wide +// rule: the oracle runs fp32 there too (the converter's default, and what +// SGLang-Omni states it runs). Narrowing that is a measured change with its own +// evidence, never a loader's initiative. +constexpr const char* kF32 = "F32"; +constexpr const char* kBf16 = "BF16"; + +std::string ShapeToString(const std::vector& shape) { + std::string out = "["; + for (size_t i = 0; i < shape.size(); ++i) { + if (i != 0) out += ", "; + out += std::to_string(shape[i]); + } + return out + "]"; +} + +std::string JoinNames(const std::vector& names, const char* separator) { + std::string out; + for (size_t i = 0; i < names.size(); ++i) { + if (i != 0) out += separator; + out += names[i]; + } + return out; +} + +void SortByName(std::vector& specs) { + std::sort(specs.begin(), specs.end(), + [](const MiniMaxMusic3TensorSpec& a, const MiniMaxMusic3TensorSpec& b) { + return a.name < b.name; + }); +} + +nlohmann::json ReadJson(const std::string& path) { + std::ifstream in(path); + if (!in) { + throw std::runtime_error("minimax_music3: cannot open " + path); + } + try { + nlohmann::json parsed; + in >> parsed; + return parsed; + } catch (const std::exception& error) { + throw std::runtime_error("minimax_music3: " + path + " is not valid JSON: " + error.what()); + } +} + +// Read a required key, refusing BY NAME rather than defaulting. A config key +// that silently falls back to a class default is a DIFFERENT model built from +// the same tensors (.agents/porting-a-model.md section 1). +int64_t RequireInt(const nlohmann::json& object, const std::string& key, + const std::string& source) { + const auto it = object.find(key); + if (it == object.end() || !it->is_number()) { + throw std::runtime_error("minimax_music3: " + source + " has no integer \"" + key + "\""); + } + return it->get(); +} + +double RequireNumber(const nlohmann::json& object, const std::string& key, + const std::string& source) { + const auto it = object.find(key); + if (it == object.end() || !it->is_number()) { + throw std::runtime_error("minimax_music3: " + source + " has no numeric \"" + key + "\""); + } + return it->get(); +} + +bool RequireBool(const nlohmann::json& object, const std::string& key, + const std::string& source) { + const auto it = object.find(key); + if (it == object.end() || !it->is_boolean()) { + throw std::runtime_error("minimax_music3: " + source + " has no boolean \"" + key + "\""); + } + return it->get(); +} + +// `_class_name` (diffusers) or `architectures[0]` (transformers) must be the +// class this port implements. A config for a DIFFERENT class that happens to +// carry compatible keys would otherwise bind silently. +void RequireClassName(const nlohmann::json& object, const std::string& expected, + const std::string& source) { + const auto it = object.find("_class_name"); + if (it != object.end() && it->is_string()) { + if (it->get() != expected) { + throw std::runtime_error("minimax_music3: " + source + " declares _class_name \"" + + it->get() + "\", but this port implements \"" + + expected + "\""); + } + return; + } + const auto arch = object.find("architectures"); + if (arch != object.end() && arch->is_array() && !arch->empty() && arch->front().is_string()) { + if (arch->front().get() != expected) { + throw std::runtime_error("minimax_music3: " + source + " declares architecture \"" + + arch->front().get() + + "\", but this port implements \"" + expected + "\""); + } + return; + } + throw std::runtime_error( + "minimax_music3: " + source + + " declares neither _class_name nor architectures; refusing to assume \"" + expected + "\""); +} + +// Shard list for a component, in the index's own order, deduplicated. Reading +// the index rather than globbing means a shard the index does not mention is +// never silently loaded and a shard it does mention is never silently skipped. +std::vector ShardsFromIndex(const std::string& directory, + const std::string& index_name, + const std::string& single_name) { + const fs::path index = fs::path(directory) / index_name; + std::error_code ec; + if (!fs::exists(index, ec)) { + const fs::path single = fs::path(directory) / single_name; + if (!fs::exists(single, ec)) { + throw std::runtime_error("minimax_music3: " + directory + " has neither " + index_name + + " nor " + single_name); + } + return {single.string()}; + } + const std::map weight_map = LoadSafetensorsIndex(index.string()); + std::set unique; + for (const auto& [tensor, shard] : weight_map) { + (void)tensor; + unique.insert(shard); + } + std::vector out; + for (const std::string& shard : unique) { + const fs::path path = fs::path(directory) / shard; + if (!fs::exists(path, ec)) { + throw std::runtime_error("minimax_music3: " + index.string() + " names shard " + shard + + ", which is not present in " + directory); + } + out.push_back(path.string()); + } + return out; +} + +// One weight-normed convolution: the module prefix and the shape of `weight_v`. +// `weight_g` is always [dim0, 1, 1] and the bias always [out_channels], so the +// pair and the bias are derived here rather than repeated at each call. +struct WeightNormedConv { + std::string module; + std::vector v_shape; // [dim0, dim1, kernel] + int64_t bias; // the CONV's output channels +}; + +// minimax_music3_vocoder.py:88-98, :54-62, :41-44 walked in construction order. +std::vector VocoderWeightNormedConvs( + const MiniMaxMusic3VocoderConfig& config) { + std::vector out; + // :89 weight_norm(nn.Conv1d(decoder_input_dim, decoder_hidden_dim, 7)). + out.push_back({"conv_in", + {config.decoder_hidden_dim, config.decoder_input_dim, 7}, + config.decoder_hidden_dim}); + + int64_t last_output = config.decoder_hidden_dim; + for (size_t index = 0; index < config.upsampling_ratios.size(); ++index) { + const int64_t stride = config.upsampling_ratios[index]; + // :91-95 input_dim = hidden >> index, output_dim = hidden >> (index + 1). + const int64_t input_dim = config.decoder_hidden_dim / (int64_t(1) << index); + const int64_t output_dim = config.decoder_hidden_dim / (int64_t(1) << (index + 1)); + last_output = output_dim; + const std::string block = "blocks." + std::to_string(index) + "."; + // :55-59 weight_norm(nn.ConvTranspose1d(input_dim, output_dim, 2*stride)). + // + // NOTE THE AXIS. A ConvTranspose1d weight is [C_in, C_out, K], so dim 0 -- + // the axis torch's weight_norm reduces over -- is the INPUT channel here + // and the OUTPUT channel for every plain Conv1d in this list. That is the + // whole reason `vocoder1d::MaterializeWeightNorm` names its argument + // `dim0`; folding these four over `out_channels` would be finite, correctly + // shaped and wrong. + out.push_back({block + "conv_t1", {input_dim, output_dim, 2 * stride}, output_dim}); + // :41-44, dilations 1/3/9 (:60-62). The dilation changes the PADDING, not + // the stored shape, so it does not appear here. + for (int unit = 1; unit <= 3; ++unit) { + const std::string prefix = block + "res_unit" + std::to_string(unit) + "."; + out.push_back({prefix + "conv1", {output_dim, output_dim, 7}, output_dim}); + out.push_back({prefix + "conv2", {output_dim, output_dim, 1}, output_dim}); + } + } + // :98 weight_norm(nn.Conv1d(output_dim, 1, 7)) -- the mono stream head; the + // stereo pair comes from the two folded 64-channel streams (:110, :115), not + // from two output channels. + out.push_back({"conv_out", {1, last_output, 7}, 1}); + return out; +} + +// Copy an F32 safetensors tensor out. Byte-wise, NOT a +// `reinterpret_cast`: safetensors puts the payload straight after +// a JSON header of ARBITRARY length, so a tensor's first byte is only 4-byte +// aligned if the writer happened to pad, and the format does not require it. +// UBSan caught exactly that cast on this project once already +// (minimax_h3_vae_loader.cpp's ReadSafetensorF32 carries the same note). +std::vector ReadF32(const std::string& component, const std::string& name, + const StTensor& tensor) { + int64_t numel = 1; + for (int64_t dim : tensor.shape) numel *= dim; + if (tensor.dtype != kF32 || tensor.nbytes != static_cast(numel) * 4) { + throw std::runtime_error("minimax_music3: " + component + ": tensor " + name + + " is not an F32 tensor of its declared shape"); + } + std::vector out(static_cast(numel)); + std::memcpy(out.data(), tensor.data, tensor.nbytes); + return out; +} + +} // namespace + +// --------------------------------------------------------------------------- +// Small accessors +// --------------------------------------------------------------------------- + +int64_t MiniMaxMusic3VocoderConfig::hop_length() const { + // minimax_music3_vocoder.py:92-95 — one ConvTranspose1d per ratio, so the + // total upsample is their product. 8*8*4*2 = 512, and 44100/512 = 86.133 Hz + // is the latent frame rate the condition encoder sets (spec section 1.1). + int64_t hop = 1; + for (int64_t ratio : upsampling_ratios) hop *= ratio; + return hop; +} + +int64_t MiniMaxMusic3Tensor::numel() const { + int64_t total = 1; + for (int64_t dim : shape) total *= dim; + return total; +} + +// --------------------------------------------------------------------------- +// RUNTIME dtype +// --------------------------------------------------------------------------- + +MiniMaxMusic3RuntimeDtypes MiniMaxMusic3OnDiskDtypes() { + // Measured from the shipped headers; pinned in tests by + // minimax_music3_manifest.inc, which the gate cross-checks against this. + MiniMaxMusic3RuntimeDtypes out; + out.language_model = kBf16; + out.rvq_depth_decoder = kBf16; + out.condition_encoder = kF32; + out.transformer = kF32; + out.vocoder = kF32; + return out; +} + +MiniMaxMusic3RuntimeDtypes MiniMaxMusic3ResolveRuntimeDtypes(MiniMaxMusic3DtypePolicy policy) { + if (policy == MiniMaxMusic3DtypePolicy::kAsStored) { + // Deliberately returned UNREPAIRED. It is not runnable, and the point of + // keeping it is that the failure stays reproducible against the oracle's + // own `--dtype-policy on-disk`; silently promoting the condition encoder + // to bf16 here would hide exactly the finding this arm exists to preserve. + return MiniMaxMusic3OnDiskDtypes(); + } + // kBf16ArFp32Acoustic: the gated configuration. The AR half runs at the + // language model's dtype because nothing casts between its stages; the + // acoustic half is fp32, which is the converter's default for the DiT and the + // vocoder and what SGLang-Omni states it runs. + MiniMaxMusic3RuntimeDtypes out; + out.language_model = kBf16; + out.rvq_depth_decoder = kBf16; + out.condition_encoder = kBf16; + out.transformer = kF32; + out.vocoder = kF32; + return out; +} + +bool MiniMaxMusic3RuntimeDtypesAreRunnable(const MiniMaxMusic3RuntimeDtypes& dtypes) { + return dtypes.language_model == dtypes.rvq_depth_decoder && + dtypes.language_model == dtypes.condition_encoder; +} + +void MiniMaxMusic3CheckRuntimeDtypes(const MiniMaxMusic3RuntimeDtypes& dtypes) { + if (MiniMaxMusic3RuntimeDtypesAreRunnable(dtypes)) return; + // Name all three and their dtypes. Upstream's own failure names a BIAS dtype + // from inside a Conv1d and never says which component disagreed with which, + // which is the whole reason this refusal exists here instead. + throw std::runtime_error( + std::string("minimax_music3: this dtype configuration cannot run. The autoregressive half " + "must share ONE dtype -- language_model=") + + dtypes.language_model + ", rvq_depth_decoder=" + dtypes.rvq_depth_decoder + + ", condition_encoder=" + dtypes.condition_encoder + + " -- because upstream casts only on the way OUT of the condition encoder " + "(denoise.py:83) and into the vocoder (decoders.py:84), never on the way in: " + "denoise.py:82 hands the language model's hidden states over with a device move and no " + "dtype move. A mismatch therefore fails inside " + "condition_embedder_minimax_music3.py:64 as \"Input type (c10::BFloat16) and bias type " + "(float) should be the same\". The gated configuration is bf16 AR half with an fp32 " + "acoustic half (MiniMaxMusic3DtypePolicy::kBf16ArFp32Acoustic). NOTE: the checkpoint's " + "ON-DISK dtypes are NOT this set and are not runnable as stored -- " + "MiniMaxMusic3DtypePolicy::kAsStored reproduces that failure on purpose."); +} + +// --------------------------------------------------------------------------- +// Which packaging is on disk +// --------------------------------------------------------------------------- + +bool MiniMaxMusic3IsNativeArm(const std::string& root) { + std::error_code ec; + const fs::path base(root); + return fs::exists(base / kMusic3NativeDitFile, ec) || + fs::exists(base / kMusic3NativeVaeFile, ec) || + fs::is_directory(base / kMusic3NativeQwenDir, ec); +} + +MiniMaxMusic3Paths MiniMaxMusic3ResolveCheckpoint(const std::string& root) { + std::error_code ec; + const fs::path base(root); + if (!fs::is_directory(base, ec)) { + throw std::runtime_error("minimax_music3: " + root + " is not a directory"); + } + + // The five weight-bearing components plus the two config-only ones. Their + // absence is what the refusals below enumerate. + const std::vector weight_components{ + "transformer", "condition_encoder", "rvq_depth_decoder", "vocoder", "language_model"}; + std::vector missing; + for (const std::string& component : weight_components) { + if (!fs::is_directory(base / component, ec)) missing.push_back(component); + } + if (!fs::is_directory(base / "scheduler", ec)) missing.push_back("scheduler"); + if (!fs::is_directory(base / "tokenizer", ec)) missing.push_back("tokenizer"); + if (!fs::exists(base / "modular_model_index.json", ec)) { + missing.push_back("modular_model_index.json"); + } + + if (MiniMaxMusic3IsNativeArm(root)) { + // THE REFUSAL THAT MATTERS. A native tree holds every weight this port + // needs, in a layout nothing here reads, so it is the one input that would + // otherwise LOOK loadable. Name the arm, name its markers, name what the + // diffusers arm wants, and say which one is supported -- spec section 2. + std::vector markers; + if (fs::exists(base / kMusic3NativeDitFile, ec)) markers.push_back(kMusic3NativeDitFile); + if (fs::exists(base / kMusic3NativeVaeFile, ec)) markers.push_back(kMusic3NativeVaeFile); + if (fs::is_directory(base / kMusic3NativeQwenDir, ec)) { + markers.push_back(std::string(kMusic3NativeQwenDir) + "/"); + } + throw std::runtime_error( + "minimax_music3: " + root + " is the NATIVE arm of MiniMaxAI/MiniMax-Music3 (found " + + JoinNames(markers, ", ") + "; the full native layout is " + kMusic3NativeQwenDir + "/" + + kMusic3NativeQwenDir + "/, " + kMusic3NativeDitFile + " and " + kMusic3NativeVaeFile + + "). Only the diffusers arm is supported: it wants the components transformer, " + "condition_encoder, rvq_depth_decoder, vocoder, language_model, scheduler and tokenizer " + "beside a modular_model_index.json, and this tree is missing " + + (missing.empty() ? std::string("none of them") : JoinNames(missing, ", ")) + + ". Convert it with diffusers' scripts/convert_minimax_music3_to_diffusers.py, or point " + "this loader at the diffusers-arm checkpoint. Loading the native arm directly is owed and " + "recorded in .agents/specs/minimax-music3.md section 2 -- it is refused here rather than " + "mis-loaded."); + } + + if (!missing.empty()) { + throw std::runtime_error( + "minimax_music3: " + root + + " is not a diffusers-arm MiniMax-Music3 checkpoint; it is missing " + + JoinNames(missing, ", ") + + " (expected a modular_model_index.json beside the component directories)."); + } + + MiniMaxMusic3Paths paths; + paths.root = root; + paths.modular_index = (base / "modular_model_index.json").string(); + paths.transformer_dir = (base / "transformer").string(); + paths.condition_encoder_dir = (base / "condition_encoder").string(); + paths.rvq_depth_decoder_dir = (base / "rvq_depth_decoder").string(); + paths.vocoder_dir = (base / "vocoder").string(); + paths.language_model_dir = (base / "language_model").string(); + paths.scheduler_config = (base / "scheduler" / "scheduler_config.json").string(); + paths.tokenizer_dir = (base / "tokenizer").string(); + paths.transformer_shards = + ShardsFromIndex(paths.transformer_dir, "diffusion_pytorch_model.safetensors.index.json", + "diffusion_pytorch_model.safetensors"); + paths.language_model_shards = + ShardsFromIndex(paths.language_model_dir, "model.safetensors.index.json", + "model.safetensors"); + return paths; +} + +// --------------------------------------------------------------------------- +// Configs +// --------------------------------------------------------------------------- + +MiniMaxMusic3Config MiniMaxMusic3LoadConfig(const MiniMaxMusic3Paths& paths) { + MiniMaxMusic3Config out; + + { + const std::string source = paths.transformer_dir + "/config.json"; + const nlohmann::json json = ReadJson(source); + RequireClassName(json, "MiniMaxMusic3Transformer1DModel", source); + MiniMaxMusic3TransformerConfig& config = out.transformer; + config.in_channels = RequireInt(json, "in_channels", source); + config.condition_dim = RequireInt(json, "condition_dim", source); + config.num_layers = RequireInt(json, "num_layers", source); + config.num_attention_heads = RequireInt(json, "num_attention_heads", source); + config.attention_head_dim = RequireInt(json, "attention_head_dim", source); + config.ff_inner_dim = RequireInt(json, "ff_inner_dim", source); + // `rotary_dim` moves every RoPE angle and no tensor shape, so it is READ + // rather than defaulted: a wrong value renders a plausible, wrong song and + // no shape gate can see it (transformer_minimax_music3.py:42-56). + config.rotary_dim = RequireInt(json, "rotary_dim", source); + config.fourier_embedding_dim = RequireInt(json, "fourier_embedding_dim", source); + } + { + const std::string source = paths.condition_encoder_dir + "/config.json"; + const nlohmann::json json = ReadJson(source); + RequireClassName(json, "MiniMaxMusic3ConditionEncoder", source); + MiniMaxMusic3ConditionEncoderConfig& config = out.condition_encoder; + config.condition_hidden_dim = RequireInt(json, "condition_hidden_dim", source); + config.num_condition_layers = RequireInt(json, "num_condition_layers", source); + config.out_dim = RequireInt(json, "out_dim", source); + // The four rate keys set the LM-frame -> latent-frame resample ratio + // (condition_embedder_minimax_music3.py:64-73). Shape-invisible, so read. + config.input_sampling_rate = RequireInt(json, "input_sampling_rate", source); + config.input_hop_length = RequireInt(json, "input_hop_length", source); + config.output_sampling_rate = RequireInt(json, "output_sampling_rate", source); + config.output_hop_length = RequireInt(json, "output_hop_length", source); + } + { + const std::string source = paths.rvq_depth_decoder_dir + "/config.json"; + const nlohmann::json json = ReadJson(source); + RequireClassName(json, "MiniMaxMusic3RVQDepthDecoder", source); + MiniMaxMusic3RvqDepthDecoderConfig& config = out.rvq_depth_decoder; + config.hidden_size = RequireInt(json, "hidden_size", source); + config.num_layers = RequireInt(json, "num_layers", source); + config.num_attention_heads = RequireInt(json, "num_attention_heads", source); + config.intermediate_size = RequireInt(json, "intermediate_size", source); + config.audio_vocab_size = RequireInt(json, "audio_vocab_size", source); + config.num_codebooks = RequireInt(json, "num_codebooks", source); + config.max_position_embeddings = RequireInt(json, "max_position_embeddings", source); + } + { + const std::string source = paths.vocoder_dir + "/config.json"; + const nlohmann::json json = ReadJson(source); + RequireClassName(json, "MiniMaxMusic3Vocoder", source); + MiniMaxMusic3VocoderConfig& config = out.vocoder; + config.latent_channels = RequireInt(json, "latent_channels", source); + config.decoder_input_dim = RequireInt(json, "decoder_input_dim", source); + config.decoder_hidden_dim = RequireInt(json, "decoder_hidden_dim", source); + config.sampling_rate = RequireInt(json, "sampling_rate", source); + const auto ratios = json.find("upsampling_ratios"); + if (ratios == json.end() || !ratios->is_array() || ratios->empty()) { + throw std::runtime_error("minimax_music3: " + source + + " has no non-empty \"upsampling_ratios\" array"); + } + config.upsampling_ratios.clear(); + for (const nlohmann::json& ratio : *ratios) { + if (!ratio.is_number_integer()) { + throw std::runtime_error("minimax_music3: " + source + + " has a non-integer entry in \"upsampling_ratios\""); + } + config.upsampling_ratios.push_back(ratio.get()); + } + } + { + const std::string source = paths.language_model_dir + "/config.json"; + const nlohmann::json json = ReadJson(source); + RequireClassName(json, "Qwen3ForCausalLM", source); + MiniMaxMusic3LanguageModelConfig& config = out.language_model; + config.hidden_size = RequireInt(json, "hidden_size", source); + config.intermediate_size = RequireInt(json, "intermediate_size", source); + config.num_hidden_layers = RequireInt(json, "num_hidden_layers", source); + config.num_attention_heads = RequireInt(json, "num_attention_heads", source); + config.num_key_value_heads = RequireInt(json, "num_key_value_heads", source); + config.head_dim = RequireInt(json, "head_dim", source); + config.vocab_size = RequireInt(json, "vocab_size", source); + config.max_position_embeddings = RequireInt(json, "max_position_embeddings", source); + config.rms_norm_eps = RequireNumber(json, "rms_norm_eps", source); + // `tie_word_embeddings` is TRI-STATE in the wild and decides whether + // `lm_head.weight` exists at all. The released config states it, so it is + // required rather than defaulted (.agents/porting-a-model.md section 1). + config.tie_word_embeddings = RequireBool(json, "tie_word_embeddings", source); + // transformers 5.x nests rope under `rope_parameters`; earlier layouts put + // `rope_theta` at the top level. Both are normalized, and a config with + // NEITHER is refused rather than silently given 10000.0 -- which would move + // every position embedding in a 10240-token context. + const auto nested = json.find("rope_parameters"); + if (nested != json.end() && nested->is_object() && nested->contains("rope_theta")) { + config.rope_theta = RequireNumber(*nested, "rope_theta", source + " rope_parameters"); + } else { + config.rope_theta = RequireNumber(json, "rope_theta", source); + } + } + { + const std::string source = paths.scheduler_config; + const nlohmann::json json = ReadJson(source); + RequireClassName(json, "FlowMatchEulerDiscreteScheduler", source); + MiniMaxMusic3SchedulerConfig& config = out.scheduler; + config.num_train_timesteps = RequireInt(json, "num_train_timesteps", source); + config.shift = RequireNumber(json, "shift", source); + config.invert_sigmas = RequireBool(json, "invert_sigmas", source); + config.use_dynamic_shifting = RequireBool(json, "use_dynamic_shifting", source); + const auto shift_type = json.find("time_shift_type"); + if (shift_type == json.end() || !shift_type->is_string()) { + throw std::runtime_error("minimax_music3: " + source + " has no string \"time_shift_type\""); + } + config.time_shift_type = shift_type->get(); + } + return out; +} + +// --------------------------------------------------------------------------- +// Enumeration — upstream's `__init__`, module for module +// --------------------------------------------------------------------------- + +std::vector EnumerateMiniMaxMusic3TransformerTensors( + const MiniMaxMusic3TransformerConfig& config) { + const int64_t inner = config.inner_dim(); + const int64_t concat = config.concat_channels(); + // transformer_minimax_music3.py:119-122 — the attention's own inner width. + const int64_t attn_inner = config.num_attention_heads * config.attention_head_dim; + std::vector out; + + // :179 MiniMaxMusic3FourierEmbedding(fourier_embedding_dim) -> :35 + // `nn.Parameter(torch.randn(embedding_dim // 2, 1))`. + out.push_back({"time_proj.weight", kF32, {config.fourier_embedding_dim / 2, 1}}); + // :180 TimestepEmbedding(fourier_embedding_dim, inner_dim), both linears biased. + out.push_back({"time_embed.linear_1.weight", kF32, {inner, config.fourier_embedding_dim}}); + out.push_back({"time_embed.linear_1.bias", kF32, {inner}}); + out.push_back({"time_embed.linear_2.weight", kF32, {inner, inner}}); + out.push_back({"time_embed.linear_2.bias", kF32, {inner}}); + // :182 nn.Conv1d(concat_channels, concat_channels, 1, bias=False). + out.push_back({"preprocess_conv.weight", kF32, {concat, concat, 1}}); + // :183 nn.Linear(concat_channels, inner_dim, bias=False). + out.push_back({"proj_in.weight", kF32, {inner, concat}}); + + for (int64_t layer = 0; layer < config.num_layers; ++layer) { + const std::string prefix = "transformer_blocks." + std::to_string(layer) + "."; + // :134 nn.LayerNorm(dim) -- weight AND bias, unlike the RMSNorms elsewhere + // in this model. A LayerNorm read as an RMSNorm silently drops the bias. + out.push_back({prefix + "norm1.weight", kF32, {inner}}); + out.push_back({prefix + "norm1.bias", kF32, {inner}}); + // :119-122 to_q/to_k/to_v, and to_out as a ModuleList whose [0] is the + // linear and whose [1] is a Dropout -- hence the `.0.` in the NAME. + out.push_back({prefix + "attn.to_q.weight", kF32, {attn_inner, inner}}); + out.push_back({prefix + "attn.to_k.weight", kF32, {attn_inner, inner}}); + out.push_back({prefix + "attn.to_v.weight", kF32, {attn_inner, inner}}); + out.push_back({prefix + "attn.to_out.0.weight", kF32, {inner, attn_inner}}); + out.push_back({prefix + "norm2.weight", kF32, {inner}}); + out.push_back({prefix + "norm2.bias", kF32, {inner}}); + // :137 nn.Linear(dim, ff_inner_dim * 2) -- the GATED feed-forward, so the + // stored width is TWICE ff_inner_dim and :141 chunks it in two. + out.push_back({prefix + "ff_in.weight", kF32, {config.ff_inner_dim * 2, inner}}); + out.push_back({prefix + "ff_in.bias", kF32, {config.ff_inner_dim * 2}}); + // :138 nn.Linear(ff_inner_dim, dim). + out.push_back({prefix + "ff_out.weight", kF32, {inner, config.ff_inner_dim}}); + out.push_back({prefix + "ff_out.bias", kF32, {inner}}); + } + + // :191 nn.Linear(inner_dim, in_channels, bias=False). + out.push_back({"proj_out.weight", kF32, {config.in_channels, inner}}); + // :192 nn.Conv1d(in_channels, in_channels, 1, bias=False). + out.push_back({"postprocess_conv.weight", kF32, {config.in_channels, config.in_channels, 1}}); + + SortByName(out); + return out; +} + +std::vector EnumerateMiniMaxMusic3ConditionEncoderTensors( + const MiniMaxMusic3ConditionEncoderConfig& config) { + // condition_embedder_minimax_music3.py:44-46. FOUR tensors: the module is a + // learned weighted MIX over `num_condition_layers` LM hidden layers plus one + // Conv1d, not an encoder tower. + std::vector out{ + {"layer_weight_logits", kF32, {config.num_condition_layers}}, + {"layer_scale", kF32, {1}}, + {"proj.weight", kF32, {config.out_dim, config.condition_hidden_dim, 3}}, + {"proj.bias", kF32, {config.out_dim}}, + }; + SortByName(out); + return out; +} + +std::vector EnumerateMiniMaxMusic3RvqDepthDecoderTensors( + const MiniMaxMusic3RvqDepthDecoderConfig& config) { + const int64_t hidden = config.hidden_size; + std::vector out; + // :113 nn.Embedding(audio_vocab_size * (num_codebooks - 1), hidden_size) -- + // the table covers the RESIDUAL codebooks only, so a reader that used + // `num_codebooks` would allocate one codebook too many and mis-index every + // frame after the first. + out.push_back({"audio_embeddings.weight", kBf16, + {config.audio_vocab_size * config.residual_codebooks(), hidden}}); + out.push_back({"projection.weight", kBf16, {hidden, hidden}}); // :114 + out.push_back({"pos_embedding.weight", kBf16, {config.max_position_embeddings, hidden}}); // :115 + + for (int64_t layer = 0; layer < config.num_layers; ++layer) { + const std::string prefix = "layers." + std::to_string(layer) + "."; + // :78-83 -- RMSNorm (weight only, no bias) and four bias-free projections. + out.push_back({prefix + "input_layernorm.weight", kBf16, {hidden}}); + out.push_back({prefix + "attn.to_q.weight", kBf16, {hidden, hidden}}); + out.push_back({prefix + "attn.to_k.weight", kBf16, {hidden, hidden}}); + out.push_back({prefix + "attn.to_v.weight", kBf16, {hidden, hidden}}); + out.push_back({prefix + "attn.to_out.weight", kBf16, {hidden, hidden}}); + out.push_back({prefix + "post_attention_layernorm.weight", kBf16, {hidden}}); + out.push_back({prefix + "gate_proj.weight", kBf16, {config.intermediate_size, hidden}}); + out.push_back({prefix + "up_proj.weight", kBf16, {config.intermediate_size, hidden}}); + out.push_back({prefix + "down_proj.weight", kBf16, {hidden, config.intermediate_size}}); + } + + out.push_back({"norm.weight", kBf16, {hidden}}); // :122 + // :123-124 -- one head per RESIDUAL codebook (c1..c7), not per codebook. + for (int64_t head = 0; head < config.residual_codebooks(); ++head) { + out.push_back({"audio_heads." + std::to_string(head) + ".weight", kBf16, + {config.audio_vocab_size, hidden}}); + } + SortByName(out); + return out; +} + +std::vector MiniMaxMusic3WeightNormedModules( + const MiniMaxMusic3VocoderConfig& config) { + std::vector out; + for (const WeightNormedConv& conv : VocoderWeightNormedConvs(config)) { + out.push_back(conv.module); + } + return out; +} + +std::vector EnumerateMiniMaxMusic3VocoderTensors( + const MiniMaxMusic3VocoderConfig& config) { + std::vector out; + // :88 nn.Conv1d(latent_channels // 2, decoder_input_dim, 1) -- the ONE + // convolution upstream does NOT weight-norm, and the one whose input width is + // the FOLDED stream (64), not the 128 latent channels. + out.push_back( + {"dec_in_proj.weight", kF32, {config.decoder_input_dim, config.stream_channels(), 1}}); + out.push_back({"dec_in_proj.bias", kF32, {config.decoder_input_dim}}); + + for (const WeightNormedConv& conv : VocoderWeightNormedConvs(config)) { + out.push_back({conv.module + ".weight_g", kF32, {conv.v_shape[0], 1, 1}}); + out.push_back({conv.module + ".weight_v", kF32, conv.v_shape}); + out.push_back({conv.module + ".bias", kF32, {conv.bias}}); + } + + // The snakes. :28 nn.Parameter(torch.ones(1, channels, 1)) throughout. + int64_t last_output = config.decoder_hidden_dim; + for (size_t index = 0; index < config.upsampling_ratios.size(); ++index) { + const int64_t input_dim = config.decoder_hidden_dim / (int64_t(1) << index); + const int64_t output_dim = config.decoder_hidden_dim / (int64_t(1) << (index + 1)); + last_output = output_dim; + const std::string block = "blocks." + std::to_string(index) + "."; + out.push_back({block + "snake1.alpha", kF32, {1, input_dim, 1}}); // :54 + for (int unit = 1; unit <= 3; ++unit) { + const std::string prefix = block + "res_unit" + std::to_string(unit) + "."; + out.push_back({prefix + "snake1.alpha", kF32, {1, output_dim, 1}}); // :41 + out.push_back({prefix + "snake2.alpha", kF32, {1, output_dim, 1}}); // :43 + } + } + out.push_back({"snake_out.alpha", kF32, {1, last_output, 1}}); // :97 + + SortByName(out); + return out; +} + +std::vector EnumerateMiniMaxMusic3LanguageModelTensors( + const MiniMaxMusic3LanguageModelConfig& config) { + const int64_t hidden = config.hidden_size; + const int64_t q_dim = config.num_attention_heads * config.head_dim; + const int64_t kv_dim = config.num_key_value_heads * config.head_dim; + std::vector out; + + out.push_back({"model.embed_tokens.weight", kBf16, {config.vocab_size, hidden}}); + for (int64_t layer = 0; layer < config.num_hidden_layers; ++layer) { + const std::string prefix = "model.layers." + std::to_string(layer) + "."; + out.push_back({prefix + "input_layernorm.weight", kBf16, {hidden}}); + out.push_back({prefix + "post_attention_layernorm.weight", kBf16, {hidden}}); + out.push_back({prefix + "self_attn.q_proj.weight", kBf16, {q_dim, hidden}}); + out.push_back({prefix + "self_attn.k_proj.weight", kBf16, {kv_dim, hidden}}); + out.push_back({prefix + "self_attn.v_proj.weight", kBf16, {kv_dim, hidden}}); + out.push_back({prefix + "self_attn.o_proj.weight", kBf16, {hidden, q_dim}}); + // Qwen3's PER-HEAD RMSNorm on q and k: [head_dim], not [hidden]. A reader + // that assumed [hidden] would refuse a correct checkpoint, and one that + // skipped them would drop a normalization the argmax often survives. + out.push_back({prefix + "self_attn.q_norm.weight", kBf16, {config.head_dim}}); + out.push_back({prefix + "self_attn.k_norm.weight", kBf16, {config.head_dim}}); + out.push_back({prefix + "mlp.gate_proj.weight", kBf16, {config.intermediate_size, hidden}}); + out.push_back({prefix + "mlp.up_proj.weight", kBf16, {config.intermediate_size, hidden}}); + out.push_back({prefix + "mlp.down_proj.weight", kBf16, {hidden, config.intermediate_size}}); + } + out.push_back({"model.norm.weight", kBf16, {hidden}}); + // The released config sets `tie_word_embeddings: false`, so `lm_head.weight` + // is a separate 200000 x 4096 tensor -- 1.6 GB of it. Tied, it would not be + // in the file at all, which is why the flag decides the ENUMERATION and not + // just a pointer. + if (!config.tie_word_embeddings) { + out.push_back({"lm_head.weight", kBf16, {config.vocab_size, hidden}}); + } + SortByName(out); + return out; +} + +std::map> EnumerateMiniMaxMusic3Tensors( + const MiniMaxMusic3Config& config) { + return { + {"transformer", EnumerateMiniMaxMusic3TransformerTensors(config.transformer)}, + {"condition_encoder", + EnumerateMiniMaxMusic3ConditionEncoderTensors(config.condition_encoder)}, + {"rvq_depth_decoder", + EnumerateMiniMaxMusic3RvqDepthDecoderTensors(config.rvq_depth_decoder)}, + {"vocoder", EnumerateMiniMaxMusic3VocoderTensors(config.vocoder)}, + {"language_model", EnumerateMiniMaxMusic3LanguageModelTensors(config.language_model)}, + }; +} + +// --------------------------------------------------------------------------- +// Accounting +// --------------------------------------------------------------------------- + +MiniMaxMusic3AccountReport MiniMaxMusic3AccountTensors( + const std::string& component, const std::vector& required, + const std::vector& present) { + MiniMaxMusic3AccountReport report; + report.required = static_cast(required.size()); + report.present = static_cast(present.size()); + + std::map by_name; + for (const MiniMaxMusic3ManifestEntry& entry : present) { + const bool inserted = by_name.emplace(entry.name, &entry).second; + if (!inserted) { + throw std::runtime_error("minimax_music3: " + component + ": tensor " + entry.name + + " appears more than once across the component's shards"); + } + } + + std::set accounted; + for (const MiniMaxMusic3TensorSpec& spec : required) { + const auto it = by_name.find(spec.name); + if (it == by_name.end()) { + throw std::runtime_error("minimax_music3: " + component + ": required tensor " + spec.name + + " " + ShapeToString(spec.shape) + " " + spec.dtype + + " is MISSING from the checkpoint"); + } + const MiniMaxMusic3ManifestEntry& entry = *it->second; + if (entry.shape != spec.shape) { + throw std::runtime_error( + "minimax_music3: " + component + ": tensor " + spec.name + " has shape " + + ShapeToString(entry.shape) + " but the config implies " + ShapeToString(spec.shape) + + "; refusing rather than binding a different model"); + } + if (entry.dtype != spec.dtype) { + // A dtype that is too WIDE is numerically correct, so no token or golden + // gate can see it (AGENTS.md, .agents/porting.md "Mirror the memory + // format"). It is therefore refused structurally, here. + throw std::runtime_error( + "minimax_music3: " + component + ": tensor " + spec.name + " has dtype " + entry.dtype + + " but this component is " + spec.dtype + + " (.agents/specs/minimax-music3.md section 2.1, " + "convert_minimax_music3_to_diffusers.py:208-211,214,267); a narrowing or widening is a " + "MEASURED change, never a default"); + } + accounted.insert(spec.name); + ++report.matched; + } + + for (const MiniMaxMusic3ManifestEntry& entry : present) { + if (accounted.count(entry.name) == 0) { + throw std::runtime_error("minimax_music3: " + component + ": tensor " + entry.name + " " + + ShapeToString(entry.shape) + + " is present in the checkpoint and UNACCOUNTED for by this port"); + } + } + + VT_CHECK(report.matched == report.required && report.present == report.required, + "minimax_music3: account returned without a total match"); + return report; +} + +std::vector MiniMaxMusic3ReadManifest(const SafetensorsFile& file) { + std::vector out; + for (const std::string& name : file.Names()) { + const StTensor& tensor = file.Get(name); + out.push_back({name, tensor.dtype, tensor.shape}); + } + return out; +} + +MiniMaxMusic3AccountReport MiniMaxMusic3AccountFile( + const std::string& component, const std::vector& required, + const SafetensorsFile& file) { + return MiniMaxMusic3AccountTensors(component, required, MiniMaxMusic3ReadManifest(file)); +} + +// --------------------------------------------------------------------------- +// Materialization +// --------------------------------------------------------------------------- + +MiniMaxMusic3ComponentWeights MiniMaxMusic3LoadComponent( + const std::string& component, const std::vector& required, + const SafetensorsFile& file) { + // Account FIRST. Every enumerated tensor exists at its enumerated shape and + // dtype before a byte is copied, so a missing tensor can never read as zeros. + MiniMaxMusic3AccountFile(component, required, file); + + MiniMaxMusic3ComponentWeights out; + out.component = component; + for (const MiniMaxMusic3TensorSpec& spec : required) { + const StTensor& source = file.Get(spec.name); + MiniMaxMusic3Tensor tensor; + tensor.dtype = source.dtype; // preserved; see spec section 2.1 + tensor.shape = spec.shape; + const size_t width = (spec.dtype == std::string(kF32)) ? 4 : 2; + const size_t expected = static_cast(tensor.numel()) * width; + if (source.nbytes != expected) { + throw std::runtime_error("minimax_music3: " + component + ": tensor " + spec.name + + " spans " + std::to_string(source.nbytes) + + " bytes but its shape " + ShapeToString(spec.shape) + + " and dtype " + spec.dtype + " imply " + std::to_string(expected)); + } + tensor.bytes.resize(source.nbytes); + std::memcpy(tensor.bytes.data(), source.data, source.nbytes); + MaybeReleaseSourcePages(source.data, source.nbytes); + out.tensors.emplace(spec.name, std::move(tensor)); + } + return out; +} + +MiniMaxMusic3VocoderWeights MiniMaxMusic3LoadVocoderWeights( + const MiniMaxMusic3VocoderConfig& config, const SafetensorsFile& file) { + const std::vector required = + EnumerateMiniMaxMusic3VocoderTensors(config); + MiniMaxMusic3AccountFile("vocoder", required, file); + + const std::vector convs = VocoderWeightNormedConvs(config); + MiniMaxMusic3VocoderWeights out; + std::set consumed; + + // Fold every weight-normed pair to ONE weight, so the decoder that consumes + // this never sees `_g` / `_v` and cannot read the direction as the weight. + for (const WeightNormedConv& conv : convs) { + const std::string g_name = conv.module + ".weight_g"; + const std::string v_name = conv.module + ".weight_v"; + const std::vector g = ReadF32("vocoder", g_name, file.Get(g_name)); + const std::vector v = ReadF32("vocoder", v_name, file.Get(v_name)); + // dim0 of `weight_v`: the OUTPUT channel for a Conv1d and the INPUT channel + // for the ConvTranspose1d, which is the axis torch reduces over either way. + const int64_t dim0 = conv.v_shape[0]; + out.tensors[conv.module + ".weight"] = vocoder1d::MaterializeWeightNorm(g, v, dim0); + out.shapes[conv.module + ".weight"] = conv.v_shape; + consumed.insert(g_name); + consumed.insert(v_name); + ++out.folded; + } + + // Everything else verbatim: the biases, the snake alphas, and the one + // un-weight-normed convolution. + for (const MiniMaxMusic3TensorSpec& spec : required) { + if (consumed.count(spec.name) != 0) continue; + out.tensors[spec.name] = ReadF32("vocoder", spec.name, file.Get(spec.name)); + out.shapes[spec.name] = spec.shape; + } + + VT_CHECK(out.folded == static_cast(convs.size()), + "minimax_music3 vocoder: folded fewer pairs than the enumeration declares"); + return out; +} + +} // namespace vllm diff --git a/src/vllm/model_executor/models/nemotron_h.cpp b/src/vllm/model_executor/models/nemotron_h.cpp new file mode 100644 index 000000000..6f94eeda4 --- /dev/null +++ b/src/vllm/model_executor/models/nemotron_h.cpp @@ -0,0 +1,838 @@ +// Nemotron-H W4: the hybrid forward. See nemotron_h_forward.h for the port +// anchors, the scope boundary and the no-RoPE finding. +// +// EVERY numeric step below is a landed `vt::` op — nothing here re-implements a +// kernel. The three Mamba2 primitives are #496's (`vt::Mamba2ChunkScan`, +// `vt::RmsNormGatedGroup`), the causal conv is the one the GDN hybrids already +// use (`vt::CausalConv1dFwd`), the non-gated expert is #517 W2's +// (`vt::MoeRelu2` + `vt::MoeCombine`'s `routed_scale`), and the router, the +// attention and the norms are the shared ops every other model calls. This file +// is the WIRING, which is exactly what W4 owns. +// +// HOST-QUEUE ONLY, deliberately. The forward asserts a CPU queue: it materializes +// fresh per-call conv/SSM state, splits the fused projections with host copies, +// and manages no paged cache. The device/paged runner path is W6 and is a +// separate entry point, exactly as Kimi-Linear kept `Forward` (host reference) +// and `ForwardDevice`/`ForwardPaged` apart. +#include "vllm/model_executor/models/nemotron_h_forward.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "vt/ops.h" +#include "vt/recipes.h" + +namespace vllm { +namespace { + +// VT_FUSED_CHAIN_ADOPT (default ON, consistent with the framework): route the +// residual add + RMSNorm preamble through the declared fusion recipe +// (`kFusedAddRmsNormStd`) via `vt::FusedChain` rather than hand-calling the +// residual overload of `vt::RmsNorm` — AGENTS.md, "Route model fusion through +// `vt::FusedChain`", and the seam `scripts/check-fusion-consistency.py` enforces. +// +// Behaviour-preserving BY CONSTRUCTION: the recipe encodes exactly this op order +// (`res += x; out = std-RMSNorm(res)`) and the default Tier-0 composite +// dispatches to the SAME `vt::RmsNorm(..., &residual)` primitive, so the fused +// path is bit-identical to the hand-call (tests/vt/test_ops_fused_chain.cpp). +// `VT_FUSED_CHAIN_ADOPT=0` restores the exact hand-call as a same-binary A/B, +// and the forward gate runs both arms. +// +// Defined file-locally rather than pulled from `dense_attn_block.h`: that header +// is the DEVICE seam (device pool, resident weights, paged KV), and this is the +// host reference forward. qwen3_5.cpp:1698 keeps its own reader for the same +// reason; this is a five-line env read, not a second numeric path. +bool FusedChainAdoptEnabled() { + static const bool on = [] { + const char* e = std::getenv("VT_FUSED_CHAIN_ADOPT"); + return e == nullptr || e[0] != '0'; + }(); + return on; +} + +using vt::DType; +using vt::Queue; +using vt::Tensor; + +// ─── dtype-generic host element access ────────────────────────────────────── +// The same LoadF32/StoreF32 idiom the CPU kernels use file-locally +// (src/vt/cpu/cpu_ops.cpp:31). Only f32 and bf16 are admitted: those are the two +// `IsOutFloat` dtypes every op below can store into, and admitting f16 here +// would create an arm no `vt::MoeRelu2` / `vt::MoeCombine` output accepts. +float LoadAt(const void* base, DType dt, int64_t i) { + if (dt == DType::kF32) return static_cast(base)[i]; + return vt::BF16ToF32(static_cast(base)[i]); +} + +void StoreAt(void* base, DType dt, int64_t i, float v) { + if (dt == DType::kF32) { + static_cast(base)[i] = v; + return; + } + static_cast(base)[i] = vt::F32ToBF16(v); +} + +void CheckActDType(DType dt) { + VT_CHECK(dt == DType::kF32 || dt == DType::kBF16, + "NemotronHForCausalLM forward: the model dtype must be bf16 (the " + "released checkpoint's) or f32 (the reference sweep arm); no other " + "activation dtype is reachable from the ops this forward composes"); +} + +// An owned host activation buffer in a declared dtype, plus its view. +struct Buf { + std::vector bytes; + DType dtype = DType::kF32; + std::vector shape; + + Buf() = default; + Buf(DType dt, std::vector s) : dtype(dt), shape(std::move(s)) { + int64_t n = 1; + for (int64_t d : shape) n *= d; + VT_CHECK(n >= 0, "NemotronH forward: negative buffer extent"); + bytes.assign(static_cast(n) * vt::SizeOf(dtype), 0); + } + int64_t Numel() const { + int64_t n = 1; + for (int64_t d : shape) n *= d; + return n; + } + // A contiguous view over the current bytes, at `as` (defaults to the buffer's + // own shape). A reshape is free because every Buf is contiguous. + Tensor t(vt::Device dev, const std::vector& as) const { + Tensor r; + r.data = const_cast(bytes.data()); + r.dtype = dtype; + r.device = dev; + r.rank = static_cast(as.size()); + VT_CHECK(r.rank >= 1 && r.rank <= vt::kMaxRank, + "NemotronH forward: buffer rank out of range"); + int64_t stride = 1; + int64_t n = 1; + for (int i = r.rank - 1; i >= 0; --i) { + r.shape[i] = as[static_cast(i)]; + r.stride[i] = stride; + stride *= as[static_cast(i)]; + n *= as[static_cast(i)]; + } + VT_CHECK(n == Numel(), "NemotronH forward: reshape changes the element count"); + return r; + } + Tensor t(vt::Device dev) const { return t(dev, shape); } + float Get(int64_t i) const { return LoadAt(bytes.data(), dtype, i); } + void Set(int64_t i, float v) { StoreAt(bytes.data(), dtype, i, v); } +}; + +Buf PackF32(const std::vector& v, DType dt, std::vector shape) { + Buf b(dt, std::move(shape)); + VT_CHECK(b.Numel() == static_cast(v.size()), + "NemotronH forward: packed buffer element count mismatch"); + for (size_t i = 0; i < v.size(); ++i) b.Set(static_cast(i), v[i]); + return b; +} + +std::vector UnpackF32(const Buf& b) { + std::vector out(static_cast(b.Numel())); + for (size_t i = 0; i < out.size(); ++i) out[i] = b.Get(static_cast(i)); + return out; +} + +Tensor I32(std::vector& v, vt::Device dev, std::vector shape) { + Tensor t; + t.data = v.data(); + t.dtype = DType::kI32; + 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; +} + +Tensor F32V(std::vector& v, vt::Device dev, std::vector shape) { + Tensor t; + t.data = v.data(); + t.dtype = DType::kF32; + 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; +} + +void RequireWeight(const NemotronHOwned& w, const char* what, DType want, + std::vector shape) { + VT_CHECK(!w.Empty(), std::string("NemotronHForCausalLM forward: weight '") + what + + "' is not materialized (the safetensors/quantized " + "weight load is owed; see " + ".agents/specs/nemotron-h-model.md §5b)"); + VT_CHECK(w.dtype == want, + std::string("NemotronHForCausalLM forward: weight '") + what + + "' has the wrong dtype for this arm"); + VT_CHECK(w.shape == shape, std::string("NemotronHForCausalLM forward: weight '") + + what + "' has the wrong shape"); +} + +// out[M,N] = a[M,K] @ b^T, b [N,K] — the torch-Linear orientation every weight +// above is stored in. +Buf Linear(Queue& q, const Buf& a, const NemotronHOwned& w, int64_t M, int64_t K, + int64_t N, const char* what) { + RequireWeight(w, what, a.dtype, {N, K}); + Buf out(a.dtype, {M, N}); + Tensor at = a.t(q.device, {M, K}); + Tensor wt = w.View(q.device); + Tensor ot = out.t(q.device); + vt::MatmulBT(q, ot, at, wt); + return out; +} + +// Copy a column range [c0, c0+width) out of `src [rows, src_cols]` into a fresh +// CONTIGUOUS buffer. `vt::Mamba2ChunkScan` validates every operand contiguous +// (ops.cpp CheckMamba2Operand), so the fused zxbcdt / xBC splits cannot be +// handed to it as the row-strided views `Tensor::Slice` produces — only +// `vt::CausalConv1dFwd` documents a padded-row `x`. +Buf SliceCols(const Buf& src, int64_t rows, int64_t src_cols, int64_t c0, int64_t width, + std::vector out_shape) { + Buf out(src.dtype, std::move(out_shape)); + VT_CHECK(out.Numel() == rows * width, "NemotronH forward: slice extent mismatch"); + const size_t esz = vt::SizeOf(src.dtype); + for (int64_t r = 0; r < rows; ++r) { + std::memcpy(out.bytes.data() + static_cast(r * width) * esz, + src.bytes.data() + static_cast(r * src_cols + c0) * esz, + static_cast(width) * esz); + } + return out; +} + +// The non-gated expert, on the seam spec §6a fixed: ONE projection, relu², one +// projection. `rows` tokens of `in [rows, H]` against expert weights. +Buf NonGatedExpert(Queue& q, const Buf& in, const NemotronHExpertWeights& w, int64_t rows, + int64_t H, int64_t I, const char* what) { + Buf h = Linear(q, in, w.up_proj, rows, H, I, what); + Buf a(in.dtype, {rows, I}); + Tensor ht = h.t(q.device); + Tensor at = a.t(q.device); + vt::MoeRelu2(q, at, ht); + return Linear(q, a, w.down_proj, rows, I, H, what); +} + +} // namespace + +// ─── NemotronHOwned ───────────────────────────────────────────────────────── + +int64_t NemotronHOwned::Numel() const { + int64_t n = 1; + for (int64_t d : shape) n *= d; + return n; +} + +vt::Tensor NemotronHOwned::View(vt::Device device) const { + Tensor t; + t.data = const_cast(bytes.data()); + t.dtype = dtype; + t.device = device; + t.rank = static_cast(shape.size()); + VT_CHECK(t.rank >= 1 && t.rank <= vt::kMaxRank, + "NemotronHOwned::View: rank out of range"); + 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; +} + +NemotronHOwned NemotronHOwned::FromF32(const std::vector& values, vt::DType dtype, + std::vector shape) { + CheckActDType(dtype); + NemotronHOwned w; + w.dtype = dtype; + w.shape = std::move(shape); + VT_CHECK(w.Numel() == static_cast(values.size()), + "NemotronHOwned::FromF32: element count does not match the shape"); + w.bytes.assign(values.size() * vt::SizeOf(dtype), 0); + for (size_t i = 0; i < values.size(); ++i) { + StoreAt(w.bytes.data(), dtype, static_cast(i), values[i]); + } + return w; +} + +// ─── (1) the Mamba2 mixer ─────────────────────────────────────────────────── +// +// mamba_mixer2.py:548-586 `MambaMixer2.forward`, with the conv+SSM body from +// `conv_ssm_forward` (:687-696 the split, :830-891 the prefill arm). +std::vector NemotronHMamba2Mixer(const NemotronHMambaWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue, + NemotronHMambaState* state) { + CheckActDType(act_dtype); + const int64_t T = num_tokens; + const int64_t H = params.hidden_size; + const int64_t I = params.mamba_intermediate_size(); // 4096 + const int64_t Cd = params.conv_dim(); // 6144 + const int64_t P = params.mamba_head_dim; // 64 + const int64_t Hh = params.mamba_num_heads; // 64 + const int64_t G = params.n_groups; // 8 + const int64_t N = params.ssm_state_size; // 128 + const int64_t K = params.conv_kernel; // 4 + const int64_t proj = params.in_proj_out_features(); // 10304 + VT_CHECK(T > 0, "NemotronH mamba mixer: empty token sequence"); + VT_CHECK(static_cast(hidden_normed.size()) == T * H, + "NemotronH mamba mixer: hidden size mismatch"); + VT_CHECK(Hh * P == I, "NemotronH mamba mixer: num_heads*head_dim != intermediate"); + VT_CHECK(I + 2 * G * N == Cd, "NemotronH mamba mixer: conv_dim mismatch"); + + const vt::Device dev = queue.device; + const Buf x = PackF32(hidden_normed, act_dtype, {T, H}); + + // 1. the fused zxbcdt projection (mamba_mixer2.py:550). + const Buf zxbcdt = Linear(queue, x, w.in_proj, T, H, proj, "mixer.in_proj"); + + // 2. split: z | xBC | dt (mamba_mixer2.py:692-696 reads xBC/dt off the tail, + // :583 reads the gate off the head). + Buf z = SliceCols(zxbcdt, T, proj, 0, I, {T, I}); + Buf xbc = SliceCols(zxbcdt, T, proj, I, Cd, {T, Cd}); + Buf dt = SliceCols(zxbcdt, T, proj, I + Cd, Hh, {T, Hh}); + + // 3. the causal depthwise conv with the silu activation + // (`activation=config.mamba_hidden_act` = "silu", mamba_mixer2.py:832-846). + VT_CHECK(params.mamba_hidden_act == "silu", + "NemotronHForCausalLM forward: only mamba_hidden_act=silu is ported " + "(the checkpoint ships silu); an other activation is refused rather " + "than silently substituted"); + RequireWeight(w.conv1d_weight, "mixer.conv1d.weight", act_dtype, {Cd, K}); + // conv_state is f32 BY OP CONTRACT (ops.cpp CheckConvCommon admits bf16 only + // where Backend::SupportsCompressedConvState()), and it is a TRANSIENT + // per-call buffer here, never the persistent cache — `MakeNemotronHKVCache` + // already declares the persistent conv page at the cache dtype and W6 owns + // driving it. + std::vector conv_state(static_cast(Cd * (K - 1)), 0.0f); + const bool carry_in = state != nullptr && state->has_initial; + if (carry_in) { + VT_CHECK(state->conv.size() == conv_state.size(), + "NemotronH mamba mixer: carried conv state has the wrong extent"); + conv_state = state->conv; + } + std::vector qsl = {0, static_cast(T)}; + std::vector has_init = {carry_in ? 1 : 0}; + Buf xbc_out(act_dtype, {T, Cd}); + { + Tensor xt = xbc.t(dev); + Tensor wt = w.conv1d_weight.View(dev); + Tensor st = F32V(conv_state, dev, {1, Cd, K - 1}); + Tensor ot = xbc_out.t(dev); + Tensor qt = I32(qsl, dev, {2}); + Tensor it = I32(has_init, dev, {1}); + vt::CausalConv1dArgs cargs; + cargs.silu_activation = true; + if (params.use_conv_bias) { + // The conv bias is a MODEL-DTYPE tensor (ColumnParallelLinear's bias), not + // one of the three f32 SSM parameters below. + RequireWeight(w.conv1d_bias, "mixer.conv1d.bias", act_dtype, {Cd}); + Tensor bt = w.conv1d_bias.View(dev); + vt::CausalConv1dFwd(queue, ot, xt, wt, &bt, st, qt, it, cargs); + } else { + vt::CausalConv1dFwd(queue, ot, xt, wt, nullptr, st, qt, it, cargs); + } + } + + // 4. split the conv output into x | B | C (mamba_mixer2.py:535-543). + Buf ssm_x = SliceCols(xbc_out, T, Cd, 0, I, {T, Hh, P}); + Buf ssm_b = SliceCols(xbc_out, T, Cd, I, G * N, {T, G, N}); + Buf ssm_c = SliceCols(xbc_out, T, Cd, I + G * N, G * N, {T, G, N}); + + // 5. the SSD scan. `A = -exp(A_log)` in f32 — upstream keeps it f32 whatever + // the model dtype (`self.A = -torch.exp(self.A_log.float())`) and + // vt::Mamba2ChunkScan validates A/D/dt_bias as f32. + RequireWeight(w.A_log, "mixer.A_log", DType::kF32, {Hh}); + RequireWeight(w.D, "mixer.D", DType::kF32, {Hh}); + RequireWeight(w.dt_bias, "mixer.dt_bias", DType::kF32, {Hh}); + std::vector A(static_cast(Hh)); + for (int64_t h = 0; h < Hh; ++h) { + A[static_cast(h)] = + -std::exp(LoadAt(w.A_log.bytes.data(), DType::kF32, h)); + } + // The SSM state dtype is resolved INDEPENDENTLY of every activation dtype + // above — `mamba_ssm_cache_dtype` is "float32" on this checkpoint while the + // tower is bf16 (nemotron_h.h NemotronHSsmCacheDType records why the shared + // qwen3_5 resolver is the wrong reader). Deriving it from act_dtype halves the + // recurrent state and is invisible to a token gate. + const DType ssm_dtype = NemotronHSsmCacheDType(params, act_dtype); + Buf final_states(ssm_dtype, {1, Hh, P, N}); + Buf initial_states(ssm_dtype, {1, Hh, P, N}); + if (carry_in) { + VT_CHECK(state->ssm.dtype == ssm_dtype && state->ssm.Numel() == Hh * P * N, + "NemotronH mamba mixer: carried SSM state has the wrong dtype or extent"); + std::memcpy(initial_states.bytes.data(), state->ssm.bytes.data(), + state->ssm.bytes.size()); + } + + // One sequence, chunked on the GLOBAL token position — the single-sequence + // case of `compute_varlen_chunk_metadata` (v1/attention/backends/mamba2_attn.py + // :22-88); starting at position 0, every logical chunk is a full physical one + // but the last. + const int64_t chunk = params.chunk_size; + std::vector cu_seqlens = {0, static_cast(T)}; + std::vector cu_chunk = {0}; + std::vector seq_idx; + for (int64_t pos = 0; pos < T; pos += chunk) { + cu_chunk.push_back(static_cast(std::min(pos + chunk, T))); + seq_idx.push_back(0); + } + std::vector last_chunk = {static_cast(seq_idx.size()) - 1}; + + Buf y(act_dtype, {T, Hh, P}); + { + Tensor outt = y.t(dev); + Tensor fst = final_states.t(dev); + Tensor xt = ssm_x.t(dev); + Tensor dtt = dt.t(dev); + Tensor At = F32V(A, dev, {Hh}); + Tensor Bt = ssm_b.t(dev); + Tensor Ct = ssm_c.t(dev); + Tensor Dt = w.D.View(dev); + Tensor dbt = w.dt_bias.View(dev); + Tensor cut = I32(cu_seqlens, dev, {2}); + Tensor cct = I32(cu_chunk, dev, {static_cast(cu_chunk.size())}); + Tensor lct = I32(last_chunk, dev, {1}); + Tensor sit = I32(seq_idx, dev, {static_cast(seq_idx.size())}); + vt::Mamba2Args args; + args.chunk_size = chunk; + // mamba_mixer2.py:888-889: dt_softplus=True, dt_limit=(0.0, +inf). `z` is + // NOT passed to the scan — upstream gates in the norm below (:583-585), and + // passing it here would apply silu(z) twice. + args.dt_softplus = true; + args.dt_min = 0.0f; + args.dt_max = std::numeric_limits::infinity(); + Tensor ist = initial_states.t(dev); + vt::Mamba2ChunkScan(queue, outt, fst, xt, dtt, At, Bt, Ct, &Dt, /*z=*/nullptr, &dbt, + carry_in ? &ist : nullptr, cut, cct, lct, sit, args); + } + if (state != nullptr) { + state->conv = conv_state; + state->ssm.dtype = ssm_dtype; + state->ssm.shape = {Hh, P, N}; + state->ssm.bytes = final_states.bytes; + state->has_initial = true; + } + + // 6. the silu-gated GROUP RMS norm (Mixer2RMSNormGated, mamba_mixer2.py:478-480, + // :583-585). n_groups is the mixer's, NOT 1. + RequireWeight(w.norm_weight, "mixer.norm.weight", act_dtype, {I}); + Buf normed(act_dtype, {T, I}); + { + Tensor ot = normed.t(dev); + Tensor xt = y.t(dev, {T, I}); + Tensor gt = z.t(dev); + Tensor wt = w.norm_weight.View(dev); + vt::RmsNormGatedGroupArgs args; + args.eps = static_cast(params.layer_norm_epsilon); + args.n_groups = G; + vt::RmsNormGatedGroup(queue, ot, xt, gt, &wt, args); + } + + // 7. out_proj (mamba_mixer2.py:586). + const Buf out = Linear(queue, normed, w.out_proj, T, I, H, "mixer.out_proj"); + return UnpackF32(out); +} + +// ─── (2) the GQA attention mixer ──────────────────────────────────────────── +// +// nemotron_h.py:473-486. NO RoPE — see kNemotronHAttentionHasNoRope. +std::vector NemotronHAttentionMixer(const NemotronHAttentionWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue) { + CheckActDType(act_dtype); + const int64_t T = num_tokens; + const int64_t H = params.hidden_size; + const int64_t Hq = params.num_attention_heads; + const int64_t Hkv = params.num_key_value_heads; + const int64_t Dh = params.head_dim; + VT_CHECK(T > 0, "NemotronH attention: empty token sequence"); + VT_CHECK(static_cast(hidden_normed.size()) == T * H, + "NemotronH attention: hidden size mismatch"); + VT_CHECK(!params.attention_bias, + "NemotronHForCausalLM forward: attention_bias is not ported (the " + "checkpoint has attention_bias=false and ships no q/k/v/o bias)"); + VT_CHECK(!params.sliding_window.has_value(), + "NemotronHForCausalLM forward: per-layer sliding_window is not ported " + "(this checkpoint ships sliding_window=null)"); + + const vt::Device dev = queue.device; + const Buf x = PackF32(hidden_normed, act_dtype, {T, H}); + const int64_t qdim = params.q_proj_out_features(); + const int64_t kvdim = params.kv_proj_out_features(); + + const Buf qb = Linear(queue, x, w.q_proj, T, H, qdim, "mixer.q_proj"); + const Buf kb = Linear(queue, x, w.k_proj, T, H, kvdim, "mixer.k_proj"); + const Buf vb = Linear(queue, x, w.v_proj, T, H, kvdim, "mixer.v_proj"); + + Buf attn(act_dtype, {T, Hq, Dh}); + { + Tensor ot = attn.t(dev); + Tensor qt = qb.t(dev, {T, Hq, Dh}); + Tensor kt = kb.t(dev, {T, Hkv, Dh}); + Tensor vt_ = vb.t(dev, {T, Hkv, Dh}); + vt::AttentionArgs args; + // `self.scaling = self.head_dim**-0.5` (nemotron_h.py:440). + args.scale = static_cast(1.0 / std::sqrt(static_cast(Dh))); + args.causal = true; + vt::Attention(queue, ot, qt, kt, vt_, args); + } + + const Buf out = Linear(queue, attn, w.o_proj, T, qdim, H, "mixer.o_proj"); + return UnpackF32(out); +} + +// ─── (3) the non-gated relu² MoE mixer ────────────────────────────────────── +// +// nemotron_h.py:126-256. Three things this block is the whole reason to gate: +// * the router runs in f32 (`GateLinear(out_dtype=torch.float32, +// force_fp32_compute=True)`, :150-156) — MIRRORED, not inherited; +// * `routed_scaling_factor` is applied to the OUTPUT +// (`apply_routed_scale_to_output=True`, :234) via MoeCombine's `routed_scale`, +// NOT folded into the router weights or the logits — the ROUTER's own factor +// is forced to 1.0 in exactly this case (layer.py:291-300); +// * the shared expert is added UNSCALED after the routed sum is scaled +// (moe_runner.py:402-406 then :722-725). +std::vector NemotronHMoeMixer(const NemotronHMoeWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue) { + CheckActDType(act_dtype); + const int64_t T = num_tokens; + const int64_t H = params.hidden_size; + const int64_t E = params.n_routed_experts; + const int64_t Kk = params.num_experts_per_tok; + const int64_t I = params.moe_intermediate_size; + VT_CHECK(T > 0, "NemotronH moe: empty token sequence"); + VT_CHECK(static_cast(hidden_normed.size()) == T * H, + "NemotronH moe: hidden size mismatch"); + VT_CHECK(!params.moe_latent_size.has_value(), + "NemotronHForCausalLM forward: moe_latent_size is out of scope " + "(fc1_latent_proj/fc2_latent_proj, spec §0); it is null in the " + "released checkpoint"); + VT_CHECK(static_cast(w.experts.size()) == E, + "NemotronH moe: expert count does not match n_routed_experts"); + + const vt::Device dev = queue.device; + + // --- router. f32 END TO END: the logits buffer, the GEMM operands and the + // bias. This is upstream's own polarity (`force_fp32_compute=True`), which is + // why an f32 buffer appears on a bf16 model path here and nowhere else in this + // file except the SSM state and the transient conv state. + RequireWeight(w.gate, "mixer.gate.weight", DType::kF32, {E, H}); + RequireWeight(w.e_score_correction_bias, "mixer.gate.e_score_correction_bias", + DType::kF32, {E}); + std::vector logits(static_cast(T * E), 0.0f); + { + std::vector hf32 = hidden_normed; + // The activation reaching the router is the model-dtype one, widened — not a + // separately-computed f32 activation. Round-tripping it through act_dtype + // first keeps that true when act_dtype is bf16. + if (act_dtype != DType::kF32) { + const Buf rounded = PackF32(hidden_normed, act_dtype, {T, H}); + hf32 = UnpackF32(rounded); + } + Tensor at = F32V(hf32, dev, {T, H}); + Tensor wt = w.gate.View(dev); + Tensor ot = F32V(logits, dev, {T, E}); + vt::MatmulBT(queue, ot, at, wt); + } + + std::vector topk_w(static_cast(T * Kk), 0.0f); + std::vector topk_id(static_cast(T * Kk), -1); + { + vt::MoeRouterTopKArgs args; + args.top_k = static_cast(Kk); + args.renormalize = params.norm_topk_prob; + args.scoring_func = vt::MoeScoringFunc::kSigmoid; // nemotron_h.py:225 + args.num_expert_group = static_cast(params.n_group); + args.topk_group = static_cast(params.topk_group); + // NOT params.routed_scaling_factor. layer.py:291-300 forces the router's + // factor to 1.0 whenever apply_routed_scale_to_output is set, which it is. + args.routed_scaling_factor = 1.0f; + Tensor lt = F32V(logits, dev, {T, E}); + Tensor wt = F32V(topk_w, dev, {T, Kk}); + Tensor it = I32(topk_id, dev, {T, Kk}); + Tensor bt = w.e_score_correction_bias.View(dev); + vt::MoeRouterTopK(queue, wt, it, lt, args, &bt); + } + + // --- routed experts, one (token, slot) pair at a time. The grouped-GEMM arms + // (kMoeGroupedGemmBf16 / kMoeGroupedGemmNvfp4Marlin) are CUDA-only + // registrations, so the host reference takes the per-expert MatmulBT loop the + // CPU MoE path already uses for every other arch (deepseek_v2.cpp's CPU + // fallback); the composition is identical and W6 selects the grouped arm. + const Buf x = PackF32(hidden_normed, act_dtype, {T, H}); + Buf expert_out(act_dtype, {T, Kk, H}); + const size_t esz = vt::SizeOf(act_dtype); + for (int64_t t = 0; t < T; ++t) { + for (int64_t j = 0; j < Kk; ++j) { + const int32_t e = topk_id[static_cast(t * Kk + j)]; + VT_CHECK(e >= 0 && e < E, "NemotronH moe: router emitted an invalid expert id"); + Buf row(act_dtype, {1, H}); + std::memcpy(row.bytes.data(), x.bytes.data() + static_cast(t * H) * esz, + static_cast(H) * esz); + const Buf y = NonGatedExpert(queue, row, w.experts[static_cast(e)], 1, H, I, + "mixer.experts"); + std::memcpy(expert_out.bytes.data() + static_cast((t * Kk + j) * H) * esz, + y.bytes.data(), static_cast(H) * esz); + } + } + + // --- shared expert (nemotron_h.py:176-190): the same non-gated shape, at + // moe_shared_expert_intermediate_size * n_shared_experts. + Buf shared; + if (w.has_shared) { + VT_CHECK(params.n_shared_experts > 0, + "NemotronH moe: shared expert weights present but n_shared_experts is 0"); + const int64_t Is = params.moe_shared_expert_intermediate_size * params.n_shared_experts; + shared = NonGatedExpert(queue, x, w.shared, T, H, Is, "mixer.shared_experts"); + } + + Buf out(act_dtype, {T, H}); + { + Tensor ot = out.t(dev); + Tensor eo = expert_out.t(dev); + Tensor wt = F32V(topk_w, dev, {T, Kk}); + Tensor st = shared.bytes.empty() ? Tensor{} : shared.t(dev); + vt::MoeCombine(queue, ot, eo, wt, shared.bytes.empty() ? nullptr : &st, + static_cast(params.routed_scaling_factor)); + } + return UnpackF32(out); +} + +// ─── (4) the dense `-` MLP mixer ──────────────────────────────────────────── +// +// nemotron_h.py:86-123 `NemotronHMLP`: up_proj -> act_fn -> down_proj, the same +// non-gated relu² shape as an expert. +std::vector NemotronHMlpMixer(const NemotronHMlpWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue) { + CheckActDType(act_dtype); + const int64_t T = num_tokens; + const int64_t H = params.hidden_size; + VT_CHECK(static_cast(hidden_normed.size()) == T * H, + "NemotronH mlp: hidden size mismatch"); + VT_CHECK(params.mlp_hidden_act == "relu2", + "NemotronHForCausalLM forward: only mlp_hidden_act=relu2 is ported " + "(the checkpoint's); another activation is refused by name rather " + "than silently substituted"); + VT_CHECK(!params.mlp_bias, + "NemotronHForCausalLM forward: mlp_bias is not ported (false in the " + "released checkpoint)"); + const Buf x = PackF32(hidden_normed, act_dtype, {T, H}); + NemotronHExpertWeights e; + e.up_proj = w.up_proj; + e.down_proj = w.down_proj; + const Buf out = NonGatedExpert(queue, x, e, T, H, params.intermediate_size, "mixer"); + return UnpackF32(out); +} + +// ─── (5) the whole decoder ────────────────────────────────────────────────── + +std::vector NemotronHForward(const NemotronHHostWeights& host, + const NemotronHParams& params, + const std::vector& token_ids, + const std::vector& logits_indices, + vt::Queue& queue, NemotronHTrace* trace) { + VT_CHECK(host.materialized, + "NemotronHForCausalLM forward: host weights are not materialized. W4 " + "ports the forward MECHANISM; the safetensors/NVFP4/FP8 weight load " + "that fills NemotronHHostWeights is still owed (see " + ".agents/specs/nemotron-h-model.md §5b), and the GGUF arm is W7. " + "Refusing by name rather than computing on zeros."); + VT_CHECK(queue.device.type == vt::DeviceType::kCPU, + "NemotronHForCausalLM forward: this is the HOST reference forward and " + "requires a CPU queue; the device/paged runner path is W6 of " + ".agents/specs/nemotron-h-model.md"); + CheckActDType(host.act_dtype); + + const DType adt = host.act_dtype; + const vt::Device dev = queue.device; + const int64_t H = params.hidden_size; + const int64_t V = params.vocab_size; + const int64_t L = params.num_hidden_layers(); + const int64_t T = static_cast(token_ids.size()); + VT_CHECK(T > 0, "NemotronHForCausalLM forward: empty token sequence"); + VT_CHECK(static_cast(host.layers.size()) == L, + "NemotronHForCausalLM forward: host layer count != layers_block_type length"); + RequireWeight(host.embeddings, "backbone.embeddings.weight", adt, {V, H}); + RequireWeight(host.norm_f, "backbone.norm_f.weight", adt, {H}); + VT_CHECK(!params.tie_word_embeddings, + "NemotronHForCausalLM forward: tie_word_embeddings is false in the " + "released checkpoint and the tied arm is not ported"); + RequireWeight(host.lm_head, "lm_head.weight", adt, {V, H}); + + // The residual stream carries the MODEL dtype, mirroring vLLM's + // fused_add_rms_norm residual. Widening it is numerically correct, invisible + // to a token gate, and doubles the bytes — the trap AGENTS.md names. + Buf residual(adt, {T, H}); + { + std::vector ids = token_ids; + for (int32_t id : ids) { + VT_CHECK(id >= 0 && id < V, "NemotronHForCausalLM forward: token id out of range"); + } + Tensor ot = residual.t(dev); + Tensor tab = host.embeddings.View(dev); + Tensor it = I32(ids, dev, {T}); + vt::Embedding(queue, ot, tab, it); + } + + vt::RmsNormArgs nargs; + nargs.eps = static_cast(params.layer_norm_epsilon); + nargs.gemma = false; + + if (trace != nullptr && trace->capture) { + trace->normed.assign(static_cast(L), {}); + trace->mixer.assign(static_cast(L), {}); + trace->hidden.assign(static_cast(L), {}); + } + + // The single-branch pre-norm stream (nemotron_h.py:625-640 + each layer's + // :301-313). Layer 0 sees `residual is None`, so the embedding IS the residual + // and the norm is un-fused; every later layer folds the previous mixer output + // into the residual inside the norm. + Buf carry(adt, {T, H}); // the previous layer's mixer output + for (int64_t l = 0; l < L; ++l) { + const NemotronHLayerWeights& lw = host.layers[static_cast(l)]; + VT_CHECK(lw.block == params.layers_block_type[static_cast(l)], + "NemotronHForCausalLM forward: host layer block kind disagrees with " + "layers_block_type"); + RequireWeight(lw.norm, "layer norm", adt, {H}); + Buf normed(adt, {T, H}); + { + Tensor ot = normed.t(dev); + Tensor wt = lw.norm.View(dev); + if (l == 0) { + // `residual is None` (nemotron_h.py:627-631): the embedding IS the + // residual and the norm is UN-fused, so there is no add to fuse here. + Tensor xt = residual.t(dev); + vt::RmsNorm(queue, ot, xt, wt, nargs, nullptr); + } else { + Tensor xt = carry.t(dev); + Tensor rt = residual.t(dev); + if (FusedChainAdoptEnabled()) { + vt::FusedChain(queue, ot, xt, wt, &rt, vt::kFusedAddRmsNormStd, + static_cast(params.layer_norm_epsilon)); + } else { + vt::RmsNorm(queue, ot, xt, wt, nargs, &rt); + } + } + } + + const std::vector nvec = UnpackF32(normed); + std::vector mvec; + switch (lw.block) { + case NemotronHBlock::kMamba: + mvec = NemotronHMamba2Mixer(lw.mamba, params, nvec, T, adt, queue); + break; + case NemotronHBlock::kAttention: + mvec = NemotronHAttentionMixer(lw.attn, params, nvec, T, adt, queue); + break; + case NemotronHBlock::kMoe: + mvec = NemotronHMoeMixer(lw.moe, params, nvec, T, adt, queue); + break; + case NemotronHBlock::kMlp: + mvec = NemotronHMlpMixer(lw.mlp, params, nvec, T, adt, queue); + break; + } + carry = PackF32(mvec, adt, {T, H}); + + if (trace != nullptr && trace->capture) { + trace->normed[static_cast(l)] = nvec; + trace->mixer[static_cast(l)] = mvec; + // The residual AFTER this layer is what the next norm will fold `carry` + // into; report the sum so a per-layer comparison sees the stream itself. + std::vector h = UnpackF32(residual); + for (size_t i = 0; i < h.size(); ++i) h[i] += mvec[i]; + trace->hidden[static_cast(l)] = std::move(h); + } + } + + // `hidden_states, _ = self.norm_f(hidden_states, residual)` (nemotron_h.py:641). + Buf final_normed(adt, {T, H}); + { + Tensor ot = final_normed.t(dev); + Tensor xt = carry.t(dev); + Tensor rt = residual.t(dev); + Tensor wt = host.norm_f.View(dev); + if (FusedChainAdoptEnabled()) { + vt::FusedChain(queue, ot, xt, wt, &rt, vt::kFusedAddRmsNormStd, + static_cast(params.layer_norm_epsilon)); + } else { + vt::RmsNorm(queue, ot, xt, wt, nargs, &rt); + } + } + if (trace != nullptr && trace->capture) trace->final_normed = UnpackF32(final_normed); + + std::vector want; + if (logits_indices.empty()) { + want.resize(static_cast(T)); + for (int64_t i = 0; i < T; ++i) want[static_cast(i)] = i; + } else { + for (int32_t idx : logits_indices) { + VT_CHECK(idx >= 0 && idx < T, + "NemotronHForCausalLM forward: logits index out of range"); + want.push_back(idx); + } + } + const int64_t R = static_cast(want.size()); + Buf gathered(adt, {R, H}); + const size_t esz = vt::SizeOf(adt); + for (int64_t r = 0; r < R; ++r) { + std::memcpy(gathered.bytes.data() + static_cast(r * H) * esz, + final_normed.bytes.data() + + static_cast(want[static_cast(r)] * H) * esz, + static_cast(H) * esz); + } + const Buf logits = Linear(queue, gathered, host.lm_head, R, H, V, "lm_head.weight"); + return UnpackF32(logits); +} + +std::vector NemotronHGreedyDecode(const NemotronHHostWeights& host, + const NemotronHParams& params, + const std::vector& prompt, + int num_new, vt::Queue& queue) { + std::vector seq = prompt; + std::vector out; + const int64_t V = params.vocab_size; + for (int step = 0; step < num_new; ++step) { + const std::vector logits = NemotronHForward( + host, params, seq, {static_cast(seq.size() - 1)}, queue, nullptr); + int32_t best = 0; + float best_v = logits[0]; + for (int64_t o = 1; o < V; ++o) { + if (logits[static_cast(o)] > best_v) { + best_v = logits[static_cast(o)]; + best = static_cast(o); + } + } + out.push_back(best); + seq.push_back(best); + } + return out; +} + +} // namespace vllm diff --git a/src/vllm/model_executor/models/nemotron_h_forward.h b/src/vllm/model_executor/models/nemotron_h_forward.h new file mode 100644 index 000000000..3176d8903 --- /dev/null +++ b/src/vllm/model_executor/models/nemotron_h_forward.h @@ -0,0 +1,279 @@ +// Nemotron-H (`NemotronHForCausalLM`) — W4, the FORWARD +// ([spec](../../../../.agents/specs/nemotron-h-model.md) §4 W4, issue #517). +// +// W3 made the architecture KNOWN, PARSED, ENUMERATED and KV-SHAPED +// (nemotron_h.h). This header is where it first COMPUTES: the hybrid 52-layer +// decoder loop over the three block kinds `layers_block_type` names — 23 Mamba2, +// 6 GQA attention at indices {5,12,19,26,33,42}, 23 non-gated relu² MoE. +// +// ─── WHAT THIS IS A PORT OF (file:line on BOTH sides, @ pin 555967922) ─────── +// NemotronHModel::Forward <- nemotron_h.py:604-660 — the single-branch +// pre-norm residual stream. NOTE: unlike Llama, +// a NemotronH decoder layer has exactly ONE norm +// and ONE mixer (:301-313, :344-355, :390-401, +// :525-528), not an attention branch plus an MLP +// branch. 52 layers == 52 (norm, mixer) pairs. +// Mamba2Mixer <- layers/mamba/mamba_mixer2.py:548-586 (in_proj → +// conv+SSM → gated group norm → out_proj), the +// split points at :532-543 and :692-696, the +// prefill scan call at :866-891. +// Attention <- nemotron_h.py:473-486. Ported deliberately +// WITHOUT any positional embedding — see +// `kNemotronHAttentionHasNoRope`. +// MoE <- nemotron_h.py:126-256 + the W2 arm recorded in +// spec §6a (up → relu² → down → scaled combine). +// +// ─── SCOPE, exactly ───────────────────────────────────────────────────────── +// This is the HOST (CPU) reference forward, the same cadence Kimi-Linear's W2-W6 +// took (kimi_linear_forward.cpp:1-26) and DeepSeek-V4's `DeepseekV4ForwardHost` +// before it: a real forward composed out of the ALREADY-LANDED `vt::` ops, run +// on a CPU queue, so the mechanism is gateable before any GPU is involved. What +// it is NOT: +// * NOT a weight loader. Nothing here reads a checkpoint. The safetensors +// materialization of the 18487 tensors `EnumerateNemotronHTensors` names — +// including the NVFP4 W4A16 g16 experts and the FP8 W8A8 mamba projections — +// is still owed, and `NemotronHForward` REFUSES BY NAME when handed +// unmaterialized weights rather than returning zeros. +// * NOT the MTP head (W5), NOT the GGUF arm (W7), NOT a speed claim. +// * NOT the paged/device runner path (W6). The recurrent state here is FRESH +// per call and single-sequence; `MakeNemotronHKVCache` already declares the +// het-KV topology the runner will drive, and wiring it is W6's. +#pragma once + +#include +#include +#include + +#include "vllm/model_executor/models/nemotron_h.h" +#include "vt/backend.h" +#include "vt/dtype.h" +#include "vt/tensor.h" + +namespace vllm { + +// NemotronH's attention carries NO positional embedding of any kind. This is not +// an omission to be repaired later: `models/nemotron_h.py` @ 555967922 contains +// ZERO occurrences of `rope`, `rotary` or `Rotary` — `NemotronHAttention.__init__` +// (:415-471) builds `qkv_proj`, `o_proj` and `Attention` and nothing else, and +// `.forward` (:473-486) is qkv → split → attn → o_proj with no rotation step. +// Position information reaches the residual stream through the 23 Mamba2 layers' +// recurrence instead, which is what makes a 1M context cheap here. +// +// The released config.json DOES ship `rope_theta: 10000` and +// `partial_rotary_factor: 1.0`, and `NemotronHParams` parses both (nemotron_h.h) +// — they are INERT for this architecture. Applying them would be numerically +// plausible, would not change a single tensor SHAPE, and on a short prompt might +// not move a token; the forward gate mutates exactly this to prove it is caught. +inline constexpr bool kNemotronHAttentionHasNoRope = true; + +// ─── host weights ─────────────────────────────────────────────────────────── +// +// One owned, contiguous host tensor in a DECLARED dtype. Deliberately a lean +// byte buffer rather than the safetensors-backed `OwnedTensor`: nothing in W4 +// loads a checkpoint, so pulling the loader's includes into this header would +// buy nothing, and the byte-buffer form is what a later loader writes into +// directly. +// +// The DTYPE IS THE POINT, not an implementation detail. vLLM resolves ONE model +// dtype and every layer inherits it (AGENTS.md); this struct carries it +// explicitly so a gate can assert the memory format instead of inferring it from +// matching tokens — a dtype that is too WIDE is numerically correct and +// therefore invisible to a token comparison. +struct NemotronHOwned { + std::vector bytes; + vt::DType dtype = vt::DType::kF32; + std::vector shape; + + bool Empty() const { return bytes.empty(); } + int64_t Numel() const; + // A non-owning contiguous view over the current buffer. Rebuilt on each call + // so it survives moves/reallocations of the owning struct. + vt::Tensor View(vt::Device device) const; + + // Pack canonical f32 values into `dtype`. This is the seam a real loader + // replaces: it hands over checkpoint bytes that are ALREADY in the model + // dtype, and no packing happens per forward. + static NemotronHOwned FromF32(const std::vector& values, vt::DType dtype, + std::vector shape); +}; + +// Mamba2 mixer weights, by the names the checkpoint ships (nemotron_h.h's +// enumeration): `mixer.{in_proj,conv1d,A_log,D,dt_bias,norm,out_proj}`. +struct NemotronHMambaWeights { + // torch Linear orientation [out, in] — vt::MatmulBT's `b [N,K]`. + NemotronHOwned in_proj; // [in_proj_out_features, hidden_size] + NemotronHOwned out_proj; // [hidden_size, mamba_intermediate_size] + // The disk tensor is [conv_dim, 1, conv_kernel]; vt::CausalConv1dFwd takes the + // squeezed [conv_dim, conv_kernel] view, exactly as upstream's + // `self.conv_weights = self.conv1d.weight.view(conv_dim, K)` + // (mamba_mixer2.py, `conv_weights`). + NemotronHOwned conv1d_weight; // [conv_dim, conv_kernel] + NemotronHOwned conv1d_bias; // [conv_dim] — use_conv_bias=true here + // f32 BY CONTRACT, not by choice: vt::Mamba2ChunkScan validates A/D/dt_bias as + // f32 (ops.cpp CheckMamba2*), mirroring upstream, which keeps `A` in f32 + // (`self.A = -torch.exp(self.A_log.float())`) regardless of the model dtype. + NemotronHOwned A_log; // [mamba_num_heads] f32 + NemotronHOwned D; // [mamba_num_heads] f32 + NemotronHOwned dt_bias; // [mamba_num_heads] f32 + // Mixer2RMSNormGated weight over the SSM intermediate width. + NemotronHOwned norm_weight; // [mamba_intermediate_size] +}; + +// GQA attention weights. q/k/v ship SEPARATE on disk (upstream fuses them into +// `qkv_proj` at load through its stacked-params mapping), so they are separate +// here too — that is the shape `EnumerateNemotronHTensors` already claims. +struct NemotronHAttentionWeights { + NemotronHOwned q_proj; // [num_attention_heads*head_dim, hidden_size] + NemotronHOwned k_proj; // [num_key_value_heads*head_dim, hidden_size] + NemotronHOwned v_proj; // [num_key_value_heads*head_dim, hidden_size] + NemotronHOwned o_proj; // [hidden_size, num_attention_heads*head_dim] +}; + +// One NON-GATED expert: `ckpt_names=("up_proj","down_proj","")` +// (nemotron_h.py:220). There is no gate_proj tensor anywhere in the checkpoint. +struct NemotronHExpertWeights { + NemotronHOwned up_proj; // [moe_intermediate_size, hidden_size] + NemotronHOwned down_proj; // [hidden_size, moe_intermediate_size] +}; + +struct NemotronHMoeWeights { + // The router. Its OUTPUT DTYPE IS F32 AND THAT IS MIRRORED, NOT INHERITED: + // `GateLinear(..., out_dtype=torch.float32, force_fp32_compute=True)` + // (nemotron_h.py:150-156). This is the annotated f32 escape AGENTS.md allows, + // and it is upstream's own polarity rather than a local precision choice. + NemotronHOwned gate; // [n_routed_experts, hidden_size] + // `self.gate.e_score_correction_bias` (nemotron_h.py:158-160), f32 upstream. + NemotronHOwned e_score_correction_bias; // [n_routed_experts] f32 + std::vector experts; + // The shared expert is the SAME non-gated shape at + // moe_shared_expert_intermediate_size * n_shared_experts (nemotron_h.py:176-190). + NemotronHExpertWeights shared; + bool has_shared = false; +}; + +// The dense `-` block. No released in-scope NemotronH checkpoint ships one, so +// this arm exists because the schedule can name it and a silent zero would be +// worse than a computed answer; it is exercised only by the unit gate. +struct NemotronHMlpWeights { + NemotronHOwned up_proj; // [intermediate_size, hidden_size] + NemotronHOwned down_proj; // [hidden_size, intermediate_size] +}; + +struct NemotronHLayerWeights { + NemotronHBlock block = NemotronHBlock::kMamba; + // The layer's SINGLE norm (`self.norm`, one per decoder layer). + NemotronHOwned norm; // [hidden_size] + NemotronHMambaWeights mamba; + NemotronHAttentionWeights attn; + NemotronHMoeWeights moe; + NemotronHMlpWeights mlp; +}; + +struct NemotronHHostWeights { + // The ONE model dtype every activation and every weight above inherits + // (AGENTS.md: "vLLM resolves ONE model dtype and every layer inherits it"). + // bf16 is the released checkpoint's; the gate also sweeps f32, because a bf16 + // store absorbs reduction-order defects. + vt::DType act_dtype = vt::DType::kBF16; + NemotronHOwned embeddings; // [vocab_size, hidden_size] + std::vector layers; + NemotronHOwned norm_f; // [hidden_size] + NemotronHOwned lm_head; // [vocab_size, hidden_size] + // False until a loader materializes the enumerated tensors. The forward + // refuses by name on false rather than computing on zeros. + bool materialized = false; +}; + +// ─── per-block entry points (each independently gateable) ─────────────────── +// +// Every one takes the ALREADY-NORMED hidden `[T, hidden_size]` in `act_dtype` +// and returns the mixer output `[T, hidden_size]` in the same dtype, which is +// exactly the contract of an upstream `layer.mixer(hidden_states)` call. They +// are public so the gate can compare a BLOCK's activations against an +// independent reference — a mechanism can be missing while the argmax is +// unchanged, so a tokens-only comparison cannot see a dropped one +// (porting-a-model.md §3). + +// The Mamba2 mixer's two recurrent states, carried ACROSS calls so a sequence +// can be fed in more than one leg (upstream's chunked prefill: leg 2 reads leg +// 1's `final_states` as its `initial_states`, ssd_combined.py:79,:194). +// +// The two dtypes are resolved SEPARATELY and that is the whole point: +// * `conv` is f32 because `vt::CausalConv1dFwd` validates the conv state as +// f32 (ops.cpp CheckConvCommon; bf16 only where the backend advertises +// SupportsCompressedConvState). The persistent page `MakeNemotronHKVCache` +// declares is the cache dtype and W6 owns reconciling the two. +// * `ssm` carries `NemotronHSsmCacheDType(params, act_dtype)` — "float32" on +// this checkpoint whatever the model dtype. A shared helper keyed on +// Qwen3.5's `mamba_ssm_dtype` spelling returns the CONVOLUTION dtype here +// and silently halves the recurrent state; nemotron_h.h records why. +// +// With FRESH state and a single leg the ssm dtype is UNOBSERVABLE in the output +// — the scan computes in f32 and only STORES `final_states` at this dtype. It +// becomes observable exactly when a state is carried in, which is what the +// two-leg gate exercises and what the W6 paged decode does on every step. +struct NemotronHMambaState { + std::vector conv; // [conv_dim, conv_kernel-1] f32 + NemotronHOwned ssm; // [mamba_num_heads, mamba_head_dim, ssm_state_size] + // False on the first leg (both states read as zero); set by the mixer on + // return so the next leg carries them in. + bool has_initial = false; +}; + +// Mamba2 mixer over ONE sequence of T tokens. `state` nullptr means FRESH state +// discarded on return (the single-leg case); non-null carries it in and out. +std::vector NemotronHMamba2Mixer(const NemotronHMambaWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue, + NemotronHMambaState* state = nullptr); + +std::vector NemotronHAttentionMixer(const NemotronHAttentionWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue); + +std::vector NemotronHMoeMixer(const NemotronHMoeWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue); + +std::vector NemotronHMlpMixer(const NemotronHMlpWeights& w, + const NemotronHParams& params, + const std::vector& hidden_normed, + int64_t num_tokens, vt::DType act_dtype, + vt::Queue& queue); + +// ─── the whole decoder ────────────────────────────────────────────────────── + +// Optional per-layer capture. `hidden` holds the residual stream AFTER each +// layer's mixer add, `normed` the norm input each mixer saw; both [T,H] and in +// f32 for comparison. Populated only when `capture` is true. This is the +// instrument porting-a-model.md §3 asks for: per-layer activations, not tokens. +struct NemotronHTrace { + bool capture = false; + std::vector> normed; // [L][T*H] + std::vector> mixer; // [L][T*H] + std::vector> hidden; // [L][T*H] (residual after the layer) + std::vector final_normed; // [T*H] +}; + +// Logits `[num_requested, vocab_size]` in f32 for the requested positions (all +// positions when `logits_indices` is empty). +std::vector NemotronHForward(const NemotronHHostWeights& host, + const NemotronHParams& params, + const std::vector& token_ids, + const std::vector& logits_indices, + vt::Queue& queue, NemotronHTrace* trace = nullptr); + +// Greedy argmax decode over a fresh forward per step. Single sequence; the +// incremental paged/recurrent decode is W6's. +std::vector NemotronHGreedyDecode(const NemotronHHostWeights& host, + const NemotronHParams& params, + const std::vector& prompt, + int num_new, vt::Queue& queue); + +} // namespace vllm diff --git a/src/vllm/model_executor/models/nemotron_h_registry.cpp b/src/vllm/model_executor/models/nemotron_h_registry.cpp index e3bac3f82..c1476d247 100644 --- a/src/vllm/model_executor/models/nemotron_h_registry.cpp +++ b/src/vllm/model_executor/models/nemotron_h_registry.cpp @@ -25,7 +25,9 @@ #include #include "vllm/model_executor/models/nemotron_h.h" -#include "vllm/model_executor/models/qwen3_5.h" // ForwardLogits +#include "vllm/model_executor/models/nemotron_h_forward.h" +#include "vllm/model_executor/models/qwen3_5.h" // ForwardLogits +#include "vllm/model_executor/models/qwen3_5_common.h" // HostLogits #include "vllm/v1/kv_cache_dtype.h" #include "vllm/v1/kv_cache_interface.h" #include "vt/dtype.h" @@ -55,9 +57,17 @@ class NemotronHLoadedModel final : public LoadedModel { NemotronHParams params) : LoadedModel(registration), params_(std::move(params)) {} const NemotronHParams& params() const { return params_; } + // W4 ports the forward MECHANISM (nemotron_h.cpp). The weight LOAD that fills + // this — the 18487 enumerated tensors, NVFP4 W4A16 g16 experts and FP8 W8A8 + // mamba projections included — is still owed, so `materialized` stays false on + // the checkpoint path and `NemotronHForward` refuses by name. A direct caller + // (the unit gate) constructs the weights itself and reaches the same forward. + NemotronHHostWeights& weights() { return weights_; } + const NemotronHHostWeights& weights() const { return weights_; } private: NemotronHParams params_; + NemotronHHostWeights weights_; }; std::unique_ptr LoadNemotronHForCausalLM( @@ -89,15 +99,17 @@ void PrepareNemotronHForCausalLM(LoadedModel& model, const HfConfig& config, ForwardLogits ForwardNemotronHForCausalLM(LoadedModel& model, const ModelForwardInput& input) { - (void)model; - (void)input; - // W4 owns the hybrid layer loop, the Mamba2 mixer wiring, the 6 attention - // layers and the MoE layers (spec §4 W4), and is itself blocked on the - // Mamba2 SSD CUDA arm (#496 W2). Refuse loudly rather than return zeros. - VT_CHECK(false, - "NemotronHForCausalLM forward is not implemented yet (W4 of " - ".agents/specs/nemotron-h-model.md, issue #517)"); - return {}; + auto& nh = static_cast(model); + // W4: the hybrid layer loop, the Mamba2 mixer wiring, the 6 attention layers + // and the MoE layers are ported (nemotron_h.cpp) and reached HERE, through the + // shared `ModelRegistry::Forward` seam — never through a parallel entry point. + // `NemotronHForward` refuses BY NAME when the host weights are not + // materialized, which is the state every checkpoint load leaves them in until + // the weight loader lands (spec §5b); that refusal names the missing piece + // instead of returning a silent zero forward. + return HostLogits(NemotronHForward(nh.weights(), nh.params(), input.token_ids, + input.logits_indices, input.queue), + nh.params().vocab_size); } const ModelFactory kNemotronHFactory{ diff --git a/src/vllm/model_executor/models/olmo2_weights.cpp b/src/vllm/model_executor/models/olmo2_weights.cpp index 268e2d543..d21758663 100644 --- a/src/vllm/model_executor/models/olmo2_weights.cpp +++ b/src/vllm/model_executor/models/olmo2_weights.cpp @@ -48,6 +48,7 @@ #include "vllm/model_executor/model_loader/safetensors_reader.h" #include "vllm/model_executor/models/dense_weight_loaders.h" // MakeOwned + bf16 helpers #include "vt/dtype.h" +#include "vt/unaligned.h" namespace vllm { namespace { @@ -65,8 +66,16 @@ bool RawBool(const nlohmann::json& doc, const char* key, bool fallback) { // Downcast a contiguous F32 buffer to bf16 (round-to-nearest-even), the same // rounding torch's .to(bfloat16) and vt::F32ToBF16 use. -void F32ToBf16Into(const float* src, int64_t n, uint16_t* dst) { - for (int64_t i = 0; i < n; ++i) dst[i] = vt::F32ToBF16(src[i]); +// `src` is `const void*`: every caller below hands it a pointer INTO the mmap'd +// safetensors payload, whose per-tensor byte offset is the running total of +// everything ahead of it and so need not be a multiple of 4. Forming or loading +// through a `const float*` there is undefined (issue #627); vt::LoadUnaligned is +// the project's seam for it. +void F32ToBf16Into(const void* src, int64_t n, uint16_t* dst) { + const auto* bytes = static_cast(src); + for (int64_t i = 0; i < n; ++i) { + dst[i] = vt::F32ToBF16(vt::LoadUnaligned(bytes + i * 4)); + } } // F32 tensor -> owned bf16, copied verbatim (optionally reshaped). Mirrors @@ -80,8 +89,7 @@ OwnedTensor LoadF32ToBf16Direct(const TensorResolver& get, const std::string& na const int64_t n = o.Numel(); VT_CHECK(t.nbytes == static_cast(n) * sizeof(float), "olmo2: byte-size mismatch for " + name); - F32ToBf16Into(reinterpret_cast(t.data), n, - reinterpret_cast(o.bytes.data())); + F32ToBf16Into(t.data, n, reinterpret_cast(o.bytes.data())); MaybeReleaseSourcePages(t.data, t.nbytes); return o; } @@ -96,11 +104,13 @@ OwnedTensor LoadF32ToBf16Transposed(const TensorResolver& get, const int64_t out_dim = t.shape[0]; const int64_t in_dim = t.shape[1]; OwnedTensor o = MakeOwned(vt::DType::kBF16, {in_dim, out_dim}); - const auto* src = reinterpret_cast(t.data); + // Unaligned: `t.data` is an arbitrary byte offset into the mmap (#627). + const uint8_t* src = t.data; auto* dst = reinterpret_cast(o.bytes.data()); for (int64_t r = 0; r < out_dim; ++r) for (int64_t c = 0; c < in_dim; ++c) - dst[c * out_dim + r] = vt::F32ToBF16(src[r * in_dim + c]); + dst[c * out_dim + r] = + vt::F32ToBF16(vt::LoadUnaligned(src + (r * in_dim + c) * 4)); MaybeReleaseSourcePages(t.data, t.nbytes); return o; } @@ -136,7 +146,7 @@ OwnedTensor LoadMergedF32ToBf16RawNK(const TensorResolver& get, const int64_t n = shard.shape[0] * in_dim; VT_CHECK(shard.nbytes == static_cast(n) * sizeof(float), "olmo2: byte-size mismatch for " + names[i]); - F32ToBf16Into(reinterpret_cast(shard.data), n, dst + off); + F32ToBf16Into(shard.data, n, dst + off); off += static_cast(n); MaybeReleaseSourcePages(shard.data, shard.nbytes); } diff --git a/src/vllm/model_executor/models/parakeet_weights.cpp b/src/vllm/model_executor/models/parakeet_weights.cpp index 3b913329e..4e3ed4ad9 100644 --- a/src/vllm/model_executor/models/parakeet_weights.cpp +++ b/src/vllm/model_executor/models/parakeet_weights.cpp @@ -56,6 +56,7 @@ #include "vllm/model_executor/models/parakeet_encoder.h" #include "vllm/model_executor/models/parakeet_transducer.h" #include "vt/dtype.h" +#include "vt/unaligned.h" namespace vllm::multimodal { namespace { @@ -100,9 +101,11 @@ std::vector ToF32(const std::string& name, const StTensor& t) { if (t.nbytes != out.size() * sizeof(uint16_t)) { throw std::runtime_error("parakeet: byte count mismatch for " + name); } - const uint16_t* src = reinterpret_cast(t.data); + // Unaligned: `t.data` is an arbitrary byte offset into the mmap (#627). + const bool f16 = (t.dtype == "F16"); for (size_t i = 0; i < out.size(); ++i) { - out[i] = (t.dtype == "F16") ? vt::F16ToF32(src[i]) : vt::BF16ToF32(src[i]); + const uint16_t bits = vt::LoadUnaligned(t.data + i * 2); + out[i] = f16 ? vt::F16ToF32(bits) : vt::BF16ToF32(bits); } } else { throw std::runtime_error("parakeet: unsupported dtype " + t.dtype + " for " + name); diff --git a/src/vllm/model_executor/models/phi_weights.cpp b/src/vllm/model_executor/models/phi_weights.cpp index f4ff4360a..d24233fbf 100644 --- a/src/vllm/model_executor/models/phi_weights.cpp +++ b/src/vllm/model_executor/models/phi_weights.cpp @@ -32,6 +32,7 @@ #include "vllm/model_executor/model_loader/safetensors_reader.h" #include "vllm/model_executor/models/dense_weight_loaders.h" #include "vt/dtype.h" +#include "vt/unaligned.h" namespace vllm { namespace { @@ -46,8 +47,16 @@ using dense_loaders::MakeOwned; // `.to(bfloat16)` uses (F16 is a subset of F32, so F16ToF32 is exact). inline uint16_t F16ToBf16(uint16_t h) { return vt::F32ToBF16(vt::F16ToF32(h)); } -void F16ToBf16Into(const uint16_t* src, int64_t n, uint16_t* dst) { - for (int64_t i = 0; i < n; ++i) dst[i] = F16ToBf16(src[i]); +// `src` is `const void*`: every caller below hands it a pointer INTO the mmap'd +// safetensors payload, whose per-tensor byte offset is the running total of +// everything ahead of it and so need not be even. Forming or loading through a +// `const uint16_t*` there is undefined (issue #627); vt::LoadUnaligned is the +// project's seam for it. +void F16ToBf16Into(const void* src, int64_t n, uint16_t* dst) { + const auto* bytes = static_cast(src); + for (int64_t i = 0; i < n; ++i) { + dst[i] = F16ToBf16(vt::LoadUnaligned(bytes + i * 2)); + } } // F16 tensor -> owned bf16, copied verbatim (optionally reshaped). Mirrors @@ -61,8 +70,7 @@ OwnedTensor LoadF16ToBf16Direct(const TensorResolver& get, const std::string& na const int64_t n = o.Numel(); VT_CHECK(t.nbytes == static_cast(n) * sizeof(uint16_t), "phi: byte-size mismatch for " + name); - F16ToBf16Into(reinterpret_cast(t.data), n, - reinterpret_cast(o.bytes.data())); + F16ToBf16Into(t.data, n, reinterpret_cast(o.bytes.data())); MaybeReleaseSourcePages(t.data, t.nbytes); return o; } @@ -76,11 +84,13 @@ OwnedTensor LoadF16ToBf16Transposed(const TensorResolver& get, const int64_t out_dim = t.shape[0]; const int64_t in_dim = t.shape[1]; OwnedTensor o = MakeOwned(vt::DType::kBF16, {in_dim, out_dim}); - const auto* src = reinterpret_cast(t.data); + // Unaligned: `t.data` is an arbitrary byte offset into the mmap (#627). + const uint8_t* src = t.data; auto* dst = reinterpret_cast(o.bytes.data()); for (int64_t r = 0; r < out_dim; ++r) for (int64_t c = 0; c < in_dim; ++c) - dst[c * out_dim + r] = F16ToBf16(src[r * in_dim + c]); + dst[c * out_dim + r] = + F16ToBf16(vt::LoadUnaligned(src + (r * in_dim + c) * 2)); MaybeReleaseSourcePages(t.data, t.nbytes); return o; } @@ -115,7 +125,7 @@ OwnedTensor LoadMergedF16ToBf16RawNK(const TensorResolver& get, const int64_t n = shard.shape[0] * in_dim; VT_CHECK(shard.nbytes == static_cast(n) * sizeof(uint16_t), "phi: byte-size mismatch for " + names[i]); - F16ToBf16Into(reinterpret_cast(shard.data), n, dst + off); + F16ToBf16Into(shard.data, n, dst + off); off += static_cast(n); MaybeReleaseSourcePages(shard.data, shard.nbytes); } @@ -148,7 +158,7 @@ OwnedTensor LoadMergedF16ToBf16Vector(const TensorResolver& get, const int64_t n = shard.shape[0]; VT_CHECK(shard.nbytes == static_cast(n) * sizeof(uint16_t), "phi: byte-size mismatch for " + names[i]); - F16ToBf16Into(reinterpret_cast(shard.data), n, dst + off); + F16ToBf16Into(shard.data, n, dst + off); off += static_cast(n); MaybeReleaseSourcePages(shard.data, shard.nbytes); } diff --git a/src/vllm/model_executor/models/qwen3_5_common.h b/src/vllm/model_executor/models/qwen3_5_common.h index d22297d3a..1e4ca96c7 100644 --- a/src/vllm/model_executor/models/qwen3_5_common.h +++ b/src/vllm/model_executor/models/qwen3_5_common.h @@ -30,6 +30,24 @@ inline constexpr ModelInfo kQwen3_5Info{ .score_type = "bi-encoder", }; +// The same record for the TEXT-ONLY arms `Qwen3_5ForCausalLM` / +// `Qwen3_5MoeForCausalLM`, whose upstream base `Qwen3_5ForCausalLMBase` +// inherits HasInnerState + IsHybrid but NOT SupportsMultiModal +// (vllm/model_executor/models/qwen3_5.py:287-296 @ `ad5d29db7`, PR #50210 — +// AHEAD of our `555967922` parity pin and recorded as such). Their multimodal +// wrappers are the separate `ForConditionalGeneration` registrations, so the +// ONLY difference is supports_multimodal. `has_inner_state` stays false for the +// same reason `KimiLinearForCausalLM` leaves it false: our ModelInfo is a +// consumed subset whose only reader short-circuits on is_hybrid. +inline constexpr ModelInfo kQwen3_5TextInfo{ + .is_text_generation_model = true, + .is_pooling_model = false, + .is_hybrid = true, + .has_inner_state = false, + .supports_multimodal = false, + .score_type = "bi-encoder", +}; + // Per-family config hook. LoadHfConfig/HfConfigFromGguf already materialize the // consumed Qwen fields; this explicit hook is where a family adds normalization // or validation without changing the registry/runner contract. diff --git a/src/vllm/model_executor/models/qwen3_5_dense.cpp b/src/vllm/model_executor/models/qwen3_5_dense.cpp index 74229a26a..4914e4733 100644 --- a/src/vllm/model_executor/models/qwen3_5_dense.cpp +++ b/src/vllm/model_executor/models/qwen3_5_dense.cpp @@ -243,4 +243,17 @@ std::unique_ptr BorrowQwen3_5DenseLoadedModel( REGISTER_VLLM_MODEL(qwen3_5_dense, "Qwen3_5ForConditionalGeneration", kQwen3_5DenseFactory, kQwen3_5Info) +// TEXT-ONLY arm of the SAME backbone. Upstream's `Qwen3_5ForCausalLM` IS +// `Qwen3_5ForCausalLMBase` unchanged (`class Qwen3_5ForCausalLM(...): pass`, +// qwen3_5.py:439-440 @ `ad5d29db7`) and is registered against the same `qwen3_5` +// module (registry.py:202 @ `ad5d29db7`, PR #50210), so this is the SAME +// factory, additively: no forward, no KV-cache spec and no loader fork. +// +// AHEAD OF THE PIN, DELIBERATELY. `555967922` (.agents/upstream-sync.md) carries +// only the ForConditionalGeneration entries; the text-only arms landed upstream +// after it. This is a forward port of ONE upstream PR and does not advance the +// pin. See .agents/specs/qwen38-text-only.md §Gates for the owed run gate. +REGISTER_VLLM_MODEL(qwen3_5_dense_text, "Qwen3_5ForCausalLM", + kQwen3_5DenseFactory, kQwen3_5TextInfo) + } // namespace vllm diff --git a/src/vllm/model_executor/models/qwen3_5_dense_weights.cpp b/src/vllm/model_executor/models/qwen3_5_dense_weights.cpp index dc904bf3d..6be8446ac 100644 --- a/src/vllm/model_executor/models/qwen3_5_dense_weights.cpp +++ b/src/vllm/model_executor/models/qwen3_5_dense_weights.cpp @@ -18,6 +18,7 @@ #include "vllm/platforms/interface.h" #include "vt/backend.h" #include "vt/dtype.h" +#include "vt/unaligned.h" namespace vllm { @@ -114,8 +115,10 @@ OwnedTensor LoadToF32(const TensorResolver& get, const std::string& name) { "qwen3_5 dense: byte-size mismatch for " + name); std::memcpy(dst, t.data, t.nbytes); } else { - const auto* src = reinterpret_cast(t.data); - for (int64_t i = 0; i < n; ++i) dst[i] = vt::BF16ToF32(src[i]); + // Unaligned: `t.data` is an arbitrary byte offset into the mmap (#627). + for (int64_t i = 0; i < n; ++i) { + dst[i] = vt::BF16ToF32(vt::LoadUnaligned(t.data + i * 2)); + } } MaybeReleaseSourcePages(t.data, t.nbytes); return o; @@ -624,12 +627,12 @@ OwnedTensor MaterializeCtNvfp4Bf16Transposed(const TensorResolver& get, return o; } -Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer(const TensorResolver& get, - const TensorExists& has, - const std::string& layer_type, - int64_t layer_idx) { +Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer( + const TensorResolver& get, const TensorExists& has, + const std::string& layer_type, int64_t layer_idx, + const std::string& backbone_prefix) { const std::string base = - "model.language_model.layers." + std::to_string(layer_idx) + "."; + backbone_prefix + "layers." + std::to_string(layer_idx) + "."; Qwen3_5DenseLayerWeights layer; layer.input_layernorm = LoadModelBf16Direct(get, base + "input_layernorm.weight"); @@ -648,21 +651,31 @@ Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer(const TensorResolver& get, return layer; } -Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer(const TensorResolver& get, - const std::string& layer_type, - int64_t layer_idx) { +Qwen3_5DenseLayerWeights LoadQwen3_5DenseLayer( + const TensorResolver& get, const std::string& layer_type, int64_t layer_idx, + const std::string& backbone_prefix) { // The public resolver-only seam is used by the compressed-tensors parity // fixture, where every routed projection is NVFP4. const TensorExists has = [](const std::string&) { return true; }; - return LoadQwen3_5DenseLayer(get, has, layer_type, layer_idx); + return LoadQwen3_5DenseLayer(get, has, layer_type, layer_idx, + backbone_prefix); } Qwen3_5DenseWeights LoadQwen3_5Dense(const std::vector& shards, const HfConfig& config, vt::Queue* load_queue) { std::unordered_map where; - for (const SafetensorsFile& shard : shards) - for (const std::string& name : shard.Names()) where[name] = &shard; + std::vector all_names; + for (const SafetensorsFile& shard : shards) { + for (const std::string& name : shard.Names()) { + where[name] = &shard; + all_names.push_back(name); + } + } + // ONE namespace decision for the whole checkpoint (qwen3_5_weights.h): the + // VL-nested spelling for the wrappers we gate, the flat `model.` spelling for + // a text-only arm, and a refusal for a mixed index. + const std::string backbone = ResolveQwen3_5BackbonePrefix(all_names); const TensorResolver get = [&where](const std::string& name) -> const StTensor& { auto it = where.find(name); @@ -679,10 +692,8 @@ Qwen3_5DenseWeights LoadQwen3_5Dense(const std::vector& shards, "qwen3_5 dense: layer_types size must equal num_hidden_layers"); Qwen3_5DenseWeights w; - w.embed_tokens = - LoadBf16Direct(get, "model.language_model.embed_tokens.weight"); - w.final_norm = - LoadModelBf16Direct(get, "model.language_model.norm.weight"); + w.embed_tokens = LoadBf16Direct(get, backbone + "embed_tokens.weight"); + w.final_norm = LoadModelBf16Direct(get, backbone + "norm.weight"); // The 27B owns an explicit head; smaller Qwen3.5 checkpoints tie logits to // the embedding table and omit lm_head.weight. if (DenseCheckpointHasLmHead(has, "lm_head")) { @@ -695,7 +706,7 @@ Qwen3_5DenseWeights LoadQwen3_5Dense(const std::vector& shards, bool direct_device = DirectDeviceLoadEligible(load_queue); for (int64_t l = 0; l < config.num_hidden_layers; ++l) { w.layers.push_back(LoadQwen3_5DenseLayer( - get, has, config.layer_types[static_cast(l)], l)); + get, has, config.layer_types[static_cast(l)], l, backbone)); if (direct_device) { direct_device = IsPlainBf16Qwen3_5Dense(w); if (direct_device) StageAndReleaseLoadedDense(w, *load_queue); diff --git a/src/vllm/model_executor/models/qwen3_5_moe.cpp b/src/vllm/model_executor/models/qwen3_5_moe.cpp index 1e33afb76..5c87a464d 100644 --- a/src/vllm/model_executor/models/qwen3_5_moe.cpp +++ b/src/vllm/model_executor/models/qwen3_5_moe.cpp @@ -215,4 +215,20 @@ std::unique_ptr BorrowQwen3_5MoeLoadedModel( REGISTER_VLLM_MODEL(qwen3_5_moe, "Qwen3_5MoeForConditionalGeneration", kQwen3_5MoeFactory, kQwen3_5Info) +// TEXT-ONLY arm of the SAME backbone. Upstream registers it against the same +// `qwen3_5` module (registry.py:202-203 @ `ad5d29db7`, PR #50210) and its class +// is `Qwen3_5ForCausalLMBase` plus `set_moe_parameters()` — not a separate model +// (qwen3_5.py:443-449). So this is the SAME factory, additively: no forward, no +// KV-cache spec and no loader fork. `Qwen/Qwen3.8-2.4T-A95B` is the motivating +// checkpoint; it declares `Qwen3_5MoeForCausalLM` / `qwen3_5_moe_text` and is +// the 35B-A3B architecture at larger scale, all of it config-driven. +// +// AHEAD OF THE PIN, DELIBERATELY. `555967922` (.agents/upstream-sync.md) carries +// only the ForConditionalGeneration entries; the text-only arms landed upstream +// after it. This is a forward port of ONE upstream PR and does not advance the +// pin. There is NO run gate for the 2.4T checkpoint on this hardware — see +// .agents/specs/qwen38-text-only.md §Gates, which records that gate as OWED. +REGISTER_VLLM_MODEL(qwen3_5_moe_text, "Qwen3_5MoeForCausalLM", + kQwen3_5MoeFactory, kQwen3_5TextInfo) + } // namespace vllm diff --git a/src/vllm/model_executor/models/qwen3_5_weights.cpp b/src/vllm/model_executor/models/qwen3_5_weights.cpp index 0f2472138..d87977228 100644 --- a/src/vllm/model_executor/models/qwen3_5_weights.cpp +++ b/src/vllm/model_executor/models/qwen3_5_weights.cpp @@ -3,6 +3,7 @@ // (.agents/specs/qwen36-forward-notes.md §6). #include "vllm/model_executor/models/qwen3_5_weights.h" +#include #include #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #include #if defined(__unix__) || defined(__APPLE__) @@ -20,6 +22,7 @@ #include "vllm/model_executor/model_loader/nvfp4_dequant.h" #include "vt/backend.h" #include "vt/dtype.h" +#include "vt/unaligned.h" namespace vllm { @@ -288,12 +291,20 @@ float ReadF32Scalar(const StTensor& t) { } // src bf16 [rows, cols] -> dst bf16 [cols, rows]. -void TransposeBf16(const uint16_t* src, int64_t rows, int64_t cols, - uint16_t* dst) { +// +// `src` is `const void*`, not `const uint16_t*`, because the caller below hands +// it a pointer INTO the mmap'd safetensors payload. A tensor's offset there is +// the running byte total of everything ahead of it, so a bf16 tensor that +// follows an odd-length one starts on an odd byte and the typed pointer is +// undefined to form or load through (issue #627). `vt::LoadUnaligned` is the +// project's seam for that — the same one `ReadF32Scalar` above open-codes with +// memcpy and `dense_loaders::TransposeBf16` already uses for this exact loop. +void TransposeBf16(const void* src, int64_t rows, int64_t cols, uint16_t* dst) { + const auto* bytes = static_cast(src); for (int64_t r = 0; r < rows; ++r) { - const uint16_t* src_row = src + r * cols; + const uint8_t* src_row = bytes + r * cols * 2; for (int64_t c = 0; c < cols; ++c) { - dst[c * rows + r] = src_row[c]; + dst[c * rows + r] = vt::LoadUnaligned(src_row + c * 2); } } } @@ -329,7 +340,7 @@ OwnedTensor LoadBf16Transposed(const TensorResolver& get, const int64_t out_dim = t.shape[0]; const int64_t in_dim = t.shape[1]; OwnedTensor o = MakeOwned(vt::DType::kBF16, {in_dim, out_dim}); - TransposeBf16(reinterpret_cast(t.data), out_dim, in_dim, + TransposeBf16(t.data, out_dim, in_dim, reinterpret_cast(o.bytes.data())); MaybeReleaseSourcePages(t.data, t.nbytes); return o; @@ -343,9 +354,12 @@ OwnedTensor LoadBf16ToF32(const TensorResolver& get, const std::string& name) { "qwen3_5 weights: expected 1-D tensor for " + name); const int64_t n = t.shape[0]; OwnedTensor o = MakeOwned(vt::DType::kF32, {n}); - const auto* src = reinterpret_cast(t.data); + // Unaligned: `t.data` is an arbitrary byte offset into the mmap (#627). + const uint8_t* src = t.data; auto* dst = reinterpret_cast(o.bytes.data()); - for (int64_t i = 0; i < n; ++i) dst[i] = vt::BF16ToF32(src[i]); + for (int64_t i = 0; i < n; ++i) { + dst[i] = vt::BF16ToF32(vt::LoadUnaligned(src + i * 2)); + } MaybeReleaseSourcePages(t.data, t.nbytes); return o; } @@ -555,9 +569,10 @@ MoeBlockWeights LoadMoe(const TensorResolver& get, const std::string& base, Qwen3_5MoeLayerWeights LoadLayerImpl(const TensorResolver& get, const std::string& layer_type, int64_t layer_idx, int64_t num_experts, - bool with_experts) { + bool with_experts, + const std::string& backbone_prefix) { const std::string base = - "model.language_model.layers." + std::to_string(layer_idx) + "."; + backbone_prefix + "layers." + std::to_string(layer_idx) + "."; Qwen3_5MoeLayerWeights layer; layer.input_layernorm = LoadBf16Direct(get, base + "input_layernorm.weight"); layer.post_attention_layernorm = @@ -575,8 +590,122 @@ Qwen3_5MoeLayerWeights LoadLayerImpl(const TensorResolver& get, return layer; } +// True iff any name in `names` is a backbone tensor under `prefix`. Only the +// three structural backbone spellings vote (see qwen3_5_weights.h): the +// vision tower (`model.visual.*`) and the top-level `lm_head.*` / `mtp.*` are +// deliberately NOT backbone names, so they cannot decide the namespace. +bool HasBackboneUnder(const std::vector& names, + std::string_view prefix) { + const std::string embed = std::string(prefix) + "embed_tokens.weight"; + const std::string norm = std::string(prefix) + "norm.weight"; + const std::string layers = std::string(prefix) + "layers."; + for (const std::string& name : names) { + if (name == embed || name == norm) return true; + if (name.compare(0, layers.size(), layers) == 0) return true; + } + return false; +} + +// --- Unimplemented MoE expert arms, REFUSED BY NAME (issue #490) ------------- +// +// `LoadMoeExpertsInto` above reads exactly ONE routed-expert layout: per-expert +// NVFP4 (`...mlp.experts..{gate,up,down}_proj` through `LoadNvfp4Raw`, which +// hard-requires a `U8` `.weight`, an `F8_E4M3` `.weight_scale` and a +// `.weight_scale_2`). There is no stacked branch and no bf16 branch — unlike +// `gemma4_weights.cpp:326`, which dispatches between layouts. +// +// The PUBLISHED Qwen3.5-family MoE repos do not have that layout. Read off the +// live safetensors indices 2026-08-12: `Qwen/Qwen3.8-2.4T-A95B` has 93x +// `mlp.experts.gate_up_proj` + 93x `.down_proj` (3-D STACKED) and ZERO +// `weight_scale` / `input_scale` tensors; `Qwen/Qwen3.6-35B-A3B` is the same +// under the VL prefix. Our gated 35B row reads the REQUANTIZED +// `nvidia/Qwen3.6-35B-A3B-NVFP4`, so this loader has never read a published +// Qwen bf16 MoE checkpoint. Left alone, such a load dies at +// `LoadNvfp4Raw(get, "lm_head")` with "expected U8 for lm_head.weight" — which +// reads as a corrupt checkpoint, not as an unimplemented arm. +// +// AGENTS.md: an arm that is not implemented "is refused with a message naming +// the missing piece ... never left to be discovered later", and the row's spec +// (.agents/specs/qwen38-text-only.md) says the same in its stop conditions. +// This is that refusal and ONLY that: the stacked/bf16 MoE expert arm is OWED, +// and implementing it needs its own spec, RED-first test and NVFP4 inertness +// proof. Inert on the supported layout — every name it inspects already has to +// exist for the load to succeed at all. +void CheckMoeExpertLayoutSupported(const std::vector& names, + const std::string& backbone) { + static const std::string kRequired = + " This loader implements only the per-expert NVFP4 layout: " + ".mlp.experts..{gate,up,down}_proj.weight (U8 packed) + " + ".weight_scale (F8_E4M3) + .weight_scale_2, and lm_head the same way. The " + "published bf16 repos (Qwen/Qwen3.8-2.4T-A95B, Qwen/Qwen3.6-35B-A3B) ship " + "the 3-D stacked, unquantized layout; an NVFP4 requant (e.g. " + "nvidia/Qwen3.6-35B-A3B-NVFP4) ships the supported one. The stacked and " + "unquantized MoE expert arms are OWED, not silently unsupported: see " + ".agents/specs/qwen38-text-only.md."; + const std::string layers = backbone + "layers."; + const std::string experts = ".mlp.experts."; + const std::string weight = ".weight"; + const std::unordered_set present(names.begin(), names.end()); + for (const std::string& name : names) { + if (name.compare(0, layers.size(), layers) != 0) continue; + const size_t at = name.find(experts); + if (at == std::string::npos) continue; + const size_t rest = at + experts.size(); + if (rest >= name.size()) continue; + // `experts.` is the per-expert spelling; anything else — the + // published `experts.gate_up_proj` / `experts.down_proj` — is the stacked + // one, where a single 3-D tensor holds every expert. + if (std::isdigit(static_cast(name[rest])) == 0) { + VT_CHECK(false, + "qwen3_5 weights: 3-D stacked routed experts are not implemented " + "for the safetensors MoE arm -- found \"" + + name + "\"." + kRequired); + } + if (name.size() > weight.size() && + name.compare(name.size() - weight.size(), weight.size(), weight) == 0 && + present.count(name + "_scale") == 0) { + VT_CHECK(false, + "qwen3_5 weights: unquantized routed experts are not implemented " + "for the safetensors MoE arm -- \"" + + name + "\" has no \"" + name + "_scale\" beside it." + + kRequired); + } + } + // The MoE head is likewise NVFP4-only here, where the DENSE loader routes a + // head by dtype (`LoadDenseLmHead` / `LoadLmHeadAnyDtype`). A checkpoint with + // no `lm_head.weight` at all is the tied-head case and is not this refusal. + if (present.count("lm_head.weight") != 0 && + present.count("lm_head.weight_scale") == 0) { + VT_CHECK(false, + "qwen3_5 weights: an unquantized lm_head is not implemented for " + "the safetensors MoE arm -- \"lm_head.weight\" has no " + "\"lm_head.weight_scale\" beside it." + + kRequired); + } +} + } // namespace +std::string ResolveQwen3_5BackbonePrefix( + const std::vector& tensor_names) { + // `model.language_model.` is tested FIRST because it is also a `model.` + // name: a plain "starts with model." test would match both spellings. + const bool vl = HasBackboneUnder(tensor_names, kQwen3_5VlBackbonePrefix); + // ...so the canonical probe must EXCLUDE the VL-prefixed names, which the + // backbone spellings above already do (`model.language_model.` is neither + // `model.embed_tokens.weight`, nor `model.norm.weight`, nor `model.layers.`). + const bool flat = HasBackboneUnder(tensor_names, kQwen3_5TextBackbonePrefix); + VT_CHECK(!(vl && flat), + "qwen3_5 weights: checkpoint carries backbone tensors under BOTH " + "\"model.language_model.\" and \"model.\"; refusing a mixed weight " + "namespace rather than binding half the model from each"); + VT_CHECK(vl || flat, + "qwen3_5 weights: no Qwen3.5 backbone tensors found under either " + "\"model.language_model.\" or \"model.\""); + return std::string(vl ? kQwen3_5VlBackbonePrefix + : kQwen3_5TextBackbonePrefix); +} + // External-linkage seam so the DENSE loader can keep an FP8 GDN tower native. Fp8Weight LoadFp8RawShared(const TensorResolver& get, const std::string& proj) { return LoadFp8Raw(get, proj); @@ -585,9 +714,10 @@ Fp8Weight LoadFp8RawShared(const TensorResolver& get, const std::string& proj) { Qwen3_5MoeLayerWeights LoadQwen3_5MoeLayer(const TensorResolver& get, const std::string& layer_type, int64_t layer_idx, - int64_t num_experts) { + int64_t num_experts, + const std::string& backbone_prefix) { return LoadLayerImpl(get, layer_type, layer_idx, num_experts, - /*with_experts=*/true); + /*with_experts=*/true, backbone_prefix); } Qwen3_5MoeWeights LoadQwen3_5Moe( @@ -601,9 +731,21 @@ Qwen3_5MoeWeights LoadQwen3_5Moe( // which `shards_owner` keeps mmap'd. auto where = std::make_shared>(); + std::vector all_names; for (const SafetensorsFile& shard : shards) { - for (const std::string& name : shard.Names()) (*where)[name] = &shard; + for (const std::string& name : shard.Names()) { + (*where)[name] = &shard; + all_names.push_back(name); + } } + // ONE namespace decision for the whole checkpoint (qwen3_5_weights.h): the + // VL-nested spelling for the wrappers we gate, the flat `model.` spelling for + // a text-only arm, and a refusal for a mixed index. + const std::string backbone = ResolveQwen3_5BackbonePrefix(all_names); + // ...and ONE decision about the routed-expert layout, before any tensor is + // touched, so an arm we do not implement is refused by name rather than + // discovered as a dtype complaint about `lm_head` (issue #490). + CheckMoeExpertLayoutSupported(all_names, backbone); const TensorResolver get = [where](const std::string& name) -> const StTensor& { auto it = where->find(name); @@ -628,16 +770,16 @@ Qwen3_5MoeWeights LoadQwen3_5Moe( const bool defer_experts = shards_owner != nullptr; Qwen3_5MoeWeights w; - w.embed_tokens = - LoadBf16Direct(get, "model.language_model.embed_tokens.weight"); - w.final_norm = LoadBf16Direct(get, "model.language_model.norm.weight"); + w.embed_tokens = LoadBf16Direct(get, backbone + "embed_tokens.weight"); + w.final_norm = LoadBf16Direct(get, backbone + "norm.weight"); w.lm_head_fp4 = LoadNvfp4Raw(get, "lm_head"); // M2.2b fp4-resident w.layers.reserve(static_cast(config.num_hidden_layers)); for (int64_t l = 0; l < config.num_hidden_layers; ++l) { w.layers.push_back(LoadLayerImpl(get, config.layer_types[static_cast(l)], l, config.num_experts, - /*with_experts=*/!defer_experts)); + /*with_experts=*/!defer_experts, + backbone)); } if (defer_experts) { @@ -647,7 +789,9 @@ Qwen3_5MoeWeights LoadQwen3_5Moe( // last layer is built. Does NOT capture the (movable) Qwen3_5MoeWeights — the // target MoE block is passed in by reference, so the closure survives the // model's move into the LoadedModel. - w.load_layer_experts = [where, shards_owner, num_experts]( + // `backbone` is captured BY VALUE: the closure outlives this frame, and it + // must keep using the ONE namespace resolved above rather than re-deciding. + w.load_layer_experts = [where, shards_owner, num_experts, backbone]( int64_t layer, MoeBlockWeights& moe) { const TensorResolver g = [where](const std::string& name) -> const StTensor& { @@ -656,8 +800,8 @@ Qwen3_5MoeWeights LoadQwen3_5Moe( "qwen3_5 weights: tensor not found: " + name); return it->second->Get(name); }; - const std::string mlp = "model.language_model.layers." + - std::to_string(layer) + ".mlp."; + const std::string mlp = + backbone + "layers." + std::to_string(layer) + ".mlp."; LoadMoeExpertsInto(g, mlp, num_experts, moe); }; } diff --git a/src/vllm/model_executor/models/qwen3_dspark_weights.cpp b/src/vllm/model_executor/models/qwen3_dspark_weights.cpp index 85e2fa811..b5ec931d0 100644 --- a/src/vllm/model_executor/models/qwen3_dspark_weights.cpp +++ b/src/vllm/model_executor/models/qwen3_dspark_weights.cpp @@ -14,6 +14,7 @@ #include "vllm/model_executor/model_loader/safetensors_reader.h" #include "vllm/model_executor/models/qwen3_dspark.h" #include "vt/dtype.h" +#include "vt/unaligned.h" namespace vllm { namespace { @@ -96,10 +97,13 @@ Qwen3DSparkWeights LoadQwen3DSpark(const TensorResolver& get, const HfConfig& co VT_CHECK(d2t->dtype == "I64", "qwen3_dspark: expected I64 for d2t"); VT_CHECK(d2t->shape.size() == 1 && d2t->shape[0] == w.draft_vocab_size, "qwen3_dspark: d2t must be [draft_vocab_size]"); - const auto* src = reinterpret_cast(d2t->data); + // Unaligned: `d2t->data` is an arbitrary byte offset into the mmap and I64 + // wants 8-byte alignment, so a `const int64_t*` onto it is undefined to form + // or load through (issue #627). + const uint8_t* src = d2t->data; w.draft_id_to_target_id.resize(static_cast(w.draft_vocab_size)); for (int64_t i = 0; i < w.draft_vocab_size; ++i) { - const int64_t off = src[i]; + const int64_t off = vt::LoadUnaligned(src + i * 8); const int64_t target = i + off; VT_CHECK(target >= 0 && target < w.vocab_size, "qwen3_dspark: d2t maps a draft id outside the target vocab"); diff --git a/src/vllm/model_executor/models/talker.cpp b/src/vllm/model_executor/models/talker.cpp new file mode 100644 index 000000000..73ae70f55 --- /dev/null +++ b/src/vllm/model_executor/models/talker.cpp @@ -0,0 +1,59 @@ +// IndexTTS-2.5 talker embedding scaffolding. See talker.h. +#include "vllm/model_executor/models/talker.h" + +#include +#include + +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace talker { + +std::vector PositionRows(const std::vector& table, int64_t dim, int64_t seq_len) { + VT_CHECK(dim > 0 && seq_len >= 0, "talker: bad dims"); + VT_CHECK(table.size() >= static_cast(seq_len * dim), + "talker: position table shorter than the sequence"); + std::vector out(static_cast(seq_len * dim)); + for (int64_t t = 0; t < seq_len; ++t) { + for (int64_t d = 0; d < dim; ++d) { + out[static_cast(t * dim + d)] = table[static_cast(t * dim + d)]; + } + } + return out; +} + +std::vector PositionRowAt(const std::vector& table, int64_t dim, int64_t index) { + VT_CHECK(index >= 0, "talker: position index must be non-negative"); + VT_CHECK(table.size() >= static_cast((index + 1) * dim), + "talker: position index past the end of the table"); + std::vector out(static_cast(dim)); + for (int64_t d = 0; d < dim; ++d) { + out[static_cast(d)] = table[static_cast(index * dim + d)]; + } + return out; +} + +std::vector EmbedWithPositions(const std::vector& tokens, + const std::vector& token_table, + const std::vector& pos_table, int64_t dim, + int64_t vocab_size) { + const int64_t seq_len = static_cast(tokens.size()); + VT_CHECK(token_table.size() == static_cast(vocab_size * dim), + "talker: token table shape"); + std::vector out(static_cast(seq_len * dim)); + for (int64_t t = 0; t < seq_len; ++t) { + const int64_t id = tokens[static_cast(t)]; + VT_CHECK(id >= 0 && id < vocab_size, "talker: token id out of range"); + for (int64_t d = 0; d < dim; ++d) { + out[static_cast(t * dim + d)] = + token_table[static_cast(id * dim + d)] + + pos_table[static_cast(t * dim + d)]; + } + } + return out; +} + +} // namespace talker +} // namespace models +} // namespace vllm diff --git a/src/vllm/model_executor/models/vocoder1d.cpp b/src/vllm/model_executor/models/vocoder1d.cpp new file mode 100644 index 000000000..799c479d5 --- /dev/null +++ b/src/vllm/model_executor/models/vocoder1d.cpp @@ -0,0 +1,307 @@ +// 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); +} + +// torch weight_norm: w = g * v / ||v||, norm over every dim except dim 0. +// Moved here from `minimax_h3_audio_vae.cpp` when MiniMax-Music3's vocoder +// became its second consumer; see the declaration for why the axis is named +// `dim0` and not `out_channels`. +std::vector MaterializeWeightNorm(const std::vector& g, + const std::vector& v, int64_t dim0) { + VT_CHECK(dim0 > 0 && v.size() % static_cast(dim0) == 0, + "vocoder1d: weight-norm direction does not divide by dim 0"); + const int64_t per_slice = static_cast(v.size()) / dim0; + VT_CHECK(static_cast(g.size()) == dim0, + "vocoder1d: weight-norm magnitude must have one value per dim-0 slice"); + std::vector out(v.size()); + for (int64_t c = 0; c < dim0; ++c) { + double norm = 0.0; + for (int64_t i = 0; i < per_slice; ++i) { + const double value = v[static_cast(c * per_slice + i)]; + norm += value * value; + } + norm = std::sqrt(norm); + const double scale = norm > 0.0 ? static_cast(g[static_cast(c)]) / norm : 0.0; + for (int64_t i = 0; i < per_slice; ++i) { + out[static_cast(c * per_slice + i)] = + static_cast(v[static_cast(c * per_slice + i)] * scale); + } + } + return out; +} + +} // namespace vocoder1d +} // namespace vllm 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/src/vllm/model_executor/models/w2vbert.cpp b/src/vllm/model_executor/models/w2vbert.cpp new file mode 100644 index 000000000..9856e033b --- /dev/null +++ b/src/vllm/model_executor/models/w2vbert.cpp @@ -0,0 +1,276 @@ +// 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; +} + + +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); +} + + +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); +} + + +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/src/vllm/model_executor/models/wavenet.cpp b/src/vllm/model_executor/models/wavenet.cpp new file mode 100644 index 000000000..612680963 --- /dev/null +++ b/src/vllm/model_executor/models/wavenet.cpp @@ -0,0 +1,183 @@ +// WaveNet stack. See wavenet.h for the upstream anchors. +#include "vllm/model_executor/models/wavenet.h" + +#include +#include +#include + +#include "vllm/model_executor/models/vocoder1d.h" +#include "vt/dtype.h" + +namespace vllm { +namespace models { +namespace wavenet { +namespace { + +// SConv1d's REFLECT padding (encodec.py:96 `pad1d`). With stride 1 the extra +// right-hand padding is always zero, so the pad is symmetric, but reflecting is +// not the same as zero-filling and the difference reaches the output. +// +// Reflection excludes the edge sample itself, mirroring torch's 'reflect'. +// Upstream inserts extra zeros first when the input is shorter than the pad; +// that case is refused here rather than approximated, because a silent +// approximation is exactly the kind of drift these gates exist to stop. +int64_t ReflectIndex(int64_t t, int64_t frames) { + if (frames == 1) { + return 0; + } + const int64_t period = 2 * (frames - 1); + int64_t m = t % period; + if (m < 0) { + m += period; + } + return (m < frames) ? m : period - m; +} + +// Conv1d over [in_ch, frames] -> [out_ch, frames], dilated, reflect-padded so +// the output keeps its length. +std::vector Conv1dSame(const std::vector& x, 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 effective = (kernel - 1) * dilation + 1; + const int64_t pad_total = effective - 1; // stride 1 + const int64_t pad_right = pad_total / 2; + const int64_t pad_left = pad_total - pad_right; + VT_CHECK(frames > pad_left && frames > pad_right, + "wavenet: reflect padding needs more frames than the pad width"); + + std::vector out(static_cast(out_ch * frames)); + 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 c = 0; c < in_ch; ++c) { + for (int64_t k = 0; k < kernel; ++k) { + // Position in the padded signal, mapped back through the reflection. + const int64_t src = t + k * dilation - pad_left; + const int64_t idx = ReflectIndex(src, frames); + const double wv = + w[static_cast((o * in_ch + c) * kernel + k)]; + acc += wv * static_cast(x[static_cast(c * frames + idx)]); + } + } + out[static_cast(o * frames + t)] = static_cast(acc); + } + } + return out; +} + +std::vector Materialize(const ConvWeights& c, int64_t out_ch) { + return vocoder1d::MaterializeWeightNorm(c.g, c.v, out_ch); +} + +} // namespace + +std::vector Forward(const Config& cfg, const Weights& w, + const std::vector& x, int64_t frames, + const std::vector& g, + const std::vector& mask) { + VT_CHECK(cfg.hidden > 0 && cfg.layers > 0 && cfg.kernel > 0, + "wavenet: hidden, layers and kernel must be positive"); + VT_CHECK(cfg.kernel % 2 == 1, "wavenet: kernel_size must be odd"); + VT_CHECK(x.size() == static_cast(cfg.hidden * frames), + "wavenet: x must be [hidden, frames]"); + VT_CHECK(static_cast(w.in_layers.size()) == cfg.layers && + static_cast(w.res_skip_layers.size()) == cfg.layers, + "wavenet: one in_layer and one res_skip_layer per layer"); + VT_CHECK(mask.empty() || static_cast(mask.size()) == frames, + "wavenet: mask must be empty or [frames]"); + + const int64_t hidden = cfg.hidden; + const auto masked = [&](int64_t t) -> float { + return mask.empty() ? 1.0F : mask[static_cast(t)]; + }; + + // ONE conditioning projection, sliced per layer. Upstream applies the whole + // cond_layer once, outside the loop, and each layer reads its own window. + std::vector cond; + if (cfg.gin != 0) { + VT_CHECK(static_cast(g.size()) == cfg.gin, + "wavenet: g must be [gin] when gin is non-zero"); + const int64_t cond_ch = 2 * hidden * cfg.layers; + const std::vector cw = Materialize(w.cond, cond_ch); + // g is one frame, and the conditioning conv has kernel 1, so this is a + // plain matrix-vector product; no padding is involved. + cond.assign(static_cast(cond_ch), 0.0F); + for (int64_t o = 0; o < cond_ch; ++o) { + double acc = w.cond.bias.empty() ? 0.0 + : static_cast(w.cond.bias[static_cast(o)]); + for (int64_t c = 0; c < cfg.gin; ++c) { + acc += static_cast(cw[static_cast(o * cfg.gin + c)]) * + static_cast(g[static_cast(c)]); + } + cond[static_cast(o)] = static_cast(acc); + } + } + + std::vector cur = x; + std::vector output(static_cast(hidden * frames), 0.0F); + + for (int64_t i = 0; i < cfg.layers; ++i) { + int64_t dilation = 1; + for (int64_t d = 0; d < i; ++d) { + dilation *= cfg.dilation_rate; + } + const ConvWeights& in_c = w.in_layers[static_cast(i)]; + const std::vector in_w = Materialize(in_c, 2 * hidden); + const std::vector x_in = + Conv1dSame(cur, hidden, frames, in_w, in_c.bias, 2 * hidden, cfg.kernel, dilation); + + // fused_add_tanh_sigmoid_multiply(x_in, g_l, hidden): tanh of the first + // half times sigmoid of the second, both AFTER adding this layer's slice of + // the conditioning. g_l is broadcast over time (it has one frame). + std::vector acts(static_cast(hidden * frames)); + const int64_t cond_offset = i * 2 * hidden; + for (int64_t c = 0; c < hidden; ++c) { + const double gt = + cond.empty() ? 0.0 : static_cast(cond[static_cast(cond_offset + c)]); + const double gs = + cond.empty() ? 0.0 + : static_cast(cond[static_cast(cond_offset + hidden + c)]); + for (int64_t t = 0; t < frames; ++t) { + const double a = static_cast(x_in[static_cast(c * frames + t)]) + gt; + const double b = + static_cast(x_in[static_cast((hidden + c) * frames + t)]) + gs; + acts[static_cast(c * frames + t)] = + static_cast(std::tanh(a) * (1.0 / (1.0 + std::exp(-b)))); + } + } + + const ConvWeights& rs_c = w.res_skip_layers[static_cast(i)]; + const int64_t rs_ch = (i < cfg.layers - 1) ? 2 * hidden : hidden; + VT_CHECK(static_cast(rs_c.g.size()) == rs_ch, + "wavenet: the last res_skip layer emits hidden, the rest 2 * hidden"); + const std::vector rs_w = Materialize(rs_c, rs_ch); + const std::vector rs = + Conv1dSame(acts, hidden, frames, rs_w, rs_c.bias, rs_ch, 1, 1); + + if (i < cfg.layers - 1) { + // First half updates the residual (masked), second half accumulates. + for (int64_t c = 0; c < hidden; ++c) { + for (int64_t t = 0; t < frames; ++t) { + const size_t at = static_cast(c * frames + t); + cur[at] = (cur[at] + rs[at]) * masked(t); + output[at] += rs[static_cast((hidden + c) * frames + t)]; + } + } + } else { + for (size_t at = 0; at < output.size(); ++at) { + output[at] += rs[at]; + } + } + } + + for (int64_t c = 0; c < hidden; ++c) { + for (int64_t t = 0; t < frames; ++t) { + output[static_cast(c * frames + t)] *= masked(t); + } + } + return output; +} + +} // namespace wavenet +} // namespace models +} // namespace vllm diff --git a/src/vllm/multimodal/ltx2_video.cpp b/src/vllm/multimodal/ltx2_video.cpp index f5a2d4c20..7a10e6ce2 100644 --- a/src/vllm/multimodal/ltx2_video.cpp +++ b/src/vllm/multimodal/ltx2_video.cpp @@ -37,6 +37,7 @@ #include "vllm/model_executor/models/ltx2_video_vae.h" #include "vllm/model_executor/models/ltx2_video_vae_encoder.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 { @@ -421,9 +422,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 @@ -532,24 +534,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 @@ -561,9 +585,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."); @@ -792,7 +817,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 +1205,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 +1267,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 +1275,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 +1445,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 +1502,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 +1512,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 +1584,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/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/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/src/vllm/multimodal/video_engine.cpp b/src/vllm/multimodal/video_engine.cpp index 656a312f1..62604274a 100644 --- a/src/vllm/multimodal/video_engine.cpp +++ b/src/vllm/multimodal/video_engine.cpp @@ -11,15 +11,15 @@ #include #include #include +#include #include #include #include #include +#include #include #include -#include - #include #include "vllm/entrypoints/openai/video_api.h" @@ -54,14 +54,44 @@ std::vector& RegistryStorage() { // detector may claim a checkpoint and exactly one entry may carry a name. const std::vector& OrderedRegistry() { return RegistryStorage(); } +// A caller's UTF-8 path as a native filesystem path. Byte-for-byte the spelling +// in v1/kv_offload/fs_io.cpp:31, and the same job the loader lane's Utf8Path +// does (gguf_reader.cpp:22, safetensors_reader.cpp:29) — no shared helper +// exists to call, because each is file-local to its own TU. The u8string step +// is not decoration: on Windows a narrow std::string handed to +// std::filesystem::path is interpreted in the ACTIVE CODE PAGE, so every +// non-ASCII checkpoint path silently resolves to the wrong file (or to none). +// Saying char8_t makes the UTF-8 explicit and the conversion to UTF-16 exact. +std::filesystem::path NativePath(const std::string& utf8) { +#if defined(_WIN32) + const std::u8string value(reinterpret_cast(utf8.data()), utf8.size()); + return std::filesystem::path(value); +#else + return std::filesystem::path(utf8); +#endif +} + +// BOTH probes take the std::error_code overloads, and that is load-bearing +// rather than stylistic. The `::stat` calls these replaced reported an +// uninspectable path — ENAMETOOLONG, ELOOP, EACCES on a parent — by returning +// -1, which arrived here as a plain `false` and became the registry's ordinary +// "no such file or directory" refusal. The THROWING +// std::filesystem::exists(p) / is_directory(p) overloads raise +// filesystem_error for exactly those cases instead, which would escape +// ReadVideoCheckpointTensorNames — whose header contract is to return false +// with *why set — and, through DescribeCheckpoint, escape LoadVideoEngine in +// place of the refusal that names the registered families. Returning false on +// error keeps the POSIX behaviour these calls had. Issue #664. bool IsDir(const std::string& path) { - struct stat st {}; - return ::stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode); + std::error_code error; + const bool result = std::filesystem::is_directory(NativePath(path), error); + return !error && result; } bool Exists(const std::string& path) { - struct stat st {}; - return ::stat(path.c_str(), &st) == 0; + std::error_code error; + const bool result = std::filesystem::exists(NativePath(path), error); + return !error && result; } std::string StripTrailingSlash(const std::string& dir) { 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/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/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_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/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/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/CMakeLists.txt b/tests/CMakeLists.txt index f4c3299f2..df9ae429c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -109,7 +109,46 @@ 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_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_wavenet vllm/models/test_wavenet.cpp) +vllm_cpp_add_test(test_dit_tail vllm/models/test_dit_tail.cpp) +vllm_cpp_add_test(test_dit_skip vllm/models/test_dit_skip.cpp) +vllm_cpp_add_test(test_dit_front vllm/models/test_dit_front.cpp) +vllm_cpp_add_test(test_dit_stack vllm/models/test_dit_stack.cpp) +vllm_cpp_add_test(test_indextts2_s2mel_loader vllm/models/test_indextts2_s2mel_loader.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) +vllm_cpp_add_test(test_adaln vllm/models/test_adaln.cpp) +vllm_cpp_add_test(test_dit vllm/models/test_dit.cpp) +vllm_cpp_add_test(test_talker vllm/models/test_talker.cpp) +vllm_cpp_add_test(test_indextts2_pipeline vllm/models/test_indextts2_pipeline.cpp) +target_include_directories(test_dit PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) +target_include_directories(test_adaln PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) +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) +target_include_directories(test_wavenet PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) +target_include_directories(test_dit_tail PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) +target_include_directories(test_dit_front PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) +target_include_directories(test_dit_stack 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) +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) +# MiniMax-Music3 W1 (#672): the six-component diffusers-arm loader, gated against +# the REAL checkpoint's headers (minimax_music3_manifest.inc, 1012 tensors, no +# weight bytes) so CI never needs the 28.5 GB asset. +vllm_cpp_add_test(test_minimax_music3_loader vllm/models/test_minimax_music3_loader.cpp) +target_include_directories(test_minimax_music3_loader PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/vllm/models) # 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 # branch base (frames + WAV byte-identical, mux argv pinned). @@ -225,6 +264,12 @@ foreach(_gateup_lever VT_DENSE_MARLIN_GATEUP VT_NVFP4_MARLIN VT_MOE_FUSED_W13) PROPERTIES ENVIRONMENT "${_gateup_lever}=0") endforeach() vllm_cpp_add_test(test_qwen35_plain_weights vllm/models/test_qwen35_plain_weights.cpp) +vllm_cpp_add_test(test_qwen3_8_text_only vllm/models/test_qwen3_8_text_only.cpp) +# The PUBLISHED `Qwen/Qwen3.8-2.4T-A95B` config.json, committed VERBATIM, so the +# config-resolution case reads the real document (nested `rope_parameters`, 92 +# `layer_types`, transformers-4.57.3 `dtype`) instead of a paraphrase of it. +target_compile_definitions(test_qwen3_8_text_only PRIVATE + QWEN3_8_CONFIG_FIXTURE="${CMAKE_CURRENT_SOURCE_DIR}/vllm/models/fixtures/qwen3_8_2_4t_a95b/config.json") # POOL-DEVICE-KEY (#516, .agents/specs/pool-device-key.md): the shared scratch # pool's free list must be keyed by DEVICE as well as size class, or a block # allocated through one backend is handed to a DBuf running on another — @@ -470,6 +515,22 @@ target_compile_definitions(test_nemotron_h_scaffold PRIVATE target_include_directories(test_nemotron_h_scaffold PRIVATE ${CMAKE_SOURCE_DIR}/tests/parity ${CMAKE_SOURCE_DIR}/src) +# Nemotron-H W4 FORWARD gate (#517, .agents/specs/nemotron-h-model.md §4 W4): the +# hybrid layer loop over all three block kinds, every block compared ELEMENTWISE +# against a reference written independently in `double` from the upstream +# formulas — not against a second call of the code under test, and not through a +# helper both arms share. Both dtype arms (f32 and the released bf16) on every +# case; a LONG-prompt arm as well as a short one, because only 6 of 52 layers are +# attention; the SSD chunk-size invariance; the SSM state dtype carried across +# two legs, which is the only way it is observable at all; the routed scale on +# the OUTPUT rather than the router weights or logits; the f32 router; and the +# no-RoPE finding. CPU-only, no checkpoint, no speed claim. +vllm_cpp_add_test(test_nemotron_h_forward vllm/models/test_nemotron_h_forward.cpp) +# nemotron_h.h / nemotron_h_forward.h are MODEL-PRIVATE headers under src/, not +# include/vllm/: W4 ships nothing on the public ABI and include/vllm/ is a +# USER_USAGE_PREFIXES surface (#515). Same call as the W1 resolver header. +target_include_directories(test_nemotron_h_forward PRIVATE ${CMAKE_SOURCE_DIR}/src) + # Kimi-Linear CPU REFERENCE FORWARD gates (W2-W6): the per-op reference forwards # (KDA layer vs the kimi_kda host-ref composition; NoPE-MLA vs a materialized-MHA # reference; sigmoid noaux_tc router + block vs a hand-computed case) and the whole @@ -747,6 +808,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) @@ -855,6 +923,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 @@ -1289,6 +1359,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/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 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 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 zcmbT7_dizu|Hn~gBt&GC$lfX$=k>hWQbZ{$l}ej-BxSD%DYBB06^XaabzaX)gCrS| zQbt2)4-E}{eE)^-_1krBw{zXjIk)qAUC-zJaerR0+h}X=!q0bt@08|F&!anzXj;tG zwA!^mQ*W;3u7gL89r5tqdhp0j&;L8V(&NBU&&lIQ_jnxgocyk3Y_eEy?&3w-dUH?D z{r`Q$!c?DwR@O4`Hr+rsZt$X`yVk+|k~?tnY!N=#`~U-HJ%rJmnN(%i6W6E{wA-#o zd@|Lw}y?drC^k# z-|1`qU~KF7N|)4pf2X zrFd53vmlz}RAOP14mp^gO?zWoXw|P{RKYcvt-TwMpE6H^fm0Ycqm+)ZP9gM__b+1Y z{usEtT=r_GG|f%xBlRNd;d^@t^UPx#nDr?^H5-7l1OL#pZ`CONpcA9>OBe(H@5Dna z6ArhP!&-$?#B`<*p7i}k*Q)QQ)5#%7nz|6c;~w0*e1|FtEx~}!yWFi(#dyiIf_&Ys zNY{}-jooz1h%@Zmn*$wvtBH@@5?b@Q7QUs|zy+yvpa6#(176>^>(uqb(C{8V<`JbXm=+a{H-%%%WX0RBd zUx;ziGBd4Ct89a^p%mP`i-LOTYOJ1-20JtpiQ!a$9SPz**>8#9@FE?{%eW}koJZdO zOJ!$TWstD!v!rapelXoN13ojqqZ4)iD)YpX>(d>+@iGAcIaBy1Z!`>&e&S8r1oh3w*fQV&!{J3bc!&*gcQB zplsDMX1CZ+hU+^G8M$3x{=I_kwQeAGYM&tWUpVgLso;esMYL}cqS0$S0S*n(9p9>m zj{7-uE_NXnzh^?+KLf1aoI^G#rD5vv4`k)CDA@kf30#$S;Humt;NyKDddlLQcUxTO z>y8#Wo70Gge`>Rr?0fJa?-!^%)Q7v*^2pflFXX5FFQy^q6k@A480dy!vCKLAy@^L7 z`rJTx?J(^h$Oa>F4)sa{*z@abiLFQ=-s0Ai+(T12v3Dz=wcrjGEy@Ou_MO--Uc}0H zxPj@r+l;#28#0^}Of91Puv6R)Ez1o-BsUcfyYdqOlXbxN;TyMIQJg36SO~whYA`de ztD$jP3ptlM!p4QjV&D6ZjNbtXVvTxuXW?yf-?folZCDAw-vCo`iPeU>*&I`IF09Mt zM~_VcjFtWp++sWhN3VQiYCb;V=3nhVjTLi9RmcnIfA;|1tXd9D?^Z(^?+_Vv3dH5* zzpOMDEXN&>tEqsqDA-nplBgv?z&jrd3O@blI=l$X<|ffyed4%>YliXDq%d~v9$Njo z*)rkjWum=i0az}wqjY;5O%K+FpqMyfy=@p#)^@qe)2 zXC2I65s&S~&!~{&YS#3>4%9mtO5Yrbg!tP8%8rzRT2dT%OB8{J{|GU=cL7bedO?uB zDsM_sCuDlYfK;y}YJ7V__Plxk-*)~-BAeP-|2kJ_w)@K5_ev!q$7I0i z`%UJA&TSek8wo8c<9NgJ6twzz(%d#BP8@G5NNiofTymdF3_iJnk)tn3-ZRL^N7ti$ z$5p%=z8jn5eefdROdRG(p>@zrOmMqRC!DwAzPe%hwQUM%ofQpp&poiRP@Bolyxjtq zXTN1fw-m#T>QsV)|9?b{YeAmF4LznW%T=Qd+@OSL8>n(;KhScnC|ZgmrEr5BA{HFlh7hr>Y4kDG`yLE>gUuUTUdy5 zBykxG@2zG!+ce3G_)F+z@4|HE?}yrlMs#(79W@&NMHJL!NkVlciSH>VU(Cy>X50{) zoES~@uLa}nCHlBv$1eD<`xp69u@0^^*U{Zm-p~y>mvFSXlUk<#A`i9qGY3D&!l};x zK+K?vDDbJV|1z&*b>mc!KB0#H^`8N~v_}|TVu0SEdJz3F5sJcRK}Y6&@a?*XTYjfv zy`LQFI;RH~T)r2h3I2x*%53s7mD%IAOKJY(_CcH{`$^X6P6TE?4<*65Yk?8F>sp(8uvJoITGE_bFC89NAs|}HUj+aC()A8MrLzjHfm?h0*$b% z%6KsbvC?0dFg*gv>RxoMO)z|Nl7yjoIz;~UDYAY?PW=;)QcUTo zM2F8hFfm^quf2}Lc3ogSpAC}arejqFOa00Yl5l9L|^J8V@! z{+l)Wz8@iX;>|$q?tN}n>{N^on8Eq{Z~^{tlcu|2Dx>yVgT$Jag51rEBqih_xFueL zjl50pOPaUQte0AR%VK-8ML4Np;qcD;7B`)X1NMK|%IaA;Pa(W`L zID8N#@9=S~rn!OfUJ26QKAldyMp9up2zOs;vVyuSYOZOer{}t1uX7e8ohT)hn?BN? zFGNY|%8yKuumegTJwZ8pz7T(tVDj#@5GR;hO5Pr6L!-A6)QL2BGQoG}~X7jHF+}Q^sFekC(|srpW=+E$|J&N%u{v>I2Pi^B?|O^i;ni)Hq6AEG$XMJ!*J zVR6lUETArU{P%YJ;bIB8szKnfBN$Sr@WH{IJ4w?CciixKJK8L*nd~L{pyK^)wBd;p zqv!U({)(kiH-d4~iXi$~{3u~|Wnt`zDV%w24)Eo^2He+-q~F>japZ#=aHUmA;N<&a z9~+DEc7ec43xa1A7f7FZBc5DdNLL@Iz;8YqP&G-FnvbWUdGcPo{U?w6;)W56J~!qmwc;Pl(e7_c2SA9z#exzYXFKPvz8X(B;Tl=HaIP-B{&4L>{?6VM99t zz+$}y9rZK84IG@G1bM{!!XNH-R2$pYU z>G#1u%*D+j(Bk$Dljh67xE()dyfG5@I>mxXTo>Wq6rqyg##G}+2OgL|4omE;Y0ID> zzN`NLU#@1uh`k#w>ix`!znTga)_znoVG#y=e}oM`Sg4Z~;=FZm!VBx~k#{@a!jU(E z^iaz~$QNHtx^nnP?z#K)@5T&V52uY~a2Z6_w#DGIETIUr!`Msw|SIn(_2+XM-H8 zs7cX2n0QbP*(QfDXqzQ>TwfBZt>=SHdM2blcgHdRMU#7aAOs5NK$33;j>@Nl@yuGf z+cb}?8|4G_hKC?=)CGBhoy2RC7D@g1kX>FO2h8-@v};@l=bY`OIZP7`oR-K4>TiKD zy9)9|R{{yehISTMkcKAKLB|z z8<_KE9GYUQ%&Rv!fre5nv!F>0I-)CyubT$PuJR&O%Nm2Jp#UfK^?$f>-VP`%w}fSH ztl_Jt4P=Z-Pxi&PsJ);q?iJg}_-?HwTdONTq;L(p?$&a=x}}e*?p+CDtItfSd3k2Q5+H?o4KyYL&Mw`v$1iXr% zV=9l?b$t&=?5{i;x#A1z<|fkA7oXwvPgRIp_KK|7nt~_#25`_?fY`?Cf!&{tMDTMw z9H;!Kj*-N0N;OObDX_=Kt6}BzF6g%tgv;IeM5g#JNIA3-p~n&U*e{8kUOq;W>TAfe zvQZ3H86vT{qL6sPn8x%J5?9-3`2AUox6&dXROAED_iG6=7P}Mzu1cYLV=MS(<)hv3 zLO7@Xit4@Zqfyq)j=o2G#KnHId^WJJuE#i+1r z9gY`mqVCek5DxQE#l{?ZGh9gc<_C1~YdffM-v#+M1W{0H9a?ERT1=B|#izF=@ftT3 zj?SEkhBXl+yyH6k_ECp3yta|LU;T)>3!jmFwbOXzzE1cp&Xgng(3^&ASwYel$>K@B zdEg%uj`XHK3dQTm44pTagZ%!X^pJ#^~KX)s^(I<}Nl zW7VIFWNP^UQMVL>bw_sA#&Lu{(%fUS&1Is$@FhdG_ zyw8a%U~Xp>RCD{t&HNJh8Ja?eeXCJvBp!~*UB;Q!OCaZ!BOV**rYC2wL4|l>j9vc* ztSsKsJt|93V`B?GH{OKzk2jGGb6tsZL=dDNK7ma&(eTArkjIxl58VPNY&+A={(h;? z4m~jhy|O@j_@5?>Er`Y1gbz6Pl0T}b_YuWdH|Y9OhGRP~L4Vi39M(a+++#X#V=A?8T>{No+u>tgB#D|b4k_X@ zA+3&}?(}`2s`G`U{^BwHf@QFIaRRgU$27)j#%7L0b`<+|<7%ony8|0m-vtK~L1O86 zg3c35h0`x{uz%Sel9jX%o&71A-CPgK*Y1)@Jcyc`V_{lTKeO~}6rEdsf!`j? z$Up6UROoRZEI$#0k++g?&GmY0l-Y%CHJv1Nnjl=K_wi0>qt%(kr;yd6SjRUXsz0yC ze6t*?wDB=@aJvq_Z)h=LO_4-6Odir3W3bG>2$q*=($I=;*t!^S^)YM0Wv1dur3l!) zL;$wwSz!IrTJHW*E37iw1L@LXxHI}Wie&AhMI;wMY#(kMEXMtOQS{UA8LYcoADXWG z1h=zwWYnxMP}~r*D`$YaX*^XljKuZk`uIFpmYTb@64tVt+cnBUlY$8PtbGAb zlLg>VjycS{_YD?yZiS4@qqIH7g^Vu#Ze^#_43WcIiKvl0YP9O{+B`zxvbZ)BzZhXo zC&}Yj_A8R~7MZ-&gJ2-lKxER|$v)jV?CU#|_3vJ9R=~B+idiG4bVxPy!UR;$Xwbz>9!a=wSkHymbE4s605 z-j7MZu|?#?b9uZvD+tFg$dcK2!l_4wAj+R>BsF@i=-+b)w%ptYbz4U1g2H*s0UdFi zpQXx?yc>u{vKPT-??Rkob{2~s+hN4Cd*p_=A39! zoR;fDMtBDJM;*hPre`MmglojlT8Qk~{gu4r9EWvG53$`d1`~oc@a0e!UB`bCHa@ID zKj9g)U-%xW68uVjC5S@8*`0WbEkv2);;^qQ6^qYhfoQ=i^1_cF9~Z5}uBT<_ySW{t zQd;rDzX;5AECMEHfaD*mWf~$oh_+1*ZaS+)+?y?Fc;-V;kUNW4mQ1oYg^x7nNG*g+ zl*2+OfyIlrQm?o;=r&);er&sjg;oSdCi`vK`FXTXWQ>@-`A43;Y6oW670So|;N^)P zm^S%aOV$*C#}o#YlxnF#WDVFXwP12+GIj}Y!Qa&s8lJbY3l|Ks&+|UA2mc6QzRpVs z$g2bX3wz+%9}y^+yx#XKQ&6I1HM+Ab^zEC2{mmLQVnckLW@syx9+c;-e76o-3esV! zdl4vho587Tj^t5V6nY;?hik3FF#YgOwDC@-Vxir1mC#p`q`DfGUMwN2gNM$oo4aV7oBL}g2t0)Se zt6<%Fm$9uilPp|mn5cJ6;S@zHz}wx{=o4@i*4H$U=B1sucNnb_TP~uioj0s{aF7;E z-lx8E1$A@Qhn|(sLC-CZSo^6#%^$_dY~%tMEC$m0hes9t&tq_t2s+y>0ZUFWiLZsR1?4AUgBFS==sLoGFl-3{Eu9bjqpfzEcy0Hq^sG#>d-aQrP-kr#)2tLxzx z-+KHVD*?$rXJYQn_au$C83IlUvOShXP@koYv3f?(GIIq4en4h*(IxnrDo?0f8wk>E z_(7(Y2&;(m67-$nkN5;h@=V7KA#vF0uT8Wp@+^Da%fR|7d%QUKoMdcP~+5!J_dM4oE(GTT<2B=t^NRIRHNJNC=t+mj2i%O;rYR}LkA-48+i z`E6L}ehlrp=ffqLeQ0nYi<%usL*7IKoo?ZV(q$`wXh(yc?`6_^ZVmo8z{6IdLh`~@ ziD)Xkf`#%+@!^H%L}<<-RB}0l4YHR}+aLj^nrF}_mo8I@wtLW1a=re=JQW_VEDh4; zrIPo~FUhyl!|0v=3(L%JlY^f-@%3OGie)WEu5S#pKKB+DHaC(Jq9(Agqy~d-$59RU zVFXSRBqsWSmPG|QAoZ5kd>8=#O%2d7;}VrP76d=!^QiPcAzU%nh+*U&!17(;q-I=@ z^t6?L&c|=?X1T@WbACt09U`e*bP~y{XohU91l%%e%xuz+Bxo5AmOmX~r_mBTyVDJ% z!pcZV#6RLw5{_bj17V5TbY$nXL!f>twdSN!k69Vyd5Q#)_u}Jx{P2+6J$@1A=>>s% znhuXs_JLJ#m7wm$n_x&lfNY2pB_9>?AhmfpgtjPy^}AB|wcm(N8Bt~*1gEi1z4Ots zVhJa``K*S{62!VkeA&GbY=Z#L*&qsU zZ@G#V4TfaJicTv>m1#`#RaMk{84ntvg81LdGEz6}i)Uk}gRfRH$Q_Udko*8`4+6nM z(G@y2Dv+#Kq3ltwaW-kh6+Y-ilj%o|fq6KN@yGK>5x0PJ2rZ_C2NmJE%_FY*Z~z3H z+Jx5<>~Vi+DhS&4;EYNxcUn~lDK}8!#eSKGS&x6hkmxjCsb?$6s~M#e1&!#{SPH@S z{-f^~+#^jkI;dyc#)vrm#V7IKSi{JBR`KIA;HkSh=ZVrRq}g+D{L?~AeJcs&+J?Ar zQ8di%xeQkQ^I+c+Z#boX1n+c5p_H*GvpIV=E1~5HExUrKNS*=cK1qi39nM(h+5)~0 zyNJ644;CM+rF)+9^P%F< zHq86@6N-gqar*vsk%Ti!=wrN@%ykmr9MGB11{qwS@mdSW#-G-xUbK>0JvZi!tvQ3w zbf&>d`+gW+QjK@RdRXtdrJ=*WaQRsU-$+Z+9qfr_H-o7EbP2%DFrWM5I@l&*nROi88NsevUN21E` z;L~hzPUxP$R@(=?$++g^o@FMBKU{^#hjbS>xm+4*-zx!!Y=e6n=72q!N32`vF;X20qdL!?a* z>Hh1CSufPllHV5BycC_R$Mczj^%D4_E(njkS_+L$Iav9`0TY(;Ky#7@|2QCzI=%)t zwdfV*i8kTY9j=q_?j0+)^dT#p2e`Pjyl&i9~T}z?D zm6EdBHE`{b2cBO0jpbF!!1CP5EX;8}3D@hzN2LOIcIFHWzwHFiQ)ZLmYuU8m>tya` zJjpg9F433oC8*lL=hS)l9=@92va{;BmBog*xCA(V8 zh}_}tM|ON4>%aXD_Q;&Xu{bW6IS0TcvHh^a!Vs1Gl3}?}8>uX1zzMan^r0B*>e&az z0&~%9k00pVup{#9NAgA170xO((o5TAX@CA75YVy&uZYX|z48mGdZLV0g@m`h`a2Eg zIMG^}8z6acE}ngOk%`%zhg-Z=P(dpgrQG9aL-7gR*JTf#;h`9`LsHw9HwJIoTd2g^7i7C$HR@&8v9J6T>Cb96%$tb9kWyvHz2k${FV-Tn_A@ma ze8%M2p2AAD#qy#+A{bpgOH3QSLClc_?A}d}>4NMDG@3a=4)OCnfst=w7`04G;N)LG<-}3& zH+zlmO-_J7i7d<*mE@JH=1|ur8>)8t77?C%3PXzd@$7m_j+ICw+!Iy?X$KzY`!u3W zsxBuu;43w7h@!4rOUU}#Ml?83MZLDj;Kh!Y5dKpVgHySb-}VV;2iu~}*H&D4hLAfl z{N%`z7zq4X0zI2M$zhG{z`m)+VmTYaJGchs)_f*mG#hSDuK)DN$y#TgAs$|~eA0)w zLmxf9KpaY5<21=+IC+r4H%+@iP2mYn-6g=AHt`?BeQZMYUsaP34?5XT+ENQ!eK1`u z0s~)0sKruqqOR|ZuEG9PWb=M<)RG^kci3RnB#VR#ZuE(l7V&Ri53X``)cvM2Emzaw zWk0%#>lI|#?lq~{vt5js%rd6)YOg`on;d9!$VQ__H_4HOs~}f0oyv6Dz%%!iaO>d( zsGXNWBsVJYJQ`j@Yg8(cdT@@ub7;lYM@!)5NSWBo zr@&XB9)A|Ry3f^X*9&s?#U*jS#U;RhU+$8k#KTPI9EutKNXqQGXoSsj7?a+IIYGjl z{E%6&Q1ma%@qdpWm$Z`;8iAAj+ZRY|v4r0GqvWv3Zurr2o%GeMB@WMKaqPnmVj3TS zamh`p5j73YMr?;GlGSKhoP$eV7t;-Y%3x~Fb}(Za;flz2mPXxVvcDHWPJ;w1Iz0K` zQOad99r@~?zLJF8dNY*ybDeaC?q_L$E>Sy)i?IK_a9g03!{A z9cVQKW352|wHTsk#i zQ7VuK`E<(HXlm|rnyB#qf|LXA*ts@ID9UKj+g?{--|eFqYc2uztz7WA;|$#ObQRo8 z8HYL1(=qtvb(Gz(5ig#$s(&;-3v7A^7zb@Y3H=KwFkKU@a}wauvyB+tQ3ydvQuwNO z3VD1`l6QDxy`@t+g8p6xyoQ#c{D)7dweT8teEmeO-g`Gd- zv3W)u7LCCx09pJXm=OtV)Q{@TLN%e6Mcl&piWEZqYS z^lQNUV@a_6K@w)zci^#aVyInzh6Ig9qpFh_!vP&Akut*j(etU{@>m)dCrQtD=pyi? z!Xy>RyT-l?%oB%|H-gb{hQE(eFH{ERX9(B^3nUwQWAG2ffY+N z#zrqKjFr;EBhIsU&KH95MpO*#a#1E1nb_Moml2duWt!=UY5s467` zajG?}Uesw-5+I5VQ%tErj09Qcd3VhFgy(@0P9 z-}JSXPBMi+m#e4`r&+C8tcitR+ed)xjR0Be+hF%Quv?jH@72G?Oii&m^zg1UT9KSKw7(FFm|=GQYlmf<};X z;$skre`AfPK~F4{ow!V!v=73m^a?naGnto~k5JJE((wMOA3d3;hE@3)uy3URj-54T zwFFL*%su90;P+xo*{F&ab#lo5VGHbgJxY|G&V`Y{aMUd=rA`&PkgFGoV%yc2Uot^> zrmmS;SGNVOIF`_qD$b|ns`Zj7k950H) z4bD7_?}%Y^kEP&fClBg#j8QPq5sH%spvUqZ<>~Nq{)xBI`gww6SXd8_Rp-*O+N&_# z^F1;AW{KY`7Q>uRDqv#X13cpbAW>C})%GtW?jH-rni1f|UZ5NPbAu)S!~WCAsgvgBu?Qk#)Py z&_lilvDaV&Xr*n!_Uf~6Oyo5AwD=HfIq5@QJGB56q7OqsfeMUnyTWL^cLUEoUEG2N zE7+#zZXmmJD}_H7;HvIIkoO8@rh1$sh4KT`%v%AK60YFUp8u%ITOpjtsU@Xj@|=5z zwolR`UsM=+NS;e@NlWz?TsZeW%Ne`|*N&LszK(}pxw0|pX;my_gQm!uQV6IV_`{J{LBZ2_)sBs;zv;I;B7*p)n;w7OknHYQ5qc}YVUr|ZyAeKD!`J&9XZ zAAm50-MCzH3wnJvg{+AMoX}ZcXwUv_c;)svT(r0y4iyDax7wSS=2`^VzjJY(+$}n6 zSVaaMmciYhVd%YSCMijaV#&KR*ryc>K~p1P`h^a-)pdsM@9cyvZ!e>7P$=_We>e2! zK7kXRlZ;{8apr=)0CdmT0X_E(A>hji$amT@S??^tdcJt}`eI*Dn7W#GYW*(ykN1k* zaW9K#wp}5|PN%{xi9FcUr%E?Jc|ewA3!ux3r+9aX2;9BC2Fs*p!sYlIkeM=%r<~!$ z+~i#Wbx%pG@%~NZ-an>&Qs-dv6m3kFuYy&(Jn@VDXWEzJh<~-tf#FkMT#%JTKM5IN zSyncC^>hj*zd1ywsOVFR!xO zX(u0nux}GC;cUQP#>Qj^J|zu?`egC0tu!x00YYMaQtWt5FYa`}XM&%sHWkU@m6glk zd!sM#Px_7vuf)I~o^~#u z8FIsu-&3$%h7V=#@!{hyp}5n>nr<@m1MR?F7;v7C&YB@ho^7YJ!+VT9rxeQ`nxBUu zmD}i+N#3&evI<%~C})n_b})`R_OgyHKqCUnX~LaC?0(UNngaHSrU&p%O9QUk?@If> z>oL=m5+P>CZn!XJK;NI3fCO=GEIYa#()VVNh72e6R(LrZys;15YF7}mN#;4|TMqln z($U`5mlTRm=f!*Gfr?8$Tv*{ql$0M(@nLUVak`6D*E$dU`VM&GQU{$hr6BD(kLHOW zS+K7Pdg7blO4b)@65fJAk)o&>DMLjz`GG*>5>nRwoVfOtQ5R*3zY}#x>M zomVqE-*P4$|10$H`6rnAXN1m;2*RvCW8`{QI2ybx0DF-N+QK~#p}a6q++UALDxvUt z*%`XKF9-%!9|Y5)UShKwacSZ|+AVKOR$OsLr$1ZZgt0bUh*6=B16W!*$>7JE)k&Ik zJ1m%IP40X0bJoASMo+Yx!Ux?r)alxXD`kG**^D)~B|s8YHr&J~pZ=R<|7!SmwJF`$ z6ppWiXW*rO5wy~MCj`H~jf;{VGbgj;=*i7@(fGswE}jw#dHS|i7gOhgYMUg7f8rPj z#b#3?ok^pYD=~v633SoJ1z@x&1Rg4<(am~Gfwz1ubqG%-${MAR0GBE6nl8+~I2&aw z_SXNXQ09Ibkbwgo!kjo27wo8;Lyh1sz5F(u=6e^@Ug0{>ow64sU%rL8&QWC1p{PlB zIuI7tu16ssMNqL>&q(|lL^Hklczy08QgQJscXW*rcvzc{ukalMq24rfG{c{`W{E>@bJ~)dtn|cTLbQhuUs#kQoqc~RoIze{I zhoR@(1lH<~0B)>%f)nu%v2C*`NSInOUo5MkYq*587e*q@d`YFmCCJqdG2D~wOV(Db zM*}x6Dz1NpZl2{wyXMR!iuc>_@Aoq>TzmnNo;#qyCm~Mx_&r=PauMq@xbXXj25(Q? zb)5cm8b`z-gTC-BqbeEi=rQd}Bz$2dTIu^h$d5cMIy8AL+lz6y%Lq*p&y%ax*YIZg zJ`7w+i9|O+xJmmKg;xLmnr`z$h3}-lz)+;?`Mi18zd*xT)(Im(@c6bq3?3%_2 zQ$B@$J=L{lxBzQg6S%d@!XWU? z8r<*x4+l=R;fVq-((Jkc?8X*A&y{$v9$SwOBtBqHjX&OBkV2PwnPI8dJ^16BKsG7Z z($LBC1L`74YGW#0Z}N!-uiHsEE;D(_N@t<a+OM-x8H5kakVu3h%$}-wFgSmN9gNQ9GDe6L4)+eSheTj&^_ve9h0*;g`fYB#~Fuc zHw}P_lN#vxU@9=)?eN&*BuUuPz*>~Z;+x$U=vtMd+`A!(V60)m*we>EJCC1}ZF`QI z*k8wf;i;76y5;xI>*6n{s%Bv zmkE2f#%;+ zOS;`;7TmwC2oE3|PwnSY<&RJ3u7VkmqWyxJY%qZ*W@m}$t&I>>S8Vm<^fP*D-zdg- z_K*YRs%Y8M3>D22M7#Oxr01Irk9NPOJ~r9#uAvh@EVn@Gfl%D5ca2=*h@*<*b22As z8kWA^jhy@OG(OTA*1LB=YGgZkFr>iiRZirl<*u+iw}FFRUartJHxqXMo=F03R>8T8 zFNnsbKopk)7&&8!YImlQ1L^1Jn{x(Wtpyy1fOxiv^8-Ms8#YU9M&9iqOd%bN&hJDT z_VydW^y}o-!(#d)Y#-Rm1w(UnC5=%}LC1s|@cKCjVIGH}rD`j#%Z~twtW4Ux(`Ls zK8%=D1pMGv#KZfJF&2&Etks;edaJrokBQ(`!l8RMF|?Uhs2#V->$Y1r29Mkh!IaB&Na>23Li!(rc$+_v;e$^ou3a z7EeB>c6ZFU-~_{C1t4{-ixjMhrF-6XVtUaHrf$a!cs=7c_0m>Dg_t5H-!%lLmukUa z#UZO}ZA3+b8to!C;G2Ij*$=)moo%)a8lY{N2moBaSZj*7v{x`m|NLmMK5 zH;@%w8nAF*37!4Bp32XhM;?d$BD-I@(fdwwKyBGu;uGpgqyF5aS%%^C;PF7Ze4``k zeoO|BWeeGa*NeFhqGdQ7f1WPeokhdvOvAb#%7nkn0hC-1!yb+uu4%f01=|W?!T5aQ zY%9l8Xy_%+7Y#s?)mK(g?+NlXOJH%t+sT>3GBPl#!jZP(;#3nc;OJ$MOq&!~A^d`z zavmiA4OUZ&{*9o=X3)~gHY?UJlf*@+!@Gns`ax(Zf$j2~oVD7}KI=MsUJ?SR_=suB z$RiEypO~(&RR}w0!9Wp@a&9C*-+}31WU&^rH|Uf3_iZ4^88}NluJa%~w}2A+5?pL*2mSZv!?1KA`S$QE-KCU|jye{#UVaUf z^u49$<5H1dRDfr??hW_OHXj&L(}$J3y7X}RWX|k7>HoG7&8C+m>)d&)eJ+cy zUoJrXRB0$;?vvGuTS(&jJ|-ml8Q9%V#-HWcu%_x9s;k~ZrIrzRuNFpk4fRlWHZ-BQfYwI@ju#pFVbK-NTT( zYNTtyL(-QgiTS70;C=T3m_9iZwBS)bq;FUXEn}fnD58Rn^aw*pqZj2V7f~NOY0mGc zG?Y241K+K)@%TU*YGv^w&w4&R?|Yg~UocFrPWjC=tg5v-ShEVoR)moy9R*}$K`;F) zE{bM9lkkAjPcq|#D);_2b@u3@D7d^{5RaXh#%rChp$%H$xTWwR+2LAA_vARyxzQG= zZPI`voO5V@d^Ho#_luQ0eTg_WUMJ(FN#x7^Z{X!p2jyHztgW~W-+l=3hF9;TvOiao z`*+2lT3wuDQCZ3S5!r;g?G7-kwiCB{-9hU&O_SlvD3it)itO!Ntlbli$uf1+TrUN3 z-B1Kj>l>OaxQz&T^8S&jp3W|c%mw1=kfDC@)kiHr;P64Mxo%69#S<>M3zewL+geu zxLABGtN;Ea@GJcyZkgg}Q!$k)-kQTbYp#PH{eZmRBUaldo{=?wHxePfB&-g6ih)w+ zVf86LsEl0>oNtD#($6B$aL*>rzLy{)Z!UDjf1#C5b+~r^VHlBroWsG z_cwP?pXZZ4>hXBUe`k)x83p*sd@B?-Oy`LX$D!2?0iM6YW0GNf9}3D$arNvmv?&R~ zj_xO{>DXD4lKzH7c8GFVlNjun>}mQAy|xm&p$;n?_rk=!8m6H2Ga5_uke!C(th;Y3 zNSytIGJoe|VxbnvVP3%Al7n;!%0% z|M3kjPV&U-Lq(AID1_=ClH@ddb--Ku1*Cn42D)FaW%CMOPp+dk#E>;W%RTqWf8iNW zHaSDFw^oum{q=?&{>Hq!9`Bjik;gD<NI#Z6Wdml+!)>z#dEeO} zn%gYPxo+G7V!h2^#~p;;fD^deJPDOVQY`+y;X^EyCaJgi=)r~NFkBW%N3&B%#Yaiz zeYq@^PL@K=6cKXJI+zR!O+}+AGswuZ@6`ACaq7>DXE-UQcv#{WRgBVvW=jq^v;GFW zv)2RT#soCIoCWoq2ZT9iM=r~a(h|-9Ibj|FOs)xcrn3brR>?!zb@9+~wgJNelzBQ1 z<&0OR4t^8AIO(4*V^&8FP>EG>;BaM-`Fc_Uf;$r7@Bb(|@2H-?KaRHxE$vX+D3u7U z_kDdxiI$|SC@T_?NVcXHNu^S$sA!?0{l2eH5>jZOC^JPNk;?eS@BRC?bDY!Zxc9za z&+&NW{6%5EE}YWwg-Nb$2C;{7_)jmEE)uDP)z+IKO6LIWd3%ih?JS?tT&@3a)#hvF9vN7<0jj)(>2k?G4R(p9qsbUaZaH zeDMS3|DoN21}-=5#pLH8k+v>|J4Q;lDSU{!FJ-X$O%T1csgbrfh7zs1OYn**K*@ii zD0JvQ>K#8H&p+lgj2S5)$!D-}%3?BiT#GL2Sj@i~Xhf>q6=09tD0L4ugBs^Lw7!|j zme0HhwhIoz+eDUL+AxBDe%7#gh5o4jb{R%k)gXJLlX}dbfFlQjsh79{QCimv^SEBw zj3Wbd^$SCM^Zpvy%za-3TE9nm4ONohsYu(#OUadg_wb;)Ij$e5rH+Ha@TzVeDyKxk zO}S&Bd?W)N8^_a8eMitNv16q0Gn`41qaVubp|g1iyb`nVxi13-Ip!d?#{zbI=pgp9 z=cA}yJqRa0gb1TKcort1YH=O;aHyF*TBuCrT&KVbCQf)q*8SZ?hvXkXRiOm$ z-HiFT!{`nfop6;J)z`tzxk$Q}Ghp(qhp7G8jeV}SYG-&Jz|iU4VE5_<&a@Tc!Rce5 z@*@iS#&{UcD8h^lGx5b?4`z3pCo|W=n%QGNfLZopq`@hcwst+nZ#!1v#$N~F)V?2V z(V{SNtVR|r#5dCI7Yb;bQ8WRsKyu>O986YPMSm{t9N@NySQ+9`d_wo2Bv2T1>r>2$t2Ch}$DkGz^#FxBN)PpeKt^ z-@6cxYtF#?EDvJKDww1}Q!w?>VOOV20=dyYa98{n+w>uaaf_V*A06gXmy9$}k+!Br zfq9(gZjCt-Ep(|`8wAK}fV6BDSu*!8m0EEIT&maMsefkpnb;Ab#8VjB|ADm^I|&oF z34pC`I?dh~1~wLd$fErzkR!2#);xcXGHf|M6A8nw$D?S)5?g#dMB#^*Gz1ST!}H&* zxc6E#%m`G)|PN9SMEuI`qE6gzVH+JWw}z_jrQPjd?LAfXNe(;hkDv?Ect{9PFp$~avce{DLsI9y4+hV$Xq zAeZM&)FJU>am-}DOBl2}9_&`NP!W|8@?f$e-XHxz<{!|f=|A7&%i2zi3)qUpcs}2| z=K)E68-nj!1^DrYjmV-u5`3_2VNHy980<3_H-Ir;YRVP*ksm>-US)&vj{PXHmh&}~ z)!_O2P*_%511n;bA&fGgz0ctrRv_RLs`?cEC@S7#ymud=6} zD%`h;;S3Z=5u$Y;qtSlM9t54&(5zM$a=lxa+MTt*E7yA;nbdRaV(U`)oH?D_&zkbr|1r>1g z`3&^zHzEd6ZFE|+054Tx2Jfwn2Cws^9$8(GiH27@*zu}+_%3Y#>#q}x;2Wdz=XhL~ zoKGLV`$JBDt0y^Yd+DmP@*ut5g$%9NhGNwOBF$67a2GW^>r@H`U$sa;%52E`)`v08 zX}H>PfLNZs#O3!D@k_yOc)mUYe;qP}kq{Hy__hy6yWi1a?Odyni*}@c#F*ZkJ(p%o z7@`|}J4k)iW{`Pv!)ok$3fyQDM!!mHtIMrp*tS3lDn)GI?KWwc8Ysx=@hRBH)Yp#x z$wuXeeEc@F5oN#LA&(PPAYy75GcLFWJG0-gkCb=fz2rM|&%-K`r*jG39Ua69kHfHN zMKi2DZ*H~q##T5q>`%6B@<#vQbvS>^Ga`Dd0wh*w@o$Cgq}>)=R+!TUtu`p}OOy_f z2Q!|Nk|$hFu*(3)B!)o3T?&;X>*%+XHDJ?GLwE033fW0+uz80dugxVEh7T`6sVQgh z+OINn_!Pmuiqhk?mnYzvYsWBA=nB0m)J4>DN3hAR1HXLr!11q_@kML~nqK<=p~ik- zFV#tM4BD_vv4{1(ID^Z!`=gKKEg1UgjC0iViNm8WG<(ic_OQd6T6Wh3;-_wm>%)v8 z?pO()ZFj~xD-FUtnuA|2XrYR%Hx|Wne}e^&@ZMz$T0CV;yvs^t=aU?qK#Z` zKmgQ|WWk_~hZAa+fVGAmM9dP#tJ_A|^u5z-<$i2}>Y*g^Z0274O-y#6k}_K*Xa z{d)NF+ZXuwv=*cVPD0>ZeON*nJc?GdkJ4?HN z^T8=M7Ux-*V(kY*v=!e0!bYFqEyeXTG93-0Pld{T-|~3*$$0zz@@JqBh41vHv?45)W(ejvf<)k&Gg=p@;GAl1Iel zVI?q6i2rZJ~ec4(8ac&~$mTu4daJ~t4 zC55oFrH*lV5l$~?&ZnJS*YMJt5~5)}A4X#R88HV7uskG5M~dFjH(hT@LXshl2i(JZ zM{9|q`!hOF{E?7@bFfIo1HZqgq`XEBbm>iuv8lrio<}h(QkF0C_a%xi&_L;e8z^z) z0$DU*0T1UHFsdIulP6tcR&sR$bi>k7>=Nw;XV-ePD>guj=eOvq1(E1bHIsf=Dusn7 zkHL3OKa8k3gpIc};b_=o-aCcaa8c$8_DgH=KMTd-<0)4#+b;vf{@o?g!l77T5yGCH z*Mo`wq);c@lStWC)jY9uhFIANylYxA{J;6GAl|@d*L=>ziicTnRwENrCcFk5ETWZ{ zez6@7T-$ON~(Cx)$`O#6fdL7cuLL#%Y3)*xi4D&X)g5t=@dc((qdB2v`kDJ3L95el)e+ zkq*0G-?e-ZG8q>yb%2eELSQ*~4DIy~(_Z^jEVn9VrpI@q^UR6xJZc%Zp5F-y9NXyE z@;-c56$!_Vmy&8mA0JET<6vL}`n9})=FH5Riw>QjRk8{9KYxKI73wkZ>qg9uFZh4A z5adrPqUIB>TPv4^Jc~V`Vi!tO{PLKrGu=@C5b>6DKdrne$$B??(zeJ{GMM(g_IZRD ze=o;Y@jfI+$~wdG!;WI~C;u3Qi^5EqxIf%#NapSu5%dqNt2t!67}hU;h|k7KNq1ij zbyiSBa-*K}kbe-preboSbTTime~1k(Xv83X0WnN6hgeHF-1?t8{Ac%#t~(e>UG`_w zrHOaYGCB}HyymZ|X`GxeO zxB@R@emKrJ9Yf}N_b_*Iw!?@*18nZ(lde_moS&@58_S!DYc_r%%Tl&uYFz^smj}XI zWf#{$0F0^BXzh zo`4mi$MIOpMaXx~gGa9k2JkP`3f(uur-`S@*4hww`*s?bF$)<3r2=&4`++Ct!=8}( zME|txz)G@_`|h-dN^?7Ey!!`hapEB}p+N)`?+eq}$uT(SD2T3k<*?@x<-YAbaU!Sb zuWavNQUxzi>EK&n!)d)zW+|A^oygY2XTbDe5lC$xK+rlFabJoI|UlC>u35ZG@E4lnl-RpV54y}(}R_vHHR)6F@i zjT;)&o`;BiGjM#;E;KSJg{|)-$Q7Rqn)K=xX-Tf4f2Li5{k0abWZNHf(267OE5~SM z_bj;8sR-ejF>D)#!P(!-z@%~^*!i+_{jzGZ&8?Fhds{3xU`0j~cE4H=)6;GD{4hqH!E)Nz4o4<7m>|?yhq46WWQYXFAmGnaEr5l}84@A3^gNhX2&Jj+919 zLo1h^U+vAq?KkA9)B$PUMPnljxEKsRX=SXcUmUtu@1>vDh=Z*0Rr<>?oslDhNaj0{ zPa+YtS(y*|k9yg4I|``kZ$iVZ6S=O@M$Fu1!WT;v$IS8Tj9FAJ845m!3GON|Lph8b zKio>CVwB+8V>PSx>$-&4-)1{@?c}^^bAHceQe%6_)94h_51>n{ihGrT;FVYQU&qYGK<*fj*$Cx zLXf^R9Yq7 zM6(52(YmIrK@lA|S$v?uMDF!_= z#Yw>Fk96qc9FP^r2G={2apRRF(q`XAn$CoSUZ4!w{8WNuPN^gd?`~j958Xw@nX+(J zuMnh`rPZw0iogxYElB0j@p>~_!9C;Lvh#KwZOJ~1)fMaWq!)y@V9m-QQnHfBgUjsH685g zB4J9qB(FN7mF^F}LDyfNh|OQW<7N>hFzxtIqh@}Dz3F>`{C%0u7$y`oCfWoTn~JXzDNNv*bhBks9ePCyw)MSf;`u~nT!6uXOmjolr2lFPFn1N&p{7+YFJ?tdu-&t+42 zDXEXC*6NpJmCqq2Hnj-SWO8bwH=IJ>Mk9RS=7?nA1*;LziuuPmT`fBcGvB_$J;KZI zK%OZ09?D=+&q~v-d;z|$UJT_wiGigTO=0@sLtyqT60#0(^L?lo&nxg4FK5T=S$*=lxuh)Y8UCAD~}z@1>k(-G}+IcG2;_Eh)zci-Tc@V&i}AM z>$C50$$km6=m~^7=hBJKoBL$r1Q$$kGpC*(qUnI4CK^};p^f4c@d1}_~@Umy({ zas_zv(Ga+P?jnzVo`APuOVRbK3e(CuFphFoG+D2;cG>wLY>8Tl7vv>CG<^>{udtuk zFA2qHbqW4Pk;Am1VnoRzoh78Jo@VruyU9=_6d0U}P!Y zpKum?)kR>2Ss$6MG7Y7y{ZJ&_3=jGT!Q#J@K|$#{jODhWPDnPeip9)+Zoc}RaR$Gb z&tjg0O`;!_?m*=A25j&;N2cBhWL(mY(YGfb(#tE3!OGAwvi52^*uFl8Q*@kB&9;;= zy5L0Qm`E&iZDKn*7L!hs^F-cBi2pP2AT8GB{{QX&Rkc;q4St*&qy`n?EID`syV@U6 zuX78bdv+Nn%5FmP{VsK#P|8SM)`M`z3n(v{N4Fmd#v?m>NYLDP6tGAHqh`*-9DWNf zo9_@mwh~RlFVlg*MEv?Fhc3_FPfM<6g7{M*cpg(j+$(a4iSTL|OF`VQu9(XXmeAk+ zHS9oBIau9XfdVsQFuZAiM)#UB&gei4-_;QLRIa-@^8yX4Qi4?rqp6Wu3Fw#I#>HGO z`OO~z-cZ&|sxTY{-7jtv!{Z^OX!8SxRW-#;1?kM`M_F+7nG&e}xrcY6@6xTSLM>Ix zCh_K3&PTI%@+1&KalOwutdT4v{*tLUTYH3wRTD(tk#1Jvr8;kl{%HtrQ^vhxYq8_? zUD`W$m~7X*1aS{Tu?PM134qLvr1+2k!A%#)%an&Ld>Pzn`2QOpi?x{)bd z=6mVtk96Orb?C6_B#eeSV$a8ov?d@N`>YZ%!%hixUL??!4>TBQBUwncm4QE#u7co^ zcdSBl5fw=C#R1DfI&N|U)+D|qG97VHGJOU-2$f+Rx^94{W;GF%2_>5P2_T%a68?5h zL4hMN;6G{rjt5WBtM7~PX-)^OZ?=YB(Hr=TT%-mY4A^fQxjXb&8kVGOBRe!WwuxFJ zCDZ<3rO*}}s8FDiPF$YiDi2p_xmYDT=TeQuqF~ph&CigIp!fRC&@85zyne1TLm0uy31tt){>;t58mh_gNzei3?9b zYI-4k_UZ$(cJETW`+hx4vyBG*X$!&9A%pE5d0l(Hl4IKp$%4XvPoZv>45rRZL8q6Q z%tw`9R7*b^KlU$&J8~Ofi{Lz7jczRlPAy=(6|>Ou`BVBT-RzNODv8 zE(ue(Pf|kf;i}gg(e7{oK3!HquP6MZi9cHL$g~#f^2LS?)K1`-nqlzrLK|+@6X)GK z9}4%!q+wp=2xjcNj1Qgtal`+!JL$V>GjDl=$g#Vad%}UX-&BOTO3I*QPD%7!NAl|Z zDeN7u#e+tPBy7q=65u3;YLgqFN9_%5D>zNWDl@=(Wt!DSrw&}KJcjXUnk0Uj0M?8= zU>7Kq(^qaw@cN5=_+6zCFL2ZOt^$_R2pVwe27R(VNDk-dT)f0mGZqa5^pY{{uzb-hOpTg>^EGL&%4v?0o zpV{`VyEx|KjG}^{V2=C<^*pLUExQfTU$hldgIw@6=jZ$$<+6uMuGhp~li{1?&4ZP% zJaFew9X*p-Pab+{ao%De-mVnqGTgUl)5Hb*8IOwCIj#k?F*_NrIZVUq@DmuZVheb> zy1|!8+9AC}fDgWZZUdNUw|M6Hq`Q9A3{C)($k1Zcvsm}g9j;9pV|mJ1edoOtqmrm_iS^S)`|7_Eiv^Qz&@)f9M{@Q+bX zUCS7N1S(8=0nSQB;C|W^&dc40wdMs->MsDYm(t+429oz>2IRN=Zz^`U3YH#CB!Abb zV&IH}=y3lD^z0Ji6Z`kIa*xIFkm^2|bx;q~LnVk;{V%J(A7Y^Ol{Bo)R-pM08fmL< z1e21gfS-dUEdmLMLg?Wb!Wt;5qdF2nka ze-vL<5{Z9ea53yK)J4za`JUZH#>KDDi39z3%!K1US3ZDZ>n3nmu` zHcYq2dbe5(6G?j=H$(f$|>@^1|Q=efxO>o5Q zgtr@Sp@8x@d9z_XdAL@Sf4xEsT}B?!N%ynZhl0B}Pg@>)cGqDE(~ef&cG!}53L*n~ zNTWtLHXKN%=Xa(;;(w`_8lOehY?#R3Sknc&v$`3ri5_(R!#pbTqynB5tc4@rBf!ug zhTOZE40C-n32Ie9?;SJJvWLL!GdHoX@(k{kDI&YqThqL!+!Yc(8SdsC!Ge49F++VJ z?4Ea=td?GaEjf$GU#ny?zbh1eX+}c2i9S1f`%xxaWtd!e{)^-(L_$}>4&24mC3jMso<)!mRz^l_D$c6_+Ovl?xI4*Vo#Mmv!6X|6WUl*g;(_vyUX8})7 z?k6)~t%gc6jj;NOF}m5cfkxDI@Vj*ulGkj2#*}%W<-kDT^~F>$p3}`WOflScDX`c4 z@b%JhD(smFxtj50xl0yJUa3dCFH3=}>uRop@Pdx+wIcj=lGt)t5nT3utsUHO9(>jv zfXDvwymPO(w<7-pdbk&0wpTc*-+YFdW$QqZafRjIa?xp?2l1H|heBNvxH~lrSgR)1 zGolT)`$_OD$PH{3H^cBVCY+zOiv3urd$b$vNmYgf%o)!PGZ2CZb$lW5xYuK{Fsl;flX zL-vN23Uls?0pPlKR5WgwRjJ;F61*V%^h*+?Ki(%j7zz5P`OHOu9@_X~Hx$f4GH>o; z+VuV&-E3%2w)dx#xA%|J+2i>nr`8Soj?RN`vJc6VmnM+p%w+-JUBdJrb+%9BHPi1C zi6KQzZ1lNeuo?Kql2uNyq53tg-Dl2AS^JV*9C8yo8F{0p43;CBxNP191+ z@dLGI6-Yj(qn;~jB8@sTVSx(At~2DaravQ?q#Swpc~gso^X8(+4_VsC|A_k>IUdB- zQ@H=JI~mB7#OIQsv>7_-x04ny>s>AxJSK%&wI-<5phcW*f1q&4VxSV6=%ByqrExY{(yS}wPzgS z$ot>wwf`NG;n^i##Lf8(YHkZ7%U_?uo!nDXJ|_u+%!Y7_=qkF%XgQRB*bTlnR^s^Q z-C&!kOBXqDyF8Y7(!GMlxc1I{RPRAp|EsXZ%Bl{WpU&dxaA&Z{=PwazP=@Oz!dMp@ zOTMaKfkAWb&f0hkEN+H^po=Xguz@f zb_ic)io|R2(D|;~NXZOZ{b?<#55GcNyIi7@sfrIHlc>FUDSq~N3bDJUf`{I6)cdZ4 zHp6kW`EL^yoqL78|1%Xz`jY5M(SOt?WIIj_yGf4jc?ESLeWd(vH}*B9;Z1jmS@{8d%@}-=io<&H-5b`2B|X@`MTe-$Wb8y zYFLVlNRSzwHPlaXjvfN#HaV0FoQAQY7wF*tQC{daCBA}sIz~ zwHdA?`Hqu8ooQq{mdD|}eLN=HO^m88lE-VW_mUqUz3?+no@MhI$;1^QjL`nebouxv zX5>>cF}4_nycuO6KjI0c^Stq??^#lO&4(JSy+f~6bhGz%UxAY5XkxihlDD9^npV6$ zM?dYm0QT~a**Ovkpck19Qx6W~-TUfLo@)+f;;nc+O_^uDb_8Xr3c=!$J(F``E|^}( zrR?NbGJ5+iK3zFPS~qe#AQpV)_WMIWh3W8f9fFyHf@q9a&#~%BTtG}Y|1j8~g^>TkAmq~%`lc$31`3#i zpmsiuc*>{0zSrW2rxYH`I)XYEXQ9i2Uu^8Tj`1J;#PTs5x z#j9d*@mU9Y$q?D(a}V(FZ(X?iip!bmI%B0z9;3OUr(`9$5OkHOy#7Nf8iH}q?k&An z*-q&3N?6zRn&u?z##Mh+@!s-x7(CSn<9o@%DIB)5?0Jr8J?>?CL~34{8$ zT-ab?3QFs@u{%URlYO5jfo0kN;ZJX-Go^e%ye=2Qwv^GvzmrMz)z{>Wu{3p7&7-Tl zC-c;Qio?BhaR|6`3bz-2qB_O?RJd&gqz;$E@`*pF^KU`EC&zN6BJW7zX;og?+e=jQ zRvZa=<_ng=XQ}R37YPiU$`1?;hT5GM$S9|kvHD4{KzxMgZSt|&U~v*GxpRZ(;_%mn z|1j~Y1=U)19Ynv}A%gEJ>G1m)&bM6yKUL1a;Vc6vU*U`sKRhB%95XF>8fCW5l!PH4 zd)Rd+fH@Z*PA}#}G7DcjqsI{sdTsM5Tx8NkpE?Cm`Q7WG%u*Le_v^sce__;MWFFtv z#1zfXWzr2C$1%6<09rkh;LUavwluMsM0?bZ)48ENWXLiZ=UasmDc4exoIVF)#Qmwc za6ZlEG9u}|Pl!O_da9Zt!mjY0iS69q%`JK-de!@p{PF>)#TGJ)w7@&-F5)uhDiPag z1wPl$LYcBS=7o%5pP@9yxMs5xJu_g|mLMut=7XNMifh!))u4gWOPHuUAFC!$1aFsW zxFu4>I+V#kv;1=Yj;IM#!c_>F*NyDK-Br+7_km90W>1^X`n=UiY^`0uTKwU+3wbkL z>AKF%r0p*^dz~DDgaS$4*8*2u+P?y4hs5KilZjw2r_J9hGzMNSVffp6Hul%3z=Y4r zWVJ*L{F-ys>QJ8wc1WJ0;j6E}&D37l`a}R0$O%%71$!aK{2rZe?F0=qe3I!E4Rd3s z;=>Os=)chAxF$so2X@yWv!enc3!+GvB1=W&%23Lo5PbDyu+Z`-Uc1DnmSbyS;jk{Z zW2m21dsu^x3zk6cnKKZxIRiKLykS>Q;26!*C-V4Fjnv6@7Grna0`3jlGAZ`G$lsty z9(dP5dXYbV36g^S)sE!nIxV`I>)zN`w!__Pv%ye(Ck>xJ%>3edv+HlO%xumBKj)Z7 z29#CV7^^I{_gf*V)cnMk|L&uEryh8kB|-m36FeEMi)FcGnANKVn(sHD+P1~GZACbK z|DA;!vO39Y)rs6Qvl&)#vwQu~Qru`}0CJ&yu;@rQvzW^=Y9%N z&h<#9m9rx@mgIng3bAtEgl=DpNObgWV!X47Y@BhCzJBOO?aI&KZTmi}x^tPtT7YzyC`VbbrZ;x5pr~$8$NT|4PEWN=w333f1YE4N-7S~tk2KL@!~W*`=tx7 zEPhL$*XZEC#Uk)jAdl`nzX-w)mBN(meEL=^6||zg@szbbt{gjvH(zCthG7j0mv=1n zo|wROK@Nku&j6K(JRtI2XRQQlO&Mf|K z3cfkqj_VIj;lJBj2|0(qkN|aiv~@MZ&1N#>*~=KnYfPcC`||L>JY9ae)o$4K=M)&L zd(qv^>R3Cg9)}G(vAOFd%`_;ahGv;eT)s1&=H$ueiAI=G!MlyPjR2ybnG@T zz}Fv|>D4`2WY9^Yc2YtGZHQs;*X(YRf9WDjtbd7{?oWn~)3wM9y&Nbwx)nd}x&ku; zWAIf!x0mcp7`XplfVOV_Fm2`8+8`>39iRGPCn(e94F_T2?3t+La0&bq)YwU%;&5(B z4;c?!jg~^EKzxe@^Fou;gR6OPaYh)|6BXg+0(G7=@wGDROvSmA&e8qBrci{R@V!t0 zbx;d}i^*J1`w+*t`*t0)T*F}F3O-I+BS?&@e}L6FU2I*hg+r6mXypYlSn63%gSP*q zZWYN;q%w*BUHK(pcJ+Z!MIhSL%aR?ms;0&L7Nnh&L505@v!)>&@=grV$hAdOO=}B& z+bKZFe+SX#nE+c7Jr@l_+N>O!xZcVe1ys|Nrk6f^qWr(raPZGrm{uBv9u6Nsvp58n z?#o59)&%;obO@U`jk$7ZFso-jgYac+fdBIxmvNtsB?Xt5o{M5=94d>GdsdULRZbXM z{t&eOuEjEmR<^5-%TOPY#>PVlu%UMqb!%wGG1g0t)Qn zu%BB!}dhG&H)u?6$110#|tNU4tjO(z*JfBF-(8OIWD*zvL(hJ(Q zyufjByjp64vg)AdMQ9MH4DVU>?S0R~l*BhJ5L-Ehq_{WqS`f zqvbViJb#T~a*I1&owb|R9~VP8&peWqoI`dfcpwUKeBuKmpugD&O78xkNjb~um%oz4 z%t4fQ`_nm0EYW2BtSjKNt1tu}EhjIJbNV31g*>ax@kMXGV`Y}Ru*ccAAXi0{(i3+&9DvPw@6wrEXi#BgFP*7`_tY0O~+dumXIj(zw9Q^i% z?Ms#CfAV)Fg7e-^=ig``nzv`;3(-TAS)9lI((WcgVm_drJ_mc|$nxjdOoL@bUg#;Yn(P>< z!mAE5G1fYml*KezZnHg1S@w@aaC`V>*yh6P!8u$PHw_h+O~LgK6#0QtrSwt#dg9@` z7>^|h)VAEXOe%lug?~ch+X}{b~*kL zqr>EHfEY$do6^ulO7LUGdk}DZ4KWc>@HDX&YPu8PhI0bVotF-;8-ih5O#!ai@{|}? zYoT2Y$6hEnkDkHTxSa!Lm@xVTGnE6siq74^GJj9&yTz99nMlglB|*B0e}=)=|RI`~O69r_F@5jT$UV=&!Ay!DwX6U?Jy?Ky+by9l;2Ra|n@!q1qu7smov3?4 z8*ZGtf@H^0Hq?ys)s9r+wfBcfQ7gv)Se{3>X)mL42gC8lWZJevs`msk&DRK5CK|!nvyC90{E>-za2Kr&A2MxY|1l@~h48j$ z1^ad&nQr=^!{fX1VbksVw6fz6@zeXrc3#hg{wEcT)RII@iFATf2B$4z?0Ra8LX*k4 znd>2H@CtdjHHiH8(;L$li12@%xkm51ii79rC1lgU2HX}jKr*W9!LOAEp@@XWrelRP66DmFNe(=h0&GEWf-rk$A`;w6NCb%ZD6L9PQDFg(xN}Xe-h$* zb36%d-4HDI7UCIdPT(hP8U-aTg+9`lNDlq?lhl0mr6&@vLdcWD=wk4iZcu9CGSEMW z$`Lzqp+*=ykM>n>O4wbq-sl5q6+MhIRtTa0{Ig7V=@#sp$nE;Ny#~|Pe?-AaCCu+x zQegSbg7_TzO}}5dM7EvICWjx(qnBkG8iehGst4t;`ome=^Xege$v33B_v%=M|M+m` zL>4%{pGMw%QNp~VcW_lMi!Zmxfd7GqTwfpqXYXGDC5xhI(4&p~(SiR+x1$bOup)<6 z9PdWQRf^#5)kpR!m(nby)hMJo8PEUXa>DlWVPfn8eDqe1SMY~pDFoc2Rb%N`e}Ll@ zd2GS)pBu62N*7faxr;lme?*zaQuNNKqy5d{;J&+=ym8kc9V@F~c>5d1@8B9@)lvtp zLOJ*|Dgh^+-i#JOZOo*HcJNl-9oKk&rNd1z?9`wrD$ensq83T>Pdj@+s*EWnExSzW zhCRp$_8yo=4(-2P36ctF6p&GV_&Kb@oHI zqa4-yvJ*^(Sf+Yb7UafqyHx&v2WT;P!iPi5-m4+zs}I7xk=zX|&03trBqlwe|5EzMlRwk=oD_~1mu#k_e+ix&kB7Z0Wc`UvddO=i2uQ3g0{!q!l1Qf-4WWakFy^!#d ztsk<1|72dUO;c`@3U0rsPQ4atEsICR1{wIL=SN$fzNGhs{*fOCGRQBBmt?QYHD-5W zH#^cQ!B;IxCBIje(XzsARGH%#uYK!A&a3+n>Uf0t%EtN$|RX8i}Q(|$9PcT4g!l(?Rs z=2QrAXJBMP1V|*ufA6sCb#T|Zeo~pwV+G|^lPYz7N1Gf2`d8$jC&17MgXd|7! za~u77Y(6pS+=nWo9Dn#nA3Y+`e>^P1q{bcFb?o_Nv>|{^>yo^5k-V)oaQ#kRS4w~j&!=;NF@p9EP*!(pflZ#8R zmZfX&)nKjfxCTHVTGvrkgqaT$je0 zs`8RrbeYo9zE<8yl|esesYk_3*6C&c%yoyN6s(8AnYK{R;qCOlPHr@dxv)A6oSXUuOMA03=el~rz=dilMTL)YhH`S)szeu zv4{6frtxYsu`5#=b;=*HU58_twv}J$oteMbyW2}}Gy98bXkVm`L66}%w|^-_kpcTH zg`oR(BVj)o)OJTE&}dxCI(ft}n-arerrt z4rsnO2EPc;!8{>Jx;D8S^|F;wS3I8DTnb=l;uKhE%IQigOyP}YD7(adF5aFQMO3XN z_}w=y;O3Pl;c-n0*sFDb_K#=i_v$Kl{~JN2Y&qySL}5sO9UQ&vMmKq!VkgA$Y4V0D zqR8>k9mZCZzl$94%WbZA*V50WnN+5L2f4G z;Wn>EyihC0CocbM=)B{xYQs2gmmQ&@G|Z-kLY(`0BGND_lr%&ok&%>^R}mQrk%(j# znMLNguSY~hq=8gMv?S4xB)#WPe|kRWbD#TM*YEfJYSH3CE23hYL1I6iBb9w}n4rBJ zPbwzC?46nrq-l(duQ1#)xdosl!Y}NyhRM(O(nogO?0BOOrqN8=E^r3ZRCC!iuE$U& zCmxPH2>`EnHEfF4L&IT9XqIgz{#>Ts-{B;iekvD!KdNN!pA_Y5)s5i9HD=&vDgnI_ z98b{u8lGN1M&3+#3TD6jNXYNy_$*nFRZuxaubU|G|5^RU*QNO=(BngU|K`BEd}){^ zD~>j?Rn#W(8XQu|BzZC=bjeKvy367KY+AmIRJ~jcZBje&^y?Db{QVO8oqa>f0#x|r zqiZ1fxH>sIHwBK(vxFmS_VY*n=}|#$Pae{?7I(fm#rfpp(0R85Ek586+q$Ls!ZGV` z@|LsUX&ivLZ|~FMzoPu(MuO;OokqTGO9j>^0EB-S(+v$xRQDX$d&^daM;#p`yvB!q z*ma!P=uVZ=@a%3ekzEo2N6&Y$&+kvh?p485Sk4Xq zZ67j}H+w)TK8WFZ7evwbweYm?5>!kNt#q&a!|{LDqLZ3Dmd}iW=dy2@%?huGW=S9H zoUV?G9AI%ZEEzK1!vS&!hvqG#t5CLn52~OA|fI)2*P^qoJU+DUZJ(GC|e{6I|;jlK6 zCeJZ+-km~+KR;Kwi1iV*>vbemW?rRnwgLI)#QiSS3QUt=I?iZRpoZ&T!mX25*x_Gc z7?@oIf0~Ec*d9@gNh*Nilv-%|Qj4P;!{ya9S?n&qW2iqIMdlw6;odLVl{?b&=%?@R zQPCxXRrgs3@=L|xiAxi%d$k?sH^oDH;RR~7%!~WJZJ{cW&nuG{?j7=sn@!2M)3-}9 zS+~##@Yqnpg`5TXlI;tbt&A_8(tHS8VyGb<4yJ8M>BO7M8cJscg5DcN)OdIcW2S3h zL+EPIQoqGMIC+IwzMn$h>{y3&g5M$h>l4i1k_`89LZR(M6x0)aNHytYU`QTtTP(GR zk4EimIo^e(b#$V_9N1%3L%mH*@Tj5@9$)eio<}do{&&0J&Qx0zQZ%L|Lbb$W*DLb0 z)POxwR=^q*JY;|EsbtN6IFpRVclbeA5YOM+iZ3kQu+Ag0u>1|jMK--i9|xQw1sO_s zKfk-ePG5$TYxU#a`@T4rD&R9`?)i`QhvV+5OuCO2cG(S)(+h*h=Gzykky9SKp(&$M zW11-~j@k<19+o8i1P|Bp7NFkBI~+f)4-OPA#1`>f+B5mD;b!f6RGB>u+q}$Br|KH{ z`AG~#^Q+*avJkJUR}wsro(I2J52|)6m|R)W4ATsb@FT9z!JIe?EDzcPqRy`PEdM!f zs+vRkHi~hxP$B+De{mRiXi+(2=~7{>FOAl3RB^+?iP(Iy08~VV(L1o67)d4(<>RUB z@>g3Sp;#UQ7-a~YB8|L7)hIl-nf2CsiV=Yh*l_3;B!$W`%KOz}^D7DZx~qtYw=cs> zUv*GG>LtnToz6Gw;W)N*DbyyLq21Gou*I{Rj7~ZTpPkNfZ1Aa6yeJL5bfx%vH%tTD zIAeUD%jE-uIUa88H8T5NDZMi*k;^*8lRj1(Kl@goTWJV>pWI5Wy}Lpkf9*xv?M*1( zo{WM6X@*9@2VgLln+4fRaI>fk4A4rU`ej>CX{{67RF|Mn3#!SZ85uCOJqL#OCWHIr zt5iN@0gi;1q4ZiaIGSF=UX$d`&^#TQU1*1rK3lo-A_IPSbdZwVV60w6VA2~QnkzjU zdZtH{>k~u4raYR^mLxb9DH*p3bJ{XyMSntHaE!fr1WxVrBd`Kx#Ynw4|Nk#kRBP<<+~ zzF*Dwa9xV-;`mDNcuub>Tt@$|(a_Vn6;&$|L9e@kIPTJ;oEy5tIYO`RK}ao$qB@})p;1L0V&;zE9Z6tsn6%@X^GWclQJS~z$PZ%Ll0!p& z*2AFEML6M`!m%4=dADl{F+3&(HgHa(_Yy8#A6$$-C9{Qk>X^b3(R-LOnn;>_1$o0Q zv9SB(ZYnT*01tW2W36v5!GtzZevh;}@#;TG?tS>p%#m2bKQyTZZRNj1kp zx1YneHNUxRdn>%S62>n3-Ux>CtN~uffIzhn2?}0^rjfBk?)4DIn9HJjWmm)Ad=={T zdp4S=UL=ku50FNe38ZJOAvaGbrdnKP)^zwk5FT&=^T1hXwN?jCdB!n~Pkxf2M}BZr z(VDCfpM#h5%dtsNi+9rKA+*1gCQ+8*a3E_MebX-v%XTO*7b8Z=hT55M>?6>~Gg-9p zum%qNJqk8MzL>Eti^702PIjol8AkI_VP8K{`l1@Kp-emu5SuEVmOgt&v3IjJGJQ^AIGK z+<+igQ67Ia_jlt#a^sviiqDnewR=!%e$NG0gi@0I-#lowHs$6Ez0|5J5T;F>PS5Xk z05ef8cX4PN9rwCA(%K7`j|QmC+fJ49ZV;N82bhiPKP;L~^)xBguT zTlg|~x_U3Y-yck0jRaE98T;Y(%0BMfR-RmDy)m{-jp%HghkgUWFz}LV9z-?Ky`2|W zsCR`cY8k}rhCU7lb<<_q&JcQI0?+-BKT++hz!XP8{-<@<$%o1;X5|(w-ng9;;dkC= z?jIR|4`OxPJwX%Cnj9qmo$`fyV{fS4QHe^&_Y?59WeT;c?j~-(j*$668}R|hcMf+L z$K&U3Q1=%zaLb4-gem5dt&&Q3=i6*_+35kocjho{Mfq4gJc>*FZ$snkue4F%J^eOw zh>ia>PXBY|?n;s~;Gg^gzU;St>i=&)V^tJ`)&uTD>6R~Q_1t7WImu$C?IdI;)G(@Z z7t^mlCi3D)0$#8>3=U(8(6N)7qov&^J~BqIU1tIW$F-7sJN83usRzbAvV?spOG%sc5TFEI}DSPo* z11>74g8qqG*eDT?&nL&A{OCbYn<0gI=ljTJtl9K*D*FuZX2C9vn?(WvGo z5qgmVZMj^(w($n~sZ`@1jw!AZmdEu}f0DpiVJz>E75kSTjy9+6Gxu(8gmnLjn6)Sl zbN(5TyC`p;AifSzV$#BpVdZf6Mxhn z&nD|D%kgVwFU?iBiFeu(!P+C3EGvy5`?aToZpAVx=UhOA6gOh-Dhl7U(;?Y&8Q5e_ zftA{v$IdDgU8a>#>Cx%vIb#XWW?clfePVFCtrafUMRejas9#>%;C-?cCF%uvkFM>+ zKi1RmM&(ART%Q61HKnXTMH5xgZX<&~MR>Z4{n0AB8A2^m@!)Dfyr^`U<87pZ^5v;u zvstxbrOXY^8$(&wg=U62k`H0jzKN!l6+s(00k4l=fsPFkgzt6}#|rFGvo)H%HsJ=G zC<=qAuAkACJZ3F-o`>_zO=xvuklAft3LP)IaNYPYDd!x=OaCr_or3R3;lv1hrZ=6x zqQ8|966}G`v3g`^{TT?pwU8HfrklPpjE35Ii5Rl;B7{rtg;lk)xV&NnS@ED0Hf=M< z@_k8QYr*Z^fBB%$fIkiXn+Xw5?vY;K2-ai&MmncNj^`P_hRZz8CaXWZf)CrLK$}4u zEYL5ZzG7{Je=L|;wmTM=aQ(0(Ni%rbn@q>=$Y7&|4w`*^3ES1K;Jb23qM1Db4I{7N zo`7V!ZfPxO99T1%?mDs<`SUb4YYd~3=dVDI+yivjG@GjKlOuCx^03KYlr%p| zB`-$XXy@Jt(76_Ym2nZR#AVivF?vmu4T$XIIFn`VKOjw`XgT@Ji zBw#EI4qcYwEuV6pgr!H3BP**(p2bXzNj0Uh2j9WUK_R#&@R<1j_ZH8JCDQL>56CL1 z7c?}C^E5wYAlo{f%3AeRo>r5mHA2IfAgPDvFH2z4*LfhgRu1}pWfFyDYrxQUKN&KA z3=_*VxZKctvP&Zab~Tzn^z3*#ebaohh0E%FqyWQX60l=?78L3qg?&=yXlFP;vv#}! zwSjgzB)1Iw1HLm`XCB6te5AQbwh$+EjyC5$M=NV7X70ycc6Pokkxg^OX_Y<@CZ$Hl zvimq5cLu9(r;XM_>h#%{luD65x!5#l!qivz!L@?(#MFN&o;I$*hO08X8CG3f?#+l} zfW3xJ4bG%;&Ur>U3Yd1sa$35_JTmhuB^ilWqJz`iEY>1JT z;3@uzx>v~X5`z;U&8e4hElnYN6mOzhvm}4$$X$b(JAGl*>rE)wpGPlmKTI^VQt9AM zFR-X+pc9{U;thLo+;p-Fw%G;7w_CuL-Bo%u{@{{yCc?+KuunZZW4qmEk|4 zkJfq$%L~WqJx0gx8|faya-0=r4^?(%e6OWk?(jhj=MlRGC+tmVj`c#?UgiS@ zdK|+dBoN)Ezr_=spI8w$1G?>cAP(*_hd;yHVV;CG{E8i?y$Um#K(lvf_wf}`_DqN0 zRYAmjRRh@};g93KoU=V42Vb-EN$QD2i21FDRvedg?1VQQ`WuL5g&C;B@ws$(6L{@4 z&TO`dHQb#$hi`g#0y?MK(J$l{Xg-?6T28ruyayBTzO5{-ReXy1-f>_VV@e))Ig-To zH_+}B1R3)pXh8EhusNjypKmL|ux2K1-Kh+^yTY+0p$l3VYs`7_ofbR3C$atk^s*I? z1SFgD4gA%?t2)x~%HOZ(l&1taF3XYM5X=f5K;zF?vfMrm2Q{+b zM)wPYUwZ^#!{<;8nAtTUnum?CdgwTi9Nd9JO!wP90@7J?q@WTBF3Iu6j>X`<( zeM=kKI2OC#^C;ZkXa^xK)i`IiD_Jzj2V`8e;Ld?&)J@q05u>eialm~-mK~-_N#k_% z+(l}?-~rr;2*OuZb!^9t3L0K>g%z`yh4*R`G5Ul#E}a>SHs|6{CApK;EYjqEm^eUd zCOn`=NG0>^ERT3ioK4|n1zZbP!nt}ENo;l?I6a?1`HC;FGn0YcZL8s6E+rzh8X&K? z4H>Z=;NvO?`gRT2uQf&|U34Sg>UH7P{b&$$T?7BF-Nd0wS>&wZ7&*Bj-_Z5XY4SM6 zkIZdx$McEra7hE_MN`j*9ZRoLlVQSSarZ1`H36h%H>r2Rd_3CLi3>hoC7a>~$XKr^ zO4rzN`>_wWGk=uz`fm%~nRF2+*c_)H4%=YF#$XHz$Rd&@M(QgOWofG@AOEFUHrNY8c_3OQt&4LbhW9#_T93wbBfB z$z26i9T6gKQU&X3fLeS!k5N?*@yM?Vtf+Ft9bVVrq+c)@nFrKmvJ~hFanG%i4F9f6 z2yHC8!##@&D#xtO(a$Zmuwj)kHZIS`zlWvyV(xdSa-}9;@S8YDN@Sqef*qi4SqqN` zkK@BBwy@oK8m7((LcIzTToN=&N){E-Fgk^EIb^c~mE-LA7cZ2U(Mu~YYU85zX}G)0 z2z{ z{=S)aVgJe|k~+{$-O^nRW6W<+lY$&>M#RGEx>{U8il~emcRt<_=MApegpckGv$9;r za+dKV{@IwBaPQ|q5OAmPtoSBveDsTsochfEbF!g7ea}K`#40pTJWYT9+yQyA-8eaW z2YbG~2#a&X@N&mCtl2Be@arDnypO*i-B5+SzSe-PPYc1tKh)69SQ}0&k5b+J{UC8G zg5ec*gILd6QnuKDJtyKskWp3yE%pdDZw2vA*A+MBzsYEJv9nn0quVC!2Ns! zIlrt5w^tAttsW)M@9>G?jB)zU^fi>;u%~WvA7E@r7Ce1oPUu;gxt)#)+@#&>HNk#eKUsO`5(L~yWV>J7uKd&!jq^YMA)9v3uQa#a z1(R>4lE^tm*xIPWTU8YYxA%I{fmaEnE8Prpdu~uYYc(7e%!W0OHj!>K8BAk58MtSR zPxO;f_soB=?c^2g{Q8#T>ILG%*ne!+77cJz2!g}cy13ltGse$a0-BqbfgQPnlSWm! z`F1h=vqy-g>gS=M^?uxSL>cEYXPA*cYRu4rJR0{&7X<$mvMYAAGO4Z6pngCP)k0gL z?Ry4pO)7lM->N3$VKs=V$~iR|Zv1kcy4M2EZr_*0;WMguEpLFg)I zlD2}p)=>PWeI0`9XK{?QG#nSXjBCo=p!aVy7KwhNQmext)WHnz6~^MZs!zngtPpQX zCc?g@5vbr(hd1Ln{@|+9ba9^pcy#P0#sUUZsAzz$5U--_@+|xqCd=O%*My6Hhp~|v zm2k2DIQq`5ryGK=5zg?0F-~UW&$|Hjm+E&!jj4REZ9M2W`W0ohH=_N^Ond_;P*)h~ zc>FRP%Kia|rksJz)yYscU74gSkI{8EBFWoJoR8+=G@7HGhYyaiAZ-%`r>6SAkBRTt zc@Z7>LdpnA4n$x~%2J+f&`NmJe3VY-&gUm550MRK8fbaW7hIFN3^iAk(#5kcf%&>M zaMI0)9^^UF44F!LsqG@z>u!LBlljCx%bSck3lMnrq;8L4=%4^p72(z{@1<#NYVKsz~HvSSg} z%QixnrY2s<4-kxd(argMM zT&`i|Uyf@RMOwD*XCmhPrenW%pv;enbR}-Z$C5Yj>%POVVIUT4UhQEDSH#ex|MAez z(gt4DxkJ;bKB_TA1jZLfV#5q22-nkNaJ-U^OiPA6?(O7dfd z%+ywfysO9Pz1QE!uSPh zJBVX>6vx`&b|UK=sfiIEE4FcbXEq0nF3vzjE=T3ynFAKP67Z(YYIIsV2jAPL;B65Xv>F5{b3hD^Y~GC9>OPabT8oK9W(@Sa!eA;gKqPP%>vu&G zN)Ot=xU41~g1hMUr`ND}wlcoCwg#P*L{T$rCfw%wT2pTCph=d>sP?gil&B^{l&C&5 zDkqbSFD;n5KOcR?X5q<%Qo8Qga#$xG?!7;eK zz5)K+{yBQ;n1oj>fQM7-piHh8;@gDqq;3c0^`^p)f~n|lc$Ws9f6x3h zc>>?e_u|$6eCWG_+nJL81PJ#X3lg^1Ky7n8mzS$GEGUv~CSN+1ShY z4sp3-jWFoUtf0>8qRH2-d363?Cs33=OPhr=;O|#O7&&~Ih=+`zg8c|A+{U@ZW3$OY z$A92_AdAbmsbXPoG|E{$#`zJI`1yMslMF7X#pVgeXqjxe+3Z|3FrQ82&Kgv$`#(IA2T_H%E_# z)f>|Zjrs$>^RnQbR{|uY$KmC^Xd+XS!j^rVz(3=dP8PLK#K*Tasa>o%U3Eu@rfu^A ztzAPnK`k63&bQMaCfC79Xt=VgO@vpKH~MCc z=y`ZM$kji@fkFpNNm~MMqjC-B9|=UAncW~VqzZpKUqY_wWk|AliQgrIVDggfG(mP5 z7TT)vR&qO0l_7uTzS0%;R$M0h()A;1Kc#u)9HYI#^f~b^zK?MSr3~Y3AFv<3Ux)PK zGIZVZ664qVutJuj<(_`BAgTU{J-9%aKek95f7?rw#CQv6;C^ST6@>GET_yEAS@<0w zM2G&106F76GTA2zfgf32d~~>yO+m87%l{t<@tt+x)QkK$Y-kgb^^TW>%-Bm zxfs4AA6L$Zg1uaqeA4tJy4vg>OLGpx*E?5`m%0SQN@MBuPnP&%VFx{4QAhNzyr5Mx zr=WpA2xj$kkdAR>x?v{Qp^8%A^+(*K^}AKTLA8Z`YMlbXNhuh5b}jzX%co{md9YBz zj_$F&2z{y-;g@X&ME_1k%lvB4n>7mseOBU9(!n@Sjl%C#iZ&fnf%2I}AlYUM>t8KJ z{)jW}2=u3qe!78J`U`srmKaDQw@oqgH#7&5r2X?cQTUrl-y+>eSA;e zd0Jyv#2A_IXDXT3_=fB+65{2|EX0xezx4Rw8NA17cbV)bVvNx~3uarybujS$MAM;= zZe3eT3h%zATP%cVX1O?YDCU7e!Z^5YA7a^eS79Vo7ms+}M!T~2RIOc~HYHi3K*|zu z&XwffUb`6HrH4a6-ddcx!zhu4| z`W|SY?`<=9S8oAkt?^WLk2<_5j=}Wr9YpNYI;OBqlDF^uICHc!8J^x*jy#3+=xiB| z_s#u*#`=-od8_e}?^=$*oW|5H83jGRDRl0YRoLghg#-3&4Fzn;dO&F9#W z60Ue70s~Zy$W3>e1Hr_ zsL+esC-4k)en5G~XO0Ur#EK{xvpVY^z&0sA_?a$By=8XMs@JZVk0Inkry{AJIUQ}@ zoMq2etbo??jZo?>0z%w=pz>oY5pGC>?H7I%zGfOY?yJUn!TUHzG9DhpOrrtCQvtReYlbtQ+d&}m8;T8;g8t$->a&3J ztjwRuin@?JC;(7(8;nY-G-162p)4v@lu2m3SE(f*Dfsk zgzFrUaedzPUv9KsegNhU@4-pG#dw}fBhVubhT#w``nOPf*v4LA)RU0=~d?Uyo zlpBYjh;%5u*g(|PD~b8~iR^HIG>IE?#9cF1pqzRPoSC^9BL8KOq|5}e=ZPb<-)y7- z<=Kp4^cAv5Plb5T*5rJ2=i%QOb7DMWH%-4kiMLx+3O>g<;sPl{Y!*ylX7%5|Z)&GO zLct8YRm;c|jrW)@CyY+-4Y2ch9W>_6f>pkMIbGvJl)Ukd{1Z8WD&=aJD`||{eG6b4 zcW%noaJjqYU}ogNT)M{LDGfg5NL!=1`PJTZut7=wB>qFPt~d)-PZ%TLc>>nY^2M?i zCvwYgB9`g+8G7eMkXz^8!Gi^bxZI1&7DuKLo7djx`1TY&RoDdDvr3?fwpPxv$mYIL zJ(!spja=A05Q2Wk;c>qdSq=S4xw4@1<{$Q(PV^wL=p=?TZFe-5c!RSJSCy zgdsCF?1_GHbu@L}9ni;8P@>aNu-*|hYqhE1yQQ!Wl0aQ37K-(%Nf@{9+i*CJHlBEl zT0TdqhrBtfE)j=6`XA%@<`nRs^p#Glze<9#FT>h)1w4FsAH4SX0~f@?Nz3Ub)Z2a% zei=#l2R7pPKA#ZC|{%0=sirDXBFX8K^~WTt393(f>{eD8&mKxU;Pul3zrR_Mqy 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 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

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 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

(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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