1%
of its writing speed left: the app's engine at 4 threads, with six of its eight cores taken out of use by hand
OpenWeights, 3 to 11 November 2026
Why CPU decode stalls when a phone throttles.
When a Snapdragon 8 Elite Gen 5 phone overheats, it stops using six of its eight cores, and llama.cpp, the engine that runs the model, nearly stops writing the reply if it uses more than two threads. Why, the fix OpenWeights now carries, and whether ordinary chat gets that hot.
What we found
When six cores go out of use, the reply nearly stops
With six of the eight cores out of use, as the phone does on its own at 78 °C, the app's engine writing with 4 threads fell from 23.3 to 0.35 tokens a second (a token is a small piece of text); with 2 threads it kept 45% of its speed.
At a realistic length, in a pause set by hand, writing still nearly stopped and reading slowed less
On a 1,751-token prompt with up to 400 tokens of reply, with six cores out of use 4 threads wrote at 2.8% of their speed and 2 threads at 37% and 38%; reading the whole prompt took 265 to 417 s at every thread count, against 65 to 268 s normally, on a phone kept cooler in the paused runs.
Waiting threads hold the cores the working ones need
With six cores out of use, the engine's six threads all ran on cores 0 and 1 and none left a core to sleep: switched out 6,361 times in 2 s, they were still runnable every time. In a profile, 70% of the CPU time was in the function that contains llama.cpp's waiting loop, against 9% normally.
The app's engine now drops to 2 threads when a step stalls
In two runs where heat took six cores out of use on its own, the one with this guard wrote 3.75 tokens a second after falling back, the one without 0.24 (21.3 to 21.6 cool); one run each, from separate heats.
In two hand-set pauses on one phone, it came back within half a second
On a Snapdragon 8 Gen 2, trying 4 threads as soon as its 2-thread steps sped up brought the guard back 0.3 and 0.4 s after a log marker written just after the switch was turned off (two runs, the pause set by hand), against about 14 to 22 s on its 30 s schedule alone; it cost one false try, about 10 s, in each 9-minute pause.
Making waiting threads give up their core was a trade-off
Giving it up early kept 3.0 to 3.6 tokens a second with six cores out of use but cut some normal replies to as little as 20% of their usual speed; giving it up late cost nothing measurable but kept only 0.6 to 1.2.
No chat session was seen to reach the hard throttle
The heaviest, three sessions with no break between turns run back to back, peaked 0.1 °C below the 78 °C trip point over 97 minutes, and the guard never dropped in 247 turns of chat.
Four threads with the guard, not two all the time
Normally, with no cores taken out of use and a 1,751-token prompt, 2 threads wrote 11% slower than 4 on the Snapdragon and 29% slower on a MediaTek phone.
The results
When it overheats, the Snapdragon stops using six of its eight cores
The two kinds of phone in this study, as read from the phones themselves.
Phones B, C, D and E: every Snapdragon run in this post
Qualcomm reference phone
Snapdragon 8 Elite Gen 5 (SM8850)
CPU, 8 cores
- Cores 0 to 5: Oryon, up to 3.63 GHz
- Cores 6 and 7: Oryon, up to 4.61 GHz
When it overheats
Cores 2 to 7 out of use. The two left are from the slower cluster.
- GPU
- Adreno 840, up to 1.2 GHz
- NPU
- Hexagon v81
- Memory
- 23 GB, as the report records it
- Software
- Android 16, root shell
- Where
- Qualcomm Device Cloud's rack, on AC power
The 4 against 2 threads test and two chat sessions
POCO X8 Pro Max
MediaTek Dimensity 9500s (MT6991)
CPU, 8 cores
- Cores 0 to 3: Cortex-A720, up to 2.40 GHz
- Cores 4 to 6: Cortex-X4, up to 3.30 GHz
- Core 7: Cortex-X925, up to 3.73 GHz
- GPU
- Mali-G925-Immortalis MC11
- Memory
- 11.0 GiB visible to Android
- Software
- Android 16, HyperOS 3
- Where
- The owner's own phone, on USB power
A core is one of the CPU's eight processors; the app writes the reply with several threads, ideally one per core; when the board reaches 78 °C, the Snapdragon stops running work on six cores until it cools, which we call the hard throttle. Core types and top clocks come from each phone's CPU registers and clock limits; the Snapdragon phones are Qualcomm's reference design for the chip.
The app picks how many threads; the phone decides which cores are in use
A thread is a worker helping write the reply; a core is a processor that runs it. The app chooses how many threads, the phone how many cores.
The app picks how many threadsAndroid puts each on a coreThe phone decides which cores runWaiting for a core
Writing, normally
4 threads, each on a core of its own: 23.3 tokens a second.
Writing, hard throttle, the usual 4
Only cores 0 and 1 run work, so two threads wait at a time: the reply crawls at 0.35 tokens a second.
Writing, hard throttle, the guard's 2
Two threads, two cores, each has one again: 10.70 tokens a second, about 30 times as fast.
How many threads the app picks
| Writing the reply (decode) | Reading the prompt (prefill) | |
|---|---|---|
| Before each reply | 4, half the cores | 6 on the Snapdragon, 8 on the POCO |
| Android says warm | 3 | 4 |
| Android says hot | 2 | 2 |
| Android says very hot | does not start the reply | does not read the prompt |
| A reply stalls | 2, trying the usual count after 30 s or more | 2 until a try works |
Why the guard times each step. On the Snapdragon we checked, Android's list of usable cores still showed all eight while six were out of use, so the app cannot count the cores left.
Why threads are not tied to cores. Linux allows it, but OpenWeights lets Android place its threads on any core the app may use.
Speeds from the app's own engine on Snapdragon phone A, six cores taken out of use by hand. The rules are from its heat policy (ThermalPolicy.kt) and its guard (stall_guard.h); the exact ones are under Every number.
With six cores out of use, the guard keeps the reply going
Tokens written after six cores were taken out of use by hand, 10 seconds into a reply, with an earlier build of the guard and without.
With the guardWithout it
Snapdragon test phone D, Qwen3 4B, 4 threads, a 1,751-token prompt, the six cores kept out of use to the end. The guard fell back to 2 threads 19 s in; the run without it wrote 56 tokens before its watchdog stopped it.
With the pause set by hand on a Snapdragon 8 Gen 2, the early try came back within about a second
How long after the cores came back the app's engine was writing with 4 threads again: with the guard trying as soon as its steps speed up, and with only its try every 30 s.
Early try, run 1, ms marker
Back to 4 threads0.3 sEarly try, run 2, ms marker
Back to 4 threads0.4 sEarly try, run 3, approx.
Back to 4 threads0.7 sEarly try, run 4, approx.
Back to 4 threads1.0 s30 s schedule only, run 1, ms marker
Back to 4 threads13.8 s30 s schedule only, run 2, approx.
Back to 4 threads15.5 s30 s schedule only, run 3, approx.
Back to 4 threads21.8 s
Snapdragon 8 Gen 2 (a different phone and chip from the rest of this post), Qwen3 4B, a 1,751-token prompt, the phone's own pause switch set by hand for 30 s, leaving its three little cores. Bars marked "ms marker" are timed from a log line written just after the switch, to the millisecond; the others are approximate, measured from a whole-second timestamp logged after the switch. In pauses that stayed for about 9 minutes, the early try made 1 false try in each run beside its 12 scheduled ones (12 with the schedule alone), each a stalled step of about 8 to 11 s; with no pause it made none.
On a 19-token prompt, every thread past the two cores left costs nearly all the speed, writing and reading
How fast the app's own engine writes the reply (decode) and reads the prompt (prefill), normally and with six cores taken out of use by hand, for each number of threads.
Writing the reply (decode)
1 thread
Normally15.7 tok/sOnly 2 cores left7.28 tok/sSpeed kept46%2 threads
Normally23.7 tok/sOnly 2 cores left10.70 tok/sSpeed kept45%3 threads
Normally23.3 tok/sOnly 2 cores left0.80 tok/sSpeed kept3.4%4 threads, the app's usual
Normally23.3 tok/sOnly 2 cores left0.35 tok/sSpeed kept1.5%5 threads
Normally23.7 tok/sOnly 2 cores left0.21 tok/sSpeed kept0.9%6 threads
Normally23.9 tok/sOnly 2 cores left0.12 tok/sSpeed kept0.5%7 threads
Normally22.7 tok/sOnly 2 cores left0.087 tok/sSpeed kept0.4%8 threads
Normally19.8 tok/sOnly 2 cores left0.069 tok/sSpeed kept0.3%
Reading the prompt (prefill)
1 thread
Normally42.7 tok/sOnly 2 cores left17.3 tok/sSpeed kept40%2 threads
Normally71.7 tok/sOnly 2 cores left31.2 tok/sSpeed kept44%3 threads
Normally84.8 tok/sOnly 2 cores left8.68 tok/sSpeed kept10%4 threads
Normally89.6 tok/sOnly 2 cores left4.09 tok/sSpeed kept4.6%5 threads
Normally111.1 tok/sOnly 2 cores left3.33 tok/sSpeed kept3.0%6 threads
Normally125.8 tok/sOnly 2 cores left2.11 tok/sSpeed kept1.7%7 threads
Normally138.7 tok/sOnly 2 cores left1.66 tok/sSpeed kept1.2%8 threads
Normally126.7 tok/sOnly 2 cores left1.04 tok/sSpeed kept0.8%
Snapdragon phone A, Qwen3 4B, a 19-token prompt and up to 32 tokens of reply, both jobs at the same thread count, the guard off. Medians of three runs in each state, run in a shuffled order, each after a cooling gate. A run its 300 s watchdog stopped counts up to its last progress line; with cores out of use a run counts only if, in every sample taken once a second, the switches read on and each busy thread was last seen on core 0 or 1; where threads ran between samples is not known. The two speed columns share a scale. The app's heat policy was not in the loop.
In a pause set by hand at a realistic length, writing stalled past two threads; reading slowed less
The same test on a 1,751-token prompt with up to 400 tokens of reply, about a typical reply in the conversations this study replayed: how fast the app's own engine writes, and how long it takes to read the whole prompt, normally and with six cores taken out of use by hand.
Writing the reply (decode)
1 thread
Normally6.5 tok/sOnly 2 cores left3.15 tok/sSpeed kept45%, 52%2 threads
Normally10.0 tok/sOnly 2 cores left3.75 tok/sSpeed kept37%, 38%3 threads
Normally13.1 tok/sOnly 2 cores left0.55 tok/sSpeed kept4.1%, 4.2%4 threads, the app's usual
Normally10.2 tok/sOnly 2 cores left0.28 tok/sSpeed kept2.8%6 threads
Normally15.8 tok/sOnly 2 cores left0.11 tok/sSpeed kept0.7%8 threads
Normally12.4 tok/sOnly 2 cores left0.064 tok/sSpeed kept0.5%
Reading the whole prompt (prefill), with the time it took
1 thread
Normally6.5 tok/s, 268 sOnly 2 cores left4.2 tok/s, 417 sSpeed kept64%2 threads
Normally12.4 tok/s, 141 sOnly 2 cores left6.6 tok/s, 265 sSpeed kept53%3 threads
Normally11.9 tok/s, 147 sOnly 2 cores left6.0 tok/s, 292 sSpeed kept50%4 threads
Normally17.1 tok/s, 102 sOnly 2 cores left6.1 tok/s, 285 sSpeed kept36%6 threads
Normally20.7 tok/s, 84 sOnly 2 cores left4.8 tok/s, 363 sSpeed kept23%8 threads
Normally27.1 tok/s, 65 sOnly 2 cores left4.8 tok/s, 362 sSpeed kept18%
Snapdragon phone F, Qwen3 4B, the guard off, each run after a cooling gate. Writing: 9 pairs, two at 1 to 3 threads and one at 4, 6 and 8, each a run with the cores taken out of use just after the reply began against one without, between the same token counts; at 3 threads and more the paused runs stopped at 300 s, having last reported 31 to 174 tokens, including those written before the pause, so their speed is over an early part of the reply. Reading: the whole prompt, 2 to 4 runs normally and 1 or 2 with the cores out of use from the start, medians. The runs with no pause warmed the board to 58 to 61 °C and had a CPU limit lowered by Android in 75% to 99% of their samples; the paused prompt runs stayed at 52 °C or below with both limits at their maximum in every sample, so reading compares runs in different heat. A paused run counts only if its samples, about one a second, cover the pause with no gap over 5 s, all read the switches on and all show each busy thread last on core 0 or 1; a sampled check cannot show where a thread ran between samples. One run where samples showed two threads on cores 6 and 7 until about 13 s after the switches read on is left out of writing.
With six cores out of use, six threads share the two left
Which thread each core ran, recorded by the phone's kernel over 10 ms of the app's own engine writing a reply with 6 threads, normally and with six cores taken out of use by hand.
Thread 1Thread 2Thread 3Thread 4Thread 5Thread 6Another taskOut of use
Normally
Each thread ran about 100% of the 2 s, a full core's worth; 2 stayed on one core and 4 moved between cores now and then.
- Thread 1core 7100%
- Thread 2cores 1, 3100%
- Thread 3cores 0, 2, 5100%
- Thread 4core 6100%
- Thread 5cores 0, 2, 3, 4100%
- Thread 6cores 0, 2, 4, 5100%
Six cores out of use
All six on cores 0 and 1: 2 on core 0 at about 48% of it each, 4 on core 1 at 22% to 23% of it each. In 2 s an engine thread was switched out 6,361 times, against 610 normally, still runnable every time: none left its core to sleep.
- Thread 1core 123%
- Thread 2core 048%
- Thread 3core 122%
- Thread 4core 122%
- Thread 5core 048%
- Thread 6core 123%
Snapdragon phone A, Qwen3 4B. The kernel's scheduler events (sched_switch) for 2 s of decode in each state; the lanes show 10 ms from 1000 ms into each recording, every stretch at its true width, and the shares are over the whole 2 s. Grey is any other task: here mostly the lab's screen streaming, the graphics driver and USB. Each switch records whether the thread it took off could still run: a thread that waited by sleeping would leave blocked and need waking, and in both recordings no engine thread did either (0 blocked, 0 woken). A thread that gave its core up by yielding would also leave runnable, which the trace cannot tell apart.
A finished thread keeps its core while the others wait for one
A token is hundreds of small operations, whether written or read; between them, every thread waits at a checkpoint for the rest. The recording above shows no thread leaving its core to sleep, and in a profile most of the sampled CPU time with two cores left was in the function that contains that wait. At 6 threads a token took about 192 times as long.
How the threads work
When the model loads
The app makes its threads once
Normally one set, kept and reused: as many as the larger of its two counts, for reading the prompt and writing the reply.
For each token
Hundreds of small operations, one after another
Llama 3.2 3B's plan for one token has 874 steps; some are skipped or combined when it runs. Reading a chunk of the prompt works the same way.
For each operation
Each thread does its slice, then waits at a checkpoint
Most operations are split one slice per thread, and the threads meet at a checkpoint between them. When every thread is done, all start the next.
Between tokens
With no work, they check, then sleep
In llama.cpp's code, a thread with no new work checks for a short while, then sleeps. In the normal recording, about 45 tokens long, no engine thread left its core to sleep or was woken. A new thread count only picks how many of the set work.
Writing the reply (decode) and reading the prompt (prefill) both work this way. On the 19-token prompt both lost most of their speed with more than two threads and six cores out of use; on the long prompt reading kept far more of its speed than writing. The two thread-count figures above show each.
The function holding the waitMatrix and attention kernelsEverything else
Normal
Everything else 13.4%The function holding the wait 8.6%Matrix and attention kernels 78%everything else 13.4%, the function holding the wait 8.6%, matrix and attention kernels 78%
Six cores out of use
Everything else 1.4%The function holding the wait 69.6%Matrix and attention kernels 29%everything else 1.4%, the function holding the wait 69.6%, matrix and attention kernels 29%
A 6-thread decode's CPU time, sampled 1,000 times a second with six cores out of use and normally: Snapdragon test phone E, llama.cpp's benchmark tool, 3.46 cores busy on average normally and 1.83 with six out of use. The samples were kept by function, so how that function's time splits between the wait and its other work is not known.
Nonstop benchmark work did reach the hard throttle, 87 minutes in
The board sensor through the benchmark run that logged the natural hard throttles: a warm-up, nine pairings of one model, then a second model, back to back with only 2-minute waits to cool.
Benchmark, nonstopHard throttle on
Snapdragon test phone C, 3 November: Qwen3 1.7B's warm-up and its nine pairings of CPU, GPU and NPU, then Qwen3 4B, 154 minutes in all. The sensor read 33.8 °C at the start and 65.3 °C after the 8.7-minute warm-up, and was logged every 2 seconds only from minute 66, so the line starts there. Mean of each 30 s.
No chat session was seen to reach the hard throttle
The board sensor that trips the hard throttle, through three chat sessions on one Snapdragon phone and three more run back to back on another.
30 s between turns5 s between turnsNo breakNo break, 3 in a row
Mean of each 30 s. Phones D and A in the rack, screen on, Qwen3 4B, 4 threads, the guard on, the study's nine conversations with each reply kept in the history. On phone A each session started the moment the one before ended, at minutes 29.4 and 62.9, so that line counts from the first session's start; none of the phone's eleven switches of this kind was seen on in any of its 4,958 samples, about one a second, though a crossing shorter than the gap between samples would not show. The benchmark run above did reach the trip.
In two throttles forced by heat, the run with the guard kept writing
Tokens written after the phone took six cores out of use on its own, in two runs heated until it did: one without the guard, one with it.
With the guardWithout itStill out of use in the run with the guard
Snapdragon phone A, Qwen3 4B, 4 threads, a 19-token prompt and up to 256 tokens. Heated with busy loops and memory-copy loops at full brightness: 29.9 minutes from 54.4 °C for the run without the guard, 9.6 from 62.3 for the one with it. The cores were first seen out of use at 77.99 and 78.02 °C and back below 70 °C, 52 and 67 s later. Reading the prompt took 7.8 and 5.6 s, against about 0.2 cool, where the same work ran at 21.3 to 21.6 tokens a second. One run each on one phone, from separate heats, stopped at 256 tokens; the heaters stopped when a switch was seen on, so these pause lengths say nothing of how long the model's own load would keep one going. The lines step to each logged progress count, about four a second.
A fixed 2 threads costs more on MediaTek
2 threads against 4 with no cores taken out of use, at the same realistic reply length on both phones.
Snapdragon 8 Elite Gen 5MediaTek MT6991
Writing the reply
How much slower 2 threads are than 4
Snapdragon 11% slowerMediaTek 29% slowerThe first token of the reply
How much later it comes with 2 threads
Snapdragon 36% laterMediaTek 97% later
Medians of two runs each, a 1,751-token prompt and up to 1,024 tokens of reply: Snapdragon phone D, and a POCO X8 Pro Max on USB power.
Every numberThe guard's runs, the llama.cpp waits, where the threads ran, the app's rules, the six natural throttles, every chat session and the thread countsShowHide
| Six cores out of use | With the guard | Without it | What the guard did |
|---|---|---|---|
| For the rest of the reply | 3.21 to 3.37 tok/s | 0.57 to 0.61 tok/s | Fell back 7 to 19 s in; each try at 4 threads cost one stalled step of 2.6 to 8.7 s |
| For 30 seconds, mid-reply | Reply written in 115.7 to 118.7 s | 122 to 124.6 s | Fell back 6 to 14 s in; back to 4 threads 6 to 14 s after it ended |
| None logged | 10.18 tok/s, and 8.90 in a run where it fell back | 10.04 and 10.3 tok/s | Nothing in one run. In another, two steps of 3.9 and 4.8 s with no logged switch set, and it fell back for the last 10 s |
Snapdragon test phone D, Qwen3 4B, a 1,751-token prompt, up to 1,024 tokens; three runs with the guard and three without for cores out of use to the end, three and two for cores out of use for 30 seconds. In the latest 30-second run the guard fell back 12 s in and was back 12 s after the cores came back, at its first try.
| The wait | Decode with six cores out of use | Decode normally |
|---|---|---|
| Stock: never gives up its core | No turn finished in 300 s with 6 threads | Stock |
| Gives it up after 10,000 checks | 3.0 to 3.6 tok/s with 6 or 4 threads | 20% to 62% of stock on the turns it slowed; others unchanged |
| Gives it up after 1,000,000 checks | 0.6 to 1.2 tok/s with 6 and 4 threads | Within run-to-run variation of stock |
| Stock, with 2 threads | 6.9 tok/s | 62% of 6 threads (11.2 to 11.3 against 18.0 to 18.2 tok/s) |
The benchmark on the same three turns of Qwen3 4B: the changed waits on phones E and D, stock 2 against 6 threads on phone B. A budget between the two was not tried.
The scheduler traces (phone A, 2 s each): normally the six engine threads ran in 616 stretches with a median of 2.147 ms; with six cores out of use, in 6,362 with a median of 0.103 ms. Then cores 0 and 1 ran engine threads 96% and 90% of the time and other tasks 4% and 10%, and cores 2 to 7 logged no switch at all. An engine thread was switched out 610 times normally and 6,361 times with six cores out of use, still runnable every time; it left blocked 0 times and was woken 0 times. An earlier run on phone B, with llama.cpp's own benchmark and the phone's own throttles, sampled every 2 s, put the six on cores 0 and 1 at a median 23% of a core each.
The app's rules. Before each reply, half the cores, never under 2 or over 6. Warm is Android's light or moderate heat status, hot is severe, very hot is critical or worse. A slow step takes over a quarter of a second and more than 8 times the recent normal step, or more than 2 s over it; before the guard has seen enough steps, any step over 1.5 s. A try comes after at least four steps at 2 threads, and keeps the planned count if its step is within 3 times the recent 2-thread step and within 2 s of it. A fallback carries over to later replies until a try works, which restores both counts.
| On | Off | Length | Decoding | Decode stuck |
|---|---|---|---|---|
| 18:00:50 | 18:01:51 | 61 s | CPU, Qwen3 1.7B | 18:00:50 to 18:01:51 |
| 18:12:04 | 18:13:29 | 85 s | NPU, Qwen3 1.7B | No |
| 18:51:27 | 18:52:26 | 59 s | CPU, Qwen3 4B | 18:51:27 to 18:52:24 |
| 18:55:43 | 18:56:48 | 65 s | CPU, Qwen3 4B | 18:55:48 to 18:56:47 |
| 19:00:31 | 19:01:33 | 62 s | CPU, Qwen3 4B | 19:00:32 to 19:01:33 |
| 19:05:20 | 19:06:27 | 67 s | CPU, Qwen3 4B | 19:05:23 to 19:06:25 |
3 November 2026, during the prefill and decode matrix: each time the CPU was writing the reply, it was stuck while the cores were out of use, to within a few seconds. The NPU kept writing through its throttle.
An earlier run on phone B recorded where the threads ran: during hard throttles the busy ones were on cores 0 and 1 in 99.7% of samples, against all 8 cores the rest of the time. A sample shows only the last core a thread ran on.
| Run | Heated | Cores out of use | Reading the 19-token prompt | Writing while out of use | The guard |
|---|---|---|---|---|---|
| Without the guard | 29.9 min from 54.4 °C | First seen at 78.0 °C, about 52 s | 7.8 s | 0.24 tok/s | Off |
| With the guard | 9.6 min from 62.3 °C | First seen at 78.0 °C, about 67 s | 5.6 s | 3.48 tok/s, 3.75 after the fall-back | Fell back 21.1 s in |
| Cool, before heating | 213 and 229 ms | 21.6 and 21.3 tok/s | Off, then on |
Snapdragon phone A, the build with the guard's final rules. The edges of each stretch come from a log read about once a second. In a third run with the guard, started after the cores had come back (board at 71.2 °C), a decode step took 3.9 s against a normal 60 ms with none of the eleven switches seen on in the 7 samples during the stall, while the hottest CPU sensor fell from 104.6 to 92.3 °C; the guard fell back, and the cause is not known.
| Phone | Between turns | Turns | Hottest | Hard throttle (Snapdragon) or Android status (POCO) | Decode, tok/s | First token, s, median (longest) |
|---|---|---|---|---|---|---|
| Snapdragon, phone D | 30 s | 29 in 40.7 min | XO 64.2 °C | none in 1,876 samples | 12.8 | 4.8 (119.3) |
| Snapdragon, phone D | 5 s | 31 in 30.4 min | XO 73.7 °C | none in 1,453 | 13.3 | 4.8 (119.4) |
| Snapdragon, phone D | none | 34 in 30.6 min | XO 76.4 °C | none in 1,709, all eleven switches read | 12.7 | 4.9 (119.4) |
| Snapdragon, phone A | none, 1st of 3 back to back | 34 in 29.4 min | XO 75.4 °C | none in 1,512, all eleven read | 12.9 | 4.9 (99.9) |
| Snapdragon, phone A | none, 2nd of 3 | 34 in 33.5 min | XO 77.6 °C | none in 1,710 | 11.4 | 5.3 (119.8) |
| Snapdragon, phone A | none, 3rd of 3 | 34 in 34 min | XO 77.9 °C | none in 1,737 | 11.2 | 5.3 (119.8) |
| MediaTek POCO, charging | 30 s | 17 in 30.9 min | CPU 73.8 °C | Android status 0 throughout | 8.2 | 3.2 (108.4) |
| MediaTek POCO, charging | none | 34 in 42.8 min | CPU 85.4 °C | Android status 0 throughout | 8.1 | 6.4 (138.2) |
The POCO's rows give Android's thermal status, since an app cannot read its throttle; it was charging. The two-minute first tokens followed a cache miss of 550 tokens whose cause was not traced.
| Phone | Writing, tok/s, 4 then 2 threads | First token, s, 4 then 2 threads |
|---|---|---|
| Snapdragon | 10.04, 10.3; 8.94, 9.16 | 101 to 103; 138 to 139 |
| MediaTek | 6.77, 7.22; 4.86, 5.07 | 128 to 140; 249 to 278 |
| Threads | Reading the prompt, normally | With six cores out of use | Writing, normally | With six cores out of use | Tokens compared |
|---|---|---|---|---|---|
| 1 | 235.7 s, 268.2 s, 268.8 s | 416 s, 417.4 s | 6.52, 6.54 tok/s | 3.378, 2.92 tok/s | 15 to 400, 10 to 400* |
| 2 | 140.7 s, 140.7 s, 140.9 s, 141 s | 265.4 s | 9.93, 10.06 tok/s | 3.757, 3.738 tok/s | 20 to 400, 18 to 400 |
| 3 | 146.8 s, 146.9 s, 148.4 s | 290.8 s, 292.5 s | 13.11, 13.05 tok/s | 0.542, 0.552 tok/s | 14 to 172, 8 to 172* |
| 4 | 102 s, 102.3 s, 102.6 s | 285.4 s | 10.16 tok/s | 0.28 tok/s | 23 to 105 |
| 6 | 84.3 s, 84.5 s | 362.6 s | 15.78 tok/s | 0.111 tok/s | 10 to 37 |
| 8 | 64.4 s, 64.7 s | 362.1 s | 12.42 tok/s | 0.064 tok/s | 12 to 30 |
Snapdragon phone F, a 1,751-token prompt and up to 400 tokens; the table lists the measurements counted. Reading is the engine's time to read the whole prompt; normally means runs with no cores out of use and runs where they went out of use only after reading. Writing pairs a run with the cores out of use from just after the reply began with one without, between the same token counts; the paused window starts at its first progress line at least 1 s after the switches were written. The logs keep counts, not the tokens, so that the two replies matched is not shown. * against the first round's normal run, which the second round did not reach. In the run left out, two threads were seen on cores 6 and 7 for about 13 s after the switches read on, while it wrote tokens 17 to 97 at 5.9 tok/s; measured after that, it wrote 0.276 tok/s against 9.89, as the first round's 4-thread run did. In the runs with no pause both CPU limits were at their maximum in 1% to 25% of samples; in the paused prompt runs, in all of them.
The thread count also changes the text: 6 threads against 2 changed 23 of 33 replies, while the same count gave identical replies on two phones.
How we ran it
- Phones
- Snapdragon 8 Elite Gen 5 (SM8850) phones A, B, C, D, E and F, Qualcomm's reference design, in Qualcomm Device Cloud's rack, root shell, AC power, 3 to 11 November 2026. A POCO X8 Pro Max (MediaTek Dimensity 9500s) on USB power for two sessions and 2 threads against 4. Their specifications are in the first figure.
- Cores out of use
- The phone does it on its own at 78 °C. For the tests we flipped the same two switches by hand after letting the phone cool, as root on the rack phones, which an app cannot do, and checked in samples about a second apart that work then ran only on cores 0 and 1; samples cannot rule out work on other cores between them. Twice, on phone A, we heated it with busy loops and memory-copy loops until the switches came on by themselves.
- Runtime
- llama.cpp's benchmark, built from b2e5e9b (release b10549), on B and C for the natural-heat runs, on B for the first runs by hand, and on E and D for the profile and the llama.cpp change. The app's own engine, on llama.cpp v0.8.0 (8642541b), every decode step timed, on A for the thread counts and the scheduler traces, on F for the thread counts at a realistic length, on D for the guard and the sessions, and on A for the longest sessions and the forced throttle.
- Model
- Qwen3 4B in Q4_0 for every run set up for this post; the six logged throttles came from the matrix, on Qwen3 1.7B and 4B.
- The guard
- Two slow steps in a row (over a quarter of a second, and more than 8 times the recent normal step or more than 2 s over it) drop decode and prompt reading to 2 threads; after at least 30 s and four steps at 2 threads it tries its count again, or at once when two 2-thread steps in a row come 1.6 times faster than usual, which in two runs on a Snapdragon 8 Gen 2, with the pause set by hand, came back 0.3 and 0.4 s after a log marker written just after the switch, against about 14 to 22 s on the schedule alone. Codex reviewed it ten times.
Limits
- One chip
- Taking cores out of use is a Qualcomm mechanism, measured on the SM8850 only. The MediaTek phone checked what 2 threads cost and that the guard stays quiet.
- Cores taken out by hand
- The final rules have met cores out of use once, in a throttle forced by heat rather than by chat, on one phone, a 19-token prompt and 256 tokens, against one run without the guard from a separate heat; the runs with cores taken out by hand used earlier builds. One of 14 attempts by hand did not hold and two more probably did not; those runs are left out.
- What the guard misses
- Cores going out of use while the prompt is being read: in the forced throttle 19 tokens took 5.6 s with the guard on, and with the cores out of use by hand and the guard off the 1,751-token prompt took 285 s at 4 threads against 265 at 2, one run each; what the guard would change there was not measured. And twice it fell back on steps of about 4 s with none of the logged switches on, once when the logger read three of the phone's eleven switches of this kind and once when it read all eleven; the cause is not known.
- The sessions
- Rack phones and a phone on a desk, not in a hand or a pocket. The longest, 97 minutes with no break on phone A, came within 0.1 °C of the trip with no switch seen on; gaps between samples could hide a brief crossing, and a longer session, a warmer room or a phone in a hand was not tested.
- Not measured
- Energy and battery; which version of a reply the thread count changed is better.
Sources
Every figure here is in the OpenWeights report. The time charts are generated from its raw logs at a pinned commit.