Repository navigation
Host RAM: keep lookup-only tables (token and per-layer embeddings) quantized and memory-mapped instead of resident copies (saves ~1 GB on Qwen3.5-2B, ~2.9 GB on Gemma 4 E2B) #807
Copy link
Copy link
Open
Description
On 16 GB Ryzen AI laptops, most of FLM's host-RAM footprint is tables that are only read one row per token. Two models show it most clearly:
Model (--ctx-len 4096) |
flm.exe private | Largest private allocations | What they are |
|---|---|---|---|
qwen3.5:2b |
3,829 MiB | 970, 517 MiB | a bf16 vocab × hidden table (248,320 × 2,048 × 2 bytes = 970.0 MiB exactly); lm_head.weight (I8, 515.3 MiB in model.q4nx) |
gemma4-it:e2b |
6,616 MiB | 2,240, 384, 280, 240 MiB | model.per_layer_token_embd.weight (I8 [262144, 8960]); model.embed_tokens.weight (I8); per_layer_token_embd.weight.scale (F32); lm_head.weight (I8) |
With gemma4-it:e2b loaded, the PC's available memory fell from 6,477 MiB to 479 MiB, so a 16 GB machine is close to paging with nothing else open.
What the code does today
- Gemma 4:
gemma4e_npubuildsembedding_q8_0for bothmodel.embed_tokensandmodel.per_layer_token_embd. Eachforward(idx)dequantizes a single row. Butinit_weightscopies the whole int8 table and its F32 scales into ownedbuffer<>s throughQ4NX::load_weights(astd::ifstreamread). So all 2,952 MiB stays resident, although a token touches about 12 KB of it. - Qwen3.5:
qwen3_5vl_npu.hppincludesmodules/embedding.hpp. ItsEmbeddingresizes a fullvocab_size × d_modelbf16vdtypeand copies one row perforward, and the 970 MiB block is exactly that size. Qwen3.5-2B hastie_word_embeddings: true, and itsmodel.q4nxhas noembed_tokenstensor, onlylm_head.weight(I8 [7760, 8, 8704]). So the 970 MiB bf16 table looks like a dequantized copy of weights FLM already holds.
Proposal, in order of payoff
- Map the lookup-only tensors from
model.q4nxread-only, instead of copying them. That meansCreateFileMappingW+MapViewOfFile(FILE_MAP_READ)on Windows, andmmap(PROT_READ, MAP_PRIVATE)on Linux.embedding_q8_0'stensor_2dviews would point into the mapping, not into owned buffers.- The pages are clean and file-backed, so only the rows of tokens actually seen stay resident. Under pressure the OS can drop them and read them back.
- For Gemma 4 E2B, that's about 2.9 GiB less private memory: a few 4 KB pages per distinct token, so tens of MB for a long conversation instead of 2,952 MiB.
- For tied-embedding models on the bf16
Embeddingpath (Qwen3.5), don't materialize the vocab × d bf16 table. Dequantize the one row per token from the int8 tied weights, asembedding_q8_0already does for Gemma 4. That saves 970 MiB on Qwen3.5-2B, and the same holds for any other tied-embedding model on that path. - A question, not a request: is the private
lm_head.weightcopy (515 MiB on Qwen3.5-2B, 240 MiB on Gemma 4 E2B) still used once the weights are on the NPU?- If it's only staging for the upload, freeing it (or mapping it per item 1) saves that too.
- If the row lookup in item 2 reads from it, it should stay, or be mapped.
- Smaller, independent of mapping: store
per_layer_token_embd.weight.scaleas bf16 or fp16 instead of F32. That's 280 → 140 MiB for Gemma 4 E2B.
The cost is a page fault the first time a cold row is used. Right after load the file is usually still in the standby cache, so that fault doesn't touch the disk.
Related
#709 asks for weight BOs not to be MAP_LOCKED on Linux, so cold MoE experts can page. This issue is about the CPU-side lookup tables on any OS, which aren't NPU buffers at all.
Environment and method
- FLM: v1.0.7.
- PC: Windows 11 24H2 (26100), Ryzen AI 7 350 (XDNA 2), 16 GB, NPU driver 32.0.20102.3930.
- Method: one
flm serve <model> --ctx-len 4096at a time, nothing else on the NPU, after one short chat request. Three readings:- flm.exe's private bytes and its largest private regions, from
VirtualQueryExover the process; - NPU memory, from the "GPU Process Memory" shared-usage counter for the NPU adapter;
- available memory, from
\Memory\Available MBytes.
- flm.exe's private bytes and its largest private regions, from
- The tensor names, dtypes and shapes come from the
model.q4nxheaders.
Drafted with an AI assistant; the measurements and header readings above are from the runs described.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity