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
4 changes: 2 additions & 2 deletions .github/workflows/kernelrace-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ jobs:
run: |
./gradlew \
:shared:jvmTest \
:shared:testDebugUnitTest \
:composeApp:assembleDebug \
:shared:testAndroidHostTest \
:androidApp:assembleDebug \
:shared:compileKotlinWasmJs \
:composeApp:compileKotlinWasmJs \
--console=plain
2 changes: 1 addition & 1 deletion GloVeEmbeddings/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Kotlin"), plus an offline Compose Multiplatform app that explores word vectors.
./gradlew :glove:runDemo # the article pipeline, end to end (JVM)
./gradlew :app:run # desktop UI (needs a display)
./gradlew :app:wasmJsBrowserDevelopmentRun # web UI in the browser
./gradlew :app:assembleDebug # Android APK (needs an Android SDK)
./gradlew :androidApp:assembleDebug # Android APK (needs an Android SDK)
```

iOS targets are declared and build on a macOS host (`:app` produces a `GloVeApp` framework);
Expand Down
49 changes: 49 additions & 0 deletions GloVeEmbeddings/androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}

kotlin {
dependencies {
implementation(projects.app)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
debugImplementation(compose.uiTooling)
}

target {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
}

android {
namespace = "sk.ainet.samples.glove.app"
compileSdk = libs.versions.android.compileSdk.get().toInt()

defaultConfig {
applicationId = "sk.ainet.samples.glove.app"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") { isMinifyEnabled = false }
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
48 changes: 13 additions & 35 deletions GloVeEmbeddings/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig

plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.androidMultiplatformLibrary)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}

kotlin {
jvmToolchain(21)

androidTarget()
// Android app entry point (MainActivity, manifest) lives in :androidApp — AGP 9 no longer
// allows 'com.android.application' directly in a KMP module. This module is now a plain KMP
// library on the Android axis; :androidApp depends on it for App().
android {
namespace = "sk.ainet.samples.glove.app.library"
compileSdk = libs.versions.android.compileSdk.get().toInt()
minSdk = libs.versions.android.minSdk.get().toInt()

compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}

listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
iosTarget.binaries.framework {
Expand Down Expand Up @@ -42,10 +53,6 @@ kotlin {
sourceSets {
val desktopMain by getting

androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
Expand Down Expand Up @@ -76,35 +83,6 @@ compose.resources {
packageOfResClass = "sk.ainet.samples.glove.app.resources"
}

android {
namespace = "sk.ainet.samples.glove.app"
compileSdk = libs.versions.android.compileSdk.get().toInt()

defaultConfig {
applicationId = "sk.ainet.samples.glove.app"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") { isMinifyEnabled = false }
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

dependencies {
debugImplementation(compose.uiTooling)
}

compose.desktop {
application {
mainClass = "sk.ainet.samples.glove.app.MainKt"
Expand Down
1 change: 1 addition & 0 deletions GloVeEmbeddings/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
// Declared here (apply false) so each plugin is loaded once for the whole build.
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidMultiplatformLibrary) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
}
Expand Down
2 changes: 1 addition & 1 deletion GloVeEmbeddings/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
kotlin.code.style=official

# Gradle
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m
org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=1536m
kotlin.daemon.jvmargs=-Xmx4g

# Kotlin Multiplatform
Expand Down
18 changes: 10 additions & 8 deletions GloVeEmbeddings/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ kotlin = "2.4.10"
# sk.ainet.core:* artifact so individual libraries are declared without versions.
skainet = "0.40.1"

# Compose Multiplatform + Android. Pinned to 1.10.1 to match the shared
# :skainet-ui design system (included build): a mismatched Compose pulls two
# Skiko versions onto the desktop classpath — newer skiko-awt classes against an
# Compose Multiplatform + Android. Must match the shared :skainet-ui design
# system (included build) exactly: a mismatched Compose pulls two Skiko
# versions onto the desktop classpath — newer skiko-awt classes against an
# older native runtime — which crashes with UnsatisfiedLinkError
# (getAdapterMaxTextureSize) at first render on macOS/Metal.
agp = "8.12.3"
composeMultiplatform = "1.10.1"
# (getAdapterMaxTextureSize) at first render on macOS/Metal. AGP must also
# match skainet-ui's: Gradle composite builds reject mixed AGP versions.
agp = "9.3.1"
composeMultiplatform = "1.11.1"
androidx-lifecycle = "2.9.6"
androidx-activityCompose = "1.12.2"
# Provides the Swing Dispatchers.Main for the desktop (JVM) target; must match
# the kotlinx-coroutines-core version on the classpath (1.11.0).
coroutines = "1.11.0"
android-compileSdk = "36"
android-compileSdk = "37"
android-minSdk = "24"
android-targetSdk = "36"
android-targetSdk = "37"

[libraries]
skainet-bom = { module = "sk.ainet:skainet-bom", version.ref = "skainet" }
Expand All @@ -36,5 +37,6 @@ kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
31 changes: 28 additions & 3 deletions GloVeEmbeddings/kotlin-js-store/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,16 @@ source-map@^0.6.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==

"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"

string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
Expand All @@ -444,7 +453,14 @@ string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"

"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"

strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
Expand Down Expand Up @@ -489,7 +505,16 @@ workerpool@^9.2.0:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.3.4.tgz#f6c92395b2141afd78e2a889e80cb338fe9fca41"
integrity sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==

"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"

wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
Expand Down
1 change: 1 addition & 0 deletions GloVeEmbeddings/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ includeBuild("../skainet-ui")

include(":glove")
include(":app")
include(":androidApp")
26 changes: 16 additions & 10 deletions KernelRace/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ only, see below).
Real ARM64 hardware shows the point best (an x86 emulator falls back to scalar):

```sh
./gradlew :composeApp:installDebug
./gradlew :androidApp:installDebug
```

Tap **Generate on-device** — the model (~145 MB) downloads on first use. Then flip the
**SCALAR** chip and generate again to see the difference, or tap **Race against scalar** for the
side-by-side version. To go fully offline, place `SmolLM2-135M-Instruct-Q8_0.gguf` in
`composeApp/src/androidMain/assets/` before building.
`androidApp/src/main/assets/` before building.

Reference numbers (SmolLM2-135M Q8_0, SKaiNET 0.39.1): ~6.4× decode-kernel throughput NEON vs
scalar on a Pixel 8a.
Expand Down Expand Up @@ -141,17 +141,23 @@ KernelRace/
│ ├── jvmMain/ # DesktopModelProvider, file-based LlamaRuntimeBuilder actual
│ ├── iosMain/ # IosModelProvider (Ktor/Darwin), pread-based LlamaRuntimeBuilder actual
│ └── wasmJsMain/ # Bytes-only LlamaRuntimeBuilder actual (no filesystem)
└── composeApp/ # Compose Multiplatform UI + platform entry points
└── src/
├── commonMain/ # App/ChatScreen (skainet-ui themed), kernelControls slot
├── androidMain/ # KernelRaceApp (kernel pinning), race UI, manifest
├── jvmMain/ # Desktop window entry point
├── iosMain/ # MainViewController — entry point called from iosApp/
└── wasmJsMain/ # Browser entry point + bundled model resource
├── composeApp/ # Compose Multiplatform UI — a KMP library (Android, jvm, iOS, wasmJs)
│ └── src/
│ ├── commonMain/ # App/ChatScreen (skainet-ui themed), kernelControls slot
│ ├── jvmMain/ # Desktop window entry point
│ ├── iosMain/ # MainViewController — entry point called from iosApp/
│ └── wasmJsMain/ # Browser entry point + bundled model resource
├── androidApp/ # Android application entry point — depends on composeApp + shared
│ └── src/main/ # KernelRaceApp (kernel pinning), race UI, manifest
iosApp/ # Xcode project shell embedding composeApp's Kotlin/Native framework
```

AGP 9 no longer allows `com.android.application` inside a Kotlin Multiplatform module, so the
Android entry point is a separate `:androidApp` module that depends on `:composeApp` (for `App()`)
and `:shared`; `:composeApp` itself is now a KMP library on its Android axis
(`com.android.kotlin.multiplatform.library`), same shape on jvm/iOS/wasmJs as before.

The race mechanics (multi-process kernel pinning, `KernelRegistry`, the split-screen button) are
entirely Android-specific and live in `composeApp/androidMain`. Common code only sees a
entirely Android-specific and live in `androidApp/src/main`. Common code only sees a
`kernelControls` composable slot and a `GenerativeEngine` interface, which is what makes
`shared`'s `ChatViewModel` unit-testable with a fake on every target — see `shared/src/*Test/`.
73 changes: 73 additions & 0 deletions KernelRace/androidApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import java.util.Properties

plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}

kotlin {
dependencies {
implementation(projects.composeApp)
implementation(projects.shared)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(libs.compose.uiTooling)
implementation(libs.androidx.activity.compose)
implementation(libs.kotlinx.coroutines)
// Hand-written ARM NEON kernels — Android JNI only.
implementation(libs.skainet.backend.jni.cpu)
}

target {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
}

android {
namespace = "sk.ainet.samples.kernelrace"
compileSdk = libs.versions.android.compileSdk.get().toInt()

defaultConfig {
applicationId = "sk.ainet.samples.kernelrace"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
// ARM only — the JNI NEON kernels are the whole point of the race.
ndk { abiFilters += "arm64-v8a" }

// Optional Hugging Face token for gated repos. Lives in local.properties
// (gitignored) as HF_TOKEN=hf_xxx — never in source, never committed.
// SmolLM2 itself is a public repo, so this is normally left blank.
val hfToken = Properties().apply {
rootProject.file("local.properties").takeIf { it.exists() }?.inputStream()?.use(::load)
}.getProperty("HF_TOKEN") ?: ""
buildConfigField("String", "HF_TOKEN", "\"$hfToken\"")
// Shown next to the logo — always matches the dependency in the catalog.
buildConfigField("String", "SKAINET_VERSION", "\"${libs.versions.skainet.asProvider().get()}\"")
}

buildFeatures {
compose = true
buildConfig = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
2 changes: 1 addition & 1 deletion KernelRace/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.androidLibrary) apply false
alias(libs.plugins.androidMultiplatformLibrary) apply false
alias(libs.plugins.kotlinMultiplatform) apply false
alias(libs.plugins.composeMultiplatform) apply false
alias(libs.plugins.composeCompiler) apply false
Expand Down
Loading
Loading