Measured report · Updated August 28, 2026. We ran every test ourselves on the same DGX Spark. Normal generation, speculative generation, optional approximate modes, and kernel-only tests are labeled separately.

August 28, 2026

By Davide Ciffa

Ling 3.0 Flash on DGX Spark: Up to 141.9 tok/s with Adaptive DSpark and FlashKDA

We got AntLing’s 124B model running on one NVIDIA DGX Spark. The result is mixed: Lucebox reads prompts faster than llama.cpp, while normal token-by-token generation is about tied. Our speculative path reaches 141.9 tok/s in a favorable test, but that is not the speed every prompt will see.

Ling 3.0 Flash running with Lucebox on an NVIDIA DGX Spark
Ling 3.0 Flash running locally through Lucebox Engine on the NVIDIA DGX Spark used for every measurement in this report.
141.9 tok/sbest matched DSpark case; 94.1% of proposed tokens accepted
74.90 tok/scomplete server loop on our saved ten-problem coding set
+36.4%faster prompt reading than llama.cpp on the shortest test
4.55×FlashKDA kernel alone at 16K; the whole model is 6.2% faster
Two speeds matter. Prefill is the time spent reading the prompt. Decode is the time spent writing the answer. Lucebox’s large advantage is in prefill, not ordinary decode.

What we found

What we built

Ling 3.0 Flash has 124 billion parameters, but it uses about 5.1 billion for each token. This design is called a mixture of experts: the model has many specialist blocks and selects only a few at a time. Our Q4_K_M build uses 72.59 GiB when loaded, so it fits comfortably in the DGX Spark’s 128 GB of unified memory.

We added native model loading, ordinary token-by-token generation, and support for AntLing’s official DSpark helper model. We also added NVIDIA-specific paths for prompt processing and expert layers. The implementation and benchmark scripts are under review in Lucebox Engine PR #661.

Watch it run

This replay turns the strict autoregressive control into one simple terminal race. Both sides read the same 83-token prompt and write the same 128-token response. Lucebox finishes first because FlashKDA shortens prompt processing; normal token-by-token generation is nearly even.

Side-by-side terminal replay of llama.cpp and Lucebox reading an 83-token prompt and generating 128 tokens with Ling 3.0 Flash on a DGX Spark
Animated replay using the five-run median timings from the matched strict-AR benchmark below. It is slowed down so prompt loading and text generation are visible.

Ordinary generation: effectively tied

This is the plain token-by-token test, with all shortcuts turned off. Both engines use the same model file, cache format, prompt template, sampling settings, and one active request. Prompt caching is disabled. This is the fairest way to compare ordinary generation.

Strict autoregressive decode throughput Lucebox reaches 46.00 tokens per second and llama.cpp reaches 45.38 tokens per second on the same 83-token prompt and 128-token response. 01020 304050 tok/s Lucebox 46.00 llama.cpp 45.38
Same Q4_K_M model, Q4_0 cache, 4,096-token allocation, one active request, and deterministic sampling. Median after two warmups and five measured runs.

Lucebox is ahead by 1.36%, which we consider a tie in practice. An earlier build produced a faster single result, but we use the newer five-run median instead. For the complete request, Lucebox spends 204.9 ms reading the prompt and 2,780.1 ms writing the answer. Llama.cpp takes 322.8 ms and 2,798.3 ms. That is 2,985.0 ms versus 3,121.1 ms in total. Lucebox finishes 136.1 ms sooner, almost entirely because it reads the prompt faster.

One important limit: each engine repeats its own output exactly, but the two engines do not produce identical text in this test. This is a matched speed comparison, not proof of byte-for-byte agreement.

Prefill: +36.4% early, then ahead through 64K

Next we measured prompt reading on its own. We sent each engine the same generated prompts, asked for one output token, disabled prompt caching, and kept one request active at a time. Each point is the middle result from three requests. Both engines returned the same one-token output.

Full-model prefill throughput by prompt length Lucebox leads llama.cpp by 36.4 percent at 145 prompt tokens, 14.5 percent at 1,042 tokens, and 1.0 percent at 4,114 tokens. 05001,0001,500 1451,0424,114 Prompt tokens Prefill throughput (tok/s) +36.4%+14.5%+1.0% Lucebox 609llama.cpp 446 1,3261,158 1,5991,584
Full-model prompt reading with speculation and caches disabled. One active request and one output token. Each point is the median of three HTTP requests; circles are Lucebox and squares are llama.cpp.

The first version of our prompt path loses most of its lead by 4K. We then integrated FlashKDA and repeated the test with longer prompts. The updated path is 14.0% ahead at 4K and still 5.7% ahead at 64K. The full long-context chart appears below.

Adaptive DSpark: up to 141.9 tok/s

DSpark is a small helper model. It proposes several future tokens at once, and the main model checks them. When most guesses are accepted, generation can be much faster. Lucebox changes how many guesses it checks based on how well the recent guesses worked.

Exact-output DSpark decode throughput Lucebox adaptive DSpark reaches 141.9 tokens per second and llama.cpp DSpark reaches 137.34 tokens per second while producing the same 128-token output. 03060 90120150 tok/s Lucebox 141.90 llama.cpp 137.34
Deterministic 128-token control using the same main model and official DSpark helper. Lucebox accepts 94.1% of proposed tokens, and both engines produce exactly the same output.

This is our highest measured generation speed. Lucebox is 3.3% faster, and both engines produce exactly the same 128 tokens. But this prompt is unusually easy for the helper model: 94.1% of its guesses are accepted. Treat 141.9 tok/s as a best case, not an average.

Why changing the block size helps

Lucebox starts cautiously. After two fully accepted blocks, it checks the largest block. If guesses begin to fail, it quickly reduces the block size; after clean blocks, it tries a larger one again. The review branch lets users disable this behavior or force a fixed size for testing.

A small coding test: changing versus fixed block size

Changing versus fixed DSpark block size on the saved coding workload Changing the block size reaches 100.69 tokens per second after the first token and 74.90 tokens per second across the complete server loop. Fixed block size 5 reaches 94.94 and 72.91. Both pass all ten cases in three repetitions. 02550 75100 tok/s After first token Changing 100.69 Fixed 5 94.94 Full engine Changing 74.90 Fixed 5 72.91
Median of three repetitions of the same ten saved HumanEval problems, with one active request. Changing the block size is 6.1% faster after the first token and 2.7% faster across the complete server loop. Both settings pass every saved case in all three repetitions.

The first number starts counting after the first token arrives. The second includes the complete server-side generation loop. Changing the block size improves them by 6.1% and 2.7%. Each setting produces 567 tokens per repetition and passes the same cases.

Why we do not show a llama.cpp HumanEval score. Our first attempt was invalid: the two engines formatted the prompt differently. Llama.cpp produced a long reasoning preamble and often hit the output limit, while Lucebox returned short code bodies. We would have been grading two different tasks. We removed that comparison and will only publish it after both engines receive exactly the same rendered prompt.

This saved set has ten problems, not the full 164-problem HumanEval suite. The 10/10 result is a local regression check, not an official HumanEval score.

Fixed-size checks

Same 83-token long-form prompt and 128-token response
PathDecodeAcceptanceOutput hash
Width 444.2 tok/s60.4%e5a…
Width 5, before grouped verifier48.3 tok/s56.9%35d…
Width 5, grouped verifier50.75 tok/s56.9%35d…
Width 645.9 tok/s47.4%b49…

A verifier optimization raises the fixed-size result from 47.15 to 50.75 tok/s, a 7.6% gain. Different block sizes do not always produce identical text, even though all saved coding cases still pass. We therefore report this as a serving-speed result, not as proof that speculative output always matches ordinary generation byte for byte.

A real tool call

We also tested the model through Lucebox’s OpenAI-compatible API. We gave it a get_weather(location) function and asked for the weather in Rome. Ling selected the function, sent valid JSON arguments, read the result, and used it in its final answer.

Single deterministic tool round trip on the August 24 baseline build; Q4_K_M target, Q4_0 K/V, 32,768-token server context, concurrency one
StageObserved resultDecode
Assistant tool request{"name":"get_weather","arguments":{"location":"Rome"}}35.0 tok/s
Injected tool result{"temperature_c":29,"condition":"sunny","source":"test fixture"}
Final assistant answerUsed both sunny and 29°C correctly34.9 tok/s

This shows that the basic tool-call loop works on the real model. It does not show that Ling can reliably choose among many tools or recover from broken calls; we tested one controlled function. The complete record is in the DGX Spark integration report.

Optional expert skipping: 10.5% faster, but approximate

Ling normally uses eight specialist blocks for each token. Our optional mode skips the specialists that contribute the least. This saves work, but it also changes the model’s calculation. It is separate from DSpark, disabled by default, and never included in the exact-serving results above.

Decode throughput by retained router mass Full top-k reaches 45.4 tokens per second. Retaining 75 percent router mass reaches 49.6, 50 percent reaches 53.2, and 25 percent reaches 56.0. Lower-mass paths are approximate. 01530 4560 tok/s Full top-k 45.4 75% mass · selected 49.6 · +9.3% 50% mass 53.2 25% mass 56.0
The same prompt with different amounts of expert work retained. Keeping less work is faster but changes the model. We selected 75% as the optional test setting; 50% and 25% are speed probes only.

The selected 75% setting reaches 50.3 tok/s, compared with 45.5 tok/s when all eight specialists are used: +10.5%. Because this mode changes the calculation, we also checked it on small saved math sets.

Ten prompts per suite; local regression cases, not canonical benchmark scores
ModeGSM8K-styleMATH-styleTotal
Full top-k46.19 tok/s · 9/1045.43 tok/s · 8/1017/20
75% mass50.82 tok/s · 9/1049.96 tok/s · 9/1018/20
50% mass54.34 tok/s · 7/1053.81 tok/s · 7/1014/20

The aggressive 50% setting falls from 17/20 to 14/20, so we reject it. The selected 75% setting reaches 18/20, but twenty local problems are far too few to claim equal quality. Expert skipping remains deterministic, optional, and off by default.

What profiling found

We recorded matched runs and used NVIDIA Nsight Systems to see where the GPU spent its time. This led to two changes that matter for long prompts.

First, Lucebox was converting the same attention cache twice. We made the value cache reuse data already stored in the key cache. Second, the general KDA path was doing bookkeeping needed for speculative trees even during ordinary generation. The DGX Spark now selects a simpler path when that bookkeeping is unnecessary.

After the changes, cache-conversion launches fall from 2,240 to 1,120, attention time falls from 132.1 to 62.9 ms, and average KDA time falls from 256.5 to 226.6 microseconds. Llama.cpp records 1,134 launches, 61.2 ms, and 217.8 microseconds in the same trace. Lucebox keeps its previous output at 4K, 8K, and 16K.

Why DSpark changes block size

The 141.9 tok/s run has 68 generation steps. The saved coding run has 117 steps, 107 of them speculative. Both traces are complete, and all ten saved coding cases pass under the local scoring setup.

Checking a large block costs roughly 40–50 ms, while producing the draft costs roughly 14–15 ms. A large block only pays off when acceptance is stable, which is why Lucebox waits for two clean blocks before trying the maximum size.

Why expert skipping helps

The matched traces cover all 128 generation rounds in both modes. They show that most of the gain comes from doing less setup work before the expert layers.

Adaptive expert routing profile changes At 75 percent router mass, profiled throughput increases 8.6 percent, inference round span falls 7.9 percent, GPU kernel busy time falls 8.4 percent, GEMM time falls 9.9 percent, and gate setup launches per token fall 54.2 percent. 01530 4560% change Throughput increase 8.6% Round-span reduction 7.9% GPU busy-time reduction 8.4% GEMM-time reduction 9.9% Gate-launch reduction 54.2%
The optional 75% mode compared with all eight experts. Setup work falls from 83 to 38 launches per generated token. User-visible speed still comes from the normal run without profiling.

FlashKDA: faster prompt reading

Most of Ling’s layers use Kimi Delta Attention (KDA). Our old KDA kernel reads a prompt one token at a time, which leaves much of the GPU idle during this phase.

FlashKDA groups prompt tokens into 16-token chunks and prepares many chunks in parallel. A second step then carries the model state from one chunk to the next. This gives the GPU much more work it can do at the same time.

We compiled this path for the DGX Spark’s Blackwell GPU and connected it to the GGML graph. It is used while reading prompts; ordinary one-token generation keeps the existing exact path.

FlashKDA latency relative to the current sequential recurrence At sequence lengths 128, 1,024, and 4,096, FlashKDA is 3.82, 3.72, and 3.99 times faster than the current standalone sequential KDA recurrence. Each row normalizes the current recurrence to 100% 128 tokens Current0.2454 ms FlashKDA0.0642 ms · 3.82× 1,024 tokens Current2.1504 ms FlashKDA0.5774 ms · 3.72× 4,096 tokens Current9.2757 ms FlashKDA2.3248 ms · 3.99×
Isolated KDA recurrence on one DGX Spark; lower latency is better. Bars are normalized within each sequence length so the ratio is visible, while labels show the measured milliseconds.

The official full-size FlashKDA test matches its Torch reference exactly. Our separate stress test compares a BF16 kernel with Lucebox’s older FP32 path, so the numbers are close but not identical:

FlashKDA versus the current standalone Lucebox recurrence on randomized Ling-shaped inputs
SequenceOutput rel. RMSEState rel. RMSEMax abs outputMax abs state
1284.8455%1.7546%0.019640.0344
1,0242.6349%3.4448%0.016420.2432
4,0962.4030%1.8541%0.015240.09621
A four-times-faster kernel does not mean a four-times-faster server. FlashKDA speeds up only one part of prompt reading. At 16K, that part is a small share of total GPU time, so the whole model is 6.2% faster rather than four times faster.

Long prompts and multiple users

We repeated the prompt-reading test at 4K, 8K, 16K, 32K, and 64K tokens. Both engines use the same model, cache, prompt template, and one active request. Caching and speculation are disabled. Lucebox stays ahead at every measured length.

Long-context prefill throughput From 4K through 64K prompt tokens, Lucebox reaches 1,798, 1,787, 1,686, 1,472, and 1,137 tokens per second. Llama.cpp reaches 1,576, 1,645, 1,587, 1,387, and 1,075. 05001,0001,5002,000 4K8K16K32K64KPrompt tokens Prefill throughput (tok/s) +14.0%+8.6%+6.2%+6.1%+5.7%
Full-model prompt reading with speculation disabled. Circles are Lucebox and squares are llama.cpp. The Lucebox lead narrows from 14.0% at 4K to 5.7% at 64K.
Context-loaded autoregressive decode throughput At 4K, 8K, and 16K prompt tokens, Lucebox reaches 44.8, 43.8, and 42.3 tokens per second. Llama.cpp reaches 44.6, 43.8, and 42.4. 01020304050 4K8K16KPrompt tokens AR decode (tok/s) L 44.8 · +0.5%L 43.8 · tiedL 42.3 · −0.3% llama 44.6llama 43.8llama 42.4
Ordinary token generation for the same requests. The two engines remain within 0.5% of each other from 4K through 16K.

The prompt-reading lead shrinks because FlashKDA speeds up 35 KDA layers, while Ling’s seven conventional attention layers become more expensive as the prompt grows. Those untouched layers take a larger share of the total time. The lead does not disappear in the measured range: it is 6.2% at 16K and 5.7% at 64K.

Handling more than one request is a clear weakness today. Two simultaneous requests mostly run one after the other instead of sharing the GPU efficiently.

Aggregate end-to-end throughput at concurrency one and two Concurrency one reaches 38.35 aggregate tokens per second and concurrency two reaches 39.33 tokens per second, a 2.5 percent increase. 01020 3040 tok/s Concurrency 1 38.35 Concurrency 2 39.33
Ordinary generation with 89 prompt tokens and 64 output tokens per request. A second simultaneous request raises total throughput by only 2.5%.

We measured prompt reading through 64K and ordinary generation through 16K. We have not yet tested the model’s full 262K limit or four and more simultaneous requests, so we do not claim results beyond those points.

What else we tested

Speed is only part of the story. We also ran saved coding problems, one complete tool call, and a real streamed response. The table separates what worked from what we have not tested yet.

What we tested and what is still missing
AreaStatusWhat the result establishes
OpenAI-compatible tool callPassedOne valid function selection, argument object, tool result, and final answer
Saved 10-case HumanEval subsetPassed within LuceboxChanging and fixed block sizes both pass 30/30; no cross-engine quality claim
Small saved math setsPartialUseful for catching regressions, not official benchmark scores
Animated benchmark evidencePassedReal generated stream plus saved prompt-reading and generation results
Multi-tool selection and malformed-call recoveryNot measuredNeeded before claiming robust tool use
Ten-plus-turn agent loopNot measuredNeeded to test state retention and repeated tool schemas
Repository-level coding build with an agent frameworkFollow-upOptional build deliverable; not claimed in this report
Changing the DSpark block sizePassedRepeated comparison against a fixed block size
Speculative output versus ordinary outputOpenNeeded before claiming they always produce identical text
Long promptsPrompt reading tested through 65,555 tokensLucebox remains 5.7% ahead at 64K; ordinary generation is tested through 16K
Four or more simultaneous requestsNot measuredNeeded before making claims about multi-user throughput

We have not yet run a full repository-level coding task through an agent framework. That would be a useful follow-up, but we do not claim it here.

Hardware and run configuration

SystemNVIDIA DGX Spark
SoC / architectureNVIDIA GB10 / SM121 / aarch64
Memory128 GB unified memory (121 GiB visible to Linux)
Driver / runtimeNVIDIA 580.173.02 / CUDA 13.0
TargetAntLing Ling 3.0 Flash, 124B total / 5.1B active, Q4_K_M GGUF
Loaded target footprint72.59 GiB
DraftLing 3.0 Flash DSpark, 1.36B, BF16 GGUF, block size 8
K/V cacheQ4_0
SamplingGreedy / temperature 0
Benchmark clientLucebox OpenAI-compatible HTTP benchmark harness
ProfilerMatched engine traces and NVIDIA Nsight Systems captures

Exact test setup and commands

This section is for readers who want to rerun the numbers. The benchmark scripts record the machine setup, hash the output, and refuse to start if another inference server could interfere. The ordinary-generation script gives both engines the same model, cache settings, and number of active requests.

Show exact commands and environment variables

After building PR #661 and setting the two binary paths, this is the decode contract used for the headline comparison:

export LING_SERVER_BINARY=/path/to/dflash_server
export LLAMA_SERVER_BINARY=/path/to/llama-server
export LING_BENCH_MAX_CTX=4096
export LING_BENCH_PROMPT_PROFILE=official-longform
export LING_BENCH_DECODE_WORKLOAD=longform
export LING_BENCH_SECTIONS=decode
export LING_BENCH_DECODE_TOKENS=128
export LING_BENCH_DECODE_RUNS=5
export LING_BENCH_WARMUPS=2

server/scripts/bench_ling3_ar.sh \
  luce /path/to/Ling-3.0-flash-Q4_K_M.gguf luce-ar.json

server/scripts/bench_ling3_ar.sh \
  llama /path/to/Ling-3.0-flash-Q4_K_M.gguf llama-ar.json

For the matched prefill ladder, change the section and context controls while leaving the launcher, model, cache policy, and concurrency unchanged:

export LING_BENCH_MAX_CTX=8192
export LING_BENCH_SECTIONS=context
export LING_BENCH_CONTEXT_TOKENS=128,1024,4096
export LING_BENCH_CONTEXT_RUNS=3

For the integrated FlashKDA long-context run, enable DFLASH_LING_FLASH_KDA=1, set DFLASH27B_PREFILL_UBATCH=384, use LING_BENCH_MAX_CTX=70000, and set LING_BENCH_CONTEXT_TOKENS=32768,65536. The 512-token micro-batch screen was 2.7% slower at 32K and 4.8% slower at 64K, so 384 remains the measured setting.

The saved coding test enables the changing DSpark block size by default:

export LING_SERVER_BINARY=/path/to/dflash_server
export LING_BENCH_SUITES=he
export LING_BENCH_N_SAMPLE=10
export LING_BENCH_MAX_CTX=8192
export LING_DSPARK_ADAPTIVE_WIDTH=1
export LING_DSPARK_ADAPTIVE_MAX_WIDTH=9

server/scripts/bench_ling3_heldout.sh \
  luce-dspark \
  /path/to/Ling-3.0-flash-Q4_K_M.gguf \
  /path/to/Ling-3.0-flash-dspark-BF16.gguf \
  adaptive-humaneval.json

Set LING_DSPARK_ADAPTIVE_WIDTH=0 and LING_DSPARK_VERIFY_WIDTH=5 for the fixed-width control. At the server level, DFLASH_ADAPTIVE_SPEC_WIDTH=0 is the kill switch, DFLASH_DSPARK_ADAPTIVE_MAX_WIDTH caps adaptive verification, and DFLASH_DSPARK_VERIFY_WIDTH forces a fixed seed-inclusive width.

This command compares two Lucebox settings. A fair cross-engine quality test must send both engines the exact same rendered prompt and verify that the prompt token counts match before scoring.

The AR wrapper pins Lucebox to target-only decoding with DFLASH_BAILING_MTP=0. It launches llama.cpp with all layers on the GPU, flash attention enabled, split mode disabled, parallelism one, the Jinja chat template, and prompt caching disabled. See the exact AR launcher, adaptive DSpark launcher, and benchmark client. The llama.cpp comparison used commit 1729ed53.

Links

Verdict

Ling 3.0 Flash runs well on one DGX Spark, but the result depends on the workload. Lucebox’s clear win is prompt reading: it is ahead at every measured point through 64K. Ordinary generation is about tied with llama.cpp. Speculative generation reaches 141.9 tok/s in the best matched case and 74.90 tok/s across the complete server loop on our small saved coding set.

Run it if you want a large local model for coding, tool experiments, or GPU work on a compact NVIDIA box. If you need high throughput from many simultaneous users or identical output across different runtimes, a more mature serving stack is the better choice today.


We measured every Lucebox and llama.cpp number on the same DGX Spark from August 24–28, 2026. Percentages use the original unrounded results. We label full-model, after-first-token, approximate, and kernel-only numbers separately. We removed the invalid cross-engine HumanEval comparison, and we do not reuse vendor benchmark numbers as our own.