Skip to content

measure(MODEL-NEMOTRON-H-ABI-A2P): the first Nemotron ratios -- decode 0.001392x, load 2.12x faster, and the GPU idle for 93.7% of our decode (#1250) - #1251

Open
localai-bot wants to merge 20 commits into
mainfrom
row/MODEL-NEMOTRON-H-ABI-A2P-speed
Open

measure(MODEL-NEMOTRON-H-ABI-A2P): the first Nemotron ratios -- decode 0.001392x, load 2.12x faster, and the GPU idle for 93.7% of our decode (#1250)#1251
localai-bot wants to merge 20 commits into
mainfrom
row/MODEL-NEMOTRON-H-ABI-A2P-speed

Conversation

@localai-bot

@localai-bot localai-bot commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

There was no Nemotron number on any axis. docs/BENCHMARKS.md carried
MODEL-NEMOTRON-H-ABI-A2P as "No speed number, by the unit's own rule". This
branch takes the first ones, against the pinned vLLM oracle, which completed a
model run inside an rc lease for the first time.

FOLLOWING_AGENTS_PROTOCOL

The numbers

Both legs on dgx:gpu0 through rc run, same box, same boot id, same
checkpoint directory, same three pre-tokenized prompts, same 32 greedy tokens
with ignore_eos, batch 1 sequential, two legs over one engine load on each
side. Tree 5325b7b970b67f97a77834e907fc34fb2990b71e, which contains
0ea5d249f (#1221). enforce_eager=False throughout: CUDA graphs were on and
were never disabled.

axis ours pinned vLLM ratio
per output token, warm (n=5) 10.3194 s 0.014369 s 718.2x slower
output throughput, batch 1 0.09691 tok/s 69.595 tok/s 0.001392x
engine load 280.9 s 596.3 s 0.4711x — we are 2.12x FASTER
peak host memory 44,616 MB 70,974 MB 0.629x raw, not like-for-like
KV pool 8192 tokens 644,096 tokens 78.6x, could NOT be matched

Every timing leg is a gated leg. Ours read TOKEN MATCH: 96/96 over 3 prompt(s) (full rows=3, short rows=0, mode=decode) on BOTH legs, STRICT PASS,
192 of 192 tokens, so no number here comes from a configuration whose tokens were
not compared in the same process. The same-binary A/B over one load is 1.0016
warm and the five warm prompts spread 0.245%.

The build was not degraded: CUDA 13.3.73, CFG_RC=0, BUILD_RC=0, zero compile
errors, and fp4-mma / cutlass-nvfp4 / cutlass-fp8 / marlin-nvfp4 / fa2
each ENABLED for [121a], counted as FEATURE_LINES_SEEN=5 DEGRADED_FEATURE_LINES=0 rather than eyeballed. The box was idle:
--query-compute-apps empty, 4,892 of 122,502 MB used.

Two refusals that travel with the ratios

They are recorded beside the numbers rather than netted out of them.

The clock gate refuses this pair. gpu_clock_state compare exits 1 with six
reasons: ours 6.31% busy and vLLM 31.05% busy against a 50% floor, vLLM spread
5.14% against a 5.0% ceiling, ours throttled SwPowerCap, persistence
Disabled on both. Same boot id, both medians 2411 MHz, median_offset_pct
0.0. What the refusal cannot do is explain the result: the tool's own recorded
basis is 0.7548 points of kernel time per point of clock, and the gap is 718x.

The KV pools could not be matched. vLLM's hybrid allocator resolves its own
block size — 512 here, 4192 at other settings — against our 32, so a token
capacity match is the most that was ever available and this run did not get one.
The memory axis is therefore reported raw, and the straggler still resident in
the oracle's window (PID 40514, 22,986 MiB, the EngineCore this row's own
watchdog killed) is named rather than subtracted.

What names the bottleneck, and why it is not a ceiling

nvidia-smi reported GPU utilization 0% in 2,019 of 2,155 samples of our
window. The GPU was busy in 6.31% of it.
That is the driver's own answer, not
a reading of the source, and the clock helper's refusal to attribute such a
window IS the finding: this decode is not GPU work.

It matches the mechanism exactly. Of 52 layers, 6 are attention and stay on the
device, 23 MoE layers run on the device through the NVFP4 Marlin arm, and 23
Mamba2 layers bounce — the normed hidden is downloaded, the mixer runs on the
CPU queue, the result is uploaded — once per layer per token; then
NemotronHHostLmHead projects on the host because nemotron_h.cpp:1031-1034
refuses the NVFP4 lm_head on a non-CPU queue.

So the next traceable hypothesis is A2-Q1 (#940), the 46 FP8 W8A8 mamba
projections that are 36.6% of decode bytes, then A2-Q2b for the lm_head. Each
now has a prediction it must answer rather than a story: it has to RAISE that
6.31% busy fraction, measured with the same battery on the same binary so the
delta is attributable.

The oracle does not reproduce its own golden here

ORACLE TOKEN MATCH: 180/192 — prompt 2 matched 26/32, identically in both legs,
so it is deterministic within the configuration. Ours matched 96/96 on the same
golden. The golden was captured at the oracle's default memory configuration,
and the two knobs the box forced (gpu_memory_utilization=0.30 because 0.9 took
104,992 MB of a 122,502 MB unified pool and was killed, and
max_num_batched_tokens=512) moved its resolved block_size to 512 and with it
the reduction order. The throughput comparison survives — max_tokens is fixed
with ignore_eos, so both sides took the same number of decode steps — but a
token-exact gate against this golden does not survive a change to the oracle's
memory configuration, and that now bounds how the gate may be re-run.

Also here

--num-blocks, --kv-cache-memory and --repeat on nemotron-h-gen. The
driver could not state its own KV budget and inherited the 256-block fallback
the engine takes when gpu_memory_utilization is accepted and does not size the
pool (#83); both legs of the A3 gate logged that warning, and this run does not.
--repeat runs the battery again over one load, and the verdict reads grand
totals, so a battery that passed once and diverged on the second leg cannot
report a pass.

#1253, filed and fixed in this flow. The oracle could not run a model in a
lease for want of python3-dev — Triton compiles cuda_utils.c at runtime and
the failure surfaces four frames up as Engine core initialization failed ... Failed core proc(s): {}, an empty proc set naming nothing. And nvcc --version
is not the toolkit postcondition: the worker ships a partial CUDA 13.0 whose
compiler answers while cuBLAS dev is absent, so CMake printed all five feature
lines and then failed with Target "vllm" links to CUDA::cublasLt but the target was not found. #1185's recorded hazard for the oracle — a reboot of the box in
the step after torch.compile — did NOT occur; the watchdog held.

Closes #1250.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

mudler added 18 commits August 18, 2026 07:38
…-prefill discriminator

The A3 gate diverges on real weights while the CPU gate is 12/12 green, so
neither arm can say whether the decode step reads the state the previous step
wrote. Two instruments, both off by default.

`VT_NEMOTRON_H_DIAG` prints, per step, the decode/prefill split, the recurrent
slot indices, the has-initial mask, and per Mamba2 layer the L2 of the state
gathered in and the state written out. On the CPU fixture it reads a healthy
carry, which is what makes it usable as a negative control on the device.

`nemotron-h-gen --fresh-prefill` generates the same stream one token per
completion from a growing prompt, so every token comes out of a prefill and
nothing is carried. Same engine, same weights, same public entry point, so a
stream that is right this way and wrong the normal way names the carry.

This is scaffolding for the #1157 measurement, not the repair.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…slot can bisect

The CPU arm now decodes this checkpoint token-exact against the oracle golden,
so the divergence is on the device side and the question is which device arm.
The MoE block is 23 of the model's 52 layers and its own gate exercises T=4 and
T=2 only, never the T=1 a decode step is, so it is the arm with the least
coverage at the shape that fails.

`VT_NEMOTRON_H_DEVICE_MOE=0` routes those layers back through the host reference
the CPU run proves correct, and `--both-modes` runs the decode and fresh-prefill
streams over ONE engine load, which is the only affordable shape when a load is
minutes long. Both default to today's behaviour, so nothing moves unless a
diagnostic asks it to.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
… the decode one

`NemotronHMoeBlockDevice` runs on 23 of this model's 52 layers, and its gate
measured T=4 and T=2. Both are prefill shapes. Every token after the first comes
out of a step carrying exactly one token, so the width the model spends its
entire decode in was the width nothing measured.

It is not a cosmetic gap. `MarlinMoeAlignBlockSizeSelect` and
`MarlinMoeAlignSizes` branch on the token count relative to the expert count,
and T=1 against 128 experts is on the other side of that branch from T=4.

A width loop rather than a third copy, so the three cannot drift, with the
covered count asserted afterwards: a loop that ran over nothing would otherwise
report a clean pass, which is the shape this tree keeps finding.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…he failing model is a third

`fa2_decode_qwen3` is DEFAULT ON for any bf16 causal pure-decode at head_dim
128. Its own comment scopes it to "only Qwen3-dense hits this", and that stopped
being true when NemotronH-3.5-Lightning landed: it is head_dim 128 as well, and
its decode goes through the same launcher.

Every case in this file measured 16/8 and 32/8 — ngroups 2 and 4, the two
Qwen3-dense gate configs. NemotronH is 32 query heads over 2 KV heads, ngroups
16, four times the widest group count the swapped presentation was ever measured
at. The launcher packs ngroups as seqlen_q, so the group count is not a detail
of the geometry, it is the grid.

The ratio is added to all five d128 cases rather than to one, so the prefill
arm, the plain-varlen decode, the group-swap decode, the swap-vs-plain near-tie
and the num_splits cap all see it. Nothing else moves.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…very decode step saw the same token

`ModelForwardInput::device_token_ids` is non-null exactly when the async
runner's device combine has already spliced each DECODE row's sampled token into
its own device buffer and left the host `token_ids` stale on purpose — not
materializing it on the host is the synchronize ENG-ASYNC-SCHED W4 exists to
remove. `NemotronHPagedForward` uploaded the host vector anyway, so on the
default CUDA path every decode step embedded the same placeholder id.

The measurement, all on the released 30B NVFP4 checkpoint and the committed
oracle golden, through `include/vllm.h` and nothing else:

  CPU, decode              96/96, STRICT PASS, full rows 3
  GB10, decode              4/24
  GB10, fresh-prefill      24/24   (one token per completion: no decode step)

and the per-layer trace names the mechanism rather than inferring it. At the
prefill step CPU and GB10 agree to six digits on every one of the 52 layers. At
the first decode step the gathered conv/SSM state is IDENTICAL on the two — the
recurrent carry is exact — while layer 0's embedding row differs, and it reads
0.228135 on GB10 at BOTH decode steps although they consume different tokens. A
constant embedding is a constant input id.

This refutes the cause on record. #1157 reasoned that `gm.num_decodes` might
classify a decode as a prefill and hand the mixer zeros; the trace reports
`nd=1 np=0 init=[1]` on every decode step, and mutating that mask to 0 turns the
existing A2-P CPU gate RED (1 case, 6 assertions), so the gate was never blind
to it.

Kimi-Linear was cut from this same divergence (kimi_linear_device.cpp:2270) and
every other registered forward already honours the field. Nothing could see that
this one did not: the runner sets the pointer only under VLLM_CPP_CUDA with a
live device mirror, so no CPU gate can reach the branch.

The host-side id range check stays on the host branch only. Validating device
ids would need the D2H synchronize this path exists to delete.

Closes #1157

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
… the seam that allowed it

The row's public record still said the A3 gate was pending on a toolchain, and
`#1157` still carried a cause that the measurement refutes. Both are corrected
against numbers rather than re-quoted.

`benchmark-record.md` carries the three-arm table the diagnosis rests on — host
96/96, GB10 decode 4/24, GB10 fresh-prefill 24/24, one binary and one
checkpoint — plus the per-layer trace that puts the divergence at layer 0's
embedding row while the recurrent state gathered identical on both sides.

`docs/STATUS.md` and `docs/BENCHMARKS.md` move the row from "gate pending" to
"host gate passes, device fixed, sm_121a re-run pending a lease", inside the
STATUS size ratchet.

[#1217](#1217) is filed and listed
under the spec's `## Owed`: the runner hands `device_token_ids` to whatever
model the step routes to, its own comment claims a model that ignores it is
never given one, and nothing enforces that. Two models have now been cut from
the identical divergence. Not fixed here because both closes change a shared
seam or checker semantics.

The two diagnostic knobs are documented in `docs/ENVIRONMENT.md` rather than
allowlisted as kernel-internal: `VT_NEMOTRON_H_DIAG` is how this bug was
separated from the carry, and the next reader of this model should find it.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
FOLLOWING_AGENTS_PROTOCOL

origin/main moved 26 files under this branch while the #1157 diagnosis was
running on a leased GB10. Merged rather than rebased so the branch keeps the
measurement order: the instruments, then the host and device arms they produced,
then the fix those arms name. No conflict in either direction; nothing this
branch owns was touched.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
… not call itself temporary

The block header said TEMPORARY while `docs/ENVIRONMENT.md` documents the knob
and the next divergence on this model will be diagnosed with it. It now says
what it is for instead: the runner hands a decode step a device-resident input
id AND a recurrent page, and when the tokens come out wrong only the per-layer
numbers say which of the two the step actually read.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
GitHub reported CONFLICTING; the merge resolves clean locally because GitHub
does not run the `merge=union` driver `.gitattributes:7` sets. The union
auto-merge of the index was DISCARDED anyway: main's file taken wholesale, only
rows whose KEY main lacks re-appended, main's file asserted a strict PREFIX of
the result, zero duplicate keys.

The fix itself is unchanged by the merge and re-verified present after it.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
A speed number taken on a tree without PR #1221 measures wrong tokens: without
it every decode step on the CUDA path embeds the same placeholder id, and the
device leg of the A3 token gate reads 4/24 rather than 96/96. The merge is
explicit and named so the measured SHA states which fix it carries; it collapses
to nothing once #1221 lands on main.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
… and the two KV facts that make it comparable (#1250)

There is no Nemotron number on any axis and none is claimed, and the A3 token
gate now passes on the device, so one is takeable. The spec fixes what must be
true before a number counts: the timing legs and the token comparison are the
same run, the five CUDA feature lines must read `ENABLED for [121a]` or the
result is VOID rather than slow, and the denominator is vLLM at the pin with
CUDA graphs on.

Two comparability facts get decided here rather than in a log. Our
`gpu-memory-utilization` does not size the KV pool (#83), so the pool is stated
explicitly and the oracle is given the same 8192 KV tokens. And the oracle has
never run a model inside a lease, so its leg runs last, after our numbers are
already persisted, under a watchdog rather than a sampler.

The expected answer is a poor ratio whose cause is already named: the NVFP4
`lm_head` and the 46 FP8 mamba projections both still execute host-side, owned
by A2-Q2b and A2-Q1. That is the current state, not a ceiling.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…dget and repeat a battery over one load (#1250)

`nemotron-h-gen` could not say what KV pool it wanted, so it inherited the
256-block fallback the engine takes when `gpu_memory_utilization` is accepted
and does not size the pool (#83). Both legs of the A3 gate logged that warning.
A speed comparison whose two sides hold different KV budgets is not
like-for-like on the memory axis, and a driver that cannot state its budget
cannot make them match. `--num-blocks` and `--kv-cache-memory` pass straight to
the two `vllm_model_params` fields that already carry it, and the resolved
sizing is printed before the load rather than left to be inferred from a
warning.

`--repeat` reruns the whole battery over ONE engine load. A load here is
minutes long, so a second timing leg is otherwise a second load, and paired
legs out of one process are the only affordable same-binary A/B on this model.
Every leg is compared against the golden and the verdict reads grand totals, so
a battery that passed once and diverged on the second leg cannot report a pass,
and no timing leg is an ungated one.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
The explicit merge of `row/MODEL-NEMOTRON-H-ABI-A2P-1157-fix` this branch
carried was a stand-in for a fix that had not landed. It has: `0ea5d249f` is an
ancestor of `origin/main`, so the measured base is now main itself and the diff
collapses to this branch's own work.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

# Conflicts:
#	examples/nemotron_h_gen/main.cpp
…overs one configuration rather than a model (#1250)

#1221 landed as `0ea5d249f`, verified by content on `main` rather than by the
API, so the spec's stand-in wording about measuring off the fix branch is dead
and the measured base is a SHA at or after that merge.

The second edit is the one that matters. A token gate is evidence about the
configuration it ran, and this one ran three prompts, 32 greedy tokens each,
batch 1 sequential, `max_model_len 512`, a 256-block pool. The timing legs run
that configuration and nothing else, and a leg that deviates in KV sizing,
batching or sampling is reported as ungated rather than allowed to borrow
coverage the gate never gave it.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…and corrected two instruments the number depends on (#1250, #1253)

`nvcc --version` is not the toolkit postcondition. The rc worker ships a partial
CUDA 13.0 whose compiler answers while the cuBLAS development component is
absent, so a harness that installs only when `nvcc` is missing installs nothing,
CMake prints all five feature lines `ENABLED for [121a]`, and generate then
fails with `Target "vllm" links to CUDA::cublasLt but the target was not found`.
That is #1185's own unconditional-repair rule applied to a postcondition chosen
wrong, which is a different defect from skipping the repair.

The oracle's recorded hazard is also not what stopped it. The model run did not
reboot the box -- peak host use was 28,534 MB of 122,502 MB and the watchdog
never fired -- it died on `Python.h: No such file or directory` inside torch
inductor, because Triton compiles `cuda_utils.c` at runtime and the worker has
no `python3-dev`. It surfaces as `Engine core initialization failed ... Failed
core proc(s): {}`, an empty proc set that names nothing, so it reads as an
oracle limitation and is a missing dev package. The recipe was already written
down for `thor:gpu0` and simply had not been applied on `dgx`.

And contention has exactly one usable instrument on this box: `--query-gpu=memory.used`
reads `[N/A]` on GB10, so a benchmark sampling it is blind to the state that
would void it. At this lease's start the compute-apps list reported 36,396 MiB
still held by the previous lease's straggler.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ned device, with the causes and the operand pinned (#1250, #1253)

No number is recorded on any axis. `dgx:gpu0` reads `unhealthy`, `out of the
pool lease_expired`, with a live heartbeat and refreshing labels, and clearing a
quarantined device is a human's call. The job is queued at position 1 pinned to
the measured SHA and runs when the device returns to the pool. Silence would
have been the wrong record here: pending a named resource is a result, and an
unwritten one reads as nobody having tried.

The record carries what the one lease that did start established, because all of
it is load-bearing for the number when it arrives. The contention state at the
window, including a previous lease's straggler holding 36,396 MiB and the fact
that `--query-gpu=memory.used` reads `[N/A]` on GB10 so the compute-apps list is
the only device-side instrument. The clock, which cannot be pinned from inside
the worker (`-lgc` returns 4, permission denied) and so is recorded rather than
controlled. The operand, hashed. And the mechanism behind the 10.3 s/token the
A3 gate legs measured, taken from the per-layer trace rather than guessed: 6 of
52 layers are attention and stay on the device, 23 MoE layers run through the
NVFP4 Marlin arm, and 23 Mamba2 layers download the normed hidden, run the mixer
on the CPU queue and upload it, once per layer per token, after which the NVFP4
`lm_head` projects on the host. The next traceable hypothesis is A2-Q1 (#940)
then A2-Q2b, measured with the same battery on the same binary so the deltas are
attributable. That is a gap with a named cause, not a ceiling.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…and the GPU is idle for 93.7% of the decode (#1250, #1253)

Numerator only, and no ratio is claimed. On an idle `dgx:gpu0` through `rc run`,
at a tree containing #1221, with all five CUDA feature lines `ENABLED for
[121a]`: engine load 280.9 s, 10.3194 s per output token, 0.09691 tok/s at batch
1, peak host 44,616 MB of 122,502. Every timing leg is a gated leg -- 96/96
`mode=decode` on both, `STRICT PASS`, 192 of 192 tokens, zero short rows -- so
no number here comes from a configuration whose tokens were not compared in the
same process. The same-binary A/B over one load is 1.0016 warm and the five warm
prompts spread 0.245%, so the figure is stable enough that the next lever's
delta will be readable.

The load-bearing part is not the rate, it is why. `nvidia-smi` reported GPU
utilization 0% in 2,019 of 2,155 samples across the measured window, so the GPU
was busy in 6.31% of it, and `gpu_clock_state`'s own comparison gate would
REFUSE that window for failing its majority-busy floor. The refusal is the
result: this decode is not GPU work. It is the 23 of 52 layers that download the
normed hidden, run the mixer on the CPU queue and upload it once per layer per
token, plus the NVFP4 `lm_head` projecting on the host. So A2-Q1 (#940) and then
A2-Q2b are the next traceable hypotheses, and each now has a prediction it has
to answer rather than a story: it must raise that busy fraction.

The denominator has its identity pinned and its blocker measured rather than
assumed. With `python3-dev` present the pinned oracle initialized, loaded in
230.4 s and completed `torch.compile`, which is further than it has ever got
inside a lease, then took 104,992 MB of host in the next step and was killed by
this row's watchdog. The box did not reboot. `gpu_memory_utilization` 0.9 of
~119 GiB of unified memory is ~107 GB, which is that peak, so the fraction is
the first thing to vary. And vLLM's hybrid allocator forces a 4192-token
attention block against our 32, so the two sides can be matched on KV token
capacity and never on block count; a future ratio has to say which.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…Nemotron ratios exist: decode 0.001392x, load 2.12x faster (#1250, #1253)

The pinned oracle completed a model run inside an `rc` lease for the first time,
so these stop being numerator-only. Same box, same boot, same checkpoint
directory, same three pre-tokenized prompts, same 32 greedy tokens with
`ignore_eos`, batch 1 sequential, two legs over one load on each side, and
`enforce_eager=False` throughout so CUDA graphs were never disabled. Decode is
10.3194 s per output token against 0.014369, which is 0.001392x. Engine load is
280.9 s against 596.3, so we are 2.12x faster on the one axis we win, measured
against an oracle whose torch.compile cache was already warm.

Two refusals travel with those ratios rather than being netted out of them. The
project's own clock gate exits 1 on this pair: both windows fail its
majority-busy floor, vLLM's spread exceeds its ceiling, ours was throttled, and
persistence was off on both. Same boot, both medians 2411 MHz, offset 0.0, and
the tool's recorded basis of 0.7548 points of kernel time per point of clock
cannot reach 718x, so the refusal is recorded and the conclusion is not. And
vLLM's hybrid allocator resolves its own block size against our 32, so the KV
pools differ by 78.6x and could not be matched at all; the memory axis is
reported raw with that stated, and the straggler still resident in the oracle
window is named rather than subtracted.

The oracle also read 180/192 against its OWN committed golden, deterministically
in both legs, because the memory configuration the box forced moved its resolved
block size to 512. Ours read 96/96 on the same golden. The throughput comparison
survives it, since max_tokens is fixed and both sides took the same number of
decode steps, but a token-exact gate against this golden does not survive a
change to the oracle's memory configuration, and that now bounds how the gate
may be re-run.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-bot localai-bot changed the title measure(MODEL-NEMOTRON-H-ABI-A2P): the first Nemotron speed numbers, and the KV budget that makes them comparable (#1250) measure(MODEL-NEMOTRON-H-ABI-A2P): the first Nemotron ratios -- decode 0.001392x, load 2.12x faster, and the GPU idle for 93.7% of our decode (#1250) Aug 18, 2026
mudler added 2 commits August 18, 2026 21:16
The measurement's own SHA is unchanged by this merge and is recorded in the
spec and the benchmark record; this only brings the branch up to a moving main
so the records land against the tree they describe.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
GitHub reported CONFLICTING; the merge resolves clean locally because GitHub
does not run the `merge=union` driver `.gitattributes:7` sets. The union
auto-merge of the index was DISCARDED anyway: main's file taken wholesale, only
rows whose KEY main lacks re-appended, main's file asserted a strict PREFIX of
the result, zero duplicate keys.

The measurement this records is unchanged by the merge: 718.2x slower per
output token, 0.4711x on engine load (we are 2.12x faster there), both legs
gated 96/96 `STRICT PASS` at `mode=decode`, box verified idle,
`enforce_eager=False`, all five CUDA feature lines `ENABLED for [121a]`.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
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.

MODEL-NEMOTRON-H-ABI-A2P: no speed number exists for Nemotron-3.5-Lightning-30B now that the device token gate passes

2 participants