Skip to content

fix(io): implement createRandomAccessSource on Android — streaming loads instead of full-file OOM (#922) - #924

Merged
michalharakal merged 1 commit into
developfrom
fix/android-random-access-source-922
Aug 10, 2026
Merged

michalharakal merged 1 commit into
developfrom
fix/android-random-access-source-922

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

What

createRandomAccessSource unconditionally returned null on Android in io-gguf, io-safetensors and io-onnx, which routed every model load through the legacy materialise-the-whole-file path. On a physical phone a 138 MiB GGUF then dies with OutOfMemoryError on the ART heap (256 MB cap, 512 MB with largeHeap) before tensors are even built — full failure analysis and measurements in #922.

How

  • New AndroidRandomAccessSource in skainet-io-core androidMain (the module already declares the android target; the source set is new). It mirrors JvmRandomAccessSource: RandomAccessFile + positional FileChannel.read, which doesn't touch the shared file pointer — thread-safe for concurrent reads per the RandomAccessSource contract, and available since API 1.
  • The three android factory actuals now open it, byte-for-byte parallel to their JVM counterparts (same guards, same fall-back-to-null-on-error behavior).
  • Side effect worth naming: TokenizerFactory.fromGguf(fields) needs StreamingGGUFReader.fields, which needs a RandomAccessSource — so this also restores building a tokenizer from GGUF metadata on Android.

Tests

  • Enabled android host-side unit tests for io-core (withHostTest {} — first module in the repo to use it; the Gradle build already warns other modules to adopt it).
  • AndroidRandomAccessSourceTest (12 cases) mirrors PosixPreadRandomAccessSourceTest and adds a multi-threaded concurrent positional-read check.

Verified

  • :skainet-io:skainet-io-core:testAndroidHostTest — 12/12 green (commonTest suites also run under the new lane, green)
  • compileAndroidMain of io-gguf / io-safetensors / io-onnx — green
  • jvmTest of all four io modules — green
  • The same implementation (app-side copy) has been running in a production Android app since we hit the bug: the OOM is gone and the same phone that crashed now loads SmolLM2-135M Q8_0 reliably.

Closes #922

🤖 Generated with Claude Code

…ads instead of full-file OOM

The Android actuals in io-gguf, io-safetensors and io-onnx unconditionally
returned null, so every model load fell back to materialising the whole file
on the ART heap; a 138 MiB GGUF OOMs a real device (256/512 MB heap cap)
before tensors are even built.

Add AndroidRandomAccessSource to skainet-io-core androidMain — a mirror of
JvmRandomAccessSource over RandomAccessFile + positional FileChannel.read
(thread-safe, available since API 1) — and point all three android factory
actuals at it. This makes the streaming GGUF/SafeTensors readers reachable
on Android and un-breaks TokenizerFactory.fromGguf, which needs
StreamingGGUFReader.fields.

Enable android host tests for io-core (withHostTest, first in the repo) and
cover the read contract: prefix/offset/EOF reads, buffer-offset windows,
bounds and closed-channel failures, close idempotency, and concurrent
positional reads from multiple threads.

Verified: :skainet-io:skainet-io-core:testAndroidHostTest green (12 new
tests), androidMain of all three io modules compiles, jvmTest of all four
io modules green.

Closes #922

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@michalharakal
michalharakal merged commit fa97f4a into develop Aug 10, 2026
11 checks passed
@michalharakal
michalharakal deleted the fix/android-random-access-source-922 branch August 10, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

createRandomAccessSource returns null on Android: full-file heap load OOMs on a 138 MiB GGUF (working ~40-line fix included)

2 participants