Large Language Models

Overview

The smile.llm package provides building blocks and complete inference stacks for decoder-only LLMs on the same FFM-backed LibTorch bridge as smile.deep. It ships a LLaMA-3 implementation, a Qwen3.5 hybrid stack (Gated DeltaNet + gated full attention), a tiktoken BPE tokenizer, a continuous-batching inference engine, KV-cache / radix prefix reuse, weight quantization, and tensor parallelism.

For the shared tensor / layer API see Deep Learning. Production HTTP serving (OpenAI-compatible chat, tool calling, web UI) is covered on SMILE Serve.

Prerequisites

smile.llm uses the same native stack as smile.deep: the smile_torch shared library and LibTorch must be discoverable by the platform loader, and FFM access must be enabled (--enable-native-access=ALL-UNNAMED). See Deep Learning — Prerequisites for per-platform details.

GPU inference needs CUDA-enabled LibTorch. On Ampere or newer hardware models load in BFloat16; older GPUs use Float16. CPU inference runs in Float32.

Package Map

Package / typeRole
Role, Message, ContentPart, …Chat dialog types (text + multimodal parts, tool fields)
ChatCompletion, ChatOptions, FinishReasonInference results and per-request tool-calling options
LanguageModelCommon façade: encodeChat, generate, chat
tokenizer/Tokenizer interface + Tiktoken (BPE) / HuggingFace vocab
transformer/Shared primitives: Attention, FeedForward, RoPE
model/llama/LLaMA-3: Llama, GQA, blocks, args
model/qwen/Qwen3.5 hybrid text stack + vision tower
engine/Continuous-batching runtime, speculative decoding
cache/Paged KV pool + radix prefix reuse
attention/, quant/, parallel/Backends, weight quantization, tensor parallelism
template/, tool/Chat templates and tool-call parsers

Core Types

TypeKindPurpose
Roleenumsystem, user, assistant, tool
MessagerecordDialog turn — role + ordered ContentParts, optional tool fields
ContentPartsealed interfaceTextPart, ImageUrlPart, VideoUrlPart, AudioUrlPart
FinishReasonenumstop, length, tool_calls
ChatCompletionrecordResult — text, tokens, log-probs, finish reason, tool calls
ChatOptionsrecordPer-request tools / toolChoice / parallel tool calls
LanguageModelinterfaceSingle-prompt façade for chat-capable decoder LLMs
Java
import smile.llm.Message;
import smile.llm.ChatCompletion;

Message[] dialog = {
    Message.system("You are a helpful assistant."),
    Message.user("What is the capital of France?")
};

ChatCompletion reply = llama.chat(dialog, 256, 0.6, 0.9, false, 0L, null);
System.out.println(reply.content());   // "The capital of France is Paris."
System.out.println(reply.reason());    // FinishReason.stop

Message also carries multimodal parts and tool fields. Concurrent multi-request scheduling belongs in InferenceEngine, not on LanguageModel.

Java
import smile.llm.Message;
import smile.llm.Role;
import smile.llm.TextPart;
import smile.llm.ImageUrlPart;
import java.util.List;

// Multimodal turn: text interleaved with an image
Message turn = new Message(Role.user, List.of(
        new TextPart("What is in this picture?"),
        new ImageUrlPart("https://example.com/cat.jpg")));

// Assistant tool call + tool result
Message call   = new Message(Role.assistant, List.of(),
        List.of(toolCall), null, null);
Message result = new Message(Role.tool, "{\"temp\": 21}",
        null, toolCall.id(), null);

Passing a non-null cancelRequested BooleanSupplier enables cooperative cancel: the implementation checks it between decode steps and throws CancellationException when it returns true (KV is still unbound in finally).

Tokenizer

Tokenizer is the encode/decode interface. Tiktoken is the BPE implementation compatible with OpenAI’s tiktoken (used by LLaMA-3). HuggingFaceBpeVocab loads a HuggingFace tokenizer.json into the same representation.

Java
import smile.llm.tokenizer.Tiktoken;

Tiktoken tok = new Tiktoken(
        pattern,           // regex splitting pattern
        specialTokens,     // map of special-token string → rank
        ranks,             // merged BPE vocabulary (bytes → rank)
        bosId, eosId       // BOS / EOS token IDs
);

int[] ids = tok.encode("Hello, world!", true, true);  // BOS+EOS
String text = tok.decode(ids);
int vocab = tok.size();

Positional Encodings

ClassAlgorithmUsed by
PositionalEncodingSinusoidal (sin/cos, fixed)Original Transformer
RotaryPositionalEncodingRoPE (complex-number rotation)LLaMA

Qwen3.5 uses partial / interleaved multi-axis RoPE (InterleavedMRope, PartialRotaryEncoding) for multimodal positions.

Java
import smile.deep.tensor.Tensor;
import smile.llm.transformer.PositionalEncoding;
import smile.llm.transformer.RotaryPositionalEncoding;

PositionalEncoding pe = new PositionalEncoding(512, 2048);
Tensor out = pe.forward(embeddingTensor);   // adds positional signal

RotaryPositionalEncoding rope =
        new RotaryPositionalEncoding(headDim, maxSeqLen);

Transformer Primitives

smile.llm.transformer holds shared blocks reused by both model families:

ClassRole
AttentionScalar-dot-product attention over the selected backend
FeedForwardSwiGLU-style MLP block
PositionalEncodingSinusoidal encoding
RotaryPositionalEncodingRoPE

LLaMA

A full LLaMA-3 inference implementation in smile.llm.model.llama. High-level entry point: Llama with build(), generate(), and chat().

ClassRole
LlamaModelArgsHyperparameters from params.json / HF config.json
LlamaModelEmbedding + N × LlamaBlock + output projection
LlamaBlockDecoder block: GQA + FeedForward + RMS norms
GroupedQueryAttentionGrouped-query attention with KV-cache and RoPE
LlamaHigh-level entry — load, generate, chat

Loading a Checkpoint

Llama.build supports two on-disk layouts: Meta (params.json plus consolidated.*.pt shards) and HuggingFace (config.json plus *.safetensors, optionally indexed by model.safetensors.index.json). Overloads accept memFractionStatic (SGLang-style static-region fraction for weights + KV) and a KV-cache dtype override.

Java
import smile.llm.model.llama.Llama;

// Loads params.json / config.json + checkpoint weights
Llama llama = Llama.build(
        "model/Meta-Llama-3-8B-Instruct",
        "model/Meta-Llama-3-8B-Instruct/tokenizer.model",
        /*maxBatchSize=*/ 4,
        /*maxSeqLen=*/    2048,
        /*deviceId=*/     (byte) 0           // CUDA:0; -1 for CPU
);

Generation and Chat

Java
import smile.llm.ChatCompletion;
import smile.llm.Message;

// Raw token IDs
int[] prompt = llama.tokenizer.encode("Once upon a time", true, false);
ChatCompletion result = llama.generate(
        prompt, /*maxGenLen=*/ 200, /*temperature=*/ 0.6,
        /*topp=*/ 0.9, /*logprobs=*/ false, /*seed=*/ 42L,
        /*listener=*/ null);
System.out.println(result.content());

// Dialog format
ChatCompletion reply = llama.chat(
        new Message[]{
            Message.system("Be concise."),
            Message.user("Explain RoPE in one sentence.")
        },
        /*maxGenLen=*/ 128, /*temperature=*/ 0.7, /*topp=*/ 0.9,
        /*logprobs=*/ false, /*seed=*/ 0L, /*listener=*/ null);
System.out.println(reply.content());

Streaming

Pass a GenerationListener to receive text chunks as they are produced. Serve wires this through GenerationListeners.toPublisher.

Java
import smile.llm.GenerationListener;

GenerationListener listener = new GenerationListener() {
    @Override public void onText(String chunk) {
        System.out.print(chunk);
    }
};
int[] prompt = llama.tokenizer.encode("Tell me a joke", true, false);
llama.generate(prompt, 200, 0.8, 0.95, false, 0L, listener);

Qwen3.5

Qwen implements the Qwen3.5 hybrid text stack: efficient linear attention (GatedDeltaNet) for most layers and full gated attention for the rest, with optional sparse MoE, a unified thinking / non-thinking mode, and a native vision tower for multimodal input. Checkpoints use HuggingFace tokenizer.json (resolved automatically from the checkpoint directory).

ClassRole
QwenModelArgsHyperparameters from config.json
QwenModel / QwenBlockTop-level decoder and per-layer block
GatedDeltaNet / GatedAttentionLinear vs full gated attention cores
QwenMtpMulti-token-prediction head (speculative decoding)
QwenVisionTower / QwenVlProcessorVision encoder and image/video preprocessing
QwenHigh-level entry — build(), generate(), chat(), complete()
Java
import smile.llm.model.qwen.Qwen;
import smile.llm.ChatCompletion;
import smile.llm.Message;

Qwen qwen = Qwen.build(
        "model/Qwen3.5-8B",
        /*maxBatchSize=*/ 4,
        /*maxSeqLen=*/    4096,   // <= 0 uses the config value
        /*deviceId=*/     (byte) 0
);

ChatCompletion out = qwen.complete(
        "Write a haiku about the sea.", 64, 0.7, 0.9, false, 0L, null);

ChatCompletion reply = qwen.chat(
        new Message[]{
            Message.system("You are a helpful assistant."),
            Message.user("What are the key differences between Java and Kotlin?")
        },
        /*maxGenLen=*/ 512, /*temperature=*/ 0.7, /*topp=*/ 0.9,
        /*logprobs=*/ false, /*seed=*/ 0L, /*listener=*/ null);
System.out.println(reply.content());

Overloads add memFractionStatic, a KV-cache dtype override, KV page size, ParallelConfig (tensor parallelism), and safetensors loader thread count. Speculative decoding (MTP) is enabled by default when MTP weights are present; prefix replay for hybrid models must be opted into explicitly:

Java
qwen.setSpeculativeEnabled(true);    // native MTP draft/verify
qwen.setPrefixReplayEnabled(true);   // radix prefix + DeltaNet restore

Inference Engine

InferenceEngine is the continuous-batching runtime used by serve. It admits work up to maxInFlight while KV pages are free (Fluid Injection), prefills (optionally chunked under a token budget), then runs a batched decodeStep over all decoding requests. GenerationHandle.abort() performs Instant Eviction of queued and in-flight KV.

TypeRole
InferenceEngineScheduler + worker; submit(...) → GenerationHandle
ModelExecutorLow-level bind / prefill / decodeStep (implemented by Llama / Qwen)
GenerationRequestOne job: tokens or dialog, sampling, optional multimodal / tools
GenerationHandleIn-flight handle: future(), abort()
SpeculativeDecodingMTP draft / verify helpers
SamplingTemperature / top-p sampling
Java
import smile.llm.engine.InferenceEngine;
import smile.llm.engine.GenerationRequest;
import smile.llm.engine.GenerationHandle;
import smile.llm.ChatCompletion;

try (var engine = new InferenceEngine(llama, /*maxInFlight=*/ 8)) {
    GenerationHandle handle = engine.submit(
            GenerationRequest.ofTokens(
                    prompt, 256, 0.6, 0.9, false, 0L, listener));
    ChatCompletion reply = handle.future().join();
}

Runtime introspection: queueSize(), inFlight(), kvFreeSlots(), kvFreePages(), activeDecodeCount(), and cumulative timing counters (queueWaitMsTotal(), prefillMsTotal(), decodeMsTotal()). Serve uses one prompt per GenerationRequest with smile.chat.max-batch-size as the in-flight cap; streaming disconnect calls GenerationHandle.abort().

KV Cache & Radix Prefix Reuse

KvCachePool is a paged KV-cache allocator. Pages are the unit of allocation (DEFAULT_PAGE_SIZE = 16 tokens). RadixCache organizes cached prefixes in a compressed radix tree so shared system prompts and conversation histories are computed once and reused across requests.

TypeRole
KvCachePoolPaged KV storage; bind / put / get / finishRequest
RadixCacheLongest-prefix match / insert over token sequences
KvCacheLayoutLayer / head / dim layout descriptor
FlashInferKvMetadataCSR metadata for the FlashInfer attention path
KvCacheExhaustedExceptionThrown when no free pages remain
Java
import smile.llm.cache.KvCachePool;
import smile.torch.ScalarType;

KvCachePool.StaticKvBudget budget =
        KvCachePool.computeStaticKvBudget(...);
KvCachePool pool = KvCachePool.allocate(
        layout, device, ScalarType.BFloat16, budget);
pool.setPrefixReuseEnabled(true);   // radix match/insert

Prefix reuse is enabled per model via Llama.setPrefixReuseEnabled(...) / Qwen.setPrefixReplayEnabled(...).

Attention Backends

AttentionBackends.install(...) selects the process-wide attention kernel at chat-service startup:

BackendNotes
TORCH_NATIVEDefault; pure LibTorch attention
FLASHINFERRequires libsmile_torch built with USE_FLASHINFER; falls back to TORCH_NATIVE with a warning when unavailable
Java
import smile.llm.attention.AttentionBackends;
import smile.llm.attention.AttentionBackend;

AttentionBackends.install(AttentionBackend.FLASHINFER);

Quantization

Weight-quantization backends for memory- and bandwidth-bound inference. The checkpoint format is detected automatically and resolved to a GEMM backend:

QuantFormatMeaning
DENSEDense BF16 / FP16 / FP32 weights
FP8Native FP8 (e4m3 / e5m2) weights + scales
NVFP4Native NVFP4 weights (Blackwell)
GPTQHuggingFace GPTQ INT4
AWQHuggingFace AWQ INT4
Java
import smile.llm.quant.QuantPolicy;

QuantPolicy.Resolved resolved =
        QuantPolicy.resolve(checkpointDir, device, backendOverride);
// resolved.format()  → QuantFormat
// resolved.backend() → WeightGemmBackend
//                    (FP8 / NVFP4 primary, Marlin Ampere failover)

QuantLinearFactory builds the appropriate linear layer (Fp8Linear, Fp8BlockLinear, Nvfp4Linear, MarlinLinear) for the resolved format. Native FP8 checkpoints (e.g. Qwen/Qwen3.8-27B-FP8) need Hopper+ GPUs (sm_90+).

Tensor Parallelism

In-process tensor parallelism for multi-GPU inference. Pipeline axes are reserved for future multi-node PP.

TypeRole
ParallelConfig(tpSize, ppSize, dpSize, devices); single(...), tensorParallel(...)
TensorParallelGroupRank group + collective ops
ColumnParallelLinear / RowParallelLinearSharded linear layers
WeightSharding / TensorShardSpecWeight partitioning
Java
import smile.llm.parallel.ParallelConfig;
import smile.llm.model.qwen.Qwen;

ParallelConfig tp = ParallelConfig.tensorParallel((byte) 0, (byte) 1);
Qwen qwen = Qwen.build(dir, /*maxBatchSize=*/ 8, /*maxSeqLen=*/ 8192,
        (byte) 0, /*memFractionStatic=*/ 0.85, "bfloat16", tp);

Tool Calling & Chat Templates

ChatOptions carries the tools and selection policy for a request; the model injects them into the chat template and parses tool calls out of the output.

Java
import smile.llm.ChatOptions;
import smile.llm.ToolDefinition;
import smile.llm.FunctionDefinition;
import smile.llm.ToolChoice;

ChatOptions options = new ChatOptions(
        new ToolDefinition[]{ new ToolDefinition(new FunctionDefinition(
                "get_weather", "Get the current weather", parametersSchema)) },
        ToolChoice.AUTO,
        /*parallelToolCalls=*/ true);

int[] prompt = model.encodeChat(dialog, options);
TypeRole
ChatTemplateInterface for model-specific prompt formatting
Qwen3ChatTemplateQwen3 template (thinking / non-thinking, tools)
ToolCallParserInterface for parsing tool calls from generated text
JsonToolCallParser / Qwen3XmlToolCallParserConcrete parsers
AssistantTextSanitizerStrips tool-call markup from assistant text

Serving

SMILE Serve is a production Quarkus inference server for classic ML, ONNX, and OpenAI-compatible LLM chat — with a bundled React web UI. See the SMILE Serve page for Docker quick start, configuration, OpenAI-compatible chat completions (including Qwen3+ tool calling), the supported models, and the web UI.

The smile.llm package ships native implementations of Llama-3.1 and Qwen3.5/3.8. All other model families — Phi, Mistral, Gemma, Granite, gpt-oss, DeepSeek, and more — are served through ONNX Runtime GenAI from a genai_config.json package; the full list of supported architectures and GenAI-ready checkpoints is on the SMILE Serve and ONNX GenAI pages.