Skip to content

Commit 5c42486

Browse files
committed
Introduced ModelReader and TensorInfo in skainet-io-core as a common abstraction.
- Refactored `MemoryChunk` to support non-copying `slice` views for efficient memory mapping. - Added dependency on `skainet-io-core` in `skainet-io-onnx`. - Fixed alignment parsing and improved tensor data handling in `GGUFReader` tests. - Increased test heap size to 8GB to support large model tests.
1 parent 7bb7d73 commit 5c42486

8 files changed

Lines changed: 105 additions & 7 deletions

File tree

build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ subprojects {
2929
jvmToolchain(21)
3030
}
3131
}
32+
33+
tasks.withType<Test>().configureEach {
34+
maxHeapSize = "8192m"
35+
}
3236
}
3337

3438
kover {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package sk.ainet.io
2+
3+
interface MemoryChunk {
4+
val size: Long
5+
6+
fun readByte(offset: Long): Byte
7+
fun readBytes(offset: Long, length: Int): ByteArray
8+
9+
fun slice(offset: Long, length: Long): MemoryChunk
10+
}
11+
12+
class ByteArrayMemoryChunk(
13+
private val data: ByteArray,
14+
private val offset: Int = 0,
15+
override val size: Long = data.size.toLong()
16+
) : MemoryChunk {
17+
18+
override fun readByte(offset: Long): Byte {
19+
require(offset >= 0 && offset < size) { "Offset out of bounds: $offset" }
20+
return data[this.offset + offset.toInt()]
21+
}
22+
23+
override fun readBytes(offset: Long, length: Int): ByteArray {
24+
require(offset >= 0 && offset + length <= size) { "Range out of bounds: $offset + $length" }
25+
return data.copyOfRange(this.offset + offset.toInt(), this.offset + offset.toInt() + length)
26+
}
27+
28+
override fun slice(offset: Long, length: Long): MemoryChunk {
29+
require(offset >= 0 && offset + length <= size) { "Slice out of bounds: $offset + $length" }
30+
return ByteArrayMemoryChunk(data, this.offset + offset.toInt(), length)
31+
}
32+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package sk.ainet.io
2+
3+
import sk.ainet.lang.tensor.Shape
4+
import sk.ainet.lang.types.DType
5+
import sk.ainet.lang.tensor.data.TensorData
6+
7+
interface ModelReader : AutoCloseable {
8+
val metadata: Map<String, Any>
9+
val tensors: Map<String, TensorInfo>
10+
11+
suspend fun loadTensor(name: String): TensorData<*, *>
12+
}
13+
14+
data class TensorInfo(
15+
val name: String,
16+
val shape: Shape,
17+
val dtype: String,
18+
val offset: Long,
19+
val size: Long, // Size in bytes
20+
val extra: Map<String, Any> = emptyMap()
21+
)
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package sk.ainet.io.gguf
2+
3+
import sk.ainet.io.ModelReader
4+
import sk.ainet.io.TensorInfo
5+
import sk.ainet.lang.tensor.data.TensorData
6+
import sk.ainet.lang.tensor.Shape
7+
8+
class GGUFModelReader : ModelReader {
9+
override val metadata: Map<String, Any> = mutableMapOf()
10+
override val tensors: Map<String, TensorInfo> = mutableMapOf()
11+
12+
override suspend fun loadTensor(name: String): TensorData<*, *> {
13+
val info = tensors[name] ?: error("Tensor $name not found")
14+
// Implementation will use MemoryMappedFileChunk or similar to slice the data
15+
TODO("Not yet implemented: streaming tensor loading for GGUF")
16+
}
17+
18+
override fun close() {
19+
// Close underlying resources (e.g. mapped file)
20+
}
21+
}

skainet-io/skainet-io-gguf/src/commonMain/kotlin/sk/ainet/io/gguf/GGUFReader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class GGUFReader(source: Source, private val loadTensorData: Boolean = true) {
133133
offs = newOffs
134134
val newAlign = fields["general.alignment"]
135135
if (newAlign != null && newAlign.types == listOf(GGUFValueType.UINT32)) {
136-
alignment = newAlign.parts.last()[0] as Int
136+
alignment = (newAlign.parts.last()[0] as UInt).toInt()
137137
}
138138
val padding = offs % alignment
139139
if (padding != 0) {

skainet-io/skainet-io-gguf/src/jvmTest/kotlin/sk/ainet/io/gguf/GGUFMinimalSampleTest.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,12 @@ class GGUFMinimalSampleTest {
7474
GGMLQuantizationType.F32 -> ctx.fromFloatArray<FP32, Float>(shape, FP32::class, (raw as List<Float>).toFloatArray())
7575
GGMLQuantizationType.I32 -> ctx.fromIntArray<Int32, Int>(shape, Int32::class, (raw as List<Int>).toIntArray())
7676
GGMLQuantizationType.I8 -> ctx.fromByteArray<Int8, Byte>(shape, Int8::class, (raw as List<Byte>).toByteArray())
77-
GGMLQuantizationType.F16 -> {
78-
// FP16 raw storage is not supported by reader; we mapped to FP16 class as a placeholder.
79-
// As a minimal sample, store as bytes to keep the payload without decoding.
80-
ctx.fromByteArray<Int8, Byte>(shape, Int8::class, (raw as List<UByte>).toUByteArray().toByteArray())
77+
else -> {
78+
// All other quantized/native types: keep raw bytes as Int8 tensor for this minimal sample
79+
// We use a flat shape to match the byte count if it doesn't match the element count (e.g. for F16)
80+
val byteData = (raw as List<UByte>).toUByteArray().toByteArray()
81+
ctx.fromByteArray<Int8, Byte>(Shape(byteData.size), Int8::class, byteData)
8182
}
82-
// All other quantized/native types: keep raw bytes as Int8 tensor for this minimal sample
83-
else -> ctx.fromByteArray<Int8, Byte>(shape, Int8::class, (raw as List<UByte>).toUByteArray().toByteArray())
8483
}
8584
created[rt.name] = dslTensor
8685
}

skainet-io/skainet-io-onnx/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ kotlin {
5353
dependencies {
5454
implementation(project(":skainet-lang:skainet-lang-core"))
5555
implementation(project(":skainet-compile:skainet-compile-dag"))
56+
implementation(project(":skainet-io:skainet-io-core"))
5657
implementation(libs.kotlinx.io.core)
5758
implementation(libs.pbandk.runtime)
5859
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package sk.ainet.io.onnx
2+
3+
import sk.ainet.io.ModelReader
4+
import sk.ainet.io.TensorInfo
5+
import sk.ainet.lang.tensor.data.TensorData
6+
7+
public class OnnxModelReader : ModelReader {
8+
override val metadata: Map<String, Any> = emptyMap<String, Any>()
9+
override val tensors: Map<String, TensorInfo> = emptyMap<String, TensorInfo>()
10+
11+
override suspend fun loadTensor(name: String): TensorData<*, *> {
12+
val info = tensors[name] ?: error("Tensor $name not found")
13+
// Implementation will handle external_data or embedded rawData offsets
14+
TODO("Not yet implemented: streaming tensor loading for ONNX")
15+
}
16+
17+
override fun close() {
18+
// Close underlying resources
19+
}
20+
}

0 commit comments

Comments
 (0)