gpu4j

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

Target version 0.4.0 (set in gpu4j-core/pom.xml). Stamp this heading as ## [0.4.0] - YYYY-MM-DD when you tag.

On the version number. This range was planned as 0.3.0 and never tagged; git tag in this repository lists nothing at all. Three further feature areas then landed on top of it — graph capture and replay, the built-in GEMM, and fp16 — followed by the gpu4j rename, so it ships as 0.4.0 instead. Entries and @since tags below that say 0.3.0 describe work that first reaches a user in 0.4.0; they are left as written rather than restamped, because the alternative is churning ~80 tags to hide a slipped release that the tag list already tells you about.

Renamed

  • The repository, the modules and the artifacts are now gpu4j. craton-gpu-javagpu4j; the craton-gpu module → gpu4j-core (io.github.craton-co:gpu4j-core); craton-sidecargpu4j-sidecar. The groupId is unchanged, because it is the namespace already being verified for Maven Central.

    The Java packages are deliberately unchanged. CratonVM's native-builtin registry binds by exact string — craton/gpu/internal/Native, the three *Impl handle wrappers, and descriptors naming Lcraton/gpu/GpuExecutor; and the rest — so renaming craton.gpu is a coordinated two-repository change and belongs in the 1.0 cut, not in a repository rename. Automatic-Module-Name stays craton.gpu / craton.sidecar to match.

    CratonVM's side was updated in the same change: its crate is now craton-gpu4j, and its build.rs resolves <repo>/gpu4j-core/src/main/java in addition to both older layouts, under both the new checkout name and the old one. That ordering matters — build.rs does not fail when it finds nothing, it emits an empty annotations jar and a cargo:warning, leaving a VM that silently recognises no @GpuKernel at all.

Added

  • Graph capture and replay. GpuExecutor.beginCapture() / endCapture() record a sequence of dispatches instead of running them; replay(long) runs the whole recorded sequence as one submission. A launch costs the host something whether or not the device is busy, and a decode step of a 1B-parameter transformer issues 453 of them: measured on an RTX 2060, 3.483 ms of host time to issue against 0.550 ms to replay. graphNodeCount(long) reports what a graph holds — worth checking against the number of dispatches made while capturing, because a graph with fewer nodes replays successfully and does less — and releaseGraph(long) frees it.

  • GpuExecutor.beginReplay(long) / endReplay(), for a caller whose argument values change between replays but whose kernels cannot be changed to read them from device memory. Re-issue the captured dispatch sequence between the two calls; each dispatch rewrites the arguments of its node rather than launching. The sequence is checked against the captured one, and a mismatch refuses rather than submitting: a partially-updated graph would run some nodes with this iteration's arguments and the rest with the previous iteration's, and produce a plausible wrong answer.

  • GpuBlas — a built-in matrix multiply. C[MxN] = A[MxK] * B[KxN], row-major and unpadded, over GpuArray handles so a weight matrix uploaded once stays resident. Either operand can be read Transpose.TRANSPOSED in place, with no duplicate tensor and no extra pass. gemmReference is the plain triple loop, accumulating in double on purpose, so a GPU result has something independent to be checked against.

    This cannot be a @GpuKernel method: a GEMM is three nested loops wanting a shared-memory tile and a barrier, and the bytecode lowering handles one counted loop or a two-level flattened nest. It rejects the shape rather than mis-lowering it, so a hand-written three-loop Java matmul silently runs on the CPU. The kernel ships with the VM and this class is the typed door to it.

  • fp16. Half converts between float and IEEE-754 binary16, in pure Java because the JDK's Float.floatToFloat16 arrived in Java 20 and this library targets 17. HalfTest checks it against the JDK's own implementation exhaustively — all 2^32 floats and all 2^16 half bit patterns — and they agree bit for bit on every input except NaN, where they agree on NaN-ness. GpuArray.wrapHalf(short[]) and allocateHalf(int) carry the bits, and GpuBlas.gemmHalf multiplies them with an fp32 accumulator.

  • GpuArray.copyFromHost(...) — overwrites a resident array in place, keeping its device pointer. The counterpart of toHost(dest), and the way to give a captured graph new input: a replay runs against the pointers it was captured with, so allocating a fresh GpuArray is precisely what does not work.

  • LocalModelServer — starts llama-server or sd-server on a resolved model, waits until it is genuinely ready, evicts on model switch, and stops it on close. SidecarConfig.llmGpuLayers() carries the -ngl knob that used to live on the deleted engine.

  • GpuStream.dispatch(...) and GpuStream.synchronize() — ordered, stream-scoped submission. GpuStream was a handle and a close() with nothing that accepted it, while the fire-and-forget dispatch path's entire safety argument rested on stream ordering. The stream owns the submission handles it issues and releases them on close(), so the leak the bare-handle API makes possible is not expressible here.

  • GpuExecutor.prepare(...) returning PreparedKernel — resolve a kernel target once instead of re-proving the same three strings on every dispatch. Its typed overloads skip the argument walk entirely.

  • craton.gpu.testing.GpuTestBridge — the supported way to run craton.gpu against a stand-in backend. Previously the swap point was package-private, so the only route was reflection into the private Native.BRIDGE field, which this repository's own example documented as the pattern.

  • Automatic-Module-Name (craton.gpu, craton.sidecar) on both jars.

  • GpuDevicecount(), get(int), all(), queryable(), and compute capability. Built on four entry points CratonVM has always registered and that nothing on the Java side ever called, so there was no way to ask how many devices existed: GpuExecutor.open(3) on a one-GPU box was a native failure carrying a string.

  • GpuArray.allocateInt/Long/Float/Double(int) — device-resident buffers with no host mirror, for the pipeline intermediates the host never reads. The arrayAllocate* natives have been registered by CratonVM since 2026-07-11 with no Java caller.

  • GpuArray.toHost(dest) uses the direct read-back entry point when the bridge has one, removing a full-size allocation and a copy per call. These overloads previously only bounded the caller's own garbage, not the total.

  • GpuFuture.get(long, TimeUnit) uses a blocking native wait when the bridge offers one, instead of polling futureStatus on a sleep loop.

  • craton.gpu.internal.ImmediateFutures — the single already-resolved GpuFuture implementation, replacing three near-identical private copies that had drifted apart.

  • Optional NativeBridge entry points for structured error classification (futureErrorKind), a blocking timed wait (futureAwait), direct read-back (arrayToHostInto) and stream-scoped dispatch (streamSubmitMethod, streamSynchronize). All defaulted; each caller degrades to the previous behaviour when a bridge does not implement them.

  • The first GPU-tagged tests in either project, and a gpu-tests profile in craton-gpu to match craton-sidecar's.

  • .gitattributes and .editorconfig. Line-ending behaviour previously depended on each contributor's core.autocrlf; two tracked files already had mixed endings within a single file. All 88 tracked files are now normalised to LF in the index.

  • .github/workflows/codeql.yml (CodeQL static analysis, both projects, weekly plus per-push).

  • New gpu4j-sidecar module (introduced as craton-sidecar): a supervisor for a local inference server, with a runtime-changeable active-model registry (ModelConfig) seeded from CRATON_LLM_MODEL / CRATON_DIFFUSION_MODEL / CRATON_AI_MODELS_DIR. Independent of the CratonVM native bridge — see docs/SIDECAR_SETUP.md. It launches each backend's prebuilt Windows+CUDA server binary (llama-server.exe, sd-server.exe) as a child process and talks to it over HTTP (SidecarProcess, SidecarConfig) rather than through an in-process JNI/JNA binding: neither project publishes a prebuilt Windows+CUDA binding library, so this is the path that works without a C++ toolchain.

    (This entry described a prompt-and-inference API when it was written. That half was removed later in the same unreleased range — see Removed — so what it describes now is the supervisor, which is what actually ships.)

  • GpuExecutor.releaseSubmission(long) — frees a submission handle returned by dispatchNamedHandle. Defaulted on the interface, so no implementor breaks. Without it there was no way at all to give such a handle back; see Fixed.

  • GpuExecutor.dispatchNamedHandle / awaitSubmission — fire-and-forget dispatch that skips the GpuFuture wrapper, for queueing a chain of kernels on one stream and waiting only on the last. (Present in the tree since the 0.3.0 work but never recorded here.)

  • craton.gpu.internal.GpuErrors — classifies a native failure string into GpuCompileException / GpuOutOfMemoryException / GpuLaunchException, used by GpuFuture.get() and awaitSubmission.

  • GpuException(Throwable, int), GpuCompileException(String, Throwable, int, String) and GpuLaunchException(String, Throwable, int, String, String) constructor overloads.

  • DiffusionOptions.defaults(), plus validation of every component.

  • build.qualifier Maven property on both POMs, appended to the artifact file name. Empty by default, so release output is unchanged.

  • CI now builds and tests craton-sidecar (previously never built by CI at all) and compiles and runs examples/Demo.java. CI also triggers on pushes to dev.

  • Dependabot now watches craton-sidecar's dependencies.

Changed

  • Breaking. craton-java-ai is renamed craton-sidecar; package craton.ai becomes craton.sidecar, the directory and artifactId follow, and docs/AI_SETUP.md becomes docs/SIDECAR_SETUP.md. The old name promised an AI library and the module supervises subprocesses; worse, sitting beside craton-gpu it implied GPU-from-Java, which is the one thing it deliberately does not do. AiException and AiInferenceException collapse into a single SidecarException, the root of the module's hierarchy, for the same reason: inference is not something this module can fail at. The system property craton.ai.llm.gpuLayers becomes craton.sidecar.llm.gpuLayers; environment variable names are unchanged.

  • GpuFutureImpl carries its completion state in one volatile int instead of two AtomicBoolean fields: two fewer heap objects per future, on a path that mints one per kernel and where building the future was already measured as the largest single item in a dispatch.

  • Argument validation for a named dispatch lives in one place (craton.gpu.internal.KernelArgs) rather than inline in GpuExecutor.submit, so the executor, stream and prepared-kernel paths cannot disagree about what a valid dispatch looks like.

  • Breaking (behavioural). GpuFuture.thenApplyGpu now reports every failure through the returned future and never throws. It used to do both depending on which stage failed — an upstream failure came back as a failed future, a dispatch failure was thrown — which a caller could not tell apart without reading the implementation.

  • GeneratedImage behaves like a value: content-based equals/hashCode, a toString that does not print [B@1b6d3586, and copies in and out so the array cannot change under its holder. Adds byteCount(), writeTo(OutputStream) and writeTo(Path) as the copy-free paths.

  • StreamCleaner creates its Cleaner lazily. It used to spawn a daemon thread the moment the class initialised, which happens as soon as anything touches craton.gpu — including a process that only reads an enum constant.

  • JaCoCo 0.8.12 → 0.8.15, which removes the wall of IllegalClassFormatException instrumentation errors on current JDKs; Mockito 5.12.0 → 5.23.0, which removes the -Dnet.bytebuddy.experimental=true workaround from both projects.

  • All six GitHub Actions references pinned to commit SHAs.

  • GpuFuture.get(long, TimeUnit) consults the memoized outcome before polling futureStatus, removing a native round-trip per poll (up to ~1000/second per waiter at the 1 ms steady-state interval).

  • StreamCleaner binds the NativeBridge at registration time rather than resolving it when cleanup runs, so a handle is always released by the backend that allocated it.

  • docs/SIDECAR_SETUP.md rewritten. It described an in-process JNA/JNI design the code does not use: a craton.ai.diffusion.StableDiffusionNative class that does not exist, and a de.kherud:llama-java dependency the POM explicitly does not declare. Both engines drive a prebuilt server binary over HTTP.

  • README corrected: the exception list omitted the three GpuException subtypes, StreamCleaner was described as cleaning up streams only, the 0.3.0 dispatch API was missing, and the CratonVM build.rs source path quoted was not where this repository lives.

Deprecated

  • Native.futureGetErrorMessage, in favour of Native.futureGetError. CratonVM registers both names to the same Rust function, and every call site here tries one and falls back to the other.

  • GpuExecutor.handleForDispatch(), for removal in 1.0.0. Nothing in the library has read it since dispatchNamed landed, and it hands out a raw native pointer that outlives the closed-state check that guarded it.

Removed

  • Breaking. craton-sidecar's inference client: LlmEngine, DiffusionEngine, LlamaCppLlmEngine, StableDiffusionCppEngine, GeneratedImage, DiffusionOptions, and the hand-rolled JsonLite. What remains is the supervisor, which is the part that was not commodity.

    llama-server speaks the OpenAI wire protocol, so the ~520 lines of HTTP and JSON here duplicated what LangChain4j, Spring AI and the OpenAI Java SDK already do against that endpoint — and did it worse: JsonLite was a hand-rolled parser this audit found three encoding defects in. Callers now point an HTTP client of their choice at LocalModelServer.baseUri().

    AiInferenceException is renamed SidecarException, since inference is no longer something this module can fail at.

  • Breaking. @LlmPrompt, @DiffusionPrompt, @PromptVar, AiClients, PromptInvocationHandler and TemplateRenderer — the annotation-driven prompt layer — along with the craton-sidecar-service triage demo that existed to showcase it. Together with the inference client removed above, that leaves the supervisor: start a server with LocalModelServer, then point an HTTP client of your choice at baseUri().

    Two behaviours the layer provided now belong to the caller: interface validation at wiring time (the compiler covers this when prompts are built in Java), and reading ModelConfig fresh on every call, which is what makes a runtime model switch take effect on the next request.

  • DiffusionOptions.from(DiffusionPrompt), with the annotation it read.

Fixed

  • examples/Demo.java did not compile. SimExecutor never grew the seven graph methods GpuExecutor gained in 0.3.0. The CI step that compiles and runs the example exists to catch exactly this — it was added after the same drift once before — and it happened again anyway. The example now implements them, and exercises GpuBlas, Half, copyFromHost and the graph API, none of which it covered while CI described it as a smoke test of "the whole public surface".

  • GpuBlas could free its own operands mid-call. dispatch extracted three GpuArray handles and one GpuStream handle as bare longs and then called gemm. Each handle() carries a reachabilityFence, but that fence ends when handle() returns; from there the caller holds only longs, which keep nothing alive, so the collector was free to run releaseArray on a buffer the GEMM was reading. Every other dispatch path was safe by accident, because it passes the GpuArray objects to the native call. Fenced explicitly, and GpuStreamImpl.handle() now fences like GpuArray.handle() does.

  • The timed get defeated its own blocking wait. GpuFuture.get(long, TimeUnit) passed the exponential-backoff interval — capped at 1 ms — to NativeBridge.futureAwait instead of the deadline, so a bridge implementing a real device wait was still woken up to a thousand times a second and still paid a native crossing per wake, which is exactly the cost that entry point was added to remove. It is now given a 50 ms slice of the remaining deadline: sliced rather than handed the whole thing because a native wait does not observe Thread.interrupt(), and costing nothing in latency because futureAwait returns on completion rather than sitting out its timeout. The backoff now applies only to the AWAIT_UNSUPPORTED polling path, and an unsupported answer is remembered for the rest of the call.

  • GpuStream.dispatch dropped a refused kernel silently. It returned the backend's 0 sentinel while its javadoc documented five exceptions and no zero return — and the usage that javadoc documents (for (l : layers) s.dispatch(...); s.synchronize();) never inspects the value, so a refused kernel produced a stale answer rather than a failure. It now throws GpuException, naming the kernel and the stream, like every sibling path in the library.

  • GpuStream's pending-submission queue was unbounded. The queue exists to fix a leak — nothing collects a bare submission handle — and turned it into a deferral, which is worse than it sounds: an un-finalized submission owns host-side writeback buffers, device buffers, and a GC-critical token that stops CratonVM's collector. A generation loop issuing 453 kernels per token over a 256-token reply would hold ~115,000 of them. It now releases finished submissions once the queue passes 1024, stopping at the first one still running (stream order means everything after it is running too), and holds a growable long[] instead of a List<Long>.

  • fp16 buffers were write-only. wrapHalf and allocateHalf produced arrays with no toHost(short[]) and no copyFromHost(short[]), and primitiveArrayLength had no short branch — so the buffer-reusing read-back path did not exist for the element type that exists for inference loops. KernelArgs also refused short[], byte[] and boolean[] as kernel arguments, which was a Java-side veto on a working path: CratonVM has marshalled all three since 2026-09-02.

  • GpuArray.toHost() did not null-check the bridge. It handed whatever arrayToHost returned into an already-completed future, so a null from the backend became a successful future carrying null and a NullPointerException somewhere unrelated. The buffer-reusing overloads had checked this since they were written.

  • The GEMM shape diagnostic named the wrong dimension. Per-operand checking reused the three-dimension message with a placeholder, so gemm(a, b, c, 4, 8, 0) reported "M=4, N=0, K=1" — K renamed to N, and a K of 1 the caller never passed. It now names the operand and its shape: "A would be 4x0".

  • A handle could be registered against the wrong backend. The GpuArray factories read Native.bridge() twice, once to allocate and once to register the cleaner, so a GpuTestBridge.install between the two bound the handle to a backend that never made it. StreamCleaner.register now takes the bridge the allocation actually went through.

  • GpuExecutorImpl.graphNodeCount and releaseGraph were the only methods in that class that took neither the lifecycle read lock nor the closed check. Harmless in itself, and an unexplained exception to an invariant the class documents at length. Both now take the lock; neither throws on a closed executor — graphNodeCount answers -1 and releaseGraph is a no-op, so it stays usable from the finally block it belongs in.

  • The optional native entry points with no fallback — arrayAllocate*, arrayWrapShort, streamSubmitMethod, streamSynchronize and gemm — threw and caught an UnsatisfiedLinkError on every call on a VM that does not register them. They now memoise the answer like the sentinel-returning ones, through a shared Probe. gemm is on the hot path.

  • GpuDevice.all() asked the bridge for the device count n+1 times to enumerate n devices, because get(int) re-queried it for its own bounds check.

  • Removed an unused CompletableFuture import from GpuArray, left behind when the already-resolved future classes moved to ImmediateFutures.

  • Javadoc that never rendered: a doubled doc comment in SidecarConfig that silently discarded the envPort explanation, prose after a block tag in SidecarProcess.awaitReady, duplicated @throws on LocalModelServer.llm() and diffusion(), and inline @since tags on three GpuArray.copyFromHost overloads. The release profile runs javadoc with failOnError=true.

  • GpuExecutor's class javadoc still said streams were "a placeholder for future enqueue APIs" two releases after GpuStream.dispatch and synchronize() landed as the recommended path for a chain.

  • The Maven Central contact of record was dev@craton-co.github.io, which cannot receive mail: craton-co.github.io is a GitHub Pages hostname with no MX record. The <email> element is removed rather than replaced with another address that might also be wrong; the issue tracker is the contact that works.

  • project.build.outputTimestamp was still 2026-08-28, which its own comment says to bump on every release.

  • GpuFuture.cancel() threw instead of returning false. Java has always called Native.futureCancel; CratonVM never registered it — the name appears in no .rs file in that workspace — so every cancellation attempt on a real CratonVM raised UnsatisfiedLinkError from inside a method whose entire documented behaviour is to answer false when it cannot do the job. Fixed on the CratonVM side (fix(craton-gpu): register Native.futureCancel); it now answers "rejected", which is what the Javadoc already promised, because no device-side cancellation primitive exists.

  • The gpu-tests profile never included anything. A profile's plugin <configuration> merges into the base one, so the profile's empty <excludedGroups/> did not clear the inherited gpu. Driven by a property now, and verified: 317 → 322 tests in craton-gpu, 67 → 71 in craton-sidecar.

  • Critical use-after-free issues in the executor, future, array, and stream lifecycle paths

  • JsonLite string extraction used a backtracking regex ((?:[^"\\]|\\.)*) that recurses per matched character in Java regex engine; a large value (a diffusion response base64-encoded PNG, several hundred KB) reliably threw an uncaught StackOverflowError. Rewritten to scan character-by-character.

  • LlamaCppLlmEngine sent prompts to llama-server raw /completion endpoint (no chat markup), which caused instruct-tuned models - observed on Llama-3.1-8B-Instruct - to produce rambling, repetitive output that ran to the token cap instead of stopping naturally. Switched to /v1/chat/completions and reduced max_tokens 512 to 256.

  • SidecarProcess leaked its child process (llama-server.exe / sd-server.exe, plus the VRAM its model held) whenever the JVM exited without an explicit close() call - no shutdown hook was registered. Each SidecarProcess now registers one, removed again on a normal close() so hooks do not accumulate across model switches.

  • dispatchNamedHandle leaked a native submission record per kernel. The fire-and-forget path trades the GpuFuture wrapper for speed, and with it the Cleaner registration that would have freed the handle — but nothing replaced it, and no API existed to release one. On the 453-kernels-per-token decode the API was designed for, that is 453 leaked records per token.

  • GpuFutureImpl raced on its result cache. The resolved value lived in three plain fields written under the read lock. A read lock is shared, so concurrent resolvers raced on them, and releasing a read lock creates no happens-before edge to another read-lock holder: a caller could see resultCached == true beside a stale null result and return that as the kernel's output. Now one volatile immutable holder.

  • cancel() could not cancel anything. It took the write lock for the whole operation including the non-blocking native cancel request, while get() holds the read lock across its blocking futureSynchronize — so the abort could not be issued until the kernel it was cancelling had finished. Split into a read-locked request and a write-locked free.

  • Failing kernels always threw a bare GpuException. GpuException's javadoc advertises three subclasses for targeted catch clauses; nothing in the library ever threw one, so all three were unreachable.

  • The test suite failed on dev. StreamCleaner resolved Native.bridge() at cleanup time, so the JDK Cleaner thread called releaseFuture on whichever mock a later test had installed, corrupting Mockito's stubbing state (GpuFutureImplTest.toCompletableFutureWithExecutor_rejectsNullExecutor).

  • examples/Demo.java did not compile. SimExecutor stopped implementing every GpuExecutor method when the 0.3.0 entry points landed; nothing in the build looks at examples/. CI now compiles and runs it.

  • GpuArray.handle() was missing the reachabilityFence every other handle-dereferencing method has, so the array could be collected — and its handle freed — before the value was returned.

  • GpuArray.toHost(dest) reports a short or mistyped native read as a GpuException instead of letting arraycopy throw ArrayIndexOutOfBoundsException at the caller.

  • GpuArray's internal completed future no longer strands an interrupt.

  • JsonLite.escape left tabs and every other character below U+0020 raw, so a prompt containing a tab — ordinary in pasted code — produced an invalid request body that the server rejected.

  • JsonLite decoded \uXXXX to the literal letter u followed by four hex digits, silently mangling any non-ASCII model output ("café" → "cafu00e9"). Surrogate pairs, \b and \f handled too.

  • JsonLite field lookup was indexOf("\"name\""), which matches the field name anywhere — including inside a client-supplied string value. Lookup now walks the document stepping over string values.

  • ModelResolver accepted any model name and joined it onto the models directory, so ../../../../Users/someone/.ssh/id_rsa resolved cleanly and whatever a caller could name, the sidecar would open. Names are now restricted to a plain file-name component, with containment and symlink-escape re-checks.

  • LlamaCppLlmEngine had no request or connect timeout, so a wedged llama-server parked the calling thread permanently.

  • Both engines evicted the running model server before resolving the new model, so an unknown model name cost the caller the model that was working. A sidecar that starts but never becomes ready is now destroyed rather than left holding VRAM.

  • SidecarProcess.awaitReady built a fresh HttpClient per call, leaking a selector thread and worker pool per model switch. launch() now creates the log file's parent directory first.

  • SidecarConfig and LlamaCppLlmEngine parsed ports and tuning values in field initialisers with a bare parseInt, so one mistyped environment variable threw ExceptionInInitializerError — naming neither the variable nor its value — and left the class permanently unusable. Both now warn and fall back.

  • craton-gpu's surefire config was missing the Byte Buddy flag craton-sidecar already carried, so its suite failed to start on newer JDKs. Applied with @{argLine} so JaCoCo's agent is not silently dropped.

  • The release workflow declared a tag input for manual dispatch and never read it, comparing the POM version against the dispatching branch name instead. It now resolves the tag up front and checks that ref out.

  • submitV() / submitScale() javadoc and the README claimed these overloads skip the varargs Object[] allocation. They delegate to the varargs submit and allocate exactly the same array. The claim is removed rather than restated: the overloads are worth having for compile-time type checking.

Security

  • Native bridge hardening
  • SECURITY.md directed vulnerability reports to security@craton-co.github.io, and pom.xml names dev@craton-co.github.io as the developer contact that Maven Central publishes. craton-co.github.io is a GitHub Pages hostname with no MX record, so both addresses silently drop mail: the documented disclosure channel did not exist. The policy now leads with GitHub Private Vulnerability Reporting, which needs no mail infrastructure. The POM address still needs a real mailbox, and Private Vulnerability Reporting has to be enabled in repository settings before the link works.
  • Model-name path traversal (see Fixed) — reachable from anywhere an application lets its users influence the active model name.

0.2.0 - 2026-05-19

First standalone release of the Java GPU API, extracted from the CratonVM workspace for independent versioning and Maven Central publication.

Added

  • Public API: GpuExecutor, GpuStream, GpuFuture, GpuArray, GpuKernel
  • Annotation surface: @EnableGpuAsync, @GpuExclude, AdmissionHint, GridShape
  • SAM types: GpuRunnable, GpuCallable, GpuFunction
  • Exception types: GpuException, GpuCompileException, GpuLaunchException, GpuOutOfMemoryException
  • Internal runtime: GpuExecutorImpl, GpuStreamImpl, GpuFutureImpl, StreamCleaner, native bridge seam (Native, NativeBridge, NativeImpl)
  • Primitive overloads on hot paths for allocation and transfer
  • Unit tests for executor, stream, future, array lifecycle, grid shape, and annotations
  • Contributor documentation and GitHub PR template

Changed

  • Library packaged as io.github.craton-co:craton-gpu (JDK 17+)

Fixed

  • Critical concurrency issues in stream and future completion paths