The last piece of #1192, re-scoped out of #1197 because it needs a design of its own.
Every GGML block format now serves from the mmap (#1189/#1192), but ternary cannot: the loader repacks stock I2_S (GROUP_128/GROUP_64) into the sequential BITNET_B1_58 layout at load (#1140), and a repacked copy cannot page from the file it no longer matches — so BitNet payloads heap-stage (~0.25 B/weight; a 2B4T is ~500 MB of heap, over the default ART cap).
Sketch
- First load under
MAPPED: repack as today, then write the BITNET_B1_58 bytes to a sidecar (Android: app-files dir; JVM: next to the model or a cache dir) — <model>.b158 keyed by source path + size + mtime (or a content hash of the header) for invalidation.
- Later loads: map the sidecar, serve tensors as
BufferPackedTensorData-style views (the ternary LUT kernel is already row-major by construction — hs_ml_ternary_neon reads [N][K/4] row bytes — so no new C; only a direct-buffer JNI entry + FFM binding for skainet_ternary_f32_gemv and the trailer-scale question: keep the per-tensor FP32 scale in the sidecar next to each payload).
StorageCapabilities.mappedServableEncodings gains BITNET_B1_58 only on platforms where the sidecar mechanism exists; the planner then budgets BitNet weights against the page cache — MappedBudgetPlanTest currently pins TQ2_0/ternary as heap-charged and moves with this.
- NeoGPU-converter files (already SEQUENTIAL) could map directly minus the scale trailer — worth handling as the no-repack fast case.
Decisions needed before implementing: sidecar location & eviction policy on Android (files dir vs cacheDir — the OS may clear cacheDir), invalidation key, and whether the write happens eagerly at load or lazily per tensor.
The last piece of #1192, re-scoped out of #1197 because it needs a design of its own.
Every GGML block format now serves from the mmap (#1189/#1192), but ternary cannot: the loader repacks stock I2_S (GROUP_128/GROUP_64) into the sequential
BITNET_B1_58layout at load (#1140), and a repacked copy cannot page from the file it no longer matches — so BitNet payloads heap-stage (~0.25 B/weight; a 2B4T is ~500 MB of heap, over the default ART cap).Sketch
MAPPED: repack as today, then write theBITNET_B1_58bytes to a sidecar (Android: app-files dir; JVM: next to the model or a cache dir) —<model>.b158keyed by source path + size + mtime (or a content hash of the header) for invalidation.BufferPackedTensorData-style views (the ternary LUT kernel is already row-major by construction —hs_ml_ternary_neonreads[N][K/4]row bytes — so no new C; only a direct-buffer JNI entry + FFM binding forskainet_ternary_f32_gemvand the trailer-scale question: keep the per-tensor FP32 scale in the sidecar next to each payload).StorageCapabilities.mappedServableEncodingsgainsBITNET_B1_58only on platforms where the sidecar mechanism exists; the planner then budgets BitNet weights against the page cache —MappedBudgetPlanTestcurrently pins TQ2_0/ternary as heap-charged and moves with this.Decisions needed before implementing: sidecar location & eviction policy on Android (files dir vs cacheDir — the OS may clear cacheDir), invalidation key, and whether the write happens eagerly at load or lazily per tensor.