Repository navigation
lattice v0.15.0
Minor release. The lattice-inference crate is split into a set of smaller crates, and it stays the facade: every public path it had in 0.14.0 outside the serving stack still resolves, now as a re-export (the serving stack moves to lattice-serve; see Breaking changes). Library crates hold grammar-constrained decoding, weight storage, quantization, the Metal runtime, model files, the host-side compute kernels and the model interface. Each model family moves into its own crate under crates/models/: BERT, EmbeddingGemma 2, Qwen3, Gemma 4, ERNIE 4.5 (with PaddleOCR-VL) and Qwen3.5. The serving stack, the lattice CLI and the Q4, QuaRot and MoE admission tools move to the new lattice-serve crate, so a default lattice-inference build no longer compiles axum or clap. lattice-embed now depends on the model-family crates directly instead of on the facade.
Breaking changes
- The HTTP serving stack and the
latticeCLI move out oflattice-inferenceinto the newlattice-servecrate (#2227).lattice_inference::serve,lattice_inference::serving_providerand theservefeature are removed. Depend onlattice-serveand import the same items fromlattice_serve::serveandlattice_serve::serving_provider; the paths below the crate root are unchanged. The undocumented#[doc(hidden)]modulesserving_cpu,serving_cpu_host,serving_embeddings,serving_factory,serving_hostandserving_worker, which 0.14.0 compiled underserve, move tolattice-serveunder the same names. Install the CLI withcargo install lattice-serve. - The
quantize_q4,quantize_quarotandmoe_admission_simbinaries move fromlattice-inferencetolattice-serve(#2252). Build or install them fromlattice-serve. lattice_embed::simdis now the newlattice-simdcrate, re-exported under the same path, so every item keeps its name. The prepared quantized-tier functions that return aResultnow returnlattice_simd::Resultwith the errorlattice_simd::SimdErrorinstead oflattice_embed::Result.SimdErrorhas the sameTierMismatchandInternalvariants, andlattice_embed::EmbedErrorimplementsFrom<SimdError>, so?in a function returninglattice_embed::Resultstill compiles; code that matches the error value namesSimdErrorinstead. (#2360)- The unused sealed embedding attestation API is removed from
lattice-embed:AttestationAlgorithm,CheckpointAttestor,NativeResourceBudgetandSupplementaryAttestationEvidence(at the crate root and underservice),service::prepared::MAX_SUPPLEMENTARY_ATTESTATION_EVIDENCE_BYTESandservice::prepared::MIN_SUPPLEMENTARY_ATTESTATION_EVIDENCE_BYTES, and theEmbedErrorvariantsAttestationReportSizeandResourceBudgetOverflow. These 0.14.0 paths no longer resolve (#2344). - The unused Qwen3-VL vision scaffold is removed from
lattice-inference:vision::config,vision::merger,vision::preprocess,vision::vit,VisionConfig,VisionEncoderandVisionOutput. These 0.14.0 paths no longer resolve. The Qwen3.5 vision encoder inlattice-model-qwen35is unrelated and unchanged (#2337). - The
#[doc(hidden)]and documented-unsupportedlattice_compute::__internal::x86_gemm_dispatch_enabledis removed. LoraAdapter::save_safetensorsinlattice-tunerefuses a trained module outside the supported text layout (self_attn,linear_attnandmlpprojections), naming the module. Before, such a module was written underself_attn, so a BERT adapter (query,key,value,attn_output,ffn_intermediate,ffn_output) was saved in a layout no PEFT reader places. Files saved by 0.14.0 still load. (#2383)lattice-model-qwen3-embeddingreads only top-levelconfig.jsonfields, so nested keys and string values cannot supply model settings. Malformed or non-object JSON, duplicate recognized top-level keys and out-of-range JSON numbers are refused (#2508).- BERT configuration loading now parses fields from the JSON root and refuses malformed JSON, a non-object root or duplicate recognized top-level keys.
BertConfig::from_json_stris available fromlattice-arch, with the existing model-crate path preserved (#2512). - BERT configuration loading refuses zero dimensions, hidden sizes that are not divisible by the attention-head count, and non-finite or non-positive layer-normalization epsilon values, including configurations inferred from safetensors shapes (#2515).
- No other change breaks a 0.14.0 path at default features. cargo-semver-checks ran against 0.14.0 with default features only (non-default features were not checked) and passes
lattice-transport,lattice-inference-coreandlattice-tokenizer. It does not follow re-exports of items defined in another crate, so for the crates that now re-export moved items it reports paths as missing that still resolve; every path it flagged was therefore also imported from a downstream crate built against this release. Forlattice-fannall 21 flagged paths resolve, re-exported fromlattice-lora, and forlattice-tuneall 4 do (LoraLayerandapply_lora, also re-exported fromlattice-lora). Forlattice-embed50 of 62 resolve through thelattice-simdre-export; the 12 that do not are the attestation items above. Forlattice-inference752 of 980 resolve through the facade's re-exports; the 228 that do not are theservemodule's items (207), thevisionmodule (20), and oneserving_provideritem, all listed above. The crates first published in this release have no baseline. - cargo-semver-checks has no check for a changed parameter, return or field type, so the public signatures in the rustdoc output of 0.14.0 and of this release were also compared at default features (
lattice-model-ernie45with thepaddleocr-vlandimagefeatures that thelattice-inferencedefaults enable). No item that 0.14 exported changed its signature, including the items that moved to a new crate.
Deprecations
Each of these keeps its 0.14 signature and behaviour through 0.15 and names its replacement in the deprecation note. ADR-103 Amendment 21 schedules the removal of the items that name a model type for 0.16.0.
SafetensorsFile::load_bert_weights,load_cross_encoder_weightsandload_qwen_weights, andShardedSafetensors::load_qwen_weights_owned, onlattice-weights: use the model layer'sweight_loadingfunctions or the neutral tensor reads (#2286, #2283, #2285).GatedDeltaNetState::new(takes the Qwen3.5 configuration): useGatedDeltaNetState::from_geometrywith aGdnGeometry(#2270).LoraHook::validate_againstandvalidate_against_bert: implementLoraHook::validate_geometryagainst aLoraTargetGeometry(#2271).FlatKVCacheConfig::for_qwen3: useFlatKVCacheConfig::new, which takes the same arguments (#2273).plan_prefix_reuse: useplan_prefix_reuse_for_slot, which also declines an entry retained for another slot (#2318).
Separately, lattice_inference::backward::simd is deprecated in favour of lattice_inference::backward::dispatch and scheduled for removal in 0.16.0. The test-utils-gated compatibility path preserves the current exports; ForcedDispatch::mark_executed now refuses caller-written execution markers, whereas 0.14 accepted a matching marker. (#2364)
New crates
lattice-archholds the model interface: the model configuration parsers,QwenConfigand the Qwen3.5 tensor names (#2228, #2232, published under this name after the rename in #2267); the generation and sampling types,StopReason, stop strings and the decode budget (#2268); the decoder session contract and the shared generation driver, public as of this release (#2269, #2272); the text encoder contract (#2274); the model family registry and the loaded decoder contract (#2275); the shared generation admission and seed helpers (#2295). The contracts are public and may still change in a minor release.lattice-grammarholds grammar-constrained decoding: GBNF parsing, the grammar engine and token masks. Itsbench-internalsfeature carries the profiling items, and the facade'sbench-internalsforwards to it (#2104, ADR-103 Amendment 2 in #2105).lattice-weightsholds weight storage, safetensors and the other checkpoint formats, half-precision decoding, and borrowed tensor views for model-neutral reads (#2111, #2278).lattice-quantholds the quantization primitives, including the QuaRot Hadamard transforms (#2113), the QuaRot conversion (#2233) and model-neutral rotation targets (#2281).lattice-computeholds the host-side compute of the forward passes: RoPE tables, KV caches, LoRA hooks, numerical diagnostics, the CPU forward and attention kernels, pooling, the GatedDeltaNet kernels, the Gemma 4 scaled embedding and logit softcap, and the scalar vision helpers (#2244-#2251, #2258-#2260, #2296). Its kernels take model-neutral inputs:AttentionProjectionsfor attention (#2273),GdnGeometryfor GatedDeltaNet (#2270),LoraTargetGeometryfor LoRA validation (#2271).lattice-model-ioholds what every model uses to find its files: the model cache directory, canonical model names, checksum verification, the cache-miss download behind thedownloadfeature (refused underLATTICE_OFFLINE), the boolean engine-switch reader (#2262), and the shared multimodal prompt inputMultimodalInput(#2306).lattice-simdholds the SIMD vector math that waslattice_embed::simd: dot product, cosine similarity, Euclidean and Manhattan distance, normalization, and INT8, INT4 and binary quantized vectors with tiered prepared queries. It has no internal dependencies, so a vector index can depend on it without the embedding models.lattice-embedre-exports it aslattice_embed::simd. (#2360)lattice-loraholds the LoRA adapter descriptor and its validation: the target-module allowlist, the effective-scale rule, the alpha, rank and element budget checks, and the blend planner (#2380), and since #2383 also the LoRA projection geometry, the host factor records with the scalar CPU apply and effective-weight blend, and, under its optionalsafetensorsfeature, the PEFT and MLX adapter file codec. It has no internal dependencies, and none at all without that feature.lattice-fannre-exports it at its published paths,lattice_fann::LoraDescriptorandlattice_fann::lora::*,lattice-tunekeepslattice_tune::lora::{LoraLayer, apply_lora}as re-exports, andlattice_compute::lora_hookre-exports the geometry items (#2380, #2383).lattice-routerholds request-level adapter routing: the on-disk gate state, bootstrap provisioning, recorded routing decisions and feedback, thelattice-fannbacked mixture router, bounded refits and held-out admission evidence.lattice_inference::{router_state, router_init, router_episode, router_refit, router_admission, mixture}stay as re-exports under the same feature gates (#2334).lattice-backwardholds the reverse-mode gradients used for LoRA training: the operation tape, the backward operations, GQA attention backward, gradient checks and the GatedDeltaNet backward.lattice_inference::backwardandlattice_inference::attention::gdn_backwardstay as re-exports undertrain-backward(#2336).lattice-forecastholds Chronos-2, Reverso and Toto 2.0 forecasting and their shared context preparation, with the same module layout and public items.lattice_inference::forecaststays as a re-export (#2332).lattice-metal-runtimeholds the shared Metal runtime (device, buffers, pipelines, argument encoding) and the shared Metal shader sources.lattice-inferencedepends on it only undermetal-gpu. The crate's library is empty off macOS (#2217, #2279, ADR-103 Amendment 3).- Model-family crates, each enabled in
lattice-inferenceby a default-on feature of the same name, with every 0.14 facade path kept as a re-export and a facade test that checks it names the family type:lattice-model-bert: the BERT and BGE encoders, the cross-encoder reranker and their weight loaders (#2293; featurebert).lattice-model-embeddinggemma2: the EmbeddingGemma 2 text and image encoders (#2284; featureembeddinggemma2).lattice-model-qwen3-embedding:QwenModel,QwenEncoderand the Qwen3 Metal forward pass (#2297, renamed fromlattice-model-qwen3in #2331; featureqwen3).lattice-model-gemma4: Gemma 4 CPU generation, its KV cache and preflight checks (#2298; featuregemma4).lattice-model-ernie45: the ERNIE 4.5 decoder on CPU and Metal, and PaddleOCR-VL behind the crate's ownpaddleocr-vlfeature (#2303, #2310, ADR-103 Amendment 23 in #2292; facade featuresernie45andpaddleocr-vl, which impliesernie45).lattice-model-qwen35: the Qwen3.5 model, its CPU, f16, Q8, NEON and Metal paths, generation sessions, speculative decoding, the MoE expert cache, the vision encoder and LoRA loading (#2312; featureqwen35).
lattice-serveholds the HTTP serving stack, thelatticeandlattice_servebinaries and the Q4, QuaRot and MoE admission tools (#2227, #2252, ADR-103 Amendments 4 and 16). See Breaking changes.
Performance
- Qwen3.5 attention accumulates cached values contiguously across each token's head dimensions on CPU f16, Q8, packed-Q8, NEON and speculative paths, while preserving ascending token summation and zero-weight handling (#1971).
- CPU prefill attention packs each query tile once and reuses each packed K/V tile across the tile's query rows, scoring a whole tile through the matrix multiplication backend instead of a scalar dot product per query row and key; masking, the online softmax order and nonfinite-input behaviour are unchanged (#1981).
- Eager Metal MoE layers route on the device: the router, top-k, weight renormalization and the shared-expert gate run as kernels inside the token's command buffer, so decode no longer waits on a host read per MoE layer. Cached Q4 expert storage keeps the host router, and
LATTICE_MOE_HOST_ROUTER=1forces it everywhere for comparison (#2214, ADR-053 amendment D3' in #2212). - Gemma CPU prefill evaluates long prompts in chunks of at most 64 tokens, batching the Q/K/V/O, GeGLU and per-layer embedding projections; causal and sliding-window attention per query are unchanged. Short prompts and prompts with invalid token ids stay on the sequential path (#1986).
- CPU prefill carves its thirteen phase buffers out of one arena sized to the largest phase instead of their sum; an oversized shape is refused before any scratch is resized (#1979).
NativeEmbeddingServicecomputes the BGE-small embedding-space identity once, when the model loads, instead of rebuilding and hashing the descriptor on every identified embedding call (#2365).
Correctness
- PaddleOCR-VL reports an out-of-range text id as an error instead of panicking, and refuses mismatched placeholder and encoder-row counts before changing embeddings. EmbeddingGemma 2 also validates image-row placement before writing it (#2503).
- Persistent Qwen embedding cache files include the SHA-256 of the loaded base-model revision in their name, so checkpoints with the same model name no longer overwrite one another's cache. When the model or tokenizer revision is unknown, only the in-memory cache is used (#2509).
- Reading a LoRA adapter with a maximum byte limit of
u64::MAXnow returns the file contents instead of overflowing the read limit (#2383). lattice-tuneaccepts adapter factors only from supported text-layer namespaces and recognized LoRA factor suffixes; factors from other towers or with unsupported suffixes now fail with the offending key. Unknown projection names under supported blocks are skipped, and the loaded adapter lists them inLoraAdapter::skipped_factor_keys. The MLX factor keyslora_aandlora_bload with or without a trailing.weight, as in 0.14.0 (#2383).- The CPU attention softmax behind the BERT-family encoders computes
expaccurately in its scalar, NEON and AVX2 arms instead of with a bit-trick approximation. The approximation's error varied within each power-of-two interval, so it did not cancel when a row was normalised. Against the sentence-transformers reference vectors, the CPU encoder's minimum cosine rises from 0.99987 (bge-small-en-v1.5) to at least 0.999998 on every f32 model, and the parity test's floor rises from 0.9990 to 0.99999. Embeddings from 0.15.0 therefore differ slightly from those of 0.14.0 on the same input. Cost on x86_64 with AVX2 and FMA: the softmax kernel takes about 1.6x as long, a 512-token encode about 2.4% to 3.5% longer, a 128-token encode under 1%. The NEON arm is not measured (#2234). - With thinking disabled, a Qwen3.5 chat continuation on Metal reuses the cross-turn prefix from the previous turn's start instead of refilling the whole prompt (#2322).
- The paged KV cache compares the stored token ids of a promoted prefix with the request before restoring it, so two different token sequences that share a hash no longer restore each other's pages (#2311).
- Cross-turn reuse planning declines an entry retained for another slot (#2307), and the prefix page cache keeps the other entries' LRU order when it refreshes one (#2323).
- On the Metal decode route, MoE layers routed from a hidden state the GPU had not yet written, so experts and the shared-expert gate were chosen from the previous kernel's output. Routing now reads the completed post-attention norm (#2213).
EmbeddingTable::write_rowderives the row width from the table and panics before any write when the row id is out of range or the destination is not exactly one row wide, on both the f16 and Q4 arms. The f16 table checks its geometry against the buffer at construction (#2109).- The BERT and Qwen weight loaders report a rank-0 tensor as
InferenceError::ShapeMismatchinstead of panicking with an index out of bounds (#2299).gemma4_scaled_embeddingnames the out-of-range id, its position and the row count when it panics (#2309). LoraAdapter::validate_against_bertinlattice-tunereads its projection shapes from the same table asvalidate_geometry, so the two checks cannot disagree; behaviour and messages are unchanged (#2313).- The Qwen3.5 Metal LoRA loader,
lattice_model_qwen35::lora_file::load_lora_safetensors, whichchat_metal --loraandlattice serve's/v1/lora/loaduse, now uses the same decoder and the sameadapter_config.jsonrules aslattice-tune. In 0.14.0 it accepted only thebase_model.model.model.layers.andmodel.layers.key prefixes and theself_attnandmlpblocks, so a PEFT adapter trained on the Hugging Face Qwen3.5 checkpoint, which names its tensorsbase_model.model.model.language_model.layers.<i>.…, failed with "no LoRA tensors found", andlinear_attnfactors were dropped. Both layouts now load. It also read alpha only from the safetensors header, accepted a non-finite alpha, fell back to the rank for an unparsable one, and kept one of two tensors for the same factor. Those inputs are now refused; a duplicate is refused with a message naming the two keys. A factor on a GatedDeltaNet projection the Metal path does not apply (in_proj_a,in_proj_b) makes the adapter load fail instead of being dropped. The load report names the alpha source and any skipped tensors (#2387, #2383). FlatKVCache::try_newreturnsInferenceError::InvalidInputinstead of panicking in the allocator when a per-layer buffer or the layer vector would exceedisize::MAXbytes;FlatKVCache::newis unchanged (#2375).- On macOS,
matmul_into,sgemm_bt_strided,sgemm_nn_stridedandsgemm_nn_abno longer pass empty shapes to Accelerate, which aborted the process on a zero leading dimension:m == 0orn == 0writes nothing, andk == 0zeroes the output rows (strided andmatmul_into) or computesbeta * C(sgemm_nn_ab) (#2357, #2377). - The portable GEMM fallbacks follow the same BLAS rules for empty shapes and zero
alpha/beta, including overwriting a non-finiteCwhenbetais zero (#2381). - The standalone WGPU GEMM (
gpu_matmul,gpu_matmul_bt) returnsfalsefor recoverable failures instead of panicking: sizes are checked against the device limits before any buffer is allocated, and a failed mapping leaves the output untouched (#2352). lattice-tune's safetensors adapter reader refuses a file with two tensors for the same factor (for example….q_proj.lora_A.weightand….q_proj.lora_A), even when their bytes are identical, and a factor pair whose A and B use different key forms (PEFTlora_A/lora_Bagainst MLXlora_a/lora_b). Before, the decoder kept whichever duplicate it read last, in the order the file's tensor names came out of a hash map, and transposed only the MLX half of a mixed pair. Both now fail withTuneError::Serializationnaming the two keys. (#2383)lattice-tune::lora::load_peft_safetensorsreads the PEFTadapter_config.jsonbeside a PEFT-form adapter file and takes itslora_alpha. Before, a PEFT export, whose header carries no alpha, loaded with alpha equal to the rank; for an export with r=16 andlora_alpha=32 that applied the adapter at half its trained strength. The config'srmust equal the factor rank, itstarget_modules(a list, or a pattern matched against the whole module path) must cover every loaded module and itsexclude_modulesmust match none, and a headeralphamust equallora_alpha; each disagreement fails withTuneError::Validationnaming both values or the module. A config using rsLoRA, DoRA, rank or alpha patterns, trained biases,modules_to_saveor trainable tokens is refused rather than approximated. A missing config keeps the previous behaviour; an unreadable or malformed one is an error. MLX-form files andload_peft_safetensors_bytes, which governed admission uses, read no config. (#2383)- Qwen3.5 self-speculation on Metal truncates its draft to the remaining generation room before verification. With little room left, a verification could advance the KV cache past the tokens the round committed (#2495).
API
lattice-archexposes the unstableargmax_f32_first_wins_usizehelper used by the scalar sampler and shared by Gemma 4, Qwen3.5 speculative decoding and LoRA training. It keeps the first index on ties, ignores NaNs and returns zero for empty or all-NaN input (#2507).lattice_arch::qwen_config::QwenConfig::from_json_strprovides the Qwen embedding configuration parser alongside its configuration type; the model loader retains the built-in 0.6B fallback whenconfig.jsonis absent (#2510).LoraAdapter::skipped_factor_keysnames the factor tensors a file load skipped for an unrecognised module, andLoraAdapter::alpha_rank_defaultreports whether the file declared no alpha, so alpha equals the rank (#2383).BertModel::from_bytes_with_max_tokensloads a BERT model from memory with a caller-chosen input token cap instead of the one derived fromconfig.json; it is marked unstable in its documentation (#2230).NativeEmbeddingService::embed_identifiedreturns vectors with an embedding-space identity (ADR-104) and, when known, the backend that produced each vector.IdentityModeandIdentifiedEmbeddingsdescribe the consumed model, tokenizer and effective settings. A Known ID binds the consumed config, tokenizer and weights to the recipe that produced the vectors, so equal IDs identify the same space. BGE small, base and large, multilingual E5 small and base, all-MiniLM-L6-v2, paraphrase-multilingual-MiniLM-L12-v2, Qwen3-Embedding and EmbeddingGemma 2 declare recipes (#2354, #2365, #2366, #2399, #2400, #2401, #2402, #2404). BGE small supportsHeadermode; other models are identified only inFullmode, which hashes every consumed weight file. Header mode does not hash tensor bytes, so edits confined to tensor data do not change its identity. BERT identities use output revisionbert-native-output-v2, so their IDs differ from the earlier prerelease recipe; the API was not in v0.14.0. The backend is result metadata rather than an identity input, so CPU implementations across hosts and Metal on one load share an ID (#2403); the cross-host prediction and drift floor are recorded at row L2 ofdecisions/LEDGER.md. Models without aconfig.json, including managed downloads, reportUnavailable; load from a directory holding the checkpoint's config to get a Known ID.EncoderIdentity::RAW_PROMPT_CONVENTIONnames the default"raw"prompt convention (#2288).
Serving
- A non-streaming chat completion now returns 500
internal_errorif the worker closes before sending its completion event, instead of returning partial text with zero usage as a successful response (#2413). - Streaming chat completions on both servers honour
stream_options.include_usage. When it is true, each chunk carriesusage: nulland a completed stream ends with one usage-only chunk holding the engine's prompt and generated token counts, before[DONE]. A failed or interrupted stream does not send it. Without the option the stream is unchanged.stream_optionson a non-streaming request is now refused with 400unsupported_feature(earlier versions ignored the field). Unknown fields insidestream_optionsare ignored. (#2388) - Each generation provider admits a
lattice servecheckpoint into a route plan that owns its selection marker, its implicit pooled-embedding action and its CPU or Metal open path; startup reads those from the selected provider (#2102). - Loading a LoRA adapter through
lattice-servewhose factor tensors name modules the decoder does not support is now refused with 400lora_load_failed, naming the first eight skipped tensors in sorted order and how many more there are. Earlier versions dropped those factors, printed a note to standard error and loaded the rest.lattice-model-qwen35addsload_lora_safetensors_with_report, which returns aLoraLoadReport(effective alpha, its source, the skipped tensor names) and prints nothing;load_lora_safetensorsis unchanged.lattice-inferencere-exports both. (#2420)
Build and dependencies
lattice-embedbuilds itssimdbenchmark and native-only integration tests only when thenativefeature is enabled, so--no-default-features --all-targetsbuilds no longer fail on targets that require native embedding support (#2464).lattice-lora'ssafetensorsfeature addsserde_jsonandregex, to read PEFTadapter_config.jsonand evaluate itstarget_modulespatterns.regexwas already in the lockfile (#2383).lattice-inferencegains default-on featuresbert,embeddinggemma2,qwen3,gemma4,ernie45,paddleocr-vlandqwen35, one per model family (#2284, #2293, #2297, #2298, #2303, #2312). With a family's feature off, that family's modules are not compiled and the targets that reach them require the feature. Default builds include every family.lattice-embeddepends onlattice-model-bert,lattice-model-qwen3-embedding,lattice-model-qwen35andlattice-model-embeddinggemma2instead of onlattice-inference, so a normal build of it no longer compiles the facade (#2315). Its manifest documents which crate owns each forwarded feature (#2321).- The native Node.js binding imports
BertModelfromlattice-model-bert(#2320). lattice-tokenizergains a default-onparallelfeature; with it off, WordPiece batches run serially and the crate has no Rayon dependency (#2216).- PNG and JPEG decoding moves behind a new
imagefeature onlattice-inferenceandlattice-embed, on by default. With it off, the image decode entry points keep their signatures and return an error naming the feature (#2103). tracingis requested once from the workspace withdefault-features = false, features = ["std"], andlattice-embedinherits that request instead of tracing's defaults (#2218, #2224).lattice-embedbuilds clean under--no-default-featuresagain: the native batch helpers are gated onnative(#2225).- The lockfile takes tokio 1.53.3 for RUSTSEC-2026-0386, an informational unsound advisory on tokio's
AsyncFdconstructors; no workspace code usesAsyncFd(#2153). lattice-inferenceno longer depends onclap: argument parsing lives with the CLI inlattice-serve(#2155, #2227).- Building with default features on newer stable toolchains no longer warns about unread
ChatMessagesfields off Metal (#2099). lattice-servedeclares theserde_jsonraw_valuefeature its chat request contract uses; it had compiled only because axum enabled it. Cargo.lock is unchanged (#2418).- A
f16,metal-gputest build oflattice-model-qwen35no longer compiles two unread Metal ViT counter fields (#2379).
Internal reorganisation
- The Metal Qwen3.5 engine is split into submodules by responsibility (#2115-#2123, #2125-#2133), and encoder helpers, checked buffer allocation and pipeline construction are shared across call sites (#2144, #2145, #2147). These now live in
lattice-model-qwen35. Public paths are unchanged. lattice_serve's structured-output code (#2136), the batched prefill kernels (#2140) and the CPU driver allocation group (#2141) move into their own files. The file-size gate now exempts no source file from its 3,000-line cap; 0.14.0 exempted nine.- The Qwen3 WGPU forward graph and its shaders move to
lattice-model-qwen3-embedding, and the BitNet ternary kernels and the standalone WGPU GEMM tolattice-compute;lattice_inference::forward::{gpu, bitnet_kernel, gpu_gemm}re-export them (#2349, #2339). Continuous-batching scheduling moves tolattice_compute::batch, with the facade paths kept as re-exports (#2335). lattice_router::serving_support::install_recorded_softmaxinstalls a recorded softmax policy for bothlattice-serveand the router's test stand-in (#2367); the CPU softmax's three vector exp arms share one set of constants, with release code generation unchanged on x86_64 and aarch64 (#2382).
Benchmarks and tooling
scripts/embedding_st_adapter.pycreates ordered JSONL reference embeddings from a local BGE-small sentence-transformers snapshot without downloading files. It verifies the caller-supplied SHA-256 manifest and model configuration, and refuses inputs longer than the model's 512-token limit (#2459).bench-comparebuilds the inference bench before every arm's load sampler starts, including the control arms (#2208), and gives each arm its own Cargo target directory when the caller sets one, so base and head can no longer run one shared executable (#2235, #2236).- On Linux, the in-phase load sampler reads per-process CPU over the same one-second window as the machine idle share, so a bench that briefly fans out over every core is no longer booked as foreign load (#2266).
lattice-fann's router experiment runs as an example rather than a bench target (#2226).bench-compareaccepts bench targets whose group names contain/(#2124), and reports an embed prebuild failure with cargo's real exit status (#2112).- Every crate README carries a generated reference section (facts, source layout, statistics), refreshed from main by CI (#2287).
- The decode harness run-record schema moves into its own module (#2142); the bench supervision, GPU-handoff launch and perf-gate self-tests are split into focused files (#2138, #2139, #2150).
- The npm release steps log the trusted-publishing token exchange at verbose level (#2101).
- Known limitation: the
lattice-embedsimdbench allocates its input vectors with the default allocator, so their cache-line offset varies from run to run. On an AMD Ryzen 7 PRO 8700GE the same binary timedsimd_dot_product/simd/768between about 31 ns and 50 ns depending only on that offset. Treat run-to-run differences on thesimd/*groups below that range as placement, not as a code change, until the bench pins its input alignment. - The machine-wide Metal test lock moves into
lattice-metal-runtime;lattice_inference::measurement::gpu_test_lockre-exports it, and the lock path, wait and handoff protocol are unchanged (#2319). scripts/publish.shwaits and retries when crates.io answers 429, and skips a version the registry already has for that crate, so a stopped run resumes where it stopped (#2324).- The
bench_gpuexample accepts--gpu-only, which times the WGPU forward without loading or keeping the CPU model and its comparison data (#2350). - The backward Stage 0 benchmark has a portable scalar arm for all eight operations, so the forced-scalar cases time real scalar code (#1977).
- The workspace dev and test profiles build six CPU-kernel crates at
opt-level = 3, which shortenscargo teston Linux; the release and bench profiles are unchanged, and profile settings do not apply to projects that depend on these crates (#2342). Thewgpu-gpufeature packages are linted by clippy in CI (#2363).
Tests
- The source-reading contracts scan every workspace package that holds serving code (#2223). The two standalone CPU generation entries have their request preflight order pinned (#2215).
- A dependency-direction contract keeps the extracted crates from depending back on the facade (#2110), and facade parity tests pin each re-export to its definition.
- The Gemma 4 softcap mutation witness calls the function it guards (#2308).
- The model-format test module is declared under plain
cfg(test)(#2128); the Metal measurement-lock contract is split into support modules (#2137); the source index's resolution rules are pinned in two row tables (#2151).
Documentation
- ADR-103 records the crate split and the model layer through Amendment 23 (#2105 and later; Amendment 21 in #2263, Amendment 23 in #2292). ADR-046 records that
lattice_serveconstrains strictjson_schemaoutput (#2114). - The README describes the workspace without a fixed crate count and lists the model-family publish tier (#2302, #2305).
docs/safety.mdlists only the files that hold unsafe Metal code (#2193). The intra-doc links the rustdoc lint rejected are repaired (#2146, #2304), and the workspace rustdoc lint passes. - Every compatibility re-export in
lattice-inferenceis marked in its documentation as removed at 1.0, naming the crate that owns the item (#2330). ADR-104 proposes embedding vector-space identity (#2353), ADR-088 is withdrawn with the attestation API (#2345), and ADR-103 Amendment 24 and ADR-102 Amendment 1 record the router, backward and compute moves and the shared Metal mechanisms (#2341, #2361).