Skip to main content

apr_cli/
extended_commands.rs

1
2/// Extended CLI commands (analysis, profiling, QA, benchmarks, and advanced tools).
3///
4/// Flattened into `Commands` via `#[command(flatten)]` so all subcommands remain
5/// top-level from the user's perspective (e.g., `apr chat`, `apr profile`).
6#[derive(Subcommand, Debug)]
7pub enum ExtendedCommands {
8    /// Interactive chat with language model
9    Chat {
10        /// Path to .apr model file
11        #[arg(value_name = "FILE")]
12        file: PathBuf,
13        /// Sampling temperature (0 = greedy, higher = more random)
14        #[arg(long, default_value = "0.7")]
15        temperature: f32,
16        /// Nucleus sampling threshold
17        #[arg(long, default_value = "0.9")]
18        top_p: f32,
19        /// Maximum tokens to generate per response
20        #[arg(long, default_value = "512")]
21        max_tokens: usize,
22        /// System prompt to set model behavior
23        #[arg(long)]
24        system: Option<String>,
25        /// Show inspection info (top-k probs, tokens/sec)
26        #[arg(long)]
27        inspect: bool,
28        /// Disable GPU acceleration (use CPU)
29        #[arg(long)]
30        no_gpu: bool,
31        /// Force GPU acceleration (requires CUDA)
32        #[arg(long)]
33        gpu: bool,
34        /// Enable inference tracing (APR-TRACE-001)
35        #[arg(long)]
36        trace: bool,
37        /// Trace specific steps only (comma-separated)
38        #[arg(long, value_delimiter = ',')]
39        trace_steps: Option<Vec<String>>,
40        /// Verbose tracing
41        #[arg(long)]
42        trace_verbose: bool,
43        /// Save trace output to JSON file
44        #[arg(long, value_name = "FILE")]
45        trace_output: Option<PathBuf>,
46        /// Trace detail level (none, basic, layer, payload)
47        #[arg(long, value_name = "LEVEL", default_value = "basic")]
48        trace_level: String,
49        /// Enable inline Roofline profiling (PMAT-SHOWCASE-METHODOLOGY-001)
50        #[arg(long)]
51        profile: bool,
52        /// PMAT-488: Compute backend override (cuda, cpu, wgpu)
53        #[arg(long, value_name = "BACKEND")]
54        backend: Option<String>,
55    },
56    /// Benchmark throughput (spec H12: >= 10 tok/s)
57    Bench {
58        /// Path to model file
59        #[arg(value_name = "FILE")]
60        file: PathBuf,
61        /// Number of warmup iterations
62        #[arg(long, default_value = "3")]
63        warmup: usize,
64        /// Number of measurement iterations
65        #[arg(long, default_value = "5")]
66        iterations: usize,
67        /// Max tokens to generate per iteration
68        #[arg(long, default_value = "32")]
69        max_tokens: usize,
70        /// Test prompt
71        #[arg(long)]
72        prompt: Option<String>,
73        /// Use realizar for fast inference (vs aprender baseline)
74        #[arg(long)]
75        fast: bool,
76        /// Benchmark specific brick
77        #[arg(long)]
78        brick: Option<String>,
79    },
80    /// Evaluate model perplexity (spec H13: PPL <= 20) or classification metrics
81    Eval {
82        /// Path to model file or checkpoint directory
83        #[arg(value_name = "FILE")]
84        file: PathBuf,
85        /// Dataset: wikitext-2, lambada, or custom
86        #[arg(long, default_value = "wikitext-2")]
87        dataset: String,
88        /// Custom text (when dataset=custom)
89        #[arg(long)]
90        text: Option<String>,
91        /// Maximum tokens to evaluate
92        #[arg(long, default_value = "512")]
93        max_tokens: usize,
94        /// Perplexity threshold for pass/fail
95        #[arg(long, default_value = "20.0")]
96        threshold: f32,
97        /// Task type: omit for perplexity, "classify" for classification eval
98        #[arg(long)]
99        task: Option<String>,
100        /// Test data file (JSONL) for classification evaluation
101        #[arg(long, value_name = "FILE")]
102        data: Option<PathBuf>,
103        /// Model size hint: "0.5B", "tiny" (for classification eval)
104        #[arg(long)]
105        model_size: Option<String>,
106        /// Number of output classes (default: 5)
107        #[arg(long, default_value = "5")]
108        num_classes: usize,
109        /// Generate HuggingFace model card (README.md) in checkpoint dir
110        #[arg(long)]
111        generate_card: bool,
112        /// Device for inference: "cpu" (default) or "cuda" (GPU-accelerated, ALB-089)
113        #[arg(long, default_value = "cpu")]
114        device: String,
115        /// Number of samples per problem for pass@k (ALB-088, default: 1)
116        #[arg(long, default_value = "1")]
117        samples: usize,
118        /// Sampling temperature (0.0 = greedy, 0.8 = standard for pass@k>1)
119        #[arg(long, default_value = "0.0")]
120        temperature: f32,
121    },
122    /// Deep profiling with Roofline analysis
123    Profile {
124        /// Path to model file
125        #[arg(value_name = "FILE")]
126        file: PathBuf,
127        /// Layer-by-layer granular analysis
128        #[arg(long)]
129        granular: bool,
130        /// Output format (human, json, flamegraph)
131        #[arg(long, default_value = "human")]
132        format: String,
133        /// Focus on specific operation
134        #[arg(long)]
135        focus: Option<String>,
136        /// Detect naive implementations
137        #[arg(long)]
138        detect_naive: bool,
139        /// GFLOPS threshold for naive detection
140        #[arg(long, default_value = "10.0")]
141        threshold: f64,
142        /// Compare against HuggingFace baseline
143        #[arg(long)]
144        compare_hf: Option<String>,
145        /// Measure energy consumption (requires RAPL)
146        #[arg(long)]
147        energy: bool,
148        /// Compute performance grade (vs Ollama baseline)
149        #[arg(long)]
150        perf_grade: bool,
151        /// Show call graph
152        #[arg(long)]
153        callgraph: bool,
154        /// Exit non-zero if naive implementation detected
155        #[arg(long)]
156        fail_on_naive: bool,
157        /// Output file path for flamegraph SVG (GH-174, PMAT-182)
158        #[arg(long, short = 'o')]
159        output: Option<PathBuf>,
160
161        // PMAT-192: CI Assertion Mode (GH-180)
162        /// Enable CI mode with assertion checks (exits 1 on failure)
163        #[arg(long)]
164        ci: bool,
165        /// Minimum throughput in tok/s (CI assertion, exits 1 if below)
166        #[arg(long)]
167        assert_throughput: Option<f64>,
168        /// Maximum p99 latency in ms (CI assertion, exits 1 if above)
169        #[arg(long)]
170        assert_p99: Option<f64>,
171        /// Maximum p50 latency in ms (CI assertion, exits 1 if above)
172        #[arg(long)]
173        assert_p50: Option<f64>,
174        /// Warmup passes before measurement (default: 3)
175        #[arg(long, default_value = "3")]
176        warmup: usize,
177        /// Measurement passes (default: 10)
178        #[arg(long, default_value = "10")]
179        measure: usize,
180        /// Number of tokens to generate per measurement pass (default: 32)
181        #[arg(long, default_value = "32")]
182        tokens: usize,
183        /// Compare against Ollama baseline (runs ollama for comparison)
184        #[arg(long)]
185        ollama: bool,
186        /// Disable GPU (force CPU-only profiling)
187        #[arg(long)]
188        no_gpu: bool,
189        /// Compare against another model format (F-PROFILE-011)
190        #[arg(long, value_name = "FILE")]
191        compare: Option<PathBuf>,
192    },
193    /// Falsifiable QA checklist for model releases
194    Qa {
195        /// Path to model file
196        #[arg(value_name = "FILE")]
197        file: PathBuf,
198        /// Minimum throughput threshold in tok/s
199        #[arg(long, value_name = "TPS")]
200        assert_tps: Option<f64>,
201        /// Minimum speedup vs Ollama
202        #[arg(long, value_name = "SPEEDUP")]
203        assert_speedup: Option<f64>,
204        /// Minimum GPU vs CPU speedup (F-PERF-042)
205        #[arg(long, value_name = "SPEEDUP")]
206        assert_gpu_speedup: Option<f64>,
207        /// Skip golden output test
208        #[arg(long)]
209        skip_golden: bool,
210        /// Skip throughput benchmark
211        #[arg(long)]
212        skip_throughput: bool,
213        /// Skip Ollama parity comparison
214        #[arg(long)]
215        skip_ollama: bool,
216        /// Skip GPU vs CPU speedup test (F-PERF-042)
217        #[arg(long)]
218        skip_gpu_speedup: bool,
219        /// Skip tensor contract validation (PMAT-235)
220        #[arg(long)]
221        skip_contract: bool,
222        /// Skip cross-format parity test (F-QUAL-032)
223        #[arg(long)]
224        skip_format_parity: bool,
225        /// Skip PTX parity validation (GH-219)
226        #[arg(long)]
227        skip_ptx_parity: bool,
228        /// SafeTensors model path for cross-format parity test (F-QUAL-032)
229        #[arg(long, value_name = "PATH")]
230        safetensors_path: Option<PathBuf>,
231        /// Number of benchmark iterations
232        #[arg(long, default_value = "10")]
233        iterations: usize,
234        /// Number of warmup iterations
235        #[arg(long, default_value = "3")]
236        warmup: usize,
237        /// Maximum tokens to generate
238        #[arg(long, default_value = "32")]
239        max_tokens: usize,
240        /// Output as JSON (for CI integration)
241        #[arg(long)]
242        json: bool,
243        /// Verbose output
244        #[arg(short, long)]
245        verbose: bool,
246        /// Minimum number of gates that must execute (fail if fewer)
247        #[arg(long, value_name = "N")]
248        min_executed: Option<usize>,
249        /// Previous QA report for regression detection
250        #[arg(long, value_name = "FILE")]
251        previous_report: Option<PathBuf>,
252        /// Maximum allowed performance regression ratio (default: 0.10 = 10%)
253        #[arg(long, value_name = "RATIO")]
254        regression_threshold: Option<f64>,
255        /// Skip GPU state isolation test
256        #[arg(long)]
257        skip_gpu_state: bool,
258        /// Skip metadata plausibility validation (Bug 210, GH-222)
259        #[arg(long)]
260        skip_metadata: bool,
261        /// Skip GPU capability match gate (GH-280)
262        #[arg(long)]
263        skip_capability: bool,
264        /// Assert classifier head presence and shape (F-CLASS-004)
265        #[arg(long)]
266        assert_classifier_head: bool,
267    },
268    /// GPU/CPU parity check (PMAT-232: genchi genbutsu — see where GPU diverges)
269    Parity {
270        /// Path to GGUF model file
271        #[arg(value_name = "FILE")]
272        file: PathBuf,
273        /// Prompt text (default: "What is 2+2?")
274        #[arg(short, long, default_value = "What is 2+2?")]
275        prompt: String,
276        /// Assert parity (exit non-zero on divergence)
277        #[arg(long)]
278        assert: bool,
279    },
280    /// Model-to-PTX source mapping (Mieruka: make GPU kernel dispatch visible)
281    #[command(name = "ptx-map")]
282    PtxMap {
283        /// Path to GGUF model file
284        #[arg(value_name = "FILE")]
285        file: PathBuf,
286        /// Filter to specific kernel (e.g., --kernel Q4KGemv)
287        #[arg(long)]
288        kernel: Option<String>,
289        /// Reverse lookup: kernel name -> which layers/steps use it
290        #[arg(long)]
291        reverse: Option<String>,
292        /// Output as JSON
293        #[arg(long)]
294        json: bool,
295        /// Full PTX snippets and detailed analysis
296        #[arg(short, long)]
297        verbose: bool,
298        /// Show batched prefill kernel variants instead of decode
299        #[arg(long)]
300        prefill: bool,
301    },
302    /// PTX analysis and bug detection (register pressure, roofline)
303    #[command(name = "ptx")]
304    Ptx {
305        /// Path to a PTX source file
306        #[arg(value_name = "FILE")]
307        file: Option<PathBuf>,
308        /// Analyze a named kernel from trueno-gpu
309        #[arg(long, short)]
310        kernel: Option<String>,
311        /// Strict mode (no performance whitelist)
312        #[arg(long)]
313        strict: bool,
314        /// Show only bug analysis (skip register/memory/roofline)
315        #[arg(long)]
316        bugs: bool,
317        /// Output as JSON
318        #[arg(long)]
319        json: bool,
320        /// Verbose output (include PTX source listing)
321        #[arg(short, long)]
322        verbose: bool,
323    },
324    /// ML tuning: LoRA/QLoRA configuration, memory planning, and HPO (GH-176, SPEC-TUNE-2026-001)
325    #[cfg(feature = "training")]
326    Tune {
327        /// Path to model file (optional if using --model)
328        #[arg(value_name = "FILE")]
329        file: Option<PathBuf>,
330        /// Tuning method: auto, full, lora, qlora
331        #[arg(long, short = 'm', default_value = "auto")]
332        method: String,
333        /// LoRA rank (default: auto-selected)
334        #[arg(long, short = 'r')]
335        rank: Option<u32>,
336        /// Available VRAM in GB
337        #[arg(long, default_value = "16.0")]
338        vram: f64,
339        /// Only plan configuration, don't train
340        #[arg(long)]
341        plan: bool,
342        /// Model size for planning (e.g., "7B", "1.5B")
343        #[arg(long, value_name = "SIZE")]
344        model: Option<String>,
345        /// Freeze base model weights
346        #[arg(long)]
347        freeze_base: bool,
348        /// Training data file (JSONL format)
349        #[arg(long, value_name = "FILE")]
350        train_data: Option<PathBuf>,
351        /// Output as JSON (for CI integration)
352        #[arg(long)]
353        json: bool,
354        /// Task type for HPO: classify (SPEC-TUNE-2026-001)
355        #[arg(long)]
356        task: Option<String>,
357        /// Number of HPO trials (default: 10)
358        #[arg(long, default_value = "10")]
359        budget: usize,
360        /// HPO search strategy: tpe, grid, random
361        #[arg(long, default_value = "tpe")]
362        strategy: String,
363        /// HPO scheduler: asha, median, none
364        #[arg(long, default_value = "asha")]
365        scheduler: String,
366        /// Scout mode: 1 epoch per trial for fast exploration
367        #[arg(long)]
368        scout: bool,
369        /// Training data file for HPO (JSONL format)
370        #[arg(long, value_name = "FILE")]
371        data: Option<PathBuf>,
372        /// Number of output classes for classification
373        #[arg(long, default_value = "5")]
374        num_classes: usize,
375        /// Model size hint for HPO (e.g., "0.5B", "1.5B")
376        #[arg(long)]
377        model_size: Option<String>,
378        /// Warm-start from scout phase results directory
379        #[arg(long, value_name = "DIR")]
380        from_scout: Option<PathBuf>,
381        /// Maximum epochs per trial (full mode, default: 20)
382        #[arg(long, default_value = "20")]
383        max_epochs: usize,
384        /// Maximum wall-clock time (e.g., "8h", "30m")
385        #[arg(long)]
386        time_limit: Option<String>,
387    },
388    /// Attach live TUI to a running training session
389    #[cfg(feature = "training")]
390    Monitor {
391        /// Experiment output directory (same as finetune -o)
392        #[arg(value_name = "DIR")]
393        dir: Option<PathBuf>,
394        /// Refresh interval in milliseconds
395        #[arg(long, default_value = "500")]
396        refresh_ms: u64,
397        /// Compact display mode
398        #[arg(long)]
399        compact: bool,
400        /// Output JSON lines instead of TUI (for LLM agents and CI)
401        #[arg(long)]
402        json: bool,
403        /// Output format: tui (default), json, text
404        #[arg(long, default_value = "tui")]
405        format: String,
406    },
407    /// List, show, and compare training experiment runs
408    #[cfg(feature = "training")]
409    Runs {
410        #[command(subcommand)]
411        command: RunsCommands,
412    },
413    /// Interactive experiment browser (TUI with loss curves)
414    #[cfg(feature = "training")]
415    Experiment {
416        #[command(subcommand)]
417        command: ExperimentCommands,
418    },
419    /// ComputeBrick pipeline monitor (cbtop)
420    Cbtop {
421        /// Model name (e.g., qwen2.5-coder-1.5b)
422        #[arg(long)]
423        model: Option<String>,
424        /// Attach to running realizar process
425        #[arg(long)]
426        attach: Option<String>,
427        /// Path to GGUF model file for real profiling
428        #[arg(long, value_name = "MODEL")]
429        model_path: Option<PathBuf>,
430        /// Run in headless mode (no TUI, for CI/automation)
431        #[arg(long)]
432        headless: bool,
433        /// Output JSON format (requires --headless)
434        #[arg(long)]
435        json: bool,
436        /// Output file path (requires --headless)
437        #[arg(long, value_name = "FILE")]
438        output: Option<PathBuf>,
439        /// CI mode: exit with code 1 if thresholds not met
440        #[arg(long)]
441        ci: bool,
442        /// Minimum throughput threshold in tok/s (for --ci)
443        #[arg(long, value_name = "TOK_S")]
444        throughput: Option<f64>,
445        /// Minimum brick score threshold 0-100 (for --ci)
446        #[arg(long, value_name = "SCORE")]
447        brick_score: Option<u32>,
448        /// Number of warmup iterations before measurement
449        #[arg(long, default_value = "10")]
450        warmup: usize,
451        /// Number of measurement iterations
452        #[arg(long, default_value = "100")]
453        iterations: usize,
454        /// PAR-100: Enable speculative decoding benchmark
455        #[arg(long)]
456        speculative: bool,
457        /// PAR-100: Number of tokens to draft speculatively (default: 4)
458        #[arg(long, default_value = "4")]
459        speculation_k: usize,
460        /// PAR-099: Path to draft model for speculative decoding
461        #[arg(long, value_name = "DRAFT_MODEL")]
462        draft_model: Option<PathBuf>,
463        /// PAR-102: Number of concurrent requests
464        #[arg(long, default_value = "1")]
465        concurrent: usize,
466        /// Use simulated data (for CI testing only)
467        #[arg(long)]
468        simulated: bool,
469    },
470    /// Export for probar visual regression testing (PMAT-481)
471    Probar {
472        /// Path to .apr model file
473        #[arg(value_name = "FILE")]
474        file: PathBuf,
475        /// Output directory for test artifacts
476        #[arg(short, long, default_value = "./probar-export")]
477        output: PathBuf,
478        /// Export format: json, png, or both
479        #[arg(long, default_value = "both")]
480        format: String,
481        /// Golden reference directory for comparison
482        #[arg(long)]
483        golden: Option<PathBuf>,
484        /// Filter layers by name pattern
485        #[arg(long)]
486        layer: Option<String>,
487        /// Exit non-zero on golden divergence (CI mode, PMAT-481)
488        #[arg(long)]
489        assert: bool,
490        /// Cosine similarity threshold for golden comparison (default: 0.98)
491        #[arg(long, default_value = "0.98")]
492        tolerance: f32,
493    },
494    /// Compare APR model against HuggingFace source
495    #[command(name = "compare-hf")]
496    CompareHf {
497        /// Path to .apr model file
498        #[arg(value_name = "FILE")]
499        file: PathBuf,
500        /// HuggingFace repo ID (e.g., openai/whisper-tiny)
501        #[arg(long)]
502        hf: String,
503        /// Filter tensors by name pattern
504        #[arg(long)]
505        tensor: Option<String>,
506        /// Comparison threshold (default: 1e-5)
507        #[arg(long, default_value = "1e-5")]
508        threshold: f64,
509        /// Output as JSON
510        #[arg(long)]
511        json: bool,
512    },
513    /// Format-aware binary forensics (10X better than xxd)
514    Hex {
515        /// Path to model file (APR, GGUF, or SafeTensors)
516        #[arg(value_name = "FILE")]
517        file: PathBuf,
518        /// Filter tensors by name pattern
519        #[arg(long)]
520        tensor: Option<String>,
521        /// Limit bytes/values to display
522        #[arg(long, default_value = "64")]
523        limit: usize,
524        /// Show tensor statistics
525        #[arg(long)]
526        stats: bool,
527        /// List tensor names only
528        #[arg(long)]
529        list: bool,
530        /// Output as JSON
531        #[arg(long)]
532        json: bool,
533        /// Annotated file header (magic, version, tensor count, metadata)
534        #[arg(long)]
535        header: bool,
536        /// Q4K/Q6K/Q8_0 super-block structure with field annotations
537        #[arg(long)]
538        blocks: bool,
539        /// Value histogram + entropy + kurtosis analysis
540        #[arg(long)]
541        distribution: bool,
542        /// Layout contract verification overlay per tensor
543        #[arg(long)]
544        contract: bool,
545        /// Per-region byte entropy analysis
546        #[arg(long)]
547        entropy: bool,
548        /// Raw bytes (like xxd but format-aware, with ASCII column)
549        #[arg(long)]
550        raw: bool,
551        /// Start at byte offset (supports 0x prefix for hex)
552        #[arg(long, default_value = "0")]
553        offset: String,
554        /// Bytes per row for raw output (default: 16)
555        #[arg(long, default_value = "16")]
556        width: usize,
557        /// Slice range for partial tensor reads (e.g., 0:3 for first 3 elements)
558        #[arg(long)]
559        slice: Option<String>,
560    },
561    /// Model architecture tree view
562    Tree {
563        /// Path to .apr model file
564        #[arg(value_name = "FILE")]
565        file: PathBuf,
566        /// Filter by component pattern
567        #[arg(long)]
568        filter: Option<String>,
569        /// Output format: ascii, dot, mermaid, json
570        #[arg(long, default_value = "ascii")]
571        format: String,
572        /// Show tensor sizes
573        #[arg(long)]
574        sizes: bool,
575        /// Maximum tree depth
576        #[arg(long)]
577        depth: Option<usize>,
578    },
579    /// Data flow visualization
580    Flow {
581        /// Path to .apr model file
582        #[arg(value_name = "FILE")]
583        file: PathBuf,
584        /// Filter by layer pattern
585        #[arg(long)]
586        layer: Option<String>,
587        /// Component to visualize: full, encoder, decoder, etc.
588        #[arg(long, default_value = "full")]
589        component: String,
590        /// Verbose output with statistics
591        #[arg(short, long)]
592        verbose: bool,
593        /// Output as JSON
594        #[arg(long)]
595        json: bool,
596    },
597    /// Cross-subcommand smoke test (does every tool handle this model?)
598    Qualify {
599        /// Path to model file (APR, GGUF, or SafeTensors)
600        #[arg(value_name = "FILE")]
601        file: PathBuf,
602        /// Testing tier: smoke (Phase 1), standard (+contracts), full (+playbook)
603        #[arg(long, default_value = "smoke")]
604        tier: String,
605        /// Timeout per gate in seconds
606        #[arg(long, default_value = "120")]
607        timeout: u64,
608        /// Output as JSON
609        #[arg(long)]
610        json: bool,
611        /// Show subcommand output (disable stdout suppression)
612        #[arg(short, long)]
613        verbose: bool,
614        /// Skip specific gates (comma-separated)
615        #[arg(long, value_delimiter = ',')]
616        skip: Option<Vec<String>>,
617    },
618    /// Training pipeline (plan/apply) — forjar-style pre-flight validation
619    #[cfg(feature = "training")]
620    Train {
621        #[command(subcommand)]
622        command: TrainCommands,
623    },
624    /// Pretraining loop driver (SHIP-TWO-001 MODEL-2).
625    ///
626    /// Wires the pretraining loop shape defined by
627    /// `contracts/training-loop-pretrain-v1.yaml`. Executes a synthetic
628    /// decreasing-loss drive by default so GATE-TRAIN-005 / -007 / -008
629    /// divergence-and-NaN guards can be exercised without an actual
630    /// 370M compute run. Real corpus wiring is a follow-up ticket.
631    #[cfg(feature = "training")]
632    Pretrain {
633        /// Dataset path (tokenized shard index or raw corpus).
634        #[arg(long, value_name = "PATH")]
635        dataset: PathBuf,
636        /// Tokenizer directory (vocab.json + merges.txt).
637        #[arg(long, value_name = "DIR")]
638        tokenizer: PathBuf,
639        /// Run output directory — checkpoints + metadata go to `{run_dir}/ckpt/`.
640        #[arg(long, value_name = "DIR")]
641        run_dir: PathBuf,
642        /// Peak learning rate after warmup (MODEL-1 v2 remedy default = 5e-5).
643        #[arg(long, default_value = "5e-5")]
644        lr: f32,
645        /// Warmup + cosine decay total steps.
646        #[arg(long, default_value = "1000")]
647        num_steps: usize,
648        /// Number of warmup steps.
649        #[arg(long, default_value = "100")]
650        warmup_steps: usize,
651        /// Micro-batch size.
652        #[arg(long, default_value = "16")]
653        batch_size: usize,
654        /// Sequence length per example.
655        #[arg(long, default_value = "1024")]
656        seq_length: usize,
657        /// Steps per epoch — controls per-epoch artifact cadence.
658        #[arg(long, default_value = "100")]
659        steps_per_epoch: usize,
660        /// GATE-TRAIN-006 fixed RNG seed.
661        #[arg(long, default_value = "42")]
662        seed: u64,
663        /// GATE-TRAIN-003 target val_loss (≤ 2.2 per spec).
664        #[arg(long, default_value = "2.2")]
665        target_val_loss: f32,
666        /// Synthetic-drive only — do not attempt real compute, exercise loop gates only.
667        #[arg(long, default_value = "true")]
668        synthetic: bool,
669    },
670    /// Tokenizer training pipeline (plan/apply) — BPE vocabulary learning
671    Tokenize {
672        #[command(subcommand)]
673        command: TokenizeCommands,
674    },
675    /// Data quality pipeline (audit, split, balance) — powered by alimentar
676    Data {
677        #[command(subcommand)]
678        command: DataCommands,
679    },
680    /// Pipeline orchestration (plan/apply/status) — wraps forjar DAG engine
681    Pipeline {
682        #[command(subcommand)]
683        command: PipelineCommands,
684    },
685    /// Automated Five Whys diagnosis on a training checkpoint
686    Diagnose {
687        /// Path to checkpoint directory
688        #[arg(value_name = "CHECKPOINT_DIR")]
689        checkpoint_dir: PathBuf,
690        /// Test data file (JSONL) for evaluation
691        #[arg(long, value_name = "FILE")]
692        data: Option<PathBuf>,
693        /// Model size hint: "0.5B", "tiny"
694        #[arg(long)]
695        model_size: Option<String>,
696        /// Number of output classes (default: 5)
697        #[arg(long, default_value = "5")]
698        num_classes: usize,
699    },
700    /// Publishing, conversion, and analysis tools
701    #[command(flatten)]
702    Tools(ToolCommands),
703}
704
705#[cfg(feature = "training")]
706/// Subcommands for `apr runs` — experiment run management (ALB-050/051)
707#[derive(Subcommand, Debug)]
708pub enum RunsCommands {
709    /// List all training experiment runs (with inline loss sparklines)
710    Ls {
711        /// Directory to scan for experiments (default: current dir)
712        #[arg(long, value_name = "DIR")]
713        dir: Option<PathBuf>,
714        /// Read from global experiment registry (~/.entrenar/experiments.db)
715        #[arg(long)]
716        global: bool,
717        /// Filter by status: running, completed, failed, all
718        #[arg(long, default_value = "all")]
719        status: String,
720        /// Output as JSON
721        #[arg(long)]
722        json: bool,
723        /// Maximum number of runs to show
724        #[arg(long, default_value = "50")]
725        limit: usize,
726    },
727    /// Show detailed metrics for a specific run (with braille loss curve)
728    Show {
729        /// Run ID
730        #[arg(value_name = "RUN_ID")]
731        run_id: String,
732        /// Directory containing experiment DB
733        #[arg(long, value_name = "DIR")]
734        dir: Option<PathBuf>,
735        /// Read from global registry
736        #[arg(long)]
737        global: bool,
738        /// Output as JSON
739        #[arg(long)]
740        json: bool,
741    },
742    /// Compare two runs side-by-side (loss curves, config diff, metrics)
743    Diff {
744        /// First run ID
745        #[arg(value_name = "RUN_A")]
746        run_a: String,
747        /// Second run ID
748        #[arg(value_name = "RUN_B")]
749        run_b: String,
750        /// Directory containing experiment DB
751        #[arg(long, value_name = "DIR")]
752        dir: Option<PathBuf>,
753        /// Read from global registry
754        #[arg(long)]
755        global: bool,
756        /// Output as JSON
757        #[arg(long)]
758        json: bool,
759    },
760}
761
762#[cfg(feature = "training")]
763/// Subcommands for `apr experiment` — interactive experiment browser (ALB-024)
764#[derive(Subcommand, Debug)]
765pub enum ExperimentCommands {
766    /// Browse experiment history with interactive TUI (loss curves, params)
767    View {
768        /// Path to experiment database file
769        #[arg(long, value_name = "FILE")]
770        db: Option<PathBuf>,
771        /// Read from global experiment registry (~/.entrenar/experiments.db)
772        #[arg(long)]
773        global: bool,
774        /// Output as JSON (non-interactive)
775        #[arg(long)]
776        json: bool,
777    },
778}