perf: per-layer tempfile round trip and autocommit SQLite dominate MLX inference CPU #12
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Toilville/forge-kv-cache#12
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Measured from Toilville/toilville_rituals while profiling
ritual_enginefor #355 there (harness: toilville_rituals PR #548,tool/profiling/). The setup was v0.1.6 (3fa4564) underritual_engine'smlx_smoketest: one 64-token greedy Qwen3-0.6B-4bit run on an M5 Max, release build, Time Profiler at 1 ms.What the profile shows
78% of the process's CPU samples have a
forge_kv_cacheframe on the stack. The heaviest parts:sqlite3_stepForgeKvSession::enqueue_layer_kvunixSync(SQLite fsync)mlx::core::save_safetensorsload_qwen3), for comparisonCharged to leaf frames, about three-quarters of the samples are kernel file syscalls:
open,pwrite,fsync,unlinkandunlinkat.Why
Qwen3-0.6B has 28 layers, so a 64-token run enqueues about 1,800 layer deltas. Each delta goes through three steps.
encode_kv_delta(mlx_cache.rs) creates a tempfile,save_safetensorsinto it, reads it back withstd::fs::read, and deletes it. That is 4–5 syscalls plus a file create and unlink per layer per token.enqueue_layer_kvis documented as non-blocking, but this happens before the channel send.allocate_block(store.rs), writes the bytes again to the tensors dir withstd::fs::write.insert_block_rowandrecord_event. The connection uses SQLite's default rollback journal, and nothing setsjournal_modeorsynchronous. So each insert creates, syncs and deletes a-journalfile: about 3,600 synced transactions per 64-token run.Suggested direction
None of this has been tried yet; each item is a starting point, not a measured fix.
as_slicebytes directly). This takes the tempfile round trip off the generating thread.PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;.