Repository navigation
qwen3 cuda: Qwen3 embedding checkpoints (BF16) on the native CUDA engine (#2261, 1 of 2) - #2285
Conversation
|
Thanks @Arminova, this is a careful first part: the family's backend-neutral pieces split out with HIP unchanged, BF16 weights as published, no new kernels, cuBLASLt pinned to one algorithm per shape so a batch keeps each input's bits, and every vector closer to fp32 than Transformers' own BF16, plus the pinned oracle passing on the 8B. Whether we take a Qwen3 backend on CUDA is still an open decision on our side, so we can't say yet whether this lands. Part of that is how it sits beside #2239, which carries a Qwen3 language model on CUDA for Qwen3-ASR: we'd want one Qwen3 CUDA forward, not two. We'll come back on #2261 once that's decided. Until then there's nothing you need to change here. |
|
Thanks @Arminova for waiting. We're taking Qwen3 embeddings on CUDA, and this PR becomes the one Qwen3 forward on CUDA. It lands first, and Qwen3-ASR's language model (#2239) will run through it rather than carry a second copy. Before we run it on our side:
|
|
Thanks @ashhart. On it: a rebase onto main that keeps We don't have an AMD card, so we can't run HIP's embedding and rerank bytes or speed against main ourselves. The rebase leaves HIP's code as main has it apart from the moves in the first commit, and we'll show that diff with the receipts. |
|
Thanks @Arminova. Not having an AMD card is fine: we'll work out the HIP embedding and rerank run on our side once
|
e0665b7 to
4257e6f
Compare
|
Thanks @ashhart. The branch is rebased on What the rebase changed
Changes on top, found while rebasing and in a review of the diff
HIP
Receipt, rebased head (one GB10, sm_121, driver 580.173.02, nvcc 13.0.88,
Per-vector hashes, as a fixed baseline for #2239 (@jschmied): sha256 of the raw little-endian fp32 bytes of every returned vector, on this head and this GB10. Each input sent alone; the same inputs batched give the same bytes (0 of 12 differ in every size). The 8 token-id inputs are the first receipt's (the 0.6B's long ones differ, from its own tokenizer run); the 4 texts are the model card's, and they give the same tokens and bytes as ids 0-3. We can post the id lists if you want to run the same list. 36 vectors
Gates on the head (laptop, no GPU)
|
|
Thanks @Arminova, this is a clean rebase.
On our side we're arranging the HIP embedding and rerank run against
|
The line ends with the pull request and the thanks, as the file's other entries do.
|
Thanks @ashhart. All five points are done as new commits on top of
Gates on |
|
Thanks @Arminova, all five are in and they hold up. The kernels refusal is back by name and read through the One small thing in What reaches HIP is moved code, and HIP's host tests pass on the merge, so no AMD run is needed for this one.
|
|
Thanks @ashhart. Done in
|
|
Thanks @Arminova, the row reads right now. Each clause matches the code: unset picks the bundled set or our own kernels (
|
…d move out of the HIP files names.zig (config, the reranker's model. scope, head rows read on the host), head.zig (the sentence-transformers head and the window a budget holds) and lane.zig (the prompt-only lane backend, generic over its engine) import no GPU runtime, so a second backend can share them. model.zig, native.zig and prompt.zig keep their names as aliases; the HIP engine and its tests are unchanged.
families/qwen3/cuda/: BF16 weights as stored and the prompt pass over CUDA's shared rows: cuBLASLt projections on one algorithm per (n, k) at every row count, so a batch's rows keep the bits they have alone; residual RMS norms; QK norm with RoPE into one cache; Nemotron's GQA chunks a row block at a time. The engine is lane.zig's backend, and cuda/native.zig opens a sentence-transformers embedding folder as the HIP family does (head from the folder, window from the budget). A reranker folder is refused on CUDA for now. checks.zig holds the GPU-free gates both GPU checks share; native/cuda.zig gets a qwen3 entry, with LFM2's refusal moved into its own open to keep the file at 600 lines.
…g is main's again The first commit shared headOf between HIP and CUDA and dropped the words on HIP from two refusals. headOf takes the backend's name; HIP passes HIP, CUDA passes CUDA, and a test pins both messages.
The qwen3 open bypassed refusals.family, so --prompt-precision int8, --spill-gib and --learn-conversations were ignored on an embedding server. It runs family() first now, and the copies of tp, vision, kv8, loops, drafter and mtp-confidence in the family's own open go, since family() and backend() give those words. A host test opens qwen3 with each flag and checks the refusal.
…the open's early refusals The open held a second driver and context beside the engine's. The engine's now serves the budget, the capability and close, and the lane host reads its health (a sticky fault stops new requests) and its memory. The folder's head and the device count are checked before the engine takes a driver, a context or weights. A load error goes through the optional-library explanations, as for the other CUDA families. --precision and TENSORFOLD_CUDA_KERNELS are refused by name, in native/cuda_qwen3.zig with the flag refusals.
…y Linux host list test-qwen3-cuda-host was a standalone step, so zig build test never ran the 16 tests of the qwen3_cuda module.
…adable split K, drop tests that cannot fail Lm.init refuses a hidden or intermediate size that is not a multiple of 64 before any request; its attention message printed the hidden size as a head size. gemm accept refuses an algorithm whose split K cannot be read instead of warning. weights.zig drops the head name nothing reads. Two gemm tests compared a value with itself or exercised the hash map alone.
No new direct read of TENSORFOLD_CUDA_KERNELS: a captured kernel set stays unrefused here, as the lean rule asks settings to come through platform/environment.zig.
…tered path setting A captured kernel set was ignored for qwen3 since the lean-rule commit; it is refused by name again, as the Qwen4 open does. The setting is registered as a path in platform/environment.zig (control characters refused, the text never shown) and read through it; a host test covers a captured folder, native, unset, blank and a malformed value.
…t tests; --record joins the flags test Lm.init calls geometry(spec), which names the attention's limit (head_dim 128, whole groups of up to 16 q heads a kv head) or the row kernels' (hidden and intermediate sizes in multiples of 64); a host test breaks each limit on its own. The flags test opens qwen3 with --record too.
The line ends with the pull request and the thanks, as the file's other entries do.
916997d to
d20d41e
Compare
The line ends with the pull request and the thanks, as the file's other entries do.
|
Thank you @Arminova. This is merged into main with your commits under your own name, so you're listed as a TensorFold contributor. It ships in the next release, credited to you in the changelog and the release notes. |
…ith a start position, an audio splice and a tied head over chosen rows (ashhart#2105) qwen3/cuda/lm.zig's forwardFrom takes an Extra: the input's first position, every layer's own k and v caches, rows spliced over the embedded ones, the final norm and the tied head over the pass's last rows, and stage taps. Each is read only when set: forward(.{}) issues the embeddings' launches with the same arguments. qwen3_asr/cuda/lm.zig (the schedule's CUDA binding) goes; the ASR checkpoint loads through weights.Model.fromCheckpoint under its thinker.model. scope, and the position and RoPE uploads are lm.tables, shared with the engine. tf-qwen3-asr-forced drops --row-gemms: every projection keeps one algorithm per (n, k).
test-gradient-serving pins the bytes of the maintained serving kernels and launch wrappers; it stopped at the first changed file, cublaslt.zig, but fourteen differed: - #2585 (dfd9ac5, 52479d3) reads TF_FLASHNEXT_* through platform/environment.zig and refuses malformed text at startup; valid values keep their meaning (ten Flash Next files). - c274b45 on this branch takes that startup check through the flashnext module (cuda_launch.zig and the prompt and compare mains). - #2285 (52375ee) adds cuBLASLt layout and algo-config entries for Qwen3 embeddings to cublaslt.zig; existing entries are unchanged. - #2205 (c8ad987) lets tp_group.zig's RoCE gathers run at any world up to roce.max_world when every rank asks (TF_ROCE's default is unchanged), and #2789 (61a2651) ends a call failing after abort as Aborted. Each row takes its file's new hash, with the change named beside it.
|
Thanks @Arminova! This is in TensorFold 1.2.8, out now: https://github.com/ashhart/TensorFold/releases/tag/v1.2.8
|
What this changes
Qwen/Qwen3-Embedding-0.6B,-4Band-8Bload on the native CUDA engine from the published BF16 weights andserve
POST /v1/embeddings.mainrefuses them on CUDA becauseqwen3has no CUDA backend. Part 1 of the plan in #2261.Part 2, the rerankers, follows once this lands.
11 commits on
maincd6060a43(after 1.2.6),1c5667a7d..2155d68bd:qwen3: checkpoint names, the folder's head and the prompt lane backend move out of the HIP files.names.zig,head.zigandlane.zigtake the parts that import no GPU runtime, so a second backend can sharethem.
model.zig,native.zigandprompt.zigkeep their names as aliases. The HIP engine and its tests do notchange.
model.zigismain's file with66c37975a's gate / up packing as it is.qwen3 cuda: Qwen3 embedding checkpoints on the native CUDA engine, infamilies/qwen3/cuda/:one cache, and Nemotron's GQA chunks one row block at a time.
so each input's rows keep the bits they have alone, in any batch.
lane.zig's backend.cuda/native.zigopens a sentence-transformers folder as the HIP familydoes: the head comes from the folder, the window from the memory budget.
qwen3: the folder head's refusals name their backend, so HIP's wording is main's again:headOftakes thebackend's name; a test pins HIP's and CUDA's wording.
qwen3 cuda: the open runs the CUDA families' flag refusals:--drafter,--vision,--kv-cache fp8,--prompt-precision int8,--loops,--tp,--spill-gib,--learn-conversationsand--precisionarerefused by name; a host test opens
qwen3with each.qwen3 cuda: one device context, the host's readiness and memory, and the open's early refusals: one driver andcontext (the engine's), read for
/status; a missing cuBLASLt library gets the other families' install hint; thefolder's head and the device are checked before any weights are uploaded.
build: the qwen3 CUDA host tests run in test-cuda-host and the nightly Linux host list.qwen3 cuda: refuse a geometry the row kernels cannot take and an unreadable split K, drop tests that cannot fail.qwen3 cuda: keep the new file within the lean rule.qwen3 cuda: keep the TENSORFOLD_CUDA_KERNELS refusal, read as a registered path setting(4894006): a capturedkernel set is refused by name again; the setting is a
.pathrule inplatform/environment.zig, with a hosttest through the open.
qwen3 cuda: the geometry limits are a function over the spec with host tests; --record joins the flags test(c2f3cfa):
Lm.initcallsgeometry(spec), and a host test breaks each limit on its own.changelog: Qwen3 embeddings on CUDA under Unreleased (#2285)(2155d68).Shared files:
cuda/cublaslt.ziggains three attribute ids fromcublasLt.handcublasLtMatmulAlgoConfigGetAttribute.native/cuda_families.ziggets aqwen3entry;native/cuda.zigdispatchesqwen3tonative/cuda_qwen3.zig(562 lines).
platform/environment.zigregistersTENSORFOLD_CUDA_KERNELS(a path;docs/environment.mdhas its row).Issue: Qwen3 embedding and reranker checkpoints (BF16) on the native CUDA engine #2261 (claimed).
Base:
maincd6060a43. Merges clean withmain22fb6014b(checked 11-10).atc overlap --json: not run, because ATC is not installed here. By hand (11-10): no other open pull request touchesa qwen3 file;
native/cuda_families.zigtakes one entry, as in 11 other open pull requests.Related: qwen3-asr: host slice, the shared tower and Qwen3 schedules over a backend binding (#2105) #2239 (@jschmied, Qwen3-ASR) carries a Qwen3 language model on CUDA for one sequence and the ASR
checkpoint's names. This one serves the plain Qwen3 checkpoints, with many independent inputs in a pass.
Receipt
Environment: one GB10 (sm_121), driver 580.173.02, nvcc 13.0.88,
--tp 1,--context 32768.Qwen/Qwen3-Embedding-0.6Bat97b0c614,-4Bat5cf2132a,-8Bat1d8ad4ca.Builds: the receipt below ran on
4257e6f01. The commits after it change only the open's refusals, the geometrycheck's shape, the changelog and tests; the 8B oracle
checkran again on a server built from this head's code:distance 9.70e-05, same as before.
Upstream's pinned oracle,
tools/zig/oracle_qwen3_embedding.py(transformers 5.22.1,1d8ad4ca; fp32 reference onthe CPU, BF16 bar on the GPU):
checkon4257e6f01: distance 9.70e-05, norm 1.000000, the card's scores within0.0013, PASS. On this head's code: distance 9.70e-05, norm 1.000000, scores within 0.0013, PASS.
Our own comparison: transformers 5.15 in fp32 on the GPU as the reference, with 8 inputs of 8, 23, 27, 31, 2,048,
8,192, 16,384 and 30,000 tokens.
digit. All norms are 1.000000.
Speed is for batches of 32 from one client.
mainhas no number to compare, because it refuses these checkpoints onCUDA.
Exactness, on every size:
dimensions(32, 1,024 and full): each vector equals the full one cropped and renormalised, within 1.8e-8.Per-vector sha256 of the raw fp32 bytes (36 vectors) and the input lists: posted in the 11-10 comment on this pull
request (qwen3 cuda: Qwen3 embedding checkpoints (BF16) on the native CUDA engine (#2261, 1 of 2) #2285 (comment)); the same inputs batched give the same bytes, 0 of 12 differ in every size.
zig build qwen3-cuda-gpu-test(8B, the oracle's nine inputs, the references' pins checked): 13 of 13 tests passedon
4257e6f01(not repeated on the later commits).Not applicable: drafts and resumed prompts. This is a prompt-only engine with no draft and no prompt cache.
Prompt speed: no number to compare, because
mainrefuses these checkpoints on CUDA. The documents-a-secondcolumn above is this change's prompt pass.
Leaks: no server reported one on exit, including the one that refused a reranker folder.
Tests (laptop, no GPU, on
2155d68bd):zig build test: 653 / 653 steps;zig build test-cuda-host: 367 / 367;compile-config-server: 7 / 7;qwen3-cuda-gpu-build: 3 / 3;test-qwen3-checks-hostandtest-qwen3-cuda-host: pass.Lean checker on
maincd6060a43and this head: 386 / 386, 0 new.tools/pr_check.py --pr 2285: 0 errors, 0 warnings.zig fmt --check build.zig zig: clean.git diff --check: clean.native/cuda.zig562 lines,platform/environment.zig363.Not run:
engine.zig,forward.zig,projection_test.zig,zig/src/hip/andzig/kernels/are byte-identical tomain; the removed lines in theother HIP files reappear in
names.zig,head.zig,lane.zigandchecks.zig.Licence of new weights: n/a.
Checklist
because the engine is prompt-only.
mainis unchanged for every other family.a device.
TF_QWEN3_EMBED_*).