From 0d9af18ab6301a74e07b7f96ec32a44e409f45dc Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Fri, 9 Oct 2026 16:25:03 +0200 Subject: [PATCH 1/2] fix(apr-cli): build without the inference feature (#4972) `cargo check -p apr-cli --no-default-features --lib` failed with 23 errors: items that use realizar or axum, or call inference-only helpers, were not behind `cfg(feature = "inference")`. The default build hid them, and the workspace-wide no-default check unifies `inference` back into apr-cli. - Gate `serve::route_index`, the `Devices` dispatch arm, the runtime thinking-ON leg, the four thinking-budget helpers and chat's realizar import. - `forced_accelerator_refusal` without inference refuses any forced accelerator by name; its realizar-table tests now need inference, and a no-inference module pins the new behaviour. - The chat banner names its template through `banner_template_family`, which says plainly when the build has no detector. - The no-inference `ChatSession::new` stub takes `force_cpu`, like its callers. The default build is unchanged. A planted undefined call in a no-inference-only arm turns the check RED (rc 101) while the default build stays green; reverted, it is green again. Clippy `-D warnings` is clean in both feature sets. Refs #4972 Agent: la-0.72b Co-Authored-By: Claude Opus 5.5 (cherry picked from commit 9447c193c8fed1346bac3a87c256bf948bfe9293) --- crates/apr-cli/src/commands/chat.rs | 18 +++++++++++-- .../src/commands/chat_generate_session.rs | 2 +- crates/apr-cli/src/commands/golden_output.rs | 4 +++ .../src/commands/output_verification.rs | 1 + crates/apr-cli/src/commands/run_entry.rs | 15 +++++++++++ .../src/commands/run_tests_accel_reconcile.rs | 25 ++++++++++++++++++- crates/apr-cli/src/commands/serve/mod.rs | 1 + crates/apr-cli/src/dispatch_analysis.rs | 1 + 8 files changed, 63 insertions(+), 4 deletions(-) diff --git a/crates/apr-cli/src/commands/chat.rs b/crates/apr-cli/src/commands/chat.rs index 31c72bdc5e..be3fa1c7cc 100644 --- a/crates/apr-cli/src/commands/chat.rs +++ b/crates/apr-cli/src/commands/chat.rs @@ -40,6 +40,7 @@ use aprender::serialization::apr::AprReader; // asking…` on Qwen3-1.7B. Two functions with the same name, two enums, one of // which could not express the right answer. use colored::Colorize; +#[cfg(feature = "inference")] use realizar::chat_template::{ auto_detect_template, detect_format_from_name, ChatMessage, ChatTemplateEngine, TemplateFormat, }; @@ -682,7 +683,9 @@ fn print_welcome_banner(path: &Path, config: &ChatConfig) { print_welcome_banner_for(path, detect_format(path), config); } -fn print_welcome_banner_for(path: &Path, format: ModelFormat, config: &ChatConfig) { +/// The chat-template family the banner names, detected from the file name. +#[cfg(feature = "inference")] +fn banner_template_family(path: &Path) -> &'static str { // Detect chat template format from model name (Toyota Way: Visual Control) let model_name = path .file_stem() @@ -692,7 +695,18 @@ fn print_welcome_banner_for(path: &Path, format: ModelFormat, config: &ChatConfi // #3801: ONE spelling of the name. This was a second copy of // `template_format_name`, and two copies of a match over an enum are how a // new variant gets handled in one place and not the other. - let template_name = crate::chat::realizar_chat::template_format_name(template_format); + crate::chat::realizar_chat::template_format_name(template_format) +} + +/// #4972: without `inference` there is no template detector, and the session +/// refuses before its first turn. +#[cfg(not(feature = "inference"))] +fn banner_template_family(_path: &Path) -> &'static str { + "none: this apr was built without the `inference` feature" +} + +fn print_welcome_banner_for(path: &Path, format: ModelFormat, config: &ChatConfig) { + let template_name = banner_template_family(path); match format { ModelFormat::Apr => { diff --git a/crates/apr-cli/src/commands/chat_generate_session.rs b/crates/apr-cli/src/commands/chat_generate_session.rs index 4023b2187c..e7adf01894 100644 --- a/crates/apr-cli/src/commands/chat_generate_session.rs +++ b/crates/apr-cli/src/commands/chat_generate_session.rs @@ -10,7 +10,7 @@ struct ChatSession; #[cfg(not(feature = "inference"))] impl ChatSession { - fn new(_path: &Path) -> Result { + fn new(_path: &Path, _force_cpu: bool) -> Result { Err(CliError::ValidationFailed( "Chat requires the 'inference' feature (realizar). Rebuild with: \ cargo install --path crates/apr-cli --features inference" diff --git a/crates/apr-cli/src/commands/golden_output.rs b/crates/apr-cli/src/commands/golden_output.rs index 6833e3352b..9feb34939e 100644 --- a/crates/apr-cli/src/commands/golden_output.rs +++ b/crates/apr-cli/src/commands/golden_output.rs @@ -355,6 +355,7 @@ fn thinking_on_budget_override() -> std::result::Result, /// One matched row of the `models` table. A row that declares no `budget` /// REFUSES rather than inheriting the default, which was measured on a /// different model (#3907). +#[cfg(feature = "inference")] fn thinking_on_budget_row( v: &serde_yaml::Value, pat: &str, @@ -385,6 +386,7 @@ fn thinking_on_budget_row( /// The first `models` pattern that matches `model_file`, or `None` when the /// model is unlisted and the `default` row applies. +#[cfg(feature = "inference")] fn thinking_on_budget_match( doc: &serde_yaml::Value, model_file: &str, @@ -400,6 +402,7 @@ fn thinking_on_budget_match( } /// The `default` row, for a model no pattern names. +#[cfg(feature = "inference")] fn thinking_on_budget_default( doc: &serde_yaml::Value, ) -> std::result::Result<(usize, String), String> { @@ -418,6 +421,7 @@ fn thinking_on_budget_default( Ok((usize::try_from(b).unwrap_or(0), format!("default: {basis}"))) } +#[cfg(feature = "inference")] fn thinking_on_budget_for(model_file: &str) -> std::result::Result<(usize, String), String> { if let Some(over) = thinking_on_budget_override()? { return Ok(over); diff --git a/crates/apr-cli/src/commands/output_verification.rs b/crates/apr-cli/src/commands/output_verification.rs index 783232c6e6..d2e0d6f0b5 100644 --- a/crates/apr-cli/src/commands/output_verification.rs +++ b/crates/apr-cli/src/commands/output_verification.rs @@ -1055,6 +1055,7 @@ fn run_golden_output_gate_runtime( /// pass message's ON-leg clause (empty when the model has no thinking-on case), and /// `Err(failed)` ends the gate. Extracted from `run_golden_output_gate_runtime` unchanged, /// to keep that function under the complexity ratchet. +#[cfg(feature = "inference")] fn runtime_thinking_on_leg( path: &Path, architecture: Option<&str>, diff --git a/crates/apr-cli/src/commands/run_entry.rs b/crates/apr-cli/src/commands/run_entry.rs index 9b5ca2cb41..12dd1050bd 100644 --- a/crates/apr-cli/src/commands/run_entry.rs +++ b/crates/apr-cli/src/commands/run_entry.rs @@ -212,6 +212,7 @@ fn reconcile_and_emit( /// (not a GGUF, a hub id rather than a path, an unreadable header), and an /// unknown architecture is never refused — this predicate lists what we know we /// did NOT build, never what we support. +#[cfg(feature = "inference")] pub(crate) fn forced_accelerator_refusal( accel_forced: bool, architecture: Option<&str>, @@ -222,6 +223,20 @@ pub(crate) fn forced_accelerator_refusal( realizar::capability::no_cuda_forward_reason(architecture?) } +/// #4972: a build without `inference` has no forward on any accelerator, so a +/// forced one is refused whatever the architecture, including an unread one. +#[cfg(not(feature = "inference"))] +pub(crate) fn forced_accelerator_refusal( + accel_forced: bool, + _architecture: Option<&str>, +) -> Option { + accel_forced.then(|| { + "an accelerator was forced, but this apr was built without the `inference` \ + feature, so it has no forward on any device (#4972)" + .to_string() + }) +} + /// Read the declared architecture from a model path without loading it. /// /// **A bounded PREFIX read, deliberately not an mmap.** The first draft called diff --git a/crates/apr-cli/src/commands/run_tests_accel_reconcile.rs b/crates/apr-cli/src/commands/run_tests_accel_reconcile.rs index b496ab747a..1addfa7cc7 100644 --- a/crates/apr-cli/src/commands/run_tests_accel_reconcile.rs +++ b/crates/apr-cli/src/commands/run_tests_accel_reconcile.rs @@ -197,7 +197,7 @@ fn json_plus_benchmark_is_not_a_machine_surface() { // #3817: --gpu on an architecture with no CUDA forward refuses BEFORE the load // ============================================================================= -#[cfg(test)] +#[cfg(all(test, feature = "inference"))] mod forced_accelerator_refusal_tests { use crate::commands::run::forced_accelerator_refusal; @@ -280,6 +280,29 @@ mod forced_accelerator_refusal_tests { } } +/// #4972: without `inference` there is no forward on any device, so a forced +/// accelerator is refused for every architecture, an unread one included, and +/// an unforced run is never refused. +#[cfg(all(test, not(feature = "inference")))] +mod forced_accelerator_refusal_without_inference_tests { + use crate::commands::run::forced_accelerator_refusal; + + #[test] + fn a_forced_accelerator_is_refused_for_every_architecture() { + for arch in [Some("qwen2"), Some("llama"), Some("qwen35moe"), None] { + let reason = forced_accelerator_refusal(true, arch) + .expect("a build without inference has no forward to reach"); + assert!(reason.contains("inference"), "{reason}"); + } + } + + #[test] + fn an_unforced_run_is_not_refused() { + assert!(forced_accelerator_refusal(false, Some("qwen2")).is_none()); + assert!(forced_accelerator_refusal(false, None).is_none()); + } +} + // ── #3826: `fell_back` must name a fallback that HAPPENED ────────────────── // // `apr run --format json` reported `"fell_back": false` on a run whose own diff --git a/crates/apr-cli/src/commands/serve/mod.rs b/crates/apr-cli/src/commands/serve/mod.rs index 232ee7bb6f..de6ed37cac 100644 --- a/crates/apr-cli/src/commands/serve/mod.rs +++ b/crates/apr-cli/src/commands/serve/mod.rs @@ -9,6 +9,7 @@ pub mod auth; pub mod handlers; #[cfg(feature = "inference")] pub mod ollama; +#[cfg(feature = "inference")] pub(crate) mod route_index; pub mod routes; #[cfg(feature = "inference")] diff --git a/crates/apr-cli/src/dispatch_analysis.rs b/crates/apr-cli/src/dispatch_analysis.rs index b87f70c0ad..a8d36e4054 100644 --- a/crates/apr-cli/src/dispatch_analysis.rs +++ b/crates/apr-cli/src/dispatch_analysis.rs @@ -495,6 +495,7 @@ fn dispatch_analysis_commands_rest(cli: &Cli) -> Option> { // complexity debt the pre-commit gate refuses to let any edit ride on. #[cfg(feature = "inference")] ExtendedCommands::Capability { json } => commands::capability::run(*json || cli.json), + #[cfg(feature = "inference")] ExtendedCommands::Devices { json } => commands::devices::run(*json || cli.json), ExtendedCommands::OtlpLint { otlp_file, From 2f27c098bb8788295be784568d22e4472863a93a Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sun, 11 Oct 2026 19:45:06 +0200 Subject: [PATCH 2/2] docs(audit): re-cite the dispatch_analysis.rs ledger rows at their current lines (Refs #4972) The inference gates in this branch moved dispatch_analysis.rs, so G2.1 freshness asks for the ledger rows that cite it. Each of the 14 rows now names the line of its anchor on this head: the eval task match arms, the chat accelerator check, and the parity per-op dispatch. Most of these citations had already drifted on main; no row changes anything but its line number. Agent: la-0.72b Co-Authored-By: Claude Opus 5.5 --- docs/audits/surface_audit.csv | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/audits/surface_audit.csv b/docs/audits/surface_audit.csv index 7f62ff9918..8521e6ada2 100644 --- a/docs/audits/surface_audit.csv +++ b/docs/audits/surface_audit.csv @@ -117,7 +117,7 @@ apr,apr cgp profile wgpu,6,x86_64-linux,NONE,no,9,profiling,crates/aprender-cgp/ apr,apr cgp roofline,6,x86_64-linux,NONE,no,7,qa-cgp,crates/aprender-cgp/src/cli.rs:64,low apr,apr cgp trace,6,x86_64-linux,NONE,no,7,qa-cgp,crates/aprender-cgp/src/cli.rs:99,low apr,apr cgp tui,6,x86_64-linux,NONE,no,7,qa-cgp,crates/aprender-cgp/src/cli.rs:115,low -apr,apr chat,6,x86_64-linux,llama.cpp,yes,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:40 (ACCELERATOR_GUARD crates/apr-cli/src/dispatch_analysis.rs:1710),high +apr,apr chat,6,x86_64-linux,llama.cpp,yes,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:40 (ACCELERATOR_GUARD crates/apr-cli/src/dispatch_analysis.rs:1722),high apr,apr chat --backend cpu,6,x86_64-linux,ollama,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:85 (BACKEND_VALUES crates/apr-cli/src/commands_enum.rs:16),high apr,apr chat --backend cuda,6,x86_64-linux;nvidia-cuda,ollama,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:85 (BACKEND_VALUES crates/apr-cli/src/commands_enum.rs:16),medium apr,apr chat --backend wgpu,6,UNKNOWN,ollama,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:85 (BACKEND_VALUES crates/apr-cli/src/commands_enum.rs:16),high @@ -184,16 +184,16 @@ apr,apr embed-viz-lint,1,x86_64-linux,NONE,yes,1,apr-lint-diag,crates/apr-cli/sr apr,apr embeddings-lint,1,x86_64-linux,NONE,yes,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:1173,high apr,apr encrypt,4,x86_64-linux,NONE,yes,10,apr-core-commands,crates/apr-cli/src/tool_commands.rs:121,high apr,apr eval,6,x86_64-linux,llama.cpp,yes,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:124,high -apr,apr eval --task classify,6,x86_64-linux,scikit-learn,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1406 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task code,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1420 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task compare,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1444 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task contamination,6,x86_64-linux,huggingface-transformers,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1441 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task correlation,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1446 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task human,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1447 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task humaneval,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1423 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task mbpp,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1432 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task plan,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1448 (flag crates/apr-cli/src/extended_commands.rs:145),high -apr,apr eval --task verify,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1445 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task classify,6,x86_64-linux,scikit-learn,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1441 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task code,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1455 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task compare,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1479 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task contamination,6,x86_64-linux,huggingface-transformers,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1476 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task correlation,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1481 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task human,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1482 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task humaneval,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1458 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task mbpp,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1467 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task plan,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1483 (flag crates/apr-cli/src/extended_commands.rs:145),high +apr,apr eval --task verify,6,x86_64-linux,NONE,no,8,rag-eval,crates/apr-cli/src/dispatch_analysis.rs:1480 (flag crates/apr-cli/src/extended_commands.rs:145),high apr,apr experiment view,6,x86_64-linux,pytorch,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:1568,low apr,apr explain,6,x86_64-linux,safetensors,yes,10,apr-core-commands,crates/apr-cli/src/commands_enum.rs:447,high apr,apr explain-token-lint,1,x86_64-linux,NONE,yes,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:1066,low @@ -239,9 +239,9 @@ apr,apr oom-lint,1,x86_64-linux,NONE,yes,1,apr-lint-diag,crates/apr-cli/src/exte apr,apr oracle,9,x86_64-linux,NONE,yes,10,apr-core-commands,crates/apr-cli/src/tool_commands.rs:84,medium apr,apr otlp-lint,1,x86_64-linux,NONE,yes,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:1108,high apr,apr parity,9,x86_64-linux,NONE,yes,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:317,medium -apr,apr parity --per-op,9,"x86_64-linux,aarch64-linux",NONE,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:330 (dispatch crates/apr-cli/src/dispatch_analysis.rs:1516),high -apr,apr parity --out,7,"x86_64-linux,aarch64-linux",NONE,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:333 (dispatch crates/apr-cli/src/dispatch_analysis.rs:1517),high -apr,apr parity --threshold,7,"x86_64-linux,aarch64-linux",NONE,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:336 (dispatch crates/apr-cli/src/dispatch_analysis.rs:1517),high +apr,apr parity --per-op,9,"x86_64-linux,aarch64-linux",NONE,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:330 (dispatch crates/apr-cli/src/dispatch_analysis.rs:1564),high +apr,apr parity --out,7,"x86_64-linux,aarch64-linux",NONE,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:333 (dispatch crates/apr-cli/src/dispatch_analysis.rs:1564),high +apr,apr parity --threshold,7,"x86_64-linux,aarch64-linux",NONE,no,1,apr-lint-diag,crates/apr-cli/src/extended_commands.rs:336 (dispatch crates/apr-cli/src/dispatch_analysis.rs:1564),high apr,apr pipeline apply,6,x86_64-linux,huggingface-transformers,no,10,apr-core-commands,crates/apr-cli/src/pipeline_commands.rs:28,low apr,apr pipeline plan,6,x86_64-linux,huggingface-transformers,no,10,apr-core-commands,crates/apr-cli/src/pipeline_commands.rs:9,low apr,apr pipeline status,6,x86_64-linux,huggingface-transformers,no,10,apr-core-commands,crates/apr-cli/src/pipeline_commands.rs:51,low