quickstart — a gradient, derived at compile time¶
The smallest complete Tlaloc program, and the smallest complete demonstration of why the compile step matters.
val g = grad { a: DTensor<Rank2<Sym, Sym>, F32> -> (a matmul a).sum().toFloat() }
g(Tensors.f32Matrix<Sym, Sym>(2, 2, floatArrayOf(1f, 2f, 3f, 4f))) // [7.0, 11.0, 9.0, 13.0]
There is no tape here and no framework object at runtime. The Tlaloc K2 compiler
plugin rewrites that call during compilation into synthesized gradient code —
d/dA sum(A·A) = 1·Aᵀ + Aᵀ·1 — so g is an ordinary Kotlin function you call.
…and a shape bug that never gets to run¶
src/shapeError/kotlin/ShapeError.kt is a
real file in a real source set. It is supposed to fail to compile, and you can
watch it happen:
e: file://<repo>/examples/quickstart/src/shapeError/kotlin/ShapeError.kt:30:5 Tlaloc named-index mismatch: contract operands share no named axis: lhs=[Batch, SeqLen] rhs=[Hidden, Hidden]
e: file://<repo>/examples/quickstart/src/shapeError/kotlin/ShapeError.kt:32:12 Cannot infer type for type parameter 'NameN'. Specify it explicitly.
The two operands share no axis name — (Batch, SeqLen) against
(Hidden, Hidden) — so there is nothing to contract over. Two independent nets
catch it: Tlaloc's own checker names the mismatch, and Kotlin's type checker
cannot resolve the overload either (the remaining e: lines). In NumPy, PyTorch
or JAX this is a runtime error at best and a silently wrong answer at worst.
That task is wired to fail the build if the compile ever succeeds — a green
run there would mean Tlaloc had stopped catching the bug. It is not part of
build, check or run, so the quickstart itself still runs green.
Running it¶
This is a standalone Gradle project. It resolves Tlaloc from mavenLocal, exactly as your own project would, so publish first:
scripts/onboarding-smoke.sh does both in one go.
Expected output¶
Verbatim, from this machine (GB10, JDK 25, Kotlin 2.4.20):
[1] a gradient, derived at compile time
val g = grad { a: DTensor<Rank2<Sym, Sym>, F32> -> (a matmul a).sum().toFloat() }
d/dA sum(A matmul A) at A = [[1, 2], [3, 4]]
= 1·Aᵀ + Aᵀ·1
= [7.0, 11.0, 9.0, 13.0]
`g` is an ordinary Kotlin function. Call it in a hot loop: nothing
allocates a tape, because there is no tape.
[2] a shape bug the compiler will not let you ship
fun shapeErrorThatMustNotCompile() {
grad2 { a: DTensor<Rank2<Named<Batch, Sym>, Named<SeqLen, Sym>>, F32>,
b: DTensor<Rank2<Named<Hidden, Sym>, Named<Hidden, Sym>>, F32> ->
(a contract b).sum()
}
}
Those two operands share no axis NAME — (Batch, SeqLen) against
(Hidden, Hidden) — so there is nothing to contract over. In NumPy,
PyTorch or JAX that is a runtime error at best. Here the build fails.
Watch it happen:
./gradlew -p examples/quickstart shapeError
e: ShapeError.kt:30:5 Tlaloc named-index mismatch: contract operands
share no named axis: lhs=[Batch, SeqLen] rhs=[Hidden, Hidden]
That task is SUPPOSED to fail. It is wired to fail the build if the
compile ever succeeds — a green run there would mean Tlaloc had
stopped catching the bug.
quickstart OK
What to read in the source¶
| Look at | For |
|---|---|
src/main/kotlin/Main.kt |
The grad { } call — three lines, one gradient, no tape |
src/shapeError/kotlin/ShapeError.kt |
The program that must not compile |
build.gradle.kts |
The one line that makes it all work: id("io.github.pedronahum.tlaloc") version "0.1.0-alpha03", the Tlaloc Gradle plugin, which puts the compiler plugin on every compilation (the BOM supplies the library versions) — the shapeError task that expects failure — and the supported consumer JDK configuration: jvmToolchain(25) to build, jvmTarget = JVM_21 to run |
settings.gradle.kts |
mavenLocal() first — this project is a consumer, not part of the repo build |
Running it on a JDK 21¶
Tlaloc's bytecode targets are split: the library modules (core, ir,
autograd, nn, stablehlo, maestro) emit Java 21, while the FFM runtime
backends and the compiler plugin stay at 25. This project is the certification
that the 21 half is real:
export JDK21_HOME=/path/to/a/jdk-21 # gradle.properties reads this
./gradlew -p examples/quickstart runOnJdk21
The program is compiled by a JDK 25 toolchain — the plugin needs one, because
Kotlin runs a compiler plugin inside the compiler's JVM — into Java 21 bytecode,
and then executed by a JDK 21 launcher. A JDK 22+ API anywhere in :core,
:ir or :autograd, or 25 bytecode in any of them or in the gradient the plugin
synthesized into this program, fails here with UnsupportedClassVersionError or
NoSuchMethodError instead of printing a derivative. Measured on OpenJDK 21.0.2.
bash scripts/jdk21-smoke.sh from the repository root is the same thing including
the publishToMavenLocal that has to come first.
Where to go next¶
readable-gradients/— the derivative as a file you can read, recompiled without the plugin, agreeing bit for bit.differentiable-physics/— the samegrad, now differentiating a physics simulator with a loop in it, to sink a free throw.named-indices/— what the type system buys you before anything runs.