Running PhoneLLM Alpha 1 on RTX 3090: VRAM, Latency, Callers

Waffles is an AI voice agent living on our softcery.com website. She runs on Gemma 4 26B-A4B, hosted on a rented RTX 3090, the card and the flags in lab 038.

Then Daily shipped PhoneLLM Alpha 1, a full-parameter fine-tune of Nemotron 3 Nano 30B-A3B, built for phone agents. On PhoneBench Alpha 1 it scores 72.3, against 72.4 for GPT-5.6 Terra and 58.1 for Gemma 4 31B. That page publishes no judge, no item list and no harness, and its Gemma row states no quant and no thinking setting.

So: does it serve the same card at the same turn budget? This article covers serve and load. Quality goes in a second article, once we have a scorer.

The outcome

On a rented RTX 3090 on vast.ai, $0.26 per hour (~$190/mo + network) + mradermacher/phonellm-alpha-1-i1-GGUF, the IQ4_XS file, llama.cpp b10902:

llama-server \
  -m phonellm-alpha-1.i1-IQ4_XS.gguf \
  -ngl 99 -c 393216 -np 12 -ub 2048 --cache-type-k f16 --cache-type-v f16 -fa on \
  --no-cache-idle-slots \
  --jinja --chat-template-kwargs '{"enable_thinking": false}' \
  --temp 0 --top-k 40 --top-p 1.0 --min-p 0 --repeat-penalty 1.0
Metric Result Context
VRAM 20.7 GiB of 24 12 slots of 32k, 240 MiB each. 18.1 GiB at 3 slots. The A4B: 16.8 GiB at 3
Warm turn, on the box 188 to 220 ms to the first token 10.4k-token history, 3 reps of 20 to 24 cases. The A4B: 91 to 109 ms
Prompt eval alone 130 to 137 ms 24 to 62 new tokens. The A4B: 40 ms at 61
Prompt eval floor 54 ms 4 new tokens. Holds at 4 flag settings. The A4B: 10 to 13 ms at 1 token
Decode, one caller 210 to 219 t/s p50 over 1 to 2 minute runs, card 52 to 88 C. 98 t/s at the end of a 24-minute sweep, section 7
Decode, 12 callers 40 t/s each all 12 talking at once, 0.233 of the one-caller rate measured beside it
New call 2.9 s 10.4k-token cold prompt, ~3,600 t/s. 1 sample, card temperature unknown
Barge-in reach 1,200 to 2,000 tokens -ub 2048, 687 ms inside the reach. 320 to 360 tokens at the default 512
Second caller joins first caller stalls 2.2 to 3.2 s cold 10.4k prompt beside 2 warm callers at -ub 2048. 6.3 to 7.9 s at 512. Open
12th caller 602 ms to the first token all 12 talking at once, 620 ms max
Empty replies 0 in 396 replay and conversation runs, both configs
Tool calls 9 of 9 as tool_calls 3 tool cases, 3 reps each. The Nemotron format, parsed by the server
Cost $0.00036 per voice minute 12 callers at 100 % occupancy, by division. $0.00066 at 55 %

1. Layers, slot cost and the quant

PhoneLLM is a Nemotron 3 Nano hybrid: 52 layers, of which 23 are Mamba2 state layers, 23 are Mixture of Experts, and 6 are attention. Only the 6 attention layers keep a KV cache, one entry per token. The 23 Mamba2 layers keep a state each, a running summary of the history. The 23 states cost 48 MiB per slot to hold. A rewind needs a checkpoint, section 3.

The cost of one caller: 6 attention layers with 2 KV heads give 6 KiB per token, 192 MiB for a 32k slot. The 23 Mamba2 states add 48 MiB per slot in f32. A slot is 240 MiB. The A4B slot is 940 MiB at the same length.

The weights are the second limit. The expert rows are 2,688 and 1,856 wide. Neither divides by 256, so every 256-block quant type falls back to a 32-block type on the 46 expert tensors. For 7 of the 9 public files the experts are IQ4_NL or Q4_0 and weigh 16.5 GB. We read the GGUF headers of the 9 public files over HTTP range requests, no download:

Public file Expert type Size Free at 12 slots of 32k
IQ1_S to IQ4_XS IQ4_NL or Q4_0 17.9 to 18.2 GB 3.8 GiB, measured
Q4_K_S Q5_0 21.9 GB under 0.3 GiB by the measured overhead
Q4_K_M Q5_0 24.5 GB none

The 7 files differ by 0.3 GB. The quantizer's model card does not say so; it is one template for every model. We took IQ4_XS, the top of the 18 GB group, and there is no smaller quant to try on this card.

Side by side on a 24 GB card:

26B-A4B PhoneLLM
Weights 14.25 GB 17.97 GB
Slot at 32k 940 MiB 240 MiB
Free VRAM 0.8 GiB at 10 slots 3.8 GiB at 12 slots

PhoneLLM needs 3.7 GB more for weights and 8.2 GiB less for 12 slots.

2. The 54 ms floor

A warm turn has 3 parts. The server renders the chat template, it does its other work before the response headers, and it runs the prompt eval over the new tokens. POST /apply-template gives the render alone, with no slot and no prompt eval. timings.prompt_ms gives the prompt eval. The time before the headers includes the render. The turn is that time plus the prompt eval.

On the box, one caller, a cached history of call-1, and a prompt that adds 4 new tokens, 3 reps each:

Messages Tools Render Before headers Prompt eval Turn
4 no 5 to 6 ms 26 to 29 ms 54 to 55 ms 76 to 79 ms
4 5 24 to 33 ms 68 ms 54 to 55 ms 118 ms
22 no 18 ms 44 ms 54 ms 93 ms
22 5 31 to 46 ms 43 to 86 ms 54 to 55 ms 93 to 137 ms
54 no 35 to 43 ms 53 to 71 ms 55 ms 103 to 121 ms
54 5 32 to 68 ms 73 to 99 ms 55 ms 123 to 149 ms

The prompt eval is 54 to 55 ms on all 6 rows. It holds at 4 and 54 messages, with and without the 5 tool schemas, at -ub 512 and 2048, and at 2 checkpoints and the default. The A4B runs a 1-token prompt eval in 10 to 13 ms. The prompt eval has a fixed cost of 54 ms. We have not found its source. The Mamba2 state restore and the checkpoint save are the candidates, neither profiled.

The prompt eval grows with the new tokens, not linearly. On the replay of call-1, 74 warm turns of 26 to 105 new tokens, the prompt eval is 135.6 ms p50 at 60 tokens. On the conversation run it is 130 ms at 24 tokens. So 54 ms at 4 tokens, 130 at 24, 136 at 60. The step between 4 and 24 is unmeasured. The A4B is at 40 ms for 61 tokens.

Render and before-headers favour PhoneLLM. PhoneLLM's template renders slower than the Gemma one, 35 to 43 ms at 54 messages against 1.6 ms at 82, and the 5 tool schemas add ~30 ms of render. But its total before the headers is lower: 73 to 99 ms at 54 messages with tools, where the A4B spent 126 to 139 ms at 42 messages. The A4B's 2.5 ms per history message does not appear on PhoneLLM. At 4 new tokens the two models reach the first token in about the same time, ~150 ms at 40 to 50 messages with tools, by addition on the A4B side.

So the ~100 ms gap in the warm turn, 188 to 220 ms against 91 to 109, is the prompt eval: the 54 ms floor plus the unmeasured step to 24 tokens. The render and the headers add nothing to the gap.

3. Checkpoints

On the 12B, --ctx-checkpoints 0 cut the warm turn from 556 ms to 72, because the server took 2 or 3 snapshots of 320 MiB on each request and a warm turn did not need them. We carried the flag over to PhoneLLM and ran the same header test as in section 2. All 12 rows came back with cache_n 0:

Messages Tools Prompt tokens Prompt eval, 3 reps
4 no 7,286 2,130 to 4,311 ms
4 5 9,992 3,629 to 5,853 ms
54 no 9,928 4,213 to 6,055 ms
54 5 12,634 5,977 to 7,727 ms

A repeat of an identical 17,878-token prompt gave the same result: cache_n 0, 8,262 ms.

The cause is the recurrent state. A KV cache holds one entry per token, so the server can cut it at any position and continue from there. A Mamba2 state is a fixed-size summary of the whole history up to a position. To continue from an earlier position, the server needs a copy of the state saved at that position. A checkpoint is that copy. With 0 checkpoints there is no copy, and the server starts from token 0 on each turn, including a turn whose prompt matches the cache in full.

With the default checkpoint count, a turn inside the reach, the distance section 4 measures, reuses 17,362 of 17,878 tokens and re-evaluates 540 to 566, in 170 to 180 ms. A turn past the reach restores the checkpoint at 10,367 tokens, the end of the recorded history, and re-evaluates the 7,500-token tail in 1.5 to 1.7 s.

Things we tried on the same prompt, against the default count:

So the count does not move the cost. The default stays. A flag that saved ~480 ms per turn on Gemma costs 2 to 8 s per turn on a hybrid.

4. Barge-in

When the caller interrupts, the agent cuts the reply and sends the next turn with the cut reply in the history. To the server that is a prompt whose prefix ends before the end of the cache. On a KV cache the server drops the tail and continues. On this model it restores the nearest checkpoint before the cut and re-evaluates from there, section 3.

We cached a 17,878-token prompt, the 10,367-token recorded call plus a 7,511-token filler tail, then replaced the last N tokens of the tail and read cache_n, prompt_n and prompt_ms on the next turn. One rep per point, 2 at the default:

-ub Reach, tokens back Inside the reach, tokens re-evaluated Inside, prompt eval Past the reach
256 50 to 200 286 166 ms 7,611 tokens, 2,579 ms
512, default 320 to 360 676 179 to 182 ms 7,691 tokens, 1,552 to 1,765 ms
1024 400 to 800 1,202 315 ms 7,911 tokens, 1,955 ms
2048 1,200 to 2,000 2,448 687 ms 8,511 tokens, 2,503 ms

Inside the reach the server re-evaluates about one ubatch plus the edit. Past it, cache_n drops to 10,367 on each of the 4 past-reach rows in the table: the server restores the checkpoint at the end of the recorded call and re-evaluates the whole tail, 1.6 to 2.6 s.

On the 3 ubatch settings the restore point inside the reach sits one ubatch before the end of the cache, within 4 tokens: 17,362 at -ub 512, 16,850 at 1024, 15,830 at 2048. The reach is shorter than that distance, 320 to 360 where the boundary is 516 back, and we have not explained the gap. The Gemma article has the same gap at ~400 against 512.

On the A4B the reach is set by the 1,024-token sliding window and does not move with the batch. On PhoneLLM the ubatch sets it. A spoken reply is 6 to 107 tokens on our calls, so a barge-in cut stays inside the reach on both models, at any ubatch from 512 up. The larger ubatch gives a deeper edit: at 2048 a change 1,200 tokens back costs 687 ms, where at 512 it costs a 1.6 s re-evaluation of the tail. A trimmed tool result or a rewritten system line inside 1.2k tokens stays under 0.7 s.

-ub 256 is rejected: the reach falls under 200 tokens and a deep edit costs 2.6 s. We run 2048 for the cold join in section 5.

5. The second caller

A new caller arrives with a prompt that shares nothing with the slots: a cold 10,377-token prompt eval. The server runs all active slots in one batch, so the cold prompt goes through in chunks of one ubatch, and each decode step of a warm caller rides with the next chunk. The warm caller hears the agent stop until the cold prompt is through.

Two warm callers with a cached 10,369-token history and one cold caller, all 3 requests fired at once, on the box, 3 reps:

-ub Cold caller, first token Cold prompt eval Warm caller, first token Warm caller, stall inside the reply
512, default 6,938 to 8,755 ms 6,816 to 8,712 ms, 1.2 to 1.5k t/s 6,267 to 7,888 ms in 5 of 6 121 ms, then 42 tokens in 7.3 s, 1 of 6
2048 3,109 to 4,309 ms 3,070 to 4,245 ms, 2.4 to 3.4k t/s 2,229 to 3,245 ms in 5 of 6 125 ms, then 46 tokens in 4.3 s, 1 of 6

The stall lands in one of 2 places. In 5 of 6 warm rows per setting the caller waits for the first token. In 1 of 6 the first token comes at 121 to 125 ms and the reply then decodes at 6 to 11 t/s, 1 to 2 times the ~4.5 t/s of speech. The Gemma article saw both shapes, with the stall after the first word in 2 of 3.

At the default ubatch the cold prompt eval runs at 1.2 to 1.5k tokens per second. At 2048 it runs at 2.4 to 3.4k. The cold caller waits half as long, and the warm callers with it. The A4B on the same join, at its 2,048-token chunk, stalls 2.2 to 2.8 s. PhoneLLM at -ub 2048 stalls 2.2 to 3.2 s. At the same chunk size the two models match.

Prefill alone, one caller, one sample: 10,394 tokens in 2,892 ms, 3,594 t/s. The A4B does ~4,200. The sample predates the temperature log, section 7, so its card temperature is unknown.

Not run:

6. Callers per card

An idle slot costs 240 MiB of VRAM and no decode time. The cost comes with the active callers, because each decode step runs the whole batch. The card slows as it heats, section 7, so a decode rate from one minute does not compare to a rate from the next. Each load point has a one-caller row measured right before it, and the column to read is the share: the decode per caller over that one-caller rate.

On the 12-slot config, all slots warm on one cached 10,369-token prompt, all callers talking at once, replies of 40 to 51 tokens, 2 reps:

Callers talking at once First token, p50 First token, max Decode per caller, p50 Share of one caller
1 96 ms 132 ms 201 t/s 1
4 250 ms 254 ms 107 t/s 0.503
8 426 ms 462 ms 52 t/s 0.263
12, the config we run 602 ms 620 ms 40 t/s 0.233

VRAM under the 12 callers: 20,734 MiB, 68 MiB over the load figure, 3,842 MiB free. The slowest of the 24 rows at 12 callers decodes 27 t/s, 6 times the speech rate. Together the 12 produce 480 t/s against 201 alone, by multiplication.

A second sweep on a 24-slot load, same method, took the count to 24. It ran on a hotter card, one-caller decode 100 to 138 t/s against 149 to 201 above, min to p50 in both, so only the shares and the first-token times compare:

Callers talking at once First token, p50 Share of one caller
4 214 ms 0.435
8 422 ms 0.236
12 600 ms 0.301
16 762 ms 0.226
20 951 ms 0.231
24 1,176 ms 0.199

The first token grows ~48 ms per added caller, 214 ms at 4 to 1,176 at 24, on both runs. The A4B grew ~16 ms per caller, 149 ms at 1 to 290 at 10. That slope sets the count. VRAM does not: 24 slots load with 1,032 MiB free, and at 24 callers the first token is 1.2 s. The A4B at 10 callers gives 48 t/s per caller at 290 ms; PhoneLLM at 12 gives 40 t/s at 602 ms. We run 12.

The first version of the load script warmed one slot before the run. Each other caller paid a cold 10k prompt eval inside its measured turn, and the first-token maxima read 25 to 74 s. Warm every slot before a load run.

7. Card temperature

The one-caller decode rate on this host fell 55 % inside one run. One 24-minute sweep, same server, same config, a one-caller row of 43 to 46 tokens at the start and at the end:

Point Card temperature Clocks Power Decode, one caller
Sweep start 52 C 1,905 MHz 338 W 218.6 t/s
Sweep end 86 C 1,695 of 2,100 MHz 166 of 350 W 97.6 t/s

nvidia-smi set no throttle flag at either point. The clocks and the power show the state: the card holds 1,695 MHz under load and draws 166 W against a 350 W limit.

We then logged the card every 3 s through the replay and the conversation runs. It idles at 57 C and reaches 88 C within 33 s of the first request. The replay log holds one dip to 750 MHz at 86 C. The clock under load sits at 1,695 MHz in the median sample of both logs.

So an absolute rate from a rented card is a function of how long the card has been running. The rule we ended with: log the card, measure a one-caller row next to each load point, and publish the share. That is why section 6 carries shares, and why the decode rows in the outcome table carry a temperature.

Two rows predate the rule. The guidellm sweep, section 10, and the one-sample prefill in section 5 hold an unknown temperature, and we report them as such.

The two ends of the sweep give the gap: 218.6 against 97.6 t/s, 2.2x, on the same server. The numbers do not fit a core limit. 1,695 MHz is the rated boost clock of a 3090, 1.70 GHz on the NVIDIA spec page. The clock fell 11 %. The decode fell 55 %. A card at 1,695 MHz drawing 166 W is idle for part of each step. Decode on this model is bound by memory bandwidth, and the GDDR6X on a 3090 runs 20 to 30 C above the GPU sensor and throttles on its own junction temperature, measured at up to 110 C on these cards. nvidia-smi does not report that sensor, an open request on the NVIDIA developer forum. Hot memory fits the rows. It is a hypothesis: we did not read the memory temperature, clocks.mem or the throttle reasons. Whether a different host holds the cold rate is not measured.

8. Renting

The host passes the same filter as in the Gemma article and runs the same image. It is a 1× RTX 3090 in Czechia at $0.260 per hour. The weight pull is 17.97 GB. We did not time the boot on this build.

The image pins llama.cpp b10902, released 2026-09-11. The 3 upstream changes this model needs all merged before the pin: Nemotron 3 Nano support on 2025-12-16, the Mamba2 shape fix on 2026-06-26 and the checkpoint skip on 2026-06-11. MTP merged on 2026-05-16 as well, and the build does not use it. It carries no draft head: on a hybrid each draft step pays a checkpoint restore, so we did not test it.

The same replay gives 201 ms to the first token over loopback and 311 ms from a laptop: 110 ms per turn for the trip to Czechia. The Gemma host in Sweden cost 150 to 230 ms from the same laptop.

Cost per voice minute on the 12-slot config, the hourly price over 12 callers by division: $0.00036 at 100 % occupancy, $0.00066 at 55 %.

We destroyed the host after the runs. Each open row in section 9 needs a new rental.

9. What we did not measure

10. What to try next

In run order:

Try Targets Expected Cost
A second host, with clocks.mem, the throttle reasons and the memory temperature logged the 2.2x cold-to-hot decode gap either the cold rate holds on a cooler host, or the log names the limit one rental
Profile the 54 ms floor the warm turn a name for the cost. The Mamba2 state restore and the checkpoint save are the candidates one run, profiler on the box
Prompt eval at 8, 12 and 16 new tokens the step from 54 to 130 ms the shape of the curve between the 2 measured points one run
A shorter tool block the ~30 ms of render the 5 schemas add render between 35 and 67 ms at 54 messages agent change
-ub 4096 cold join stall, barge-in reach stall under 2 s, reach over 2k tokens, from the 512 to 2048 trend. VRAM cost unmeasured one restart
A cold join against 12 warm callers the config we run the stall at 12 warm callers. The rows are against 2 one run
guidellm at 12 slots with the card logged unique-prompt load a prefill row with a temperature on it. The 3 existing rows hold none one run

Not on the list: