Skip to content
All research

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

01234567

4 threads, each on a core of its own: 23.3 tokens a second.

Writing, hard throttle, the usual 4

01234567

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

01234567

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 reply4, half the cores6 on the Snapdragon, 8 on the POCO
Android says warm34
Android says hot22
Android says very hotdoes not start the replydoes not read the prompt
A reply stalls2, trying the usual count after 30 s or more2 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

02505007501,000Tokens060120180240300Six cores out of useFalls back to 2 threadsWith the guardWithout it0 s after the cores went out of useWith the guard0 tokens writtenWithout it0 tokens written30 s after the cores went out of useWith the guard37 tokens writtenWithout it15 tokens written60 s after the cores went out of useWith the guard159 tokens writtenWithout it19 tokens written90 s after the cores went out of useWith the guard291 tokens writtenWithout it23 tokens written120 s after the cores went out of useWith the guard396 tokens writtenWithout it27 tokens written150 s after the cores went out of useWith the guard506 tokens writtenWithout it32 tokens written180 s after the cores went out of useWith the guard576 tokens writtenWithout it36 tokens written210 s after the cores went out of useWith the guard663 tokens writtenWithout it41 tokens written240 s after the cores went out of useWith the guard754 tokens writtenWithout it46 tokens written270 s after the cores went out of useWith the guard834 tokens writtenWithout it51 tokens written300 s after the cores went out of useWith the guardendedWithout it56 tokens writtenSeconds since six cores went out of use

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 s
  • Early try, run 2, ms marker

    Back to 4 threads0.4 s
  • Early try, run 3, approx.

    Back to 4 threads0.7 s
  • Early try, run 4, approx.

    Back to 4 threads1.0 s
  • 30 s schedule only, run 1, ms marker

    Back to 4 threads13.8 s
  • 30 s schedule only, run 2, approx.

    Back to 4 threads15.5 s
  • 30 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/s
    Only 2 cores left7.28 tok/s
    Speed kept46%
  • 2 threads

    Normally23.7 tok/s
    Only 2 cores left10.70 tok/s
    Speed kept45%
  • 3 threads

    Normally23.3 tok/s
    Only 2 cores left0.80 tok/s
    Speed kept3.4%
  • 4 threads, the app's usual

    Normally23.3 tok/s
    Only 2 cores left0.35 tok/s
    Speed kept1.5%
  • 5 threads

    Normally23.7 tok/s
    Only 2 cores left0.21 tok/s
    Speed kept0.9%
  • 6 threads

    Normally23.9 tok/s
    Only 2 cores left0.12 tok/s
    Speed kept0.5%
  • 7 threads

    Normally22.7 tok/s
    Only 2 cores left0.087 tok/s
    Speed kept0.4%
  • 8 threads

    Normally19.8 tok/s
    Only 2 cores left0.069 tok/s
    Speed kept0.3%

Reading the prompt (prefill)

  • 1 thread

    Normally42.7 tok/s
    Only 2 cores left17.3 tok/s
    Speed kept40%
  • 2 threads

    Normally71.7 tok/s
    Only 2 cores left31.2 tok/s
    Speed kept44%
  • 3 threads

    Normally84.8 tok/s
    Only 2 cores left8.68 tok/s
    Speed kept10%
  • 4 threads

    Normally89.6 tok/s
    Only 2 cores left4.09 tok/s
    Speed kept4.6%
  • 5 threads

    Normally111.1 tok/s
    Only 2 cores left3.33 tok/s
    Speed kept3.0%
  • 6 threads

    Normally125.8 tok/s
    Only 2 cores left2.11 tok/s
    Speed kept1.7%
  • 7 threads

    Normally138.7 tok/s
    Only 2 cores left1.66 tok/s
    Speed kept1.2%
  • 8 threads

    Normally126.7 tok/s
    Only 2 cores left1.04 tok/s
    Speed 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/s
    Only 2 cores left3.15 tok/s
    Speed kept45%, 52%
  • 2 threads

    Normally10.0 tok/s
    Only 2 cores left3.75 tok/s
    Speed kept37%, 38%
  • 3 threads

    Normally13.1 tok/s
    Only 2 cores left0.55 tok/s
    Speed kept4.1%, 4.2%
  • 4 threads, the app's usual

    Normally10.2 tok/s
    Only 2 cores left0.28 tok/s
    Speed kept2.8%
  • 6 threads

    Normally15.8 tok/s
    Only 2 cores left0.11 tok/s
    Speed kept0.7%
  • 8 threads

    Normally12.4 tok/s
    Only 2 cores left0.064 tok/s
    Speed kept0.5%

Reading the whole prompt (prefill), with the time it took

  • 1 thread

    Normally6.5 tok/s, 268 s
    Only 2 cores left4.2 tok/s, 417 s
    Speed kept64%
  • 2 threads

    Normally12.4 tok/s, 141 s
    Only 2 cores left6.6 tok/s, 265 s
    Speed kept53%
  • 3 threads

    Normally11.9 tok/s, 147 s
    Only 2 cores left6.0 tok/s, 292 s
    Speed kept50%
  • 4 threads

    Normally17.1 tok/s, 102 s
    Only 2 cores left6.1 tok/s, 285 s
    Speed kept36%
  • 6 threads

    Normally20.7 tok/s, 84 s
    Only 2 cores left4.8 tok/s, 363 s
    Speed kept23%
  • 8 threads

    Normally27.1 tok/s, 65 s
    Only 2 cores left4.8 tok/s, 362 s
    Speed 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

  1. 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.

  2. 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.

  3. 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.

  4. 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

4050607080°C6080100120140160Hard throttle trips at 78 °CBenchmark, nonstopMinute 70 of the runBenchmark, nonstop68 °CMinute 80 of the runBenchmark, nonstop73.5 °CMinute 90 of the runBenchmark, nonstop66.7 °CMinute 100 of the runBenchmark, nonstop69.9 °CMinute 110 of the runBenchmark, nonstop69.2 °CMinute 120 of the runBenchmark, nonstop69.6 °CMinute 130 of the runBenchmark, nonstop64.9 °CMinute 140 of the runBenchmark, nonstop75.9 °CMinute 150 of the runBenchmark, nonstop77 °CMinutes into the run

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

304050607080°C020406080100Hard throttle trips at 78 °CNo break, 3 in a rowNo break5 s between turns30 s between turnsMinute 030 s between turns46.3 °C5 s between turns45.9 °CNo break46.2 °CNo break, 3 in a row36.1 °CMinute 1030 s between turns57.4 °C5 s between turns62.6 °CNo break66.5 °CNo break, 3 in a row64.5 °CMinute 2030 s between turns61.5 °C5 s between turns70.3 °CNo break74.5 °CNo break, 3 in a row72.9 °CMinute 3030 s between turns59.7 °C5 s between turns72.5 °CNo break75.7 °CNo break, 3 in a row75.5 °CMinute 4030 s between turns63.5 °C5 s between turnsendedNo breakendedNo break, 3 in a row74.9 °CMinute 5030 s between turnsended5 s between turnsendedNo breakendedNo break, 3 in a row76.4 °CMinute 6030 s between turnsended5 s between turnsendedNo breakendedNo break, 3 in a row76.3 °CMinute 7030 s between turnsended5 s between turnsendedNo breakendedNo break, 3 in a row71.3 °CMinute 8030 s between turnsended5 s between turnsendedNo breakendedNo break, 3 in a row76.7 °CMinute 9030 s between turnsended5 s between turnsendedNo breakendedNo break, 3 in a row77.8 °CMinutes since the session began

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

064128192256Tokens01530456075Out of use in both runsFalls back to 2 threadsWithout itWith the guard0 s after the cores went out of useWith the guard0 tokens writtenWithout it0 tokens written10 s after the cores went out of useWith the guard0 tokens writtenWithout it0 tokens written20 s after the cores went out of useWith the guard2 tokens writtenWithout it0 tokens written30 s after the cores went out of useWith the guard43 tokens writtenWithout it3 tokens written40 s after the cores went out of useWith the guard83 tokens writtenWithout it6 tokens written50 s after the cores went out of useWith the guard123 tokens writtenWithout it8 tokens written60 s after the cores went out of useWith the guard145 tokens writtenWithout it134 tokens written70 s after the cores went out of useWith the guard218 tokens writtenWithout itendedSeconds since the cores went out of use

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% slower
    MediaTek 29% slower
  • The first token of the reply

    How much later it comes with 2 threads

    Snapdragon 36% later
    MediaTek 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 countsShow
Six cores out of useWith the guardWithout itWhat the guard did
For the rest of the reply3.21 to 3.37 tok/s0.57 to 0.61 tok/sFell 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-replyReply written in 115.7 to 118.7 s122 to 124.6 sFell back 6 to 14 s in; back to 4 threads 6 to 14 s after it ended
None logged10.18 tok/s, and 8.90 in a run where it fell back10.04 and 10.3 tok/sNothing 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 waitDecode with six cores out of useDecode normally
Stock: never gives up its coreNo turn finished in 300 s with 6 threadsStock
Gives it up after 10,000 checks3.0 to 3.6 tok/s with 6 or 4 threads20% to 62% of stock on the turns it slowed; others unchanged
Gives it up after 1,000,000 checks0.6 to 1.2 tok/s with 6 and 4 threadsWithin run-to-run variation of stock
Stock, with 2 threads6.9 tok/s62% 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.

OnOffLengthDecodingDecode stuck
18:00:5018:01:5161 sCPU, Qwen3 1.7B18:00:50 to 18:01:51
18:12:0418:13:2985 sNPU, Qwen3 1.7BNo
18:51:2718:52:2659 sCPU, Qwen3 4B18:51:27 to 18:52:24
18:55:4318:56:4865 sCPU, Qwen3 4B18:55:48 to 18:56:47
19:00:3119:01:3362 sCPU, Qwen3 4B19:00:32 to 19:01:33
19:05:2019:06:2767 sCPU, Qwen3 4B19: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.

RunHeatedCores out of useReading the 19-token promptWriting while out of useThe guard
Without the guard29.9 min from 54.4 °CFirst seen at 78.0 °C, about 52 s7.8 s0.24 tok/sOff
With the guard9.6 min from 62.3 °CFirst seen at 78.0 °C, about 67 s5.6 s3.48 tok/s, 3.75 after the fall-backFell back 21.1 s in
Cool, before heating213 and 229 ms21.6 and 21.3 tok/sOff, 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.

PhoneBetween turnsTurnsHottestHard throttle (Snapdragon) or Android status (POCO)Decode, tok/sFirst token, s, median (longest)
Snapdragon, phone D30 s29 in 40.7 minXO 64.2 °Cnone in 1,876 samples12.84.8 (119.3)
Snapdragon, phone D5 s31 in 30.4 minXO 73.7 °Cnone in 1,45313.34.8 (119.4)
Snapdragon, phone Dnone34 in 30.6 minXO 76.4 °Cnone in 1,709, all eleven switches read12.74.9 (119.4)
Snapdragon, phone Anone, 1st of 3 back to back34 in 29.4 minXO 75.4 °Cnone in 1,512, all eleven read12.94.9 (99.9)
Snapdragon, phone Anone, 2nd of 334 in 33.5 minXO 77.6 °Cnone in 1,71011.45.3 (119.8)
Snapdragon, phone Anone, 3rd of 334 in 34 minXO 77.9 °Cnone in 1,73711.25.3 (119.8)
MediaTek POCO, charging30 s17 in 30.9 minCPU 73.8 °CAndroid status 0 throughout8.23.2 (108.4)
MediaTek POCO, chargingnone34 in 42.8 minCPU 85.4 °CAndroid status 0 throughout8.16.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.

PhoneWriting, tok/s, 4 then 2 threadsFirst token, s, 4 then 2 threads
Snapdragon10.04, 10.3; 8.94, 9.16101 to 103; 138 to 139
MediaTek6.77, 7.22; 4.86, 5.07128 to 140; 249 to 278
ThreadsReading the prompt, normallyWith six cores out of useWriting, normallyWith six cores out of useTokens compared
1235.7 s, 268.2 s, 268.8 s416 s, 417.4 s6.52, 6.54 tok/s3.378, 2.92 tok/s15 to 400, 10 to 400*
2140.7 s, 140.7 s, 140.9 s, 141 s265.4 s9.93, 10.06 tok/s3.757, 3.738 tok/s20 to 400, 18 to 400
3146.8 s, 146.9 s, 148.4 s290.8 s, 292.5 s13.11, 13.05 tok/s0.542, 0.552 tok/s14 to 172, 8 to 172*
4102 s, 102.3 s, 102.6 s285.4 s10.16 tok/s0.28 tok/s23 to 105
684.3 s, 84.5 s362.6 s15.78 tok/s0.111 tok/s10 to 37
864.4 s, 64.7 s362.1 s12.42 tok/s0.064 tok/s12 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.

Keep reading

All research
  • 9 of 9

    models were fastest with NPU prefill

    OpenWeights, 2 to 10 November 2026

    Prefill and decode on CPU, GPU and NPU

    A model reads the prompt (prefill), then writes the reply a token at a time (decode). Nine models on a Snapdragon 8 Elite Gen 5, with each job on the CPU, the GPU or the NPU, the chip built for AI.

  • 42%

    of 24 real tasks with Qwen3.6 on an RTX 4090, against 92% on Copilot Pro+ Auto

    GGUF, October 2026

    When is local Copilot worth it?

    The same Copilot CLI agent on the same real coding tasks: downloadable models running locally on an RTX 4090 and a MacBook Air M5, against hosted Copilot.

  • 8 agents

    in a simulated coding workload, at a 4.9 s wait per turn on vLLM with no errors. At 16, waits reach 16.5 s

    Hardware, 23 and 24 September 2026

    How many coding agents can one RTX 4090 serve?

    Qwen3.8-27B on one rented RTX 4090, through five engine setups, rising load and real coding agents.