Skip to content

qwen3 cuda: Qwen3 embedding checkpoints (BF16) on the native CUDA engine (#2261, 1 of 2) - #2285

Merged
ashhart merged 13 commits into
ashhart:mainfrom
Arminova:qwen3/cuda-embeddings
Nov 12, 2026
Merged

ashhart merged 13 commits into
ashhart:mainfrom
Arminova:qwen3/cuda-embeddings

Conversation

@Arminova

@Arminova Arminova commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

Qwen/Qwen3-Embedding-0.6B, -4B and -8B load on the native CUDA engine from the published BF16 weights and
serve POST /v1/embeddings. main refuses them on CUDA because qwen3 has no CUDA backend. Part 1 of the plan in #2261.
Part 2, the rerankers, follows once this lands.

11 commits on main cd6060a43 (after 1.2.6), 1c5667a7d .. 2155d68bd:

  1. qwen3: checkpoint names, the folder's head and the prompt lane backend move out of the HIP files.
    names.zig, head.zig and lane.zig take the parts that 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 do not
    change. model.zig is main's file with 66c37975a's gate / up packing as it is.
  2. qwen3 cuda: Qwen3 embedding checkpoints on the native CUDA engine, in families/qwen3/cuda/:
    • BF16 weights as stored.
    • The batched prompt pass on CUDA's shared rows, with no new kernels: residual RMS norms, QK norm with RoPE into
      one cache, and Nemotron's GQA chunks one row block at a time.
    • Projections on cuBLASLt with one algorithm per (n, k), chosen once. Split-K and reduction schemes are refused,
      so each input's rows keep the bits they have alone, in any batch.
    • The engine is lane.zig's backend. cuda/native.zig opens a sentence-transformers folder as the HIP family
      does: the head comes from the folder, the window from the memory budget.
    • The engine takes Rerank on HIP: compute the shared trunk once a pass (25% faster, same bytes) #2262's trunk argument and refuses a nonzero trunk, which embeddings never send.
    • A reranker folder is refused on CUDA with a message, until part 2.
  3. Review fixes, each its own commit:
    • qwen3: the folder head's refusals name their backend, so HIP's wording is main's again: headOf takes the
      backend'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-conversations and --precision are
      refused by name; a host test opens qwen3 with each.
    • qwen3 cuda: one device context, the host's readiness and memory, and the open's early refusals: one driver and
      context (the engine's), read for /status; a missing cuBLASLt library gets the other families' install hint; the
      folder'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 captured
      kernel set is refused by name again; the setting is a .path rule in platform/environment.zig, with a host
      test through the open.
    • qwen3 cuda: the geometry limits are a function over the spec with host tests; --record joins the flags test
      (c2f3cfa): Lm.init calls geometry(spec), and a host test breaks each limit on its own.
    • changelog: Qwen3 embeddings on CUDA under Unreleased (#2285) (2155d68).

Shared files:

  • cuda/cublaslt.zig gains three attribute ids from cublasLt.h and cublasLtMatmulAlgoConfigGetAttribute.

  • native/cuda_families.zig gets a qwen3 entry; native/cuda.zig dispatches qwen3 to native/cuda_qwen3.zig
    (562 lines).

  • platform/environment.zig registers TENSORFOLD_CUDA_KERNELS (a path; docs/environment.md has its row).

  • Issue: Qwen3 embedding and reranker checkpoints (BF16) on the native CUDA engine #2261 (claimed).

  • Base: main cd6060a43. Merges clean with main 22fb6014b (checked 11-10).

  • atc overlap --json: not run, because ATC is not installed here. By hand (11-10): no other open pull request touches
    a qwen3 file; native/cuda_families.zig takes 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.

    • Checkpoints: Qwen/Qwen3-Embedding-0.6B at 97b0c614, -4B at 5cf2132a, -8B at 1d8ad4ca.
  • Builds: the receipt below ran on 4257e6f01. The commits after it change only the open's refusals, the geometry
    check's shape, the changelog and tests; the 8B oracle check ran 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 on
    the CPU, BF16 bar on the GPU): check on 4257e6f01: distance 9.70e-05, norm 1.000000, the card's scores within
    0.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.

    • Every vector is closer to the fp32 vector than transformers' own BF16 run of the same input.
    • Every input's distance is equal to the first receipt's, taken on the first version of this change, to the last
      digit. All norms are 1.000000.
    worst cosine distance to fp32 transformers BF16's worst short documents a second
    0.6B 1.55e-4 1.88e-4 630
    4B 1.34e-4 1.79e-4 248
    8B 1.08e-4 1.77e-4 187

    Speed is for batches of 32 from one client. main has no number to compare, because it refuses these checkpoints on
    CUDA.

  • Exactness, on every size:

    • The 8 inputs in one request give the same bytes as each one alone (8 of 8).
    • 4 clients sending 5 requests each at once: 20 of 20 equal their bytes alone.
    • Text input equals the same token ids sent as ids: 4 of 4.
    • 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 passed
    on 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 main refuses these checkpoints on CUDA. The documents-a-second
    column 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-host and test-qwen3-cuda-host: pass.
  • Lean checker on main cd6060a43 and 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.zig 562 lines, platform/environment.zig 363.

  • Not run:

    • HIP on a device: we have no AMD GPU. Its host tests pass. HIP's engine.zig, forward.zig,
      projection_test.zig, zig/src/hip/ and zig/kernels/ are byte-identical to main; the removed lines in the
      other HIP files reappear in names.zig, head.zig, lane.zig and checks.zig.
    • Other CUDA GPUs, TP2, Windows and macOS: no runner here.
  • Licence of new weights: n/a.

Checklist

  • Every token still goes through the lane rounds. No serial path, and nothing that needs drafts off.
  • Concurrent equals solo, and a batch equals its inputs alone. Drafts and resumed prompts are not applicable,
    because the engine is prompt-only.
  • No precision traded for speed: BF16 weights as published, f32 sums, and closer to fp32 than transformers' BF16.
  • No new GPU kernel. It uses CUDA's shared row kernels and cuBLASLt.
  • Prompt processing on main is unchanged for every other family.
  • Platforms: CUDA on one GB10. Not run: other CUDA GPUs, Metal (no qwen3 Metal backend is touched), and HIP on
    a device.
  • New tests pass. The GPU check skips cleanly without its checkpoint and references (TF_QWEN3_EMBED_*).
  • Comments and docstrings are one line. No measurements or history in the source.
  • No personal data, machine names, internal hosts or local paths. No AI attribution lines.

@ashhart

ashhart commented Nov 5, 2026

Copy link
Copy Markdown
Owner

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.

@ashhart

ashhart commented Nov 10, 2026

Copy link
Copy Markdown
Owner

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:

  1. Rebase on main. It conflicts in four files, including qwen3/model.zig, where main now packs gate and up for HIP's WMMA path (66c37975a). The split needs to keep that change as it is.
  2. Because HIP's files move, we need HIP's embedding and rerank bytes and speed against main on an AMD card. If you don't have one, say so and we'll work out the run.
  3. Your receipts for all three sizes on the rebased head, and qwen3-cuda-gpu-test run on the GB10.

  • Synthena (Ash's Agent)

@Arminova

Copy link
Copy Markdown
Contributor Author

Thanks @ashhart. On it: a rebase onto main that keeps 66c37975a's gate / up packing as it is, then receipts for the 0.6B, 4B and 8B on the rebased head and qwen3-cuda-gpu-test on the GB10.

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.

@ashhart

ashhart commented Nov 10, 2026

Copy link
Copy Markdown
Owner

Thanks @Arminova. Not having an AMD card is fine: we'll work out the HIP embedding and rerank run on our side once
the rebased head is up, and the diff of HIP's code against main that you offered will help us read it. The four
conflicts are still build.zig, zig/build/cuda_engines.zig, qwen3/model.zig and native/cuda.zig; main's
native/cuda.zig is at 556 lines now, so the qwen3 entry still needs to keep it at 600 or under. We'll pick it
up again when the rebased head and its receipts are posted.

  • Synthena (Ash's Agent)

@Arminova
Arminova force-pushed the qwen3/cuda-embeddings branch from e0665b7 to 4257e6f Compare November 10, 2026 17:39
@Arminova

Copy link
Copy Markdown
Contributor Author

Thanks @ashhart. The branch is rebased on main cd6060a43 (after 1.2.6) and force-pushed: the two commits from before, rebased, and six fixes on top, 1c5667a7d .. 4257e6f01. It merges with today's main (14dea6c6a) without conflicts, and native/cuda.zig is 561 lines.

What the rebase changed

  • qwen3/model.zig: main's file as it is, including 66c37975a's gate / up packing (Layout, Mlp, packedMlp, interleave, Model.load(..., layout)). The first commit only takes out what moved to names.zig and leaves its aliases. None of 66c37975a's lines is in the diff.
  • native/cuda.zig: main moved the CUDA family registry to native/cuda_families.zig; the qwen3 entry and its capability test are there now.
  • build.zig, zig/build/cuda_engines.zig: both sides' lines kept.
  • TF_CUDA_DEVICE / --device are read through setting() like every other CUDA family (the lean check flagged our direct read).
  • LFM2's refusal stays in cuda.zig; the 600-line cap no longer needs it moved.

Changes on top, found while rebasing and in a review of the diff

  • HIP's two pooling refusals read as on main again ("qwen3 on HIP ..."): headOf takes the backend's name; a test pins both wordings.
  • The CUDA open runs the shared flag refusals, so --prompt-precision int8, --spill-gib, --learn-conversations, --kv-cache fp8, --vision, --drafter, --loops, --tp and --precision are refused by name instead of ignored; a host test opens qwen3 with each.
  • The open keeps one driver and context (the engine's); the lane host now reads that context's health and the process's memory counts for /status.
  • A missing cuBLASLt library gets the same install hint as the other CUDA families; the folder's head and the device are checked before any weights are uploaded.
  • Lm.init refuses a hidden or intermediate size that is not a multiple of 64; an algorithm whose split K can't be read is refused instead of warned about.
  • test-qwen3-cuda-host is part of test-cuda-host and the nightly Linux host list (it was a standalone step).

HIP

  • Thanks for taking HIP's embedding and rerank run.
  • HIP's files against main (git diff cd6060a43 4257e6f01): engine.zig, forward.zig, projection_test.zig, zig/src/hip/ and zig/kernels/ are unchanged. model.zig, native.zig, prompt.zig, score.zig, qwen3.zig and forward_test.zig: 42 lines added, 727 removed.
  • The removed lines moved to names.zig, head.zig, lane.zig and checks.zig, which CUDA shares. The only edits on the way: pub, the backend's name passed into the folder head's two refusals (HIP passes "HIP", so its text is main's), and fitWindow taking the engine's least window as an argument.
  • The 42 added lines are the imports, the aliases that keep the old names, and HIP's one-line headOf wrapper. git diff --color-moved cd6060a43 4257e6f01 -- zig/src/families/qwen3/ shows the moves.
  • git range-diff of the old and the new two commits: only the conflict resolutions above.

Receipt, rebased head (one GB10, sm_121, driver 580.173.02, nvcc 13.0.88, --tp 1, --context 32768; checkpoints Qwen/Qwen3-Embedding-0.6B at 97b0c614, -4B at 5cf2132a, -8B at 1d8ad4ca; the same references and scripts as the first receipt)

worst cosine distance to fp32 first receipt transformers BF16's worst short documents a second first receipt
0.6B 1.55e-4 1.55e-4 1.88e-4 630 628
4B 1.34e-4 1.34e-4 1.79e-4 248 249
8B 1.08e-4 1.08e-4 1.77e-4 187 185
  • Every input's distance in all three sizes is equal to the first receipt's to the last digit. All norms are 1.000000.
  • Exactness, every size: the 8 inputs in one request equal each one alone (8 of 8); 4 clients x 5 requests: 20 of 20 equal their bytes alone; text equals ids 4 of 4; dimensions 32, 1,024 and full: crop-and-renormalise within 1.8e-8.
  • tools/zig/oracle_qwen3_embedding.py check on the 8B: distance 9.70e-05, norm 1.000000, scores within 0.0013, PASS.
  • zig build qwen3-cuda-gpu-test (8B, the oracle's nine inputs, the references' pins checked): 13 of 13 tests passed, 8 minutes.
  • No server reported a leak on exit; a reranker folder is refused with its message.

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

Qwen3-Embedding-0.6B

input tokens input sha256 vector sha256 (fp32 bytes)
ids 0 27 6dbe29d7a4305283 ff31adc8c37bcfb832789f0a9325d5de63d524ec868dfc8b02a7acc0fe34062f
ids 1 23 f75df040207962e1 151366cc7b6c7f92742b23374efef8d231f8d87757b60d55f8f89eb2c91a3c42
ids 2 8 5455f79e6775261a 0482e7729f0c8a9987e653aa7992279873b324cdfe921ca532dc92199b29f347
ids 3 31 9936daa471cb8845 2f31404bf7ca02ff2c0f8eeccba84242507e72b2c982a872559ed2a6c423fb72
ids 4 2048 6111b04fed46b015 44604eb56c32f903411a5525e330c54317989fc3a4e9cbce669c63b5f4e34b0b
ids 5 8192 8f5d4c8a214e6a15 abbc20104c1de8fc06f9b8be9610e76089e83676a249011d82a075c0b4d51c76
ids 6 16384 92a6e8f8f28df14f 5c8f00ad92ae112c78af7d796c298a6c89ac40618498d6042a8a5c2d48565234
ids 7 30000 83ede0a52cac36fc ade331d64699acaf52b8725b7f049ad247d391bac0fde805dc260751bf9a1b04
text 0 b11f8d3ae64c3c70 ff31adc8c37bcfb832789f0a9325d5de63d524ec868dfc8b02a7acc0fe34062f
text 1 bd6e4e8bb1b5c2e1 151366cc7b6c7f92742b23374efef8d231f8d87757b60d55f8f89eb2c91a3c42
text 2 b6d0a6ed3a94d5e5 0482e7729f0c8a9987e653aa7992279873b324cdfe921ca532dc92199b29f347
text 3 ba5bd6b5de9d9f82 2f31404bf7ca02ff2c0f8eeccba84242507e72b2c982a872559ed2a6c423fb72

Qwen3-Embedding-4B

input tokens input sha256 vector sha256 (fp32 bytes)
ids 0 27 6dbe29d7a4305283 59f5860349c7f06f6ec57d5b9b66710cf1345186d2c64b22889d3c9dc888b60e
ids 1 23 f75df040207962e1 b7475ebabcefc91047c19fe25e84b8b8dc6b6b0008f493cce3a5f220f105dd90
ids 2 8 5455f79e6775261a 083ab23ebaf4c7475a05599356e9d5a31d584e5013b5367eebb4592967b146b9
ids 3 31 9936daa471cb8845 133d92f7f5d271722a70dd15e122c4c0b2ea6a790f258ed69a7487ba77127778
ids 4 2048 6111b04fed46b015 abcddb98da50dd1bc29443ffe8c0dd2f3d8bf0df2d503eff97a4d2d5c9925774
ids 5 8192 825f4feec54969e5 fa7a0313786c8dfa66c989baa9522ff3554d12bd53734d392cc902b633968324
ids 6 16384 fd9fba7f22a90346 2f45d6e59efb0f3f20a872d74f4d6d3a5e775206aaeae21d01fa3cbdc0ed8005
ids 7 30000 af38eca2ebd2fe7e f229e54ba50c702d3d13a7cf52234eed3035cfff331b5ce96c0dff2a6dea7da7
text 0 b11f8d3ae64c3c70 59f5860349c7f06f6ec57d5b9b66710cf1345186d2c64b22889d3c9dc888b60e
text 1 bd6e4e8bb1b5c2e1 b7475ebabcefc91047c19fe25e84b8b8dc6b6b0008f493cce3a5f220f105dd90
text 2 b6d0a6ed3a94d5e5 083ab23ebaf4c7475a05599356e9d5a31d584e5013b5367eebb4592967b146b9
text 3 ba5bd6b5de9d9f82 133d92f7f5d271722a70dd15e122c4c0b2ea6a790f258ed69a7487ba77127778

Qwen3-Embedding-8B

input tokens input sha256 vector sha256 (fp32 bytes)
ids 0 27 6dbe29d7a4305283 152b00ef0d69b347931957c03667df1e2188636b78110707dcb6b06ec62a6934
ids 1 23 f75df040207962e1 76a0b3af39ae16df09889332eeec1b53ce561f3d779027f74651c5a6c413c81c
ids 2 8 5455f79e6775261a 5f060d42bd38dbf165565e32bf6a83dfc3aa61c3a0fd76ecca1f48474f951e12
ids 3 31 9936daa471cb8845 c840a5827668b8c84c010dc8091371e93c61d985d08b1250d42cfae3b44674ab
ids 4 2048 6111b04fed46b015 91806929ebce2e603e7dc6e09a36087d56418ce5dedd2879c8dcc4e3daa37d32
ids 5 8192 825f4feec54969e5 f901d24cfc42cd7a999b262b016e5b435b2c29a5d87e88980965efaa398ccc5b
ids 6 16384 fd9fba7f22a90346 ae89d4d08b6d82a2dbbe356891926e13b9e116265e2d0c15b5e065d335727903
ids 7 30000 af38eca2ebd2fe7e bdb2570d1e8965b35f31578cd0d08e5b3c7d20bf976add5b0fc3d27a8a2e3b79
text 0 b11f8d3ae64c3c70 152b00ef0d69b347931957c03667df1e2188636b78110707dcb6b06ec62a6934
text 1 bd6e4e8bb1b5c2e1 76a0b3af39ae16df09889332eeec1b53ce561f3d779027f74651c5a6c413c81c
text 2 b6d0a6ed3a94d5e5 5f060d42bd38dbf165565e32bf6a83dfc3aa61c3a0fd76ecca1f48474f951e12
text 3 ba5bd6b5de9d9f82 c840a5827668b8c84c010dc8091371e93c61d985d08b1250d42cfae3b44674ab

Gates on the head (laptop, no GPU)

  • zig build test: 653 / 653 steps; test-cuda-host: 367 / 367; compile-config-server: 7 / 7; qwen3-cuda-gpu-build: 3 / 3; test-qwen3-checks-host, test-qwen3-cuda-host: pass.
  • zig fmt --check build.zig zig: clean. Lean checker against main: 386 problems on both, 0 new. git diff --check: clean.
  • Not run: HIP on a device, other CUDA GPUs, TP2, Windows and macOS, failure injection on allocations.

@ashhart

ashhart commented Nov 10, 2026

Copy link
Copy Markdown
Owner

Thanks @Arminova, this is a clean rebase. 66c37975a's packing is untouched, native/cuda.zig is at 561 lines, it
merges with today's main without conflicts, and every distance matches the first receipt to the last digit. Your
summary of HIP's diff made the moves easy to check: the removed lines all reappear in names.zig, head.zig,
lane.zig and checks.zig, with only the edits you listed. The per-vector hashes will be a good baseline for #2239.
A few small things before we run it:

  1. 4257e6f01 drops the TENSORFOLD_CUDA_KERNELS refusal, so a captured kernel set is now ignored for qwen3
    instead of refused by name, as the Qwen4 open does. Please keep the refusal and read the setting the way the lean
    rule asks, for example by registering it as a path setting in platform/environment.zig, with a case in
    cuda_qwen3_test.zig.
  2. The new multiple-of-64 check (cuda/lm.zig:150) and the head geometry check above it run only inside Lm.init,
    so no test reaches them. A small function over config.Spec that Lm.init calls would let a host test cover
    both. The --record refusal could join the flags test too.
  3. Please add a line under Unreleased in CHANGELOG.md ending "(qwen3 cuda: Qwen3 embedding checkpoints (BF16) on the native CUDA engine (#2261, 1 of 2) #2285). Thanks to @Arminova."
  4. The description still describes the first version (two commits, base 1d891001b, LFM2's refusal moving to
    lfm2.zig, nvcc 13.3.73). Could you bring it up to date with this head and the new receipt?
  5. Which commit did the new receipt run on? If it was before 4257e6f01, that's fine, since that commit only changes
    the open's refusals; just say so. After the change in 1, the 8B oracle check on the new head is enough.

On our side we're arranging the HIP embedding and rerank run against main, and CI on the new head.

  • Synthena (Ash's Agent)

Arminova added a commit to Arminova/TensorFold that referenced this pull request Nov 10, 2026
The line ends with the pull request and the thanks, as the file's other entries do.
@Arminova

Copy link
Copy Markdown
Contributor Author

Thanks @ashhart. All five points are done as new commits on top of 4257e6f01 (no force-push): 4894006f2, c2f3cfa32, 2155d68bd.

  1. TENSORFOLD_CUDA_KERNELS (4894006f2): the refusal is back, by name, in the Qwen4 open's words ("Qwen3 uses embedded native images: unset TENSORFOLD_CUDA_KERNELS or set native"). The setting is a .path rule in platform/environment.zig (control characters refused, the text never shown), with its row in docs/environment.md, and cuda_qwen3.zig reads it through environment.path. A host test covers a captured folder, native, unset, blank and a malformed value, and opens qwen3 with the setting present: it fails without the refusal (checked by taking the check out).
  2. Geometry (c2f3cfa32): geometry(spec) in cuda/lm.zig holds the head check (head_dim 128, whole groups of at most 16 q heads a kv head) and the multiple-of-64 check; Lm.init calls it and logs by case. A host test breaks each limit on its own (head size, zero kv heads, a group that is not whole, 17 q heads a kv head, hidden, intermediate); each of five single-check removals fails it. The flags test opens qwen3 with --record too.
  3. Changelog (2155d68bd): one line under Unreleased, ending "(qwen3 cuda: Qwen3 embedding checkpoints (BF16) on the native CUDA engine (#2261, 1 of 2) #2285). Thanks to @Arminova." It sits last in the section, so the branch merges with today's main (22fb6014b) without conflicts.
  4. Description: brought up to date with this head and the new receipt: the commits on cd6060a43, no 1d891001b, no LFM2 move, nvcc 13.0.88, the receipt table and checks from the 17:39Z comment, gates of the head.
  5. Receipt: the 17:39Z receipt ran on 4257e6f01 itself (the box's source tree for that build is that commit). The commits above change only the open's refusals, the geometry check's shape, the changelog and tests. After point 1, the 8B oracle check on a server rebuilt from this head's code (same references and window as the first receipt): distance 9.70e-05, norm 1.000000, the card's scores within 0.0013, PASS; every input's distance equals the first receipt's; no leak reported on exit.

Gates on 2155d68bd (laptop, no GPU): zig build test: 653 / 653 steps; test-cuda-host: 367 / 367; compile-config-server: 7 / 7; qwen3-cuda-gpu-build: 3 / 3; test-qwen3-checks-host, test-qwen3-cuda-host: pass. Each of the two code commits also passes test-cuda-host, test-qwen3-cuda-host and test-environment-host on its own. tools/pr_check.py --pr 2285 against 22fb6014b: 0 errors, 0 warnings, lean 386 / 386, 0 new. zig fmt --check build.zig zig: clean. git diff --check: clean. native/cuda.zig is 562 lines, platform/environment.zig 363.

@ashhart

ashhart commented Nov 10, 2026

Copy link
Copy Markdown
Owner

Thanks @Arminova, all five are in and they hold up. The kernels refusal is back by name and read through the
registry. Taking out the open's call, the native exception or the malformed-value branch each fails the new test,
and so does dropping the .path row. geometry(spec) is a clean cut: removing any one of its six checks fails the
host test, and so does turning > 16 into >= 16. --record now fails the flags test if its refusal goes. The
changelog line sits under Unreleased with the receipt's numbers, the description matches this head, and naming
4257e6f01 for the receipt plus the 8B check on the new code is what we needed. It merges cleanly with today's
main, the lean count is unchanged, and HIP's host tests and the HIP server's compile check pass on the merge.

One small thing in docs/environment.md. The new TENSORFOLD_CUDA_KERNELS row ends "unset or blank picks the
engine's default", and the page says blank text means unset for every variable on it. That holds for Qwen3, but the
main CUDA open (native/cuda.zig) still passes a blank value to the kernel-set lookup as a folder name and refuses
it ("holds no aot.json"), and the Qwen4 and Drama 3 opens refuse a whitespace-only value. Could you make the row say
only what holds? For example: unset uses the binary's bundled set for the GPU when it has one, else the engine's own
kernels, and Qwen3 reads a blank value as unset. Then list TENSORFOLD_CUDA_KERNELS beside TF_GEMMA4_CHUNK_SOLO as
an exception to the blank rule. A doc-only commit is enough, with no new receipt.

What reaches HIP is moved code, and HIP's host tests pass on the merge, so no AMD run is needed for this one. main freezes tomorrow morning for 1.2.7, so if
the doc line is in before then, this can go in with it.

  • Synthena (Ash's Agent)

@Arminova

Copy link
Copy Markdown
Contributor Author

Thanks @ashhart. Done in 916997dfb (docs only): the row now says unset uses the binary's bundled set for the GPU when it has one, else the engine's own kernels, and that Qwen3 reads a blank value as unset. TENSORFOLD_CUDA_KERNELS is listed beside TF_GEMMA4_CHUNK_SOLO as an exception to the blank rule: the main CUDA open refuses a blank value as a folder with no aot.json, and the Qwen4 and Drama 3 opens refuse a value of only whitespace.

zig build test: 653 / 653 steps; tools/pr_check.py --pr 2285: 0 errors, 0 warnings, lean unchanged; merges cleanly with main fb8fdf3d8.

@ashhart

ashhart commented Nov 11, 2026

Copy link
Copy Markdown
Owner

Thanks @Arminova, the row reads right now. Each clause matches the code: unset picks the bundled set or our own kernels (zig/src/cuda/aot.zig:15-22), the main CUDA open takes a blank value as a folder and refuses it (zig/src/native/cuda.zig:249-253), the Qwen4 and Drama 3 opens refuse whitespace but not an empty value (zig/src/native/qwen4_cuda.zig:79, zig/src/native/drama_open.zig:60), and Qwen3 reads blank as unset through environment.path (zig/src/platform/environment.zig:287-291). Listing it beside TF_GEMMA4_CHUNK_SOLO keeps the page honest. Nothing else is needed from you; this goes on our landing queue.

  • Synthena (Ash's Agent)

…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.
@ashhart
ashhart force-pushed the qwen3/cuda-embeddings branch from 916997d to d20d41e Compare November 12, 2026 02:10
@ashhart
ashhart merged commit d7cba21 into ashhart:main Nov 12, 2026
2 of 10 checks passed
ashhart pushed a commit that referenced this pull request Nov 12, 2026
The line ends with the pull request and the thanks, as the file's other entries do.
@ashhart

ashhart commented Nov 12, 2026

Copy link
Copy Markdown
Owner

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.

jschmied added a commit to jschmied/TensorFold that referenced this pull request Nov 12, 2026
…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).
ashhart added a commit that referenced this pull request Nov 12, 2026
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.
@ashhart

ashhart commented Nov 13, 2026

Copy link
Copy Markdown
Owner

Thanks @Arminova! This is in TensorFold 1.2.8, out now: https://github.com/ashhart/TensorFold/releases/tag/v1.2.8

  • Synthena (Ash's Agent)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants