Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/roadmap_v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ issue is not yet placed. Keyed record: update in place, never append.
| [#652](https://github.com/mudler/vllm.cpp/issues/652) | — | `model-matrix.md` prose counters drifted: LTX-2.5 reached the rows and the CI-enforced rollup but none of the five sentences that count them | bug |
| [#659](https://github.com/mudler/vllm.cpp/issues/659) | — | LTX-2.5 device select adopts M3a's platform seam but not its companion capability guard: `ltx2_video.cpp` asks `CurrentPlatform().device_type()` and `TryGetBackend(...)` but never `supports_model_architecture`, so a PARTIAL backend (Metal 15/75 ops, Tenstorrent) is handed a queue and dies in a kernel bind where it used to be refused BY NAME (found while reviewing #553 for landing) | bug |
| [#660](https://github.com/mudler/vllm.cpp/issues/660) | — | `check-device-leakage`'s `kcuda` bucket is the token grep `\bkCUDA\b`, so `minimax_h3_video.cpp:221-226`'s `static_cast<vt::DeviceType>(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 |
| [#674](https://github.com/mudler/vllm.cpp/issues/674) | — | `main` is RED on `sanitize-cpu (address,undefined)` since `cefacd2d0`: `Ltx2LoadVaeWeights` (`ltx2_loader.cpp:1325`) reinterpret_casts the safetensors mmap to `const uint16_t*`, and that payload offset carries NO alignment guarantee — UB everywhere, a real fault on `build-test-cpu-arm64` and Jetson/Orin sm_110. THIRD recurrence of one class after [#301](https://github.com/mudler/vllm.cpp/issues/301) (closed; it left the `vt::LoadUnaligned` seam) and [#627](https://github.com/mudler/vllm.cpp/issues/627) (`qwen3_5_weights.cpp`, still open). The coverage that caught it was ACCIDENTAL — the fixture's JSON header happens to land that tensor odd — so the fix owes a case that FORCES the odd offset and asserts the parity | bug |
| [#608](https://github.com/mudler/vllm.cpp/issues/608) | `TOOLS-PARSER-BREADTH` | **W0 (record backfill) landed 2026-08-13; row `INVENTORIED` → `PARTIAL`, 41 shipped names / 37 families recorded.** Re-derived from the two REGISTRIES rather than from recipe usage: **five** `--tool-call-parser` names are upstream-only at the pin — `openai`, `inkling`, `minimax_m3` (W1, recipe demand) and `cohere_command3`, `cohere_command4` (W2, ZERO recipe uses, so usage-driven audits miss them). **Only `inkling` is PORTABLE from vLLM source**: `minimax_m3` is backed by the Rust crate, `openai` is a declared Harmony stub that raises on both methods, and both Cohere names are shims over the out-of-tree `cohere_melody` package, so W1/W2 each owe a recorded decision before code rather than a text port. `nemotron_json`, `kimi_k3` and `ling3` are in NEITHER registry and arrive with the pin advance, not here. W3 ports upstream's shared `ToolParserTestConfig` harness. The earlier "six missing" framing was usage-derived: it listed `nemotron_json` as portable (it is not registered at the pin) and missed both Cohere entries | feature |
| [#647](https://github.com/mudler/vllm.cpp/issues/647) | — | Oracle policy had no fallback and no pin concept: five upstreams beyond vLLM are already compared against (vLLM-Omni, SGLang, llama.cpp, `transformers`, tt-forge) with their pins scattered across individual specs or absent entirely. AGENTS.md now admits a named secondary oracle where vLLM implements nothing, `.agents/oracles/<id>.md` pins each one file-per-oracle, and `check-oracle-pins.py` enforces both directions. The gateability debts for `sglang`, `diffusers` and `tt-forge` stay open on this issue | feature |
| [#649](https://github.com/mudler/vllm.cpp/issues/649) | `TOOLS-CALLING-CORE` | That row's prose still records `tool_parser_names()` 40 / `reasoning_parser_names()` 7; both enumerations have grown since 2026-07-24 and are now **41** (`tool_parsers/abstract.cpp:269`) and **12** (`reasoning_parsers/abstract.cpp:72`). Code and tests are correct — `test_detect.cpp:221` already pins 41 — only the record drifted. Halves belong to two other rows (#608, #605), so it is filed rather than repaired inside #643 (found while implementing #643's review findings) | bug |
Expand Down
2 changes: 1 addition & 1 deletion docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | ✅ a payload offset carries no alignment guarantee, so reads of the mapping go byte-wise ([#674](https://github.com/mudler/vllm.cpp/issues/674) VAE loader; [#627](https://github.com/mudler/vllm.cpp/issues/627) the rest) | ✅ | ✅ | ☐ |
| 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
Expand Down
20 changes: 18 additions & 2 deletions src/vllm/model_executor/models/ltx2_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#include "vllm/model_executor/model_loader/nvfp4_dequant.h"
#include "vllm/model_executor/model_loader/safetensors_reader.h"
#include "vt/unaligned.h" // LoadUnaligned — safetensors offsets carry no alignment

namespace vllm {
namespace {
Expand Down Expand Up @@ -1321,8 +1322,23 @@ Ltx2VaeWeights Ltx2LoadVaeWeights(const SafetensorsFile& file,
" BF16 bytes but its shape needs " +
std::to_string(static_cast<size_t>(numel) * sizeof(uint16_t)));
}
const uint16_t* src = reinterpret_cast<const uint16_t*>(t.data);
for (int64_t i = 0; i < numel; ++i) values[static_cast<size_t>(i)] = Bf16ToF32(src[i]);
// Byte-wise load through the shared seam, NOT
// `reinterpret_cast<const uint16_t*>(t.data)[i]`. `t.data` points into the
// safetensors mmap at `8 + <JSON header length> + <sum of the preceding
// tensors' sizes>`, and NONE of those three terms is required to be even,
// so this tensor's first byte is 2-byte aligned only when the writer
// happened to pad. The cast was UB on every such file, UBSan caught it
// here (issue #674, "load of misaligned address ... requires 2 byte
// alignment"), and it is a genuine fault on the strict-alignment targets
// this builds for (build-test-cpu-arm64, Jetson/Orin sm_110).
// `vt::LoadUnaligned` is a memcpy with no alignment precondition and
// compiles to the same single load where the address does happen to be
// aligned; it is the seam #301 left behind, and
// minimax_h3_vae_loader.cpp:87-101 took the same repair.
for (int64_t i = 0; i < numel; ++i) {
values[static_cast<size_t>(i)] = Bf16ToF32(
vt::LoadUnaligned<uint16_t>(t.data + static_cast<size_t>(i) * sizeof(uint16_t)));
}
} else if (t.dtype == "F32") {
if (t.nbytes != static_cast<size_t>(numel) * sizeof(float)) {
Fail("'" + name + "' declares " + std::to_string(t.nbytes) +
Expand Down
110 changes: 110 additions & 0 deletions tests/vllm/multimodal/test_ltx2_video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,116 @@ TEST_CASE("ltx2 video: the SHIPPED Lightricks checkpoints parse and load") {
}
}

// ─── the payload has NO alignment guarantee (issue #674) ────────────────────
//
// A tensor's first byte sits at `8 + <JSON header length> + <sum of the
// preceding tensors' sizes>`. Not one of those three terms is required to be
// even, so a BF16 tensor beginning on an ODD address is an ordinary safetensors
// file and not a corrupt one. `Ltx2LoadVaeWeights` formed a `const uint16_t*`
// over that address and dereferenced it, which is undefined behaviour on every
// target and a real fault on the strict-alignment ones this project builds for
// (`build-test-cpu-arm64`, Jetson/Orin sm_110). UBSan reported it as
//
// ltx2_loader.cpp:1288:91: runtime error: load of misaligned address ...
// for type 'const uint16_t', which requires 2 byte alignment
//
// and the `sanitize-cpu (address,undefined)` lane had been red on it since
// `cefacd2d0`. Third recurrence of one class: #301 (closed, and the source of
// the `vt::LoadUnaligned` seam) and #627 (`qwen3_5_weights.cpp`) are the others,
// and `minimax_h3_vae_loader.cpp:87-101` already carries the repair AND the
// reason in prose.
//
// WHY THIS CASE EXISTS AT ALL, given the suite above already reached the defect:
// it reached it BY ACCIDENT. `ltx2_fixture`'s JSON header happens to land one
// VAE tensor on an odd byte today, and any rename or reshape in that fixture
// silently retires the coverage while leaving every assertion green. So here the
// odd offset is FORCED, and the parity it depends on is ASSERTED — an edit that
// makes the address even fails the REQUIRE instead of passing while covering
// nothing.
namespace {

// A bare temp directory. Deliberately NOT `Workspace`: writing the whole LTX-2.5
// fixture here would make this case depend on the very fixture whose accidental
// coverage it exists to replace.
struct TempDir {
std::string root;
TempDir() {
static int counter = 0;
root = "/tmp/vllm_ltx2_align_" + std::to_string(::getpid()) + "_" +
std::to_string(counter++);
::mkdir(root.c_str(), 0755);
}
~TempDir() {
const int rc = std::system(("rm -rf '" + root + "'").c_str());
(void)rc;
}
};

// safetensors written by hand, so the header length — and with it the payload's
// parity — is ours to choose. `header_pad` spaces are appended INSIDE the
// counted header; trailing whitespace is legal JSON and padding the header is
// exactly how real writers align their payloads. Returns the absolute file
// offset of the single tensor's first byte.
size_t WriteOneBf16Safetensors(const std::string& path, const std::string& name,
const std::vector<float>& values, size_t header_pad) {
std::string header = "{\"" + name + "\":{\"dtype\":\"BF16\",\"shape\":[" +
std::to_string(values.size()) + "],\"data_offsets\":[0," +
std::to_string(values.size() * sizeof(uint16_t)) + "]}}";
header.append(header_pad, ' ');
std::string payload;
for (const float v : values) {
const uint16_t b = ltx2_fixture::F32ToBf16(v);
payload.append(reinterpret_cast<const char*>(&b), sizeof(b));
}
std::string out;
const uint64_t len = header.size();
for (int i = 0; i < 8; ++i) out.push_back(static_cast<char>((len >> (8 * i)) & 0xFFU));
out += header;
out += payload;
ltx2_fixture::WriteFileBytes(path, out);
return 8 + header.size();
}

} // namespace

TEST_CASE("ltx2 VAE weights load from an ODD safetensors payload offset (#674)") {
TempDir ws;
// Values chosen so every one survives a bf16 store EXACTLY, which is what lets
// the check below be equality rather than a tolerance: a wrong-by-one-byte
// read is then a hard failure and not something a band could absorb.
const std::vector<float> values = {1.0F, -2.0F, 0.5F, 384.0F, -0.125F, 3.0F, -48.0F};
const std::string name = "decoder.conv_in.conv.weight";

// The unpadded header lands the payload on some parity; one space flips it.
// Write both and keep whichever is ODD, so this does not depend on the exact
// length of the JSON above.
const std::string a = ws.root + "/odd_offset_a.safetensors";
const std::string b = ws.root + "/odd_offset_b.safetensors";
const size_t off_a = WriteOneBf16Safetensors(a, name, values, 0);
const size_t off_b = WriteOneBf16Safetensors(b, name, values, 1);
REQUIRE((off_a % 2) != (off_b % 2));
const std::string path = (off_a % 2 == 1) ? a : b;

const vllm::SafetensorsFile file = vllm::SafetensorsFile::Open(path);
// The fixture really is what this case claims: the tensor's mapped address is
// ODD, so the loader below cannot satisfy a `uint16_t`'s alignment by luck.
// Page-aligned mmap base means file-offset parity IS address parity, but assert
// the address rather than infer it.
REQUIRE((reinterpret_cast<uintptr_t>(file.Get(name).data) % 2) == 1);

const vllm::Ltx2VaeWeights weights =
vllm::Ltx2LoadVaeWeights(file, vllm::Ltx2VideoVaeDecoderKeyRules());
// `decoder.` is rewritten away by the video-VAE rules, exactly as upstream's
// SDOps do.
REQUIRE(weights.Has("conv_in.conv.weight"));
const std::vector<float>& got = weights.Get("conv_in.conv.weight");
REQUIRE(got.size() == values.size());
for (size_t i = 0; i < values.size(); ++i) {
INFO("element " << i);
CHECK(got[i] == doctest::Approx(values[i]).scale(0.0));
}
}

// ─── the embeddings connector (phase L9c) ───────────────────────────────────
//
// WHAT THESE ARE FOR. Until L9c the conditioning this engine handed the DiT was
Expand Down
Loading