Skip to content

Commit a8ae6ed

Browse files
Merge pull request #384 from SKaiNET-developers/feature/refactor-loading-models
Feature/refactor loading models
2 parents a018876 + 25cf2c5 commit a8ae6ed

28 files changed

Lines changed: 1496 additions & 1386 deletions

File tree

skainet-apps/skainet-kgemma/src/commonMain/kotlin/sk/ainet/apps/kgemma/Gemma3nIngestion.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import kotlinx.io.Source
44
import sk.ainet.context.ExecutionContext
55
import sk.ainet.io.RandomAccessSource
66
import sk.ainet.io.gguf.gemma.Gemma3nRuntimeWeights
7-
import sk.ainet.io.gguf.gemma.Gemma3nWeightLoader
7+
import sk.ainet.io.gguf.dequant.QuantPolicy
88
import sk.ainet.io.gguf.gemma.loadGemma3nRuntimeWeights
99
import sk.ainet.io.gguf.gemma.loadGemma3nRuntimeWeightsFromSafeTensors
1010
import sk.ainet.io.gguf.gemma.loadGemma3nRuntimeWeightsStreaming
@@ -18,7 +18,7 @@ import kotlin.reflect.KClass
1818
* @property allowQuantized If false, error on encountering quantized tensors
1919
*/
2020
public data class Gemma3nLoadConfig(
21-
val quantPolicy: Gemma3nWeightLoader.QuantPolicy = Gemma3nWeightLoader.QuantPolicy.DEQUANTIZE_TO_FP32,
21+
val quantPolicy: QuantPolicy = QuantPolicy.DEQUANTIZE_TO_FP32,
2222
val allowQuantized: Boolean = false
2323
)
2424

Lines changed: 28 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package sk.ainet.apps.kgemma
22

3-
import kotlin.math.exp
43
import kotlin.random.Random
4+
import sk.ainet.apps.llm.DecoderRuntime
55
import sk.ainet.context.ExecutionContext
6-
import sk.ainet.io.gguf.gemma.Gemma3nLayerWeights
76
import sk.ainet.io.gguf.gemma.Gemma3nRuntimeWeights
87
import sk.ainet.lang.nn.layers.Embedding
98
import sk.ainet.lang.tensor.Tensor
@@ -12,7 +11,6 @@ import sk.ainet.lang.tensor.plus
1211
import sk.ainet.lang.tensor.t
1312
import sk.ainet.lang.tensor.times
1413
import sk.ainet.lang.nn.normalization.RMSNormalization
15-
import sk.ainet.lang.tensor.data.FloatArrayTensorData
1614
import sk.ainet.lang.types.DType
1715
import kotlin.reflect.KClass
1816

@@ -25,9 +23,7 @@ import kotlin.reflect.KClass
2523
* - Hybrid attention (local sliding-window + global)
2624
* - Per-layer embeddings (optional)
2725
*
28-
* The attention strategy (CPU vs GPU) is injected via [AttentionBackend].
29-
* All other logic (embedding, norms, projections, FFN, sampling, generate loop)
30-
* is shared.
26+
* Extends [DecoderRuntime] for shared forward/generate/sample logic.
3127
*
3228
* @param ctx ExecutionContext for tensor operations
3329
* @param weights Gemma 3n model weights
@@ -44,18 +40,19 @@ public class Gemma3nRuntime<T : DType>(
4440
private val dtype: KClass<T>,
4541
private val config: Gemma3nConfig,
4642
private val eps: Float = 1e-6f,
47-
private val random: Random = Random.Default
48-
) {
43+
random: Random = Random.Default
44+
) : DecoderRuntime<T>(random) {
4945

5046
private companion object {
5147
const val BOS_TOKEN: Int = 2 // Gemma uses 2 as BOS
5248
}
5349

54-
private val dim = config.hiddenSize
55-
private val seqLen = weights.metadata.contextLength
56-
private val vocabSize = weights.metadata.vocabSize
57-
58-
private var position: Int = 0
50+
// ---- DecoderRuntime abstract properties ----
51+
override val dim: Int = config.hiddenSize
52+
override val seqLen: Int = weights.metadata.contextLength
53+
override val vocabSize: Int = weights.metadata.vocabSize
54+
override val nLayers: Int = weights.layers.size
55+
override val bosToken: Int = BOS_TOKEN
5956

6057
private val embedding = Embedding(
6158
numEmbeddings = vocabSize,
@@ -64,7 +61,7 @@ public class Gemma3nRuntime<T : DType>(
6461
name = "token_embd"
6562
)
6663

67-
private val finalNorm = RMSNormalization<T, Float>(
64+
private val finalNormLayer = RMSNormalization<T, Float>(
6865
normalizedShape = intArrayOf(dim),
6966
eps = eps.toDouble(),
7067
name = "final_norm",
@@ -92,73 +89,13 @@ public class Gemma3nRuntime<T : DType>(
9289
public val currentPosition: Int
9390
get() = position
9491

95-
public fun reset() {
96-
attentionBackend.reset()
97-
position = 0
98-
}
99-
100-
/**
101-
* Perform a single forward pass for one token.
102-
*
103-
* @param tokenId Input token ID
104-
* @return Logits tensor [1, vocabSize]
105-
*/
106-
public fun forward(tokenId: Int): Tensor<T, Float> {
107-
require(position < seqLen) { "Context length exceeded: pos=$position seqLen=$seqLen" }
108-
109-
var x: Tensor<T, Float> = embedding.forward(intArrayOf(tokenId), ctx)
110-
111-
weights.layers.forEachIndexed { layerIdx, layer ->
112-
x = runLayer(layerIdx, layer, x)
113-
}
114-
115-
val norm = finalNorm.forward(x, ctx)
116-
val logits = norm.matmul(weights.lmHead.t())
92+
// ---- DecoderRuntime template methods ----
11793

118-
position++
119-
return logits
120-
}
121-
122-
/**
123-
* Generate tokens autoregressively.
124-
*
125-
* @param prompt Initial prompt as token IDs
126-
* @param steps Maximum number of tokens to generate
127-
* @param temperature Sampling temperature (0 = greedy)
128-
* @param onToken Callback for each generated token
129-
*/
130-
public fun generate(prompt: IntArray, steps: Int, temperature: Float, onToken: (Int) -> Unit) {
131-
require(steps > 0) { "steps must be > 0" }
132-
133-
val fullPrompt = if (prompt.isNotEmpty() && prompt[0] != BOS_TOKEN) {
134-
intArrayOf(BOS_TOKEN) + prompt
135-
} else if (prompt.isEmpty()) {
136-
intArrayOf(BOS_TOKEN)
137-
} else {
138-
prompt
139-
}
140-
141-
var token = fullPrompt[0]
142-
var pos = 0
143-
var generatedCount = 0
144-
while (generatedCount < steps) {
145-
val logits = forward(token)
146-
val next = if (pos + 1 < fullPrompt.size) {
147-
fullPrompt[pos + 1]
148-
} else {
149-
sample(logits, temperature)
150-
}
151-
if (pos + 1 >= fullPrompt.size) {
152-
onToken(next)
153-
generatedCount++
154-
}
155-
token = next
156-
pos++
157-
}
158-
}
94+
override fun embedToken(tokenId: Int): Tensor<T, Float> =
95+
embedding.forward(intArrayOf(tokenId), ctx)
15996

160-
private fun runLayer(layerIdx: Int, layer: Gemma3nLayerWeights<T>, input: Tensor<T, Float>): Tensor<T, Float> {
161-
var x = input
97+
override fun runLayer(layerIdx: Int, x: Tensor<T, Float>): Tensor<T, Float> {
98+
val layer = weights.layers[layerIdx]
16299

163100
// Pre-attention normalization
164101
val attnNorm = inputLayernorms[layerIdx].forward(x, ctx)
@@ -185,6 +122,18 @@ public class Gemma3nRuntime<T : DType>(
185122
return afterAttn + ffnOut
186123
}
187124

125+
override fun outputNorm(x: Tensor<T, Float>): Tensor<T, Float> =
126+
finalNormLayer.forward(x, ctx)
127+
128+
override fun outputProject(x: Tensor<T, Float>): Tensor<T, Float> =
129+
x.matmul(weights.lmHead.t())
130+
131+
override fun resetState() {
132+
attentionBackend.reset()
133+
}
134+
135+
// ---- Gemma-specific: GELU activation ----
136+
188137
/**
189138
* Apply GELU (Gaussian Error Linear Unit) activation.
190139
* Gemma uses GELU instead of SiLU.
@@ -194,7 +143,6 @@ public class Gemma3nRuntime<T : DType>(
194143
val out = FloatArray(buf.size)
195144

196145
// GELU approximation: x * 0.5 * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))
197-
// Using the exact formulation for better accuracy
198146
val sqrtTwoPi = 0.7978845608028654f // sqrt(2/pi)
199147
val c = 0.044715f
200148

@@ -208,51 +156,4 @@ public class Gemma3nRuntime<T : DType>(
208156

209157
return ctx.fromFloatArray(this.shape, dtype, out)
210158
}
211-
212-
private fun sample(logits: Tensor<T, Float>, temperature: Float): Int {
213-
val buf = logits.expectFloatBuffer()
214-
215-
if (temperature <= 1e-6f) {
216-
// Greedy decoding
217-
var best = 0
218-
var bestVal = buf[0]
219-
for (i in 1 until buf.size) {
220-
if (buf[i] > bestVal) {
221-
bestVal = buf[i]
222-
best = i
223-
}
224-
}
225-
return best
226-
}
227-
228-
// Temperature sampling
229-
val scaled = FloatArray(buf.size)
230-
var maxLogit = Float.NEGATIVE_INFINITY
231-
for (i in buf.indices) {
232-
val v = buf[i] / temperature
233-
scaled[i] = v
234-
if (v > maxLogit) maxLogit = v
235-
}
236-
237-
var sum = 0f
238-
for (i in scaled.indices) {
239-
val e = exp((scaled[i] - maxLogit).toDouble()).toFloat()
240-
scaled[i] = e
241-
sum += e
242-
}
243-
244-
val r = random.nextFloat() * sum
245-
var acc = 0f
246-
for (i in scaled.indices) {
247-
acc += scaled[i]
248-
if (acc >= r) return i
249-
}
250-
return scaled.lastIndex
251-
}
252-
253-
private fun Tensor<T, Float>.expectFloatBuffer(): FloatArray {
254-
val data = this.data
255-
if (data is FloatArrayTensorData<*>) return data.buffer
256-
return data.copyToFloatArray()
257-
}
258159
}

skainet-apps/skainet-kgemma/src/jvmMain/kotlin/sk/ainet/apps/kgemma/cli/Main.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import sk.ainet.apps.llm.tokenizer.HuggingFaceBPETokenizer
1212
import sk.ainet.context.DirectCpuExecutionContext
1313
import sk.ainet.io.JvmRandomAccessSource
1414
import sk.ainet.io.gguf.gemma.Gemma3nRuntimeWeights
15-
import sk.ainet.io.gguf.gemma.Gemma3nWeightLoader
15+
import sk.ainet.io.gguf.dequant.QuantPolicy
1616
import sk.ainet.lang.types.FP32
1717
import java.io.File
1818
import java.nio.file.Path
@@ -122,7 +122,7 @@ fun main(args: Array<String>) {
122122
ctx = ctx,
123123
dtype = FP32::class,
124124
config = Gemma3nLoadConfig(
125-
quantPolicy = Gemma3nWeightLoader.QuantPolicy.DEQUANTIZE_TO_FP32,
125+
quantPolicy = QuantPolicy.DEQUANTIZE_TO_FP32,
126126
allowQuantized = false
127127
)
128128
)

skainet-apps/skainet-kllama/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ kotlin {
113113
}
114114

115115
tasks.withType<Test>().configureEach {
116-
jvmArgs("--enable-preview", "--add-modules", "jdk.incubator.vector")
116+
jvmArgs("--enable-preview", "--add-modules", "jdk.incubator.vector", "-XX:MaxDirectMemorySize=12g")
117117
maxHeapSize = "6g"
118118
}
119119

skainet-apps/skainet-kllama/src/commonMain/kotlin/sk/ainet/apps/kllama/LlamaIngestion.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import sk.ainet.context.ExecutionContext
55
import sk.ainet.io.RandomAccessSource
66
import sk.ainet.io.gguf.llama.LlamaModelMetadata
77
import sk.ainet.io.gguf.llama.LlamaRuntimeWeights
8-
import sk.ainet.io.gguf.llama.LlamaWeightLoader
8+
import sk.ainet.io.gguf.dequant.QuantPolicy
99
import sk.ainet.io.gguf.llama.loadLlamaRuntimeWeights
1010
import sk.ainet.io.gguf.llama.loadLlamaRuntimeWeightsStreaming
1111
import sk.ainet.lang.types.DType
@@ -16,7 +16,7 @@ import kotlin.reflect.KClass
1616
* Default policy dequantizes to FP32 to ensure parity before quant-aware kernels are wired.
1717
*/
1818
public data class LlamaLoadConfig(
19-
val quantPolicy: LlamaWeightLoader.QuantPolicy = LlamaWeightLoader.QuantPolicy.DEQUANTIZE_TO_FP32,
19+
val quantPolicy: QuantPolicy = QuantPolicy.DEQUANTIZE_TO_FP32,
2020
val allowQuantized: Boolean = false
2121
)
2222

0 commit comments

Comments
 (0)