Skip to content
Merged
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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- **`LayaModelLoader.load(directory: Path, …)` is a member again.** 0.62.0 had turned the JVM
convenience into an extension function on the common `LayaModelLoader` object; an extension needs
an explicit `import sk.ainet.models.laya.load`, so every existing Kotlin caller of
`LayaModelLoader.load(dir, ctx, exactGelu = true)` stopped compiling against 0.62.0 — the opposite
of the source compatibility 0.62.0 claimed. `LayaModelLoader` is now an `expect` object: the common
`load(files, weights, ctx, …)` is unchanged on every target, and the JVM `actual` adds
`load(directory, ctx, exactGelu, weightFormFor)` as a member, `@JvmOverloads` so that Java callers
get the 0.61 `load(Path, ExecutionContext, boolean)` signature back as well.

## [0.62.0] — 2026-10-17

**Laya on the packed Q8_0 lane.** Against engine **0.61.0** (SKEEP-007, the browser lane:
Expand Down
9 changes: 4 additions & 5 deletions llm-inference/laya/api/jvm/laya.api
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,12 @@ public final class sk/ainet/models/laya/LayaLinear : sk/ainet/lang/nn/Module, sk

public final class sk/ainet/models/laya/LayaModelLoader {
public static final field INSTANCE Lsk/ainet/models/laya/LayaModelLoader;
public final fun load (Ljava/nio/file/Path;Lsk/ainet/context/ExecutionContext;)Lsk/ainet/models/laya/LayaAgent;
public final fun load (Ljava/nio/file/Path;Lsk/ainet/context/ExecutionContext;Z)Lsk/ainet/models/laya/LayaAgent;
public final fun load (Ljava/nio/file/Path;Lsk/ainet/context/ExecutionContext;ZLkotlin/jvm/functions/Function1;)Lsk/ainet/models/laya/LayaAgent;
public final fun load (Lsk/ainet/models/laya/LayaCheckpointFiles;Lkotlin/jvm/functions/Function0;Lsk/ainet/context/ExecutionContext;ZLsk/ainet/lang/memory/plan/WeightForm;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun load$default (Lsk/ainet/models/laya/LayaModelLoader;Lsk/ainet/models/laya/LayaCheckpointFiles;Lkotlin/jvm/functions/Function0;Lsk/ainet/context/ExecutionContext;ZLsk/ainet/lang/memory/plan/WeightForm;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}

public final class sk/ainet/models/laya/LayaModelLoaderKt {
public static final fun load (Lsk/ainet/models/laya/LayaModelLoader;Ljava/nio/file/Path;Lsk/ainet/context/ExecutionContext;ZLkotlin/jvm/functions/Function1;)Lsk/ainet/models/laya/LayaAgent;
public static synthetic fun load$default (Lsk/ainet/models/laya/LayaModelLoader;Ljava/nio/file/Path;Lsk/ainet/context/ExecutionContext;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lsk/ainet/models/laya/LayaAgent;
public static synthetic fun load$default (Lsk/ainet/models/laya/LayaModelLoader;Lsk/ainet/models/laya/LayaCheckpointFiles;Lkotlin/jvm/functions/Function0;Lsk/ainet/context/ExecutionContext;ZLsk/ainet/lang/memory/plan/WeightForm;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}

public final class sk/ainet/models/laya/LayaNetwork {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package sk.ainet.models.laya

import sk.ainet.context.ExecutionContext
import sk.ainet.io.RandomAccessSource
import sk.ainet.lang.memory.plan.WeightForm
import sk.ainet.lang.types.FP32

public actual object LayaModelLoader {
public actual suspend fun load(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean,
weightForm: WeightForm?,
weightFormFor: ((String) -> WeightForm?)?,
): LayaAgent<FP32> = loadLayaAgent(files, weights, ctx, exactGelu, weightForm, weightFormFor)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,45 @@ import sk.ainet.lang.types.FP32
*
* Common code since engine 0.61 (SKEEP-007): the JVM, Android and browser builds all run this
* function; only how the bytes are obtained differs (a file, an app's files directory, a download
* held in memory). The FP32 reference path (REQ-EXE-008) is the default: F16 weights are up-cast
* held in memory). It is an `expect` object so that the JVM can add its `load(directory: Path, …)`
* convenience as a *member*: callers reach it as `LayaModelLoader.load(dir, ctx)` without an
* import, as they did before 0.62.0. The FP32 reference path (REQ-EXE-008) is the default: F16 weights are up-cast
* on load, which is what the Python reference does on CPU.
*
* [weightForm] / [weightFormFor] are passed through to the engine's SafeTensors loader. With
* [LayaWeightForms.q8_0For] the encoder and head projections and the token table are quantized to
* Q8_0 at load — about a quarter of the FP32 bytes, served by the packed kernels on every tier —
* and the decision-quality gate (`LayaQuantizedParityTest`) is the contract that lane keeps.
*/
public object LayaModelLoader {
public expect object LayaModelLoader {
public suspend fun load(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean = true,
weightForm: WeightForm? = null,
weightFormFor: ((String) -> WeightForm?)? = null,
): LayaAgent<FP32> {
val preprocessor = LayaPreprocessor.fromFiles(files)
val raw = ArrayList<WeightTensor<FP32, Float>>()
SafeTensorsParametersLoader(sourceProvider = weights, weightForm = weightForm, weightFormFor = weightFormFor)
.load<FP32, Float>(ctx, FP32::class) { name, tensor ->
raw += WeightTensor(name, tensor.shape.dimensions.toList(), tensor)
}
val prepared = LayaWeightPreparation.prepare(raw, preprocessor.encoderConfig, preprocessor.agentConfig, ctx, FP32::class)
val model = LayaDecisionModel.build(preprocessor.encoderConfig, preprocessor.agentConfig, prepared, ctx, FP32::class, exactGelu)
return LayaAgent(preprocessor, model)
}
): LayaAgent<FP32>
}

/** The six loading steps every platform's [LayaModelLoader.load] runs. */
internal suspend fun loadLayaAgent(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean,
weightForm: WeightForm?,
weightFormFor: ((String) -> WeightForm?)?,
): LayaAgent<FP32> {
val preprocessor = LayaPreprocessor.fromFiles(files)
val raw = ArrayList<WeightTensor<FP32, Float>>()
SafeTensorsParametersLoader(sourceProvider = weights, weightForm = weightForm, weightFormFor = weightFormFor)
.load<FP32, Float>(ctx, FP32::class) { name, tensor ->
raw += WeightTensor(name, tensor.shape.dimensions.toList(), tensor)
}
val prepared = LayaWeightPreparation.prepare(raw, preprocessor.encoderConfig, preprocessor.agentConfig, ctx, FP32::class)
val model = LayaDecisionModel.build(preprocessor.encoderConfig, preprocessor.agentConfig, prepared, ctx, FP32::class, exactGelu)
return LayaAgent(preprocessor, model)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package sk.ainet.models.laya

import sk.ainet.context.ExecutionContext
import sk.ainet.io.RandomAccessSource
import sk.ainet.lang.memory.plan.WeightForm
import sk.ainet.lang.types.FP32

public actual object LayaModelLoader {
public actual suspend fun load(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean,
weightForm: WeightForm?,
weightFormFor: ((String) -> WeightForm?)?,
): LayaAgent<FP32> = loadLayaAgent(files, weights, ctx, exactGelu, weightForm, weightFormFor)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,49 @@ package sk.ainet.models.laya
import kotlinx.coroutines.runBlocking
import sk.ainet.context.ExecutionContext
import sk.ainet.io.JvmRandomAccessSource
import sk.ainet.io.RandomAccessSource
import sk.ainet.lang.memory.plan.WeightForm
import sk.ainet.lang.types.FP32
import java.nio.file.Files
import java.nio.file.Path

/**
* Loads a checkpoint directory (`rl_agent_config.json`, `encoder/config.json`, `tokenizer/`,
* `model.safetensors`) into a [LayaAgent] — the JVM convenience over the common
* [LayaModelLoader.load]. Blocking; call off the UI thread.
*
* [weightFormFor] selects the lane: `null` is the FP32 reference path (REQ-EXE-008, F16 up-cast on
* load), [LayaWeightForms.q8_0For] the packed Q8_0 lane.
*/
public fun LayaModelLoader.load(
directory: Path,
ctx: ExecutionContext,
exactGelu: Boolean = true,
weightFormFor: ((String) -> WeightForm?)? = null,
): LayaAgent<FP32> {
val weights = directory.resolve("model.safetensors")
require(Files.exists(weights)) { "no model.safetensors in $directory" }
val files = LayaCheckpointFiles.read(directory)
return runBlocking {
load(
files = files,
weights = { JvmRandomAccessSource.open(weights.toString()) },
ctx = ctx,
exactGelu = exactGelu,
weightFormFor = weightFormFor,
)
public actual object LayaModelLoader {
public actual suspend fun load(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean,
weightForm: WeightForm?,
weightFormFor: ((String) -> WeightForm?)?,
): LayaAgent<FP32> = loadLayaAgent(files, weights, ctx, exactGelu, weightForm, weightFormFor)

/**
* Loads a checkpoint directory (`rl_agent_config.json`, `encoder/config.json`, `tokenizer/`,
* `model.safetensors`) into a [LayaAgent]: the JVM convenience over the common [load].
* Blocking; call off the UI thread.
*
* [weightFormFor] selects the lane: `null` is the FP32 reference path (REQ-EXE-008, F16 up-cast on
* load), [LayaWeightForms.q8_0For] the packed Q8_0 lane. `@JvmOverloads` keeps the 0.61
* `load(Path, ExecutionContext, boolean)` signature for Java callers.
*/
@JvmOverloads
public fun load(
directory: Path,
ctx: ExecutionContext,
exactGelu: Boolean = true,
weightFormFor: ((String) -> WeightForm?)? = null,
): LayaAgent<FP32> {
val weights = directory.resolve("model.safetensors")
require(Files.exists(weights)) { "no model.safetensors in $directory" }
val files = LayaCheckpointFiles.read(directory)
return runBlocking {
load(
files = files,
weights = { JvmRandomAccessSource.open(weights.toString()) },
ctx = ctx,
exactGelu = exactGelu,
weightFormFor = weightFormFor,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package sk.ainet.models.laya

import sk.ainet.context.ExecutionContext
import sk.ainet.io.RandomAccessSource
import sk.ainet.lang.memory.plan.WeightForm
import sk.ainet.lang.types.FP32

public actual object LayaModelLoader {
public actual suspend fun load(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean,
weightForm: WeightForm?,
weightFormFor: ((String) -> WeightForm?)?,
): LayaAgent<FP32> = loadLayaAgent(files, weights, ctx, exactGelu, weightForm, weightFormFor)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package sk.ainet.models.laya

import sk.ainet.context.ExecutionContext
import sk.ainet.io.RandomAccessSource
import sk.ainet.lang.memory.plan.WeightForm
import sk.ainet.lang.types.FP32

public actual object LayaModelLoader {
public actual suspend fun load(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean,
weightForm: WeightForm?,
weightFormFor: ((String) -> WeightForm?)?,
): LayaAgent<FP32> = loadLayaAgent(files, weights, ctx, exactGelu, weightForm, weightFormFor)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package sk.ainet.models.laya

import sk.ainet.context.ExecutionContext
import sk.ainet.io.RandomAccessSource
import sk.ainet.lang.memory.plan.WeightForm
import sk.ainet.lang.types.FP32

public actual object LayaModelLoader {
public actual suspend fun load(
files: LayaCheckpointFiles,
weights: () -> RandomAccessSource,
ctx: ExecutionContext,
exactGelu: Boolean,
weightForm: WeightForm?,
weightFormFor: ((String) -> WeightForm?)?,
): LayaAgent<FP32> = loadLayaAgent(files, weights, ctx, exactGelu, weightForm, weightFormFor)
}
Loading