Repository navigation
Native server: --backend xpu for Nemotron on Intel Arc - #547
Conversation
9f91e08 to
10be7a4
Compare
|
Thank you, @BHCC2025. This comes after #523. Before it can land, the server path needs to be lane-native like every other backend's: Nemotron's drafts (its MTP head and copies) through the lane core, and concurrent requests sharing rounds, with drafted equal to |
…ory, refused when they do not fit
|
Hello @BHCC2025, Thank you for the Arc server entry and the B70 receipts, including the four-stream queue and the seeded draws. The server path still decodes one stream, one row, with drafts off. xpu_lane.zig:77 sets mtp false, speculate false, exact_width 1, and max_streams 1. draft refuses (xpu_lane.zig:164). A prompt longer than 16 tokens is prefilled in windows (xpu_lane.zig:115), and each decode round is one row. There is no prompt-cache restore, so a second turn does not keep the first prompt. Please serve through the lane core the way the other backends do: Nemotron's MTP head and copy drafts on, concurrent requests in one round, a resumed turn equal to a fresh prompt, and drafted output equal to a run with drafts off. A push with those is the update we need. The B70 is the machine for the receipts. Thank you again. |
…Intel Arc (experimental)
…n this tree, and the default build mode
… kernel comments NEM_LAYERS, NEM_OLD_ATTN, NEM_OLD_DEC, NEM_SCALAR and NEM_TRACE are gone with the row-kernel path they selected (and its kernels). The ARC_* memory settings become one fixed rule: a program does not start while other processes hold more than 8 GB of device memory. Every comment in the .cl kernels is one line of at most 120 columns.
…econd loader The runtime's own loader and ABI declarations (driver.zig, abi.zig) are gone. Driver resolves ze.Api with ze.resolve, plus the few entries ze.Api leaves out (teardown, fills, the SPIR-V build log); Context keeps its device as a ze.Gpu, and the descriptors, handles and device properties are ze.zig's. ze.zig is its own build module, imported by the Qwen27 runner and the Nemotron runtime alike, so both can share one binary. Kernels stay SPIR-V, built by the driver at load. On the Arc Pro B70 the CLI gives the same tokens as before: 128 greedy tokens from a 5-token prompt (sha 2fb8107dc0ce), a 3,000-token prompt with 512- and 128-row prefill (b0d8c69d8fa3), at the same speed; the op tests pass.
…ndow equals the decode step The 4-bit projections of a window of up to 16 rows (Mamba in/out, q/k/v/o, head, shared and routed experts) run on one row kernel body with the decode kernels' per-output order: a lane takes the 8-input words l, l+16, ..., per word dot and sum of inputs in the decode's order, then acc += sc * dot + bi * sum, one sub-group reduction a row; each weight word is read and unpacked once for all the rows. It is built per row bucket (1, 2, 4, 8, 16 rows). Windows no longer take the split-K matrix-engine GEMMs; prompt chunks above 16 rows keep the prefill GEMMs. tf-xpu-nem_rows-test on the B70: a one-row window against the decode step, max |logit diff| 0; windows of 2 to 16 rows, in place and pending then committed, 0 rows differ. Cost: decode step 5.54 ms, window of 1 / 2 / 4 / 8 / 16 rows 6.47 / 9.82 / 17.06 / 32.73 / 76.66 ms.
…mpt's bits do not depend on how it is cut Prompt chunks above 16 rows ran on matrix-engine GEMMs that summed in another order than the one-row path, so the same prompt cut at another place gave other bits. They now run nem_prow.cl: every output sums exactly as the decode kernels do (a lane takes the 8-input words l, l+16, ..., dot and sum of inputs in the decode's order, one sub-group reduction), with a sub-group holding 8 rows and 8 outputs so each weight word is unpacked once for 8 rows and each input word once for 8 outputs. Routed experts take their slots per expert in the same order; prompt attention runs the decode kernel per row, 16 rows a launch. The prefill GEMMs (xpu_pf.zig, nem_pf.cl, nem_attn_pfs.cl) and their test go. On the Arc Pro B70: after 640 tokens cut in chunks of 16, 17, 100, 128 and 512 rows the Mamba states and KV bytes are byte-identical to the row-by-row state and the next 8 decode logits are identical (tf-xpu-nem_seam-test); the 15-prompt teacher-forced compare gives the plain decode figures exactly with 512- and 128-row prompt windows; tf-xpu-nem_fp64-test within bounds. Prompt speed (tensorfold-xpu run --prefill 512): 851 tok/s at 2,048 tokens, 755 at 32,768, 511 at 131,072, against 3,108 / 2,888 / 2,281 with the GEMMs and about 215 in 16-row windows. Kernels that spill registers now say so.
native/xpu.zig (the XPU server's engines) lists nemotron_h with format mlx-q4g64 and opens it on its own lane host (native/xpu_nem.zig): the Nemotron engine behind the lane core, host sampling, one stream at a time. The XPU build step links Nemotron's runtime and family modules into the server, sharing ze.zig and the lanes module with the Qwen27 runner. tools/zig/xpu_token_sha.py prints the CLI's and the server's token digests for one token list.
…, drafted == "draft": false With the head loaded (mtp-4bit.safetensors present, drafts not turned off) the Arc lane host serves windows of up to 16 rows: verify runs the pending token and its drafts as one window, keep commits the path, and draft absorbs the kept rows into the head and holds its chain. Every round is a window, so a reply with drafts off takes the same arithmetic (the windows equal one-row windows, nem_rows). The prompt runs in 512-row windows and the head absorbs every row but the last, 16 rows a step. Window costs (1 to 16 rows) and a head step are timed at load for the depth rule. --no-drafts (or TENSORFOLD_XPU_MTP=0) leaves the head unloaded and keeps the one-row decode path. tools/zig/xpu_draft_receipt.py checks drafted against "draft": false through the server.
Windows take the decode step's arithmetic (ashhart#523), so a one-row window and the decode step give the same bits: with the MTP head loaded, a round of one row (no drafts held, or plain_guard) runs the decode step (5.54 ms) instead of a window (6.47 ms), and plain replies keep the decode speed. The head reads the decode step's row through Model.normRow; the measured window costs take the decode step as width 1.
…sequence With drafted windows the Arc host serves up to --parallel streams a round. Each stream holds a sequence: its Mamba states, its attention KV and the head's, its verify window's scratch and record, and the rows the head absorbs; Engine.bind puts a sequence in the model before the stream's window, keep or draft, and the first stream takes the engine's own buffers. A round runs every stream's window in turn, so a stream's bits never depend on the others'. tools/zig/xpu_concurrent_receipt.py checks concurrent replies against solo runs through the server (token_sha).
With sequences (drafted windows) the Arc host keeps prompt states: the server plans 512-row prompt chunks, the prompt pass cuts there and calls the cache at its marks, and a stream whose prompt starts with a kept state's tokens restores it and prefills from there. A state is the sequence's Mamba states and its KV rows (and the head's) below the mark, copied on the device (Runtime.copy). The cache's rules: planned (resume and keep at chunk starts only, as a prompt piece's rows choose its kernels) and lookahead 1 (the head absorbs row at-1 with token at). Budget: --prompt-cache-gib, else 4 GiB within the device memory left past 2 GiB (Sysman, ze.Gpu.memory). tools/zig/xpu_resume_receipt.py runs a two-turn chat over a long document for the comparison.
…he XPU server, as measured on the B70
10be7a4 to
9674eb6
Compare
|
@ashhart, here is the update, measured on the Arc Pro B70 with What changed underneath. The branch is rebased onto #802 (its head,
The three things you asked for, all through that server:
|
|
Hello @BHCC2025, Thank you for the native server on the Intel engine, and for the fast fixes on the Qwen pull. Intel Arc is accepted through #802's server. One Level Zero runtime, and one 27B forward. This host is one stream, one row, no draft head, and no prompt cache (xpu_lane.zig:77, xpu_lane.zig:164). #802's server sizes streams from the card's free memory, restores a kept prompt, and runs DFlash2 when a drafter is attached. Nemotron waits until a prompt wider than 16 rows uses the same sum as one row. Today the wider projection takes the dense GEMM (xpu_win.zig:324). It comes onto #802's runtime with that. EXL3, GGUF, and the MTP head from the Qwen pull come onto that runtime too. Thank you again. |
|
Thank you, @BHCC2025. Serving Nemotron on Arc through the lane core, with MTP and copy drafts, shared rounds and a prompt cache whose resumed turn equals a cold one, turns the Arc path into a real server. Your commits ship in TensorFold 1.0.12 as they are, merged in We have no Intel GPU, so this lands on your receipts and our host checks, as #523 does. One gap we noticed: the two sampling tests in Posted by Ash's AI Assistant Synthena |
Part 2 of 3, stacked on part 1 (#523: the Intel GPU runtime and the Nemotron engine). Issue: #522. Part 1 is not merged yet, so this branch contains its commit; review the second commit only (
5ef2493, 9 files). Opened as a draft until part 1 is settled.What this adds
The Intel GPU engine as a lane backend of
tensorfold-native serve, soserve MODEL --backend xpuanswers OpenAI-stylerequests. It is registration plus one backend; no scheduler is added beside the engine.
zig/src/native/xpu.zig: registers the familynemotron_h/ formatmlx-q4g64and the chip classintel-<PCI id>, readsdevice memory for admission, builds the lane engine and
api.LaneHost(one lane, one stream, exact width 1).zig/src/native/xpu_lane.zig: the backend behindlanes.backend.Backend, generic over a family spec (prefill in 512-row windows, first,read, verify as one-row windows, keep, release);
zig/src/native/xpu_nemotron.zigis the Nemotron spec. Greedy uses the device argmax. For temperature above 0 the step's logits aredrawn on the host with the lane core's keyed sampler (
temperature,top_k,top_p,min_p,seed; keyed by seed,absolute token position and candidate id). Presence and repetition penalties are not implemented.
zig/src/server/cli.zig,main.zig: thexpubackend choice and the usage text;README.md: one line about the backend.zig/src/lanes/or in the other families changes.Requests queue and run one at a time. Not supported: drafts, tool-call gates, grammars, structured output, prompt cache.
Gates (CONTRIBUTING.md)
zig build -Dxpu native-xpu,xpu-tests,zig build test: 88 of 89 pass; the failure is upstream'sdirect_iotest (same on an unmodified 1.0.2 checkout on this machine)bench_concurrent.py --levels 1,4 --alone --serial: 12 of 12 four-stream replies equal their solo runs, 3 of 3 for one stream, 0 failed (code and chat prompts)7a7c26a1ac4etwice); another seed differs (8650291a63e2)[DONE], a queued request is answered withthe server is shutting down, exit status 0 in 0.11 s, 0 MB of device memory lefttest-goldenReceipts
Same machine and checkpoint as part 1 (Intel Arc Pro B70, Linux 7.0.0, Zig 0.17.0, revision
d9d758fb83953437f7263256b0d96157e2a348b8),greedy, 256 tokens,
tools/bench_openai.py; cold prefill through/v1/completionswith exact token-id arrays,--prompt-cache-gib 0,cached_tokens0, 8 tokens generated.ca2e7e412e5dfor the test prompt, identical alone, three at once, after a disconnect--context 4096, 19.37 GB at 131,200, 20.25 GB at 262,144Reproduce
zig builddefaults to-Doptimize=fast(ReleaseFast); every number in this description was measured with it, and a debug build is much slower. (Thetensorfold-nativebinary keeps its HTTP side at ReleaseSafe, as upstream does.)xpu_token_arrays.pywrites the cold-prefill arrays (the first L tokens of a public text under the model's tokenizer).tensorfold-native capabilities --jsonreports"backends": ["xpu"], chipintel-e223, familynemotron_h, formatmlx-q4g64.What moved (a reviewer's guide)
The lane backend that lived in
zig/src/families/nemotron/xpu_lanes.zigis nowzig/src/native/xpu_lane.zig, generic over a family spec (its Engine type and the rows of one prompt window): the sampler, the round logic and the two samplertests are the same code.
zig/src/native/xpu_nemotron.zigis the Nemotron spec (family table row, loader with the NVFP4 message, 512-row windows) andzig/src/native/xpu.zigdispatches on the model type over a list of specs.This keeps a second family (part 3) to one added file. The family's own
xpu.zigno longer exports the lane. Behaviour for Nemotron is unchanged: the server gates above were rerun on this layout.Digests
The CLI prints the SHA-256 of the generated ids written as a JSON list (
[1, 2, 3]) and--reportstores it; the server'stoken_shais the SHA-256 of the ids joined by commas (1,2,3). The same tokens give different digests.tools/zig/xpu_token_sha.py REPORT.jsonprints both for one token list. The CLI digest quoted in part 1 (af29e8d9691a) and the server digest above (ca2e7e412e5d) belong to different prompts and are not to be compared.Notes for review
ShuttingDown; the queued request gets prose. Left as isbecause the text comes from
lane_host.zig, which this PR does not touch.