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, 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