Conversation
6f48a48 to
65251b1
Compare
A dense forward that owns a device embedding table pays [vocab, H] of device memory for it even when the gather could run in host RAM. Record the row, the structured spec the record checker requires of it, the seam that takes both arms, the llama.cpp ggml_get_rows shape this is a secondary oracle port of, and the golden vectors the host arm is gated on. The row addition moves the engine-matrix counts and the pinned ENGINE_ROWS, and carries the CLAIM-ENG-HOST-EMBEDDING record the ACTIVE state requires. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:opencode-go/deepseek-v4.1-flash [pi]
VT_HOST_EMBEDDING=1 keeps the embedding table in host RAM and runs the gather on a cached CPU queue, then copies the [T,H] rows to the device. The CPU vt::Embedding kernel decodes one row per gathered id, so every table residency the loaders produce works: bf16/f16/f32 and the GGUF block formats. A bf16 table takes a byte-copy fast path. The async runner's device-resident id override is consumed before the gather. `EmbedGather` is the one call every dense forward that owns a device table uses; it owns both arms, so the upload happens only when the host arm declines. Wired across the Qwen3.5 family, the shared Qwen3 dense driver, MuseGlimmer, and the classic dense families (Gemma 1-4, GLM4, Granite, MiniCPM, OLMo2, OPT, Phi, Phi3, StableLM, Command-R, DeepSeek-V2, GLM-MoE-DSA, Dots3-Note, Nemotron-H, Voxtral). Decode-graph arms whose ids already live on device keep the device gather. The device gather is unchanged and runs when the flag is off or the host bytes are gone. VT_HOST_EMBED_TRACE logs each call. The new test is the arm's first coverage and has two halves: the rows are compared against the same pinned IQ4_NL/Q5_0 oracle vectors the device-side op test uses, AND the table must stay off the device (`d_dev == nullptr`). A gather that uploaded first passes the first half and fails the second. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:opencode-go/deepseek-v4.1-flash [pi]
65251b1 to
3cd5700
Compare
localai-org-maint-bot
left a comment
There was a problem hiding this comment.
The new host-embedding path bypasses an existing safety check. In HostEmbedInto, dids holds T int32 identifiers, but the raw copy uses override_ids.count without checking that it fits. With T=1 and a two-element DeviceTokenIdsScope, this requests an 8-byte write into a 4-byte buffer. The device arm's detail::ApplyDeviceTokenIds already rejects that mismatch, and provides an overload accepting the taken override. Please route the host arm through that helper before downloading the identifiers, and add an oversized-override regression plus a shorter-prefix case preserving the padded host tail. The new suite currently never supplies an override.
There is also a Windows compile blocker: EnableHostEmbedding calls ::setenv twice outside any platform guard, while test_host_embedding is an unconditional target. Use the existing portable vllm_test::SetEnv helper from support/test_env.h.
These are focused static findings at 3cd5700, based on the new helper/tests and the existing override contract and implementation. I have not completed acceptance of all model call sites or executed the C++/device gates; this host lacks the required toolchain. @mudler @richiejp: these paths need correction before merge.
The upstream review found that `HostEmbedInto` copied
`override_ids.count` identifiers into a `[T]` buffer with no bound, so a
two-element override with T=1 wrote 8 bytes into 4. The device arm
already refuses that mismatch in `detail::ApplyDeviceTokenIds`, which
also tolerates a shorter override as the padded case. The host arm now
splices through that same body before downloading the identifiers: a
longer override throws with the caller's name, and a shorter one replaces
its prefix while the host upload's tail stays. Two cases in
`test_host_embedding` pin both boundaries, which the suite previously
never exercised at all.
The same review found the test's global initializer called POSIX
`::setenv` unconditionally. The target is registered unconditionally, so
a CPU-only MSVC build could not compile it. It now uses
`vllm_test::SetEnv` from `tests/support/test_env.h`. The override cases
reach the internal `detail::DeviceTokenIdsScope` declaration, so the
target takes `${CMAKE_SOURCE_DIR}/src` on its include path like the other
detail-seam gates.
Verified on a Visual Studio 2022 Build Tools 17.14 x64 Release CPU-only
build: `test_host_embedding` compiles, runs 7/7 cases and 862/862
assertions, and `test_ops_embedding_quant` stays at 1637/1637. Running
any vllm-linked binary on this host needs a one-line uncommitted unblock
of a pre-existing MSVC static-init crash in `tev1_registry.cpp` (added
by upstream commit 25f99e2); that bug is not part of this change.
Refs ISSUE-LOCAL-01M3QETSTM8X8AJKBM9QCTGBKM
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: opencode-go:deepseek-v4.1-flash [pi]
|
Repairs pushed at Override bounds. Windows compile. Evidence: Visual Studio 2022 Build Tools 17.14, x64 Release, CPU-only — |
|
Addressed in
Both cases you asked for are covered:
Verified on Linux (Release CPU build): and end to end on the local
|
Row
ENG-HOST-EMBEDDING(new row in this PR) — one row per PR. Spec:.agents/specs/host-embedding.md.Before starting
has no equivalent, so this is a vllm.cpp-original capability whose shape comes
from llama.cpp's
ggml_get_rows(the secondary oracle next to the pinnedIQ4_NL/Q5_0 vectors). Filed
ISSUE-LOCAL-01M3QETSTM8X8AJKBM9QCTGBKM.implementation.
ENG-WEIGHT-OFFLOADisvLLM's
cpu_offload_gbmirror andENG-EXPERT-STREAM-DEVICEis thehost-addressable destination half; this is a host-side gather, not either.
The category counts and the total in
.agents/engine-matrix.mdmove with it.ResidentWeight+vt::Embedding),docs/ENVIRONMENT.md's knob table, and the pinned goldensin
tests/vt/iq4nl_q5_0_golden_vectors.h.What changed
VT_HOST_EMBEDDING=1keeps the token table in host RAM and gathers the requestedrows on a cached CPU queue, then copies the
[T,H]result to the device. The CPUvt::Embeddingkernel decodes one row per id, so every table residency theloaders produce works (bf16/f16/f32 and the GGUF block formats); a bf16 table
takes a byte-copy fast path. A new
EmbedGatherseam owns both arms, so everydense forward that owns a device table gets it and the device arm is unchanged
when the flag is off or the host bytes are gone. The async runner's
device-resident id override is consumed before the gather.
Evidence
New and first coverage for the arm,
tests/vllm/models/test_host_embedding.cpp:The gate has two halves. The rows are compared against the SAME pinned IQ4_NL
golden bits the device-side
test_ops_embedding_quantgate uses (bf16, roundedonce), and the case asserts the arm actually ran — the CPU device arm aliases the
host bytes instead of staging, so
d_dev == nullptralone cannot tell the armsapart on this backend; the
[host-embed]banner is captured and checked, and theother cases check
HostEmbedInto's return value.MUTATION, the guarantee this pins — disable the arm and rebuild (the flag is
process-static):
The device arm is untouched:
python3 scripts/check-agent-record.py— OK (ENGINE=180 MODEL=384 QUANT=87 KERNEL=60 BACKEND=90); the row, its structured spec, its claimrecord, the matrix counts and the pinned
ENGINE_ROWSland togetherENGINE_ROWSbump carries its executable evidence:tests/scripts/test_agent_record.py::HostEmbeddingRowIsCounted(3 tests,green) asserts the row exists once, names
specs/host-embedding.mdandCLAIM-ENG-HOST-EMBEDDING, and that the spec carries all nine structuredsections. Under the mutation that drops the claim owner, the class reports
FAILED (failures=1, errors=1); restored, 3/3 green(
check-pr-size.pynow exits 0 for this branch)tests/vllm/models/test_host_embedding.cpp,tests/vt/test_ops_embedding_quant.cppdocs/ENVIRONMENT.mddocuments both new variables in this change(
check-env-docreports only the pre-existingVT_VK_*gap)scripts/agent-preflight.sh— RED ON THIS BOX AT BASE, attributed:every failing gate was re-run on a clean
origin/mainworktree and failsthere too (
check-release-binary-contract,check-windows-release-state,check-benchmark-index,check-env-doc,check-test-registration,check-oracle-pins— the latter on thee126687a9avsa7c23ac96dpinmismatch that predates this branch — and
check-symbol-anchors). The fullctest failures are attributed in PR fix(LOAD-MODELOPT-NVFP4-BORROW): one owner per device resident #3355's body; the 12 of them reproduce
on base binaries built from
b45a94273.Review follow-up, verified on Linux and on the GPU
The review's two findings were fixed in
d686c8582and re-verified here:[T]id buffer: the hostarm splices through the same
detail::ApplyDeviceTokenIdsthe device arm uses.::setenvin an unconditional target) is gonevia
vllm_test::SetEnv.Linux, Release CPU build:
The two new cases are the ones the review asked for: a one-id override over a
three-row host upload, which must yield
[1, 1, 2](the shorter-prefix casepreserving the host tail), and a two-id override over a one-row upload, which
must throw.
Linux + the local
sm_120acard, on the official checkpoint, a scratch treecarrying this fix plus #3355/#3360 (all three produce identical tokens):
override=1is the async runner's spliced device ids — the exact path thereview found unbounded. It runs in production and the tokens are identical to
the flag-off arm.
Speed claims
saving and any decode effect are owed and unmeasured.
Honest gaps
Measured on the official
ukisai/Swift-1.5-Qwen3.8-27b-NVFP4safetensorscheckpoint (21.94 GB) on the local
sm_120a24 GiB card, same binary, onlyVT_HOST_EMBEDDINGdiffers (a scratch tree carrying this change plus fix(LOAD-MODELOPT-NVFP4-BORROW): one owner per device resident #3355'sNVFP4 ownership fix, which this artifact needs to fit — see PR fix(LOAD-MODELOPT-NVFP4-BORROW): one owner per device resident #3355):
2.368 GiB less
device_uploadwith identical tokens, and theoverride=1banner shows the async runner's spliced device ids were consumed before the
gather. Not a throughput claim (an 8-token decode is not a speed gate).
The arm is opt-in (flag off by default). Nothing in the shipped default path
changes.
The mutation evidence is author-run; a fresh reviewer over the immutable head
still owes its own pass.
scripts/agent-preflight.shon this box is red at base (35 gates): recorddrift,
check-test-registration's CMake probe, the oracle pin mismatch(
e126687a9avsa7c23ac96d), and the missingfiletool. Each failing gatewas re-run on a clean
origin/mainworktree and fails there too; none of themtouches a file this PR changes.
Refs ISSUE-LOCAL-01M3QETSTM8X8AJKBM9QCTGBKM
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:opencode-go/deepseek-v4.1-flash [pi]