1
2#[allow(clippy::too_many_arguments)]
4fn dispatch_run(
5 source: &str,
6 positional_prompt: Option<&String>,
7 input: Option<&Path>,
8 prompt: Option<&String>,
9 max_tokens: usize,
10 stream: bool,
11 language: Option<&str>,
12 task: Option<&str>,
13 format: &str,
14 no_gpu: bool,
15 offline: bool,
16 benchmark: bool,
17 verbose: bool,
18 trace: bool,
19 trace_payload: bool,
20 trace_steps: Option<&[String]>,
21 trace_verbose: bool,
22 trace_output: Option<PathBuf>,
23 trace_level: &str,
24 profile: bool,
25 chat: bool,
26 temperature: f32,
28 top_k: usize,
29 top_p: Option<f32>,
30 seed: u64,
31 repeat_penalty: f32,
32 repeat_last_n: usize,
33 split_prompt: bool,
34) -> Result<(), CliError> {
35 let effective_trace = trace || trace_payload;
36 let effective_trace_level = if trace_payload {
37 "payload"
38 } else {
39 trace_level
40 };
41 let merged_prompt = prompt.or(positional_prompt).cloned();
42 let use_chat = chat || {
47 let src_lower = source.to_lowercase();
48 merged_prompt.is_some()
49 && (src_lower.contains("instruct") || src_lower.contains("chat"))
50 };
51 let effective_prompt = if use_chat {
52 merged_prompt
53 .as_ref()
54 .map(|p| format!("<|im_start|>user\n{p}<|im_end|>\n<|im_start|>assistant\n"))
55 } else {
56 merged_prompt
57 };
58
59 run::run(
60 source,
61 input,
62 effective_prompt.as_deref(),
63 max_tokens,
64 stream,
65 language,
66 task,
67 format,
68 no_gpu,
69 offline,
70 benchmark,
71 verbose,
72 effective_trace,
73 trace_steps,
74 trace_verbose,
75 trace_output,
76 effective_trace_level,
77 profile,
78 temperature,
79 top_k,
80 top_p,
81 seed,
82 repeat_penalty,
83 repeat_last_n,
84 split_prompt,
85 )
86}
87
88#[allow(clippy::too_many_arguments)]
90fn dispatch_serve(
91 file: &Path,
92 port: u16,
93 host: &str,
94 no_cors: bool,
95 no_metrics: bool,
96 no_gpu: bool,
97 gpu: bool,
98 batch: bool,
99 trace: bool,
100 trace_level: &str,
101 profile: bool,
102 verbose: bool,
103 backend: &Option<String>,
104 otlp_endpoint: &Option<String>,
105 context_length: usize,
106 no_fp8_cache: bool,
107 ollama_compat: bool,
108) -> Result<(), CliError> {
109 if let Some(ref endpoint) = otlp_endpoint {
110 eprintln!("OTLP tracing enabled → {endpoint}");
111 eprintln!(" Spans exported as W3C Trace Context (PMAT-485)");
112 }
113 let config = serve::ServerConfig {
114 port: if ollama_compat && port == 8080 { 11434 } else { port },
115 host: host.to_owned(),
116 cors: !no_cors,
117 metrics: !no_metrics,
118 no_gpu,
119 gpu,
120 batch,
121 trace,
122 trace_level: trace_level.to_owned(),
123 profile,
124 verbose,
125 backend: backend.clone(),
126 otlp_endpoint: otlp_endpoint.clone(),
127 context_length,
128 no_fp8_cache,
129 ollama_compat,
130 ..Default::default()
131 };
132 serve::run(file, &config)
133}
134
135fn dispatch_serve_command(command: &ServeCommands, cli: &Cli) -> Result<(), CliError> {
137 match command {
138 ServeCommands::Plan {
139 model,
140 gpu,
141 batch_size,
142 seq_len,
143 format,
144 quant,
145 } => {
146 let effective_format = if cli.json { "json" } else { format.as_str() };
148 commands::serve_plan::run_serve_plan(
149 model, *gpu, *batch_size, *seq_len, effective_format, quant.as_deref(),
150 )
151 }
152 ServeCommands::Run {
153 file,
154 port,
155 host,
156 no_cors,
157 no_metrics,
158 no_gpu,
159 gpu,
160 batch,
161 trace,
162 trace_level,
163 profile,
164 backend,
165 otlp_endpoint,
166 context_length,
167 no_fp8_cache,
168 ollama_compat,
169 } => crate::error::resolve_model_path(file).and_then(|r| {
170 dispatch_serve(
171 &r,
172 *port,
173 host,
174 *no_cors,
175 *no_metrics,
176 *no_gpu,
177 *gpu,
178 *batch,
179 *trace,
180 trace_level,
181 *profile,
182 cli.verbose,
183 backend,
184 otlp_endpoint,
185 *context_length,
186 *no_fp8_cache,
187 *ollama_compat,
188 )
189 }),
190 }
191}
192
193#[allow(clippy::too_many_arguments)]
195fn dispatch_hex(
196 file: &Path,
197 tensor: Option<&str>,
198 limit: usize,
199 stats: bool,
200 list: bool,
201 json: bool,
202 header: bool,
203 blocks: bool,
204 distribution: bool,
205 contract: bool,
206 entropy: bool,
207 raw: bool,
208 offset: &str,
209 width: usize,
210 slice: Option<&str>,
211) -> Result<(), CliError> {
212 let parsed_offset = hex::parse_hex_offset(offset).map_err(CliError::InvalidFormat)?;
213 hex::run(&hex::HexOptions {
214 file: file.to_path_buf(),
215 tensor: tensor.map(String::from),
216 limit,
217 stats,
218 list,
219 json,
220 header,
221 blocks,
222 distribution,
223 contract,
224 entropy,
225 raw,
226 offset: parsed_offset,
227 width,
228 slice: slice.map(String::from),
229 })
230}
231
232fn dispatch_rosetta(action: &RosettaCommands, global_json: bool) -> Result<(), CliError> {
234 match action {
235 RosettaCommands::Inspect {
236 file,
237 hexdump,
238 json,
239 } => rosetta::run_inspect(file, *hexdump, *json || global_json),
240 RosettaCommands::Convert {
241 source,
242 target,
243 quantize,
244 verify,
245 json,
246 tokenizer,
247 } => rosetta::run_convert(
248 source,
249 target,
250 quantize.as_deref(),
251 *verify,
252 *json || global_json,
253 tokenizer.as_deref(),
254 ),
255 RosettaCommands::Chain {
256 source,
257 formats,
258 work_dir,
259 json,
260 } => rosetta::run_chain(source, formats, work_dir, *json || global_json),
261 RosettaCommands::Verify {
262 source,
263 intermediate,
264 tolerance,
265 json,
266 } => rosetta::run_verify(source, intermediate, *tolerance, *json || global_json),
267 RosettaCommands::CompareInference {
268 model_a,
269 model_b,
270 prompt,
271 max_tokens,
272 temperature,
273 tolerance,
274 json,
275 } => rosetta::run_compare_inference(
276 model_a,
277 model_b,
278 prompt,
279 *max_tokens,
280 *temperature,
281 *tolerance,
282 *json || global_json,
283 ),
284 RosettaCommands::DiffTensors {
285 model_a,
286 model_b,
287 mismatches_only,
288 show_values,
289 filter,
290 json,
291 } => rosetta::run_diff_tensors(
292 model_a,
293 model_b,
294 *mismatches_only,
295 *show_values,
296 filter.as_deref(),
297 *json || global_json,
298 ),
299 RosettaCommands::Fingerprint {
300 model,
301 model_b,
302 output,
303 filter,
304 verbose,
305 json,
306 } => rosetta::run_fingerprint(
307 model,
308 model_b.as_ref().map(std::path::PathBuf::as_path),
309 output.as_ref().map(std::path::PathBuf::as_path),
310 filter.as_deref(),
311 *verbose,
312 *json || global_json,
313 ),
314 RosettaCommands::ValidateStats {
315 model,
316 reference,
317 fingerprints,
318 threshold,
319 strict,
320 json,
321 } => rosetta::run_validate_stats(
322 model,
323 reference.as_ref().map(std::path::PathBuf::as_path),
324 fingerprints.as_ref().map(std::path::PathBuf::as_path),
325 *threshold,
326 *strict,
327 *json || global_json,
328 ),
329 }
330}
331
332pub fn execute_command(cli: &Cli) -> Result<(), CliError> {
334 contract_pre_contract_gate_enforcement!();
335 if !cli.skip_contract {
337 let paths = extract_model_paths(&cli.command);
338 validate_model_contract(&paths)?;
339 }
340
341 dispatch_core_command(cli).unwrap_or_else(|| dispatch_extended_command(cli))
342}