teeny_quant/cli/
quantize.rs1use std::collections::HashMap;
20use std::path::PathBuf;
21
22use anyhow::{Context, Result};
23use clap::Args;
24
25use crate::format::{self, PackedTensorInfo};
26use crate::quant::{Fp8Variant, Granularity, Scheme};
27use crate::read::read_f32;
28use crate::write::{OutputTensor, write_safetensors};
29
30#[derive(Args, Debug)]
32pub struct QuantizeArgs {
33 #[arg(long)]
35 pub input: PathBuf,
36
37 #[arg(long)]
39 pub output: PathBuf,
40
41 #[arg(long, value_enum)]
43 pub scheme: SchemeArg,
44
45 #[arg(long)]
48 pub asymmetric: bool,
49
50 #[arg(long, value_enum, default_value = "channel")]
52 pub granularity: GranularityArg,
53
54 #[arg(long, default_value_t = 0)]
58 pub axis: usize,
59
60 #[arg(long)]
62 pub group_size: Option<usize>,
63
64 #[arg(long, value_enum, default_value = "e4m3")]
66 pub fp8_variant: Fp8VariantArg,
67}
68
69#[derive(clap::ValueEnum, Clone, Copy, Debug)]
71pub enum SchemeArg {
72 #[value(name = "int8")]
74 Int8,
75 #[value(name = "int4")]
77 Int4,
78 #[value(name = "fp8")]
80 Fp8,
81}
82
83#[derive(clap::ValueEnum, Clone, Copy, Debug)]
85pub enum GranularityArg {
86 #[value(name = "tensor")]
88 Tensor,
89 #[value(name = "channel")]
91 Channel,
92 #[value(name = "group")]
94 Group,
95}
96
97#[derive(clap::ValueEnum, Clone, Copy, Debug)]
99pub enum Fp8VariantArg {
100 #[value(name = "e4m3")]
102 E4M3,
103 #[value(name = "e5m2")]
105 E5M2,
106}
107
108pub fn run(args: QuantizeArgs) -> Result<()> {
110 let scheme = match args.scheme {
111 SchemeArg::Int8 => Scheme::Int8 {
112 symmetric: !args.asymmetric,
113 },
114 SchemeArg::Int4 => Scheme::Int4 {
115 symmetric: !args.asymmetric,
116 },
117 SchemeArg::Fp8 => Scheme::Fp8 {
118 variant: match args.fp8_variant {
119 Fp8VariantArg::E4M3 => Fp8Variant::E4M3,
120 Fp8VariantArg::E5M2 => Fp8Variant::E5M2,
121 },
122 },
123 };
124
125 let granularity = match args.granularity {
126 GranularityArg::Tensor => Granularity::PerTensor,
127 GranularityArg::Channel => Granularity::PerChannel { axis: args.axis },
128 GranularityArg::Group => {
129 let group_size = args
130 .group_size
131 .context("--group-size is required for --granularity group")?;
132 Granularity::Group {
133 axis: args.axis,
134 group_size,
135 }
136 }
137 };
138
139 let mapped = teeny_data::safetensors::SafeTensors::from_pretrained(&args.input)
140 .with_context(|| format!("failed to open '{}'", args.input.display()))?;
141 let tensors = mapped.tensors().with_context(|| {
142 format!(
143 "failed to read tensor headers from '{}'",
144 args.input.display()
145 )
146 })?;
147
148 let mut outputs: HashMap<String, OutputTensor> = HashMap::new();
149 let mut ignored = Vec::new();
150 let mut packed_int4 = HashMap::new();
151 let mut quantized_count = 0usize;
152
153 for name in tensors.names() {
154 let view = tensors
155 .tensor(name)
156 .with_context(|| format!("reading tensor '{name}'"))?;
157 let shape = view.shape().to_vec();
158
159 if !format::should_quantize(&shape, view.dtype()) {
160 ignored.push(name.to_string());
161 outputs.insert(
162 name.to_string(),
163 OutputTensor {
164 dtype: view.dtype(),
165 shape,
166 data: view.data().to_vec(),
167 },
168 );
169 continue;
170 }
171
172 let data =
173 read_f32(&view, name).with_context(|| format!("reading tensor '{name}' as f32"))?;
174 let quantized = format::quantize_tensor(name, &data, &shape, scheme, granularity)
175 .with_context(|| format!("quantizing tensor '{name}'"))?;
176
177 if matches!(scheme, Scheme::Int4 { .. }) {
178 packed_int4.insert(
179 name.to_string(),
180 PackedTensorInfo {
181 logical_shape: shape.clone(),
182 elements: shape.iter().product(),
183 },
184 );
185 }
186
187 outputs.extend(quantized);
188 quantized_count += 1;
189 }
190
191 let ignored_count = ignored.len();
192 let config = format::build_config(scheme, granularity, ignored, packed_int4);
193 let metadata = format::config_to_metadata(&config)?;
194
195 write_safetensors(&args.output, &outputs, metadata)
196 .with_context(|| format!("failed to write '{}'", args.output.display()))?;
197
198 println!(
199 "Quantized {quantized_count} tensor(s) ({scheme}, {granularity}), left {ignored_count} tensor(s) unquantized -> {output}",
200 scheme = scheme.short_name(),
201 granularity = granularity.strategy_name(),
202 output = args.output.display(),
203 );
204
205 Ok(())
206}