atlas_kernels/
lib.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3#![deny(warnings)]
4#![deny(clippy::all)]
5
6//! Atlas CUDA kernel PTX modules.
7//!
8//! Single source of truth for embedded PTX. The `spark-runtime`
9//! (pure Rust engine) and benchmarks consume these.
10//!
11//! PTX modules are grouped by [`KernelTarget`] — each `(H, M_q)`
12//! tuple maps to a distinct set of hyperoptimized kernels.
13//!
14//! Constants, `ptx_modules()`, and `all_ptx_sets()` are auto-generated
15//! by `build.rs` from the `kernels/{hw}/{model}/{quant}/` directories.
16//! When `ATLAS_TARGET_MODEL=*` or `ATLAS_TARGET_QUANT=*`, multiple
17//! targets are compiled and available at runtime.
18
19use atlas_core::target::KernelTarget;
20
21pub mod resolve;
22pub use resolve::{ResolveCandidate, TargetResolveError, ptx_for_config, ptx_for_exact_target};
23
24// Build-time/run-time shared `[behavior]` defaults — also `include!`d by
25// `build_parse_behavior.rs` so the build script's parse defaults cannot
26// drift from `ModelBehavior::default()` (the #328 failure mode).
27mod behavior_defaults;
28pub use behavior_defaults::{
29    DEFAULT_EFFORT_CAPPED_AT_CEILING, DEFAULT_MAX_INTER_TOOL_PROSE, DEFAULT_MAX_THINKING_BUDGET,
30};
31
32// Auto-generated: per-target PTX constants, ptx_modules() function,
33// and all_ptx_sets() for multi-target builds.
34// NOTE: cargo does NOT track this build-script-generated include! as a
35// recompile trigger, so when build.rs regenerates target_ptx.rs (e.g. the
36// module set changes) this lib can keep a STALE embedded set. Any edit to
37// this file (or `cargo clean -p atlas-kernels`) forces a fresh recompile
38// against the current OUT_DIR/target_ptx.rs.
39include!(concat!(env!("OUT_DIR"), "/target_ptx.rs"));
40
41/// Content fingerprint of the generated kernel set, emitted by `build.rs` as a
42/// `rustc-env`. Referencing it here makes cargo recompile this crate whenever
43/// the kernel set changes — closing the `include!`-not-tracked staleness hole
44/// that silently embedded a stale module list (the 98-vs-99 regression).
45pub const KERNEL_SET_HASH: &str = env!("ATLAS_KERNEL_SET_HASH");
46
47/// What each kernel target in THIS binary was compiled from, as JSON.
48///
49/// A map of `"hardware/model/quant"` to `{hash, arch, compiler, flags}`, baked
50/// by `build.rs` at the moment the kernels were compiled. The benchmark gate
51/// copies it into a record so the record attests to the binary's sources rather
52/// than to whatever the working tree held when the record was written — a
53/// commit sha does not describe a stale `target/`, a dirty tree, or an image
54/// carried between boxes.
55///
56/// `{}` when the build compiled nothing (`ATLAS_SKIP_BUILD=1`) or could not
57/// identify its compiler. That is not an error: an empty attestation excuses no
58/// future diff, so such a binary's records behave exactly as they did before
59/// attestations existed.
60///
61/// `option_env!` rather than `env!` so the crate still builds against an
62/// `OUT_DIR` produced by an older build script.
63pub const TARGET_CLOSURES: &str = match option_env!("ATLAS_TARGET_CLOSURES") {
64    Some(json) => json,
65    None => "{}",
66};
67
68// ═══════════════════════════════════════════════════════════════════
69// Target-aware PTX grouping
70// ═══════════════════════════════════════════════════════════════════
71
72/// Per-category sampling defaults from MODEL.toml.
73#[derive(Debug, Clone, Copy)]
74pub struct SamplingCategory {
75    pub temperature: f32,
76    pub top_p: f32,
77    pub top_k: u32,
78    pub presence_penalty: f32,
79    pub frequency_penalty: f32,
80    /// Multiplicative penalty on already-seen tokens (1.0 = disabled).
81    /// Populated from MODEL.toml `[sampling.*].repetition_penalty` via build.rs.
82    pub repetition_penalty: f32,
83    /// DRY (Don't-Repeat-Yourself) sampler parameters. Penalises tokens
84    /// that extend repeated n-grams past `dry_allowed_length` with an
85    /// exponential `dry_multiplier * dry_base^(match_len - allowed)` —
86    /// the targeted fix for phrase-level attractors (e.g. the
87    /// ```` ```bash cd … cargo test ``` ```` fence-narration loop
88    /// observed in Qwen3.5-35B-A3B-FP8 opencode sessions at turn ≥ 8).
89    ///
90    /// `presence_penalty` on its own is a FLAT per-unique-token hit
91    /// (does not scale with repetition count), so it can't break a
92    /// phrase attractor where individual tokens already paid their
93    /// penalty once. DRY scales with the repeat-length and is the
94    /// published remedy (oobabooga/text-generation-webui#5677, used in
95    /// llama.cpp / Aphrodite / TabbyAPI).
96    ///
97    /// `dry_multiplier = 0.0` disables DRY for this category (default
98    /// for every preset unless MODEL.toml sets it explicitly).
99    pub dry_multiplier: f32,
100    pub dry_base: f32,
101    pub dry_allowed_length: u32,
102    /// LZ penalty (arXiv:2504.20131). Per-extension n-gram penalty
103    /// over a 256-token rolling window. Frequency-weighted and length-
104    /// scaled, so it correctly distinguishes "phrase loop" from
105    /// "legitimate vocabulary reuse" without the flat-per-token
106    /// `presence_penalty` regression. 0.0 = disabled. SGLang reference
107    /// strength = 0.2 (lossless on AIME/GPQA).
108    pub lz_penalty: f32,
109    /// Model-declared min-p, or `None` when MODEL.toml is silent.
110    ///
111    /// `Option`, not `f32`, because absence and `0.0` mean opposite things
112    /// here. The server ships `--default-min-p 0.08` and every request that
113    /// does not name min_p takes it, so a model whose card specifies
114    /// `min_p = 0` had no way to say so: `[behavior].min_p_floor` only ever
115    /// RAISES min_p (`min_p.max(floor)`), and the preset did not carry the
116    /// value at all. A plain `f32` defaulting to 0.0 would silently strip the
117    /// 0.08 floor from every model that has a `[sampling.*]` table, which is
118    /// the opposite regression.
119    ///
120    /// `Some(x)` outranks the CLI default and is outranked by
121    /// `generation_config.json` — the same precedence temperature/top_k/top_p
122    /// already follow. `None` preserves the CLI-owned behaviour exactly.
123    pub min_p: Option<f32>,
124    /// Model-declared top-n-sigma, or `None` when MODEL.toml is silent.
125    ///
126    /// Same absence-vs-zero problem as `min_p`: the server ships
127    /// `--default-top-n-sigma 1.0`, so a model whose card asks for NO sigma
128    /// filter had no way to say so. `Some(0.0)` disables it; `None` leaves the
129    /// CLI default owning the field.
130    pub top_n_sigma: Option<f32>,
131}
132
133/// Model-specific sampling presets loaded from MODEL.toml `[sampling.*]`.
134#[derive(Debug, Clone, Copy)]
135pub struct SamplingPresets {
136    pub thinking_text: SamplingCategory,
137    pub thinking_coding: SamplingCategory,
138    pub non_thinking: SamplingCategory,
139    /// Tool-calling preset: model-recommended sampling for agentic tasks.
140    /// Qwen3.5 recommends temperature=0.6 (NOT greedy) to avoid repetition loops.
141    pub tools: SamplingCategory,
142}
143
144impl Default for SamplingPresets {
145    fn default() -> Self {
146        let default_cat = SamplingCategory {
147            temperature: 0.7,
148            top_p: 0.95,
149            top_k: 20,
150            presence_penalty: 0.0,
151            frequency_penalty: 0.0,
152            repetition_penalty: 1.0,
153            // DRY defaults = disabled (multiplier 0.0). Per-MODEL.toml
154            // tools presets opt in when the model needs it.
155            dry_multiplier: 0.0,
156            dry_base: 1.75,
157            dry_allowed_length: 2,
158            lz_penalty: 0.0,
159            min_p: None,
160            top_n_sigma: None,
161        };
162        let tools_cat = SamplingCategory {
163            temperature: 0.6,
164            top_p: 0.95,
165            top_k: 20,
166            presence_penalty: 0.0,
167            frequency_penalty: 0.0,
168            repetition_penalty: 1.0,
169            dry_multiplier: 0.0,
170            dry_base: 1.75,
171            dry_allowed_length: 2,
172            lz_penalty: 0.0,
173            min_p: None,
174            top_n_sigma: None,
175        };
176        Self {
177            thinking_text: default_cat,
178            thinking_coding: default_cat,
179            non_thinking: default_cat,
180            tools: tools_cat,
181        }
182    }
183}
184
185/// Model-specific behavior flags from MODEL.toml `[behavior]`.
186#[derive(Debug, Clone)]
187pub struct ModelBehavior {
188    /// Allow thinking when tools are active. Default: true.
189    pub thinking_in_tools: bool,
190    /// Maximum thinking budget (tokens). Default:
191    /// [`DEFAULT_MAX_THINKING_BUDGET`].
192    pub max_thinking_budget: u32,
193    /// Clamp qualitative `reasoning_effort` levels at the model's effective
194    /// ceiling (high/xhigh resolve to `max_thinking_budget` instead of
195    /// 2x/4x it). Default [`DEFAULT_EFFORT_CAPPED_AT_CEILING`] = `false`
196    /// (historical ladder shape). See `behavior_defaults.rs` for when a
197    /// model should set `true` (measured budget non-monotonicity).
198    pub effort_capped_at_ceiling: bool,
199    /// Default thinking state for this model when the client request does not
200    /// specify a reasoning_effort / thinking parameter. Typical values:
201    /// - thinking-first models (Mistral Small 4, Qwen3.5, …): `true`
202    /// - instruct-only models with no `<think>` tokens: `false`
203    ///
204    /// Overridden per-request by `reasoning_effort`, and globally by the
205    /// `--disable-thinking` CLI flag.
206    pub thinking_default: bool,
207    /// Default FP8 KV calibration tokens (0 = disabled).
208    pub fp8_kv_calibration_tokens: usize,
209    /// Default KV cache dtype from MODEL.toml (e.g., "bf16", "fp8").
210    /// When non-empty, overrides the CLI default for models that need
211    /// higher precision. User can still override with explicit --kv-cache-dtype.
212    pub default_kv_dtype: &'static str,
213    /// Default num_drafts for speculative decoding (0 = use CLI default).
214    /// K = num_drafts + 1 (num_drafts=1 → K=2 verifies 2 tokens per step).
215    /// Optimal K varies per model; benchmarks sometimes show K=2 beats K=3.
216    /// User override with --num-drafts still wins.
217    pub default_num_drafts: u32,
218    /// Skip the `<tool_call>\n` steering prefix in the chat template's
219    /// generation prompt. Some Nemotron variants (Super 120B) weren't
220    /// trained on qwen3_coder XML and emit a `<tool_call>` token loop
221    /// when the prefix forces them into that structure. Default: false
222    /// (keep the existing Nemotron-Nano-correct behavior).
223    pub disable_tool_steering: bool,
224    /// Do not append Atlas's derived `<environment>working_directory` block to
225    /// a client system prompt. Native agent clients may already provide the
226    /// cwd; duplicating it can become a tool-selection attractor.
227    pub disable_cwd_hint_injection: bool,
228    /// Use the selected MODEL.toml sampling category for default temperature,
229    /// top-k, and top-p instead of generation_config.json. Explicit request
230    /// values still take precedence.
231    pub use_sampling_presets_for_core: bool,
232    /// Per-model tool-call parser override. Empty string = use the
233    /// `tool_defaults.toml` mapping for this `model_type`. Set in MODEL.toml
234    /// `[behavior].tool_call_parser` when one variant of a model_type needs
235    /// a different parser than its siblings (e.g. Nemotron-Super-120B uses
236    /// `bare_json` while Nemotron-Nano-30B stays on `qwen3_coder`).
237    pub tool_call_parser: &'static str,
238    /// Enable the content-loop watchdog (period-N token-repetition detector
239    /// at `decode_logits_step.rs:230`). Default: `false` — most models
240    /// terminate cleanly via EOS / `max_tokens` without it. Models with a
241    /// known prose-attractor failure mode (Qwen3.5-35B-A3B's "Running:```bash
242    /// cmd```Executing:" loop, observed during agentic Claude Code sessions)
243    /// should set this `true` in MODEL.toml `[behavior]`.
244    ///
245    /// The watchdog has false-positives on legitimate structured output
246    /// (chess board JS init `{color:BLACK,type:'P'},` × 8, HTML tables,
247    /// JSON arrays of similar objects, multiplication tables). Enable only
248    /// when the model has been observed to need it.
249    pub enable_loop_watchdog: bool,
250    /// See build_parse.rs: gate for the THINKING-phase loop watchdog.
251    pub enable_think_loop_watchdog: bool,
252    /// See build_parse_behavior.rs: honor a mid-`<think>` EOS by implicitly
253    /// closing the block. Defaults FALSE (pre-p350 behaviour).
254    pub honor_eos_inside_thinking: bool,
255    /// A4 floor: suppress `</think>` until this many think tokens
256    /// (16 = historical constant; 0 disables — card-native brief thinking).
257    pub min_reasoning_floor_tokens: u32,
258    /// Cap the thinking budget at 90% of the request's `max_tokens` (true), or
259    /// let `max_thinking_budget` be the sole cap (false = vLLM single-budget:
260    /// reasoning may use the full generation budget). See thinking.rs::resolve.
261    pub cap_thinking_at_max_tokens: bool,
262    /// Server-side min-p FLOOR (0.0 = disabled). Applied as `min_p.max(floor)`
263    /// AFTER request/preset resolution, so it binds even when a client sends
264    /// `min_p = 0` (or omits it on a server without `--default-min-p`). On
265    /// drift-prone quantized models (FP8 / NVFP4 lm-head) an unfloored tail
266    /// lets the degenerate low-probability tail be sampled into repetition
267    /// loops + argmax-flip garbling on long generation — the Claude-Code
268    /// failure mode. MEASURED 2026-06-07 (nvfp4-head@64k): 0.05 turned 4 loop-
269    /// watchdog fires → 0. Set in MODEL.toml `[behavior]`.
270    pub min_p_floor: f32,
271    /// Server-side temperature CEILING (0.0 = disabled). `temperature.min(max)`
272    /// AFTER resolution — defense-in-depth net against a client sending a high
273    /// temperature; min_p_floor is the dominant lever. Set in MODEL.toml.
274    pub temperature_max: f32,
275    /// Thinking-loop watchdog: substring-occurrence count that trips a
276    /// forced `</think>`. Default 3 (historical `THINK_LOOP_MIN_REPEATS`).
277    pub think_loop_min_repeats: u32,
278    /// Thinking-loop watchdog: trailing-token scan window. Default 160.
279    pub think_loop_scan_window: u32,
280    /// F2 confidence-run early-stop enabled. Default `true`. Set false
281    /// for models whose deterministic code drafting trips the heuristic.
282    pub confidence_early_stop: bool,
283    /// F2 confidence run length before arming forced `</think>`.
284    /// Default 30.
285    pub confidence_run_length: u32,
286    /// Fuzzy-repetition detector Hamming tolerance divisor: a
287    /// `pattern_len`-token window tolerates `pattern_len / div`
288    /// mismatches. Default 12 (~8%).
289    pub fuzzy_repeat_tolerance_div: u32,
290    /// Cap on free-text tokens between successive `<tool_call>` opens in
291    /// `tool_choice=auto`. Default [`DEFAULT_MAX_INTER_TOOL_PROSE`]
292    /// (see `behavior_defaults.rs` for the tuning history — #328).
293    pub max_inter_tool_prose: u32,
294    /// Unconditional per-generation cap on post-`</think>` content tokens
295    /// for tool-active requests (grammar attached). Bounds a runaway where
296    /// a grammar-legal-but-never-closing tool value burns to `max_tokens`
297    /// (the dominant opencode `webserver_ok` 360s-timeout cause). Default
298    /// 100_000 — effectively unbounded, the historical no-op — so a model
299    /// that sets nothing is byte-identical to before. Set a small value
300    /// (e.g. 1536) per-model to backstop the runaway. Never caps plain
301    /// chat: the runtime gate also requires `grammar_state.is_some()`.
302    pub max_post_think_content_tokens: u32,
303    /// TSCG (Tool-Schema Compilation) enabled — compile tool JSON
304    /// schemas to compact function signatures before prompting.
305    /// Default `false`; the TAS operator is tokenizer-specific so
306    /// enable + verify per model. arXiv:2605.04107.
307    pub tscg: bool,
308    /// Disable XGrammar tool-call constrained decoding for this model.
309    /// Default `false`. Escape hatch for the "structure snowballing"
310    /// alignment tax (arXiv:2604.06066) — a few models tool-call more
311    /// reliably unconstrained. When `true`, tool calls are parsed but
312    /// not grammar-enforced.
313    pub disable_tool_grammar: bool,
314    /// Phase-C: when a decode-time watchdog (content-loop, fuzzy-repeat,
315    /// inter-tool prose) detects degeneration, roll the sequence back to
316    /// the last well-formed boundary and let generation re-steer, instead
317    /// of hard-stopping the response. Default `true` (recovers responses,
318    /// especially mid-tool-call — arXiv:2603.27905 ATLAS-RTC). Set `false`
319    /// to keep the legacy hard-stop behavior. Capped at
320    /// [`crate::ROLLBACK_RESTEER_CAP`] rollbacks per sequence, after which
321    /// the hard-stop fires regardless.
322    pub rollback_resteer: bool,
323    /// Phase-C ROM (arXiv:2603.22016) scaffold. Path to a trained
324    /// repetition-onset detection head artifact. Empty string = no ROM
325    /// head; the F2 confidence heuristic stays as the fallback. A trained
326    /// artifact can be dropped in later via MODEL.toml
327    /// `[behavior].rom_head` without further code changes — the runtime
328    /// loads it through the `RomHead` trait seam. The detector
329    /// itself is intentionally NOT implemented (no per-model trained head
330    /// is available); only the optional hook is wired.
331    pub rom_head: &'static str,
332    /// Tier 5c (2026-05-26): one-shot tool-call re-roll on hard
333    /// validation failure. When `true`, `validate_tool_calls` errors on
334    /// the chat path fire a single retry inference with the same
335    /// grammar spec + a correction nudge appended to the prompt. If the
336    /// retry produces valid tool calls, they replace the failed call
337    /// before the response leaves the server. Default `true` — the
338    /// blocking-path canonical-probe trace shows a write-→bash recovery
339    /// path that's strictly better than the previous "`[atlas]` Tool call
340    /// rejected" content fallback. Set `false` per-model when a
341    /// specific model is known to ALWAYS get tool args right on the
342    /// first attempt (extra inference round-trip cost is wasted there).
343    pub tool_retry: bool,
344    /// Jinja `preserve_thinking` chat-template flag (Qwen3.6+ dense family):
345    /// keep historical `<think>` blocks in re-rendered assistant turns
346    /// instead of stripping them before the last user query.
347    ///
348    /// Tri-state on purpose (SSOT): `None` = do NOT inject the variable —
349    /// the model's own template default applies (Qwen3.6 strips unless
350    /// `preserve_thinking` is true; Qwen3.8 KEEPS unless it is explicitly
351    /// false). `Some(_)` pins the value for this target, changing
352    /// multi-turn prompt bytes and therefore prefix-cache hit rate.
353    /// Per-request `chat_template_kwargs.preserve_thinking` still wins.
354    pub preserve_thinking: Option<bool>,
355}
356
357/// Phase-C: maximum number of watchdog-triggered rollbacks a single
358/// sequence may perform before the watchdog reverts to a hard stop.
359/// Bounds the worst case where re-steering re-enters the same attractor
360/// — without this a degenerate sequence could rollback indefinitely.
361pub const ROLLBACK_RESTEER_CAP: u32 = 2;
362
363/// Phase-C: number of boundary SSM-state snapshots retained per sequence in
364/// the decode-rollback ring (hybrid GDN/Mamba models). DECOUPLED from
365/// [`ROLLBACK_RESTEER_CAP`]: the cap bounds how many times we re-steer, but
366/// the ring must retain enough *boundary* snapshots that a clean PRE-loop
367/// boundary survives long enough to roll back to. Sizing it at the old
368/// `CAP + 1 = 3` meant a loop spanning ≥3 sentence/newline boundaries evicted
369/// the clean boundary before the fuzzy detector (3 repeats) fired, forcing a
370/// `NoSsmSnapshot` decline → hard-stop (observed: Claude-Code @ nvfp4-head,
371/// 2026-06-07). 8 covers the 3-repeat detector with margin at modest cost
372/// (8 × max_batch × per-layer GDN state, allocated once). Pure-attention
373/// models ignore this (their ring is 0; they roll back to any boundary).
374pub const DECODE_ROLLBACK_RING_SLOTS: usize = 8;
375
376/// Domain salt folded into every decode cold-tier key so a decode-ring blob can
377/// never collide with a Marconi prefix-hash key on a shared store/peer.
378pub const DECODE_DOMAIN: u64 = 0xD3C0_DE12_A5B6_C7D8;
379
380impl Default for ModelBehavior {
381    fn default() -> Self {
382        Self {
383            thinking_in_tools: true,
384            max_thinking_budget: DEFAULT_MAX_THINKING_BUDGET,
385            effort_capped_at_ceiling: DEFAULT_EFFORT_CAPPED_AT_CEILING,
386            thinking_default: false,
387            fp8_kv_calibration_tokens: 0,
388            default_kv_dtype: "",
389            default_num_drafts: 0,
390            disable_tool_steering: false,
391            disable_cwd_hint_injection: false,
392            use_sampling_presets_for_core: false,
393            tool_call_parser: "",
394            enable_loop_watchdog: false,
395            enable_think_loop_watchdog: true,
396            honor_eos_inside_thinking: false,
397            min_reasoning_floor_tokens: 16,
398            cap_thinking_at_max_tokens: true,
399            min_p_floor: 0.0,
400            temperature_max: 0.0,
401            think_loop_min_repeats: 3,
402            think_loop_scan_window: 160,
403            confidence_early_stop: true,
404            confidence_run_length: 30,
405            fuzzy_repeat_tolerance_div: 12,
406            max_inter_tool_prose: DEFAULT_MAX_INTER_TOOL_PROSE,
407            max_post_think_content_tokens: 100_000,
408            tscg: false,
409            disable_tool_grammar: false,
410            rollback_resteer: true,
411            rom_head: "",
412            tool_retry: true,
413            preserve_thinking: None,
414        }
415    }
416}
417
418/// Declares which `(model_type, hidden_size)` pairs a kernel target supports.
419/// Parsed from `[[model_types]]` in MODEL.toml at build time.
420pub struct ModelTypeMatch {
421    pub model_type: &'static str,
422    /// `None` = wildcard (matches any hidden_size not caught by a more specific entry).
423    pub hidden_size: Option<usize>,
424}
425
426/// DFlash speculative-decoding pairing for a target model.
427/// Parsed from `[dflash]` in MODEL.toml at build time. `None` when the
428/// model has no DFlash drafter associated.
429#[derive(Debug, Clone)]
430pub struct DflashConfig {
431    /// HuggingFace id (or local path) of the drafter checkpoint.
432    pub draft_model: &'static str,
433    /// Block size γ (parallel draft tokens per step). Defaults to 16.
434    pub gamma: usize,
435    /// Drafter sliding-window size in tokens. 0 = full attention.
436    pub window_size: usize,
437    /// Token id used to fill the γ "to-be-predicted" positions during
438    /// drafter forward. From the drafter's `dflash_config.mask_token_id`.
439    pub mask_token_id: u32,
440    /// Target-side layer indices to capture intermediate hidden states from
441    /// (shallow-to-deep). The drafter's `fc` projection consumes the stack
442    /// of these hiddens. From the drafter's `dflash_config.target_layer_ids`.
443    pub target_layer_ids: &'static [usize],
444}
445
446/// Kernel modules hyperoptimized for a specific (H, M_q) target.
447///
448/// Each blob is the compiled kernel for one module, emitted uniformly as
449/// `&'static [u8]` by build.rs (`include_bytes!`). NVIDIA PTX is ASCII
450/// text but valid as bytes; SCALE/AMD and Metal produce binary objects.
451/// The runtime registry sniffs text-vs-binary per blob at load time.
452pub struct TargetPtxSet {
453    pub target: KernelTarget,
454    pub modules: Vec<(&'static str, &'static [u8])>,
455    pub sampling: SamplingPresets,
456    pub behavior: ModelBehavior,
457    pub model_type_matches: Vec<ModelTypeMatch>,
458    /// `[model] match_names` needles from MODEL.toml — case-insensitive
459    /// substrings of the checkpoint reference (HF id / `--model-name` /
460    /// resolved model dir) that identify checkpoints THIS target serves.
461    /// Consulted only to break a tie when several targets declare the same
462    /// `(model_type, hidden_size)` (e.g. qwen3.6-27b vs qwen3.8-27b, whose
463    /// configs are bit-identical); see [`resolve::resolve_target`]. Empty
464    /// for targets that never collide — `build.rs` panics if a colliding
465    /// target omits them.
466    pub match_names: &'static [&'static str],
467    /// DFlash drafter pairing for this model. `None` when the MODEL.toml has
468    /// no `[dflash]` section. Consumed by spark-server when `--dflash` is
469    /// set without an explicit `--draft-model` flag.
470    pub dflash: Option<DflashConfig>,
471    /// `(module, kernel)` pairs this model's kernel files DROPPED by shadowing
472    /// their `common/` namesakes — the kernel exists in `common/` but this
473    /// model's fork of the file does not define it, so it is not compiled here.
474    ///
475    /// Shadowing is whole-file, so a fork that predates a kernel added to
476    /// `common/` silently loses it: `try_kernel` returns handle 0 and whatever
477    /// depends on it fails CLOSED. The startup audit joins this against the
478    /// kernels the model actually looked up, which separates the two classes of
479    /// missing kernel — dropped-by-fork (a build defect) from
480    /// never-built-for-this-architecture (expected, e.g. MLA on a Qwen model).
481    pub shadowed_dropped: &'static [(&'static str, &'static str)],
482    /// `(module, kernel)` lookups this model's dispatch may issue and fail to
483    /// resolve WITHOUT that being an error, declared in the model's MODEL.toml
484    /// `[expected_absent]` with a mandatory stated reason per entry.
485    ///
486    /// The boot audit (`kernel_audit::classify_failures`) fails CLOSED on every
487    /// unresolved lookup that is not in this list, so the list is the entire
488    /// difference between "this model is known to run this way" and "nobody has
489    /// looked". It is TRANSITIONAL: the right fix for a lookup that can never
490    /// resolve is to gate it on config so it is never issued (see
491    /// `qwen3_attention::init_arch_gates`), which removes it from here.
492    pub expected_absent: &'static [(&'static str, &'static str)],
493}
494
495mod query;
496pub use query::{available_targets, ptx_for_model};
497
498#[cfg(test)]
499#[path = "lib_tests.rs"]
500mod tests;