Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

2.5. Key Design Decisions

GPU-related decisions (Panama FFI, lazy dequantization, explicit weight lifecycle) are covered separately in GPU acceleration. This page covers the remaining architecture-level decisions.

Runs directly inside the JVM. GgufReader parses the GGUF binary format directly, and the full transformer forward pass executes inside the same process; there is no subprocess and no separate inference runtime to shell out to.

Javalin instead of a full application framework. Javalin serves REST. Virtual threads (Executors.newVirtualThreadPerTaskExecutor()) run on the gRPC ServerBuilder, which is required to avoid OS-thread saturation under concurrent prefill sessions.

OpenAI wire compatibility without framework coupling. OpenAiChatHandler and OpenAiAdapter are new classes added to the coordinator module. No existing classes were modified beyond InferenceApiServer wiring and ConsoleMain flag parsing. The existing POST /v1/inference and POST /v1/inference/stream endpoints are untouched. Adding new classes rather than extending InferenceApiServer keeps each concern isolated and the existing server stable.

Configurable activation byte order. ActivationCodec reads juno.byteOrder once at class-load time and branches to ActivationBECodec (big-endian, default) or ActivationLECodec (little-endian, native x86 order). ClusterHarness injects -Djuno.byteOrder into every forked node process; juno-deploy.sh writes it into /etc/juno/node.env for systemd-managed nodes.

KV cache wired at the node level. NodeKVCacheAdapter connects LlamaTransformerHandler and Phi3TransformerHandler to KVCacheManager (GPU byte-budget LRU plus a Caffeine W-TinyLFU CPU tier). Every forward pass flushes K/V data write-through into both tiers. On a local cache miss, the next forward pass at that position restores transparently. evict(requestId) propagates to both the local map and both cache tiers.

LoRA fine-tuning without touching the base model. LoraTrainableHandler wraps LlamaTransformerHandler and adds trainable low-rank adapters (A/B matrices, rank 4-16) on the Q and V projections. Frozen weights stay quantized at all times. Adapters persist to a .lora binary checkpoint; the GGUF is never modified. For a standalone merged model, use ./juno merge. See LoRA fine-tuning for the full guide.

Native LoRA merge. LoraMerge writes a new GGUF where the 44 LoRA-patched projection tensors (wq/wv per layer) are stored as F32. The LoRA delta (roughly 6e-4 per element) is smaller than Q4_K quantization noise (roughly 3e-3), so re-quantizing would erase all training. All other tensors are copied verbatim in their original quantized form.

GPT-2 BPE and SentencePiece BPE both supported. GgufTokenizer reads tokenizer.ggml.model from GGUF metadata. The value "gpt2" activates the GPT-2 / tiktoken path (Llama 3+). Any other value uses SentencePiece (Llama 1/2, TinyLlama, Mistral). Gemma uses the same SentencePiece path via LlamaTransformerHandler but is under development. Phi-3 uses a dedicated handler and phi3 chat template (supported). Gemma, Qwen 2, Qwen3, and Qwen3.5 use family-specific templates with validation in progress; treat these as under development. Detection is automatic at load time and requires no configuration.

AWS infrastructure fully scripted. juno-deploy.sh is the unified cluster lifecycle script. Hardware is auto-detected during bootstrap: GPU nodes set JUNO_USE_GPU=true (CUDA is pre-installed in the golden AMI by make-ami.sh). Commands: setup | start | stop | teardown | status | scan-regions. GPU quota is checked before any instances launch; insufficient vCPUs fail hard. State persists to ~/.juno-deploy-state. See AWS deployment for the operational walkthrough.

Full JFR instrumentation across every hot path. Six custom event types, juno.MatVec, juno.ForwardPass, juno.TokenProduced, juno.Tokenizer, juno.TemplateFormat, and juno.LoraTrainStep, make every layer of the stack observable in JDK Mission Control without any agent or bytecode manipulation. In cluster mode, the coordinator and every forked node JVM each write their own .jfr file. On exit, ConsoleMain collects coordinator and node paths and calls MetricsMain.extractToJson() once per existing file, printing a summary for each; target/metrics/metrics.json reflects the last processed file. Use ./juno local --jfr when you need all custom events in a single recording. Throughput (TPS) metrics come from the coordinator file (juno.TokenProduced). The programmatic MetricsMain.extractToJsonMerged() API merges event lists across files for percentile math but is not invoked by the cluster shutdown hook today.

juno.TokenProduced is a coordinator-side instantaneous event fired once per token delivered to a client after sampling and end-of-sequence checks. Because it lives in the coordinator JFR alongside tokenizer events, JfrMetricsExtractor derives aggregate TPS directly from the span between the first and last event timestamps and the total count, with no synthetic timer or counter needed in the inference path. The JSON report exposes juno.TokenProduced.count, juno.TokenProduced.elapsed_seconds, and juno.TokenProduced.tps. See JFR and metrics for the extraction workflow.

Stub mode. EmbeddedNodeServer uses an internal StubForwardPassHandler (zero-filled arrays) before a shard is loaded. CyclicForwardPassHandler lives in node/src/test and is shared with integration tests in juno-master and coordinator via the node:tests classifier jar. Integration tests run in stub mode: no model file, no GPU, boots in seconds.

See also


<- 2.4 GPU Acceleration  |  Table of Contents  |  2.6 Module Map ->