0.0.0-alpha.13
Pre-release- IBM Granite 3.x dense support (
GraniteForCausalLM), safetensors and GGUF, verified E2E on granite-3.3-2b-instruct across chat, streaming, concurrency, multi-turn prefix reuse, tool calling, and structured output. - A correctness fix that affected every Llama-family GGUF file (arch
llamaandgranite): the converter's per-head q/k row interleave was never undone for our NeoX-style RoPE, so these models produced fluent but positionally corrupted output. Llama 3.x GGUFs were broken this way since GGUF support landed. - candle 0.11.0 and tokenizers 0.23.1 upgrade. Four Metal-backend issues in candle 0.11 were identified with dedicated repros and worked around; model loading is now two-phase (parallel CPU decode, sequential drained device transfers), which also cut FP8 warmup from 326 s to 0.5 s.
- Quantized GEMV kernels are now bitwise deterministic: the AWQ/GPTQ split-K epilogue accumulated partial sums with float
atomic_fetch_add, whose scheduling-dependent order flipped borderline tokens at temperature 0 roughly one run in five on GPTQ-Int8 checkpoints. - OpenTelemetry integration: OTLP trace export (request-lifecycle span with a decode child span whose offset visualises TTFT) and expanded Prometheus metrics with per-model labels and memory gauges.
New Features
- Granite architecture (
src/models/parsers/hf_parser.rs,src/models/arch_defaults.rs,src/common/block.rs,src/common/config.rs): the four Granite scalar multipliers map onto the blueprint:attention_multiplieris the softmax scale itself (not an inverted divisor),embedding_multiplierscales embeddings, the newresidual_multiplierscales every sub-layer output before its residual add, and the newlogits_scalingdivides the final logits. GGUF metadata keys (granite.attention.scale,granite.embedding_scale,granite.residual_scale,granite.logit_scale) map to the same fields, treating absent and non-positive values as unpublished.GraniteMoeForCausalLMandGraniteMoeHybridForCausalLMare rejected with explicit reasons (fused 3D expert tensors / Mamba2 layers). - OpenTelemetry (
src/telemetry.rs,src/main.rs,src/server/routes/engine_loop.rs):--otel-endpoint(server only) installs an OTLP layer at subscriber init; each request carries a lifecycle span with model, token counts, and finish reason, plus adecodechild span from first token to completion. Prometheus metrics gained per-model labels, TTFT histograms, and memory gauges driven by the engine loop.
Reliability and Correctness
- Llama-family GGUF q/k de-interleave (
src/common/gguf_weights.rs::depermute_qk_rows,ArchDefaults.gguf_qk_permuted):convert_hf_to_gguf.pyreorders each head's q/k projection rows from the HF[first_half | second_half]layout to the interleaved layout llama.cpp's paired-rotation RoPE expects; our RoPE is NeoX/HF split-half. The de-interleave is a pure row-wise byte shuffle, valid for every GGML dtype because quantization blocks run along the input dimension. Verified: bartowski/Llama-3.2-1B-Instruct-Q4_K_M now answers byte-identically to its meta-llama safetensors twin; before the fix it misread digits and garbled dates while remaining fluent. - GGUF pre-tokenizer repair (
src/tokenizer.rs): candle maps unknowntokenizer.ggml.prekinds ontoByteLevel::default(), whoseadd_prefix_space = trueinjects a spurious space at the start of every segment after a special token (<|start_of_role|>userencoded the role asuser).refact,starcoder, andgpt-2now get ByteLevel without the prefix space;llama-bpe(what llama.cpp actually writes for Llama 3, which candle does not match) gets the Llama 3 split regex with digit runs capped at three. Encoding verified identical to the referencetokenizer.jsonand tollama-tokenize. - Multi-variant GGUF directories (
src/models/loader.rs::find_gguf_files): previously only the first.ggufin a directory was returned, so a Q4_K_M + F16 pair silently resolved every request to the first file (or fell through to a sibling safetensors model). All variants are now listed and resolved by stem; ambiguous requests fail with the available variants named. - Deterministic split-K GEMV (
src/common/quant_kernels.metal,src/common/metal_ops.rs): every k-split of an output column now lands in one threadgroup and reduces in threadgroup memory in a fixed order with a single writer per output. Geometry is selected per shape class (AWQ M=1 adapts topacked_out; batch kernels reduce one round per activation row). The retired*_atomickernels remain solely for the paired micro-benchmark (quant_gemv_bench), which interleaves det/atomic samples so GPU timing drift cancels per pair. Measured cost: GPTQ at parity (74.6 to 74.8 tok/s), AWQ minus 3.7% (36.8 to 35.5). A 48-run bitwise determinism test fails on the atomic epilogue and passes on the deterministic one; E2E reruns are hash-identical (10/10 GPTQ, 8/8 AWQ). - Tool calls (
src/server/routes/chat.rs::parse_tool_call_tag_blocks):<tool_call>blocks carrying JSON payloads now parse into propertool_calls, covering both the canonical single{"name", "arguments"}object the Qwen2.5/Qwen3 templates instruct and the{"tool_calls": [...]}shape models emit when echoing the injected system-prompt instruction. Parallel blocks, unknown-function filtering, and reasoning text before the block are handled; verified E2E on Qwen2.5-1.5B emission and streaming. - KV budget diagnostics (
src/models/manager.rs): without--memory-budgetthe budget derives from the memory free at startup, which under system pressure can start near zero and fail every load with an unexplained "KV cache budget exhausted"; the failure now names the derived budget and the remedy.
Dependencies
- candle-core 0.10.2 to 0.11.0, tokenizers 0.22.2 to 0.23.1. The four candle 0.11 Metal issues and their mitigations (
src/common/weights.rs): (1)MTLResidencySetis mutated without a lock, so concurrent tensor creation from rayon loader threads leaves buffers non-resident and the GPU reads zeros: all device-touching loader calls now serialize throughmetal_alloc_lock. (2) F8E4M3 ships with no Metal cast kernels ("not implemented" on contiguous, garbage on strided): FP8 checkpoints dequantize entirely on CPU at load, and a dedicatedcast_f8e4m3_f32kernel (bitwise-verified against the CPU cast over all 256 encodings) covers runtime widening. (3) Host-to-device copies queue as GPU commands and staging buffers are reclaimed only at sync points, so loading a multi-GB checkpoint without syncing exceeds the Metal working-set limit and failed command buffers silently zero the weights:ModelWeights::loadis now two-phase, with parallel CPU-only reads/casts/scale-folds followed by sequential device transfers drained every 16 tensors. (4) Synchronizing from one thread while another encodes corrupts state: the loader never does. TheCANDLE_METAL_COMMAND_POOL_SIZE=1workaround from alpha.12 is removed; candle 0.11's per-encoder hazard tracking replaces it (FP8 output re-verified byte-identical). The GGUF tokenizer builder is vendored intosrc/tokenizer.rs(candle 0.11 pins tokenizers 0.22, this project ships 0.23), folding the pre-tokenizer repairs into its table. - Security and unsoundness bumps: crossbeam-epoch 0.9.20 (RUSTSEC-2026-0204, also failing CI's cargo audit), quinn-proto (RUSTSEC-2026-0185), and anyhow/memmap2/rand advisories.
Documentation
- Rustdoc pass across modules; comment cleanup pass (stale historical notes removed, the repeated Metal staging-buffer rationale consolidated into the two-phase note in
ModelWeights::load).
Tests
- 312 unit tests green (294 in alpha.12.2). New coverage: Granite arch regression (including
logits_scalingdivision semantics), de-interleave inversion against the converter's permute, GGUF pre-tokenizer no-prefix-space matrix, K-quant GGUFmul_mmprefill parity and odd-N M=1 GEMV parity (previously untested kernels), F8E4M3 spec-decode and Metal/CPU cast equivalence, 48-run GEMV bitwise determinism, and JSON-in-tags tool-call parsing. - A/B regression campaigns against baseline binaries at each step: 33 local models, two prompts each, after the Granite work (57/66 byte-identical, 8 expected differences: Granite newly supported, Llama GGUF previously corrupted); light sweep after the candle upgrade (46/48 byte-identical, 2 = a stable borderline-token flip on Llama-3.2-1B GGUF, output correct); heavy sweep one model at a time under memory barriers (OLMoE, Phi-3.5, Ministral, Qwen2.5-3B, Qwen3.5-4B, gpt-oss-20b: all byte-identical except one benign paraphrase flip on OLMoE, verified with an extended battery). FP8 probes byte-identical with 10/10 run stability.
Installation
macOS (Apple Silicon)
curl -fsSL https://raw.githubusercontent.com/giovannifil-64/oxydllm/main/install.sh | shLinux x86_64 (CUDA — Ada / Hopper / Blackwell / Blackwell Ultra / Blackwell Desktop)
curl -fsSL https://raw.githubusercontent.com/giovannifil-64/oxydllm/main/install.sh | shLinux arm64 (CUDA — GH200 / GB300 / Jetson Thor / DGX Spark)
curl -fsSL https://raw.githubusercontent.com/giovannifil-64/oxydllm/main/install.sh | shInstaller auto-selects the right binary from detected GPU compute capability.
Override with OXYDLLM_CUDA_TARGET=<ada|hopper|blackwell|blackwell-ultra|blackwell-desktop> (x86_64)
or OXYDLLM_CUDA_TARGET=<hopper|blackwell|blackwell-ultra|thor|blackwell-desktop> (arm64).
Linux (CPU only)
curl -fsSL https://raw.githubusercontent.com/giovannifil-64/oxydllm/main/install.sh | OXYDLLM_NO_GPU=1 shDocker (CUDA)
docker run --gpus all -p 11313:11313 \
-v ~/.oxydllm/models:/root/.oxydllm/models \
ghcr.io/giovannifil-64/oxydllm:latest startx86_64 explicit CUDA tags: :cuda-ada, :cuda-hopper, :cuda-blackwell, :cuda-blackwell-ultra, :cuda-blackwell-desktop.
ARM64 explicit CUDA tags: :cuda-hopper-arm64, :cuda-blackwell-arm64, :cuda-blackwell-ultra-arm64, :cuda-thor-arm64, :cuda-blackwell-desktop-arm64.
Docker (CPU)
docker run -p 11313:11313 \
-v ~/.oxydllm/models:/root/.oxydllm/models \
ghcr.io/giovannifil-64/oxydllm:cpu startSHA-256 checksums
See the .sha256 files attached to this release.
Full Changelog: nightly...0.0.0-alpha.13