1use std::path::PathBuf;
2
3use anyhow::Context;
4use clap::Subcommand;
5use serde::Serialize;
6
7use zsfm_tabicl::config::TabIclConfig;
8
9#[derive(Subcommand)]
10pub enum Command {
11 InspectTensors { path: PathBuf },
14 Upload {
16 #[arg(short, long, default_value = "amaye15/tabicl-gguf")]
17 repo: String,
18 #[arg(long, env = "HF_TOKEN")]
19 token: String,
20 #[arg(long, default_value = ".")]
21 root: PathBuf,
22 },
23 Infer {
34 #[arg(short, long)]
35 gguf: PathBuf,
36 },
37
38 Delete {
45 #[arg(short, long, default_value = "jingang/TabICL")]
47 model: String,
48 #[arg(long, default_value = "models")]
50 model_dir: PathBuf,
51 #[arg(short, long)]
53 output: Option<PathBuf>,
54 },
55}
56
57pub async fn run(command: Command) -> anyhow::Result<()> {
58 match command {
59 Command::Delete {
60 model,
61 model_dir,
62 output,
63 } => {
64 let canonical = zsfm_hub::canonical_gguf_path(&model_dir, &model);
65 crate::common::delete_cached_model(&canonical, output.as_deref())?;
66 }
67 Command::Upload { repo, token, root } => {
68 zsfm_hub::upload_repo(&repo, &token, &root).await?;
69 }
70
71 Command::InspectTensors { path } => crate::common::inspect_tensors(&path)?,
72
73 Command::Infer { gguf } => {
74 run_infer_burn(&gguf).await?;
75 }
76 }
77
78 Ok(())
79}
80
81async fn run_infer_burn(gguf: &std::path::PathBuf) -> anyhow::Result<()> {
82 use zsfm_tabicl::infer::BurnTabIclModel;
83
84 let buf = zsfm_core::read_stdin_limited()?;
85 let req: serde_json::Value = serde_json::from_str(&buf).context("parse JSON input")?;
86
87 let x_support = zsfm_core::parse_matrix(&req["x_support"])?;
88 let x_query = zsfm_core::parse_matrix(&req["x_query"])?;
89 let y_support: Vec<usize> = serde_json::from_value(req["y_support"].clone())
90 .context("expected a 1D JSON integer array for `y_support`")?;
91 let n_classes = req
92 .get("n_classes")
93 .and_then(|v| v.as_u64())
94 .unwrap_or_else(|| {
95 y_support
96 .iter()
97 .copied()
98 .max()
99 .map(|m| m as u64 + 1)
100 .unwrap_or(1)
101 }) as usize;
102
103 let config = TabIclConfig::v2();
104 eprintln!("Loading Burn model from {} …", gguf.display());
105 let model = BurnTabIclModel::load(gguf, config).context("load model")?;
106
107 eprintln!(
108 "Running classification ({} support / {} query rows, {n_classes} classes) …",
109 x_support.len(),
110 x_query.len()
111 );
112 let probabilities = model
113 .predict_classification(&x_support, &y_support, &x_query, n_classes)
114 .context("predict")?;
115
116 #[derive(Serialize)]
117 struct Resp {
118 task: &'static str,
119 probabilities: Vec<Vec<f32>>,
120 }
121 println!(
122 "{}",
123 serde_json::to_string_pretty(&Resp {
124 task: "classification",
125 probabilities
126 })?
127 );
128 Ok(())
129}