Skip to content

Commit 1073f5f

Browse files
committed
wip: llama fp8 example
1 parent 0032b56 commit 1073f5f

10 files changed

Lines changed: 1011 additions & 141 deletions

File tree

Cargo.toml

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
[workspace]
22
exclude = [
3-
<<<<<<< HEAD
4-
=======
53
"candle-book",
6-
>>>>>>> upstream/main
74
"candle-flash-attn",
85
"candle-kernels",
96
"candle-metal-kernels",
107
"candle-onnx",
11-
]members = [
8+
]
9+
members = [
1210
"candle-core",
1311
"candle-datasets",
1412
"candle-examples",
@@ -31,11 +29,6 @@ categories = ["science"]
3129
license = "MIT OR Apache-2.0"
3230

3331
[workspace.dependencies]
34-
35-
<<<<<<< HEAD
36-
=======
37-
38-
>>>>>>> upstream/main
3932
ab_glyph = "0.2.23"
4033
accelerate-src = { version = "0.3.2" }
4134
anyhow = { version = "1", features = ["backtrace"] }
@@ -50,14 +43,15 @@ candle-onnx = { path = "./candle-onnx", version = "0.9.1" }
5043
candle-transformers = { path = "./candle-transformers", version = "0.9.1" }
5144
clap = { version = "4.2.4", features = ["derive"] }
5245
criterion = { version = "0.5.1", default-features = false }
53-
cudarc = { version = "0.16.3", features = [
46+
cudarc = { path = "../cudarc", features = [
5447
"std",
5548
"cublas",
5649
"cublaslt",
5750
"curand",
5851
"driver",
5952
"nvrtc",
6053
"f16",
54+
"f8",
6155
"cuda-version-from-build-system",
6256
"dynamic-linking",
6357
], default-features = false }
@@ -69,10 +63,7 @@ half = { version = "2.5.0", features = [
6963
"use-intrinsics",
7064
"rand_distr",
7165
] }
72-
float8 = { git = "https://github.com/zackangelo/float8", branch = "cudarc_0_16", features = [
73-
"num-traits",
74-
"rand_distr",
75-
] }
66+
float8 = { version = "0.3.0", features = ["num-traits", "rand_distr"] }
7667
hound = "3.5.1"
7768
image = { version = "0.25.2", default-features = false, features = [
7869
"jpeg",
@@ -89,7 +80,7 @@ parquet = { version = "51.0.0" }
8980
rand = "0.9.0"
9081
rand_distr = "0.5.1"
9182
rayon = "1.7.0"
92-
safetensors = "0.4.1"
83+
safetensors = "0.6"
9384
serde = { version = "1.0.171", features = ["derive"] }
9485
serde_plain = "1.0.2"
9586
serde_json = "1.0.99"

candle-core/src/safetensors.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,10 @@ impl st::View for &Tensor {
9393

9494
impl Tensor {
9595
pub fn save_safetensors<P: AsRef<Path>>(&self, name: &str, filename: P) -> Result<()> {
96-
let data = [(name, self.clone())];
97-
Ok(st::serialize_to_file(data, &None, filename.as_ref())?)
96+
// let data = [(name, self.clone())];
97+
// Ok(st::serialize_to_file(data, &None, filename.as_ref())?)
98+
99+
todo!()
98100
}
99101
}
100102

@@ -232,6 +234,7 @@ fn convert(view: &st::TensorView<'_>, device: &Device) -> Result<Tensor> {
232234
st::Dtype::F16 => convert_::<half::f16>(view, device),
233235
st::Dtype::F32 => convert_::<f32>(view, device),
234236
st::Dtype::F64 => convert_::<f64>(view, device),
237+
st::Dtype::F8_E4M3 => convert_::<F8E4M3>(view, device),
235238
dtype => Err(Error::UnsupportedSafeTensorDtype(dtype)),
236239
}
237240
}
@@ -268,7 +271,8 @@ pub fn save<K: AsRef<str> + Ord + std::fmt::Display, P: AsRef<Path>>(
268271
tensors: &HashMap<K, Tensor>,
269272
filename: P,
270273
) -> Result<()> {
271-
Ok(st::serialize_to_file(tensors, &None, filename.as_ref())?)
274+
// Ok(st::serialize_to_file(tensors, None, filename.as_ref())?)
275+
todo!()
272276
}
273277

274278
#[derive(yoke::Yokeable)]

candle-examples/Cargo.toml

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,10 @@ candle-flash-attn = { workspace = true, optional = true }
1919
candle-onnx = { workspace = true, optional = true }
2020

2121
pyo3 = { version = "0.22.0", features = [
22-
<<<<<<< HEAD
2322
"auto-initialize",
2423
"abi3-py311",
25-
=======
26-
"auto-initialize",
27-
"abi3-py311",
28-
>>>>>>> upstream/main
29-
], optional = true }csv = "1.3.0"
24+
], optional = true }
25+
csv = "1.3.0"
3026
cudarc = { workspace = true, optional = true }
3127
half = { workspace = true, optional = true }
3228
hf-hub = { workspace = true, features = ["tokio"] }
@@ -85,14 +81,6 @@ cuda = [
8581
"candle-transformers/cuda",
8682
"dep:bindgen_cuda",
8783
]
88-
cudnn = ["candle/cudnn"]
89-
flash-attn = ["cuda", "candle-transformers/flash-attn", "dep:candle-flash-attn"]
90-
cuda = [
91-
"candle/cuda",
92-
"candle-nn/cuda",
93-
"candle-transformers/cuda",
94-
"dep:bindgen_cuda",
95-
]
9684
cudnn = ["candle/cudnn", "candle-nn/cudnn", "candle-transformers/cudnn"]
9785
flash-attn = ["cuda", "candle-transformers/flash-attn", "dep:candle-flash-attn"]
9886
nccl = ["cuda", "cudarc/nccl", "dep:half"]
@@ -165,6 +153,10 @@ required-features = ["onnx"]
165153
name = "colpali"
166154
required-features = ["pdf2image"]
167155

156+
[[example]]
157+
name = "llama_fp8"
158+
required-features = ["cuda"]
159+
168160
[[example]]
169161
name = "voxtral"
170162
required-features = ["symphonia"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# candle-llama
2+
3+
Candle implementations of various Llama based architectures.
4+
5+
## Running an example
6+
7+
```bash
8+
$ cargo run --example llama -- --prompt "Machine learning is " --which v32-3b-instruct
9+
10+
> Machine learning is the part of computer science which deals with the development of algorithms and
11+
```
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
// An fp8 implementation of llama using accelerated
2+
// fp8 matmuls in cublaslt
3+
4+
use anyhow::{bail, Error as E, Result};
5+
use clap::{Parser, ValueEnum};
6+
7+
use candle::{DType, Tensor};
8+
use candle_nn::VarBuilder;
9+
use candle_transformers::generation::{LogitsProcessor, Sampling};
10+
use hf_hub::{api::sync::Api, Repo, RepoType};
11+
use std::io::Write;
12+
13+
use candle_transformers::models::llama_fp8 as model;
14+
use model::{Llama, LlamaConfig};
15+
16+
const EOS_TOKEN: &str = "</s>";
17+
const DEFAULT_PROMPT: &str = "My favorite theorem is ";
18+
19+
#[derive(Clone, Debug, Copy, PartialEq, Eq, ValueEnum)]
20+
enum Which {
21+
#[value(name = "RedHat-3.1-8B-Instruct-FP8")]
22+
#[allow(non_camel_case_types)]
23+
RedHat31_8b_Instruct_FP8,
24+
}
25+
26+
#[derive(Parser, Debug)]
27+
#[command(author, version, about, long_about = None)]
28+
struct Args {
29+
/// Run on CPU rather than on GPU.
30+
#[arg(long)]
31+
cpu: bool,
32+
33+
/// The temperature used to generate samples.
34+
#[arg(long, default_value_t = 0.8)]
35+
temperature: f64,
36+
37+
/// Nucleus sampling probability cutoff.
38+
#[arg(long)]
39+
top_p: Option<f64>,
40+
41+
/// Only sample among the top K samples.
42+
#[arg(long)]
43+
top_k: Option<usize>,
44+
45+
/// The seed to use when generating random samples.
46+
#[arg(long, default_value_t = 299792458)]
47+
seed: u64,
48+
49+
/// The length of the sample to generate (in tokens).
50+
#[arg(short = 'n', long, default_value_t = 10000)]
51+
sample_len: usize,
52+
53+
/// Disable the key-value cache.
54+
#[arg(long)]
55+
no_kv_cache: bool,
56+
57+
/// The initial prompt.
58+
#[arg(long)]
59+
prompt: Option<String>,
60+
61+
/// Use different dtype than f16
62+
#[arg(long)]
63+
dtype: Option<String>,
64+
65+
/// Enable tracing (generates a trace-timestamp.json file).
66+
#[arg(long)]
67+
tracing: bool,
68+
69+
#[arg(long)]
70+
model_id: Option<String>,
71+
72+
#[arg(long)]
73+
revision: Option<String>,
74+
75+
/// The model size to use.
76+
#[arg(long, default_value = "v3")]
77+
which: Which,
78+
79+
#[arg(long)]
80+
use_flash_attn: bool,
81+
82+
/// Penalty to be applied for repeating tokens, 1. means no penalty.
83+
#[arg(long, default_value_t = 1.1)]
84+
repeat_penalty: f32,
85+
86+
/// The context size to consider for the repeat penalty.
87+
#[arg(long, default_value_t = 128)]
88+
repeat_last_n: usize,
89+
}
90+
91+
fn main() -> anyhow::Result<()> {
92+
use tokenizers::Tokenizer;
93+
use tracing_chrome::ChromeLayerBuilder;
94+
use tracing_subscriber::prelude::*;
95+
96+
let args = Args::parse();
97+
let _guard = if args.tracing {
98+
let (chrome_layer, guard) = ChromeLayerBuilder::new().build();
99+
tracing_subscriber::registry().with(chrome_layer).init();
100+
Some(guard)
101+
} else {
102+
None
103+
};
104+
105+
let device = candle_examples::device(args.cpu)?;
106+
let dtype = match args.dtype.as_deref() {
107+
Some("f16") => DType::F16,
108+
Some("bf16") => DType::BF16,
109+
Some("f32") => DType::F32,
110+
Some(dtype) => bail!("Unsupported dtype {dtype}"),
111+
None => DType::F16,
112+
};
113+
114+
let (llama, tokenizer_filename, mut cache, config) = {
115+
let api = Api::new()?;
116+
let model_id = args.model_id.unwrap_or_else(|| {
117+
let str = match args.which {
118+
Which::RedHat31_8b_Instruct_FP8 => "RedHatAI/Meta-Llama-3.1-8B-Instruct-FP8",
119+
};
120+
121+
str.to_string()
122+
});
123+
124+
println!("loading the model weights from {model_id}");
125+
let revision = args.revision.unwrap_or("main".to_string());
126+
let api = api.repo(Repo::with_revision(model_id, RepoType::Model, revision));
127+
128+
let tokenizer_filename = api.get("tokenizer.json")?;
129+
let config_filename = api.get("config.json")?;
130+
let config: LlamaConfig = serde_json::from_slice(&std::fs::read(config_filename)?)?;
131+
let config = config.into_config(args.use_flash_attn);
132+
133+
let filenames = match args.which {
134+
_ => candle_examples::hub_load_safetensors(&api, "model.safetensors.index.json")?,
135+
};
136+
137+
let cache = model::Cache::new(!args.no_kv_cache, dtype, &config, &device)?;
138+
139+
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&filenames, dtype, &device)? };
140+
(Llama::load(vb, &config)?, tokenizer_filename, cache, config)
141+
};
142+
let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?;
143+
let eos_token_id = config.eos_token_id.or_else(|| {
144+
tokenizer
145+
.token_to_id(EOS_TOKEN)
146+
.map(model::LlamaEosToks::Single)
147+
});
148+
let prompt = args.prompt.as_ref().map_or(DEFAULT_PROMPT, |p| p.as_str());
149+
let mut tokens = tokenizer
150+
.encode(prompt, true)
151+
.map_err(E::msg)?
152+
.get_ids()
153+
.to_vec();
154+
let mut tokenizer = candle_examples::token_output_stream::TokenOutputStream::new(tokenizer);
155+
156+
println!("starting the inference loop");
157+
print!("{prompt}");
158+
let mut logits_processor = {
159+
let temperature = args.temperature;
160+
let sampling = if temperature <= 0. {
161+
Sampling::ArgMax
162+
} else {
163+
match (args.top_k, args.top_p) {
164+
(None, None) => Sampling::All { temperature },
165+
(Some(k), None) => Sampling::TopK { k, temperature },
166+
(None, Some(p)) => Sampling::TopP { p, temperature },
167+
(Some(k), Some(p)) => Sampling::TopKThenTopP { k, p, temperature },
168+
}
169+
};
170+
LogitsProcessor::from_sampling(args.seed, sampling)
171+
};
172+
173+
let mut start_gen = std::time::Instant::now();
174+
let mut index_pos = 0;
175+
let mut token_generated = 0;
176+
for index in 0..args.sample_len {
177+
let (context_size, context_index) = if cache.use_kv_cache && index > 0 {
178+
(1, index_pos)
179+
} else {
180+
(tokens.len(), 0)
181+
};
182+
if index == 1 {
183+
start_gen = std::time::Instant::now()
184+
}
185+
let ctxt = &tokens[tokens.len().saturating_sub(context_size)..];
186+
let input = Tensor::new(ctxt, &device)?.unsqueeze(0)?;
187+
let logits = llama.forward(&input, context_index, &mut cache)?;
188+
let logits = logits.squeeze(0)?;
189+
let logits = if args.repeat_penalty == 1. {
190+
logits
191+
} else {
192+
let start_at = tokens.len().saturating_sub(args.repeat_last_n);
193+
candle_transformers::utils::apply_repeat_penalty(
194+
&logits,
195+
args.repeat_penalty,
196+
&tokens[start_at..],
197+
)?
198+
};
199+
index_pos += ctxt.len();
200+
201+
let next_token = logits_processor.sample(&logits)?;
202+
token_generated += 1;
203+
tokens.push(next_token);
204+
205+
match eos_token_id {
206+
Some(model::LlamaEosToks::Single(eos_tok_id)) if next_token == eos_tok_id => {
207+
break;
208+
}
209+
Some(model::LlamaEosToks::Multiple(ref eos_ids)) if eos_ids.contains(&next_token) => {
210+
break;
211+
}
212+
_ => (),
213+
}
214+
if let Some(t) = tokenizer.next_token(next_token)? {
215+
print!("{t}");
216+
std::io::stdout().flush()?;
217+
}
218+
}
219+
if let Some(rest) = tokenizer.decode_rest().map_err(E::msg)? {
220+
print!("{rest}");
221+
}
222+
let dt = start_gen.elapsed();
223+
println!(
224+
"\n\n{} tokens generated ({} token/s)\n",
225+
token_generated,
226+
(token_generated - 1) as f64 / dt.as_secs_f64(),
227+
);
228+
Ok(())
229+
}

0 commit comments

Comments
 (0)