Skip to main content

teeny_cli/
lib.rs

1/*
2 * Copyright (c) 2026 Teenygrad.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! Shared `--device`/`--options` CLI configuration for ahead-of-time kernel
18//! compilation, meant to be `#[command(flatten)]`-ed into a downstream
19//! project's own binary (e.g. a `vision-rs` demo) and driven end-to-end by
20//! `cargo teeny aot` in `cargo-teeny`.
21//!
22//! `--device` selects a backend; everything after that (including
23//! `--options`) is opaque here and handed to that backend's own parser —
24//! today that's `teeny_cuda::compiler::options::Options::parse` for
25//! `--device cuda`. Adding a new backend means adding a match arm in
26//! [`aot_compile`], not touching [`AotArgs`].
27
28#![warn(missing_docs)]
29
30use std::path::PathBuf;
31
32use anyhow::{Result, anyhow};
33use teeny_core::graph::{DtypeRepr, Shape, SymTensor};
34use teeny_core::model::{Lowering, LoweringMode};
35use teeny_core::nn::Layer;
36
37/// Default fallback cache directory when neither `--cache-dir` nor
38/// `$TEENYC_CACHE_DIR` is set. Matches the default used by the runtime JIT
39/// compile path (`teeny_compiler::compiler::driver::cuda::compile_kernel`).
40const DEFAULT_CACHE_DIR: &str = "/tmp/teenyc_cache";
41
42/// Shared AOT-compile CLI arguments. Flatten this into your own `clap`
43/// struct: `#[command(flatten)] aot: teeny_cli::AotArgs`.
44#[derive(clap::Args, Clone, Debug)]
45pub struct AotArgs {
46    /// Target backend to compile for (e.g. `cuda`).
47    #[arg(long)]
48    pub device: String,
49
50    /// Backend-specific compiler options as comma-separated `key=value`
51    /// pairs (e.g. `"capability=sm_90,maxnreg=16"`). Parsed by whichever
52    /// backend `--device` selects.
53    #[arg(long)]
54    pub options: Option<String>,
55
56    /// Directory to write/read compiled kernels. Falls back to
57    /// `$TEENYC_CACHE_DIR`, then `/tmp/teenyc_cache` if neither is set.
58    #[arg(long)]
59    pub cache_dir: Option<PathBuf>,
60
61    /// Recompile even if a cached artifact already exists.
62    #[arg(long)]
63    pub force: bool,
64}
65
66impl AotArgs {
67    /// Resolve the effective cache directory: `--cache-dir` > `$TEENYC_CACHE_DIR` > default.
68    pub fn resolve_cache_dir(&self) -> PathBuf {
69        self.cache_dir
70            .clone()
71            .or_else(|| std::env::var_os("TEENYC_CACHE_DIR").map(PathBuf::from))
72            .unwrap_or_else(|| PathBuf::from(DEFAULT_CACHE_DIR))
73    }
74}
75
76/// Trace `model` with a symbolic sample input of `input_dtype`/`input_shape`,
77/// then ahead-of-time compile every kernel the traced graph references for
78/// the backend/config selected by `args`.
79///
80/// The compiled artifacts are written to `args.resolve_cache_dir()` as a
81/// side effect; this returns `()` rather than a backend-specific model type
82/// so that callers (and `--device` dispatch here) don't need to unify return
83/// types across backends. Callers that need the compiled model in-process
84/// (e.g. to immediately `.load()` it onto a live device) should call the
85/// backend's own compile entry point directly instead.
86#[cfg_attr(not(feature = "cuda"), allow(unused_variables))]
87pub fn aot_compile<'a, M, L>(
88    model: &M,
89    input_dtype: DtypeRepr,
90    input_shape: Shape,
91    lowering: &L,
92    mode: LoweringMode,
93    args: &AotArgs,
94) -> Result<()>
95where
96    M: Layer<SymTensor>,
97    L: Lowering<'a>,
98{
99    match args.device.as_str() {
100        #[cfg(feature = "cuda")]
101        "cuda" => {
102            let (input, graph) = SymTensor::input(input_dtype, input_shape);
103            let _ = model.call(input);
104            let graph = graph.borrow();
105
106            let options = teeny_cuda::compiler::options::Options::parse(
107                args.options.as_deref().unwrap_or(""),
108            )?;
109
110            let cache_dir = args.resolve_cache_dir();
111            std::fs::create_dir_all(&cache_dir)?;
112
113            teeny_cuda::compiler::aot::compile_graph(
114                &graph,
115                lowering,
116                mode,
117                &options,
118                cache_dir.to_string_lossy().as_ref(),
119                args.force,
120            )?;
121
122            Ok(())
123        }
124        other => Err(anyhow!(
125            "unsupported --device '{other}'; supported backends: {}",
126            supported_backends()
127        )),
128    }
129}
130
131fn supported_backends() -> &'static str {
132    #[cfg(feature = "cuda")]
133    {
134        "cuda"
135    }
136    #[cfg(not(feature = "cuda"))]
137    {
138        "(none enabled; rebuild teeny-cli with --features cuda)"
139    }
140}