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
3 changes: 2 additions & 1 deletion skainet-bom/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ dependencies {
// Core language module
api(project(":skainet-lang:skainet-lang-core"))

// CPU backend
// Backend abstraction + CPU backend
api(project(":skainet-backends:skainet-backend-api"))
api(project(":skainet-backends:skainet-backend-cpu"))

// IO modules
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import sk.ainet.compile.hlo.converters.MathOperationsConverter
import sk.ainet.compile.hlo.converters.NeuralNetOperationsConverter
import sk.ainet.compile.hlo.converters.ReductionOperationsConverter
import sk.ainet.compile.hlo.converters.ShapeOperationsConverter
import kotlin.jvm.JvmStatic

/**
* Factory for creating StableHLO converters with default configurations.
Expand All @@ -21,6 +22,7 @@ public object StableHloConverterFactory {
/**
* Create a converter with basic operations support (add, matmul, relu)
*/
@JvmStatic
public fun createBasic(): StableHloConverter {
val registry = StableHloOperationRegistry()
val typeMapper = TypeMapper()
Expand Down Expand Up @@ -57,6 +59,7 @@ public object StableHloConverterFactory {
/**
* Create a converter with extended operations support
*/
@JvmStatic
public fun createExtended(): StableHloConverter {
val registry = StableHloOperationRegistry()
val typeMapper = TypeMapper()
Expand Down Expand Up @@ -96,6 +99,7 @@ public object StableHloConverterFactory {
/**
* Create a converter without validation (for performance)
*/
@JvmStatic
public fun createFast(): StableHloConverter {
val registry = StableHloOperationRegistry()
val typeMapper = TypeMapper()
Expand All @@ -115,6 +119,8 @@ public object StableHloConverterFactory {
/**
* Create a custom converter with the provided components
*/
@JvmStatic
@kotlin.jvm.JvmOverloads
public fun createCustom(
registry: StableHloOperationRegistry,
typeMapper: TypeMapper = TypeMapper(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sk.ainet.io.tokenizer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlin.jvm.JvmStatic

/**
* Selects the right [Tokenizer] implementation for a model.
Expand Down Expand Up @@ -33,6 +34,7 @@ public object TokenizerFactory {
* `ggufModelMetadata.rawFields` — this keeps `skainet-io-core` free of a
* dependency on `skainet-io-gguf`.
*/
@JvmStatic
public fun fromGguf(fields: Map<String, Any?>): Tokenizer {
val model = (fields["tokenizer.ggml.model"] as? String)?.lowercase()
?: throw UnsupportedTokenizerException(
Expand All @@ -57,6 +59,7 @@ public object TokenizerFactory {
* to [QwenByteLevelBpeTokenizer]; `"Unigram"` (SentencePiece) and
* `"WordPiece"` currently throw.
*/
@JvmStatic
public fun fromTokenizerJson(json: String): Tokenizer {
val root = JSON.parseToJsonElement(json).jsonObject
val modelType = root["model"]?.jsonObject?.get("type")?.jsonPrimitive?.content
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
@file:JvmName("TensorSpecs")

package sk.ainet.lang.tensor.ops

import sk.ainet.lang.tensor.data.TensorData
import sk.ainet.lang.tensor.storage.PackedBlockStorage
import sk.ainet.lang.tensor.storage.TensorEncoding
import kotlin.jvm.JvmName

/**
* Metadata key used to carry a [TensorEncoding] on a [TensorSpec].
Expand Down
6 changes: 6 additions & 0 deletions skainet-test/skainet-test-java/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ dependencies {
testImplementation(project(":skainet-lang:skainet-lang-core"))
testImplementation(project(":skainet-backends:skainet-backend-cpu"))
testImplementation(project(":skainet-data:skainet-data-simple"))
// 0.19.0 Java consumption surface: converter factory, tokenizer
// factory, and the TensorSpec encoding helper facade. Tested in
// ReleaseApiJavaTest so a Java consumer of the upcoming release
// has a reference invocation pattern for each.
testImplementation(project(":skainet-compile:skainet-compile-hlo"))
testImplementation(project(":skainet-io:skainet-io-core"))
}

tasks.test {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package sk.ainet.java;

import org.junit.jupiter.api.Test;

import sk.ainet.compile.hlo.StableHloConverter;
import sk.ainet.compile.hlo.StableHloConverterFactory;
import sk.ainet.io.tokenizer.TokenizerFactory;
import sk.ainet.io.tokenizer.UnsupportedTokenizerException;
import sk.ainet.lang.tensor.ops.TensorSpec;
import sk.ainet.lang.tensor.ops.TensorSpecs;
import sk.ainet.lang.tensor.storage.TensorEncoding;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

/**
* Java consumer smoke tests for the Kotlin surfaces polished in the
* 0.19.0 release for Java-first-citizenship (#400). Each test is
* deliberately close to the call sites a Java consumer of the BOM
* would write so the patterns are self-documenting.
*
* If any of these lose their clean static form on a future Kotlin
* refactor (e.g. `@JvmStatic` dropped), the tests fail at compile
* time rather than at bytecode-verification time in production.
*/
class ReleaseApiJavaTest {

// --- StableHloConverterFactory -----------------------------------------

/**
* The converter factory must be reachable via the idiomatic
* static form, not through the Kotlin object's INSTANCE marker.
* Written as a compile-time smoke test — if someone drops the
* @JvmStatic annotations this fails to compile before any
* assertion runs.
*/
@Test
void stableHloConverterFactoryIsStatic() {
StableHloConverter basic = StableHloConverterFactory.createBasic();
StableHloConverter extended = StableHloConverterFactory.createExtended();
StableHloConverter fast = StableHloConverterFactory.createFast();

assertNotNull(basic, "createBasic() must return a non-null converter");
assertNotNull(extended, "createExtended() must return a non-null converter");
assertNotNull(fast, "createFast() must return a non-null converter");
}

// --- TokenizerFactory --------------------------------------------------

/**
* TokenizerFactory's static form is the entry point Java consumers
* hit when they load a GGUF or HuggingFace tokenizer.json. The
* call shape must stay clean across releases.
*
* We pass an empty GGUF field map and expect an
* UnsupportedTokenizerException — the point is to prove the
* factory is dispatched via static, not to actually tokenize.
*/
@Test
void tokenizerFactoryFromGgufIsStatic() {
Map<String, Object> emptyFields = Collections.emptyMap();
assertThrows(
UnsupportedTokenizerException.class,
() -> TokenizerFactory.fromGguf(emptyFields),
"empty GGUF metadata map must trip UnsupportedTokenizerException"
);
}

@Test
void tokenizerFactoryFromTokenizerJsonIsStatic() {
String emptyJson = "{}";
assertThrows(
UnsupportedTokenizerException.class,
() -> TokenizerFactory.fromTokenizerJson(emptyJson),
"tokenizer.json with no model.type must trip UnsupportedTokenizerException"
);
}

// --- TensorSpecs (JvmName of TensorSpecEncoding.kt) --------------------

/**
* The TensorEncoding accessor helpers live on
* skainet-lang-core/.../ops/TensorSpecEncoding.kt, which now
* compiles to a class named TensorSpecs (via @file:JvmName).
* Java callers access read / copy via static-method syntax.
*/
@Test
void tensorSpecsEncodingHelpers() {
TensorSpec bare = new TensorSpec(
/* name= */ "w",
/* shape= */ List.of(8, 4),
/* dtype= */ "FP32",
/* requiresGrad= */ false,
/* metadata= */ Collections.emptyMap()
);

// Reader: an un-annotated spec has a null encoding.
assertNull(TensorSpecs.getTensorEncoding(bare),
"a fresh TensorSpec must have no tensorEncoding");

// Setter: returns a copy with the encoding attached.
TensorSpec annotated = TensorSpecs.withTensorEncoding(
bare, TensorEncoding.Q8_0.INSTANCE);
assertNotNull(TensorSpecs.getTensorEncoding(annotated),
"annotated spec must have a non-null tensorEncoding");
assertSame(TensorEncoding.Q8_0.INSTANCE,
TensorSpecs.getTensorEncoding(annotated),
"annotated spec must carry the encoding we set");

// The original is unchanged — data-class copy semantics.
assertNull(TensorSpecs.getTensorEncoding(bare),
"withTensorEncoding must not mutate the source spec");
}
}
Loading