fix(io): implement createRandomAccessSource on Android — streaming loads instead of full-file OOM (#922) - #924
Merged
Conversation
…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>
aharakal
approved these changes
Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
createRandomAccessSourceunconditionally returnednullon 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 withOutOfMemoryErroron the ART heap (256 MB cap, 512 MB withlargeHeap) before tensors are even built — full failure analysis and measurements in #922.How
AndroidRandomAccessSourceinskainet-io-coreandroidMain (the module already declares the android target; the source set is new). It mirrorsJvmRandomAccessSource:RandomAccessFile+ positionalFileChannel.read, which doesn't touch the shared file pointer — thread-safe for concurrent reads per theRandomAccessSourcecontract, and available since API 1.TokenizerFactory.fromGguf(fields)needsStreamingGGUFReader.fields, which needs aRandomAccessSource— so this also restores building a tokenizer from GGUF metadata on Android.Tests
withHostTest {}— first module in the repo to use it; the Gradle build already warns other modules to adopt it).AndroidRandomAccessSourceTest(12 cases) mirrorsPosixPreadRandomAccessSourceTestand 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)compileAndroidMainof io-gguf / io-safetensors / io-onnx — greenjvmTestof all four io modules — greenCloses #922
🤖 Generated with Claude Code