Repository navigation
v0.12.0
lattice v0.12.0
Minor release with breaking changes, listed under "Breaking changes" with what a caller has to change for each. All of them are in lattice_inference::router_state, the on-disk LoRA router artifact API. Code that does not use that module compiles unchanged.
The headline is faster and smaller CPU embedding on Linux: a SentencePiece tokenizer that takes a third of the memory, an opt-in x86-gemm feature that cuts encoder CPU time per text by a third to two thirds on x86_64, and decode that splits single-row products across cores. The release also adds the EmbeddingGemma 2 vision tower on the CPU, the request and read-out layers for multi-slot decision models, and the next steps of the learned LoRA router.
CPU embedding on Linux
- The SentencePiece tokenizer stores its piece trie in flat arrays instead of a hash map per byte node, and
with_max_seq_lenshares the pieces and the trie instead of copying them. Onparaphrase-multilingual-MiniLM-L12-v2the tokenizer's live heap drops from 127.7 MiB to 38.8 MiB and the whole loaded model's from 148.2 MiB to 59.2 MiB, on an x86_64 Linux host measured aftermalloc_trim(0). Token ids and embeddings are bit-identical, and CPU time per text did not change beyond run-to-run spread (#1933). - New
x86-gemmfeature onlattice-inference, forwarded bylattice-embed, off by default. On Linux x86_64 it sends every multi-rowmatmul_btproduct to thegemmcrate (pinned at 0.18.2) on the calling thread, with one packing allocation per call, and keeps single-row products on the existing kernels. Measured end to end throughNativeEmbeddingService::embedon one core of an AMD EPYC (Zen 4) host, CPU time per text was 0.64x for all-MiniLM-L6-v2 and 0.65x for the multilingual MiniLM-L12 on a short text, and 0.34x to 0.40x on longer texts and batches. Embeddings stay within 2.4e-7 of the default build. The feature also moves decoder prefill ontogemm, so enable it where only encoders run. ADR-099 Amendment 1 records the scope (#1934). - On x86_64, a single-row
matmul_btproduct withn * k >= 524,288now splits its output columns across the current rayon pool, so CPU decode steps use more than one core. The split stays off below the threshold, on a one-thread pool, and inside a rayon worker. Each chunk runs the same kernel as before, and outputs are bit-identical (#1931). - ADR-099 (Proposed) sets how lattice makes f32 CPU embedding fast on Linux x86_64 and aarch64: stock
gemmfirst under a one-allocation-per-call exception, a per-phase threading policy, and CPU and memory budgets for the hosts it runs on (#1926).
EmbeddingGemma 2 images on the CPU
- EmbeddingGemma 2 checkpoints can now embed inputs that mix text and image parts. Images are validated, resized to the 16-pixel patch grid within the patch budget, and run through the vision tower in f32 on the CPU, then bridged into the text model. Vision weights load only when an image is embedded, so text-only use never reads them (#1906).
- Against the Hugging Face reference on a Linux x86_64 CPU, the normalized embedding differs by at most 1.2e-6 per element, with cosine at least 0.99999999998, on both test inputs (#1906).
examples/dump_embeddinggemma2_vision.rswrites every stage to safetensors for comparison with a reference (#1906).
Decision models (System One)
Qwen35Model::prefill_readout(input, slots, rows), documented as unstable: one prefill that returns logits at chosen positions over chosenlm_headrows, with each slot equal to what the single-position scorer computes for that prefix.PrefillInputis#[non_exhaustive](#1915).serve::systemone, under theservefeature: request parsing, a byte-exact port of thedecider-0.8bprompt layout, the checkpoint's owndecider_config.jsonreader, a 255-entry decision label table (option_scoring::resolve_decision_labels), and answer assembly. It adds no HTTP route yet (#1914).- ADR-098 is amended to match the published
decidercheckpoints (#1914, #1915). bench/decision-index/holds a reference table for public decision models on Decision Index edition 0.3, with the scripts that produced it. No row runs lattice yet (#1907, #1909, #1919).
Learned LoRA router
- Router artifacts move to format 5 and record their lineage and admission policy. A state directory can commit a head that selects the served version, and
--router-pintakes<version>or<version>:<hash-prefix>. Format 4 artifacts still load and stay reachable by pin (#1916). lattice router initprovisions an empty router state directory with a deterministic, untrained bootstrap gate, andGET /v1/lorareports the gate'straining_state(#1921).RouterPolicy, new in this release, records the admission policy includingexploration_scale, andAdapterRouter::route_explored(behind themixturefeature) draws the served mixture from a Gaussian around the gate's output and records what is needed to score that draw.route_detailedreturns the same record without a draw;routereturns the same weights as before (#1928).lattice-fann(online-routerfeature) can update the gate from an explored mixture and refit one round with a leave-one-out baseline. Every update entry now refuses inputs that would write a non-finite value into the gate, and the gate write is all-or-nothing (#1917).
Serving fixes
lattice serve --max-cpu-generations <N>(default 2) caps concurrent CPU chat generations. A request over the cap gets HTTP 503server_busyimmediately, and a client disconnect does not release the slot before the generation ends (#1927).- A Qwen3.5 Q4 checkpoint on Metal now refuses
logprobsandtop_logprobswith 400unsupported_featureduring request preparation, instead of failing the request with 500internal_errorduring generation (#1901). lattice_serve'sPOST /v1/lora/loadand/v1/lora/unloadvalidate the request before checking adapter support, so a malformed request gets its own error on a model without adapter support (#1899).- A Qwen
config.jsonwith notext_configobject is read from its top level when it carriesnum_hidden_layersandhidden_size, the shape a text-onlyQwen3_5ForCausalLMcheckpoint writes. Before, lattice substituted the Qwen3.6-35B-A3B MoE preset for such a file. A file with neither is now refused with a message (#1913).
lattice-tune
train_grad_full --gradcheckrefuses--loadand--save, which it used to accept and ignore (#1894).
npm packages
- The npm release job publishes through npm trusted publishing instead of a stored token, and keeps provenance attestations (#1908).
- The native package's prepublish test loads the platform binding from the validated binaries (#1911).
Testing, CI and benchmarks
- The x86_64 embedding-drift job is now required by the parity gate, as the aarch64 job already was (#1922).
- CI runs
train_grad_full's own argument-parser tests (#1896). - The Metal measurement lock contract requires three generation targets to hold the GPU lock across their
generatecalls (#1900). - The test module walkers resolve
#[path]attributes relative to the declaring file, as the Rust Reference specifies (#1895). - The benchmark supervisor protects the Cargo target directory a run actually uses, and refreshes the times of inherited lock files so macOS does not delete them from
/tmp(#1897, #1904).
Breaking changes
All of these are in lattice_inference::router_state, which compiles in every feature set.
resolve_startup(dir, pin: Option<u64>)is nowresolve_startup(dir, pin: Option<&RouterPin>). Build the pin withRouterPin::parse, which takes<version>or<version>:<hash-prefix>(#1916).RouterArtifacthas a new public field,provenance: RouterProvenance, so a struct literal needs it. UseRouterProvenance::Legacyfor an artifact without recorded lineage (#1916).RouterArtifactandResolvedRouterno longer implementEq; they keepPartialEq(#1916).RouterArtifactError::UnknownFormat.knownis aStringinstead of au32, so the error can name both known formats (#1916).ROUTER_ARTIFACT_FORMATis 5.ROUTER_ARTIFACT_FORMAT_LEGACYis 4, and format 4 artifacts still load (#1916).
cargo semver-checks check-release --release-type patch against 0.11.1 reports the provenance field, the two removed Eq derives, and shifted discriminants on RouterArtifactError, whose new variants sit before existing ones. Every variant of that enum carries fields and the enum is #[non_exhaustive], so no caller can depend on those values. The signature and field-type changes above are not covered by its lints and come from reading the diff. lattice-fann, lattice-transport, lattice-embed and lattice-tune report no breaking change.
Upgrading
Code that does not use router_state needs no source changes. Behaviour changes that reach existing callers:
lattice serveon the CPU backend now refuses a third concurrent chat generation with 503server_busyby default. Raise--max-cpu-generationson a host with spare cores and memory (#1927).- A Qwen
config.jsonwithouttext_configloads its own geometry instead of the MoE preset, or is refused when it carries neither shape (#1913). - Metal Qwen Q4 chat requests with
logprobsget 400 instead of 500 (#1901).