Skip to content

Commit 4042af0

Browse files
michalharakalclaude
andcommitted
Extract transformer-core: NN primitives reusable on all targets incl. androidNative
llm-core's transformer primitives (KV-cache family, MultiHeadAttention, Embedding, RMSNormalization, RoPE, SwiGLU/GeGLU FFN, ResidualAdd, LinearProjection, …) only need skainet-lang-core (which has androidNative), but were trapped in llm-core, whose other deps (io-gguf/io-core/compile-*/backend-cpu) have no androidNative — so ARM-native consumers (the Amlogic box) couldn't reuse them and had to reimplement. Move the 15 lang-core-only NN files (transformer/, layers/, normalization/, dsl/TransformerDsl.kt) into a new transformer-core module that depends ONLY on skainet-lang-core and declares the full matrix INCLUDING androidNativeArm32/Arm64. llm-core api-depends on transformer-core (re-exports), so existing consumers are unaffected. dsl/decoder/* stays in llm-core (DecoderTransformerNetwork needs apps.llm.HybridTransformerBlock, which is compile-opt-coupled). Decoupled the one back-reference: MultiHeadAttention's diagnostic dumpStats call now goes through a settable `mhaStatSink` (default no-op) that HybridTransformerBlock wires to llm-core's platform dumpStats — no functionality lost. Verified: transformer-core compiles for jvm + androidNativeArm32 + arm64; llm-core builds + jvmTest green (5/5). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9f4dde7 commit 4042af0

19 files changed

Lines changed: 58 additions & 3 deletions

File tree

llm-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ kotlin {
4848
// versions. Bumping the engine is then a one-line change at the
4949
// top of `gradle/libs.versions.toml`.
5050
implementation(project.dependencies.platform(project(":llm-bom")))
51+
api(project(":transformer-core"))
5152
implementation(libs.skainet.lang.core)
5253
implementation(libs.skainet.compile.dag)
5354
implementation(libs.skainet.compile.opt)

llm-core/src/commonMain/kotlin/sk/ainet/apps/llm/HybridTransformerBlock.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,11 @@ public class HybridTransformerBlock<T : DType, V>(
168168
// the same name — so MHA can't gate its own dump on the block id.
169169
// Toggle the static flag from here, where we know which block we're in.
170170
val isMhaCall = dumpMha && module is MultiHeadAttention<*, *>
171-
if (isMhaCall) sk.ainet.lang.nn.transformer.MultiHeadAttentionDiag.shouldDumpThisCall = true
171+
if (isMhaCall) {
172+
// wire transformer-core's MHA diagnostic sink to llm-core's platform dumpStats (idempotent)
173+
sk.ainet.lang.nn.transformer.mhaStatSink = { l, t -> sk.ainet.apps.llm.diag.dumpStats(l, t) }
174+
sk.ainet.lang.nn.transformer.MultiHeadAttentionDiag.shouldDumpThisCall = true
175+
}
172176
tmp = module.forward(tmp, ctx)
173177
if (isMhaCall) sk.ainet.lang.nn.transformer.MultiHeadAttentionDiag.shouldDumpThisCall = false
174178
outputs[i + 1] = tmp

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ if (providers.gradleProperty("useLocalSkainet").orNull == "true") {
2424
rootProject.name = "SKaiNET-transformers"
2525

2626
include("llm-api")
27+
include("transformer-core")
2728
include("llm-core")
2829
include("llm-agent")
2930
include("llm-providers")

transformer-core/build.gradle.kts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
2+
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
3+
4+
plugins {
5+
alias(libs.plugins.kotlinMultiplatform)
6+
alias(libs.plugins.androidMultiplatformLibrary)
7+
alias(libs.plugins.vanniktech.mavenPublish)
8+
}
9+
10+
// Framework NN primitives (attention, KV-cache family, embedding, norms, RoPE, FFNs) extracted from
11+
// llm-core so they build on the FULL target matrix — including androidNative (the 32-bit box + phones).
12+
// Depends ONLY on skainet-lang-core (which has androidNative); no io/compile/backend deps. llm-core
13+
// re-exports this module, so existing consumers are unaffected; ARM-native consumers depend on it directly.
14+
kotlin {
15+
android {
16+
namespace = "sk.ainet.lang.nn"
17+
compileSdk = libs.versions.android.compileSdk.get().toInt()
18+
minSdk = libs.versions.android.minSdk.get().toInt()
19+
compilerOptions { jvmTarget.set(JvmTarget.JVM_11) }
20+
}
21+
22+
jvm()
23+
androidNativeArm32()
24+
androidNativeArm64()
25+
iosArm64()
26+
iosSimulatorArm64()
27+
linuxX64()
28+
linuxArm64()
29+
macosArm64()
30+
js { browser() }
31+
@OptIn(ExperimentalWasmDsl::class) wasmJs { browser() }
32+
@OptIn(ExperimentalWasmDsl::class) wasmWasi { nodejs() }
33+
34+
sourceSets {
35+
commonMain.dependencies {
36+
implementation(project.dependencies.platform(project(":llm-bom")))
37+
api(libs.skainet.lang.core) // public API is lang-core-typed (Tensor/Module/ExecutionContext)
38+
}
39+
commonTest.dependencies {
40+
implementation(libs.kotlin.test)
41+
}
42+
}
43+
}

llm-core/src/commonMain/kotlin/sk/ainet/lang/nn/dsl/TransformerDsl.kt renamed to transformer-core/src/commonMain/kotlin/sk/ainet/lang/nn/dsl/TransformerDsl.kt

File renamed without changes.

llm-core/src/commonMain/kotlin/sk/ainet/lang/nn/layers/Embedding.kt renamed to transformer-core/src/commonMain/kotlin/sk/ainet/lang/nn/layers/Embedding.kt

File renamed without changes.

llm-core/src/commonMain/kotlin/sk/ainet/lang/nn/layers/EmbeddingAdapter.kt renamed to transformer-core/src/commonMain/kotlin/sk/ainet/lang/nn/layers/EmbeddingAdapter.kt

File renamed without changes.

llm-core/src/commonMain/kotlin/sk/ainet/lang/nn/layers/EmbeddingParams.kt renamed to transformer-core/src/commonMain/kotlin/sk/ainet/lang/nn/layers/EmbeddingParams.kt

File renamed without changes.

llm-core/src/commonMain/kotlin/sk/ainet/lang/nn/normalization/RMSNormalization.kt renamed to transformer-core/src/commonMain/kotlin/sk/ainet/lang/nn/normalization/RMSNormalization.kt

File renamed without changes.

llm-core/src/commonMain/kotlin/sk/ainet/lang/nn/transformer/GeGLUFFN.kt renamed to transformer-core/src/commonMain/kotlin/sk/ainet/lang/nn/transformer/GeGLUFFN.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import kotlin.reflect.KClass
1515
*
1616
* Computes: down_proj(gelu(gate_proj(x)) * up_proj(x))
1717
*
18-
* Identical parameter layout to [SwiGLUFFN] so [sk.ainet.apps.llm.weights.LlamaGGUFNameResolver]
18+
* Identical parameter layout to [SwiGLUFFN] so `LlamaGGUFNameResolver` (llm-core)
1919
* maps the same GGUF tensor names. The only difference is the activation
2020
* (GELU instead of SiLU).
2121
*

0 commit comments

Comments
 (0)