Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions skainet-backends/skainet-backend-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ kotlin {
macosArm64()
linuxX64()
linuxArm64()
androidNativeArm32()

jvm()

Expand Down Expand Up @@ -96,5 +97,9 @@ kotlin {
val linuxArm64Main by getting {
dependsOn(linuxMain)
}

val androidNativeArm32Main by getting {
dependsOn(nativeMain)
}
}
}
5 changes: 4 additions & 1 deletion skainet-backends/skainet-backend-cpu/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
POM_ARTIFACT_ID=skainet-backend-cpu
POM_NAME=skainet neural network scripting API

kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.mpp.applyDefaultHierarchyTemplate=false
# DirectCpuExecutionContext (eager execution, no backend-native cinterop
# kernels here) is pure-Kotlin — safe to build for Android-native too.
skainet.targets=jvm,js,wasmJs,wasmWasi,apple,linux,androidNative
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package sk.ainet.exec.tensor.ops

import sk.ainet.backend.api.kernel.KernelRegistry
import sk.ainet.exec.kernel.ScalarKernelProvider
import sk.ainet.lang.tensor.data.TensorDataFactory
import sk.ainet.lang.tensor.ops.TensorOps

/**
* Portable fallback — identical to [PlatformCpuOpsFactory.linux.kt]'s, no
* accelerated (NEON cinterop) path yet for this target. See
* skainet-backend-native-cpu's own doc comments: its NEON kernels are built
* for Linux aarch64 (glibc, the SL2610 board target), not Android's bionic
* 32-bit ABI — wiring a real accelerated path here is separate, larger work.
*/
internal actual fun platformDefaultCpuOpsFactory(): (TensorDataFactory) -> TensorOps {
// Non-JVM has no ServiceLoader; register the scalar packed-quant kernels
// (Q4_K/Q6_K/Q5_1/Q5_0/Q8_0/Q4_0) so DefaultCpuOpsBase can dispatch them.
KernelRegistry.register(ScalarKernelProvider)
return { factory -> DefaultCpuOps(factory) }
}
1 change: 1 addition & 0 deletions skainet-compile/skainet-compile-dag/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ kotlin {
macosArm64 ()
linuxX64 ()
linuxArm64 ()
androidNativeArm32()

jvm()

Expand Down
1 change: 1 addition & 0 deletions skainet-compile/skainet-compile-opt/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ kotlin {
macosArm64 ()
linuxX64 ()
linuxArm64 ()
androidNativeArm32()

jvm()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package sk.ainet.io

import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.alloc
import kotlinx.cinterop.convert
import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr
import kotlinx.cinterop.toKString
import kotlinx.cinterop.usePinned
import platform.posix.O_RDONLY
import platform.posix.errno
import platform.posix.fstat
import platform.posix.pread
import platform.posix.stat
import platform.posix.strerror

/**
* `androidNativeArm32`'s [RandomAccessSource], backed by POSIX `pread(2)`.
*
* Logic-identical to [PosixPreadRandomAccessSource] (native64Main) — `.convert()`
* already targets whatever width `pread`'s generated signature expects — but kept
* as a separate file/source set rather than shared, because androidNativeArm32's
* `ssize_t`/`off_t` are 32-bit where every other native target is 64-bit: putting
* both in one shared native source set fails Kotlin/Native's metadata compile
* ("numbers with different bit widths"), see native64Main's own doc comment and
* the `native64Targets` split in skainet-io-core's build.gradle.kts.
*
* 32-bit `off_t` without `_FILE_OFFSET_BITS=64` caps addressable file size at
* 2 GiB — acceptable for this target's real workloads (GGUF/safetensors reads
* on-device); large-file (100+ GB) loading stays a native64/JVM concern.
*/
@OptIn(ExperimentalForeignApi::class)
public class Posix32PreadRandomAccessSource private constructor(
private val fd: Int,
override val size: Long,
) : RandomAccessSource {

private var closed = false

override fun readAt(position: Long, length: Int): ByteArray {
require(position >= 0) { "Position must be non-negative: $position" }
require(length >= 0) { "Length must be non-negative: $length" }
require(position + length <= size) {
"Read beyond end of file: position=$position, length=$length, size=$size"
}
if (length == 0) return ByteArray(0)

val buffer = ByteArray(length)
val bytesRead = readAt(position, buffer, 0, length)
return if (bytesRead < length) buffer.copyOf(bytesRead) else buffer
}

override fun readAt(position: Long, buffer: ByteArray, offset: Int, length: Int): Int {
require(position >= 0) { "Position must be non-negative: $position" }
require(offset >= 0) { "Offset must be non-negative: $offset" }
require(length >= 0) { "Length must be non-negative: $length" }
require(offset + length <= buffer.size) {
"Buffer overflow: offset=$offset, length=$length, buffer.size=${buffer.size}"
}
check(!closed) { "Source is closed" }
if (length == 0) return 0

return buffer.usePinned { pinned ->
var totalRead = 0
while (totalRead < length) {
val n = pread(
fd,
pinned.addressOf(offset + totalRead),
(length - totalRead).convert(),
(position + totalRead).convert(),
)
if (n < 0) {
val cause = strerror(errno)?.toKString() ?: "errno=$errno"
error("pread failed at offset ${position + totalRead}: $cause")
}
if (n == 0) break // EOF
totalRead += n
}
totalRead
}
}

override fun close() {
if (closed) return
closed = true
platform.posix.close(fd)
}

public companion object {
/**
* Open [path] for read-only random access. Returns `null` if the file
* cannot be opened or stat'd, or exceeds the 32-bit `off_t` 2 GiB cap —
* matching [PosixPreadRandomAccessSource]'s contract so callers can fall
* back to the legacy sequential reader.
*/
public fun open(path: String): Posix32PreadRandomAccessSource? = memScoped {
val fd = platform.posix.open(path, O_RDONLY)
if (fd < 0) return@memScoped null
val st = alloc<stat>()
if (fstat(fd, st.ptr) != 0) {
platform.posix.close(fd)
return@memScoped null
}
val size = st.st_size
if (size < 0 || size > Int.MAX_VALUE.toLong()) {
// Negative == 32-bit off_t overflow; >2GiB is unreachable on a
// true 32-bit off_t anyway, but guard explicitly for clarity.
platform.posix.close(fd)
return@memScoped null
}
Posix32PreadRandomAccessSource(fd, size)
}
}
}
37 changes: 36 additions & 1 deletion skainet-io/skainet-io-gguf/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ kotlin {
macosArm64 ()
linuxX64 ()
linuxArm64 ()
// 32-bit: RandomAccessSourceFactory needs its own actual (see
// androidNativeArm32Main) — pread's ssize_t/off_t are Int here vs Long on
// every other native target, so it can't share nativeMain's actual with
// them (see the native64Main split below and skainet-io-core's own).
androidNativeArm32()

js {
browser()
Expand All @@ -50,17 +55,47 @@ kotlin {
nodejs()
}

// 64-bit-only intermediate: holds the pread-based RandomAccessSourceFactory
// actual, whose ssize_t/off_t widths are uniform (Long) across every native
// target EXCEPT androidNativeArm32 (which gets its own actual instead — see
// androidNativeArm32Main). Mirrors skainet-io-core's identical split.
val native64Targets = listOf("iosArm64", "iosSimulatorArm64", "macosArm64", "linuxX64", "linuxArm64")
applyDefaultHierarchyTemplate()

sourceSets {
val nativeMain by getting
val commonMain by getting {
dependencies {
implementation(libs.kotlinx.io.core)
implementation(project(":skainet-lang:skainet-lang-core"))
implementation(project(":skainet-io:skainet-io-core"))
}
}
// GgufExportFacade (host-side model export/compile tooling) needs
// skainet-compile-dag, which doesn't build for androidNativeArm32
// (32-bit) — see PosixPreadRandomAccessSource's own split for the
// identical reasoning. Model export isn't an on-device concern anyway,
// so it lives here instead of commonMain: jvm/android/native64 (every
// target except androidNativeArm32) get it.
val exportMain by creating {
dependsOn(commonMain)
dependencies {
implementation(project(":skainet-compile:skainet-compile-core"))
implementation(project(":skainet-compile:skainet-compile-dag"))

}
}
val native64Main by creating {
dependsOn(nativeMain)
dependsOn(exportMain)
}
native64Targets.forEach { t -> getByName("${t}Main").dependsOn(native64Main) }
val jvmMain by getting { dependsOn(exportMain) }
val androidMain by getting { dependsOn(exportMain) }
// js/wasmJs/wasmWasi: skainet-compile-dag supports these too — only
// androidNativeArm32 is actually excluded from exportMain.
val jsMain by getting { dependsOn(exportMain) }
val wasmJsMain by getting { dependsOn(exportMain) }
val wasmWasiMain by getting { dependsOn(exportMain) }
val commonTest by getting {
dependencies {
implementation(libs.kotlin.test)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package sk.ainet.io.gguf

import sk.ainet.io.Posix32PreadRandomAccessSource
import sk.ainet.io.RandomAccessSource

/**
* `androidNativeArm32` implementation of [createRandomAccessSource] using POSIX
* `pread(2)` via [Posix32PreadRandomAccessSource] — the 32-bit-`off_t` sibling of
* [sk.ainet.io.PosixPreadRandomAccessSource] (native64Main), kept as a separate
* actual because this target can't share a source set with the 64-bit natives
* (mixed `ssize_t`/`off_t` widths fail Kotlin/Native's metadata compile).
*
* Returns `null` if the file cannot be opened, matching every other actual's
* contract so callers fall back to the legacy sequential reader.
*/
public actual fun createRandomAccessSource(filePath: String): RandomAccessSource? =
Posix32PreadRandomAccessSource.open(filePath)
1 change: 1 addition & 0 deletions skainet-lang/skainet-lang-dag/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ kotlin {
macosArm64()
linuxX64()
linuxArm64()
androidNativeArm32()

jvm()

Expand Down
Loading