↰ Learning hub  ·  ← Back to "How Training Works"
Advanced · guide 6 of the set

Advanced Edge Internals

The deep end. Now that the fundamentals are solid, here's what really happens inside a fast on-device runtime: smarter quantization, squeezing the CPU, the reality of NPUs, and serving many requests at once. This is EdgeLM's actual engineering frontier.

0. A one-line recap to build on

From guide 1: generating a token means reading the model's weights out of memory, so decode speed is usually limited by memory bandwidth, not math. Every technique here is another angle on the same fight — move fewer bytes, waste no compute, keep every core busy. If that sentence makes sense, you're ready.

1. Beyond Q4_0: K-quants & mixed precision

Guide 1 explained Q4_0: chop weights into blocks, store each as a 4-bit integer with one shared scale. It's simple but blunt — every weight in a block is treated as equally important, and it uses just one scale.

First, a cousin: Q4_0 vs Q4_1

Before the K-quants, the simplest upgrade is Q4_1. Both are 4-bit and block-based; the difference is how each block's 16 levels are placed.

Q4_0 is symmetric: it stores just a scale per block and assumes the weights are centred on zero, so w ≈ q × scale with a signed integer q. Q4_1 is asymmetric: it stores a scale and a minimum (offset), so w ≈ q × scale + min with an unsigned q. That extra min lets the levels line up with a lopsided block instead of forcing them around zero — more accurate for skewed weights, at the cost of a bit more storage (~5 bits/weight vs ~4.5).

Q4_0 — symmetric, one scale, centred on 0: 0 w = q × scale Q4_1 — asymmetric, scale + offset, shifted to fit the data: min w = q × scale + min
Q4_0 spreads its levels around zero; Q4_1 adds an offset so the same 16 levels can hug a lopsided block.

Newer schemes do better still. Q8_0 keeps 8 bits — bigger and more accurate, often used for the KV cache. But the clever family is the K-quants (you'll see names like Q4_K_M, Q5_K_M, Q6_K).

What K-quants do differently

Two upgrades. First, sub-blocks with their own scales: instead of one scale per big block, they add finer-grained scales (and offsets) so the rounding fits the local data much better. Second, mixed precision: not all weights matter equally, so the important ones get more bits and the rest get fewer. The _M ("medium") in Q4_K_M refers to which layers are bumped up.

smaller / faster → ← more accurate Q4_0blunt Q4_K_Msweet spot Q6_K Q8_0 FP16 K-quants sit above the line: nearly Q8 quality at close to Q4 size
K-quants (like Q4_K_M) get most of the accuracy of bigger formats while staying near 4-bit size.
Analogy: Q4_0 compresses a photo at one quality setting for the whole image. K-quants spend more bits on the faces and fewer on the flat sky — same file size, better where it matters.
EdgeLM reality: a measured finding from the project is that a math-acceleration library (KleidiAI) gave ~0% decode gain on Q4_0, because decode is bandwidth-bound and the runtime already repacks Q4_0 efficiently. Picking the format (e.g. Q4_K_M vs Q4_0 vs Q8_0 KV) is often a bigger lever than accelerating the math.

2. Squeezing the CPU: repacking & threads

Most phones do inference on the CPU, so wringing more out of it matters. Two levers stand out.

Weight repacking (SIMD-friendly layout)

A CPU core can multiply several numbers in one instruction using SIMD ("single instruction, multiple data"), and modern ARM chips have an i8mm extension for fast 8-bit integer matrix math. But it only pays off if the weights are laid out in exactly the order the instruction wants. So the runtime repacks the quantized weights into that friendly layout up front. This is why extra math libraries can add nothing — the fast path is already taken.

Pinning threads to the big cores

Phone CPUs are big.LITTLE: a few fast "big" cores and several slow, efficient "little" ones. If work drifts onto the little cores, throughput drops. A tuned runtime builds a threadpool pinned to the big cores, picks the best thread count by measuring, and can request a sustained performance mode so the chip doesn't throttle mid-generation.

Threads pinned to the big cores: big big big little little ← keep the work here and repack weights into the layout the SIMD instruction wants: messy order → slow repacked order → fast (i8mm)
Keep the heavy work on fast cores, and pre-arrange weights so the CPU's vector instructions run at full speed.
EdgeLM reality: pinned big-core threadpool, cached thread-count sweep, Q8_0 KV cache, and sustained-perf mode are all built — but noted as unmeasured pending an on-device build. On the edge, a change isn't real until it's measured on the actual phone.

3. The NPU reality: QNN and why it's hard

Guide 1 introduced the NPU — a chip built only for neural-net math, the most power-efficient option when it works. Qualcomm's software stack for it is QNN. So why isn't everything on the NPU already?

Because NPUs are rigid by design. Where a CPU runs any code on the fly, an NPU wants the whole model compiled ahead of time into a fixed graph of operations it supports, in a specific quantization format. Three friction points follow:

Operator coverage — if the model uses an operation the NPU doesn't implement, that part falls back to CPU, and the handoffs can erase the win.

Format constraints — the NPU accepts particular quantization layouts, not whatever .gguf you happen to have.

Ahead-of-time compilation — the model must be converted and compiled per chip family, which is real engineering, not a flag you flip.

CPU / GPU runs the model as-isflexible · any op · on the fly NPU (QNN) compile → fixed graphrigid · supported ops only unsupported op?falls back to CPU — costly easy to run,but less efficient
The NPU is efficient but demands an ahead-of-time compiled graph; anything unsupported spills back to the CPU.
EdgeLM reality: the project has device- and accelerator-detection (a /v1/edge/device endpoint) and an honest integration plan, but no QNN backend yet — it's groundwork. That candor is the point: "supports NPU" is a long road, not a checkbox.

4. Continuous batching

Guide 1's scheduler batches requests so one model serves several at once. But there's a naive way and a smart way.

Static batching: gather a group, run them together, and don't start new work until the whole group finishes. The problem: requests have different lengths, so everyone waits for the slowest one, and the model sits half-idle.

Continuous batching fixes this. The moment any request in the batch finishes, its slot is freed and a waiting request drops in — the batch is refilled every step instead of drained and refilled. The model stays full, and new users don't wait for a whole group to clear.

Static — finished slots sit idle until the whole batch clears: red = wasted idle time Continuous — a new request drops into each freed slot immediately: blue = new work, no gaps
Continuous batching refills freed slots every step, so the model never idles waiting for the slowest request.
Analogy: A ride-share van. Static batching waits until every passenger from one trip is dropped off before picking anyone up. Continuous batching picks up a new rider the instant a seat opens — the van's always full.

5. Why paged-KV makes it possible

Continuous batching needs to add and remove requests from the running batch at any moment — each with its own growing KV cache (guide 1). If each cache were one big fixed block, you couldn't cleanly slot conversations in and out without wasting or fragmenting memory.

Paged-KV is what unlocks it: because each cache is split into small reusable pages, a finishing request hands its pages back to the pool and an incoming request grabs what it needs. The scheduler and paged-KV are two halves of the same capability.

EdgeLM reality: paged-KV was built in increments and integrated with a batched service — the groundwork continuous batching stands on. The pieces connect: paged memory below, a batching scheduler above.

6. Where EdgeLM actually is

Pulling the honest status together, so you can tell built-and-proven from work-in-progress:

PieceStatus
mmap sharingBuilt & proven on device — "one copy in RAM" across apps.
paged-KVBuilt in increments, integrated with a batched service.
CPU perf leversBuilt (big-core threadpool, Q8_0 KV, thread sweep, sustained mode) — unmeasured, needs on-device build.
spec. decodingGated GPU-only; net loss on CPU.
GPU backendIntegrated engine; unsupported on Mali-G615 — needs a flagship Adreno.
NPU / QNNDetection + plan only; no QNN backend yet.
The recurring lesson: on the edge, an idea isn't done when it compiles — it's done when it's measured on the real device. Half of good edge work is honest benchmarking.

7. Test yourself

Pick an answer and it'll explain why. Score updates as you go.

1. How do K-quants (like Q4_K_M) beat plain Q4_0?

Finer scales + mixed precision. Important weights get more bits; local scales fit the data better — near-Q8 quality at near-Q4 size.

2. Why can a math-acceleration library add ~0% to decode on Q4_0?

Bandwidth-bound. Faster math doesn't help when you're waiting on memory and the SIMD path is already taken.

3. What is weight "repacking" for?

SIMD-friendly layout. Instructions like i8mm are only fast if the data is pre-arranged in their expected order.

4. Why are threads pinned to the "big" cores?

Keep the heavy work fast. big.LITTLE chips mix fast and slow cores; pinning keeps generation on the fast ones.

5. What makes NPUs hard to use?

Rigid by design. Unsupported ops fall back to CPU, and the model must be converted/compiled per chip — real work, not a flag.

6. How does continuous batching beat static batching?

No idle gaps. As each request finishes, a waiting one drops in, so the model stays full every step.

7. Why does continuous batching depend on paged-KV?

Two halves of one capability. Reusable pages let conversations slot in and out without wasting or fragmenting memory.

Score: 0 / 7

Mini-glossary

K-quants
Quantization schemes (Q4_K_M, Q5_K_M, Q6_K) using finer sub-block scales and mixed precision.
Mixed precision
Giving more bits to the weights that matter and fewer to the rest.
Q8_0
An 8-bit format — larger and more accurate; often used for the KV cache.
SIMD / i8mm
CPU instructions that do many multiplications at once; i8mm is ARM's fast 8-bit matrix extension.
Repacking
Rearranging quantized weights into the exact layout a SIMD instruction expects.
big.LITTLE
Phone CPUs with a few fast "big" cores and several slow, efficient "little" ones.
NPU / QNN
A neural-net-only chip; QNN is Qualcomm's software stack for its NPU.
Graph (compiled)
A model converted ahead of time into a fixed set of operations an NPU can run.
Static batching
Running a fixed group of requests together and waiting for all to finish.
Continuous batching
Refilling freed batch slots with waiting requests every step, so the model never idles.
Paged-KV
Storing each request's KV cache in small reusable pages, which enables continuous batching.