From 9c61b0fc9b9062b347c176b5f0f86b97b6804a1b Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 3 Sep 2023 21:33:50 +0200 Subject: [PATCH 001/150] Proper log buckets for t5. (#727) * Proper log buckets for t5. * Properly pass the position bias. --- candle-examples/examples/musicgen/t5_model.rs | 111 ++++++++++++------ 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/candle-examples/examples/musicgen/t5_model.rs b/candle-examples/examples/musicgen/t5_model.rs index 607b5c93..22f0a4f5 100644 --- a/candle-examples/examples/musicgen/t5_model.rs +++ b/candle-examples/examples/musicgen/t5_model.rs @@ -202,7 +202,11 @@ impl T5Attention { }) } - fn forward(&self, xs: &Tensor) -> Result { + fn forward( + &self, + xs: &Tensor, + position_bias: Option<&Tensor>, + ) -> Result<(Tensor, Option)> { // TODO: Apply the mask(s)? // TODO: kv caching. let (b_sz, seq_len) = (xs.dim(0)?, xs.dim(1)?); @@ -223,34 +227,57 @@ impl T5Attention { .contiguous()?; let scores = q.matmul(&k.t()?)?; - let scores = match &self.relative_attention_bias { - None => scores, - Some(relative_attention_bias) => { - let query_length = seq_len; - let key_length = seq_len; - // This only handles the bidirectional case. - let num_buckets = self.relative_attention_num_buckets / 2; - let relative_position = (0..query_length as u32) - .map(|i| { - (0..key_length as u32) - .map(|j| { - if i < j { - j - i + num_buckets as u32 - } else { - i - j - } - }) - .collect::>() - }) - .collect::>>(); - let relative_buckets = Tensor::new(relative_position, q.device())?; - let position_bias = relative_attention_bias - .forward(&relative_buckets)? - .permute((2, 0, 1))? - .unsqueeze(0)?; - (scores + position_bias)? - // TODO: position_bias_masked? - } + let (scores, position_bias) = match position_bias { + Some(position_bias) => ((scores + position_bias)?, Some(position_bias.clone())), + None => match &self.relative_attention_bias { + None => (scores, None), + Some(relative_attention_bias) => { + let query_length = seq_len; + let key_length = seq_len; + // This only handles the bidirectional case. + let num_buckets = self.relative_attention_num_buckets as u32 / 2; + let max_exact = num_buckets / 2; + let relative_position = (0..query_length as u32) + .map(|i| { + (0..key_length as u32) + .map(|j| { + if i < j { + if j - i < max_exact { + j - i + num_buckets + } else { + let b = f32::log( + (j - i) as f32 / max_exact as f32, + self.relative_attention_max_distance as f32 + / max_exact as f32, + ) * (num_buckets - max_exact) as f32; + u32::min( + max_exact + num_buckets + b as u32, + self.relative_attention_num_buckets as u32 - 1, + ) + } + } else if i - j < max_exact { + i - j + } else { + let b = f32::log( + (i - j) as f32 / max_exact as f32, + self.relative_attention_max_distance as f32 + / max_exact as f32, + ) * (num_buckets - max_exact) as f32; + max_exact + b as u32 + } + }) + .collect::>() + }) + .collect::>>(); + let relative_buckets = Tensor::new(relative_position, q.device())?; + let position_bias = relative_attention_bias + .forward(&relative_buckets)? + .permute((2, 0, 1))? + .unsqueeze(0)?; + ((scores + &position_bias)?, Some(position_bias)) + // TODO: position_bias_masked? + } + }, }; let attn_weights = candle_nn::ops::softmax(&scores, D::Minus1)?; @@ -259,7 +286,7 @@ impl T5Attention { .transpose(1, 2)? .reshape((b_sz, seq_len, self.inner_dim))?; let attn_output = self.o.forward(&attn_output)?; - Ok(attn_output) + Ok((attn_output, position_bias)) } } @@ -280,11 +307,15 @@ impl T5LayerSelfAttention { }) } - fn forward(&self, xs: &Tensor) -> Result { + fn forward( + &self, + xs: &Tensor, + position_bias: Option<&Tensor>, + ) -> Result<(Tensor, Option)> { let normed_xs = self.layer_norm.forward(xs)?; - let ys = self.self_attention.forward(&normed_xs)?; + let (ys, position_bias) = self.self_attention.forward(&normed_xs, position_bias)?; let ys = (xs + ys)?; - Ok(ys) + Ok((ys, position_bias)) } } @@ -326,8 +357,12 @@ impl T5Block { }) } - fn forward(&self, xs: &Tensor) -> Result { - let mut xs = self.self_attn.forward(xs)?; + fn forward( + &self, + xs: &Tensor, + position_bias: Option<&Tensor>, + ) -> Result<(Tensor, Option)> { + let (mut xs, position_bias) = self.self_attn.forward(xs, position_bias)?; // TODO: clamp for f16? if let Some(cross_attn) = &self.cross_attn { xs = cross_attn.forward(&xs)?; @@ -335,7 +370,7 @@ impl T5Block { } let xs = self.ff.forward(&xs)?; // TODO: clamp for f16? - Ok(xs) + Ok((xs, position_bias)) } } @@ -368,8 +403,10 @@ impl T5Stack { let (_b_sz, _seq_len) = (input_embeds.dim(0)?, input_embeds.dim(1)?); let mut hidden_states = input_embeds; + let mut position_bias = None; for block in self.block.iter() { - hidden_states = block.forward(&hidden_states)? + (hidden_states, position_bias) = + block.forward(&hidden_states, position_bias.as_ref())? } let hidden_states = self.final_layer_norm.forward(&hidden_states)?; Ok(hidden_states) From 20512ba408f9840828e902b7dd824be5a0969feb Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 4 Sep 2023 08:07:00 +0200 Subject: [PATCH 002/150] Return the metadata in the gguf pyo3 bindings. (#729) * Return the metadata in the gguf pyo3 bindings. * Read the metadata in the quantized llama example. * Get inference to work on gguf files. --- candle-pyo3/quant-llama.py | 39 ++++++++++++++++++++++++++++++++---- candle-pyo3/src/lib.rs | 41 ++++++++++++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/candle-pyo3/quant-llama.py b/candle-pyo3/quant-llama.py index 7d74c25e..0f7a51c6 100644 --- a/candle-pyo3/quant-llama.py +++ b/candle-pyo3/quant-llama.py @@ -111,7 +111,8 @@ class QuantizedLlama: self.norm = RmsNorm(all_tensors["norm.weight"]) self.output = all_tensors["output.weight"] self.layers = [] - cos_sin = precompute_freqs_cis(hparams, 10000.) + rope_freq = hparams.get("rope_freq", 10000.) + cos_sin = precompute_freqs_cis(hparams, rope_freq) for layer_idx in range(hparams["n_layer"]): layer = QuantizedLayer(layer_idx, hparams, all_tensors, cos_sin) self.layers.append(layer) @@ -133,15 +134,45 @@ class QuantizedLlama: x = self.output.matmul_t(x) return x +def gguf_rename(tensor_name): + if tensor_name == 'token_embd.weight': return 'tok_embeddings.weight' + if tensor_name == 'output_norm.weight': return 'norm.weight' + tensor_name = tensor_name.replace('blk.', 'layers.') + tensor_name = tensor_name.replace('.attn_q.', '.attention.wq.') + tensor_name = tensor_name.replace('.attn_k.', '.attention.wk.') + tensor_name = tensor_name.replace('.attn_v.', '.attention.wv.') + tensor_name = tensor_name.replace('.attn_output.', '.attention.wo.') + tensor_name = tensor_name.replace('.ffn_gate.', '.feed_forward.w1.') + tensor_name = tensor_name.replace('.ffn_down.', '.feed_forward.w2.') + tensor_name = tensor_name.replace('.ffn_up.', '.feed_forward.w3.') + tensor_name = tensor_name.replace('.attn_norm.', '.attention_norm.') + return tensor_name + def main(): if len(sys.argv) < 2: raise ValueError("missing weight file argument") filename = sys.argv[1] print(f"reading model file {filename}") if filename.endswith("gguf"): - all_tensors = candle.load_gguf(sys.argv[1]) - hparams = None - vocab = None + all_tensors, metadata = candle.load_gguf(sys.argv[1]) + vocab = metadata["tokenizer.ggml.tokens"] + for i, v in enumerate(vocab): + vocab[i] = '\n' if v == '<0x0A>' else v.replace('▁', ' ') + hparams = {k: v for (k, v) in metadata.items() if not k.startswith("tokenizer")} + print(hparams) + hparams = { + 'n_vocab': len(vocab), + 'n_embd': metadata['llama.embedding_length'], + 'n_mult': 256, + 'n_head': metadata['llama.attention.head_count'], + 'n_head_kv': metadata['llama.attention.head_count_kv'], + 'n_layer': metadata['llama.block_count'], + 'n_rot': metadata['llama.rope.dimension_count'], + 'rope_freq': metadata['llama.rope.freq_base'], + 'ftype': metadata['general.file_type'], + } + all_tensors = { gguf_rename(k): v for k, v in all_tensors.items() } + else: all_tensors, hparams, vocab = candle.load_ggml(sys.argv[1]) print(hparams) diff --git a/candle-pyo3/src/lib.rs b/candle-pyo3/src/lib.rs index 79f86479..f71970d5 100644 --- a/candle-pyo3/src/lib.rs +++ b/candle-pyo3/src/lib.rs @@ -746,10 +746,35 @@ fn load_ggml(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject, PyObje } #[pyfunction] -fn load_gguf(path: &str, py: Python<'_>) -> PyResult { +fn load_gguf(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject)> { + use ::candle::quantized::gguf_file; + fn gguf_value_to_pyobject(v: &gguf_file::Value, py: Python<'_>) -> PyResult { + let v: PyObject = match v { + gguf_file::Value::U8(x) => x.into_py(py), + gguf_file::Value::I8(x) => x.into_py(py), + gguf_file::Value::U16(x) => x.into_py(py), + gguf_file::Value::I16(x) => x.into_py(py), + gguf_file::Value::U32(x) => x.into_py(py), + gguf_file::Value::I32(x) => x.into_py(py), + gguf_file::Value::U64(x) => x.into_py(py), + gguf_file::Value::I64(x) => x.into_py(py), + gguf_file::Value::F32(x) => x.into_py(py), + gguf_file::Value::F64(x) => x.into_py(py), + gguf_file::Value::Bool(x) => x.into_py(py), + gguf_file::Value::String(x) => x.into_py(py), + gguf_file::Value::Array(x) => { + let list = pyo3::types::PyList::empty(py); + for elem in x.iter() { + list.append(gguf_value_to_pyobject(elem, py)?)?; + } + list.into() + } + }; + Ok(v) + } let mut file = std::fs::File::open(path)?; - let gguf = ::candle::quantized::gguf_file::Content::read(&mut file).map_err(wrap_err)?; - let res = gguf + let gguf = gguf_file::Content::read(&mut file).map_err(wrap_err)?; + let tensors = gguf .tensor_infos .keys() .map(|key| { @@ -758,7 +783,15 @@ fn load_gguf(path: &str, py: Python<'_>) -> PyResult { }) .collect::<::candle::Result>>() .map_err(wrap_err)?; - Ok(res.into_py_dict(py).to_object(py)) + let tensors = tensors.into_py_dict(py).to_object(py); + let metadata = gguf + .metadata + .iter() + .map(|(key, value)| Ok((key, gguf_value_to_pyobject(value, py)?))) + .collect::>>()? + .into_py_dict(py) + .to_object(py); + Ok((tensors, metadata)) } #[pyfunction] From d0cdea95a5ec8f53b24c6de19f6029060339ed98 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 4 Sep 2023 08:50:52 +0200 Subject: [PATCH 003/150] Add back the bf16 flash-attn kernels. (#730) --- candle-flash-attn/build.rs | 18 +++++++++--------- candle-flash-attn/kernels/flash_api.cu | 26 +++++++++++++------------- candle-flash-attn/src/ffi.rs | 1 + candle-flash-attn/src/lib.rs | 2 ++ 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/candle-flash-attn/build.rs b/candle-flash-attn/build.rs index 773c5638..2a3b7eb1 100644 --- a/candle-flash-attn/build.rs +++ b/candle-flash-attn/build.rs @@ -6,7 +6,7 @@ use rayon::prelude::*; use std::path::PathBuf; use std::str::FromStr; -const KERNEL_FILES: [&str; 9] = [ +const KERNEL_FILES: [&str; 17] = [ "flash_api.cu", "flash_fwd_hdim128_fp16_sm80.cu", "flash_fwd_hdim160_fp16_sm80.cu", @@ -16,14 +16,14 @@ const KERNEL_FILES: [&str; 9] = [ "flash_fwd_hdim32_fp16_sm80.cu", "flash_fwd_hdim64_fp16_sm80.cu", "flash_fwd_hdim96_fp16_sm80.cu", - // "flash_fwd_hdim128_bf16_sm80.cu", - // "flash_fwd_hdim160_bf16_sm80.cu", - // "flash_fwd_hdim192_bf16_sm80.cu", - // "flash_fwd_hdim224_bf16_sm80.cu", - // "flash_fwd_hdim256_bf16_sm80.cu", - // "flash_fwd_hdim32_bf16_sm80.cu", - // "flash_fwd_hdim64_bf16_sm80.cu", - // "flash_fwd_hdim96_bf16_sm80.cu", + "flash_fwd_hdim128_bf16_sm80.cu", + "flash_fwd_hdim160_bf16_sm80.cu", + "flash_fwd_hdim192_bf16_sm80.cu", + "flash_fwd_hdim224_bf16_sm80.cu", + "flash_fwd_hdim256_bf16_sm80.cu", + "flash_fwd_hdim32_bf16_sm80.cu", + "flash_fwd_hdim64_bf16_sm80.cu", + "flash_fwd_hdim96_bf16_sm80.cu", ]; fn main() -> Result<()> { diff --git a/candle-flash-attn/kernels/flash_api.cu b/candle-flash-attn/kernels/flash_api.cu index d928bcb6..72991257 100644 --- a/candle-flash-attn/kernels/flash_api.cu +++ b/candle-flash-attn/kernels/flash_api.cu @@ -1,20 +1,19 @@ #include "flash_fwd_launch_template.h" -// TODO: Switch back to handling bf16. -void run_mha_fwd(Flash_fwd_params ¶ms, cudaStream_t stream) { - FWD_HEADDIM_SWITCH(params.d, [&] { - run_mha_fwd_(params, stream); - }); -} - // void run_mha_fwd(Flash_fwd_params ¶ms, cudaStream_t stream) { -// FP16_SWITCH(!params.is_bf16, [&] { -// FWD_HEADDIM_SWITCH(params.d, [&] { -// run_mha_fwd_(params, stream); -// }); +// FWD_HEADDIM_SWITCH(params.d, [&] { +// run_mha_fwd_(params, stream); // }); // } +void run_mha_fwd(Flash_fwd_params ¶ms, cudaStream_t stream) { + FP16_SWITCH(!params.is_bf16, [&] { + FWD_HEADDIM_SWITCH(params.d, [&] { + run_mha_fwd_(params, stream); + }); + }); +} + extern "C" void run_mha( void *q_ptr, void *k_ptr, @@ -52,7 +51,8 @@ extern "C" void run_mha( uint32_t seqlen_q_rounded, uint32_t seqlen_k_rounded, - int is_causal + int is_causal, + int is_bf16 ) { Flash_fwd_params params; // Reset the parameters @@ -102,7 +102,7 @@ extern "C" void run_mha( params.p_dropout_in_uint8_t = uint8_t(std::floor(params.p_dropout * 255.0)); params.rp_dropout = 1.f / params.p_dropout; params.scale_softmax_rp_dropout = params.rp_dropout * params.scale_softmax; - params.is_bf16 = 0; + params.is_bf16 = is_bf16; params.cu_seqlens_q = cu_seqlens_q_ptr; params.cu_seqlens_k = cu_seqlens_k_ptr; params.p_ptr = nullptr; // used for `return_softmax`. diff --git a/candle-flash-attn/src/ffi.rs b/candle-flash-attn/src/ffi.rs index ae61c405..90f34e43 100644 --- a/candle-flash-attn/src/ffi.rs +++ b/candle-flash-attn/src/ffi.rs @@ -38,6 +38,7 @@ extern "C" { seqlen_k_rounded: u32, is_causal: c_int, + is_bf16: c_int, ); } diff --git a/candle-flash-attn/src/lib.rs b/candle-flash-attn/src/lib.rs index 3c5fd455..cdb4b083 100644 --- a/candle-flash-attn/src/lib.rs +++ b/candle-flash-attn/src/lib.rs @@ -146,6 +146,7 @@ impl candle::CustomOp3 for FlashAttn { /* seqlen_q_rounded */ seqlen_q_rounded as u32, /* seqlen_k_rounded */ seqlen_k_rounded as u32, /* is_causal */ causal, + /* is_bf16 */ 0, ) } @@ -354,6 +355,7 @@ impl candle::CustomOp3 for FlashAttnVarLen { /* seqlen_q_rounded */ seqlen_q_rounded as u32, /* seqlen_k_rounded */ seqlen_k_rounded as u32, /* is_causal */ causal, + /* is_bf16 */ 0, ) } From e2f9f60ac2e4ab6b39b8275442f7ad4e76995707 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 4 Sep 2023 09:18:32 +0200 Subject: [PATCH 004/150] Avoid some redundant clone. (#731) --- candle-nn/tests/batch_norm.rs | 4 ++-- candle-wasm-examples/llama2-c/src/bin/m.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/candle-nn/tests/batch_norm.rs b/candle-nn/tests/batch_norm.rs index 209fc10a..5bbaf238 100644 --- a/candle-nn/tests/batch_norm.rs +++ b/candle-nn/tests/batch_norm.rs @@ -59,8 +59,8 @@ fn batch_norm() -> Result<()> { ); let bn2 = BatchNorm::new( 5, - running_mean.clone(), - running_var.clone(), + running_mean, + running_var, Tensor::new(&[0.5f32], &Device::Cpu)?.broadcast_as(5)?, Tensor::new(&[-1.5f32], &Device::Cpu)?.broadcast_as(5)?, 1e-8, diff --git a/candle-wasm-examples/llama2-c/src/bin/m.rs b/candle-wasm-examples/llama2-c/src/bin/m.rs index d014e38a..da71f071 100644 --- a/candle-wasm-examples/llama2-c/src/bin/m.rs +++ b/candle-wasm-examples/llama2-c/src/bin/m.rs @@ -80,7 +80,7 @@ impl Model { let tokens = self .inner .tokenizer - .encode(prompt.to_string(), true) + .encode(prompt, true) .map_err(|m| JsError::new(&m.to_string()))? .get_ids() .to_vec(); From 8395152d20c7c72fb866ca3f8cbcab8859bfed57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radam=C3=A9s=20Ajna?= Date: Mon, 4 Sep 2023 07:59:22 -0700 Subject: [PATCH 005/150] Llama2c WASM UI improvements (#732) * pass seed, expose model seq_len * wip new llama2.c ui * final new UI example * small coppy * copy --- candle-wasm-examples/llama2-c/README.md | 47 +++ candle-wasm-examples/llama2-c/build-lib.sh | 2 + .../llama2-c/lib-example.html | 311 ++++++++++++++++++ .../llama2-c/llama2cWorker.js | 96 ++++++ candle-wasm-examples/llama2-c/src/bin/m.rs | 8 +- candle-wasm-examples/llama2-c/src/worker.rs | 2 +- 6 files changed, 464 insertions(+), 2 deletions(-) create mode 100644 candle-wasm-examples/llama2-c/README.md create mode 100644 candle-wasm-examples/llama2-c/build-lib.sh create mode 100644 candle-wasm-examples/llama2-c/lib-example.html create mode 100644 candle-wasm-examples/llama2-c/llama2cWorker.js diff --git a/candle-wasm-examples/llama2-c/README.md b/candle-wasm-examples/llama2-c/README.md new file mode 100644 index 00000000..0b41e064 --- /dev/null +++ b/candle-wasm-examples/llama2-c/README.md @@ -0,0 +1,47 @@ +## Running [llama2.c](https://github.com/karpathy/llama2.c) Examples + +Here, we provide two examples of how to run [llama2.c](https://github.com/karpathy/llama2.c) written in Rust using a Candle-compiled WASM binary and runtimes. + +### Pure Rust UI + +To build and test the UI made in Rust you will need [Trunk](https://trunkrs.dev/#install) +From the `candle-wasm-examples/llama2-c` directory run: + +Download assets: + +```bash +# Model and tokenizer + +wget -c https://huggingface.co/spaces/lmz/candle-llama2/resolve/main/model.bin +wget -c https://huggingface.co/spaces/lmz/candle-llama2/resolve/main/tokenizer.json + +``` + +Run hot reload server: + +```bash +trunk serve --release --public-url / --port 8080 +``` + +### Vanilla JS and WebWorkers + +To build and test the UI made in Vanilla JS and WebWorkers, first we need to build the WASM library: + +```bash +sh build-lib.sh +``` + +This will bundle the library under `./build` and we can import it inside our WebWorker like a normal JS module: + +```js +import init, { Model } from "./build/m.js"; +``` + +The full example can be found under `./lib-example.html`. All needed assets are fetched from the web, so no need to download anything. +Finally, you can preview the example by running a local HTTP server. For example: + +```bash +python -m http.server +``` + +Then open `http://localhost:8000/lib-example.html` in your browser. diff --git a/candle-wasm-examples/llama2-c/build-lib.sh b/candle-wasm-examples/llama2-c/build-lib.sh new file mode 100644 index 00000000..b0ebb182 --- /dev/null +++ b/candle-wasm-examples/llama2-c/build-lib.sh @@ -0,0 +1,2 @@ +cargo build --target wasm32-unknown-unknown --release +wasm-bindgen ../../target/wasm32-unknown-unknown/release/m.wasm --out-dir build --target web diff --git a/candle-wasm-examples/llama2-c/lib-example.html b/candle-wasm-examples/llama2-c/lib-example.html new file mode 100644 index 00000000..bc519e4b --- /dev/null +++ b/candle-wasm-examples/llama2-c/lib-example.html @@ -0,0 +1,311 @@ + + + + Candle Llama.c Rust/WASM + + + + + + + + + + + + + + +
+ 🕯️ +
+

Candle Llama2.c

+

Rust/WASM Demo

+

+ Llama2.c + is Andrey Karpathy's C implementation of the Llama 2 LLM model in C. + This demo uses + Candle + + to run Llama2.c in the browser using rust/wasm. +

+
+ +
+ + +
+
+ + + + +
+
+ + + + 200 + + + + 0.50 + + + + + 1.10 + + +
+
+

Generation:

+ +
+ + No output yet +
+
+
+ + diff --git a/candle-wasm-examples/llama2-c/llama2cWorker.js b/candle-wasm-examples/llama2-c/llama2cWorker.js new file mode 100644 index 00000000..ba303aaa --- /dev/null +++ b/candle-wasm-examples/llama2-c/llama2cWorker.js @@ -0,0 +1,96 @@ +import init, { Model } from "./build/m.js"; + +async function fetchArrayBuffer(url) { + const res = await fetch(url, { + cache: "force-cache", + }); + const data = await res.arrayBuffer(); + return new Uint8Array(data); +} + +class Llama2C { + static instance = {}; + + static async getInstance(weightsURL, modelID, tokenizerURL) { + // load individual modelID only once + if (!this.instance[modelID]) { + await init(); + + self.postMessage({ status: "loading", message: "Loading Model" }); + + const [weightsArrayU8, tokenizerArrayU8] = await Promise.all([ + fetchArrayBuffer(weightsURL), + fetchArrayBuffer(tokenizerURL), + ]); + + this.instance[modelID] = new Model(weightsArrayU8, tokenizerArrayU8); + } + return this.instance[modelID]; + } +} + +let controller = null; +self.addEventListener("message", (event) => { + if (event.data.command === "start") { + controller = new AbortController(); + generate(event.data); + } else if (event.data.command === "abort") { + controller.abort(); + } +}); + +async function generate(data) { + const { + weightsURL, + modelID, + tokenizerURL, + prompt, + temp, + repeatPenalty, + seed, + maxSeqLen, + } = data; + try { + self.postMessage({ status: "loading", message: "Starting llama2.c" }); + const model = await Llama2C.getInstance(weightsURL, modelID, tokenizerURL); + + self.postMessage({ status: "loading", message: "Initializing model" }); + model.init_with_prompt(prompt, temp, repeatPenalty, seed); + + const seq_len = model.get_seq_len(); + + let sentence = ""; + let max_tokens = maxSeqLen ? maxSeqLen : seq_len - prompt.length - 1; + + while (max_tokens--) { + await new Promise(async (resolve) => { + if (controller && controller.signal.aborted) { + self.postMessage({ + status: "aborted", + message: "Aborted", + output: prompt + sentence, + }); + return; + } + const token = await model.next_token(); + + sentence += token; + self.postMessage({ + status: "generating", + message: "Generating token", + token: token, + sentence: sentence, + prompt: prompt, + }); + setTimeout(resolve, 0); + }); + } + self.postMessage({ + status: "complete", + message: "complete", + output: prompt + sentence, + }); + } catch (e) { + self.postMessage({ error: e }); + } +} diff --git a/candle-wasm-examples/llama2-c/src/bin/m.rs b/candle-wasm-examples/llama2-c/src/bin/m.rs index da71f071..62b1bdf7 100644 --- a/candle-wasm-examples/llama2-c/src/bin/m.rs +++ b/candle-wasm-examples/llama2-c/src/bin/m.rs @@ -58,6 +58,11 @@ impl Model { Err(e) => Err(JsError::new(&e.to_string())), } } + #[wasm_bindgen] + pub fn get_seq_len(&mut self) -> usize { + let seq_len = self.inner.config.seq_len; + seq_len + } #[wasm_bindgen] pub fn init_with_prompt( @@ -65,6 +70,7 @@ impl Model { prompt: String, temp: f64, repeat_penalty: f32, + seed: u64, ) -> Result { // First reset the cache. { @@ -74,7 +80,7 @@ impl Model { } } let temp = if temp <= 0. { None } else { Some(temp) }; - self.logits_processor = LogitsProcessor::new(299792458, temp); + self.logits_processor = LogitsProcessor::new(seed, temp); self.repeat_penalty = repeat_penalty; self.tokens.clear(); let tokens = self diff --git a/candle-wasm-examples/llama2-c/src/worker.rs b/candle-wasm-examples/llama2-c/src/worker.rs index 3d187fcc..7e97b5da 100644 --- a/candle-wasm-examples/llama2-c/src/worker.rs +++ b/candle-wasm-examples/llama2-c/src/worker.rs @@ -51,7 +51,7 @@ fn read_tensor>( pub struct Model { pub cache: Cache, - config: Config, + pub config: Config, pub llama: Llama, pub tokenizer: Tokenizer, } From 0d00c06a83b98c55d146564b96d913de4cec71c7 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 4 Sep 2023 17:09:19 +0200 Subject: [PATCH 006/150] Fix clippy lint. (#736) --- candle-wasm-examples/llama2-c/src/bin/m.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/candle-wasm-examples/llama2-c/src/bin/m.rs b/candle-wasm-examples/llama2-c/src/bin/m.rs index 62b1bdf7..6628ab7e 100644 --- a/candle-wasm-examples/llama2-c/src/bin/m.rs +++ b/candle-wasm-examples/llama2-c/src/bin/m.rs @@ -58,10 +58,10 @@ impl Model { Err(e) => Err(JsError::new(&e.to_string())), } } + #[wasm_bindgen] pub fn get_seq_len(&mut self) -> usize { - let seq_len = self.inner.config.seq_len; - seq_len + self.inner.config.seq_len } #[wasm_bindgen] From f80fd44201a61833781131b4fdc83d2e58e1b559 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 4 Sep 2023 17:35:43 +0200 Subject: [PATCH 007/150] BF16 support for flash-attn. (#737) --- candle-flash-attn/src/lib.rs | 122 +++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 41 deletions(-) diff --git a/candle-flash-attn/src/lib.rs b/candle-flash-attn/src/lib.rs index cdb4b083..b610915b 100644 --- a/candle-flash-attn/src/lib.rs +++ b/candle-flash-attn/src/lib.rs @@ -4,7 +4,7 @@ use candle::backend::BackendStorage; use candle::cuda_backend::cudarc::driver::DevicePtr; use candle::cuda_backend::WrapErr; use candle::{CpuStorage, Layout, Result, Shape, Tensor}; -use half::f16; +use half::{bf16, f16}; pub struct FlashAttn { pub softmax_scale: f32, @@ -15,24 +15,10 @@ fn round_multiple(x: usize, m: usize) -> usize { (x + m - 1) / m * m } -impl candle::CustomOp3 for FlashAttn { - fn name(&self) -> &'static str { - "flash-attn" - } - - fn cpu_fwd( - &self, - _: &CpuStorage, - _: &Layout, - _: &CpuStorage, - _: &Layout, - _: &CpuStorage, - _: &Layout, - ) -> Result<(CpuStorage, Shape)> { - candle::bail!("no cpu support for flash-attn") - } - - fn cuda_fwd( +impl FlashAttn { + fn cuda_fwd_t< + T: candle::cuda_backend::CudaDType + candle::cuda_backend::cudarc::driver::DeviceRepr, + >( &self, q: &candle::CudaStorage, q_l: &Layout, @@ -46,9 +32,9 @@ impl candle::CustomOp3 for FlashAttn { let out_shape = q_l.shape().clone(); let out_l = Layout::contiguous(&out_shape); - let q = q.as_cuda_slice::()?; - let k = k.as_cuda_slice::()?; - let v = v.as_cuda_slice::()?; + let q = q.as_cuda_slice::()?; + let k = k.as_cuda_slice::()?; + let v = v.as_cuda_slice::()?; let q = q.slice(q_l.start_offset()..); let k = k.slice(k_l.start_offset()..); let v = v.slice(v_l.start_offset()..); @@ -104,7 +90,7 @@ impl candle::CustomOp3 for FlashAttn { let seqlen_k_rounded = round_multiple(seqlen_k, 128); let elem_count = out_shape.elem_count(); - let dst = unsafe { dev.alloc::(elem_count) }.w()?; + let dst = unsafe { dev.alloc::(elem_count) }.w()?; let softmax_lse = dev.alloc_zeros::(b_sz * num_heads * seqlen_q).w()?; let causal = if self.causal { 1 } else { 0 }; @@ -155,6 +141,40 @@ impl candle::CustomOp3 for FlashAttn { } } +impl candle::CustomOp3 for FlashAttn { + fn name(&self) -> &'static str { + "flash-attn" + } + + fn cpu_fwd( + &self, + _: &CpuStorage, + _: &Layout, + _: &CpuStorage, + _: &Layout, + _: &CpuStorage, + _: &Layout, + ) -> Result<(CpuStorage, Shape)> { + candle::bail!("no cpu support for flash-attn") + } + + fn cuda_fwd( + &self, + q: &candle::CudaStorage, + q_l: &Layout, + k: &candle::CudaStorage, + k_l: &Layout, + v: &candle::CudaStorage, + v_l: &Layout, + ) -> Result<(candle::CudaStorage, Shape)> { + match q.dtype() { + candle::DType::F16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), + candle::DType::BF16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), + dt => candle::bail!("flash-attn is only supported for f16/bf16 ({dt:?})"), + } + } +} + /// Flash-attention v2 layer. /// /// This implements scaled dot-product attention, `softmax(Q @ K^T . softmax_scale) @ V`. @@ -191,24 +211,10 @@ struct FlashAttnVarLen { seqlens_k: Tensor, } -impl candle::CustomOp3 for FlashAttnVarLen { - fn name(&self) -> &'static str { - "flash-attn-varlen" - } - - fn cpu_fwd( - &self, - _: &CpuStorage, - _: &Layout, - _: &CpuStorage, - _: &Layout, - _: &CpuStorage, - _: &Layout, - ) -> Result<(CpuStorage, Shape)> { - candle::bail!("no cpu support for flash-attn") - } - - fn cuda_fwd( +impl FlashAttnVarLen { + fn cuda_fwd_t< + T: candle::cuda_backend::CudaDType + candle::cuda_backend::cudarc::driver::DeviceRepr, + >( &self, q: &candle::CudaStorage, q_l: &Layout, @@ -364,6 +370,40 @@ impl candle::CustomOp3 for FlashAttnVarLen { } } +impl candle::CustomOp3 for FlashAttnVarLen { + fn name(&self) -> &'static str { + "flash-attn-varlen" + } + + fn cpu_fwd( + &self, + _: &CpuStorage, + _: &Layout, + _: &CpuStorage, + _: &Layout, + _: &CpuStorage, + _: &Layout, + ) -> Result<(CpuStorage, Shape)> { + candle::bail!("no cpu support for flash-attn") + } + + fn cuda_fwd( + &self, + q: &candle::CudaStorage, + q_l: &Layout, + k: &candle::CudaStorage, + k_l: &Layout, + v: &candle::CudaStorage, + v_l: &Layout, + ) -> Result<(candle::CudaStorage, Shape)> { + match q.dtype() { + candle::DType::F16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), + candle::DType::BF16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), + dt => candle::bail!("flash-attn is only supported for f16/bf16 ({dt:?})"), + } + } +} + #[allow(clippy::too_many_arguments)] /// Flash-attention v2 layer with variable-length batching. /// From ab0d9fbdd1db6a586c0cd6ca9ee1a31203db3684 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 4 Sep 2023 17:45:26 +0200 Subject: [PATCH 008/150] Properly set the is_bf16 flag. (#738) --- candle-flash-attn/src/lib.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/candle-flash-attn/src/lib.rs b/candle-flash-attn/src/lib.rs index b610915b..61980a58 100644 --- a/candle-flash-attn/src/lib.rs +++ b/candle-flash-attn/src/lib.rs @@ -26,6 +26,7 @@ impl FlashAttn { k_l: &Layout, v: &candle::CudaStorage, v_l: &Layout, + is_bf16: bool, ) -> Result<(candle::CudaStorage, Shape)> { // https://github.com/Dao-AILab/flash-attention/blob/b252072409e69c25f2b9d473cc534e49b24decd2/csrc/flash_attn/flash_api.cpp#L187 let dev = q.device(); @@ -94,6 +95,7 @@ impl FlashAttn { let softmax_lse = dev.alloc_zeros::(b_sz * num_heads * seqlen_q).w()?; let causal = if self.causal { 1 } else { 0 }; + let is_bf16 = if is_bf16 { 1 } else { 0 }; unsafe { let q_ptr = *q.device_ptr() as *const core::ffi::c_void; @@ -132,7 +134,7 @@ impl FlashAttn { /* seqlen_q_rounded */ seqlen_q_rounded as u32, /* seqlen_k_rounded */ seqlen_k_rounded as u32, /* is_causal */ causal, - /* is_bf16 */ 0, + /* is_bf16 */ is_bf16, ) } @@ -168,8 +170,8 @@ impl candle::CustomOp3 for FlashAttn { v_l: &Layout, ) -> Result<(candle::CudaStorage, Shape)> { match q.dtype() { - candle::DType::F16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), - candle::DType::BF16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), + candle::DType::F16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l, false), + candle::DType::BF16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l, true), dt => candle::bail!("flash-attn is only supported for f16/bf16 ({dt:?})"), } } @@ -222,6 +224,7 @@ impl FlashAttnVarLen { k_l: &Layout, v: &candle::CudaStorage, v_l: &Layout, + is_bf16: bool, ) -> Result<(candle::CudaStorage, Shape)> { // https://github.com/Dao-AILab/flash-attention/blob/184b992dcb2a0890adaa19eb9b541c3e4f9d2a08/csrc/flash_attn/flash_api.cpp#L327 let dev = q.device(); @@ -321,6 +324,7 @@ impl FlashAttnVarLen { .w()?; let causal = if self.causal { 1 } else { 0 }; + let is_bf16 = if is_bf16 { 1 } else { 0 }; unsafe { let q_ptr = *q.device_ptr() as *const core::ffi::c_void; @@ -361,7 +365,7 @@ impl FlashAttnVarLen { /* seqlen_q_rounded */ seqlen_q_rounded as u32, /* seqlen_k_rounded */ seqlen_k_rounded as u32, /* is_causal */ causal, - /* is_bf16 */ 0, + /* is_bf16 */ is_bf16, ) } @@ -397,8 +401,8 @@ impl candle::CustomOp3 for FlashAttnVarLen { v_l: &Layout, ) -> Result<(candle::CudaStorage, Shape)> { match q.dtype() { - candle::DType::F16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), - candle::DType::BF16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l), + candle::DType::F16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l, false), + candle::DType::BF16 => self.cuda_fwd_t::(q, q_l, k, k_l, v, v_l, true), dt => candle::bail!("flash-attn is only supported for f16/bf16 ({dt:?})"), } } From 000487c36fc6da3a8d645fffbd8c24e23bcdeed1 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 4 Sep 2023 21:32:14 +0200 Subject: [PATCH 009/150] Add a python function to save as safetensors. (#740) --- candle-pyo3/src/lib.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/candle-pyo3/src/lib.rs b/candle-pyo3/src/lib.rs index f71970d5..eddc0fda 100644 --- a/candle-pyo3/src/lib.rs +++ b/candle-pyo3/src/lib.rs @@ -1,5 +1,4 @@ #![allow(clippy::redundant_closure_call)] -// TODO: Handle negative dimension indexes. use pyo3::exceptions::{PyTypeError, PyValueError}; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyTuple}; @@ -714,6 +713,18 @@ fn load_safetensors(path: &str, py: Python<'_>) -> PyResult { Ok(res.into_py_dict(py).to_object(py)) } +#[pyfunction] +fn save_safetensors( + path: &str, + tensors: std::collections::HashMap, +) -> PyResult<()> { + let tensors = tensors + .into_iter() + .map(|(s, t)| (s, t.0)) + .collect::>(); + ::candle::safetensors::save(&tensors, path).map_err(wrap_err) +} + #[pyfunction] fn load_ggml(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject, PyObject)> { let mut file = std::fs::File::open(path)?; @@ -867,6 +878,7 @@ fn candle(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(rand, m)?)?; m.add_function(wrap_pyfunction!(randn, m)?)?; m.add_function(wrap_pyfunction!(tensor, m)?)?; + m.add_function(wrap_pyfunction!(save_safetensors, m)?)?; m.add_function(wrap_pyfunction!(stack, m)?)?; m.add_function(wrap_pyfunction!(zeros, m)?)?; Ok(()) From 4698eb5cb6cb068fb053d5b6b2b11b1eb1d324d5 Mon Sep 17 00:00:00 2001 From: Masato Mori <13274198+spica314@users.noreply.github.com> Date: Tue, 5 Sep 2023 17:25:11 +0900 Subject: [PATCH 010/150] Fix typo in the nll function document (#742) --- candle-nn/src/loss.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/candle-nn/src/loss.rs b/candle-nn/src/loss.rs index cddf278e..72451f83 100644 --- a/candle-nn/src/loss.rs +++ b/candle-nn/src/loss.rs @@ -1,6 +1,6 @@ use candle::{Result, Tensor}; -/// The negative loss likelihodd loss. +/// The negative log likelihood loss. /// /// Arguments /// From cda45a7443a5f2cd14e4c48ef08733029a68f689 Mon Sep 17 00:00:00 2001 From: Gonzalo <456459+grzuy@users.noreply.github.com> Date: Tue, 5 Sep 2023 05:27:32 -0300 Subject: [PATCH 011/150] Let outside CustomOp2 implementations use binary_map/binary_map_vec (#741) --- candle-core/src/cpu_backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/candle-core/src/cpu_backend.rs b/candle-core/src/cpu_backend.rs index ed3dd3fc..c3ec8249 100644 --- a/candle-core/src/cpu_backend.rs +++ b/candle-core/src/cpu_backend.rs @@ -445,7 +445,7 @@ pub fn unary_map_vec U, FV: FnMut(&[T], &mut [U } // This function maps over two strided index sequences. -fn binary_map U>( +pub fn binary_map U>( lhs_l: &Layout, rhs_l: &Layout, lhs: &[T], @@ -525,7 +525,7 @@ fn binary_map U>( } // Similar to binary_map but with vectorized variants. -fn binary_map_vec T, FV: FnMut(&[T], &[T], &mut [T])>( +pub fn binary_map_vec T, FV: FnMut(&[T], &[T], &mut [T])>( lhs_l: &Layout, rhs_l: &Layout, lhs: &[T], From a8410bf35ea3ad8eb973f48d301e65309d232377 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 5 Sep 2023 10:51:12 +0200 Subject: [PATCH 012/150] Add some documentation. (#743) --- candle-core/src/dtype.rs | 11 +++++++++++ candle-core/src/shape.rs | 6 ++++++ candle-core/src/tensor.rs | 1 + 3 files changed, 18 insertions(+) diff --git a/candle-core/src/dtype.rs b/candle-core/src/dtype.rs index adfc4a3c..c7a1567f 100644 --- a/candle-core/src/dtype.rs +++ b/candle-core/src/dtype.rs @@ -1,15 +1,24 @@ +//! Types for elements that can be stored and manipulated using tensors. #![allow(clippy::redundant_closure_call)] use crate::backend::BackendStorage; use crate::{CpuStorage, Error, Result}; +/// The different types of elements allowed in tensors. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum DType { + // Unsigned 8 bits integer. U8, + // Unsigned 32 bits integer. U32, + // Signed 64 bits integer. I64, + // Brain floating-point using half precision (16 bits). BF16, + // Floating-point using half precision (16 bits). F16, + // Floating-point using single precision (32 bits). F32, + // Floating-point using double precision (64 bits). F64, } @@ -33,6 +42,7 @@ impl std::str::FromStr for DType { } impl DType { + /// String representation for dtypes. pub fn as_str(&self) -> &'static str { match self { Self::U8 => "u8", @@ -45,6 +55,7 @@ impl DType { } } + /// The size used by each element in bytes, i.e. 1 for `U8`, 4 for `F32`. pub fn size_in_bytes(&self) -> usize { match self { Self::U8 => 1, diff --git a/candle-core/src/shape.rs b/candle-core/src/shape.rs index aea8b887..db0fe98a 100644 --- a/candle-core/src/shape.rs +++ b/candle-core/src/shape.rs @@ -1,3 +1,4 @@ +//! The shape of a tensor is a tuple with the size of each of its dimensions. #![allow(clippy::redundant_closure_call)] use crate::{Error, Result}; @@ -119,6 +120,7 @@ impl Shape { Self(dims.to_vec()) } + /// The rank is the number of dimensions, 0 for a scalar value, 1 for a vector, etc. pub fn rank(&self) -> usize { self.0.len() } @@ -127,10 +129,12 @@ impl Shape { self.0 } + /// The dimensions as a slice of `usize`. pub fn dims(&self) -> &[usize] { &self.0 } + /// The total number of elements, this is the product of all dimension sizes. pub fn elem_count(&self) -> usize { self.0.iter().product() } @@ -182,6 +186,8 @@ impl Shape { true } + /// Modifies the shape by adding a list of additional dimensions at the end of the existing + /// dimensions. pub fn extend(mut self, additional_dims: &[usize]) -> Self { self.0.extend(additional_dims); self diff --git a/candle-core/src/tensor.rs b/candle-core/src/tensor.rs index e181f240..1eca694c 100644 --- a/candle-core/src/tensor.rs +++ b/candle-core/src/tensor.rs @@ -1,3 +1,4 @@ +//! Tensors are N-dimenional matrixes of elements using a single data type. #![allow(clippy::redundant_closure_call)] use crate::backend::{BackendDevice, BackendStorage}; use crate::op::{ From 1c9e5394a5056aadc948f9330ea31fea4972e65e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 5 Sep 2023 15:20:23 +0200 Subject: [PATCH 013/150] Add a custom softmax implementation. (#744) * Add a custom softmax implementation. * Add softmaxlastdim to the benchmarks. * And add a test. * Support more dtypes. * Polish the code. * Use the slow implementation on cuda. * Add a todo for the cuda kernel. --- .../examples/stable-diffusion/attention.rs | 2 +- candle-nn/Cargo.toml | 4 ++ .../examples/cpu_benchmarks.rs | 42 ++++++----- candle-nn/src/ops.rs | 69 ++++++++++++++++++- candle-nn/tests/ops.rs | 10 +++ 5 files changed, 109 insertions(+), 18 deletions(-) rename {candle-core => candle-nn}/examples/cpu_benchmarks.rs (80%) diff --git a/candle-examples/examples/stable-diffusion/attention.rs b/candle-examples/examples/stable-diffusion/attention.rs index 1ae1bfc3..000cd2fe 100644 --- a/candle-examples/examples/stable-diffusion/attention.rs +++ b/candle-examples/examples/stable-diffusion/attention.rs @@ -198,7 +198,7 @@ impl CrossAttention { let xs = query.matmul(&(key.t()? * self.scale)?)?; let xs = { let _enter = self.span_softmax.enter(); - nn::ops::softmax(&xs, D::Minus1)? + nn::ops::softmax_last_dim(&xs)? }; xs.matmul(&value)?.to_dtype(in_dtype)? }; diff --git a/candle-nn/Cargo.toml b/candle-nn/Cargo.toml index aa055583..db0f6a8f 100644 --- a/candle-nn/Cargo.toml +++ b/candle-nn/Cargo.toml @@ -12,12 +12,16 @@ readme = "README.md" [dependencies] accelerate-src = { workspace = true, optional = true } candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } +half = { workspace = true } thiserror = { workspace = true } intel-mkl-src = { workspace = true, optional = true } +num-traits = { workspace = true } +rayon = { workspace = true } safetensors = { workspace = true } [dev-dependencies] anyhow = { workspace = true } +clap = { workspace = true } [features] default = [] diff --git a/candle-core/examples/cpu_benchmarks.rs b/candle-nn/examples/cpu_benchmarks.rs similarity index 80% rename from candle-core/examples/cpu_benchmarks.rs rename to candle-nn/examples/cpu_benchmarks.rs index 13175ac1..20c92dbb 100644 --- a/candle-core/examples/cpu_benchmarks.rs +++ b/candle-nn/examples/cpu_benchmarks.rs @@ -5,19 +5,10 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; -use candle_core::quantized::GgmlType; -use candle_core::{Device, Result, Tensor, D}; +use candle::quantized::GgmlType; +use candle::{Device, Result, Tensor, D}; use clap::{Parser, Subcommand}; -fn softmax(xs: &Tensor, dim: D) -> Result { - let dim = dim.to_index(xs.shape(), "softmax")?; - let max = xs.max_keepdim(dim)?; - let diff = xs.broadcast_sub(&max)?; - let num = diff.exp()?; - let den = num.sum_keepdim(dim)?; - num.broadcast_div(&den) -} - trait Benchmark { type PreProcessData; type RunResult; @@ -86,12 +77,12 @@ impl Benchmark for Matmul { // https://github.com/ggerganov/llama.cpp/blob/master/examples/benchmark/benchmark-matmult.cpp struct QMatMul; impl Benchmark for QMatMul { - type PreProcessData = (candle_core::quantized::QMatMul, Tensor); + type PreProcessData = (candle::quantized::QMatMul, Tensor); type RunResult = Tensor; fn preprocess() -> Result { - let zeros = vec![candle_core::quantized::k_quants::BlockQ4_0::zeros(); 4096 * 11008 / 32]; - let mm = candle_core::quantized::QTensor::new(zeros, (4096, 11008))?; - let mm = candle_core::quantized::QMatMul::from_qtensor(mm); + let zeros = vec![candle::quantized::k_quants::BlockQ4_0::zeros(); 4096 * 11008 / 32]; + let mm = candle::quantized::QTensor::new(zeros, (4096, 11008))?; + let mm = candle::quantized::QMatMul::from_qtensor(mm); let arg = Tensor::randn(0f32, 1., (128, 11008), &Device::Cpu)?; Ok((mm, arg)) } @@ -114,7 +105,24 @@ impl Benchmark for Softmax { } fn run_one(d: &Self::PreProcessData) -> Result { - softmax(d, D::Minus1) + candle_nn::ops::softmax(d, D::Minus1) + } + + const ITERS: usize = 100; +} + +struct SoftmaxLastDim; +impl Benchmark for SoftmaxLastDim { + type PreProcessData = Tensor; + type RunResult = Tensor; + fn preprocess() -> Result { + // Typical whisper tiny size. + let x = Tensor::randn(0f32, 1., (1, 6, 200, 1500), &Device::Cpu)?; + Ok(x) + } + + fn run_one(d: &Self::PreProcessData) -> Result { + candle_nn::ops::softmax_last_dim(d) } const ITERS: usize = 100; @@ -140,6 +148,7 @@ enum Task { Matmul, Qmatmul, Softmax, + SoftmaxLastDim, } #[derive(Parser, Debug)] @@ -160,6 +169,7 @@ fn main() -> Result<()> { Task::Conv2d => run::(args.iters)?, Task::Matmul => run::(args.iters)?, Task::Softmax => run::(args.iters)?, + Task::SoftmaxLastDim => run::(args.iters)?, Task::Qmatmul => run::(args.iters)?, } Ok(()) diff --git a/candle-nn/src/ops.rs b/candle-nn/src/ops.rs index c3b6ffa2..55da46f8 100644 --- a/candle-nn/src/ops.rs +++ b/candle-nn/src/ops.rs @@ -1,4 +1,5 @@ -use candle::{Result, Tensor}; +use candle::{CpuStorage, Layout, Result, Shape, Tensor}; +use rayon::prelude::*; /// Applies the softmax function to the input tensor, rescaling the element so that elements on /// a slice of fixed index on dimension `dim` are between 0 and 1 and sum to 1. @@ -77,3 +78,69 @@ impl Dropout { } } } + +struct SoftmaxLastDim; + +impl candle::CustomOp1 for SoftmaxLastDim { + fn name(&self) -> &'static str { + "softmax-last-dim" + } + + fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)> { + fn softmax( + src: &[T], + layout: &Layout, + ) -> Result<(CpuStorage, Shape)> { + let src = match layout.contiguous_offsets() { + None => candle::bail!("input has to be contiguous"), + Some((o1, o2)) => &src[o1..o2], + }; + let el_count = layout.shape().elem_count(); + let dims = layout.shape().dims(); + let dim_m1 = dims[dims.len() - 1]; + let mut dst = vec![T::zero(); el_count]; + src.par_chunks(dim_m1) + .zip(dst.par_chunks_mut(dim_m1)) + .for_each(|(src, dst)| { + let mut max = T::neg_infinity(); + for &s in src.iter() { + max = T::max(s, max) + } + let mut sum_exp = T::zero(); + for (s, d) in src.iter().zip(dst.iter_mut()) { + *d = (*s - max).exp(); + sum_exp += *d + } + for d in dst.iter_mut() { + *d /= sum_exp + } + }); + let storage = candle::WithDType::to_cpu_storage_owned(dst); + Ok((storage, Shape::from_dims(dims))) + } + + match storage { + CpuStorage::BF16(slice) => softmax::(slice, layout), + CpuStorage::F16(slice) => softmax::(slice, layout), + CpuStorage::F32(slice) => softmax::(slice, layout), + CpuStorage::F64(slice) => softmax::(slice, layout), + _ => candle::bail!("unsupported dtype for softmax {:?}", storage), + } + } + + fn cuda_fwd( + &self, + _storage: &candle::CudaStorage, + _layout: &Layout, + ) -> Result<(candle::CudaStorage, Shape)> { + candle::bail!("TODO: implement a cuda kernel") + } +} + +pub fn softmax_last_dim(xs: &Tensor) -> Result { + if xs.device().is_cpu() { + xs.apply_op1_no_bwd(&SoftmaxLastDim) + } else { + softmax(xs, candle::D::Minus1) + } +} diff --git a/candle-nn/tests/ops.rs b/candle-nn/tests/ops.rs index 4ba8cfcc..5ca01b37 100644 --- a/candle-nn/tests/ops.rs +++ b/candle-nn/tests/ops.rs @@ -41,6 +41,16 @@ fn softmax() -> Result<()> { [[0.2, 0.1, 0.7], [0.4444, 0.1111, 0.4444]] ] ); + let t2 = candle_nn::ops::softmax_last_dim(&tensor.log()?)?; + assert_eq!( + to_vec3_round(&t2, 4)?, + &[ + // (3, 1, 4) / 8, (1, 5, 9) / 15 + [[0.375, 0.125, 0.5], [0.0667, 0.3333, 0.6]], + // (2, 1, 7) / 10, (8, 2, 8) / 18 + [[0.2, 0.1, 0.7], [0.4444, 0.1111, 0.4444]] + ] + ); Ok(()) } From 6615daf2425bbf33f0e1f97d2a18534e6bdb9fc3 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 5 Sep 2023 16:22:27 +0200 Subject: [PATCH 014/150] Tweaks to softmax. (#745) --- candle-core/src/cpu/kernels.rs | 95 +++++++++++++++++++++++++++++----- candle-nn/src/ops.rs | 8 ++- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/candle-core/src/cpu/kernels.rs b/candle-core/src/cpu/kernels.rs index 97e195ef..527646d6 100644 --- a/candle-core/src/cpu/kernels.rs +++ b/candle-core/src/cpu/kernels.rs @@ -1,4 +1,7 @@ -pub trait VecOps: num_traits::NumAssign + PartialOrd + Copy { +pub trait VecOps: num_traits::NumAssign + Copy { + fn min(self, rhs: Self) -> Self; + fn max(self, rhs: Self) -> Self; + /// Dot-product of two vectors. /// /// # Safety @@ -37,10 +40,7 @@ pub trait VecOps: num_traits::NumAssign + PartialOrd + Copy { unsafe fn vec_reduce_max(xs: *const Self, res: *mut Self, len: usize) { *res = *xs; for i in 1..len { - let x = *xs.add(i); - if x > *res { - *res = x - } + *res = (*res).max(*xs.add(i)) } } @@ -54,15 +54,22 @@ pub trait VecOps: num_traits::NumAssign + PartialOrd + Copy { unsafe fn vec_reduce_min(xs: *const Self, res: *mut Self, len: usize) { *res = *xs; for i in 1..len { - let x = *xs.add(i); - if x < *res { - *res = x - } + *res = (*res).min(*xs.add(i)) } } } impl VecOps for f32 { + #[inline(always)] + fn min(self, other: Self) -> Self { + Self::min(self, other) + } + + #[inline(always)] + fn max(self, other: Self) -> Self { + Self::max(self, other) + } + #[inline(always)] unsafe fn vec_dot(lhs: *const Self, rhs: *const Self, res: *mut Self, len: usize) { super::vec_dot_f32(lhs, rhs, res, len) @@ -75,6 +82,16 @@ impl VecOps for f32 { } impl VecOps for half::f16 { + #[inline(always)] + fn min(self, other: Self) -> Self { + Self::min(self, other) + } + + #[inline(always)] + fn max(self, other: Self) -> Self { + Self::max(self, other) + } + #[inline(always)] unsafe fn vec_dot(lhs: *const Self, rhs: *const Self, res: *mut Self, len: usize) { let mut res_f32 = 0f32; @@ -83,11 +100,61 @@ impl VecOps for half::f16 { } } -impl VecOps for f64 {} -impl VecOps for half::bf16 {} -impl VecOps for u8 {} -impl VecOps for u32 {} -impl VecOps for i64 {} +impl VecOps for f64 { + #[inline(always)] + fn min(self, other: Self) -> Self { + Self::min(self, other) + } + + #[inline(always)] + fn max(self, other: Self) -> Self { + Self::max(self, other) + } +} +impl VecOps for half::bf16 { + #[inline(always)] + fn min(self, other: Self) -> Self { + Self::min(self, other) + } + + #[inline(always)] + fn max(self, other: Self) -> Self { + Self::max(self, other) + } +} +impl VecOps for u8 { + #[inline(always)] + fn min(self, other: Self) -> Self { + ::min(self, other) + } + + #[inline(always)] + fn max(self, other: Self) -> Self { + ::max(self, other) + } +} +impl VecOps for u32 { + #[inline(always)] + fn min(self, other: Self) -> Self { + ::min(self, other) + } + + #[inline(always)] + fn max(self, other: Self) -> Self { + ::max(self, other) + } +} +impl VecOps for i64 { + #[inline(always)] + fn min(self, other: Self) -> Self { + ::min(self, other) + } + + #[inline(always)] + fn max(self, other: Self) -> Self { + ::max(self, other) + } +} #[inline(always)] pub fn par_for_each(n_threads: usize, func: impl Fn(usize) + Send + Sync) { diff --git a/candle-nn/src/ops.rs b/candle-nn/src/ops.rs index 55da46f8..73214077 100644 --- a/candle-nn/src/ops.rs +++ b/candle-nn/src/ops.rs @@ -103,14 +103,12 @@ impl candle::CustomOp1 for SoftmaxLastDim { .zip(dst.par_chunks_mut(dim_m1)) .for_each(|(src, dst)| { let mut max = T::neg_infinity(); - for &s in src.iter() { - max = T::max(s, max) - } - let mut sum_exp = T::zero(); + unsafe { T::vec_reduce_max(src.as_ptr(), &mut max, dim_m1) }; for (s, d) in src.iter().zip(dst.iter_mut()) { *d = (*s - max).exp(); - sum_exp += *d } + let mut sum_exp = T::zero(); + unsafe { T::vec_reduce_sum(dst.as_ptr(), &mut sum_exp, dim_m1) }; for d in dst.iter_mut() { *d /= sum_exp } From 94c6a8d3d3381f10afd41f6a2f98d5fad4a249ac Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 5 Sep 2023 17:53:20 +0200 Subject: [PATCH 015/150] Add a dedicated cuda kernel for softmax. (#746) --- candle-kernels/src/reduce.cu | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/candle-kernels/src/reduce.cu b/candle-kernels/src/reduce.cu index 271502c5..fca6865e 100644 --- a/candle-kernels/src/reduce.cu +++ b/candle-kernels/src/reduce.cu @@ -49,6 +49,50 @@ fast_sum(const size_t src_numel, const size_t el_to_sum_per_block, dst[dst_id] = shr[0]; } +// Softmax implementation adapted from ggml. +// https://github.com/ggerganov/llama.cpp/blob/d59bd97065cd7ded6c4ecab54b1d5e0b1b11e318/ggml-cuda.cu#L4159 +template +__device__ void softmax(const T * x, T * dst, const int ncols) { + const int row = blockDim.x*blockIdx.x + threadIdx.x; + const int block_size = blockDim.y; + const int tid = threadIdx.y; + + T max_val = -INFINITY; + + for (int col = tid; col < ncols; col += block_size) { + const int i = row*ncols + col; + max_val = maxg(max_val, x[i]); + } + + // find the max value in the block +#pragma unroll + for (int mask = 16; mask > 0; mask >>= 1) { + max_val = maxg(max_val, __shfl_xor_sync(0xffffffff, max_val, mask, 32)); + } + + ACC tmp = 0.; + + for (int col = tid; col < ncols; col += block_size) { + const int i = row*ncols + col; + const T val = expg(x[i] - max_val); + tmp += static_cast(val); + dst[i] = val; + } + + // sum up partial sums +#pragma unroll + for (int mask = 16; mask > 0; mask >>= 1) { + tmp += __shfl_xor_sync(0xffffffff, tmp, mask, 32); + } + + const ACC inv_tmp = 1. / tmp; + + for (int col = tid; col < ncols; col += block_size) { + const int i = row*ncols + col; + dst[i] *= inv_tmp; + } +} + template __device__ void fast_max(const size_t src_numel, const size_t el_to_sum_per_block, @@ -290,12 +334,21 @@ fast_argmax(const size_t src_numel, const size_t el_to_sum_per_block, } \ } +#define SOFTMAX_OP(TYPENAME, ACC_TYPENAME, FN_NAME) \ + extern "C" __global__ void FN_NAME( \ + const TYPENAME *src, TYPENAME *dst, \ + const int n_cols) { \ + softmax(src, dst, n_cols); \ + } \ + #if __CUDA_ARCH__ >= 800 +SOFTMAX_OP(__nv_bfloat16, float, softmax_bf16) SUM_OP(__nv_bfloat16, sum_bf16) FAST_OP(__nv_bfloat16, fast_min_bf16, fast_max_bf16, fast_argmin_bf16, fast_argmax_bf16, fast_sum_bf16) #endif #if __CUDA_ARCH__ >= 530 +SOFTMAX_OP(__half, float, softmax_f16) SUM_OP(__half, sum_f16) FAST_OP(__half, fast_min_f16, fast_max_f16, fast_argmin_f16, fast_argmax_f16, fast_sum_f16) #endif @@ -303,6 +356,8 @@ FAST_OP(__half, fast_min_f16, fast_max_f16, fast_argmin_f16, fast_argmax_f16, fa SUM_OP(float, sum_f32) SUM_OP(double, sum_f64) SUM_OP(uint32_t, sum_u32) +SOFTMAX_OP(float, float, softmax_f32) +SOFTMAX_OP(double, double, softmax_f64) FAST_OP(float, fast_min_f32, fast_max_f32, fast_argmin_f32, fast_argmax_f32, fast_sum_f32) FAST_OP(double, fast_min_f64, fast_max_f64, fast_argmin_f64, fast_argmax_f64, fast_sum_f64) From a0d65585db0323747f71b4f33831a165b56a759b Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 5 Sep 2023 19:38:03 +0200 Subject: [PATCH 016/150] Softmax implementation for cuda. (#747) --- candle-core/src/cuda_backend.rs | 20 +++++------ candle-nn/src/ops.rs | 59 ++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index 663f2319..2180be5e 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -1,7 +1,7 @@ use crate::backend::{BackendDevice, BackendStorage}; use crate::op::{BinaryOpT, CmpOp, ReduceOp, UnaryOpT}; use crate::{CpuStorage, DType, Layout, Result, Shape, WithDType}; -use candle_kernels as kernels; +pub use candle_kernels as kernels; pub use cudarc; use cudarc::cublas::{Gemm, GemmConfig, StridedBatchedConfig}; use cudarc::driver::{ @@ -383,7 +383,7 @@ impl BackendDevice for CudaDevice { } #[derive(Debug)] -enum CudaStorageSlice { +pub enum CudaStorageSlice { U8(CudaSlice), U32(CudaSlice), I64(CudaSlice), @@ -394,7 +394,7 @@ enum CudaStorageSlice { } type S = CudaStorageSlice; -trait Map1 { +pub trait Map1 { fn f( &self, src: &CudaSlice, @@ -416,7 +416,7 @@ trait Map1 { } } -trait Map2 { +pub trait Map2 { fn f( &self, src1: &CudaSlice, @@ -441,7 +441,7 @@ trait Map2 { } } -trait Map2InPlace { +pub trait Map2InPlace { fn f( &self, dst: &mut CudaSlice, @@ -472,7 +472,7 @@ trait Map2InPlace { } } -trait Map1Any { +pub trait Map1Any { fn f) -> S>( &self, src: &CudaSlice, @@ -495,7 +495,7 @@ trait Map1Any { } } -trait Map2Any { +pub trait Map2Any { fn f( &self, src1: &CudaSlice, @@ -532,7 +532,7 @@ impl Map1 for Clone { } } -fn kernel_name(root: &str) -> String { +pub fn kernel_name(root: &str) -> String { let dtype = T::DTYPE.as_str(); format!("{root}_{dtype}") } @@ -1310,8 +1310,8 @@ fn slice_src_and_dst<'a, T>( #[derive(Debug)] pub struct CudaStorage { - slice: CudaStorageSlice, - device: CudaDevice, + pub slice: CudaStorageSlice, + pub device: CudaDevice, } pub trait CudaDType: Sized { diff --git a/candle-nn/src/ops.rs b/candle-nn/src/ops.rs index 73214077..adf1451c 100644 --- a/candle-nn/src/ops.rs +++ b/candle-nn/src/ops.rs @@ -126,19 +126,62 @@ impl candle::CustomOp1 for SoftmaxLastDim { } } + #[cfg(feature = "cuda")] fn cuda_fwd( &self, - _storage: &candle::CudaStorage, - _layout: &Layout, + storage: &candle::CudaStorage, + layout: &Layout, ) -> Result<(candle::CudaStorage, Shape)> { - candle::bail!("TODO: implement a cuda kernel") + use candle::cuda_backend::cudarc::driver::{ + CudaSlice, DeviceRepr, LaunchAsync, LaunchConfig, + }; + use candle::cuda_backend::{kernel_name, kernels, Map1, WrapErr}; + use candle::{CudaDevice, WithDType}; + + struct S; + impl Map1 for S { + fn f( + &self, + src: &CudaSlice, + dev: &CudaDevice, + layout: &Layout, + ) -> Result> { + let src = match layout.contiguous_offsets() { + None => candle::bail!("input has to be contiguous"), + Some((o1, o2)) => src.slice(o1..o2), + }; + let el = layout.shape().elem_count(); + let dims = layout.shape().dims(); + let dim_m1 = dims[dims.len() - 1]; + let (n_rows, n_cols) = (el / dim_m1, dim_m1); + + let cfg = LaunchConfig { + grid_dim: (n_rows as u32, 1, 1), + block_dim: (1, 32, 1), + shared_mem_bytes: 0, + }; + let src = &src.slice(layout.start_offset()..); + let func = dev.get_or_load_func(&kernel_name::("softmax"), kernels::REDUCE)?; + // SAFETY: Set later by running the kernel. + let dst = unsafe { dev.alloc::(el) }.w()?; + let params = (src, &dst, n_cols as i32); + // SAFETY: ffi. + unsafe { func.launch(cfg, params) }.w()?; + Ok(dst) + } + } + + use candle::backend::BackendStorage; + let dev = storage.device(); + let slice = S.map(&storage.slice, dev, layout)?; + let dst = candle::cuda_backend::CudaStorage { + slice, + device: dev.clone(), + }; + Ok((dst, layout.shape().clone())) } } pub fn softmax_last_dim(xs: &Tensor) -> Result { - if xs.device().is_cpu() { - xs.apply_op1_no_bwd(&SoftmaxLastDim) - } else { - softmax(xs, candle::D::Minus1) - } + xs.apply_op1_no_bwd(&SoftmaxLastDim) } From 6a40decc76246b23f7f1c6ada539c93afadfffba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radam=C3=A9s=20Ajna?= Date: Tue, 5 Sep 2023 11:24:43 -0700 Subject: [PATCH 017/150] Minor WASM UI improvements (#748) * add stats * random seed btn * minor ui improvoments --- .../llama2-c/lib-example.html | 38 ++++++--- .../llama2-c/llama2cWorker.js | 12 ++- candle-wasm-examples/whisper/lib-example.html | 17 ++-- candle-wasm-examples/yolo/lib-example.html | 78 +++++++++++++------ 4 files changed, 101 insertions(+), 44 deletions(-) diff --git a/candle-wasm-examples/llama2-c/lib-example.html b/candle-wasm-examples/llama2-c/lib-example.html index bc519e4b..5995f003 100644 --- a/candle-wasm-examples/llama2-c/lib-example.html +++ b/candle-wasm-examples/llama2-c/lib-example.html @@ -60,23 +60,30 @@ const seed = getValue("seed"); const maxSeqLen = getValue("max-seq"); - function updateStatus({ status, message, prompt, sentence }) { + function updateStatus(data) { const outStatus = document.querySelector("#output-status"); const outGen = document.querySelector("#output-generation"); + const outCounter = document.querySelector("#output-counter"); - switch (status) { + switch (data.status) { case "loading": outStatus.hidden = false; - outStatus.textContent = message; + outStatus.textContent = data.message; outGen.hidden = true; + outCounter.hidden = true; break; case "generating": + const { message, prompt, sentence, tokensSec, totalTime } = data; outStatus.hidden = true; + outCounter.hidden = false; outGen.hidden = false; outGen.innerHTML = `${prompt}${sentence.replace( /\|\<\/s\>/g, "" )}`; + outCounter.innerHTML = `${(totalTime / 1000).toFixed( + 2 + )}s (${tokensSec.toFixed(2)} tok/s)`; break; case "complete": outStatus.hidden = true; @@ -206,8 +213,9 @@ id="prompt" class="font-light w-full px-3 py-2 mx-1 resize-none outline-none" placeholder="Add your prompt here..." + value="Once upon a time" /> -

Transcription:

-
+ class="min-h-[250px] bg-slate-100 text-gray-500 p-4 rounded-md flex flex-col gap-2" + > + + No transcription results yet +
diff --git a/candle-wasm-examples/yolo/lib-example.html b/candle-wasm-examples/yolo/lib-example.html index bab2ec13..8f7d07c7 100644 --- a/candle-wasm-examples/yolo/lib-example.html +++ b/candle-wasm-examples/yolo/lib-example.html @@ -145,6 +145,10 @@ } }); + document.querySelector("#clear-btn").addEventListener("click", () => { + drawImageCanvas(); + }); + function drawImageCanvas(imgURL) { const canvas = document.querySelector("#canvas"); const canvasResult = document.querySelector("#canvas-result"); @@ -153,21 +157,28 @@ .clearRect(0, 0, canvas.width, canvas.height); const ctx = canvas.getContext("2d"); ctx.clearRect(0, 0, canvas.width, canvas.height); - document.querySelector("#share-btn").hidden = true; + document.querySelector("#share-btn").classList.add("invisible"); + document.querySelector("#clear-btn").classList.add("invisible"); + document.querySelector("#detect").disabled = true; + hasImage = false; + canvas.parentElement.style.height = "auto"; - const img = new Image(); - img.crossOrigin = "anonymous"; + if (imgURL && imgURL !== "") { + const img = new Image(); + img.crossOrigin = "anonymous"; - img.onload = () => { - canvas.width = img.width; - canvas.height = img.height; - ctx.drawImage(img, 0, 0); + img.onload = () => { + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); - canvas.parentElement.style.height = canvas.offsetHeight + "px"; - hasImage = true; - document.querySelector("#detect").disabled = false; - }; - img.src = imgURL; + canvas.parentElement.style.height = canvas.offsetHeight + "px"; + hasImage = true; + document.querySelector("#detect").disabled = false; + document.querySelector("#clear-btn").classList.remove("invisible"); + }; + img.src = imgURL; + } } async function classifyImage( @@ -310,7 +321,7 @@ button.classList.add("bg-blue-950"); button.classList.remove("bg-blue-700"); button.textContent = "Predict"; - document.querySelector("#share-btn").hidden = false; + document.querySelector("#share-btn").classList.remove("invisible"); } } document.querySelector("#share-btn").addEventListener("click", () => { @@ -372,8 +383,37 @@ +
+ +
+
+ +
-
- -
From a4f40f3dc881802daa973c8f4f89d133cfa25e2b Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 5 Sep 2023 21:26:15 +0200 Subject: [PATCH 018/150] Use rayon directly rather than constraining the number of threads. (#749) --- candle-core/src/cpu_backend.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/candle-core/src/cpu_backend.rs b/candle-core/src/cpu_backend.rs index c3ec8249..01ccfde7 100644 --- a/candle-core/src/cpu_backend.rs +++ b/candle-core/src/cpu_backend.rs @@ -2,6 +2,7 @@ use crate::backend::{BackendDevice, BackendStorage}; use crate::op::{BinaryOpT, CmpOp, ReduceOp, UnaryOpT}; use crate::{DType, Error, IntDType, Layout, Result, Shape, WithDType}; use half::{bf16, f16}; +use rayon::prelude::*; // TODO: Maybe we should not implement [Clone] here and instead have an explicit allocator + // intercept the oom errors to avoid panicking and provide a proper error. @@ -1052,10 +1053,8 @@ impl<'a> Map2 for Conv1D<'a> { } } - let num_threads = crate::utils::get_num_threads(); - for offset in 0..p.k_size { - crate::cpu::kernels::par_range(0, p.c_out, num_threads, |dst_c_idx| { + (0..p.c_out).into_par_iter().for_each(|dst_c_idx| { let dst_idx = dst_c_idx * l_out; let k_cont = (0..p.c_in) .map(|c_in_idx| k[dst_c_idx * k_s0 + c_in_idx * k_s1 + offset * k_s2]) @@ -1123,11 +1122,9 @@ impl<'a> Map2 for Conv2D<'a> { } } - let num_threads = crate::utils::get_num_threads(); - for offset_h in 0..p.k_h { for offset_w in 0..p.k_w { - crate::cpu::kernels::par_range(0, p.c_out, num_threads, |dst_c_idx| { + (0..p.c_out).into_par_iter().for_each(|dst_c_idx| { let dst_idx = dst_c_idx * out_w * out_h; let k_cont = (0..p.c_in) .map(|c_in_idx| { @@ -1216,11 +1213,10 @@ impl<'a> Map2 for ConvTranspose2D<'a> { } } } - let num_threads = crate::utils::get_num_threads(); for k_y in 0..p.k_h { for k_x in 0..p.k_w { - crate::cpu::kernels::par_range(0, p.c_out, num_threads, |dst_c_idx| { + (0..p.c_out).into_par_iter().for_each(|dst_c_idx| { let k_cont = (0..p.c_in) .map(|c_in_idx| { k[c_in_idx * k_s0 + dst_c_idx * k_s1 + k_y * k_s2 + k_x * k_s3] From 16bf44f6e9ed89d68260baa7914277fa269dcaee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radam=C3=A9s=20Ajna?= Date: Tue, 5 Sep 2023 20:53:31 -0700 Subject: [PATCH 019/150] force model cache (#751) --- .../llama2-c/lib-example.html | 12 ++++++++++-- .../llama2-c/llama2cWorker.js | 16 ++++++++++------ candle-wasm-examples/whisper/whisperWorker.js | 19 ++++++++++--------- candle-wasm-examples/yolo/yoloWorker.js | 17 ++++++++++++++--- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/candle-wasm-examples/llama2-c/lib-example.html b/candle-wasm-examples/llama2-c/lib-example.html index 5995f003..b5033c54 100644 --- a/candle-wasm-examples/llama2-c/lib-example.html +++ b/candle-wasm-examples/llama2-c/lib-example.html @@ -38,11 +38,11 @@ }, stories42M: { url: "stories42M.bin", - seq_len: 256, + seq_len: 1024, }, stories110M: { url: "stories110M.bin", - seq_len: 256, + seq_len: 1024, }, }; @@ -124,9 +124,17 @@ const prompt = document.querySelector("#prompt"); const clearBtn = document.querySelector("#clear-btn"); const runBtn = document.querySelector("#run"); + const modelSelect = document.querySelector("#model"); let runController = new AbortController(); let isRunning = false; + modelSelect.addEventListener("change", (e) => { + const model = MODELS[e.target.value]; + document.querySelector("#max-seq").max = model.seq_len; + document.querySelector("#max-seq").nextElementSibling.value = + model.seq_len; + }); + form.addEventListener("submit", async (e) => { e.preventDefault(); if (isRunning) { diff --git a/candle-wasm-examples/llama2-c/llama2cWorker.js b/candle-wasm-examples/llama2-c/llama2cWorker.js index e4229055..abaf3401 100644 --- a/candle-wasm-examples/llama2-c/llama2cWorker.js +++ b/candle-wasm-examples/llama2-c/llama2cWorker.js @@ -1,13 +1,17 @@ import init, { Model } from "./build/m.js"; async function fetchArrayBuffer(url) { - const res = await fetch(url, { - cache: "force-cache", - }); - const data = await res.arrayBuffer(); - return new Uint8Array(data); + const cacheName = "llama2c-candle-cache"; + const cache = await caches.open(cacheName); + const cachedResponse = await cache.match(url); + if (cachedResponse) { + const data = await cachedResponse.arrayBuffer(); + return new Uint8Array(data); + } + const res = await fetch(url, { cache: "force-cache" }); + cache.put(url, res.clone()); + return new Uint8Array(await res.arrayBuffer()); } - class Llama2C { static instance = {}; diff --git a/candle-wasm-examples/whisper/whisperWorker.js b/candle-wasm-examples/whisper/whisperWorker.js index 2598adde..d2ad8e0b 100644 --- a/candle-wasm-examples/whisper/whisperWorker.js +++ b/candle-wasm-examples/whisper/whisperWorker.js @@ -2,16 +2,17 @@ import init, { Decoder } from "./build/m.js"; async function fetchArrayBuffer(url) { - const res = await fetch(url, { - cache: "force-cache", - headers: { - "Cache-Control": "public, max-age=31536000", - }, - }); - const data = await res.arrayBuffer(); - return new Uint8Array(data); + const cacheName = "whisper-candle-cache"; + const cache = await caches.open(cacheName); + const cachedResponse = await cache.match(url); + if (cachedResponse) { + const data = await cachedResponse.arrayBuffer(); + return new Uint8Array(data); + } + const res = await fetch(url, { cache: "force-cache" }); + cache.put(url, res.clone()); + return new Uint8Array(await res.arrayBuffer()); } - class Whisper { static instance = {}; // Retrieve the Whisper model. When called for the first time, diff --git a/candle-wasm-examples/yolo/yoloWorker.js b/candle-wasm-examples/yolo/yoloWorker.js index 93097372..8b5ef8b9 100644 --- a/candle-wasm-examples/yolo/yoloWorker.js +++ b/candle-wasm-examples/yolo/yoloWorker.js @@ -1,6 +1,19 @@ //load the candle yolo wasm module import init, { Model, ModelPose } from "./build/m.js"; +async function fetchArrayBuffer(url) { + const cacheName = "yolo-candle-cache"; + const cache = await caches.open(cacheName); + const cachedResponse = await cache.match(url); + if (cachedResponse) { + const data = await cachedResponse.arrayBuffer(); + return new Uint8Array(data); + } + const res = await fetch(url, { cache: "force-cache" }); + cache.put(url, res.clone()); + return new Uint8Array(await res.arrayBuffer()); +} + class Yolo { static instance = {}; // Retrieve the YOLO model. When called for the first time, @@ -11,9 +24,7 @@ class Yolo { await init(); self.postMessage({ status: `loading model ${modelID}:${modelSize}` }); - const modelRes = await fetch(modelURL); - const yoloArrayBuffer = await modelRes.arrayBuffer(); - const weightsArrayU8 = new Uint8Array(yoloArrayBuffer); + const weightsArrayU8 = await fetchArrayBuffer(modelURL); if (/pose/.test(modelID)) { // if pose model, use ModelPose this.instance[modelID] = new ModelPose(weightsArrayU8, modelSize); From 7299a683534a84e13ec3ff8a92b2fff77102d7e7 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 6 Sep 2023 08:06:49 +0200 Subject: [PATCH 020/150] img2img pipeline for stable diffusion. (#752) * img2img pipeline for stable diffusion. * Rename the arguments + fix. * Fix for zero strength. * Another fix. * Another fix. * Revert. * Include the backtrace. * Noise scaling. * Fix the height/width. --- candle-core/src/error.rs | 4 +- .../examples/stable-diffusion/ddim.rs | 11 +++ .../examples/stable-diffusion/main.rs | 85 ++++++++++++++++--- candle-examples/src/lib.rs | 8 +- 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/candle-core/src/error.rs b/candle-core/src/error.rs index 1cf20a84..d030fab1 100644 --- a/candle-core/src/error.rs +++ b/candle-core/src/error.rs @@ -207,11 +207,11 @@ pub type Result = std::result::Result; impl Error { pub fn wrap(err: impl std::error::Error + Send + Sync + 'static) -> Self { - Self::Wrapped(Box::new(err)) + Self::Wrapped(Box::new(err)).bt() } pub fn msg(err: impl std::error::Error + Send + Sync + 'static) -> Self { - Self::Msg(err.to_string()) + Self::Msg(err.to_string()).bt() } pub fn bt(self) -> Self { diff --git a/candle-examples/examples/stable-diffusion/ddim.rs b/candle-examples/examples/stable-diffusion/ddim.rs index f2e021ce..260a4965 100644 --- a/candle-examples/examples/stable-diffusion/ddim.rs +++ b/candle-examples/examples/stable-diffusion/ddim.rs @@ -163,6 +163,17 @@ impl DDIMScheduler { } } + pub fn add_noise(&self, original: &Tensor, noise: Tensor, timestep: usize) -> Result { + let timestep = if timestep >= self.alphas_cumprod.len() { + timestep - 1 + } else { + timestep + }; + let sqrt_alpha_prod = self.alphas_cumprod[timestep].sqrt(); + let sqrt_one_minus_alpha_prod = (1.0 - self.alphas_cumprod[timestep]).sqrt(); + (original * sqrt_alpha_prod)? + (noise * sqrt_one_minus_alpha_prod)? + } + pub fn init_noise_sigma(&self) -> f64 { self.init_noise_sigma } diff --git a/candle-examples/examples/stable-diffusion/main.rs b/candle-examples/examples/stable-diffusion/main.rs index 8372edcd..70e1e92c 100644 --- a/candle-examples/examples/stable-diffusion/main.rs +++ b/candle-examples/examples/stable-diffusion/main.rs @@ -96,6 +96,15 @@ struct Args { #[arg(long)] use_f16: bool, + + #[arg(long, value_name = "FILE")] + img2img: Option, + + /// The strength, indicates how much to transform the initial image. The + /// value must be between 0 and 1, a value of 1 discards the initial image + /// information. + #[arg(long, default_value_t = 0.8)] + img2img_strength: f64, } #[derive(Debug, Clone, Copy, clap::ValueEnum)] @@ -306,6 +315,26 @@ fn text_embeddings( Ok(text_embeddings) } +fn image_preprocess>(path: T) -> anyhow::Result { + let img = image::io::Reader::open(path)?.decode()?; + let (height, width) = (img.height() as usize, img.width() as usize); + let height = height - height % 32; + let width = width - width % 32; + let img = img.resize_to_fill( + width as u32, + height as u32, + image::imageops::FilterType::CatmullRom, + ); + let img = img.to_rgb8(); + let img = img.into_raw(); + let img = Tensor::from_vec(img, (height, width, 3), &Device::Cpu)? + .permute((2, 0, 1))? + .to_dtype(DType::F32)? + .affine(2. / 255., -1.)? + .unsqueeze(0)?; + Ok(img) +} + fn run(args: Args) -> Result<()> { use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; @@ -328,9 +357,15 @@ fn run(args: Args) -> Result<()> { tracing, use_f16, use_flash_attn, + img2img, + img2img_strength, .. } = args; + if !(0. ..=1.).contains(&img2img_strength) { + anyhow::bail!("img2img-strength should be between 0 and 1, got {img2img_strength}") + } + let _guard = if tracing { let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); tracing_subscriber::registry().with(chrome_layer).init(); @@ -382,25 +417,53 @@ fn run(args: Args) -> Result<()> { println!("Building the autoencoder."); let vae_weights = ModelFile::Vae.get(vae_weights, sd_version, use_f16)?; let vae = sd_config.build_vae(&vae_weights, &device, dtype)?; + let init_latent_dist = match &img2img { + None => None, + Some(image) => { + let image = image_preprocess(image)?.to_device(&device)?; + Some(vae.encode(&image)?) + } + }; println!("Building the unet."); let unet_weights = ModelFile::Unet.get(unet_weights, sd_version, use_f16)?; let unet = sd_config.build_unet(&unet_weights, &device, 4, use_flash_attn, dtype)?; + let t_start = if img2img.is_some() { + n_steps - (n_steps as f64 * img2img_strength) as usize + } else { + 0 + }; let bsize = 1; for idx in 0..num_samples { - let mut latents = Tensor::randn( - 0f32, - 1f32, - (bsize, 4, sd_config.height / 8, sd_config.width / 8), - &device, - )? - .to_dtype(dtype)?; - - // scale the initial noise by the standard deviation required by the scheduler - latents = (latents * scheduler.init_noise_sigma())?; + let timesteps = scheduler.timesteps(); + let latents = match &init_latent_dist { + Some(init_latent_dist) => { + let latents = (init_latent_dist.sample()? * 0.18215)?.to_device(&device)?; + if t_start < timesteps.len() { + let noise = latents.randn_like(0f64, 1f64)?; + scheduler.add_noise(&latents, noise, timesteps[t_start])? + } else { + latents + } + } + None => { + let latents = Tensor::randn( + 0f32, + 1f32, + (bsize, 4, sd_config.height / 8, sd_config.width / 8), + &device, + )?; + // scale the initial noise by the standard deviation required by the scheduler + (latents * scheduler.init_noise_sigma())? + } + }; + let mut latents = latents.to_dtype(dtype)?; println!("starting sampling"); - for (timestep_index, ×tep) in scheduler.timesteps().iter().enumerate() { + for (timestep_index, ×tep) in timesteps.iter().enumerate() { + if timestep_index < t_start { + continue; + } let start_time = std::time::Instant::now(); let latent_model_input = Tensor::cat(&[&latents, &latents], 0)?; diff --git a/candle-examples/src/lib.rs b/candle-examples/src/lib.rs index 395162eb..f9581b02 100644 --- a/candle-examples/src/lib.rs +++ b/candle-examples/src/lib.rs @@ -35,14 +35,14 @@ pub fn load_image_and_resize>( } /// Saves an image to disk using the image crate, this expects an input with shape -/// (c, width, height). +/// (c, height, width). pub fn save_image>(img: &Tensor, p: P) -> Result<()> { let p = p.as_ref(); - let (channel, width, height) = img.dims3()?; + let (channel, height, width) = img.dims3()?; if channel != 3 { - candle::bail!("save_image expects an input of shape (3, width, height)") + candle::bail!("save_image expects an input of shape (3, height, width)") } - let img = img.transpose(0, 1)?.t()?.flatten_all()?; + let img = img.permute((1, 2, 0))?.flatten_all()?; let pixels = img.to_vec1::()?; let image: image::ImageBuffer, Vec> = match image::ImageBuffer::from_raw(width as u32, height as u32, pixels) { From dcf708559da9f5d9f07a1394200236aec261e820 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 6 Sep 2023 08:49:28 +0200 Subject: [PATCH 021/150] Fix for cudnn to work with img2img. (#753) --- candle-examples/examples/stable-diffusion/unet_2d_blocks.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/candle-examples/examples/stable-diffusion/unet_2d_blocks.rs b/candle-examples/examples/stable-diffusion/unet_2d_blocks.rs index 26a1035b..be258acb 100644 --- a/candle-examples/examples/stable-diffusion/unet_2d_blocks.rs +++ b/candle-examples/examples/stable-diffusion/unet_2d_blocks.rs @@ -754,6 +754,7 @@ impl UpBlock2D { let mut xs = xs.clone(); for (index, resnet) in self.resnets.iter().enumerate() { xs = Tensor::cat(&[&xs, &res_xs[res_xs.len() - index - 1]], 1)?; + xs = xs.contiguous()?; xs = resnet.forward(&xs, temb)?; } match &self.upsampler { @@ -855,6 +856,7 @@ impl CrossAttnUpBlock2D { let mut xs = xs.clone(); for (index, resnet) in self.upblock.resnets.iter().enumerate() { xs = Tensor::cat(&[&xs, &res_xs[res_xs.len() - index - 1]], 1)?; + xs = xs.contiguous()?; xs = resnet.forward(&xs, temb)?; xs = self.attentions[index].forward(&xs, encoder_hidden_states)?; } From bdc9d46fe38e550dd0b332a26f14711947e6c0d9 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 6 Sep 2023 16:29:09 +0200 Subject: [PATCH 022/150] Use an arc in the varbuilder rather than rc. (#757) * Use an arc in the varbuilder rather than rc. * Require the backends to be send. * Request send and sync. --- candle-nn/src/var_builder.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/candle-nn/src/var_builder.rs b/candle-nn/src/var_builder.rs index bf5d5b43..4ccbaf17 100644 --- a/candle-nn/src/var_builder.rs +++ b/candle-nn/src/var_builder.rs @@ -5,14 +5,14 @@ use crate::VarMap; use candle::{safetensors::Load, DType, Device, Error, Result, Shape, Tensor}; use safetensors::{slice::IndexOp, tensor::SafeTensors}; use std::collections::HashMap; -use std::rc::Rc; +use std::sync::Arc; /// A structure used to retrieve variables, these variables can either come from storage or be /// generated via some form of initialization. /// /// The way to retrieve variables is defined in the backend embedded in the `VarBuilder`. pub struct VarBuilderArgs<'a, B: Backend> { - data: Rc>, + data: Arc>, path: Vec, _phantom: std::marker::PhantomData<&'a B>, } @@ -43,7 +43,7 @@ struct TensorData { /// Note that there is a speciliazed version of this trait (`SimpleBackend`) that can be used most /// of the time. The main restriction is that it doesn't allow for specific args (besides /// initialization hints). -pub trait Backend { +pub trait Backend: Send + Sync { type Hints: Default; /// Retrieve a tensor with some target shape. @@ -59,7 +59,7 @@ pub trait Backend { fn contains_tensor(&self, name: &str) -> bool; } -pub trait SimpleBackend { +pub trait SimpleBackend: Send + Sync { /// Retrieve a tensor based on a target name and shape. fn get( &self, @@ -99,7 +99,7 @@ impl<'a, B: Backend> VarBuilderArgs<'a, B> { device: dev.clone(), }; Self { - data: Rc::new(data), + data: Arc::new(data), path: vec![], _phantom: std::marker::PhantomData, } @@ -333,7 +333,7 @@ impl<'a> VarBuilder<'a> { device, }; Self { - data: Rc::new(data), + data: Arc::new(data), path: vec![], _phantom: std::marker::PhantomData, } From 7b1f2da828302ff938af76311ecf68a4ce0d7c2e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 6 Sep 2023 18:39:39 +0200 Subject: [PATCH 023/150] Cudnn fix. (#758) --- candle-core/src/cudnn.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/candle-core/src/cudnn.rs b/candle-core/src/cudnn.rs index 235ad6e3..dd466ba2 100644 --- a/candle-core/src/cudnn.rs +++ b/candle-core/src/cudnn.rs @@ -54,8 +54,8 @@ pub(crate) fn launch_conv2d< let x_shape = [ params.b_size as i32, params.c_in as i32, - params.i_w as i32, params.i_h as i32, + params.i_w as i32, ]; // Note that `src` already starts at the proper offset. let x = if src_l.is_contiguous() { @@ -75,14 +75,14 @@ pub(crate) fn launch_conv2d< [ params.c_out as i32, params.c_in as i32, - params.k_w as i32, params.k_h as i32, + params.k_w as i32, ], )?; let (w_out, h_out) = (params.out_w() as i32, params.out_h() as i32); let y = cudnn.create_4d_tensor( cudarc::cudnn::sys::cudnnTensorFormat_t::CUDNN_TENSOR_NCHW, - [params.b_size as i32, params.c_out as i32, w_out, h_out], + [params.b_size as i32, params.c_out as i32, h_out, w_out], )?; let conv2d = Conv2dForward { conv: &conv, From 6527ab81a3a6f26dae37f6c56eecf0f4bb826f02 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 7 Sep 2023 06:34:05 +0200 Subject: [PATCH 024/150] Sketch the segment anything model. (#759) * Sketch the segment anything model. * Fix some clippy lint. * Add the mask decoder. --- candle-core/src/shape.rs | 11 + .../examples/segment-anything/main.rs | 446 ++++++++++++++++++ 2 files changed, 457 insertions(+) create mode 100644 candle-examples/examples/segment-anything/main.rs diff --git a/candle-core/src/shape.rs b/candle-core/src/shape.rs index db0fe98a..578e8ac9 100644 --- a/candle-core/src/shape.rs +++ b/candle-core/src/shape.rs @@ -425,6 +425,17 @@ impl Dims for (D1, D2, D3, D4) { } } +impl Dims for (D1, D2, D3, D4, D5) { + fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result> { + let d0 = self.0.to_index(shape, op)?; + let d1 = self.1.to_index(shape, op)?; + let d2 = self.2.to_index(shape, op)?; + let d3 = self.3.to_index(shape, op)?; + let d4 = self.4.to_index(shape, op)?; + Ok(vec![d0, d1, d2, d3, d4]) + } +} + extract_dims!(dims0, 0, |_: &[usize]| (), ()); extract_dims!(dims1, 1, |d: &[usize]| d[0], usize); extract_dims!(dims2, 2, |d: &[usize]| (d[0], d[1]), (usize, usize)); diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs new file mode 100644 index 00000000..a53cff8b --- /dev/null +++ b/candle-examples/examples/segment-anything/main.rs @@ -0,0 +1,446 @@ +//! SAM: Segment Anything Model +//! https://github.com/facebookresearch/segment-anything +#![allow(unused)] + +#[cfg(feature = "mkl")] +extern crate intel_mkl_src; + +#[cfg(feature = "accelerate")] +extern crate accelerate_src; + +use clap::Parser; + +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; + +const IMG_SIZE: usize = 518; +const PATCH_SIZE: usize = 14; +const NUM_CLASSES: usize = 1000; + +fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { + if bias { + candle_nn::linear(in_dim, out_dim, vb) + } else { + candle_nn::linear_no_bias(in_dim, out_dim, vb) + } +} + +#[derive(Debug)] +struct MlpBlock { + lin1: Linear, + lin2: Linear, +} + +impl MlpBlock { + fn new(embedding_dim: usize, mlp_dim: usize, vb: VarBuilder) -> Result { + let lin1 = candle_nn::linear(embedding_dim, mlp_dim, vb.pp("lin1"))?; + let lin2 = candle_nn::linear(mlp_dim, embedding_dim, vb.pp("lin2"))?; + Ok(Self { lin1, lin2 }) + } +} + +impl Module for MlpBlock { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.lin1)?.gelu()?.apply(&self.lin2) + } +} + +#[derive(Debug)] +struct PatchEmbed { + proj: candle_nn::Conv2d, +} + +impl PatchEmbed { + fn new( + in_chans: usize, + embed_dim: usize, + k_size: usize, + stride: usize, + padding: usize, + vb: VarBuilder, + ) -> Result { + let cfg = candle_nn::Conv2dConfig { + stride, + padding, + ..Default::default() + }; + let proj = candle_nn::conv2d(in_chans, embed_dim, k_size, cfg, vb.pp("proj"))?; + Ok(Self { proj }) + } +} + +impl Module for PatchEmbed { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.proj)?.permute((0, 2, 3, 1)) + } +} + +#[derive(Debug)] +struct Attention { + qkv: Linear, + proj: Linear, + num_heads: usize, + scale: f64, + use_rel_pos: bool, + rel_pos_hw: Option<(Tensor, Tensor)>, +} + +impl Attention { + fn new( + dim: usize, + num_heads: usize, + qkv_bias: bool, + use_rel_pos: bool, + window_size: usize, + vb: VarBuilder, + ) -> Result { + let qkv = linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; + let proj = linear(vb.pp("proj"), dim, dim, true)?; + let head_dim = dim / num_heads; + let scale = 1. / (head_dim as f64).sqrt(); + let rel_pos_hw = if use_rel_pos { + let h = vb.get((2 * window_size - 1, head_dim), "rel_pos_h")?; + let w = vb.get((2 * window_size - 1, head_dim), "rel_pos_w")?; + Some((h, w)) + } else { + None + }; + Ok(Self { + qkv, + proj, + num_heads, + scale, + use_rel_pos, + rel_pos_hw, + }) + } +} + +impl Module for Attention { + fn forward(&self, xs: &Tensor) -> Result { + let (b, h, w, c) = xs.dims4()?; + let qkv = self + .qkv + .forward(xs)? + .reshape((b, h * w, 3, self.num_heads, c / self.num_heads))? + .permute((2, 0, 3, 1, 4))? + .reshape((3, b * self.num_heads, h * w, c / self.num_heads))?; + let q = qkv.i(0)?; + let k = qkv.i(1)?; + let v = qkv.i(2)?; + let attn = (q * self.scale)?.matmul(&k.t()?)?; + if self.use_rel_pos { + todo!() + } + let attn = candle_nn::ops::softmax_last_dim(&attn)?; + let attn = attn + .matmul(&v)? + .reshape((b, self.num_heads, h, w, c / self.num_heads))? + .permute((0, 2, 3, 1, 4))? + .reshape((b, h, w, c / self.num_heads))?; + self.proj.forward(&attn) + } +} + +#[derive(Debug)] +struct Block { + norm1: LayerNorm, + attn: Attention, + norm2: LayerNorm, + mlp: MlpBlock, + window_size: usize, +} + +impl Block { + fn new( + dim: usize, + num_heads: usize, + qkv_bias: bool, + use_rel_pos: bool, + window_size: usize, + vb: VarBuilder, + ) -> Result { + let norm1 = layer_norm(dim, 1e-5, vb.pp("norm1"))?; + let norm2 = layer_norm(dim, 1e-5, vb.pp("norm2"))?; + let attn = Attention::new( + dim, + num_heads, + qkv_bias, + use_rel_pos, + window_size, + vb.pp("attn"), + )?; + let mlp = MlpBlock::new(dim, dim * 4, vb.pp("mlp"))?; + Ok(Self { + norm1, + attn, + norm2, + mlp, + window_size, + }) + } +} + +impl Module for Block { + fn forward(&self, xs: &Tensor) -> Result { + let shortcut = xs; + let xs = self.norm1.forward(xs)?; + if self.window_size > 0 { + todo!() + } + let xs = self.attn.forward(&xs)?; + if self.window_size > 0 { + todo!() + } + let xs = (xs + shortcut)?; + &xs + xs.apply(&self.norm2)?.apply(&self.mlp)? + } +} + +#[derive(Debug)] +struct ImageEncoderViT { + img_size: usize, + patch_embed: PatchEmbed, + blocks: Vec, + neck_conv1: candle_nn::Conv2d, + neck_ln1: LayerNorm, + neck_conv2: candle_nn::Conv2d, + neck_ln2: LayerNorm, + pos_embed: Option, +} + +impl ImageEncoderViT { + #[allow(clippy::too_many_arguments)] + fn new( + img_size: usize, + patch_size: usize, + in_chans: usize, + embed_dim: usize, + depth: usize, + num_heads: usize, + out_chans: usize, + qkv_bias: bool, + use_rel_pos: bool, + use_abs_pos: bool, + window_size: usize, + vb: VarBuilder, + ) -> Result { + let patch_embed = PatchEmbed::new( + in_chans, + embed_dim, + patch_size, + patch_size, + 0, + vb.pp("patch_embed"), + )?; + let mut blocks = Vec::with_capacity(depth); + let vb_b = vb.pp("blocks"); + for i in 0..depth { + let block = Block::new( + embed_dim, + num_heads, + qkv_bias, + use_rel_pos, + window_size, + vb_b.pp(i), + )?; + blocks.push(block) + } + let neck_conv1 = candle_nn::conv2d_no_bias( + embed_dim, + out_chans, + 1, + Default::default(), + vb.pp("neck.0"), + )?; + let neck_ln1 = layer_norm(out_chans, 1e-6, vb.pp("neck.1"))?; + let cfg = candle_nn::Conv2dConfig { + padding: 1, + ..Default::default() + }; + let neck_conv2 = candle_nn::conv2d_no_bias(out_chans, out_chans, 3, cfg, vb.pp("neck.2"))?; + let neck_ln2 = layer_norm(out_chans, 1e-6, vb.pp("neck.3"))?; + let pos_embed = if use_abs_pos { + let p = vb.get( + (1, img_size / patch_size, img_size / patch_size, embed_dim), + "pos_embed", + )?; + Some(p) + } else { + None + }; + Ok(Self { + img_size, + patch_embed, + blocks, + neck_conv1, + neck_ln1, + neck_conv2, + neck_ln2, + pos_embed, + }) + } +} + +impl Module for ImageEncoderViT { + fn forward(&self, xs: &Tensor) -> Result { + let xs = self.patch_embed.forward(xs)?; + let mut xs = match &self.pos_embed { + Some(pos_embed) => (xs + pos_embed)?, + None => xs, + }; + for block in self.blocks.iter() { + xs = block.forward(&xs)? + } + xs.permute((0, 3, 1, 2))? + .apply(&self.neck_conv1)? + .apply(&self.neck_ln1)? + .apply(&self.neck_conv2)? + .apply(&self.neck_ln2) + } +} + +#[derive(Debug)] +struct MlpMaskDecoder { + layers: Vec, + sigmoid_output: bool, +} + +impl MlpMaskDecoder { + fn new( + input_dim: usize, + hidden_dim: usize, + output_dim: usize, + num_layers: usize, + sigmoid_output: bool, + vb: VarBuilder, + ) -> Result { + let mut layers = Vec::with_capacity(num_layers); + let vb = vb.pp("layers"); + for i in 0..num_layers { + let in_dim = if i == 0 { input_dim } else { hidden_dim }; + let out_dim = if i + 1 == num_layers { + output_dim + } else { + hidden_dim + }; + let layer = linear(vb.pp(i), in_dim, out_dim, true)?; + layers.push(layer) + } + Ok(Self { + layers, + sigmoid_output, + }) + } +} + +impl Module for MlpMaskDecoder { + fn forward(&self, xs: &Tensor) -> Result { + let mut xs = xs.clone(); + for (i, layer) in self.layers.iter().enumerate() { + xs = layer.forward(&xs)?; + if i + 1 < self.layers.len() { + xs = xs.relu()? + } + } + if self.sigmoid_output { + candle_nn::ops::sigmoid(&xs) + } else { + Ok(xs) + } + } +} + +#[derive(Debug)] +struct MaskDecoder { + iou_tokens: candle_nn::Embedding, + mask_tokens: candle_nn::Embedding, + iou_prediction_head: MlpMaskDecoder, +} + +impl MaskDecoder { + fn new( + transformer_dim: usize, + num_multimask_outputs: usize, + iou_head_depth: usize, + iou_head_hidden_dim: usize, + vb: VarBuilder, + ) -> Result { + let num_mask_tokens = num_multimask_outputs - 1; + let iou_prediction_head = MlpMaskDecoder::new( + transformer_dim, + iou_head_hidden_dim, + num_mask_tokens, + iou_head_depth, + false, + vb.pp("iou_prediction_head"), + )?; + let iou_tokens = candle_nn::embedding(1, transformer_dim, vb.pp("iou_tokens"))?; + let mask_tokens = + candle_nn::embedding(num_mask_tokens, transformer_dim, vb.pp("mask_tokens"))?; + Ok(Self { + iou_tokens, + mask_tokens, + iou_prediction_head, + }) + } +} + +/* + fn interpolate_pos_encoding(&self, xs: &Tensor, w: usize, h: usize) -> Result { + let npatch = xs.dim(1)? - 1; + let n = self.pos_embed.dim(1)? - 1; + let sqrt_n = (n as f64).sqrt(); + if npatch == n && w == h { + return Ok(xs.clone()); + } + let class_pos_embed = self.pos_embed.i((.., ..1))?; + let patch_pos_embed = self.pos_embed.i((.., 1..))?; + let dim = xs.dim(D::Minus1)?; + let (w0, h0) = ((w / PATCH_SIZE) as f64 + 0.1, (h / PATCH_SIZE) as f64 + 0.1); + let patch_pos_embed = patch_pos_embed + .reshape((1, sqrt_n as usize, sqrt_n as usize, dim))? + .transpose(2, 3)? + .transpose(1, 2)?; + // This uses bicubic interpolation in the original implementation. + let patch_pos_embed = patch_pos_embed.upsample_nearest2d(h0 as usize, w0 as usize)?; + let el_count = patch_pos_embed.shape().elem_count(); + let patch_pos_embed = + patch_pos_embed + .transpose(1, 2)? + .transpose(2, 3)? + .reshape((1, el_count / dim, dim))?; + Tensor::cat(&[&class_pos_embed, &patch_pos_embed], 1) + } + + fn prepare_tokens_with_mask(&self, xs: &Tensor) -> Result { + let (_b, _nc, w, h) = xs.dims4()?; + let xs = self.patch_embed.forward(xs)?; + let xs = Tensor::cat(&[&self.cls_token, &xs], 1)?; + &xs + &self.interpolate_pos_encoding(&xs, w, h)? + } +*/ + +#[derive(Parser)] +struct Args { + #[arg(long)] + model: Option, + + #[arg(long)] + image: String, + + /// Run on CPU rather than on GPU. + #[arg(long)] + cpu: bool, +} + +pub fn main() -> anyhow::Result<()> { + let args = Args::parse(); + + let _device = candle_examples::device(args.cpu)?; + + let image = candle_examples::imagenet::load_image224(args.image)?; + println!("loaded image {image:?}"); + + Ok(()) +} From a17a7c42c1b95f4d710d95be86efcc2665eadb19 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 7 Sep 2023 06:47:28 +0200 Subject: [PATCH 025/150] Add a nn layer for conv-transpose2d. (#760) --- candle-nn/src/conv.rs | 51 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/candle-nn/src/conv.rs b/candle-nn/src/conv.rs index dbf23aa5..f985cfd6 100644 --- a/candle-nn/src/conv.rs +++ b/candle-nn/src/conv.rs @@ -80,7 +80,6 @@ impl Default for Conv2dConfig { } } -#[allow(dead_code)] #[derive(Debug)] pub struct Conv2d { weight: Tensor, @@ -122,6 +121,56 @@ impl crate::Module for Conv2d { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ConvTranspose2dConfig { + pub padding: usize, + pub output_padding: usize, + pub stride: usize, + pub dilation: usize, + // TODO: support groups. +} + +#[derive(Debug)] +pub struct ConvTranspose2d { + weight: Tensor, + bias: Option, + config: ConvTranspose2dConfig, +} + +impl ConvTranspose2d { + pub fn new(weight: Tensor, bias: Option, config: ConvTranspose2dConfig) -> Self { + Self { + weight, + bias, + config, + } + } + + pub fn config(&self) -> &ConvTranspose2dConfig { + &self.config + } +} + +impl crate::Module for ConvTranspose2d { + fn forward(&self, x: &Tensor) -> Result { + let x = x.conv_transpose2d( + &self.weight, + self.config.padding, + self.config.output_padding, + self.config.stride, + self.config.dilation, + )?; + match &self.bias { + None => Ok(x), + Some(bias) => { + let b = bias.dims1()?; + let bias = bias.reshape((1, b, 1, 1))?; + Ok(x.broadcast_add(&bias)?) + } + } + } +} + pub fn conv1d( in_channels: usize, out_channels: usize, From 000fa00e3171205c217ab7f25602a70d6473f8e9 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 7 Sep 2023 07:04:52 +0200 Subject: [PATCH 026/150] Expose the conv2d-transpose layers. (#761) --- candle-nn/src/conv.rs | 41 +++++++++++++++++++++++++++++++++++++++++ candle-nn/src/lib.rs | 5 ++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/candle-nn/src/conv.rs b/candle-nn/src/conv.rs index f985cfd6..fe44c153 100644 --- a/candle-nn/src/conv.rs +++ b/candle-nn/src/conv.rs @@ -240,3 +240,44 @@ pub fn conv2d_no_bias( )?; Ok(Conv2d::new(ws, None, cfg)) } + +pub fn conv_transpose2d( + in_channels: usize, + out_channels: usize, + kernel_size: usize, + cfg: ConvTranspose2dConfig, + vs: crate::VarBuilder, +) -> Result { + let bound = 1. / (out_channels as f64).sqrt() / kernel_size as f64; + let init = crate::Init::Uniform { + lo: -bound, + up: bound, + }; + let ws = vs.get_with_hints( + (in_channels, out_channels, kernel_size, kernel_size), + "weight", + init, + )?; + let bs = vs.get_with_hints(out_channels, "bias", init)?; + Ok(ConvTranspose2d::new(ws, Some(bs), cfg)) +} + +pub fn conv_transpose2d_no_bias( + in_channels: usize, + out_channels: usize, + kernel_size: usize, + cfg: ConvTranspose2dConfig, + vs: crate::VarBuilder, +) -> Result { + let bound = 1. / (out_channels as f64).sqrt() / kernel_size as f64; + let init = crate::Init::Uniform { + lo: -bound, + up: bound, + }; + let ws = vs.get_with_hints( + (out_channels, in_channels, kernel_size, kernel_size), + "weight", + init, + )?; + Ok(ConvTranspose2d::new(ws, None, cfg)) +} diff --git a/candle-nn/src/lib.rs b/candle-nn/src/lib.rs index 6e268f4e..8e5580df 100644 --- a/candle-nn/src/lib.rs +++ b/candle-nn/src/lib.rs @@ -16,7 +16,10 @@ pub mod var_map; pub use activation::Activation; pub use batch_norm::{batch_norm, BatchNorm, BatchNormConfig}; -pub use conv::{conv1d, conv2d, conv2d_no_bias, Conv1d, Conv1dConfig, Conv2d, Conv2dConfig}; +pub use conv::{ + conv1d, conv2d, conv2d_no_bias, conv_transpose2d, conv_transpose2d_no_bias, Conv1d, + Conv1dConfig, Conv2d, Conv2dConfig, ConvTranspose2d, ConvTranspose2dConfig, +}; pub use embedding::{embedding, Embedding}; pub use func::{func, Func}; pub use group_norm::{group_norm, GroupNorm}; From 8c991df3945a7c86ae86a7a52a74639ec321cef2 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 7 Sep 2023 08:28:30 +0200 Subject: [PATCH 027/150] More segment-anything. (#763) * More segment-anything. * Split the model in multiple files. * Start adding the transformer. * Add the attention block. * Move the MLP Block. --- .../examples/segment-anything/main.rs | 356 +----------------- .../segment-anything/model_image_encoder.rs | 257 +++++++++++++ .../segment-anything/model_mask_decoder.rs | 222 +++++++++++ .../segment-anything/model_transformer.rs | 77 ++++ candle-nn/src/conv.rs | 11 + 5 files changed, 574 insertions(+), 349 deletions(-) create mode 100644 candle-examples/examples/segment-anything/model_image_encoder.rs create mode 100644 candle-examples/examples/segment-anything/model_mask_decoder.rs create mode 100644 candle-examples/examples/segment-anything/model_transformer.rs diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index a53cff8b..de16f70c 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -8,16 +8,15 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; -use clap::Parser; +mod model_image_encoder; +mod model_mask_decoder; +mod model_transformer; use candle::{DType, IndexOp, Result, Tensor, D}; use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use clap::Parser; -const IMG_SIZE: usize = 518; -const PATCH_SIZE: usize = 14; -const NUM_CLASSES: usize = 1000; - -fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { +pub fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { if bias { candle_nn::linear(in_dim, out_dim, vb) } else { @@ -26,13 +25,13 @@ fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result Result { + pub fn new(embedding_dim: usize, mlp_dim: usize, vb: VarBuilder) -> Result { let lin1 = candle_nn::linear(embedding_dim, mlp_dim, vb.pp("lin1"))?; let lin2 = candle_nn::linear(mlp_dim, embedding_dim, vb.pp("lin2"))?; Ok(Self { lin1, lin2 }) @@ -45,347 +44,6 @@ impl Module for MlpBlock { } } -#[derive(Debug)] -struct PatchEmbed { - proj: candle_nn::Conv2d, -} - -impl PatchEmbed { - fn new( - in_chans: usize, - embed_dim: usize, - k_size: usize, - stride: usize, - padding: usize, - vb: VarBuilder, - ) -> Result { - let cfg = candle_nn::Conv2dConfig { - stride, - padding, - ..Default::default() - }; - let proj = candle_nn::conv2d(in_chans, embed_dim, k_size, cfg, vb.pp("proj"))?; - Ok(Self { proj }) - } -} - -impl Module for PatchEmbed { - fn forward(&self, xs: &Tensor) -> Result { - xs.apply(&self.proj)?.permute((0, 2, 3, 1)) - } -} - -#[derive(Debug)] -struct Attention { - qkv: Linear, - proj: Linear, - num_heads: usize, - scale: f64, - use_rel_pos: bool, - rel_pos_hw: Option<(Tensor, Tensor)>, -} - -impl Attention { - fn new( - dim: usize, - num_heads: usize, - qkv_bias: bool, - use_rel_pos: bool, - window_size: usize, - vb: VarBuilder, - ) -> Result { - let qkv = linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; - let proj = linear(vb.pp("proj"), dim, dim, true)?; - let head_dim = dim / num_heads; - let scale = 1. / (head_dim as f64).sqrt(); - let rel_pos_hw = if use_rel_pos { - let h = vb.get((2 * window_size - 1, head_dim), "rel_pos_h")?; - let w = vb.get((2 * window_size - 1, head_dim), "rel_pos_w")?; - Some((h, w)) - } else { - None - }; - Ok(Self { - qkv, - proj, - num_heads, - scale, - use_rel_pos, - rel_pos_hw, - }) - } -} - -impl Module for Attention { - fn forward(&self, xs: &Tensor) -> Result { - let (b, h, w, c) = xs.dims4()?; - let qkv = self - .qkv - .forward(xs)? - .reshape((b, h * w, 3, self.num_heads, c / self.num_heads))? - .permute((2, 0, 3, 1, 4))? - .reshape((3, b * self.num_heads, h * w, c / self.num_heads))?; - let q = qkv.i(0)?; - let k = qkv.i(1)?; - let v = qkv.i(2)?; - let attn = (q * self.scale)?.matmul(&k.t()?)?; - if self.use_rel_pos { - todo!() - } - let attn = candle_nn::ops::softmax_last_dim(&attn)?; - let attn = attn - .matmul(&v)? - .reshape((b, self.num_heads, h, w, c / self.num_heads))? - .permute((0, 2, 3, 1, 4))? - .reshape((b, h, w, c / self.num_heads))?; - self.proj.forward(&attn) - } -} - -#[derive(Debug)] -struct Block { - norm1: LayerNorm, - attn: Attention, - norm2: LayerNorm, - mlp: MlpBlock, - window_size: usize, -} - -impl Block { - fn new( - dim: usize, - num_heads: usize, - qkv_bias: bool, - use_rel_pos: bool, - window_size: usize, - vb: VarBuilder, - ) -> Result { - let norm1 = layer_norm(dim, 1e-5, vb.pp("norm1"))?; - let norm2 = layer_norm(dim, 1e-5, vb.pp("norm2"))?; - let attn = Attention::new( - dim, - num_heads, - qkv_bias, - use_rel_pos, - window_size, - vb.pp("attn"), - )?; - let mlp = MlpBlock::new(dim, dim * 4, vb.pp("mlp"))?; - Ok(Self { - norm1, - attn, - norm2, - mlp, - window_size, - }) - } -} - -impl Module for Block { - fn forward(&self, xs: &Tensor) -> Result { - let shortcut = xs; - let xs = self.norm1.forward(xs)?; - if self.window_size > 0 { - todo!() - } - let xs = self.attn.forward(&xs)?; - if self.window_size > 0 { - todo!() - } - let xs = (xs + shortcut)?; - &xs + xs.apply(&self.norm2)?.apply(&self.mlp)? - } -} - -#[derive(Debug)] -struct ImageEncoderViT { - img_size: usize, - patch_embed: PatchEmbed, - blocks: Vec, - neck_conv1: candle_nn::Conv2d, - neck_ln1: LayerNorm, - neck_conv2: candle_nn::Conv2d, - neck_ln2: LayerNorm, - pos_embed: Option, -} - -impl ImageEncoderViT { - #[allow(clippy::too_many_arguments)] - fn new( - img_size: usize, - patch_size: usize, - in_chans: usize, - embed_dim: usize, - depth: usize, - num_heads: usize, - out_chans: usize, - qkv_bias: bool, - use_rel_pos: bool, - use_abs_pos: bool, - window_size: usize, - vb: VarBuilder, - ) -> Result { - let patch_embed = PatchEmbed::new( - in_chans, - embed_dim, - patch_size, - patch_size, - 0, - vb.pp("patch_embed"), - )?; - let mut blocks = Vec::with_capacity(depth); - let vb_b = vb.pp("blocks"); - for i in 0..depth { - let block = Block::new( - embed_dim, - num_heads, - qkv_bias, - use_rel_pos, - window_size, - vb_b.pp(i), - )?; - blocks.push(block) - } - let neck_conv1 = candle_nn::conv2d_no_bias( - embed_dim, - out_chans, - 1, - Default::default(), - vb.pp("neck.0"), - )?; - let neck_ln1 = layer_norm(out_chans, 1e-6, vb.pp("neck.1"))?; - let cfg = candle_nn::Conv2dConfig { - padding: 1, - ..Default::default() - }; - let neck_conv2 = candle_nn::conv2d_no_bias(out_chans, out_chans, 3, cfg, vb.pp("neck.2"))?; - let neck_ln2 = layer_norm(out_chans, 1e-6, vb.pp("neck.3"))?; - let pos_embed = if use_abs_pos { - let p = vb.get( - (1, img_size / patch_size, img_size / patch_size, embed_dim), - "pos_embed", - )?; - Some(p) - } else { - None - }; - Ok(Self { - img_size, - patch_embed, - blocks, - neck_conv1, - neck_ln1, - neck_conv2, - neck_ln2, - pos_embed, - }) - } -} - -impl Module for ImageEncoderViT { - fn forward(&self, xs: &Tensor) -> Result { - let xs = self.patch_embed.forward(xs)?; - let mut xs = match &self.pos_embed { - Some(pos_embed) => (xs + pos_embed)?, - None => xs, - }; - for block in self.blocks.iter() { - xs = block.forward(&xs)? - } - xs.permute((0, 3, 1, 2))? - .apply(&self.neck_conv1)? - .apply(&self.neck_ln1)? - .apply(&self.neck_conv2)? - .apply(&self.neck_ln2) - } -} - -#[derive(Debug)] -struct MlpMaskDecoder { - layers: Vec, - sigmoid_output: bool, -} - -impl MlpMaskDecoder { - fn new( - input_dim: usize, - hidden_dim: usize, - output_dim: usize, - num_layers: usize, - sigmoid_output: bool, - vb: VarBuilder, - ) -> Result { - let mut layers = Vec::with_capacity(num_layers); - let vb = vb.pp("layers"); - for i in 0..num_layers { - let in_dim = if i == 0 { input_dim } else { hidden_dim }; - let out_dim = if i + 1 == num_layers { - output_dim - } else { - hidden_dim - }; - let layer = linear(vb.pp(i), in_dim, out_dim, true)?; - layers.push(layer) - } - Ok(Self { - layers, - sigmoid_output, - }) - } -} - -impl Module for MlpMaskDecoder { - fn forward(&self, xs: &Tensor) -> Result { - let mut xs = xs.clone(); - for (i, layer) in self.layers.iter().enumerate() { - xs = layer.forward(&xs)?; - if i + 1 < self.layers.len() { - xs = xs.relu()? - } - } - if self.sigmoid_output { - candle_nn::ops::sigmoid(&xs) - } else { - Ok(xs) - } - } -} - -#[derive(Debug)] -struct MaskDecoder { - iou_tokens: candle_nn::Embedding, - mask_tokens: candle_nn::Embedding, - iou_prediction_head: MlpMaskDecoder, -} - -impl MaskDecoder { - fn new( - transformer_dim: usize, - num_multimask_outputs: usize, - iou_head_depth: usize, - iou_head_hidden_dim: usize, - vb: VarBuilder, - ) -> Result { - let num_mask_tokens = num_multimask_outputs - 1; - let iou_prediction_head = MlpMaskDecoder::new( - transformer_dim, - iou_head_hidden_dim, - num_mask_tokens, - iou_head_depth, - false, - vb.pp("iou_prediction_head"), - )?; - let iou_tokens = candle_nn::embedding(1, transformer_dim, vb.pp("iou_tokens"))?; - let mask_tokens = - candle_nn::embedding(num_mask_tokens, transformer_dim, vb.pp("mask_tokens"))?; - Ok(Self { - iou_tokens, - mask_tokens, - iou_prediction_head, - }) - } -} - /* fn interpolate_pos_encoding(&self, xs: &Tensor, w: usize, h: usize) -> Result { let npatch = xs.dim(1)? - 1; diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-examples/examples/segment-anything/model_image_encoder.rs new file mode 100644 index 00000000..c8b6fd7b --- /dev/null +++ b/candle-examples/examples/segment-anything/model_image_encoder.rs @@ -0,0 +1,257 @@ +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; + +#[derive(Debug)] +struct PatchEmbed { + proj: candle_nn::Conv2d, +} + +impl PatchEmbed { + fn new( + in_chans: usize, + embed_dim: usize, + k_size: usize, + stride: usize, + padding: usize, + vb: VarBuilder, + ) -> Result { + let cfg = candle_nn::Conv2dConfig { + stride, + padding, + ..Default::default() + }; + let proj = candle_nn::conv2d(in_chans, embed_dim, k_size, cfg, vb.pp("proj"))?; + Ok(Self { proj }) + } +} + +impl Module for PatchEmbed { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.proj)?.permute((0, 2, 3, 1)) + } +} + +#[derive(Debug)] +struct Attention { + qkv: Linear, + proj: Linear, + num_heads: usize, + scale: f64, + use_rel_pos: bool, + rel_pos_hw: Option<(Tensor, Tensor)>, +} + +impl Attention { + fn new( + dim: usize, + num_heads: usize, + qkv_bias: bool, + use_rel_pos: bool, + window_size: usize, + vb: VarBuilder, + ) -> Result { + let qkv = crate::linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; + let proj = crate::linear(vb.pp("proj"), dim, dim, true)?; + let head_dim = dim / num_heads; + let scale = 1. / (head_dim as f64).sqrt(); + let rel_pos_hw = if use_rel_pos { + let h = vb.get((2 * window_size - 1, head_dim), "rel_pos_h")?; + let w = vb.get((2 * window_size - 1, head_dim), "rel_pos_w")?; + Some((h, w)) + } else { + None + }; + Ok(Self { + qkv, + proj, + num_heads, + scale, + use_rel_pos, + rel_pos_hw, + }) + } +} + +impl Module for Attention { + fn forward(&self, xs: &Tensor) -> Result { + let (b, h, w, c) = xs.dims4()?; + let qkv = self + .qkv + .forward(xs)? + .reshape((b, h * w, 3, self.num_heads, c / self.num_heads))? + .permute((2, 0, 3, 1, 4))? + .reshape((3, b * self.num_heads, h * w, c / self.num_heads))?; + let q = qkv.i(0)?; + let k = qkv.i(1)?; + let v = qkv.i(2)?; + let attn = (q * self.scale)?.matmul(&k.t()?)?; + if self.use_rel_pos { + todo!() + } + let attn = candle_nn::ops::softmax_last_dim(&attn)?; + let attn = attn + .matmul(&v)? + .reshape((b, self.num_heads, h, w, c / self.num_heads))? + .permute((0, 2, 3, 1, 4))? + .reshape((b, h, w, c / self.num_heads))?; + self.proj.forward(&attn) + } +} + +#[derive(Debug)] +struct Block { + norm1: LayerNorm, + attn: Attention, + norm2: LayerNorm, + mlp: crate::MlpBlock, + window_size: usize, +} + +impl Block { + fn new( + dim: usize, + num_heads: usize, + qkv_bias: bool, + use_rel_pos: bool, + window_size: usize, + vb: VarBuilder, + ) -> Result { + let norm1 = layer_norm(dim, 1e-5, vb.pp("norm1"))?; + let norm2 = layer_norm(dim, 1e-5, vb.pp("norm2"))?; + let attn = Attention::new( + dim, + num_heads, + qkv_bias, + use_rel_pos, + window_size, + vb.pp("attn"), + )?; + let mlp = crate::MlpBlock::new(dim, dim * 4, vb.pp("mlp"))?; + Ok(Self { + norm1, + attn, + norm2, + mlp, + window_size, + }) + } +} + +impl Module for Block { + fn forward(&self, xs: &Tensor) -> Result { + let shortcut = xs; + let xs = self.norm1.forward(xs)?; + if self.window_size > 0 { + todo!() + } + let xs = self.attn.forward(&xs)?; + if self.window_size > 0 { + todo!() + } + let xs = (xs + shortcut)?; + &xs + xs.apply(&self.norm2)?.apply(&self.mlp)? + } +} + +#[derive(Debug)] +struct ImageEncoderViT { + img_size: usize, + patch_embed: PatchEmbed, + blocks: Vec, + neck_conv1: candle_nn::Conv2d, + neck_ln1: LayerNorm, + neck_conv2: candle_nn::Conv2d, + neck_ln2: LayerNorm, + pos_embed: Option, +} + +impl ImageEncoderViT { + #[allow(clippy::too_many_arguments)] + fn new( + img_size: usize, + patch_size: usize, + in_chans: usize, + embed_dim: usize, + depth: usize, + num_heads: usize, + out_chans: usize, + qkv_bias: bool, + use_rel_pos: bool, + use_abs_pos: bool, + window_size: usize, + vb: VarBuilder, + ) -> Result { + let patch_embed = PatchEmbed::new( + in_chans, + embed_dim, + patch_size, + patch_size, + 0, + vb.pp("patch_embed"), + )?; + let mut blocks = Vec::with_capacity(depth); + let vb_b = vb.pp("blocks"); + for i in 0..depth { + let block = Block::new( + embed_dim, + num_heads, + qkv_bias, + use_rel_pos, + window_size, + vb_b.pp(i), + )?; + blocks.push(block) + } + let neck_conv1 = candle_nn::conv2d_no_bias( + embed_dim, + out_chans, + 1, + Default::default(), + vb.pp("neck.0"), + )?; + let neck_ln1 = layer_norm(out_chans, 1e-6, vb.pp("neck.1"))?; + let cfg = candle_nn::Conv2dConfig { + padding: 1, + ..Default::default() + }; + let neck_conv2 = candle_nn::conv2d_no_bias(out_chans, out_chans, 3, cfg, vb.pp("neck.2"))?; + let neck_ln2 = layer_norm(out_chans, 1e-6, vb.pp("neck.3"))?; + let pos_embed = if use_abs_pos { + let p = vb.get( + (1, img_size / patch_size, img_size / patch_size, embed_dim), + "pos_embed", + )?; + Some(p) + } else { + None + }; + Ok(Self { + img_size, + patch_embed, + blocks, + neck_conv1, + neck_ln1, + neck_conv2, + neck_ln2, + pos_embed, + }) + } +} + +impl Module for ImageEncoderViT { + fn forward(&self, xs: &Tensor) -> Result { + let xs = self.patch_embed.forward(xs)?; + let mut xs = match &self.pos_embed { + Some(pos_embed) => (xs + pos_embed)?, + None => xs, + }; + for block in self.blocks.iter() { + xs = block.forward(&xs)? + } + xs.permute((0, 3, 1, 2))? + .apply(&self.neck_conv1)? + .apply(&self.neck_ln1)? + .apply(&self.neck_conv2)? + .apply(&self.neck_ln2) + } +} diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-examples/examples/segment-anything/model_mask_decoder.rs new file mode 100644 index 00000000..55a006c4 --- /dev/null +++ b/candle-examples/examples/segment-anything/model_mask_decoder.rs @@ -0,0 +1,222 @@ +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; + +#[derive(Debug)] +struct MlpMaskDecoder { + layers: Vec, + sigmoid_output: bool, +} + +impl MlpMaskDecoder { + fn new( + input_dim: usize, + hidden_dim: usize, + output_dim: usize, + num_layers: usize, + sigmoid_output: bool, + vb: VarBuilder, + ) -> Result { + let mut layers = Vec::with_capacity(num_layers); + let vb = vb.pp("layers"); + for i in 0..num_layers { + let in_dim = if i == 0 { input_dim } else { hidden_dim }; + let out_dim = if i + 1 == num_layers { + output_dim + } else { + hidden_dim + }; + let layer = crate::linear(vb.pp(i), in_dim, out_dim, true)?; + layers.push(layer) + } + Ok(Self { + layers, + sigmoid_output, + }) + } +} + +impl Module for MlpMaskDecoder { + fn forward(&self, xs: &Tensor) -> Result { + let mut xs = xs.clone(); + for (i, layer) in self.layers.iter().enumerate() { + xs = layer.forward(&xs)?; + if i + 1 < self.layers.len() { + xs = xs.relu()? + } + } + if self.sigmoid_output { + candle_nn::ops::sigmoid(&xs) + } else { + Ok(xs) + } + } +} + +#[derive(Debug)] +struct MaskDecoder { + iou_token: candle_nn::Embedding, + mask_tokens: candle_nn::Embedding, + iou_prediction_head: MlpMaskDecoder, + output_upscaling_conv1: candle_nn::ConvTranspose2d, + output_upscaling_ln: LayerNorm, + output_upscaling_conv2: candle_nn::ConvTranspose2d, + num_mask_tokens: usize, + output_hypernetworks_mlps: Vec, +} + +impl MaskDecoder { + fn new( + transformer_dim: usize, + num_multimask_outputs: usize, + iou_head_depth: usize, + iou_head_hidden_dim: usize, + vb: VarBuilder, + ) -> Result { + let num_mask_tokens = num_multimask_outputs - 1; + let iou_prediction_head = MlpMaskDecoder::new( + transformer_dim, + iou_head_hidden_dim, + num_mask_tokens, + iou_head_depth, + false, + vb.pp("iou_prediction_head"), + )?; + let iou_token = candle_nn::embedding(1, transformer_dim, vb.pp("iou_token"))?; + let mask_tokens = + candle_nn::embedding(num_mask_tokens, transformer_dim, vb.pp("mask_tokens"))?; + let cfg = candle_nn::ConvTranspose2dConfig { + stride: 2, + ..Default::default() + }; + let output_upscaling_conv1 = candle_nn::conv_transpose2d( + transformer_dim, + transformer_dim / 4, + 2, + cfg, + vb.pp("output_upscaling.0"), + )?; + let output_upscaling_ln = + layer_norm(transformer_dim / 4, 1e-6, vb.pp("output_upscaling.1"))?; + let output_upscaling_conv2 = candle_nn::conv_transpose2d( + transformer_dim / 4, + transformer_dim / 8, + 2, + cfg, + vb.pp("output_upscaling.3"), + )?; + let mut output_hypernetworks_mlps = Vec::with_capacity(num_mask_tokens); + let vb_o = vb.pp("output_hypernetworks_mlps"); + for i in 0..num_mask_tokens { + let mlp = MlpMaskDecoder::new( + transformer_dim, + transformer_dim, + transformer_dim / 8, + 3, + false, + vb_o.pp(i), + )?; + output_hypernetworks_mlps.push(mlp) + } + Ok(Self { + iou_token, + mask_tokens, + iou_prediction_head, + output_upscaling_conv1, + output_upscaling_ln, + output_upscaling_conv2, + num_mask_tokens, + output_hypernetworks_mlps, + }) + } + + fn forward( + &self, + image_embeddings: &Tensor, + image_pe: &Tensor, + sparse_prompt_embeddings: &Tensor, + dense_prompt_embeddings: &Tensor, + multimask_output: bool, + ) -> Result<(Tensor, Tensor)> { + let (masks, iou_pred) = self.predict_masks( + image_embeddings, + image_pe, + sparse_prompt_embeddings, + dense_prompt_embeddings, + )?; + let masks = if multimask_output { + masks.i((.., 1..))? + } else { + masks.i((.., 0..1))? + }; + let iou_pred = if multimask_output { + iou_pred.i((.., 1..))? + } else { + iou_pred.i((.., 0..1))? + }; + Ok((masks, iou_pred)) + } + + fn predict_masks( + &self, + image_embeddings: &Tensor, + image_pe: &Tensor, + sparse_prompt_embeddings: &Tensor, + dense_prompt_embeddings: &Tensor, + ) -> Result<(Tensor, Tensor)> { + // Concatenate ouput tokens. + let output_tokens = Tensor::cat( + &[self.iou_token.embeddings(), self.mask_tokens.embeddings()], + 0, + )?; + let (d1, d2) = output_tokens.dims2()?; + let output_tokens = + output_tokens + .unsqueeze(0)? + .expand((sparse_prompt_embeddings.dim(0)?, d1, d2))?; + let tokens = Tensor::cat(&[&output_tokens, sparse_prompt_embeddings], 1)?; + + // Expand per-image data in batch direction to be per mask + let src = repeat_interleave(image_embeddings, tokens.dim(0)?, 0)?; + let src = (src + dense_prompt_embeddings)?; + let pos_src = repeat_interleave(image_pe, tokens.dim(0)?, 0)?; + let (b, c, h, w) = src.dims4()?; + + // Run the transformer + let (hs, src) = run_transformer(&src, &pos_src, &tokens)?; + let iou_token_out = hs.i((.., 0))?; + let mask_tokens_out = hs.i((.., 1, 1 + self.num_mask_tokens))?; + + // Upscale mask embeddings and predict masks using the masks tokens. + let src = src.transpose(1, 2)?.reshape((b, c, h, w))?; + let upscaled_embedding = self + .output_upscaling_conv1 + .forward(&src)? + .apply(&self.output_upscaling_ln)? + .gelu()? + .apply(&self.output_upscaling_conv2)? + .gelu()?; + let mut hyper_in_list = Vec::with_capacity(self.num_mask_tokens); + for (i, mlp) in self.output_hypernetworks_mlps.iter().enumerate() { + let h = mlp.forward(&mask_tokens_out.i((.., i))?)?; + hyper_in_list.push(h) + } + let hyper_in = Tensor::stack(hyper_in_list.as_slice(), 1)?; + let (b, c, h, w) = upscaled_embedding.dims4()?; + let masks = hyper_in + .matmul(&upscaled_embedding.reshape((b, c, h * w))?)? + .reshape((b, 0, h, w))?; + + // Generate mask quality predictions. + let iou_pred = self.iou_prediction_head.forward(&iou_token_out)?; + Ok((masks, iou_pred)) + } +} + +// Equivalent to torch.repeat_interleave +fn repeat_interleave(_img: &Tensor, _repeats: usize, _dim: usize) -> Result { + todo!() +} + +fn run_transformer(_src: &Tensor, _pos: &Tensor, _tokens: &Tensor) -> Result<(Tensor, Tensor)> { + todo!() +} diff --git a/candle-examples/examples/segment-anything/model_transformer.rs b/candle-examples/examples/segment-anything/model_transformer.rs new file mode 100644 index 00000000..10f7f4e5 --- /dev/null +++ b/candle-examples/examples/segment-anything/model_transformer.rs @@ -0,0 +1,77 @@ +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; + +#[derive(Debug)] +struct Attention { + q_proj: Linear, + k_proj: Linear, + v_proj: Linear, + out_proj: Linear, + internal_dim: usize, + num_heads: usize, +} + +impl Attention { + fn new( + embedding_dim: usize, + num_heads: usize, + downsample_rate: usize, + vb: VarBuilder, + ) -> Result { + let internal_dim = embedding_dim / downsample_rate; + let q_proj = candle_nn::linear(embedding_dim, internal_dim, vb.pp("q_proj"))?; + let k_proj = candle_nn::linear(embedding_dim, internal_dim, vb.pp("k_proj"))?; + let v_proj = candle_nn::linear(embedding_dim, internal_dim, vb.pp("v_proj"))?; + let out_proj = candle_nn::linear(internal_dim, embedding_dim, vb.pp("out_proj"))?; + Ok(Self { + q_proj, + k_proj, + v_proj, + out_proj, + internal_dim, + num_heads, + }) + } + + fn separate_heads(&self, x: &Tensor) -> Result { + let (b, n, c) = x.dims3()?; + x.reshape((b, n, self.num_heads, c / self.num_heads))? + .transpose(1, 2) + } + + fn recombine_heads(&self, x: &Tensor) -> Result { + let (b, n_heads, n_tokens, c_per_head) = x.dims4()?; + x.transpose(1, 2)? + .reshape((b, n_tokens, n_heads * c_per_head)) + } + + fn forward(&self, q: &Tensor, k: &Tensor, v: &Tensor) -> Result { + let q = self.q_proj.forward(q)?; + let k = self.k_proj.forward(k)?; + let v = self.v_proj.forward(v)?; + + let q = self.separate_heads(&q)?; + let k = self.separate_heads(&k)?; + let v = self.separate_heads(&v)?; + + let (_, _, _, c_per_head) = q.dims4()?; + let attn = (q.matmul(&k.t()?)? / (c_per_head as f64).sqrt())?; + let attn = candle_nn::ops::softmax_last_dim(&attn)?; + + let out = attn.matmul(&v)?; + self.recombine_heads(&out)?.apply(&self.out_proj) + } +} + +#[derive(Debug)] +struct TwoWayAttentionBlock { + self_attn: Attention, + norm1: LayerNorm, + cross_attn_token_to_image: Attention, + norm2: LayerNorm, + mlp: crate::MlpBlock, + norm3: LayerNorm, + norm4: LayerNorm, + cross_attn_image_to_token: Attention, + skip_first_layer_pe: bool, +} diff --git a/candle-nn/src/conv.rs b/candle-nn/src/conv.rs index fe44c153..b2483058 100644 --- a/candle-nn/src/conv.rs +++ b/candle-nn/src/conv.rs @@ -130,6 +130,17 @@ pub struct ConvTranspose2dConfig { // TODO: support groups. } +impl Default for ConvTranspose2dConfig { + fn default() -> Self { + Self { + padding: 0, + output_padding: 0, + stride: 1, + dilation: 1, + } + } +} + #[derive(Debug)] pub struct ConvTranspose2d { weight: Tensor, From 7b50f3e106b3d3a333e1c67f2006cbfd60c8a55a Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 7 Sep 2023 13:06:55 +0200 Subject: [PATCH 028/150] More segment-anything again. (#764) * More segment-anything again. * Transformer block forward. * Two-ways transformer. * Position embeddings. * Sketch the prompt encoder. * More prompt-encoder. * More prompt-encoder. * Add the main sam module. * Embed the transformer. * And hook the transformer forward step. * Build the model. * Handle the global attn indexes. * Get the model to load. --- .../examples/segment-anything/main.rs | 19 +- .../segment-anything/model_image_encoder.rs | 25 ++- .../segment-anything/model_mask_decoder.rs | 23 ++- .../segment-anything/model_prompt_encoder.rs | 192 ++++++++++++++++++ .../examples/segment-anything/model_sam.rs | 72 +++++++ .../segment-anything/model_transformer.rs | 143 +++++++++++++ 6 files changed, 454 insertions(+), 20 deletions(-) create mode 100644 candle-examples/examples/segment-anything/model_prompt_encoder.rs create mode 100644 candle-examples/examples/segment-anything/model_sam.rs diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index de16f70c..368b5a33 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -8,9 +8,11 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; -mod model_image_encoder; -mod model_mask_decoder; -mod model_transformer; +pub mod model_image_encoder; +pub mod model_mask_decoder; +pub mod model_prompt_encoder; +pub mod model_sam; +pub mod model_transformer; use candle::{DType, IndexOp, Result, Tensor, D}; use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; @@ -82,7 +84,7 @@ impl Module for MlpBlock { #[derive(Parser)] struct Args { #[arg(long)] - model: Option, + model: String, #[arg(long)] image: String, @@ -95,10 +97,15 @@ struct Args { pub fn main() -> anyhow::Result<()> { let args = Args::parse(); - let _device = candle_examples::device(args.cpu)?; + let device = candle_examples::device(args.cpu)?; - let image = candle_examples::imagenet::load_image224(args.image)?; + let image = candle_examples::imagenet::load_image224(args.image)?.to_device(&device); println!("loaded image {image:?}"); + let weights = unsafe { candle::safetensors::MmapedFile::new(args.model)? }; + let weights = weights.deserialize()?; + let vb = VarBuilder::from_safetensors(vec![weights], DType::F32, &device); + let _sam = model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)?; // sam_vit_b + Ok(()) } diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-examples/examples/segment-anything/model_image_encoder.rs index c8b6fd7b..cfcdbb38 100644 --- a/candle-examples/examples/segment-anything/model_image_encoder.rs +++ b/candle-examples/examples/segment-anything/model_image_encoder.rs @@ -47,7 +47,7 @@ impl Attention { num_heads: usize, qkv_bias: bool, use_rel_pos: bool, - window_size: usize, + input_size: (usize, usize), vb: VarBuilder, ) -> Result { let qkv = crate::linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; @@ -55,8 +55,8 @@ impl Attention { let head_dim = dim / num_heads; let scale = 1. / (head_dim as f64).sqrt(); let rel_pos_hw = if use_rel_pos { - let h = vb.get((2 * window_size - 1, head_dim), "rel_pos_h")?; - let w = vb.get((2 * window_size - 1, head_dim), "rel_pos_w")?; + let h = vb.get((2 * input_size.0 - 1, head_dim), "rel_pos_h")?; + let w = vb.get((2 * input_size.1 - 1, head_dim), "rel_pos_w")?; Some((h, w)) } else { None @@ -114,16 +114,22 @@ impl Block { qkv_bias: bool, use_rel_pos: bool, window_size: usize, + input_size: (usize, usize), vb: VarBuilder, ) -> Result { let norm1 = layer_norm(dim, 1e-5, vb.pp("norm1"))?; let norm2 = layer_norm(dim, 1e-5, vb.pp("norm2"))?; + let input_size_attn = if window_size == 0 { + input_size + } else { + (window_size, window_size) + }; let attn = Attention::new( dim, num_heads, qkv_bias, use_rel_pos, - window_size, + input_size_attn, vb.pp("attn"), )?; let mlp = crate::MlpBlock::new(dim, dim * 4, vb.pp("mlp"))?; @@ -154,7 +160,7 @@ impl Module for Block { } #[derive(Debug)] -struct ImageEncoderViT { +pub struct ImageEncoderViT { img_size: usize, patch_embed: PatchEmbed, blocks: Vec, @@ -167,7 +173,7 @@ struct ImageEncoderViT { impl ImageEncoderViT { #[allow(clippy::too_many_arguments)] - fn new( + pub fn new( img_size: usize, patch_size: usize, in_chans: usize, @@ -179,6 +185,7 @@ impl ImageEncoderViT { use_rel_pos: bool, use_abs_pos: bool, window_size: usize, + global_attn_indexes: &[usize], vb: VarBuilder, ) -> Result { let patch_embed = PatchEmbed::new( @@ -192,12 +199,18 @@ impl ImageEncoderViT { let mut blocks = Vec::with_capacity(depth); let vb_b = vb.pp("blocks"); for i in 0..depth { + let window_size = if global_attn_indexes.contains(&i) { + 0 + } else { + window_size + }; let block = Block::new( embed_dim, num_heads, qkv_bias, use_rel_pos, window_size, + (img_size / patch_size, img_size / patch_size), vb_b.pp(i), )?; blocks.push(block) diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-examples/examples/segment-anything/model_mask_decoder.rs index 55a006c4..cf3879cd 100644 --- a/candle-examples/examples/segment-anything/model_mask_decoder.rs +++ b/candle-examples/examples/segment-anything/model_mask_decoder.rs @@ -1,6 +1,8 @@ use candle::{DType, IndexOp, Result, Tensor, D}; use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use crate::model_transformer::TwoWayTransformer; + #[derive(Debug)] struct MlpMaskDecoder { layers: Vec, @@ -53,7 +55,7 @@ impl Module for MlpMaskDecoder { } #[derive(Debug)] -struct MaskDecoder { +pub struct MaskDecoder { iou_token: candle_nn::Embedding, mask_tokens: candle_nn::Embedding, iou_prediction_head: MlpMaskDecoder, @@ -62,17 +64,18 @@ struct MaskDecoder { output_upscaling_conv2: candle_nn::ConvTranspose2d, num_mask_tokens: usize, output_hypernetworks_mlps: Vec, + transformer: TwoWayTransformer, } impl MaskDecoder { - fn new( + pub fn new( transformer_dim: usize, num_multimask_outputs: usize, iou_head_depth: usize, iou_head_hidden_dim: usize, vb: VarBuilder, ) -> Result { - let num_mask_tokens = num_multimask_outputs - 1; + let num_mask_tokens = num_multimask_outputs + 1; let iou_prediction_head = MlpMaskDecoder::new( transformer_dim, iou_head_hidden_dim, @@ -117,6 +120,13 @@ impl MaskDecoder { )?; output_hypernetworks_mlps.push(mlp) } + let transformer = TwoWayTransformer::new( + /* depth */ 2, + /* embedding_dim */ transformer_dim, + /* num_heads */ 8, + /* mlp_dim */ 2048, + vb.pp("transformer"), + )?; Ok(Self { iou_token, mask_tokens, @@ -126,6 +136,7 @@ impl MaskDecoder { output_upscaling_conv2, num_mask_tokens, output_hypernetworks_mlps, + transformer, }) } @@ -182,7 +193,7 @@ impl MaskDecoder { let (b, c, h, w) = src.dims4()?; // Run the transformer - let (hs, src) = run_transformer(&src, &pos_src, &tokens)?; + let (hs, src) = self.transformer.forward(&src, &pos_src, &tokens)?; let iou_token_out = hs.i((.., 0))?; let mask_tokens_out = hs.i((.., 1, 1 + self.num_mask_tokens))?; @@ -216,7 +227,3 @@ impl MaskDecoder { fn repeat_interleave(_img: &Tensor, _repeats: usize, _dim: usize) -> Result { todo!() } - -fn run_transformer(_src: &Tensor, _pos: &Tensor, _tokens: &Tensor) -> Result<(Tensor, Tensor)> { - todo!() -} diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-examples/examples/segment-anything/model_prompt_encoder.rs new file mode 100644 index 00000000..7ac4c66d --- /dev/null +++ b/candle-examples/examples/segment-anything/model_prompt_encoder.rs @@ -0,0 +1,192 @@ +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; + +#[derive(Debug)] +struct PostionEmbeddingRandom { + positional_encoding_gaussian_matrix: Tensor, +} + +impl PostionEmbeddingRandom { + fn new(num_pos_feats: usize, vb: VarBuilder) -> Result { + let positional_encoding_gaussian_matrix = + vb.get((2, num_pos_feats), "positional_encoding_gaussian_matrix")?; + Ok(Self { + positional_encoding_gaussian_matrix, + }) + } + + fn pe_encoding(&self, coords: &Tensor) -> Result { + let coords = coords.affine(2., -1.)?; + let coords = coords.matmul(&self.positional_encoding_gaussian_matrix)?; + let coords = (coords * (2. * std::f64::consts::PI))?; + Tensor::cat(&[coords.sin()?, coords.cos()?], D::Minus1) + } + + fn forward(&self, h: usize, w: usize) -> Result { + let device = self.positional_encoding_gaussian_matrix.device(); + let grid = Tensor::ones((h, w), DType::F32, device)?; + // TODO: cumsum + let x_embed = (&grid - 0.5)?; + // TODO: cumsum + let y_embed = (&grid - 0.5)?; + let x_embed = (x_embed / w as f64)?; + let y_embed = (y_embed / h as f64)?; + let coords = Tensor::stack(&[&x_embed, &y_embed], D::Minus1)?; + self.pe_encoding(&coords)?.permute((2, 0, 1)) + } + + fn forward_with_coords( + &self, + coords_input: &Tensor, + image_size: (usize, usize), + ) -> Result { + let coords0 = (coords_input.narrow(D::Minus1, 0, 1)? / image_size.1 as f64)?; + let coords1 = (coords_input.narrow(D::Minus1, 1, 1)? / image_size.0 as f64)?; + let c = coords_input.dim(D::Minus1)?; + let coords_rest = coords_input.narrow(D::Minus1, 2, c - 2)?; + let coords = Tensor::cat(&[&coords0, &coords1, &coords_rest], D::Minus1)?; + self.pe_encoding(&coords) + } +} + +#[derive(Debug)] +pub struct PromptEncoder { + pe_layer: PostionEmbeddingRandom, + point_embeddings: Vec, + not_a_point_embed: candle_nn::Embedding, + mask_downscaling_conv1: candle_nn::Conv2d, + mask_downscaling_ln1: LayerNorm, + mask_downscaling_conv2: candle_nn::Conv2d, + mask_downscaling_ln2: LayerNorm, + mask_downscaling_conv3: candle_nn::Conv2d, + no_mask_embed: candle_nn::Embedding, + image_embedding_size: (usize, usize), + input_image_size: (usize, usize), +} + +impl PromptEncoder { + pub fn new( + embed_dim: usize, + image_embedding_size: (usize, usize), + input_image_size: (usize, usize), + mask_in_chans: usize, + vb: VarBuilder, + ) -> Result { + let num_points_embeddings = 4; + let pe_layer = PostionEmbeddingRandom::new(embed_dim / 2, vb.pp("pe_layer"))?; + let not_a_point_embed = candle_nn::embedding(1, embed_dim, vb.pp("not_a_point_embed"))?; + let no_mask_embed = candle_nn::embedding(1, embed_dim, vb.pp("no_mask_embed"))?; + let cfg = candle_nn::Conv2dConfig { + stride: 2, + ..Default::default() + }; + let mask_downscaling_conv1 = + candle_nn::conv2d(1, mask_in_chans / 4, 2, cfg, vb.pp("mask_downscaling.0"))?; + let mask_downscaling_conv2 = candle_nn::conv2d( + mask_in_chans / 4, + mask_in_chans, + 2, + cfg, + vb.pp("mask_downscaling.3"), + )?; + let mask_downscaling_conv3 = candle_nn::conv2d( + mask_in_chans, + embed_dim, + 1, + Default::default(), + vb.pp("mask_downscaling.6"), + )?; + let mask_downscaling_ln1 = + layer_norm(mask_in_chans / 4, 1e-6, vb.pp("mask_downscaling.1"))?; + let mask_downscaling_ln2 = layer_norm(mask_in_chans, 1e-6, vb.pp("mask_downscaling.4"))?; + let mut point_embeddings = Vec::with_capacity(num_points_embeddings); + let vb_e = vb.pp("point_embeddings"); + for i in 0..num_points_embeddings { + let emb = candle_nn::embedding(1, embed_dim, vb_e.pp(i))?; + point_embeddings.push(emb) + } + Ok(Self { + pe_layer, + point_embeddings, + not_a_point_embed, + mask_downscaling_conv1, + mask_downscaling_ln1, + mask_downscaling_conv2, + mask_downscaling_ln2, + mask_downscaling_conv3, + no_mask_embed, + image_embedding_size, + input_image_size, + }) + } + + fn embed_masks(&self, masks: &Tensor) -> Result { + masks + .apply(&self.mask_downscaling_conv1)? + .apply(&self.mask_downscaling_ln1)? + .gelu()? + .apply(&self.mask_downscaling_conv2)? + .apply(&self.mask_downscaling_ln2)? + .gelu()? + .apply(&self.mask_downscaling_conv3) + } + + fn embed_points(&self, points: &Tensor, labels: &Tensor, pad: bool) -> Result { + let points = (points + 0.5)?; + let points = if pad { todo!() } else { points }; + let point_embedding = self + .pe_layer + .forward_with_coords(&points, self.input_image_size)?; + // TODO: tweak based on labels. + Ok(point_embedding) + } + + fn embed_boxes(&self, boxes: &Tensor) -> Result { + let boxes = (boxes + 0.5)?; + let coords = boxes.reshape((boxes.elem_count() / 4, 2, 2))?; + let corner_embedding = self + .pe_layer + .forward_with_coords(&coords, self.input_image_size)?; + let ce1 = corner_embedding.i((.., 0))?; + let ce2 = corner_embedding.i((.., 1))?; + let ce1 = (ce1 + self.point_embeddings[2].embeddings())?; + let ce2 = (ce2 + self.point_embeddings[3].embeddings())?; + Tensor::cat(&[&ce1, &ce2], 1) + } + + fn forward( + &self, + points: Option<(&Tensor, &Tensor)>, + boxes: Option<&Tensor>, + masks: Option<&Tensor>, + ) -> Result<(Tensor, Tensor)> { + let se_points = match points { + Some((coords, labels)) => Some(self.embed_points(coords, labels, boxes.is_none())?), + None => None, + }; + let se_boxes = match boxes { + Some(boxes) => Some(self.embed_boxes(boxes)?), + None => None, + }; + let sparse_embeddings = match (se_points, se_boxes) { + (Some(se_points), Some(se_boxes)) => Tensor::cat(&[se_points, se_boxes], 1)?, + (Some(se_points), None) => se_points, + (None, Some(se_boxes)) => se_boxes, + (None, None) => Tensor::zeros(1, DType::F32, &candle::Device::Cpu)?, + }; + + let dense_embeddings = match masks { + None => { + let emb = self.no_mask_embed.embeddings(); + emb.reshape((1, emb.elem_count(), 1, 1))?.expand(( + 1, + 0, + self.image_embedding_size.0, + self.image_embedding_size.1, + ))? + } + Some(masks) => self.embed_masks(masks)?, + }; + Ok((sparse_embeddings, dense_embeddings)) + } +} diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-examples/examples/segment-anything/model_sam.rs new file mode 100644 index 00000000..5a0d7e8f --- /dev/null +++ b/candle-examples/examples/segment-anything/model_sam.rs @@ -0,0 +1,72 @@ +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; + +use crate::model_image_encoder::ImageEncoderViT; +use crate::model_mask_decoder::MaskDecoder; +use crate::model_prompt_encoder::PromptEncoder; + +#[derive(Debug)] +pub struct Sam { + image_encoder: ImageEncoderViT, + prompt_encoder: PromptEncoder, + mask_decoder: MaskDecoder, + pixel_mean: Tensor, + pixel_std: Tensor, +} + +impl Sam { + pub fn new( + encoder_embed_dim: usize, + encoder_depth: usize, + encoder_num_heads: usize, + encoder_global_attn_indexes: &[usize], + vb: VarBuilder, + ) -> Result { + const PROMPT_EMBED_DIM: usize = 256; + const IMAGE_SIZE: usize = 1024; + const VIT_PATCH_SIZE: usize = 16; + + let image_embedding_size = IMAGE_SIZE / VIT_PATCH_SIZE; + + let image_encoder = ImageEncoderViT::new( + IMAGE_SIZE, + VIT_PATCH_SIZE, + 3, + encoder_embed_dim, + encoder_depth, + encoder_num_heads, + PROMPT_EMBED_DIM, + /* qkv_bias */ true, + /* use_rel_pos */ true, + /* use_abs_pos */ true, + /* window_size */ 14, + /* global_attn_indexes */ encoder_global_attn_indexes, + vb.pp("image_encoder"), + )?; + let prompt_encoder = PromptEncoder::new( + PROMPT_EMBED_DIM, + (image_embedding_size, image_embedding_size), + (IMAGE_SIZE, IMAGE_SIZE), + 16, + vb.pp("prompt_encoder"), + )?; + let mask_decoder = MaskDecoder::new( + PROMPT_EMBED_DIM, + /* num_multitask_outputs */ 3, + /* iou_head_depth */ 3, + /* iou_head_hidden_dim */ 256, + vb.pp("mask_decoder"), + )?; + let pixel_mean = + Tensor::new(&[123.675f32, 116.28, 103.53], vb.device())?.reshape((3, 1, 1))?; + let pixel_std = + Tensor::new(&[58.395f32, 57.12, 57.375], vb.device())?.reshape((3, 1, 1))?; + Ok(Self { + image_encoder, + prompt_encoder, + mask_decoder, + pixel_std, + pixel_mean, + }) + } +} diff --git a/candle-examples/examples/segment-anything/model_transformer.rs b/candle-examples/examples/segment-anything/model_transformer.rs index 10f7f4e5..a845085d 100644 --- a/candle-examples/examples/segment-anything/model_transformer.rs +++ b/candle-examples/examples/segment-anything/model_transformer.rs @@ -75,3 +75,146 @@ struct TwoWayAttentionBlock { cross_attn_image_to_token: Attention, skip_first_layer_pe: bool, } + +impl TwoWayAttentionBlock { + fn new( + embedding_dim: usize, + num_heads: usize, + mlp_dim: usize, + skip_first_layer_pe: bool, + vb: VarBuilder, + ) -> Result { + let self_attn = Attention::new(embedding_dim, num_heads, 1, vb.pp("self_attn"))?; + let norm1 = layer_norm(embedding_dim, 1e-5, vb.pp("norm1"))?; + let norm2 = layer_norm(embedding_dim, 1e-5, vb.pp("norm2"))?; + let norm3 = layer_norm(embedding_dim, 1e-5, vb.pp("norm3"))?; + let norm4 = layer_norm(embedding_dim, 1e-5, vb.pp("norm4"))?; + let self_attn = Attention::new(embedding_dim, num_heads, 1, vb.pp("self_attn"))?; + let cross_attn_token_to_image = Attention::new( + embedding_dim, + num_heads, + 2, + vb.pp("cross_attn_token_to_image"), + )?; + let cross_attn_image_to_token = Attention::new( + embedding_dim, + num_heads, + 2, + vb.pp("cross_attn_image_to_token"), + )?; + // TODO: use relu in this mlp + let mlp = crate::MlpBlock::new(embedding_dim, mlp_dim, vb.pp("mlp"))?; + Ok(Self { + self_attn, + norm1, + cross_attn_image_to_token, + norm2, + mlp, + norm3, + norm4, + cross_attn_token_to_image, + skip_first_layer_pe, + }) + } + + fn forward( + &self, + queries: &Tensor, + keys: &Tensor, + query_pe: &Tensor, + key_pe: &Tensor, + ) -> Result<(Tensor, Tensor)> { + // Self attention block + let queries = if self.skip_first_layer_pe { + self.self_attn.forward(queries, keys, queries)? + } else { + let q = (queries + query_pe)?; + let attn_out = self.self_attn.forward(&q, &q, queries)?; + (queries + attn_out)? + }; + let queries = self.norm1.forward(&queries)?; + + // Cross attention block, tokens attending to image embedding + let q = (&queries + query_pe)?; + let k = (keys + key_pe)?; + let attn_out = self.cross_attn_token_to_image.forward(&q, &k, keys)?; + let queries = (&queries + attn_out)?; + let queries = self.norm2.forward(&queries)?; + + // MLP block + let mlp_out = self.mlp.forward(&queries); + let queries = (queries + mlp_out)?; + let queries = self.norm3.forward(&queries)?; + + // Cross attention block, image embedding attending to tokens + let q = (&queries + query_pe)?; + let k = (keys + key_pe)?; + let attn_out = self.cross_attn_image_to_token.forward(&k, &q, &queries)?; + let keys = (keys + attn_out)?; + let keys = self.norm4.forward(&keys)?; + + Ok((queries, keys)) + } +} + +#[derive(Debug)] +pub struct TwoWayTransformer { + layers: Vec, + final_attn_token_to_image: Attention, + norm_final_attn: LayerNorm, +} + +impl TwoWayTransformer { + pub fn new( + depth: usize, + embedding_dim: usize, + num_heads: usize, + mlp_dim: usize, + vb: VarBuilder, + ) -> Result { + let vb_l = vb.pp("layers"); + let mut layers = Vec::with_capacity(depth); + for i in 0..depth { + let layer = + TwoWayAttentionBlock::new(embedding_dim, num_heads, mlp_dim, i == 0, vb_l.pp(i))?; + layers.push(layer) + } + let final_attn_token_to_image = Attention::new( + embedding_dim, + num_heads, + 2, + vb.pp("final_attn_token_to_image"), + )?; + let norm_final_attn = layer_norm(embedding_dim, 1e-5, vb.pp("norm_final_attn"))?; + Ok(Self { + layers, + final_attn_token_to_image, + norm_final_attn, + }) + } + + pub fn forward( + &self, + image_embedding: &Tensor, + image_pe: &Tensor, + point_embedding: &Tensor, + ) -> Result<(Tensor, Tensor)> { + let (bs, c, h, w) = image_embedding.dims4()?; + let image_embedding = image_embedding.flatten_from(2)?.permute((0, 2, 1))?; + let image_pe = image_pe.flatten_from(2)?.permute((0, 2, 1))?; + + let mut queries = point_embedding.clone(); + let mut keys = image_embedding; + + for layer in self.layers.iter() { + (queries, keys) = layer.forward(&queries, &keys, point_embedding, &image_pe)? + } + + let q = (&queries + point_embedding)?; + let k = (&keys + image_pe)?; + let attn_out = self.final_attn_token_to_image.forward(&q, &k, &keys)?; + let queries = (queries + attn_out)?.apply(&self.norm_final_attn)?; + + Ok((queries, keys)) + } +} From 7396b8ed1a5394c58fcc772e5f6e6038577505b8 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 7 Sep 2023 19:22:45 +0100 Subject: [PATCH 029/150] Segment Anything - process images (#766) * Start processing images. * Add LayerNorm2d. * Properly use LayerNorm2d. * Tweak eps. * Use LayerNorm on inputs with a rank different from 3. * Window partitioning. * Fix a couple todos. * More todos. * Hard-code the einsums. * More padding support. * Some sizes tweaks. * Use the hub to get the weights. * Use a batch matmul. * Tweaks. * More fixes. * Get some predictions to be generated. --- candle-core/src/error.rs | 2 +- candle-core/src/shape.rs | 8 + .../examples/segment-anything/main.rs | 106 +++++++----- .../segment-anything/model_image_encoder.rs | 153 +++++++++++++++--- .../segment-anything/model_mask_decoder.rs | 22 +-- .../segment-anything/model_prompt_encoder.rs | 54 +++++-- .../examples/segment-anything/model_sam.rs | 37 ++++- .../segment-anything/model_transformer.rs | 13 +- candle-nn/src/layer_norm.rs | 8 +- candle-nn/src/linear.rs | 5 +- 10 files changed, 303 insertions(+), 105 deletions(-) diff --git a/candle-core/src/error.rs b/candle-core/src/error.rs index d030fab1..be8f7b07 100644 --- a/candle-core/src/error.rs +++ b/candle-core/src/error.rs @@ -30,7 +30,7 @@ pub enum Error { UnsupportedDTypeForOp(DType, &'static str), // === Dimension Index Errors === - #[error("{op}: dimension index {dim} out of range for {shape:?}")] + #[error("{op}: dimension index {dim} out of range for shape {shape:?}")] DimOutOfRange { shape: Shape, dim: i32, diff --git a/candle-core/src/shape.rs b/candle-core/src/shape.rs index 578e8ac9..9617d1ac 100644 --- a/candle-core/src/shape.rs +++ b/candle-core/src/shape.rs @@ -73,6 +73,14 @@ impl From<(usize, usize, usize, usize, usize)> for Shape { } } +impl From<(usize, usize, usize, usize, usize, usize)> for Shape { + fn from(d123456: (usize, usize, usize, usize, usize, usize)) -> Self { + Self(vec![ + d123456.0, d123456.1, d123456.2, d123456.3, d123456.4, d123456.5, + ]) + } +} + impl From> for Shape { fn from(dims: Vec) -> Self { Self(dims) diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index 368b5a33..a2722270 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -15,7 +15,7 @@ pub mod model_sam; pub mod model_transformer; use candle::{DType, IndexOp, Result, Tensor, D}; -use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use candle_nn::{Linear, Module, VarBuilder}; use clap::Parser; pub fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { @@ -26,65 +26,74 @@ pub fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Resu } } +#[derive(Debug)] +pub struct LayerNorm2d { + weight: Tensor, + bias: Tensor, + num_channels: usize, + eps: f64, +} + +impl LayerNorm2d { + pub fn new(num_channels: usize, eps: f64, vb: VarBuilder) -> Result { + let weight = vb.get(num_channels, "weight")?; + let bias = vb.get(num_channels, "bias")?; + Ok(Self { + weight, + bias, + num_channels, + eps, + }) + } +} + +impl Module for LayerNorm2d { + fn forward(&self, xs: &Tensor) -> Result { + let u = xs.mean_keepdim(1)?; + let xs = xs.broadcast_sub(&u)?; + let s = xs.sqr()?.mean_keepdim(1)?; + let xs = xs.broadcast_div(&(s + self.eps)?.sqrt()?)?; + xs.broadcast_mul(&self.weight.reshape((1, self.num_channels, 1, 1))?)? + .broadcast_add(&self.bias.reshape((1, self.num_channels, 1, 1))?) + } +} + #[derive(Debug)] pub struct MlpBlock { lin1: Linear, lin2: Linear, + activation: candle_nn::Activation, } impl MlpBlock { - pub fn new(embedding_dim: usize, mlp_dim: usize, vb: VarBuilder) -> Result { + pub fn new( + embedding_dim: usize, + mlp_dim: usize, + activation: candle_nn::Activation, + vb: VarBuilder, + ) -> Result { let lin1 = candle_nn::linear(embedding_dim, mlp_dim, vb.pp("lin1"))?; let lin2 = candle_nn::linear(mlp_dim, embedding_dim, vb.pp("lin2"))?; - Ok(Self { lin1, lin2 }) + Ok(Self { + lin1, + lin2, + activation, + }) } } impl Module for MlpBlock { fn forward(&self, xs: &Tensor) -> Result { - xs.apply(&self.lin1)?.gelu()?.apply(&self.lin2) + xs.apply(&self.lin1)? + .apply(&self.activation)? + .apply(&self.lin2) } } -/* - fn interpolate_pos_encoding(&self, xs: &Tensor, w: usize, h: usize) -> Result { - let npatch = xs.dim(1)? - 1; - let n = self.pos_embed.dim(1)? - 1; - let sqrt_n = (n as f64).sqrt(); - if npatch == n && w == h { - return Ok(xs.clone()); - } - let class_pos_embed = self.pos_embed.i((.., ..1))?; - let patch_pos_embed = self.pos_embed.i((.., 1..))?; - let dim = xs.dim(D::Minus1)?; - let (w0, h0) = ((w / PATCH_SIZE) as f64 + 0.1, (h / PATCH_SIZE) as f64 + 0.1); - let patch_pos_embed = patch_pos_embed - .reshape((1, sqrt_n as usize, sqrt_n as usize, dim))? - .transpose(2, 3)? - .transpose(1, 2)?; - // This uses bicubic interpolation in the original implementation. - let patch_pos_embed = patch_pos_embed.upsample_nearest2d(h0 as usize, w0 as usize)?; - let el_count = patch_pos_embed.shape().elem_count(); - let patch_pos_embed = - patch_pos_embed - .transpose(1, 2)? - .transpose(2, 3)? - .reshape((1, el_count / dim, dim))?; - Tensor::cat(&[&class_pos_embed, &patch_pos_embed], 1) - } - - fn prepare_tokens_with_mask(&self, xs: &Tensor) -> Result { - let (_b, _nc, w, h) = xs.dims4()?; - let xs = self.patch_embed.forward(xs)?; - let xs = Tensor::cat(&[&self.cls_token, &xs], 1)?; - &xs + &self.interpolate_pos_encoding(&xs, w, h)? - } -*/ - #[derive(Parser)] struct Args { #[arg(long)] - model: String, + model: Option, #[arg(long)] image: String, @@ -99,13 +108,24 @@ pub fn main() -> anyhow::Result<()> { let device = candle_examples::device(args.cpu)?; - let image = candle_examples::imagenet::load_image224(args.image)?.to_device(&device); + let image = candle_examples::imagenet::load_image224(args.image)?.to_device(&device)?; println!("loaded image {image:?}"); - let weights = unsafe { candle::safetensors::MmapedFile::new(args.model)? }; + let model = match args.model { + Some(model) => std::path::PathBuf::from(model), + None => { + let api = hf_hub::api::sync::Api::new()?; + let api = api.model("lmz/candle-sam".to_string()); + api.get("sam_vit_b_01ec64.safetensors")? + } + }; + let weights = unsafe { candle::safetensors::MmapedFile::new(model)? }; let weights = weights.deserialize()?; let vb = VarBuilder::from_safetensors(vec![weights], DType::F32, &device); - let _sam = model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)?; // sam_vit_b + let sam = model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)?; // sam_vit_b + let (mask, iou_predictions) = sam.forward(&image, false)?; + println!("mask: {mask:?}"); + println!("iou_predictions: {iou_predictions:?}"); Ok(()) } diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-examples/examples/segment-anything/model_image_encoder.rs index cfcdbb38..f5db2830 100644 --- a/candle-examples/examples/segment-anything/model_image_encoder.rs +++ b/candle-examples/examples/segment-anything/model_image_encoder.rs @@ -70,6 +70,60 @@ impl Attention { rel_pos_hw, }) } + + fn add_decomposed_rel_pos( + &self, + attn: Tensor, + q: &Tensor, + (q_h, q_w): (usize, usize), + (k_h, k_w): (usize, usize), + ) -> Result { + match &self.rel_pos_hw { + Some((rel_pos_h, rel_pos_w)) => { + let r_h = get_rel_pos(q_h, k_h, rel_pos_h)?; + let r_w = get_rel_pos(q_w, k_w, rel_pos_w)?; + let (b, _, dim) = q.dims3()?; + let r_q = q.reshape((b, q_h, q_w, dim))?; + // rel_h = torch.einsum("bhwc,hkc->bhwk", r_q, Rh) + let rel_h = r_q.matmul(&r_h.broadcast_left(b)?.t()?.contiguous()?)?; + // rel_w = torch.einsum("bhwc,wkc->bhwk", r_q, Rw) + let rel_w = r_q + .transpose(1, 2)? // -> bwhc + .contiguous()? + .matmul(&r_w.broadcast_left(b)?.t()?.contiguous()?)? // bwhc,bwck -> bwhk + .transpose(1, 2)?; + (attn.reshape((b, q_h, q_w, k_h, k_w))? + + rel_h.unsqueeze(4)?.broadcast_add(&rel_w.unsqueeze(3)?)?)? + .reshape((b, q_h * q_w, k_h * k_w)) + } + None => Ok(attn), + } + } +} + +fn get_rel_pos(q_size: usize, k_size: usize, rel_pos: &Tensor) -> Result { + let max_rel_dist = 2 * usize::max(q_size, k_size) - 1; + let dev = rel_pos.device(); + let rel_pos_resized = if rel_pos.dim(0)? != max_rel_dist { + todo!("interpolation") + } else { + rel_pos + }; + let q_coords = Tensor::arange(0u32, q_size as u32, dev)? + .reshape((q_size, 1))? + .to_dtype(DType::F32)?; + let k_coords = Tensor::arange(0u32, k_size as u32, dev)? + .reshape((1, k_size))? + .to_dtype(DType::F32)?; + let q_coords = (q_coords * f64::max(1f64, k_size as f64 / q_size as f64))?; + let k_coords = (k_coords * f64::max(1f64, q_size as f64 / k_size as f64))?; + let relative_coords = (q_coords.broadcast_sub(&k_coords)? + + (k_size as f64 - 1.) * f64::max(1f64, q_size as f64 / k_size as f64))?; + let (d1, d2) = relative_coords.dims2()?; + let relative_coords = relative_coords.to_dtype(DType::U32)?; + rel_pos_resized + .index_select(&relative_coords.reshape(d1 * d2)?, 0)? + .reshape((d1, d2, rel_pos_resized.dim(1)?)) } impl Module for Attention { @@ -77,24 +131,22 @@ impl Module for Attention { let (b, h, w, c) = xs.dims4()?; let qkv = self .qkv - .forward(xs)? + .forward(&xs.flatten_to(1)?)? .reshape((b, h * w, 3, self.num_heads, c / self.num_heads))? .permute((2, 0, 3, 1, 4))? .reshape((3, b * self.num_heads, h * w, c / self.num_heads))?; let q = qkv.i(0)?; let k = qkv.i(1)?; let v = qkv.i(2)?; - let attn = (q * self.scale)?.matmul(&k.t()?)?; - if self.use_rel_pos { - todo!() - } + let attn = (&q * self.scale)?.matmul(&k.t()?)?; + let attn = self.add_decomposed_rel_pos(attn, &q, (h, w), (h, w))?; let attn = candle_nn::ops::softmax_last_dim(&attn)?; + let attn = attn.matmul(&v)?; let attn = attn - .matmul(&v)? .reshape((b, self.num_heads, h, w, c / self.num_heads))? .permute((0, 2, 3, 1, 4))? - .reshape((b, h, w, c / self.num_heads))?; - self.proj.forward(&attn) + .reshape((b, h * w, c))?; + self.proj.forward(&attn)?.reshape((b, h, w, c)) } } @@ -117,8 +169,8 @@ impl Block { input_size: (usize, usize), vb: VarBuilder, ) -> Result { - let norm1 = layer_norm(dim, 1e-5, vb.pp("norm1"))?; - let norm2 = layer_norm(dim, 1e-5, vb.pp("norm2"))?; + let norm1 = layer_norm(dim, 1e-6, vb.pp("norm1"))?; + let norm2 = layer_norm(dim, 1e-6, vb.pp("norm2"))?; let input_size_attn = if window_size == 0 { input_size } else { @@ -132,7 +184,7 @@ impl Block { input_size_attn, vb.pp("attn"), )?; - let mlp = crate::MlpBlock::new(dim, dim * 4, vb.pp("mlp"))?; + let mlp = crate::MlpBlock::new(dim, dim * 4, candle_nn::Activation::Gelu, vb.pp("mlp"))?; Ok(Self { norm1, attn, @@ -143,17 +195,76 @@ impl Block { } } +fn window_partition(xs: Tensor, window_size: usize) -> Result<(Tensor, (usize, usize))> { + let (b, h, w, c) = xs.dims4()?; + let pad_h = (window_size - h % window_size) % window_size; + let pad_w = (window_size - w % window_size) % window_size; + let xs = if pad_h > 0 { + xs.pad_with_zeros(1, 0, pad_h)? + } else { + xs + }; + let xs = if pad_w > 0 { + xs.pad_with_zeros(2, 0, pad_w)? + } else { + xs + }; + let (h_p, w_p) = (h + pad_h, w + pad_w); + let windows = xs + .reshape(( + b, + h_p / window_size, + window_size, + w_p / window_size, + window_size, + c, + ))? + .transpose(2, 3)? + .contiguous()? + .flatten_to(2)?; + Ok((windows, (h_p, w_p))) +} + +fn window_unpartition( + windows: Tensor, + window_size: usize, + (h_p, w_p): (usize, usize), + (h, w): (usize, usize), +) -> Result { + let b = windows.dim(0)? / (h_p * w_p / window_size / window_size); + let xs = windows + .reshape(( + b, + h_p / window_size, + w_p / window_size, + window_size, + window_size, + windows.elem_count() / b / h_p / w_p, + ))? + .transpose(2, 3)? + .contiguous()? + .reshape((b, h_p, w_p, windows.elem_count() / b / h_p / w_p))?; + let xs = if h_p > h { xs.narrow(1, 0, h)? } else { xs }; + let xs = if w_p > w { xs.narrow(2, 0, w)? } else { xs }; + Ok(xs) +} + impl Module for Block { fn forward(&self, xs: &Tensor) -> Result { let shortcut = xs; let xs = self.norm1.forward(xs)?; - if self.window_size > 0 { - todo!() - } + let hw = (xs.dim(1)?, xs.dim(2)?); + let (xs, pad_hw) = if self.window_size > 0 { + window_partition(xs, self.window_size)? + } else { + (xs, (0, 0)) + }; let xs = self.attn.forward(&xs)?; - if self.window_size > 0 { - todo!() - } + let xs = if self.window_size > 0 { + window_unpartition(xs, self.window_size, pad_hw, hw)? + } else { + xs + }; let xs = (xs + shortcut)?; &xs + xs.apply(&self.norm2)?.apply(&self.mlp)? } @@ -165,9 +276,9 @@ pub struct ImageEncoderViT { patch_embed: PatchEmbed, blocks: Vec, neck_conv1: candle_nn::Conv2d, - neck_ln1: LayerNorm, + neck_ln1: crate::LayerNorm2d, neck_conv2: candle_nn::Conv2d, - neck_ln2: LayerNorm, + neck_ln2: crate::LayerNorm2d, pos_embed: Option, } @@ -222,13 +333,13 @@ impl ImageEncoderViT { Default::default(), vb.pp("neck.0"), )?; - let neck_ln1 = layer_norm(out_chans, 1e-6, vb.pp("neck.1"))?; + let neck_ln1 = crate::LayerNorm2d::new(out_chans, 1e-6, vb.pp("neck.1"))?; let cfg = candle_nn::Conv2dConfig { padding: 1, ..Default::default() }; let neck_conv2 = candle_nn::conv2d_no_bias(out_chans, out_chans, 3, cfg, vb.pp("neck.2"))?; - let neck_ln2 = layer_norm(out_chans, 1e-6, vb.pp("neck.3"))?; + let neck_ln2 = crate::LayerNorm2d::new(out_chans, 1e-6, vb.pp("neck.3"))?; let pos_embed = if use_abs_pos { let p = vb.get( (1, img_size / patch_size, img_size / patch_size, embed_dim), diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-examples/examples/segment-anything/model_mask_decoder.rs index cf3879cd..1ef46eeb 100644 --- a/candle-examples/examples/segment-anything/model_mask_decoder.rs +++ b/candle-examples/examples/segment-anything/model_mask_decoder.rs @@ -1,5 +1,5 @@ use candle::{DType, IndexOp, Result, Tensor, D}; -use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use candle_nn::{Linear, Module, VarBuilder}; use crate::model_transformer::TwoWayTransformer; @@ -60,7 +60,7 @@ pub struct MaskDecoder { mask_tokens: candle_nn::Embedding, iou_prediction_head: MlpMaskDecoder, output_upscaling_conv1: candle_nn::ConvTranspose2d, - output_upscaling_ln: LayerNorm, + output_upscaling_ln: crate::LayerNorm2d, output_upscaling_conv2: candle_nn::ConvTranspose2d, num_mask_tokens: usize, output_hypernetworks_mlps: Vec, @@ -99,7 +99,7 @@ impl MaskDecoder { vb.pp("output_upscaling.0"), )?; let output_upscaling_ln = - layer_norm(transformer_dim / 4, 1e-6, vb.pp("output_upscaling.1"))?; + crate::LayerNorm2d::new(transformer_dim / 4, 1e-6, vb.pp("output_upscaling.1"))?; let output_upscaling_conv2 = candle_nn::conv_transpose2d( transformer_dim / 4, transformer_dim / 8, @@ -140,7 +140,7 @@ impl MaskDecoder { }) } - fn forward( + pub fn forward( &self, image_embeddings: &Tensor, image_pe: &Tensor, @@ -195,7 +195,7 @@ impl MaskDecoder { // Run the transformer let (hs, src) = self.transformer.forward(&src, &pos_src, &tokens)?; let iou_token_out = hs.i((.., 0))?; - let mask_tokens_out = hs.i((.., 1, 1 + self.num_mask_tokens))?; + let mask_tokens_out = hs.i((.., 1..1 + self.num_mask_tokens))?; // Upscale mask embeddings and predict masks using the masks tokens. let src = src.transpose(1, 2)?.reshape((b, c, h, w))?; @@ -213,9 +213,8 @@ impl MaskDecoder { } let hyper_in = Tensor::stack(hyper_in_list.as_slice(), 1)?; let (b, c, h, w) = upscaled_embedding.dims4()?; - let masks = hyper_in - .matmul(&upscaled_embedding.reshape((b, c, h * w))?)? - .reshape((b, 0, h, w))?; + let masks = hyper_in.matmul(&upscaled_embedding.reshape((b, c, h * w))?)?; + let masks = masks.reshape((b, masks.elem_count() / b / h / w, h, w))?; // Generate mask quality predictions. let iou_pred = self.iou_prediction_head.forward(&iou_token_out)?; @@ -224,6 +223,9 @@ impl MaskDecoder { } // Equivalent to torch.repeat_interleave -fn repeat_interleave(_img: &Tensor, _repeats: usize, _dim: usize) -> Result { - todo!() +fn repeat_interleave(img: &Tensor, repeats: usize, dim: usize) -> Result { + let img = img.unsqueeze(dim + 1)?; + let mut dims = img.dims().to_vec(); + dims[dim + 1] = repeats; + img.broadcast_as(dims)?.flatten(dim, dim + 1) } diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-examples/examples/segment-anything/model_prompt_encoder.rs index 7ac4c66d..c6ffffd2 100644 --- a/candle-examples/examples/segment-anything/model_prompt_encoder.rs +++ b/candle-examples/examples/segment-anything/model_prompt_encoder.rs @@ -1,5 +1,5 @@ use candle::{DType, IndexOp, Result, Tensor, D}; -use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use candle_nn::{Linear, Module, VarBuilder}; #[derive(Debug)] struct PostionEmbeddingRandom { @@ -17,7 +17,7 @@ impl PostionEmbeddingRandom { fn pe_encoding(&self, coords: &Tensor) -> Result { let coords = coords.affine(2., -1.)?; - let coords = coords.matmul(&self.positional_encoding_gaussian_matrix)?; + let coords = coords.broadcast_matmul(&self.positional_encoding_gaussian_matrix)?; let coords = (coords * (2. * std::f64::consts::PI))?; Tensor::cat(&[coords.sin()?, coords.cos()?], D::Minus1) } @@ -25,12 +25,14 @@ impl PostionEmbeddingRandom { fn forward(&self, h: usize, w: usize) -> Result { let device = self.positional_encoding_gaussian_matrix.device(); let grid = Tensor::ones((h, w), DType::F32, device)?; - // TODO: cumsum - let x_embed = (&grid - 0.5)?; - // TODO: cumsum - let y_embed = (&grid - 0.5)?; - let x_embed = (x_embed / w as f64)?; - let y_embed = (y_embed / h as f64)?; + let x_embed = (Tensor::arange(0u32, w as u32, device)?.to_dtype(DType::F32)? + 0.5)?; + let y_embed = (Tensor::arange(0u32, h as u32, device)?.to_dtype(DType::F32)? + 0.5)?; + let x_embed = (x_embed / w as f64)? + .reshape((1, w))? + .broadcast_as((h, w))?; + let y_embed = (y_embed / h as f64)? + .reshape((h, 1))? + .broadcast_as((h, w))?; let coords = Tensor::stack(&[&x_embed, &y_embed], D::Minus1)?; self.pe_encoding(&coords)?.permute((2, 0, 1)) } @@ -55,13 +57,14 @@ pub struct PromptEncoder { point_embeddings: Vec, not_a_point_embed: candle_nn::Embedding, mask_downscaling_conv1: candle_nn::Conv2d, - mask_downscaling_ln1: LayerNorm, + mask_downscaling_ln1: crate::LayerNorm2d, mask_downscaling_conv2: candle_nn::Conv2d, - mask_downscaling_ln2: LayerNorm, + mask_downscaling_ln2: crate::LayerNorm2d, mask_downscaling_conv3: candle_nn::Conv2d, no_mask_embed: candle_nn::Embedding, image_embedding_size: (usize, usize), input_image_size: (usize, usize), + embed_dim: usize, } impl PromptEncoder { @@ -97,8 +100,9 @@ impl PromptEncoder { vb.pp("mask_downscaling.6"), )?; let mask_downscaling_ln1 = - layer_norm(mask_in_chans / 4, 1e-6, vb.pp("mask_downscaling.1"))?; - let mask_downscaling_ln2 = layer_norm(mask_in_chans, 1e-6, vb.pp("mask_downscaling.4"))?; + crate::LayerNorm2d::new(mask_in_chans / 4, 1e-6, vb.pp("mask_downscaling.1"))?; + let mask_downscaling_ln2 = + crate::LayerNorm2d::new(mask_in_chans, 1e-6, vb.pp("mask_downscaling.4"))?; let mut point_embeddings = Vec::with_capacity(num_points_embeddings); let vb_e = vb.pp("point_embeddings"); for i in 0..num_points_embeddings { @@ -117,9 +121,16 @@ impl PromptEncoder { no_mask_embed, image_embedding_size, input_image_size, + embed_dim, }) } + pub fn get_dense_pe(&self) -> Result { + self.pe_layer + .forward(self.image_embedding_size.0, self.image_embedding_size.1)? + .unsqueeze(0) + } + fn embed_masks(&self, masks: &Tensor) -> Result { masks .apply(&self.mask_downscaling_conv1)? @@ -133,7 +144,16 @@ impl PromptEncoder { fn embed_points(&self, points: &Tensor, labels: &Tensor, pad: bool) -> Result { let points = (points + 0.5)?; - let points = if pad { todo!() } else { points }; + let dev = points.device(); + let (points, labels) = if pad { + let padding_point = Tensor::zeros((points.dim(0)?, 1, 2), DType::F32, dev)?; + let padding_label = (Tensor::ones((labels.dim(0)?, 1), DType::F32, dev)? * (-1f64))?; + let points = Tensor::cat(&[&points, &padding_point], 1)?; + let labels = Tensor::cat(&[labels, &padding_label], 1)?; + (points, labels) + } else { + (points, labels.clone()) + }; let point_embedding = self .pe_layer .forward_with_coords(&points, self.input_image_size)?; @@ -154,7 +174,7 @@ impl PromptEncoder { Tensor::cat(&[&ce1, &ce2], 1) } - fn forward( + pub fn forward( &self, points: Option<(&Tensor, &Tensor)>, boxes: Option<&Tensor>, @@ -172,7 +192,9 @@ impl PromptEncoder { (Some(se_points), Some(se_boxes)) => Tensor::cat(&[se_points, se_boxes], 1)?, (Some(se_points), None) => se_points, (None, Some(se_boxes)) => se_boxes, - (None, None) => Tensor::zeros(1, DType::F32, &candle::Device::Cpu)?, + (None, None) => { + Tensor::zeros((1, 0, self.embed_dim), DType::F32, &candle::Device::Cpu)? + } }; let dense_embeddings = match masks { @@ -180,7 +202,7 @@ impl PromptEncoder { let emb = self.no_mask_embed.embeddings(); emb.reshape((1, emb.elem_count(), 1, 1))?.expand(( 1, - 0, + emb.elem_count(), self.image_embedding_size.0, self.image_embedding_size.1, ))? diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-examples/examples/segment-anything/model_sam.rs index 5a0d7e8f..1c8e9a59 100644 --- a/candle-examples/examples/segment-anything/model_sam.rs +++ b/candle-examples/examples/segment-anything/model_sam.rs @@ -5,6 +5,10 @@ use crate::model_image_encoder::ImageEncoderViT; use crate::model_mask_decoder::MaskDecoder; use crate::model_prompt_encoder::PromptEncoder; +const PROMPT_EMBED_DIM: usize = 256; +const IMAGE_SIZE: usize = 1024; +const VIT_PATCH_SIZE: usize = 16; + #[derive(Debug)] pub struct Sam { image_encoder: ImageEncoderViT, @@ -22,10 +26,6 @@ impl Sam { encoder_global_attn_indexes: &[usize], vb: VarBuilder, ) -> Result { - const PROMPT_EMBED_DIM: usize = 256; - const IMAGE_SIZE: usize = 1024; - const VIT_PATCH_SIZE: usize = 16; - let image_embedding_size = IMAGE_SIZE / VIT_PATCH_SIZE; let image_encoder = ImageEncoderViT::new( @@ -69,4 +69,33 @@ impl Sam { pixel_mean, }) } + + pub fn forward(&self, img: &Tensor, multimask_output: bool) -> Result<(Tensor, Tensor)> { + let img = self.preprocess(img)?.unsqueeze(0)?; + let img_embeddings = self.image_encoder.forward(&img)?; + let image_pe = self.prompt_encoder.get_dense_pe()?; + let (sparse_prompt_embeddings, dense_prompt_embeddings) = + self.prompt_encoder.forward(None, None, None)?; + let (low_res_mask, iou_predictions) = self.mask_decoder.forward( + &img_embeddings, + &image_pe, + &sparse_prompt_embeddings, + &dense_prompt_embeddings, + multimask_output, + )?; + // TODO: post-processing. + Ok((low_res_mask, iou_predictions)) + } + + fn preprocess(&self, img: &Tensor) -> Result { + let (c, h, w) = img.dims3()?; + let img = img + .broadcast_sub(&self.pixel_mean)? + .broadcast_div(&self.pixel_std)?; + if h > IMAGE_SIZE || w > IMAGE_SIZE { + candle::bail!("image is too large ({w}, {h}), maximum size {IMAGE_SIZE}") + } + let img = img.pad_with_zeros(1, 0, IMAGE_SIZE - h)?; + img.pad_with_zeros(2, 0, IMAGE_SIZE - w) + } } diff --git a/candle-examples/examples/segment-anything/model_transformer.rs b/candle-examples/examples/segment-anything/model_transformer.rs index a845085d..044dce9b 100644 --- a/candle-examples/examples/segment-anything/model_transformer.rs +++ b/candle-examples/examples/segment-anything/model_transformer.rs @@ -36,7 +36,8 @@ impl Attention { fn separate_heads(&self, x: &Tensor) -> Result { let (b, n, c) = x.dims3()?; x.reshape((b, n, self.num_heads, c / self.num_heads))? - .transpose(1, 2) + .transpose(1, 2)? + .contiguous() } fn recombine_heads(&self, x: &Tensor) -> Result { @@ -102,8 +103,12 @@ impl TwoWayAttentionBlock { 2, vb.pp("cross_attn_image_to_token"), )?; - // TODO: use relu in this mlp - let mlp = crate::MlpBlock::new(embedding_dim, mlp_dim, vb.pp("mlp"))?; + let mlp = crate::MlpBlock::new( + embedding_dim, + mlp_dim, + candle_nn::Activation::Relu, + vb.pp("mlp"), + )?; Ok(Self { self_attn, norm1, @@ -126,7 +131,7 @@ impl TwoWayAttentionBlock { ) -> Result<(Tensor, Tensor)> { // Self attention block let queries = if self.skip_first_layer_pe { - self.self_attn.forward(queries, keys, queries)? + self.self_attn.forward(queries, queries, queries)? } else { let q = (queries + query_pe)?; let attn_out = self.self_attn.forward(&q, &q, queries)?; diff --git a/candle-nn/src/layer_norm.rs b/candle-nn/src/layer_norm.rs index 08e2f628..d2e80a82 100644 --- a/candle-nn/src/layer_norm.rs +++ b/candle-nn/src/layer_norm.rs @@ -28,7 +28,7 @@ //! ``` //! //! [`Layer Normalization`]: https://arxiv.org/abs/1607.06450 -use candle::{DType, Result, Tensor}; +use candle::{DType, Result, Tensor, D}; #[derive(Debug, Clone, Copy, PartialEq)] pub struct LayerNormConfig { @@ -104,15 +104,15 @@ impl crate::Module for LayerNorm { DType::F16 | DType::BF16 => DType::F32, d => d, }; - let (_bsize, _seq_len, hidden_size) = x.dims3()?; + let hidden_size = x.dim(D::Minus1)?; let x = x.to_dtype(internal_dtype)?; let x = if self.remove_mean { - let mean_x = (x.sum_keepdim(2)? / hidden_size as f64)?; + let mean_x = (x.sum_keepdim(D::Minus1)? / hidden_size as f64)?; x.broadcast_sub(&mean_x)? } else { x }; - let norm_x = (x.sqr()?.sum_keepdim(2)? / hidden_size as f64)?; + let norm_x = (x.sqr()?.sum_keepdim(D::Minus1)? / hidden_size as f64)?; let x_normed = x.broadcast_div(&(norm_x + self.eps)?.sqrt()?)?; let x = x_normed.to_dtype(x_dtype)?.broadcast_mul(&self.weight)?; match &self.bias { diff --git a/candle-nn/src/linear.rs b/candle-nn/src/linear.rs index 7028f68c..de335964 100644 --- a/candle-nn/src/linear.rs +++ b/candle-nn/src/linear.rs @@ -41,8 +41,9 @@ impl Linear { impl super::Module for Linear { fn forward(&self, x: &Tensor) -> candle::Result { - let w = match x.dims() { - &[bsize, _, _] => self.weight.broadcast_left(bsize)?.t()?, + let w = match *x.dims() { + [b1, b2, _, _] => self.weight.broadcast_left((b1, b2))?.t()?, + [bsize, _, _] => self.weight.broadcast_left(bsize)?.t()?, _ => self.weight.t()?, }; let x = x.matmul(&w)?; From 79c27fc489f2eece486fa433a0ae75c66a398e6f Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 7 Sep 2023 21:45:16 +0100 Subject: [PATCH 030/150] Segment-anything fixes: avoid normalizing twice. (#767) * Segment-anything fixes: avoid normalizing twice. * More fixes for the image aspect ratio. --- .../examples/segment-anything/main.rs | 5 ++-- .../examples/segment-anything/model_sam.rs | 3 +- candle-examples/src/lib.rs | 28 +++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index a2722270..03ebe346 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -108,7 +108,8 @@ pub fn main() -> anyhow::Result<()> { let device = candle_examples::device(args.cpu)?; - let image = candle_examples::imagenet::load_image224(args.image)?.to_device(&device)?; + let image = + candle_examples::load_image(args.image, Some(model_sam::IMAGE_SIZE))?.to_device(&device)?; println!("loaded image {image:?}"); let model = match args.model { @@ -125,7 +126,7 @@ pub fn main() -> anyhow::Result<()> { let sam = model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)?; // sam_vit_b let (mask, iou_predictions) = sam.forward(&image, false)?; - println!("mask: {mask:?}"); + println!("mask:\n{mask}"); println!("iou_predictions: {iou_predictions:?}"); Ok(()) } diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-examples/examples/segment-anything/model_sam.rs index 1c8e9a59..acba7ef4 100644 --- a/candle-examples/examples/segment-anything/model_sam.rs +++ b/candle-examples/examples/segment-anything/model_sam.rs @@ -6,7 +6,7 @@ use crate::model_mask_decoder::MaskDecoder; use crate::model_prompt_encoder::PromptEncoder; const PROMPT_EMBED_DIM: usize = 256; -const IMAGE_SIZE: usize = 1024; +pub const IMAGE_SIZE: usize = 1024; const VIT_PATCH_SIZE: usize = 16; #[derive(Debug)] @@ -90,6 +90,7 @@ impl Sam { fn preprocess(&self, img: &Tensor) -> Result { let (c, h, w) = img.dims3()?; let img = img + .to_dtype(DType::F32)? .broadcast_sub(&self.pixel_mean)? .broadcast_div(&self.pixel_std)?; if h > IMAGE_SIZE || w > IMAGE_SIZE { diff --git a/candle-examples/src/lib.rs b/candle-examples/src/lib.rs index f9581b02..66cd2f99 100644 --- a/candle-examples/src/lib.rs +++ b/candle-examples/src/lib.rs @@ -16,6 +16,34 @@ pub fn device(cpu: bool) -> Result { } } +pub fn load_image>( + p: P, + resize_longest: Option, +) -> Result { + let img = image::io::Reader::open(p)? + .decode() + .map_err(candle::Error::wrap)?; + let img = match resize_longest { + None => img, + Some(resize_longest) => { + let (height, width) = (img.height(), img.width()); + let resize_longest = resize_longest as u32; + let (height, width) = if height < width { + let h = (resize_longest * height) / width; + (h, resize_longest) + } else { + let w = (resize_longest * width) / height; + (resize_longest, w) + }; + img.resize_exact(width, height, image::imageops::FilterType::CatmullRom) + } + }; + let (height, width) = (img.height() as usize, img.width() as usize); + let img = img.to_rgb8(); + let data = img.into_raw(); + Tensor::from_vec(data, (height, width, 3), &Device::Cpu)?.permute((2, 0, 1)) +} + pub fn load_image_and_resize>( p: P, width: usize, From 3898e500debd632b520054ebfa42f8333323a20e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 05:53:08 +0100 Subject: [PATCH 031/150] Generate a mask image + the scaled input image. (#769) * Also round-trip the original image. * Make it possible to use a safetensors input. --- .../examples/segment-anything/main.rs | 27 +++++++++++++++++-- .../examples/segment-anything/model_sam.rs | 10 ++++++- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index 03ebe346..89d5b56c 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -108,8 +108,20 @@ pub fn main() -> anyhow::Result<()> { let device = candle_examples::device(args.cpu)?; - let image = - candle_examples::load_image(args.image, Some(model_sam::IMAGE_SIZE))?.to_device(&device)?; + let image = if args.image.ends_with(".safetensors") { + let mut tensors = candle::safetensors::load(&args.image, &device)?; + match tensors.remove("image") { + Some(image) => image, + None => { + if tensors.len() != 1 { + anyhow::bail!("multiple tensors in '{}'", args.image) + } + tensors.into_values().next().unwrap() + } + } + } else { + candle_examples::load_image(args.image, Some(model_sam::IMAGE_SIZE))?.to_device(&device)? + }; println!("loaded image {image:?}"); let model = match args.model { @@ -128,5 +140,16 @@ pub fn main() -> anyhow::Result<()> { let (mask, iou_predictions) = sam.forward(&image, false)?; println!("mask:\n{mask}"); println!("iou_predictions: {iou_predictions:?}"); + + // Save the mask as an image. + let mask = mask.ge(&mask.zeros_like()?)?; + let mask = (mask * 255.)?.squeeze(0)?; + let (_one, h, w) = mask.dims3()?; + let mask = mask.expand((3, h, w))?; + candle_examples::save_image(&mask, "sam_mask.png")?; + + let image = sam.preprocess(&image)?; + let image = sam.unpreprocess(&image)?.to_dtype(DType::U8)?; + candle_examples::save_image(&image, "sam_input_scaled.png")?; Ok(()) } diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-examples/examples/segment-anything/model_sam.rs index acba7ef4..237163a3 100644 --- a/candle-examples/examples/segment-anything/model_sam.rs +++ b/candle-examples/examples/segment-anything/model_sam.rs @@ -87,7 +87,15 @@ impl Sam { Ok((low_res_mask, iou_predictions)) } - fn preprocess(&self, img: &Tensor) -> Result { + pub fn unpreprocess(&self, img: &Tensor) -> Result { + let img = img + .broadcast_mul(&self.pixel_std)? + .broadcast_add(&self.pixel_mean)?; + img.maximum(&img.zeros_like()?)? + .minimum(&(img.ones_like()? * 255.)?) + } + + pub fn preprocess(&self, img: &Tensor) -> Result { let (c, h, w) = img.dims3()?; let img = img .to_dtype(DType::F32)? From cfcbec9fc70aca2b0e08f382dec8634f88b61bce Mon Sep 17 00:00:00 2001 From: Zsombor Date: Fri, 8 Sep 2023 09:15:14 +0200 Subject: [PATCH 032/150] Add small customization to the build (#768) * Add ability to override the compiler used by NVCC from an environment variable * Allow relative paths in CANDLE_FLASH_ATTN_BUILD_DIR * Add the compilation failure to the readme, with a possible solution * Adjust the error message, and remove the special handling of the relative paths --- README.md | 11 +++++++++++ candle-flash-attn/build.rs | 24 ++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 140382c7..076f2363 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,17 @@ authentication token. See issue git submodule update --init ``` +#### Compiling with flash-attention fails + +``` +/usr/include/c++/11/bits/std_function.h:530:146: error: parameter packs not expanded with ‘...’: +``` + +This is a bug in gcc-11 triggered by the Cuda compiler. To fix this, install a different, supported gcc version - for example gcc-10, and specify the path to the compiler in the CANDLE_NVCC_CCBIN environment variable. +``` +env CANDLE_NVCC_CCBIN=/usr/lib/gcc/x86_64-linux-gnu/10 cargo ... +``` + #### Tracking down errors You can set `RUST_BACKTRACE=1` to be provided with backtraces when a candle diff --git a/candle-flash-attn/build.rs b/candle-flash-attn/build.rs index 2a3b7eb1..4cc7e5fb 100644 --- a/candle-flash-attn/build.rs +++ b/candle-flash-attn/build.rs @@ -57,9 +57,17 @@ fn main() -> Result<()> { #[allow(clippy::redundant_clone)] out_dir.clone() } - Ok(build_dir) => PathBuf::from(build_dir), + Ok(build_dir) => + { + let path = PathBuf::from(build_dir); + path.canonicalize().expect(&format!("Directory doesn't exists: {} (the current directory is {})", &path.display(), std::env::current_dir()?.display())) + } }; set_cuda_include_dir()?; + + let ccbin_env = std::env::var("CANDLE_NVCC_CCBIN"); + println!("cargo:rerun-if-env-changed=CANDLE_NVCC_CCBIN"); + let compute_cap = compute_cap()?; let out_file = build_dir.join("libflashattention.a"); @@ -95,14 +103,21 @@ fn main() -> Result<()> { .args(["--default-stream", "per-thread"]) .arg("-Icutlass/include") .arg("--expt-relaxed-constexpr") - .arg(cu_file); + .arg("--verbose"); + if let Ok(ccbin_path) = &ccbin_env { + command + .arg("-allow-unsupported-compiler") + .args(["-ccbin", ccbin_path]); + } + command.arg(cu_file); let output = command .spawn() .context("failed spawning nvcc")? .wait_with_output()?; if !output.status.success() { anyhow::bail!( - "nvcc error while compiling:\n\n# stdout\n{:#}\n\n# stderr\n{:#}", + "nvcc error while executing compiling: {:?}\n\n# stdout\n{:#}\n\n# stderr\n{:#}", + &command, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ) @@ -122,7 +137,8 @@ fn main() -> Result<()> { .wait_with_output()?; if !output.status.success() { anyhow::bail!( - "nvcc error while linking:\n\n# stdout\n{:#}\n\n# stderr\n{:#}", + "nvcc error while linking: {:?}\n\n# stdout\n{:#}\n\n# stderr\n{:#}", + &command, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ) From 0e250aee4fcff8991c086ba0606a90db92b4e488 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 08:38:13 +0100 Subject: [PATCH 033/150] Shape with holes (#770) * Shape with holes. * rustfmt. --- candle-core/src/shape.rs | 168 +++++++++++++++++++++++++++++++++++++ candle-core/src/tensor.rs | 13 ++- candle-flash-attn/build.rs | 9 +- 3 files changed, 184 insertions(+), 6 deletions(-) diff --git a/candle-core/src/shape.rs b/candle-core/src/shape.rs index 9617d1ac..b1f56817 100644 --- a/candle-core/src/shape.rs +++ b/candle-core/src/shape.rs @@ -482,3 +482,171 @@ mod tests { assert_eq!(shape.stride_contiguous(), [458 * 792, 458, 1]); } } + +pub trait ShapeWithOneHole { + fn into_shape(self, el_count: usize) -> Result; +} + +impl> ShapeWithOneHole for S { + fn into_shape(self, _el_count: usize) -> Result { + Ok(self.into()) + } +} + +impl ShapeWithOneHole for ((),) { + fn into_shape(self, el_count: usize) -> Result { + Ok(el_count.into()) + } +} + +impl ShapeWithOneHole for ((), usize) { + fn into_shape(self, el_count: usize) -> Result { + let ((), d1) = self; + if el_count % d1 != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d1}") + } + Ok((el_count / d1, d1).into()) + } +} + +impl ShapeWithOneHole for (usize, ()) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, ()) = self; + if el_count % d1 != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d1}") + } + Ok((d1, el_count / d1).into()) + } +} + +impl ShapeWithOneHole for ((), usize, usize) { + fn into_shape(self, el_count: usize) -> Result { + let ((), d1, d2) = self; + let d = d1 * d2; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((el_count / d, d1, d2).into()) + } +} + +impl ShapeWithOneHole for (usize, (), usize) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, (), d2) = self; + let d = d1 * d2; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, el_count / d, d2).into()) + } +} + +impl ShapeWithOneHole for (usize, usize, ()) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, d2, ()) = self; + let d = d1 * d2; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, d2, el_count / d).into()) + } +} + +impl ShapeWithOneHole for ((), usize, usize, usize) { + fn into_shape(self, el_count: usize) -> Result { + let ((), d1, d2, d3) = self; + let d = d1 * d2 * d3; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((el_count / d, d1, d2, d3).into()) + } +} + +impl ShapeWithOneHole for (usize, (), usize, usize) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, (), d2, d3) = self; + let d = d1 * d2 * d3; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, el_count / d, d2, d3).into()) + } +} + +impl ShapeWithOneHole for (usize, usize, (), usize) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, d2, (), d3) = self; + let d = d1 * d2 * d3; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, d2, el_count / d, d3).into()) + } +} + +impl ShapeWithOneHole for (usize, usize, usize, ()) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, d2, d3, ()) = self; + let d = d1 * d2 * d3; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, d2, d3, el_count / d).into()) + } +} + +impl ShapeWithOneHole for ((), usize, usize, usize, usize) { + fn into_shape(self, el_count: usize) -> Result { + let ((), d1, d2, d3, d4) = self; + let d = d1 * d2 * d3 * d4; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((el_count / d, d1, d2, d3, d4).into()) + } +} + +impl ShapeWithOneHole for (usize, (), usize, usize, usize) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, (), d2, d3, d4) = self; + let d = d1 * d2 * d3 * d4; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, el_count / d, d2, d3, d4).into()) + } +} + +impl ShapeWithOneHole for (usize, usize, (), usize, usize) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, d2, (), d3, d4) = self; + let d = d1 * d2 * d3 * d4; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, d2, el_count / d, d3, d4).into()) + } +} + +impl ShapeWithOneHole for (usize, usize, usize, (), usize) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, d2, d3, (), d4) = self; + let d = d1 * d2 * d3 * d4; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, d2, d3, el_count / d, d4).into()) + } +} + +impl ShapeWithOneHole for (usize, usize, usize, usize, ()) { + fn into_shape(self, el_count: usize) -> Result { + let (d1, d2, d3, d4, ()) = self; + let d = d1 * d2 * d3 * d4; + if el_count % d != 0 { + crate::bail!("tensor number of elements {el_count} is not divisible by {d}") + } + Ok((d1, d2, d3, d4, el_count / d).into()) + } +} diff --git a/candle-core/src/tensor.rs b/candle-core/src/tensor.rs index 1eca694c..6bb3d740 100644 --- a/candle-core/src/tensor.rs +++ b/candle-core/src/tensor.rs @@ -1685,12 +1685,15 @@ impl Tensor { Ok(from_storage(storage, shape, BackpropOp::none(), true)) } - // TODO: Do we want to allow target shape using -1 on some dimensions? /// Reshape returns a tensor with the target shape provided that the number of elements of the /// original tensor is the same. /// If the input tensor is contiguous, this is a view on the original data. Otherwise this uses /// a new storage and copies the data over, the returned tensor is always contiguous. /// + /// The shape can be specified using a tuple of `usize` and at most one `()` in which case + /// the behavior is the same as when using `-1` in PyTorch: this dimension size is adjusted so + /// as to match the number of elements in the tensor. + /// /// ```rust /// # use candle_core::{Tensor, DType, Device, D}; /// let a = Tensor::zeros((2, 3), DType::F32, &Device::Cpu)?; @@ -1700,10 +1703,14 @@ impl Tensor { /// /// let c = a.reshape((3, 2))?; /// assert_eq!(c.shape().dims(), &[3, 2]); + /// + /// let c = a.reshape((2, (), 1))?; + /// assert_eq!(c.shape().dims(), &[2, 3, 1]); + /// /// # Ok::<(), candle_core::Error>(()) /// ``` - pub fn reshape>(&self, shape: S) -> Result { - let shape = shape.into(); + pub fn reshape(&self, s: S) -> Result { + let shape = s.into_shape(self.elem_count())?; if shape.elem_count() != self.elem_count() { return Err(Error::ShapeMismatchBinaryOp { lhs: self.shape().clone(), diff --git a/candle-flash-attn/build.rs b/candle-flash-attn/build.rs index 4cc7e5fb..64275fda 100644 --- a/candle-flash-attn/build.rs +++ b/candle-flash-attn/build.rs @@ -57,10 +57,13 @@ fn main() -> Result<()> { #[allow(clippy::redundant_clone)] out_dir.clone() } - Ok(build_dir) => - { + Ok(build_dir) => { let path = PathBuf::from(build_dir); - path.canonicalize().expect(&format!("Directory doesn't exists: {} (the current directory is {})", &path.display(), std::env::current_dir()?.display())) + path.canonicalize().expect(&format!( + "Directory doesn't exists: {} (the current directory is {})", + &path.display(), + std::env::current_dir()?.display() + )) } }; set_cuda_include_dir()?; From 989a4807b151f08c651b5027cc1b547a59adf966 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 08:50:27 +0100 Subject: [PATCH 034/150] Use shape with holes. (#771) --- .../examples/segment-anything/model_image_encoder.rs | 4 ++-- .../examples/segment-anything/model_mask_decoder.rs | 2 +- .../examples/segment-anything/model_prompt_encoder.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-examples/examples/segment-anything/model_image_encoder.rs index f5db2830..79e52d47 100644 --- a/candle-examples/examples/segment-anything/model_image_encoder.rs +++ b/candle-examples/examples/segment-anything/model_image_encoder.rs @@ -123,7 +123,7 @@ fn get_rel_pos(q_size: usize, k_size: usize, rel_pos: &Tensor) -> Result let relative_coords = relative_coords.to_dtype(DType::U32)?; rel_pos_resized .index_select(&relative_coords.reshape(d1 * d2)?, 0)? - .reshape((d1, d2, rel_pos_resized.dim(1)?)) + .reshape((d1, d2, ())) } impl Module for Attention { @@ -243,7 +243,7 @@ fn window_unpartition( ))? .transpose(2, 3)? .contiguous()? - .reshape((b, h_p, w_p, windows.elem_count() / b / h_p / w_p))?; + .reshape((b, h_p, w_p, ()))?; let xs = if h_p > h { xs.narrow(1, 0, h)? } else { xs }; let xs = if w_p > w { xs.narrow(2, 0, w)? } else { xs }; Ok(xs) diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-examples/examples/segment-anything/model_mask_decoder.rs index 1ef46eeb..acbfeeea 100644 --- a/candle-examples/examples/segment-anything/model_mask_decoder.rs +++ b/candle-examples/examples/segment-anything/model_mask_decoder.rs @@ -214,7 +214,7 @@ impl MaskDecoder { let hyper_in = Tensor::stack(hyper_in_list.as_slice(), 1)?; let (b, c, h, w) = upscaled_embedding.dims4()?; let masks = hyper_in.matmul(&upscaled_embedding.reshape((b, c, h * w))?)?; - let masks = masks.reshape((b, masks.elem_count() / b / h / w, h, w))?; + let masks = masks.reshape((b, (), h, w))?; // Generate mask quality predictions. let iou_pred = self.iou_prediction_head.forward(&iou_token_out)?; diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-examples/examples/segment-anything/model_prompt_encoder.rs index c6ffffd2..aab0c4fd 100644 --- a/candle-examples/examples/segment-anything/model_prompt_encoder.rs +++ b/candle-examples/examples/segment-anything/model_prompt_encoder.rs @@ -28,10 +28,10 @@ impl PostionEmbeddingRandom { let x_embed = (Tensor::arange(0u32, w as u32, device)?.to_dtype(DType::F32)? + 0.5)?; let y_embed = (Tensor::arange(0u32, h as u32, device)?.to_dtype(DType::F32)? + 0.5)?; let x_embed = (x_embed / w as f64)? - .reshape((1, w))? + .reshape((1, ()))? .broadcast_as((h, w))?; let y_embed = (y_embed / h as f64)? - .reshape((h, 1))? + .reshape(((), 1))? .broadcast_as((h, w))?; let coords = Tensor::stack(&[&x_embed, &y_embed], D::Minus1)?; self.pe_encoding(&coords)?.permute((2, 0, 1)) @@ -163,7 +163,7 @@ impl PromptEncoder { fn embed_boxes(&self, boxes: &Tensor) -> Result { let boxes = (boxes + 0.5)?; - let coords = boxes.reshape((boxes.elem_count() / 4, 2, 2))?; + let coords = boxes.reshape(((), 2, 2))?; let corner_embedding = self .pe_layer .forward_with_coords(&coords, self.input_image_size)?; @@ -200,7 +200,7 @@ impl PromptEncoder { let dense_embeddings = match masks { None => { let emb = self.no_mask_embed.embeddings(); - emb.reshape((1, emb.elem_count(), 1, 1))?.expand(( + emb.reshape((1, (), 1, 1))?.expand(( 1, emb.elem_count(), self.image_embedding_size.0, From c1453f00b11c9dd12c5aa81fb4355ce47d22d477 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 09:39:10 +0100 Subject: [PATCH 035/150] Improve the safetensor loading in the segment-anything example. (#772) * Improve the safetensor loading in the segment-anything example. * Properly handle the labels when embedding the point prompts. --- .../examples/segment-anything/main.rs | 7 +++++- .../segment-anything/model_prompt_encoder.rs | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index 89d5b56c..c53c1010 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -110,7 +110,7 @@ pub fn main() -> anyhow::Result<()> { let image = if args.image.ends_with(".safetensors") { let mut tensors = candle::safetensors::load(&args.image, &device)?; - match tensors.remove("image") { + let image = match tensors.remove("image") { Some(image) => image, None => { if tensors.len() != 1 { @@ -118,6 +118,11 @@ pub fn main() -> anyhow::Result<()> { } tensors.into_values().next().unwrap() } + }; + if image.rank() == 4 { + image.get(0)? + } else { + image } } else { candle_examples::load_image(args.image, Some(model_sam::IMAGE_SIZE))?.to_device(&device)? diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-examples/examples/segment-anything/model_prompt_encoder.rs index aab0c4fd..e4291ebb 100644 --- a/candle-examples/examples/segment-anything/model_prompt_encoder.rs +++ b/candle-examples/examples/segment-anything/model_prompt_encoder.rs @@ -157,7 +157,28 @@ impl PromptEncoder { let point_embedding = self .pe_layer .forward_with_coords(&points, self.input_image_size)?; - // TODO: tweak based on labels. + let zeros = point_embedding.zeros_like()?; + let point_embeddings = labels.lt(&labels.zeros_like()?)?.where_cond( + &self + .not_a_point_embed + .embeddings() + .broadcast_as(zeros.shape())?, + &point_embedding, + )?; + let labels0 = labels.eq(&labels.zeros_like()?)?.where_cond( + &self.point_embeddings[0] + .embeddings() + .broadcast_as(zeros.shape())?, + &zeros, + )?; + let point_embedding = (point_embedding + labels0)?; + let labels1 = labels.eq(&labels.ones_like()?)?.where_cond( + &self.point_embeddings[1] + .embeddings() + .broadcast_as(zeros.shape())?, + &zeros, + )?; + let point_embedding = (point_embedding + labels1)?; Ok(point_embedding) } From 28c87f6a34e594aca5f558bceebc4c0a9c95911a Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 12:26:56 +0100 Subject: [PATCH 036/150] Automatic mask generator + point base mask (#773) * Add more to the automatic mask generator. * Add the target point. * Fix. * Remove the allow-unused. * Mask post-processing. --- .../examples/segment-anything/main.rs | 58 ++++-- .../segment-anything/model_image_encoder.rs | 6 +- .../segment-anything/model_mask_decoder.rs | 4 +- .../segment-anything/model_prompt_encoder.rs | 6 +- .../examples/segment-anything/model_sam.rs | 181 +++++++++++++++++- .../segment-anything/model_transformer.rs | 6 +- candle-examples/src/lib.rs | 30 ++- 7 files changed, 249 insertions(+), 42 deletions(-) diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index c53c1010..0f0c0482 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -1,6 +1,5 @@ //! SAM: Segment Anything Model //! https://github.com/facebookresearch/segment-anything -#![allow(unused)] #[cfg(feature = "mkl")] extern crate intel_mkl_src; @@ -14,7 +13,7 @@ pub mod model_prompt_encoder; pub mod model_sam; pub mod model_transformer; -use candle::{DType, IndexOp, Result, Tensor, D}; +use candle::{DType, Result, Tensor}; use candle_nn::{Linear, Module, VarBuilder}; use clap::Parser; @@ -101,6 +100,15 @@ struct Args { /// Run on CPU rather than on GPU. #[arg(long)] cpu: bool, + + #[arg(long)] + generate_masks: bool, + + #[arg(long)] + point_x: Option, + + #[arg(long)] + point_y: Option, } pub fn main() -> anyhow::Result<()> { @@ -108,7 +116,7 @@ pub fn main() -> anyhow::Result<()> { let device = candle_examples::device(args.cpu)?; - let image = if args.image.ends_with(".safetensors") { + let (image, initial_h, initial_w) = if args.image.ends_with(".safetensors") { let mut tensors = candle::safetensors::load(&args.image, &device)?; let image = match tensors.remove("image") { Some(image) => image, @@ -119,13 +127,16 @@ pub fn main() -> anyhow::Result<()> { tensors.into_values().next().unwrap() } }; - if image.rank() == 4 { + let image = if image.rank() == 4 { image.get(0)? } else { image - } + }; + let (_c, h, w) = image.dims3()?; + (image, h, w) } else { - candle_examples::load_image(args.image, Some(model_sam::IMAGE_SIZE))?.to_device(&device)? + let (image, h, w) = candle_examples::load_image(args.image, Some(model_sam::IMAGE_SIZE))?; + (image.to_device(&device)?, h, w) }; println!("loaded image {image:?}"); @@ -142,19 +153,30 @@ pub fn main() -> anyhow::Result<()> { let vb = VarBuilder::from_safetensors(vec![weights], DType::F32, &device); let sam = model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)?; // sam_vit_b - let (mask, iou_predictions) = sam.forward(&image, false)?; - println!("mask:\n{mask}"); - println!("iou_predictions: {iou_predictions:?}"); + if args.generate_masks { + // Default options similar to the Python version. + sam.generate_masks( + &image, + /* points_per_side */ 32, + /* crop_n_layer */ 0, + /* crop_overlap_ratio */ 512. / 1500., + /* crop_n_points_downscale_factor */ 1, + )? + } else { + let point = args.point_x.zip(args.point_y); + let (mask, iou_predictions) = sam.forward(&image, point, false)?; + println!("mask:\n{mask}"); + println!("iou_predictions: {iou_predictions:?}"); - // Save the mask as an image. - let mask = mask.ge(&mask.zeros_like()?)?; - let mask = (mask * 255.)?.squeeze(0)?; - let (_one, h, w) = mask.dims3()?; - let mask = mask.expand((3, h, w))?; - candle_examples::save_image(&mask, "sam_mask.png")?; + // Save the mask as an image. + let mask = (mask.ge(&mask.zeros_like()?)? * 255.)?; + let (_one, h, w) = mask.dims3()?; + let mask = mask.expand((3, h, w))?; + candle_examples::save_image_resize(&mask, "sam_mask.png", initial_h, initial_w)?; - let image = sam.preprocess(&image)?; - let image = sam.unpreprocess(&image)?.to_dtype(DType::U8)?; - candle_examples::save_image(&image, "sam_input_scaled.png")?; + let image = sam.preprocess(&image)?; + let image = sam.unpreprocess(&image)?.to_dtype(DType::U8)?; + candle_examples::save_image(&image, "sam_input_scaled.png")?; + } Ok(()) } diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-examples/examples/segment-anything/model_image_encoder.rs index 79e52d47..f1b76e23 100644 --- a/candle-examples/examples/segment-anything/model_image_encoder.rs +++ b/candle-examples/examples/segment-anything/model_image_encoder.rs @@ -1,4 +1,4 @@ -use candle::{DType, IndexOp, Result, Tensor, D}; +use candle::{DType, IndexOp, Result, Tensor}; use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; #[derive(Debug)] @@ -37,7 +37,6 @@ struct Attention { proj: Linear, num_heads: usize, scale: f64, - use_rel_pos: bool, rel_pos_hw: Option<(Tensor, Tensor)>, } @@ -66,7 +65,6 @@ impl Attention { proj, num_heads, scale, - use_rel_pos, rel_pos_hw, }) } @@ -272,7 +270,6 @@ impl Module for Block { #[derive(Debug)] pub struct ImageEncoderViT { - img_size: usize, patch_embed: PatchEmbed, blocks: Vec, neck_conv1: candle_nn::Conv2d, @@ -350,7 +347,6 @@ impl ImageEncoderViT { None }; Ok(Self { - img_size, patch_embed, blocks, neck_conv1, diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-examples/examples/segment-anything/model_mask_decoder.rs index acbfeeea..598af1f6 100644 --- a/candle-examples/examples/segment-anything/model_mask_decoder.rs +++ b/candle-examples/examples/segment-anything/model_mask_decoder.rs @@ -1,4 +1,4 @@ -use candle::{DType, IndexOp, Result, Tensor, D}; +use candle::{IndexOp, Result, Tensor}; use candle_nn::{Linear, Module, VarBuilder}; use crate::model_transformer::TwoWayTransformer; @@ -188,7 +188,7 @@ impl MaskDecoder { // Expand per-image data in batch direction to be per mask let src = repeat_interleave(image_embeddings, tokens.dim(0)?, 0)?; - let src = (src + dense_prompt_embeddings)?; + let src = src.broadcast_add(dense_prompt_embeddings)?; let pos_src = repeat_interleave(image_pe, tokens.dim(0)?, 0)?; let (b, c, h, w) = src.dims4()?; diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-examples/examples/segment-anything/model_prompt_encoder.rs index e4291ebb..b401a900 100644 --- a/candle-examples/examples/segment-anything/model_prompt_encoder.rs +++ b/candle-examples/examples/segment-anything/model_prompt_encoder.rs @@ -1,5 +1,5 @@ use candle::{DType, IndexOp, Result, Tensor, D}; -use candle_nn::{Linear, Module, VarBuilder}; +use candle_nn::VarBuilder; #[derive(Debug)] struct PostionEmbeddingRandom { @@ -24,7 +24,6 @@ impl PostionEmbeddingRandom { fn forward(&self, h: usize, w: usize) -> Result { let device = self.positional_encoding_gaussian_matrix.device(); - let grid = Tensor::ones((h, w), DType::F32, device)?; let x_embed = (Tensor::arange(0u32, w as u32, device)?.to_dtype(DType::F32)? + 0.5)?; let y_embed = (Tensor::arange(0u32, h as u32, device)?.to_dtype(DType::F32)? + 0.5)?; let x_embed = (x_embed / w as f64)? @@ -157,8 +156,9 @@ impl PromptEncoder { let point_embedding = self .pe_layer .forward_with_coords(&points, self.input_image_size)?; + let labels = labels.unsqueeze(2)?.broadcast_as(point_embedding.shape())?; let zeros = point_embedding.zeros_like()?; - let point_embeddings = labels.lt(&labels.zeros_like()?)?.where_cond( + let point_embedding = labels.lt(&labels.zeros_like()?)?.where_cond( &self .not_a_point_embed .embeddings() diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-examples/examples/segment-anything/model_sam.rs index 237163a3..884559af 100644 --- a/candle-examples/examples/segment-anything/model_sam.rs +++ b/candle-examples/examples/segment-anything/model_sam.rs @@ -1,5 +1,5 @@ -use candle::{DType, IndexOp, Result, Tensor, D}; -use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use candle::{DType, IndexOp, Result, Tensor}; +use candle_nn::{Module, VarBuilder}; use crate::model_image_encoder::ImageEncoderViT; use crate::model_mask_decoder::MaskDecoder; @@ -70,12 +70,30 @@ impl Sam { }) } - pub fn forward(&self, img: &Tensor, multimask_output: bool) -> Result<(Tensor, Tensor)> { + pub fn forward( + &self, + img: &Tensor, + point: Option<(f64, f64)>, + multimask_output: bool, + ) -> Result<(Tensor, Tensor)> { + let (_c, original_h, original_w) = img.dims3()?; let img = self.preprocess(img)?.unsqueeze(0)?; let img_embeddings = self.image_encoder.forward(&img)?; let image_pe = self.prompt_encoder.get_dense_pe()?; + let points = match point { + None => None, + Some((x, y)) => { + let points = Tensor::new( + &[[[x as f32 * original_w as f32, y as f32 * original_h as f32]]], + img.device(), + )?; + let labels = Tensor::ones((1, 1), DType::F32, img.device())?; + Some((points, labels)) + } + }; + let points = points.as_ref().map(|(x, y)| (x, y)); let (sparse_prompt_embeddings, dense_prompt_embeddings) = - self.prompt_encoder.forward(None, None, None)?; + self.prompt_encoder.forward(points, None, None)?; let (low_res_mask, iou_predictions) = self.mask_decoder.forward( &img_embeddings, &image_pe, @@ -83,8 +101,11 @@ impl Sam { &dense_prompt_embeddings, multimask_output, )?; - // TODO: post-processing. - Ok((low_res_mask, iou_predictions)) + let mask = low_res_mask + .upsample_nearest2d(IMAGE_SIZE, IMAGE_SIZE)? + .get(0)? + .i((.., ..original_h, ..original_w))?; + Ok((mask, iou_predictions)) } pub fn unpreprocess(&self, img: &Tensor) -> Result { @@ -96,7 +117,7 @@ impl Sam { } pub fn preprocess(&self, img: &Tensor) -> Result { - let (c, h, w) = img.dims3()?; + let (_c, h, w) = img.dims3()?; let img = img .to_dtype(DType::F32)? .broadcast_sub(&self.pixel_mean)? @@ -107,4 +128,150 @@ impl Sam { let img = img.pad_with_zeros(1, 0, IMAGE_SIZE - h)?; img.pad_with_zeros(2, 0, IMAGE_SIZE - w) } + + fn process_crop(&self, img: &Tensor, cb: CropBox, point_grids: &[(f64, f64)]) -> Result<()> { + // Crop the image and calculate embeddings. + let img = img.i((.., cb.y0..cb.y1, cb.x0..cb.x1))?; + let img = self.preprocess(&img)?.unsqueeze(0)?; + let img_embeddings = self.image_encoder.forward(&img)?; + + let crop_w = cb.x1 - cb.x0; + let crop_h = cb.y1 - cb.y0; + + // Generate masks for this crop. + let image_pe = self.prompt_encoder.get_dense_pe()?; + let points = point_grids + .iter() + .map(|&(x, y)| vec![x as f32 * crop_w as f32, y as f32 * crop_h as f32]) + .collect::>(); + for points in points.chunks(64) { + let points_len = points.len(); + let in_points = Tensor::new(points.to_vec(), img.device())?.unsqueeze(1)?; + let in_labels = Tensor::ones((points_len, 1), DType::F32, img.device())?; + let (sparse_prompt_embeddings, dense_prompt_embeddings) = + self.prompt_encoder + .forward(Some((&in_points, &in_labels)), None, None)?; + let (_low_res_mask, iou_predictions) = self.mask_decoder.forward( + &img_embeddings, + &image_pe, + &sparse_prompt_embeddings, + &dense_prompt_embeddings, + /* multimask_output */ true, + )?; + + println!("{cb:?} {iou_predictions}"); + } + + // Remove duplicates within this crop. + + // Return to the original image frame. + Ok(()) + } + + pub fn generate_masks( + &self, + img: &Tensor, + points_per_side: usize, + crop_n_layer: usize, + crop_overlap_ratio: f64, + crop_n_points_downscale_factor: usize, + ) -> Result<()> { + let (_c, h, w) = img.dims3()?; + let point_grids = build_all_layer_point_grids( + points_per_side, + crop_n_layer, + crop_n_points_downscale_factor, + ); + let crop_boxes = generate_crop_boxes((h, w), crop_n_layer, crop_overlap_ratio); + for crop_box in crop_boxes.into_iter() { + let layer_idx = crop_box.layer_idx; + self.process_crop(img, crop_box, &point_grids[layer_idx])? + } + // TODO: remove duplicates + Ok(()) + } +} + +#[derive(Debug)] +struct CropBox { + x0: usize, + y0: usize, + x1: usize, + y1: usize, + layer_idx: usize, +} + +impl CropBox { + fn new(x0: usize, y0: usize, x1: usize, y1: usize, layer_idx: usize) -> Self { + Self { + x0, + y0, + x1, + y1, + layer_idx, + } + } +} + +fn generate_crop_boxes( + (im_h, im_w): (usize, usize), + n_layers: usize, + overlap_ratio: f64, +) -> Vec { + fn crop_len(orig_len: usize, n_crops: usize, overlap: usize) -> usize { + f64::ceil((overlap * (n_crops - 1) + orig_len) as f64 / n_crops as f64) as usize + } + + let short_side = usize::min(im_h, im_w); + + let mut crop_boxes = Vec::new(); + + // Original image. + crop_boxes.push(CropBox::new(0, 0, im_w, im_h, 0)); + + for layer_idx in 1..=n_layers { + let n_crops_per_side = 1 << layer_idx; + let overlap = (overlap_ratio * short_side as f64 * 2. / n_crops_per_side as f64) as usize; + let crop_w = crop_len(im_w, n_crops_per_side, overlap); + let crop_h = crop_len(im_w, n_crops_per_side, overlap); + + for i_x in 0..n_crops_per_side { + let x0 = (crop_w - overlap) * i_x; + for i_y in 0..n_crops_per_side { + let y0 = (crop_h - overlap) * i_y; + let x1 = usize::min(im_w, x0 + crop_w); + let y1 = usize::min(im_h, y0 + crop_h); + crop_boxes.push(CropBox::new(x0, y0, x1, y1, layer_idx)); + } + } + } + + crop_boxes +} + +// Generates a 2D grid of points evenly spaced in [0,1]x[0,1]. +fn build_point_grid(n_per_side: usize) -> Vec<(f64, f64)> { + let offset = 1f64 / (2 * n_per_side) as f64; + let mut points = Vec::with_capacity(n_per_side * n_per_side); + for i_x in 0..n_per_side { + let x = offset + i_x as f64 / n_per_side as f64; + for i_y in 0..n_per_side { + let y = offset + i_y as f64 / n_per_side as f64; + points.push((x, y)) + } + } + points +} + +fn build_all_layer_point_grids( + n_per_side: usize, + n_layers: usize, + scale_per_layer: usize, +) -> Vec> { + let mut points_by_layer = Vec::with_capacity(n_layers + 1); + for i in 0..=n_layers { + let n_points = n_per_side / scale_per_layer.pow(i as u32); + points_by_layer.push(build_point_grid(n_points)) + } + points_by_layer } diff --git a/candle-examples/examples/segment-anything/model_transformer.rs b/candle-examples/examples/segment-anything/model_transformer.rs index 044dce9b..e4de27cb 100644 --- a/candle-examples/examples/segment-anything/model_transformer.rs +++ b/candle-examples/examples/segment-anything/model_transformer.rs @@ -1,4 +1,4 @@ -use candle::{DType, IndexOp, Result, Tensor, D}; +use candle::{Result, Tensor}; use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; #[derive(Debug)] @@ -7,7 +7,6 @@ struct Attention { k_proj: Linear, v_proj: Linear, out_proj: Linear, - internal_dim: usize, num_heads: usize, } @@ -28,7 +27,6 @@ impl Attention { k_proj, v_proj, out_proj, - internal_dim, num_heads, }) } @@ -85,7 +83,6 @@ impl TwoWayAttentionBlock { skip_first_layer_pe: bool, vb: VarBuilder, ) -> Result { - let self_attn = Attention::new(embedding_dim, num_heads, 1, vb.pp("self_attn"))?; let norm1 = layer_norm(embedding_dim, 1e-5, vb.pp("norm1"))?; let norm2 = layer_norm(embedding_dim, 1e-5, vb.pp("norm2"))?; let norm3 = layer_norm(embedding_dim, 1e-5, vb.pp("norm3"))?; @@ -204,7 +201,6 @@ impl TwoWayTransformer { image_pe: &Tensor, point_embedding: &Tensor, ) -> Result<(Tensor, Tensor)> { - let (bs, c, h, w) = image_embedding.dims4()?; let image_embedding = image_embedding.flatten_from(2)?.permute((0, 2, 1))?; let image_pe = image_pe.flatten_from(2)?.permute((0, 2, 1))?; diff --git a/candle-examples/src/lib.rs b/candle-examples/src/lib.rs index 66cd2f99..c14b2d6b 100644 --- a/candle-examples/src/lib.rs +++ b/candle-examples/src/lib.rs @@ -19,10 +19,11 @@ pub fn device(cpu: bool) -> Result { pub fn load_image>( p: P, resize_longest: Option, -) -> Result { +) -> Result<(Tensor, usize, usize)> { let img = image::io::Reader::open(p)? .decode() .map_err(candle::Error::wrap)?; + let (initial_h, initial_w) = (img.height() as usize, img.width() as usize); let img = match resize_longest { None => img, Some(resize_longest) => { @@ -41,7 +42,8 @@ pub fn load_image>( let (height, width) = (img.height() as usize, img.width() as usize); let img = img.to_rgb8(); let data = img.into_raw(); - Tensor::from_vec(data, (height, width, 3), &Device::Cpu)?.permute((2, 0, 1)) + let data = Tensor::from_vec(data, (height, width, 3), &Device::Cpu)?.permute((2, 0, 1))?; + Ok((data, initial_h, initial_w)) } pub fn load_image_and_resize>( @@ -80,3 +82,27 @@ pub fn save_image>(img: &Tensor, p: P) -> Result<()> { image.save(p).map_err(candle::Error::wrap)?; Ok(()) } + +pub fn save_image_resize>( + img: &Tensor, + p: P, + h: usize, + w: usize, +) -> Result<()> { + let p = p.as_ref(); + let (channel, height, width) = img.dims3()?; + if channel != 3 { + candle::bail!("save_image expects an input of shape (3, height, width)") + } + let img = img.permute((1, 2, 0))?.flatten_all()?; + let pixels = img.to_vec1::()?; + let image: image::ImageBuffer, Vec> = + match image::ImageBuffer::from_raw(width as u32, height as u32, pixels) { + Some(image) => image, + None => candle::bail!("error saving image {p:?}"), + }; + let image = image::DynamicImage::from(image); + let image = image.resize_to_fill(w as u32, h as u32, image::imageops::FilterType::CatmullRom); + image.save(p).map_err(candle::Error::wrap)?; + Ok(()) +} From 98172d46fa866ecaad2afd52777e9ac9a5f15b52 Mon Sep 17 00:00:00 2001 From: zmlcc Date: Fri, 8 Sep 2023 20:29:40 +0800 Subject: [PATCH 037/150] Fix some errors about BlockQ8_1 (#776) * use int8 type instead of uint8 for BlockQ8_1.qs The uint8 type of BlockQ8_1.qs causes great loss for negative weights Ref: https://github.com/ggerganov/llama.cpp/blob/ebc96086af49fe70108cafcea6ab4bebd658a41a/ggml.c#L904 Signed-off-by: Zhang Miaolei * fix sum error in vec_dot of BlockQ4_1 Ref: https://github.com/ggerganov/llama.cpp/blob/ebc96086af49fe70108cafcea6ab4bebd658a41a/ggml.c#L2840 Signed-off-by: Zhang Miaolei * fix sum error in vec_dot of BlockQ5_1 Ref: https://github.com/ggerganov/llama.cpp/blob/ebc96086af49fe70108cafcea6ab4bebd658a41a/ggml.c#L3490 Signed-off-by: Zhang Miaolei --------- Signed-off-by: Zhang Miaolei --- candle-core/src/quantized/k_quants.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/candle-core/src/quantized/k_quants.rs b/candle-core/src/quantized/k_quants.rs index 65fd6a6e..a0fe455c 100644 --- a/candle-core/src/quantized/k_quants.rs +++ b/candle-core/src/quantized/k_quants.rs @@ -85,7 +85,7 @@ const _: () = assert!(std::mem::size_of::() == 34); pub struct BlockQ8_1 { pub(crate) d: f16, pub(crate) s: f16, - pub(crate) qs: [u8; QK8_1], + pub(crate) qs: [i8; QK8_1], } const _: () = assert!(std::mem::size_of::() == 36); @@ -278,6 +278,7 @@ impl GgmlType for BlockQ4_1 { } sumf += sumi as f32 * f16::to_f32(xs.d) * f16::to_f32(ys.d) + + f16::to_f32(xs.m) * f16::to_f32(ys.s) } Ok(sumf) } @@ -471,6 +472,7 @@ impl GgmlType for BlockQ5_1 { } sumf += sumi as f32 * f16::to_f32(xs.d) * f16::to_f32(ys.d) + + f16::to_f32(xs.m) * f16::to_f32(ys.s) } Ok(sumf) } @@ -652,8 +654,8 @@ impl GgmlType for BlockQ8_1 { for j in 0..Self::BLCK_SIZE / 2 { let v0 = xs[j] * id; let v1 = xs[j + Self::BLCK_SIZE / 2] * id; - ys.qs[j] = f32::round(v0) as u8; - ys.qs[j + Self::BLCK_SIZE / 2] = f32::round(v1) as u8; + ys.qs[j] = f32::round(v0) as i8; + ys.qs[j + Self::BLCK_SIZE / 2] = f32::round(v1) as i8; sum += ys.qs[j] as i32 + ys.qs[j + Self::BLCK_SIZE / 2] as i32; } ys.s = f16::from_f32(sum as f32) * ys.d; From e5703d2f56ce24652e7ae85dc74484681e4dbcb9 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 14:04:34 +0100 Subject: [PATCH 038/150] Draw the mask on a merged image. (#775) * Draw the mask on a merged image. * Clippy fix. * Enable the target point by default. * Add to the readme. --- README.md | 3 + .../examples/segment-anything/main.rs | 59 ++++++++++++++++--- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 076f2363..9e5f938a 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,8 @@ Check out our [examples](./candle-examples/examples/): - [yolo-v3](./candle-examples/examples/yolo-v3/) and [yolo-v8](./candle-examples/examples/yolo-v8/): object detection and pose estimation models. + [segment-anything](./candle-examples/examples/segment-anything/): image + segmentation model with prompt. Run them using the following commands: ``` cargo run --example whisper --release @@ -76,6 +78,7 @@ cargo run --example dinov2 --release -- --image path/to/myinput.jpg cargo run --example quantized --release cargo run --example yolo-v3 --release -- myimage.jpg cargo run --example yolo-v8 --release -- myimage.jpg # for pose estimation, add --task pose +cargo run --example segment-anything --release -- --image myimage.jpg ``` In order to use **CUDA** add `--features cuda` to the example command line. If diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index 0f0c0482..c5095c0e 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -104,11 +104,11 @@ struct Args { #[arg(long)] generate_masks: bool, - #[arg(long)] - point_x: Option, + #[arg(long, default_value_t = 0.5)] + point_x: f64, - #[arg(long)] - point_y: Option, + #[arg(long, default_value_t = 0.5)] + point_y: f64, } pub fn main() -> anyhow::Result<()> { @@ -135,7 +135,7 @@ pub fn main() -> anyhow::Result<()> { let (_c, h, w) = image.dims3()?; (image, h, w) } else { - let (image, h, w) = candle_examples::load_image(args.image, Some(model_sam::IMAGE_SIZE))?; + let (image, h, w) = candle_examples::load_image(&args.image, Some(model_sam::IMAGE_SIZE))?; (image.to_device(&device)?, h, w) }; println!("loaded image {image:?}"); @@ -163,7 +163,7 @@ pub fn main() -> anyhow::Result<()> { /* crop_n_points_downscale_factor */ 1, )? } else { - let point = args.point_x.zip(args.point_y); + let point = Some((args.point_x, args.point_y)); let (mask, iou_predictions) = sam.forward(&image, point, false)?; println!("mask:\n{mask}"); println!("iou_predictions: {iou_predictions:?}"); @@ -174,9 +174,50 @@ pub fn main() -> anyhow::Result<()> { let mask = mask.expand((3, h, w))?; candle_examples::save_image_resize(&mask, "sam_mask.png", initial_h, initial_w)?; - let image = sam.preprocess(&image)?; - let image = sam.unpreprocess(&image)?.to_dtype(DType::U8)?; - candle_examples::save_image(&image, "sam_input_scaled.png")?; + if !args.image.ends_with(".safetensors") { + let mut img = image::io::Reader::open(&args.image)? + .decode() + .map_err(candle::Error::wrap)?; + let mask_pixels = mask.permute((1, 2, 0))?.flatten_all()?.to_vec1::()?; + let mask_img: image::ImageBuffer, Vec> = + match image::ImageBuffer::from_raw(w as u32, h as u32, mask_pixels) { + Some(image) => image, + None => anyhow::bail!("error saving merged image"), + }; + let mask_img = image::DynamicImage::from(mask_img).resize_to_fill( + img.width(), + img.height(), + image::imageops::FilterType::CatmullRom, + ); + for x in 0..img.width() { + for y in 0..img.height() { + let mask_p = imageproc::drawing::Canvas::get_pixel(&mask_img, x, y); + if mask_p.0[0] > 100 { + let mut img_p = imageproc::drawing::Canvas::get_pixel(&img, x, y); + img_p.0[2] = 255 - (255 - img_p.0[2]) / 2; + img_p.0[1] /= 2; + img_p.0[0] /= 2; + imageproc::drawing::Canvas::draw_pixel(&mut img, x, y, img_p) + } + } + } + match point { + Some((x, y)) => { + let (x, y) = ( + (x * img.width() as f64) as i32, + (y * img.height() as f64) as i32, + ); + imageproc::drawing::draw_filled_circle( + &img, + (x, y), + 3, + image::Rgba([255, 0, 0, 200]), + ) + .save("sam_merged.jpg")? + } + None => img.save("sam_merged.jpg")?, + }; + } } Ok(()) } From 158ff3c609b22ed998dea5283738cc1ed13aa592 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 15:31:29 +0100 Subject: [PATCH 039/150] Add tracing to segment-anything (#777) * Tracing support for segment-anything. * More tracing. * Handle the empty slice case. --- candle-core/src/cuda_backend.rs | 3 ++ .../examples/segment-anything/main.rs | 47 ++++++++++++++++--- .../segment-anything/model_image_encoder.rs | 39 ++++++++++++--- .../segment-anything/model_mask_decoder.rs | 12 ++++- .../segment-anything/model_prompt_encoder.rs | 4 ++ candle-examples/examples/whisper/main.rs | 1 - 6 files changed, 90 insertions(+), 16 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index 2180be5e..cb00441f 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -1889,6 +1889,9 @@ impl BackendStorage for CudaStorage { let src_shape = src_l.shape(); let dims = src_shape.dims(); let el_count = src_shape.elem_count(); + if el_count == 0 { + return Ok(()); + } let cfg = LaunchConfig::for_num_elems(el_count as u32); let dev = &self.device; let ds = dev.htod_copy([dims, src_l.stride()].concat()).w()?; diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index c5095c0e..a749ba2a 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -14,15 +14,17 @@ pub mod model_sam; pub mod model_transformer; use candle::{DType, Result, Tensor}; -use candle_nn::{Linear, Module, VarBuilder}; +use candle_nn::{Module, VarBuilder}; use clap::Parser; pub fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { - if bias { - candle_nn::linear(in_dim, out_dim, vb) + let inner = if bias { + candle_nn::linear(in_dim, out_dim, vb)? } else { - candle_nn::linear_no_bias(in_dim, out_dim, vb) - } + candle_nn::linear_no_bias(in_dim, out_dim, vb)? + }; + let span = tracing::span!(tracing::Level::TRACE, "linear"); + Ok(Linear { inner, span }) } #[derive(Debug)] @@ -62,6 +64,7 @@ pub struct MlpBlock { lin1: Linear, lin2: Linear, activation: candle_nn::Activation, + span: tracing::Span, } impl MlpBlock { @@ -71,24 +74,40 @@ impl MlpBlock { activation: candle_nn::Activation, vb: VarBuilder, ) -> Result { - let lin1 = candle_nn::linear(embedding_dim, mlp_dim, vb.pp("lin1"))?; - let lin2 = candle_nn::linear(mlp_dim, embedding_dim, vb.pp("lin2"))?; + let lin1 = linear(vb.pp("lin1"), embedding_dim, mlp_dim, true)?; + let lin2 = linear(vb.pp("lin2"), mlp_dim, embedding_dim, true)?; + let span = tracing::span!(tracing::Level::TRACE, "mlp-block"); Ok(Self { lin1, lin2, activation, + span, }) } } impl Module for MlpBlock { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); xs.apply(&self.lin1)? .apply(&self.activation)? .apply(&self.lin2) } } +#[derive(Debug)] +pub struct Linear { + inner: candle_nn::Linear, + span: tracing::Span, +} + +impl Module for Linear { + fn forward(&self, x: &Tensor) -> Result { + let _enter = self.span.enter(); + self.inner.forward(x) + } +} + #[derive(Parser)] struct Args { #[arg(long)] @@ -109,10 +128,24 @@ struct Args { #[arg(long, default_value_t = 0.5)] point_y: f64, + + /// Enable tracing (generates a trace-timestamp.json file). + #[arg(long)] + tracing: bool, } pub fn main() -> anyhow::Result<()> { + use tracing_chrome::ChromeLayerBuilder; + use tracing_subscriber::prelude::*; + let args = Args::parse(); + let _guard = if args.tracing { + let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); + tracing_subscriber::registry().with(chrome_layer).init(); + Some(guard) + } else { + None + }; let device = candle_examples::device(args.cpu)?; diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-examples/examples/segment-anything/model_image_encoder.rs index f1b76e23..f997170d 100644 --- a/candle-examples/examples/segment-anything/model_image_encoder.rs +++ b/candle-examples/examples/segment-anything/model_image_encoder.rs @@ -1,9 +1,10 @@ use candle::{DType, IndexOp, Result, Tensor}; -use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use candle_nn::{layer_norm, LayerNorm, Module, VarBuilder}; #[derive(Debug)] struct PatchEmbed { proj: candle_nn::Conv2d, + span: tracing::Span, } impl PatchEmbed { @@ -21,23 +22,28 @@ impl PatchEmbed { ..Default::default() }; let proj = candle_nn::conv2d(in_chans, embed_dim, k_size, cfg, vb.pp("proj"))?; - Ok(Self { proj }) + let span = tracing::span!(tracing::Level::TRACE, "patch-embed"); + Ok(Self { proj, span }) } } impl Module for PatchEmbed { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); xs.apply(&self.proj)?.permute((0, 2, 3, 1)) } } #[derive(Debug)] struct Attention { - qkv: Linear, - proj: Linear, + qkv: crate::Linear, + proj: crate::Linear, num_heads: usize, scale: f64, rel_pos_hw: Option<(Tensor, Tensor)>, + span: tracing::Span, + span_rel_pos: tracing::Span, + span_softmax: tracing::Span, } impl Attention { @@ -49,6 +55,9 @@ impl Attention { input_size: (usize, usize), vb: VarBuilder, ) -> Result { + let span = tracing::span!(tracing::Level::TRACE, "attention"); + let span_rel_pos = tracing::span!(tracing::Level::TRACE, "attn-rel-pos"); + let span_softmax = tracing::span!(tracing::Level::TRACE, "attn-sm"); let qkv = crate::linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; let proj = crate::linear(vb.pp("proj"), dim, dim, true)?; let head_dim = dim / num_heads; @@ -66,6 +75,9 @@ impl Attention { num_heads, scale, rel_pos_hw, + span, + span_rel_pos, + span_softmax, }) } @@ -126,6 +138,7 @@ fn get_rel_pos(q_size: usize, k_size: usize, rel_pos: &Tensor) -> Result impl Module for Attention { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let (b, h, w, c) = xs.dims4()?; let qkv = self .qkv @@ -137,8 +150,14 @@ impl Module for Attention { let k = qkv.i(1)?; let v = qkv.i(2)?; let attn = (&q * self.scale)?.matmul(&k.t()?)?; - let attn = self.add_decomposed_rel_pos(attn, &q, (h, w), (h, w))?; - let attn = candle_nn::ops::softmax_last_dim(&attn)?; + let attn = { + let _enter = self.span_rel_pos.enter(); + self.add_decomposed_rel_pos(attn, &q, (h, w), (h, w))? + }; + let attn = { + let _enter = self.span_softmax.enter(); + candle_nn::ops::softmax_last_dim(&attn)? + }; let attn = attn.matmul(&v)?; let attn = attn .reshape((b, self.num_heads, h, w, c / self.num_heads))? @@ -155,6 +174,7 @@ struct Block { norm2: LayerNorm, mlp: crate::MlpBlock, window_size: usize, + span: tracing::Span, } impl Block { @@ -183,12 +203,14 @@ impl Block { vb.pp("attn"), )?; let mlp = crate::MlpBlock::new(dim, dim * 4, candle_nn::Activation::Gelu, vb.pp("mlp"))?; + let span = tracing::span!(tracing::Level::TRACE, "ie-block"); Ok(Self { norm1, attn, norm2, mlp, window_size, + span, }) } } @@ -249,6 +271,7 @@ fn window_unpartition( impl Module for Block { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let shortcut = xs; let xs = self.norm1.forward(xs)?; let hw = (xs.dim(1)?, xs.dim(2)?); @@ -277,6 +300,7 @@ pub struct ImageEncoderViT { neck_conv2: candle_nn::Conv2d, neck_ln2: crate::LayerNorm2d, pos_embed: Option, + span: tracing::Span, } impl ImageEncoderViT { @@ -346,6 +370,7 @@ impl ImageEncoderViT { } else { None }; + let span = tracing::span!(tracing::Level::TRACE, "image-encoder-vit"); Ok(Self { patch_embed, blocks, @@ -354,12 +379,14 @@ impl ImageEncoderViT { neck_conv2, neck_ln2, pos_embed, + span, }) } } impl Module for ImageEncoderViT { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let xs = self.patch_embed.forward(xs)?; let mut xs = match &self.pos_embed { Some(pos_embed) => (xs + pos_embed)?, diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-examples/examples/segment-anything/model_mask_decoder.rs index 598af1f6..1f6d62a4 100644 --- a/candle-examples/examples/segment-anything/model_mask_decoder.rs +++ b/candle-examples/examples/segment-anything/model_mask_decoder.rs @@ -1,12 +1,13 @@ use candle::{IndexOp, Result, Tensor}; -use candle_nn::{Linear, Module, VarBuilder}; +use candle_nn::{Module, VarBuilder}; use crate::model_transformer::TwoWayTransformer; #[derive(Debug)] struct MlpMaskDecoder { - layers: Vec, + layers: Vec, sigmoid_output: bool, + span: tracing::Span, } impl MlpMaskDecoder { @@ -30,15 +31,18 @@ impl MlpMaskDecoder { let layer = crate::linear(vb.pp(i), in_dim, out_dim, true)?; layers.push(layer) } + let span = tracing::span!(tracing::Level::TRACE, "mlp-mask-decoder"); Ok(Self { layers, sigmoid_output, + span, }) } } impl Module for MlpMaskDecoder { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let mut xs = xs.clone(); for (i, layer) in self.layers.iter().enumerate() { xs = layer.forward(&xs)?; @@ -65,6 +69,7 @@ pub struct MaskDecoder { num_mask_tokens: usize, output_hypernetworks_mlps: Vec, transformer: TwoWayTransformer, + span: tracing::Span, } impl MaskDecoder { @@ -127,6 +132,7 @@ impl MaskDecoder { /* mlp_dim */ 2048, vb.pp("transformer"), )?; + let span = tracing::span!(tracing::Level::TRACE, "mask-decoder"); Ok(Self { iou_token, mask_tokens, @@ -137,6 +143,7 @@ impl MaskDecoder { num_mask_tokens, output_hypernetworks_mlps, transformer, + span, }) } @@ -148,6 +155,7 @@ impl MaskDecoder { dense_prompt_embeddings: &Tensor, multimask_output: bool, ) -> Result<(Tensor, Tensor)> { + let _enter = self.span.enter(); let (masks, iou_pred) = self.predict_masks( image_embeddings, image_pe, diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-examples/examples/segment-anything/model_prompt_encoder.rs index b401a900..40cc6e36 100644 --- a/candle-examples/examples/segment-anything/model_prompt_encoder.rs +++ b/candle-examples/examples/segment-anything/model_prompt_encoder.rs @@ -64,6 +64,7 @@ pub struct PromptEncoder { image_embedding_size: (usize, usize), input_image_size: (usize, usize), embed_dim: usize, + span: tracing::Span, } impl PromptEncoder { @@ -108,6 +109,7 @@ impl PromptEncoder { let emb = candle_nn::embedding(1, embed_dim, vb_e.pp(i))?; point_embeddings.push(emb) } + let span = tracing::span!(tracing::Level::TRACE, "prompt-encoder"); Ok(Self { pe_layer, point_embeddings, @@ -121,6 +123,7 @@ impl PromptEncoder { image_embedding_size, input_image_size, embed_dim, + span, }) } @@ -201,6 +204,7 @@ impl PromptEncoder { boxes: Option<&Tensor>, masks: Option<&Tensor>, ) -> Result<(Tensor, Tensor)> { + let _enter = self.span.enter(); let se_points = match points { Some((coords, labels)) => Some(self.embed_points(coords, labels, boxes.is_none())?), None => None, diff --git a/candle-examples/examples/whisper/main.rs b/candle-examples/examples/whisper/main.rs index 5dd8ee20..dbe9cc8d 100644 --- a/candle-examples/examples/whisper/main.rs +++ b/candle-examples/examples/whisper/main.rs @@ -431,7 +431,6 @@ fn main() -> Result<()> { let args = Args::parse(); let _guard = if args.tracing { - println!("tracing..."); let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); tracing_subscriber::registry().with(chrome_layer).init(); Some(guard) From 0906acab9186fbb14a2268e12dd66c13b0877f3e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 19:11:34 +0100 Subject: [PATCH 040/150] Automatic mask generation (#779) * A few more contiguous fixes for cuda. * Mask generation. * Generic bbox. * Generate all the masks. --- .../examples/segment-anything/main.rs | 16 ++- .../segment-anything/model_mask_decoder.rs | 2 +- .../examples/segment-anything/model_sam.rs | 103 ++++++++++++++++-- .../segment-anything/model_transformer.rs | 6 +- candle-examples/examples/yolo-v3/main.rs | 4 +- candle-examples/examples/yolo-v8/main.rs | 12 +- candle-examples/src/object_detection.rs | 8 +- 7 files changed, 125 insertions(+), 26 deletions(-) diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index a749ba2a..4627248c 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -188,13 +188,25 @@ pub fn main() -> anyhow::Result<()> { if args.generate_masks { // Default options similar to the Python version. - sam.generate_masks( + let bboxes = sam.generate_masks( &image, /* points_per_side */ 32, /* crop_n_layer */ 0, /* crop_overlap_ratio */ 512. / 1500., /* crop_n_points_downscale_factor */ 1, - )? + )?; + for (idx, bbox) in bboxes.iter().enumerate() { + println!("{bbox:?}"); + let mask = (&bbox.data.to_dtype(DType::U8)? * 255.)?; + let (h, w) = mask.dims2()?; + let mask = mask.broadcast_as((3, h, w))?; + candle_examples::save_image_resize( + &mask, + format!("sam_mask{idx}.png"), + initial_h, + initial_w, + )?; + } } else { let point = Some((args.point_x, args.point_y)); let (mask, iou_predictions) = sam.forward(&image, point, false)?; diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-examples/examples/segment-anything/model_mask_decoder.rs index 1f6d62a4..c02b44a7 100644 --- a/candle-examples/examples/segment-anything/model_mask_decoder.rs +++ b/candle-examples/examples/segment-anything/model_mask_decoder.rs @@ -219,7 +219,7 @@ impl MaskDecoder { let h = mlp.forward(&mask_tokens_out.i((.., i))?)?; hyper_in_list.push(h) } - let hyper_in = Tensor::stack(hyper_in_list.as_slice(), 1)?; + let hyper_in = Tensor::stack(hyper_in_list.as_slice(), 1)?.contiguous()?; let (b, c, h, w) = upscaled_embedding.dims4()?; let masks = hyper_in.matmul(&upscaled_embedding.reshape((b, c, h * w))?)?; let masks = masks.reshape((b, (), h, w))?; diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-examples/examples/segment-anything/model_sam.rs index 884559af..ade976c1 100644 --- a/candle-examples/examples/segment-anything/model_sam.rs +++ b/candle-examples/examples/segment-anything/model_sam.rs @@ -8,6 +8,11 @@ use crate::model_prompt_encoder::PromptEncoder; const PROMPT_EMBED_DIM: usize = 256; pub const IMAGE_SIZE: usize = 1024; const VIT_PATCH_SIZE: usize = 16; +const PRED_IOU_THRESH: f32 = 0.88; +const STABILITY_SCORE_OFFSET: f32 = 1.0; +const STABILITY_SCORE_THRESHOLD: f32 = 0.95; +const MODEL_MASK_THRESHOLD: f32 = 0.0; +const CROP_NMS_THRESH: f32 = 0.7; #[derive(Debug)] pub struct Sam { @@ -129,7 +134,12 @@ impl Sam { img.pad_with_zeros(2, 0, IMAGE_SIZE - w) } - fn process_crop(&self, img: &Tensor, cb: CropBox, point_grids: &[(f64, f64)]) -> Result<()> { + fn process_crop( + &self, + img: &Tensor, + cb: CropBox, + point_grids: &[(f64, f64)], + ) -> Result>> { // Crop the image and calculate embeddings. let img = img.i((.., cb.y0..cb.y1, cb.x0..cb.x1))?; let img = self.preprocess(&img)?.unsqueeze(0)?; @@ -144,28 +154,86 @@ impl Sam { .iter() .map(|&(x, y)| vec![x as f32 * crop_w as f32, y as f32 * crop_h as f32]) .collect::>(); + + let mut bboxes = Vec::new(); for points in points.chunks(64) { + // Run the model on this batch. let points_len = points.len(); let in_points = Tensor::new(points.to_vec(), img.device())?.unsqueeze(1)?; let in_labels = Tensor::ones((points_len, 1), DType::F32, img.device())?; let (sparse_prompt_embeddings, dense_prompt_embeddings) = self.prompt_encoder .forward(Some((&in_points, &in_labels)), None, None)?; - let (_low_res_mask, iou_predictions) = self.mask_decoder.forward( + + let (low_res_mask, iou_predictions) = self.mask_decoder.forward( &img_embeddings, &image_pe, &sparse_prompt_embeddings, &dense_prompt_embeddings, /* multimask_output */ true, )?; + let low_res_mask = low_res_mask.flatten(0, 1)?; + let iou_predictions = iou_predictions.flatten(0, 1)?.to_vec1::()?; + let dev = low_res_mask.device(); - println!("{cb:?} {iou_predictions}"); + for (i, iou) in iou_predictions.iter().enumerate() { + // Filter by predicted IoU. + if *iou < PRED_IOU_THRESH { + continue; + } + let low_res_mask = low_res_mask.get(i)?; + + // Calculate stability score. + let bound = Tensor::new(MODEL_MASK_THRESHOLD + STABILITY_SCORE_OFFSET, dev)? + .broadcast_as(low_res_mask.shape())?; + let intersections = low_res_mask + .ge(&bound)? + .to_dtype(DType::F32)? + .sum_all()? + .to_vec0::()?; + let bound = Tensor::new(MODEL_MASK_THRESHOLD - STABILITY_SCORE_OFFSET, dev)? + .broadcast_as(low_res_mask.shape())?; + let unions = low_res_mask + .ge(&bound)? + .to_dtype(DType::F32)? + .sum_all()? + .to_vec0::()?; + let stability_score = intersections / unions; + if stability_score < STABILITY_SCORE_THRESHOLD { + continue; + } + + // Threshold masks and calculate boxes. + let low_res_mask = low_res_mask + .ge(&Tensor::new(0f32, dev)?.broadcast_as(low_res_mask.shape())?)? + .to_dtype(DType::U32)?; + let low_res_mask_per_x = low_res_mask.sum(0)?.to_vec1::()?; + let low_res_mask_per_y = low_res_mask.sum(1)?.to_vec1::()?; + let min_max_x = min_max_indexes(&low_res_mask_per_x); + let min_max_y = min_max_indexes(&low_res_mask_per_y); + if let Some(((x0, x1), (y0, y1))) = min_max_x.zip(min_max_y) { + let bbox = candle_examples::object_detection::Bbox { + xmin: x0 as f32, + ymin: y0 as f32, + xmax: x1 as f32, + ymax: y1 as f32, + confidence: *iou, + data: low_res_mask, + }; + bboxes.push(bbox); + } + // TODO: + // Filter boxes that touch crop boundaries + // Compress to RLE. + } } + let mut bboxes = vec![bboxes]; // Remove duplicates within this crop. + candle_examples::object_detection::non_maximum_suppression(&mut bboxes, CROP_NMS_THRESH); - // Return to the original image frame. - Ok(()) + // TODO: Return to the original image frame. + Ok(bboxes.remove(0)) } pub fn generate_masks( @@ -175,7 +243,7 @@ impl Sam { crop_n_layer: usize, crop_overlap_ratio: f64, crop_n_points_downscale_factor: usize, - ) -> Result<()> { + ) -> Result>> { let (_c, h, w) = img.dims3()?; let point_grids = build_all_layer_point_grids( points_per_side, @@ -183,12 +251,31 @@ impl Sam { crop_n_points_downscale_factor, ); let crop_boxes = generate_crop_boxes((h, w), crop_n_layer, crop_overlap_ratio); + let mut bboxes = Vec::new(); for crop_box in crop_boxes.into_iter() { let layer_idx = crop_box.layer_idx; - self.process_crop(img, crop_box, &point_grids[layer_idx])? + let b = self.process_crop(img, crop_box, &point_grids[layer_idx])?; + bboxes.extend(b) } // TODO: remove duplicates - Ok(()) + Ok(bboxes) + } +} + +// Return the first and last indexes i for which values[i] > 0 +fn min_max_indexes(values: &[u32]) -> Option<(usize, usize)> { + let (mut min_i, mut max_i) = (usize::MAX, usize::MIN); + for (i, &s) in values.iter().enumerate() { + if s == 0 { + continue; + } + min_i = usize::min(i, min_i); + max_i = usize::max(i, max_i); + } + if max_i < min_i { + None + } else { + Some((min_i, max_i)) } } diff --git a/candle-examples/examples/segment-anything/model_transformer.rs b/candle-examples/examples/segment-anything/model_transformer.rs index e4de27cb..e12aac08 100644 --- a/candle-examples/examples/segment-anything/model_transformer.rs +++ b/candle-examples/examples/segment-anything/model_transformer.rs @@ -45,9 +45,9 @@ impl Attention { } fn forward(&self, q: &Tensor, k: &Tensor, v: &Tensor) -> Result { - let q = self.q_proj.forward(q)?; - let k = self.k_proj.forward(k)?; - let v = self.v_proj.forward(v)?; + let q = self.q_proj.forward(&q.contiguous()?)?; + let k = self.k_proj.forward(&k.contiguous()?)?; + let v = self.v_proj.forward(&v.contiguous()?)?; let q = self.separate_heads(&q)?; let k = self.separate_heads(&k)?; diff --git a/candle-examples/examples/yolo-v3/main.rs b/candle-examples/examples/yolo-v3/main.rs index 5e388921..20021b45 100644 --- a/candle-examples/examples/yolo-v3/main.rs +++ b/candle-examples/examples/yolo-v3/main.rs @@ -46,7 +46,7 @@ pub fn report( let (npreds, pred_size) = pred.dims2()?; let nclasses = pred_size - 5; // The bounding boxes grouped by (maximum) class index. - let mut bboxes: Vec> = (0..nclasses).map(|_| vec![]).collect(); + let mut bboxes: Vec>> = (0..nclasses).map(|_| vec![]).collect(); // Extract the bounding boxes for which confidence is above the threshold. for index in 0..npreds { let pred = Vec::::try_from(pred.get(index)?)?; @@ -65,7 +65,7 @@ pub fn report( xmax: pred[0] + pred[2] / 2., ymax: pred[1] + pred[3] / 2., confidence, - keypoints: vec![], + data: (), }; bboxes[class_index].push(bbox) } diff --git a/candle-examples/examples/yolo-v8/main.rs b/candle-examples/examples/yolo-v8/main.rs index d5c5ac1c..2017b5be 100644 --- a/candle-examples/examples/yolo-v8/main.rs +++ b/candle-examples/examples/yolo-v8/main.rs @@ -64,7 +64,7 @@ pub fn report_detect( let (pred_size, npreds) = pred.dims2()?; let nclasses = pred_size - 4; // The bounding boxes grouped by (maximum) class index. - let mut bboxes: Vec> = (0..nclasses).map(|_| vec![]).collect(); + let mut bboxes: Vec>>> = (0..nclasses).map(|_| vec![]).collect(); // Extract the bounding boxes for which confidence is above the threshold. for index in 0..npreds { let pred = Vec::::try_from(pred.i((.., index))?)?; @@ -83,7 +83,7 @@ pub fn report_detect( xmax: pred[0] + pred[2] / 2., ymax: pred[1] + pred[3] / 2., confidence, - keypoints: vec![], + data: vec![], }; bboxes[class_index].push(bbox) } @@ -176,7 +176,7 @@ pub fn report_pose( xmax: pred[0] + pred[2] / 2., ymax: pred[1] + pred[3] / 2., confidence, - keypoints, + data: keypoints, }; bboxes.push(bbox) } @@ -204,7 +204,7 @@ pub fn report_pose( image::Rgb([255, 0, 0]), ); } - for kp in b.keypoints.iter() { + for kp in b.data.iter() { if kp.mask < 0.6 { continue; } @@ -219,8 +219,8 @@ pub fn report_pose( } for &(idx1, idx2) in KP_CONNECTIONS.iter() { - let kp1 = &b.keypoints[idx1]; - let kp2 = &b.keypoints[idx2]; + let kp1 = &b.data[idx1]; + let kp2 = &b.data[idx2]; if kp1.mask < 0.6 || kp2.mask < 0.6 { continue; } diff --git a/candle-examples/src/object_detection.rs b/candle-examples/src/object_detection.rs index c7c60136..ce579316 100644 --- a/candle-examples/src/object_detection.rs +++ b/candle-examples/src/object_detection.rs @@ -1,12 +1,12 @@ /// A bounding box around an object. #[derive(Debug, Clone)] -pub struct Bbox { +pub struct Bbox { pub xmin: f32, pub ymin: f32, pub xmax: f32, pub ymax: f32, pub confidence: f32, - pub keypoints: Vec, + pub data: D, } #[derive(Debug, Clone, Copy, PartialEq)] @@ -17,7 +17,7 @@ pub struct KeyPoint { } /// Intersection over union of two bounding boxes. -pub fn iou(b1: &Bbox, b2: &Bbox) -> f32 { +pub fn iou(b1: &Bbox, b2: &Bbox) -> f32 { let b1_area = (b1.xmax - b1.xmin + 1.) * (b1.ymax - b1.ymin + 1.); let b2_area = (b2.xmax - b2.xmin + 1.) * (b2.ymax - b2.ymin + 1.); let i_xmin = b1.xmin.max(b2.xmin); @@ -28,7 +28,7 @@ pub fn iou(b1: &Bbox, b2: &Bbox) -> f32 { i_area / (b1_area + b2_area - i_area) } -pub fn non_maximum_suppression(bboxes: &mut [Vec], threshold: f32) { +pub fn non_maximum_suppression(bboxes: &mut [Vec>], threshold: f32) { // Perform non-maximum suppression. for bboxes_for_class in bboxes.iter_mut() { bboxes_for_class.sort_by(|b1, b2| b2.confidence.partial_cmp(&b1.confidence).unwrap()); From acf8f10ae17d7f472dc1a634fbd7358a79d7b4d4 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 20:13:29 +0100 Subject: [PATCH 041/150] Get the comparison operation to work on scalar values. (#780) * Get the comparison operation to work on scalar values. * Add some time measurement. --- candle-core/src/lib.rs | 1 + candle-core/src/scalar.rs | 23 ++++++++++++++++++ candle-core/src/tensor.rs | 24 ++++++++++++------- .../examples/segment-anything/main.rs | 7 +++++- .../segment-anything/model_prompt_encoder.rs | 6 ++--- 5 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 candle-core/src/scalar.rs diff --git a/candle-core/src/lib.rs b/candle-core/src/lib.rs index a0347416..3504b0a6 100644 --- a/candle-core/src/lib.rs +++ b/candle-core/src/lib.rs @@ -59,6 +59,7 @@ mod op; pub mod pickle; pub mod quantized; pub mod safetensors; +pub mod scalar; pub mod shape; mod storage; mod strided_index; diff --git a/candle-core/src/scalar.rs b/candle-core/src/scalar.rs new file mode 100644 index 00000000..43e1f4c8 --- /dev/null +++ b/candle-core/src/scalar.rs @@ -0,0 +1,23 @@ +use crate::{Result, Tensor, WithDType}; + +pub enum TensorScalar { + Tensor(Tensor), + Scalar(Tensor), +} + +pub trait TensorOrScalar { + fn to_tensor_scalar(self) -> Result; +} + +impl TensorOrScalar for &Tensor { + fn to_tensor_scalar(self) -> Result { + Ok(TensorScalar::Tensor(self.clone())) + } +} + +impl TensorOrScalar for T { + fn to_tensor_scalar(self) -> Result { + let scalar = Tensor::new(self, &crate::Device::Cpu)?; + Ok(TensorScalar::Scalar(scalar)) + } +} diff --git a/candle-core/src/tensor.rs b/candle-core/src/tensor.rs index 6bb3d740..8ad9322b 100644 --- a/candle-core/src/tensor.rs +++ b/candle-core/src/tensor.rs @@ -4,6 +4,7 @@ use crate::backend::{BackendDevice, BackendStorage}; use crate::op::{ BackpropOp, BinaryOp, CmpOp, CustomOp1, CustomOp2, CustomOp3, Op, ReduceOp, UnaryOp, }; +use crate::scalar::TensorOrScalar; use crate::shape::{Dim, Dims}; use crate::{storage::Storage, DType, Device, Error, Layout, Result, Shape}; use std::sync::{Arc, RwLock}; @@ -776,8 +777,15 @@ impl Tensor { /// comparison operation is specified by the `op` argument. /// /// The returned tensor has the same shape as the original tensors and uses `u8` elements. - pub fn cmp(&self, rhs: &Self, op: CmpOp) -> Result { - let shape = self.same_shape_binary_op(rhs, "cmp")?; + pub fn cmp(&self, rhs: T, op: CmpOp) -> Result { + let rhs = match rhs.to_tensor_scalar()? { + crate::scalar::TensorScalar::Tensor(rhs) => rhs, + crate::scalar::TensorScalar::Scalar(rhs) => rhs + .to_dtype(self.dtype())? + .to_device(self.device())? + .broadcast_as(self.shape())?, + }; + let shape = self.same_shape_binary_op(&rhs, "cmp")?; let storage = self .storage() .cmp(op, &rhs.storage(), self.layout(), rhs.layout())?; @@ -786,36 +794,36 @@ impl Tensor { } /// Element-wise equality. - pub fn eq(&self, rhs: &Self) -> Result { + pub fn eq(&self, rhs: T) -> Result { self.cmp(rhs, CmpOp::Eq) } /// Element-wise non-equality. - pub fn ne(&self, rhs: &Self) -> Result { + pub fn ne(&self, rhs: T) -> Result { self.cmp(rhs, CmpOp::Ne) } /// Element-wise comparison with lower-than, the returned tensor uses value 1 where `self < /// rhs` and 0 otherwise. - pub fn lt(&self, rhs: &Self) -> Result { + pub fn lt(&self, rhs: T) -> Result { self.cmp(rhs, CmpOp::Lt) } /// Element-wise comparison with greater-than, the returned tensor uses value 1 where `self > /// rhs` and 0 otherwise. - pub fn gt(&self, rhs: &Self) -> Result { + pub fn gt(&self, rhs: T) -> Result { self.cmp(rhs, CmpOp::Gt) } /// Element-wise comparison with greater-equal, the returned tensor uses value 1 where `self >= /// rhs` and 0 otherwise. - pub fn ge(&self, rhs: &Self) -> Result { + pub fn ge(&self, rhs: T) -> Result { self.cmp(rhs, CmpOp::Ge) } /// Element-wise comparison with lower-equal, the returned tensor uses value 1 where `self <= /// rhs` and 0 otherwise. - pub fn le(&self, rhs: &Self) -> Result { + pub fn le(&self, rhs: T) -> Result { self.cmp(rhs, CmpOp::Le) } diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index 4627248c..ce8e3bb4 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -209,12 +209,17 @@ pub fn main() -> anyhow::Result<()> { } } else { let point = Some((args.point_x, args.point_y)); + let start_time = std::time::Instant::now(); let (mask, iou_predictions) = sam.forward(&image, point, false)?; + println!( + "mask generated in {:.2}s", + start_time.elapsed().as_secs_f32() + ); println!("mask:\n{mask}"); println!("iou_predictions: {iou_predictions:?}"); // Save the mask as an image. - let mask = (mask.ge(&mask.zeros_like()?)? * 255.)?; + let mask = (mask.ge(0f32)? * 255.)?; let (_one, h, w) = mask.dims3()?; let mask = mask.expand((3, h, w))?; candle_examples::save_image_resize(&mask, "sam_mask.png", initial_h, initial_w)?; diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-examples/examples/segment-anything/model_prompt_encoder.rs index 40cc6e36..7bbe8419 100644 --- a/candle-examples/examples/segment-anything/model_prompt_encoder.rs +++ b/candle-examples/examples/segment-anything/model_prompt_encoder.rs @@ -161,21 +161,21 @@ impl PromptEncoder { .forward_with_coords(&points, self.input_image_size)?; let labels = labels.unsqueeze(2)?.broadcast_as(point_embedding.shape())?; let zeros = point_embedding.zeros_like()?; - let point_embedding = labels.lt(&labels.zeros_like()?)?.where_cond( + let point_embedding = labels.lt(0f32)?.where_cond( &self .not_a_point_embed .embeddings() .broadcast_as(zeros.shape())?, &point_embedding, )?; - let labels0 = labels.eq(&labels.zeros_like()?)?.where_cond( + let labels0 = labels.eq(0f32)?.where_cond( &self.point_embeddings[0] .embeddings() .broadcast_as(zeros.shape())?, &zeros, )?; let point_embedding = (point_embedding + labels0)?; - let labels1 = labels.eq(&labels.ones_like()?)?.where_cond( + let labels1 = labels.eq(1f32)?.where_cond( &self.point_embeddings[1] .embeddings() .broadcast_as(zeros.shape())?, From 057f7909bc5544b0dcb0e6d5360288633dbf443f Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 8 Sep 2023 21:58:56 +0100 Subject: [PATCH 042/150] Accelerate support for gelu. (#782) --- candle-core/src/accelerate.rs | 32 ++++++++++++++++++++++++++++++++ candle-core/src/op.rs | 18 ++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/candle-core/src/accelerate.rs b/candle-core/src/accelerate.rs index 87e0ee8d..1cb34e19 100644 --- a/candle-core/src/accelerate.rs +++ b/candle-core/src/accelerate.rs @@ -370,6 +370,38 @@ pub fn vd_sqr(a: &[f64], y: &mut [f64]) { y.iter_mut().zip(a.iter()).for_each(|(y, a)| *y = *a * *a) } +#[inline] +pub fn vs_tanh_inplace(y: &mut [f32]) { + unsafe { ffi::vvtanhf(y.as_mut_ptr(), y.as_ptr(), &(y.len() as i32)) } +} + +#[inline] +pub fn vd_tanh_inplace(y: &mut [f64]) { + unsafe { ffi::vvtanh(y.as_mut_ptr(), y.as_ptr(), &(y.len() as i32)) } +} + +#[inline] +pub fn vs_gelu(vs: &[f32], ys: &mut [f32]) { + for (&v, y) in vs.iter().zip(ys.iter_mut()) { + *y = (2.0f32 / std::f32::consts::PI).sqrt() * v * (1.0 + 0.044715 * v * v) + } + vs_tanh_inplace(ys); + for (&v, y) in vs.iter().zip(ys.iter_mut()) { + *y = 0.5 * v * (1.0 + *y) + } +} + +#[inline] +pub fn vd_gelu(vs: &[f64], ys: &mut [f64]) { + for (&v, y) in vs.iter().zip(ys.iter_mut()) { + *y = (2.0f64 / std::f64::consts::PI).sqrt() * v * (1.0 + 0.044715 * v * v) + } + vd_tanh_inplace(ys); + for (&v, y) in vs.iter().zip(ys.iter_mut()) { + *y = 0.5 * v * (1.0 + *y) + } +} + macro_rules! binary_op { ($fn_name:ident, $ty:ty, $accelerate_name:ident) => { #[inline] diff --git a/candle-core/src/op.rs b/candle-core/src/op.rs index fbfc9c1a..9382b217 100644 --- a/candle-core/src/op.rs +++ b/candle-core/src/op.rs @@ -600,6 +600,24 @@ impl UnaryOpT for Gelu { fn f64_vec(xs: &[f64], ys: &mut [f64]) { crate::mkl::vd_gelu(xs, ys) } + + #[cfg(feature = "accelerate")] + const F32_VEC: bool = true; + + #[cfg(feature = "accelerate")] + #[inline(always)] + fn f32_vec(xs: &[f32], ys: &mut [f32]) { + crate::accelerate::vs_gelu(xs, ys) + } + + #[cfg(feature = "accelerate")] + const F64_VEC: bool = true; + + #[cfg(feature = "accelerate")] + #[inline(always)] + fn f64_vec(xs: &[f64], ys: &mut [f64]) { + crate::accelerate::vd_gelu(xs, ys) + } } impl UnaryOpT for Relu { From c88d6fd4b9615307c7689ec3133d09f16a6b7d77 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 9 Sep 2023 08:27:37 +0100 Subject: [PATCH 043/150] Remove set_training. (#784) --- candle-core/src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/candle-core/src/lib.rs b/candle-core/src/lib.rs index 3504b0a6..0a64a5a6 100644 --- a/candle-core/src/lib.rs +++ b/candle-core/src/lib.rs @@ -112,12 +112,6 @@ impl ToUsize2 for (usize, usize) { // A simple trait defining a module with forward method using a single argument. pub trait Module: std::fmt::Debug { fn forward(&self, xs: &Tensor) -> Result; - - /// Change the module to use training mode vs eval mode. - /// - /// The default implementation does nothing as this is only used for a couple modules such as - /// dropout or batch-normalization. - fn set_training(&mut self, _training: bool) {} } impl Module for quantized::QMatMul { From 976a1086ee8c30398582df1727ffb27679b2c2d7 Mon Sep 17 00:00:00 2001 From: YangNianYi Date: Sat, 9 Sep 2023 15:55:35 +0800 Subject: [PATCH 044/150] feat: u32 from_be_bytes (#765) --- candle-datasets/src/vision/mnist.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/candle-datasets/src/vision/mnist.rs b/candle-datasets/src/vision/mnist.rs index 30b0d01f..7a8240cf 100644 --- a/candle-datasets/src/vision/mnist.rs +++ b/candle-datasets/src/vision/mnist.rs @@ -9,12 +9,9 @@ use std::fs::File; use std::io::{self, BufReader, Read}; fn read_u32(reader: &mut T) -> Result { - let mut b = vec![0u8; 4]; + let mut b = [0u8; 4]; reader.read_exact(&mut b)?; - let (result, _) = b.iter().rev().fold((0u64, 1u64), |(s, basis), &x| { - (s + basis * u64::from(x), basis * 256) - }); - Ok(result as u32) + Ok(u32::from_be_bytes(b)) } fn check_magic_number(reader: &mut T, expected: u32) -> Result<()> { From 722c50bb0ce18d749edcf86268238e5d9c9ee57e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 9 Sep 2023 09:03:59 +0100 Subject: [PATCH 045/150] Use byteorder in mnist. (#785) --- candle-datasets/src/vision/mnist.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/candle-datasets/src/vision/mnist.rs b/candle-datasets/src/vision/mnist.rs index 7a8240cf..2dac883c 100644 --- a/candle-datasets/src/vision/mnist.rs +++ b/candle-datasets/src/vision/mnist.rs @@ -8,10 +8,9 @@ use parquet::file::reader::{FileReader, SerializedFileReader}; use std::fs::File; use std::io::{self, BufReader, Read}; -fn read_u32(reader: &mut T) -> Result { - let mut b = [0u8; 4]; - reader.read_exact(&mut b)?; - Ok(u32::from_be_bytes(b)) +fn read_u32(reader: &mut T) -> std::io::Result { + use byteorder::ReadBytesExt; + reader.read_u32::() } fn check_magic_number(reader: &mut T, expected: u32) -> Result<()> { From 3cd7e7b51dc7bf49215b136702f5bc3cd4642144 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 9 Sep 2023 10:46:09 +0100 Subject: [PATCH 046/150] Fuse the rel-pos additions via a custom-op. (#786) * Fuse the rel-pos additions via a custom-op. * Run with rayon. * Add more tracing. --- candle-examples/Cargo.toml | 1 + .../segment-anything/model_image_encoder.rs | 91 +++++++++++++++++-- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/candle-examples/Cargo.toml b/candle-examples/Cargo.toml index 9035eae0..6f8792a3 100644 --- a/candle-examples/Cargo.toml +++ b/candle-examples/Cargo.toml @@ -24,6 +24,7 @@ intel-mkl-src = { workspace = true, optional = true } cudarc = { workspace = true, optional = true } half = { workspace = true, optional = true } image = { workspace = true } +rayon = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-examples/examples/segment-anything/model_image_encoder.rs index f997170d..76cd15d0 100644 --- a/candle-examples/examples/segment-anything/model_image_encoder.rs +++ b/candle-examples/examples/segment-anything/model_image_encoder.rs @@ -34,6 +34,70 @@ impl Module for PatchEmbed { } } +// A custom op to make add_decomposed_rel_pos faster. Most of the time is spent on the final +// addition in the case where b = 12, q_h = q_w = 4096, k_h = k_w = 4096 +// (attn.reshape((b, q_h, q_w, k_h, k_w))? +// + rel_h.unsqueeze(4)?.broadcast_add(&rel_w.unsqueeze(3)?)?)? +// .reshape((b, q_h * q_w, k_h * k_w)) +// Ideally we would perform this operation in place but this is not supported in candle at the +// moment. We should also investigate using f16 rather than f32. +struct Add3(usize, usize, usize, usize, usize); +impl candle::CustomOp3 for Add3 { + fn name(&self) -> &'static str { + "add3" + } + + fn cpu_fwd( + &self, + s1: &candle::CpuStorage, + l1: &candle::Layout, + s2: &candle::CpuStorage, + l2: &candle::Layout, + s3: &candle::CpuStorage, + l3: &candle::Layout, + ) -> Result<(candle::CpuStorage, candle::Shape)> { + use rayon::prelude::*; + + let Add3(b, q_h, q_w, k_h, k_w) = *self; + let s1 = s1.as_slice::()?; + let s1 = match l1.contiguous_offsets() { + None => candle::bail!("input1 has to be contiguous"), + Some((o1, o2)) => &s1[o1..o2], + }; + let s2 = s2.as_slice::()?; + let s2 = match l2.contiguous_offsets() { + None => candle::bail!("input2 has to be contiguous"), + Some((o1, o2)) => &s2[o1..o2], + }; + let s3 = s3.as_slice::()?; + let s3 = match l3.contiguous_offsets() { + None => candle::bail!("input3 has to be contiguous"), + Some((o1, o2)) => &s3[o1..o2], + }; + let mut dst = vec![0f32; b * q_h * q_w * k_h * k_w]; + dst.par_chunks_exact_mut(k_h * k_w) + .enumerate() + .for_each(|(b_idx, dst)| { + let s1_idx = b_idx * k_h * k_w; + let s2_idx = b_idx * k_h; + let s3_idx = b_idx * k_w; + for h_idx in 0..k_h { + let s1_idx = s1_idx + h_idx * k_w; + let s2_idx = s2_idx + h_idx; + let dst_idx = h_idx * k_w; + for w_idx in 0..k_w { + let s1_idx = s1_idx + w_idx; + let s3_idx = s3_idx + w_idx; + let dst_idx = dst_idx + w_idx; + dst[dst_idx] = s1[s1_idx] + s2[s2_idx] + s3[s3_idx] + } + } + }); + let dst = candle::WithDType::to_cpu_storage_owned(dst); + Ok((dst, (b, q_h * q_w, k_h * k_w).into())) + } +} + #[derive(Debug)] struct Attention { qkv: crate::Linear, @@ -42,6 +106,7 @@ struct Attention { scale: f64, rel_pos_hw: Option<(Tensor, Tensor)>, span: tracing::Span, + span_matmul: tracing::Span, span_rel_pos: tracing::Span, span_softmax: tracing::Span, } @@ -56,6 +121,7 @@ impl Attention { vb: VarBuilder, ) -> Result { let span = tracing::span!(tracing::Level::TRACE, "attention"); + let span_matmul = tracing::span!(tracing::Level::TRACE, "attn-matmul"); let span_rel_pos = tracing::span!(tracing::Level::TRACE, "attn-rel-pos"); let span_softmax = tracing::span!(tracing::Level::TRACE, "attn-sm"); let qkv = crate::linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; @@ -76,6 +142,7 @@ impl Attention { scale, rel_pos_hw, span, + span_matmul, span_rel_pos, span_softmax, }) @@ -101,10 +168,16 @@ impl Attention { .transpose(1, 2)? // -> bwhc .contiguous()? .matmul(&r_w.broadcast_left(b)?.t()?.contiguous()?)? // bwhc,bwck -> bwhk - .transpose(1, 2)?; - (attn.reshape((b, q_h, q_w, k_h, k_w))? - + rel_h.unsqueeze(4)?.broadcast_add(&rel_w.unsqueeze(3)?)?)? - .reshape((b, q_h * q_w, k_h * k_w)) + .transpose(1, 2)? + .contiguous()?; + if attn.device().is_cpu() { + let op = Add3(b, q_h, q_w, k_h, k_w); + attn.apply_op3_no_bwd(&rel_h, &rel_w, &op) + } else { + (attn.reshape((b, q_h, q_w, k_h, k_w))? + + rel_h.unsqueeze(4)?.broadcast_add(&rel_w.unsqueeze(3)?)?)? + .reshape((b, q_h * q_w, k_h * k_w)) + } } None => Ok(attn), } @@ -149,7 +222,10 @@ impl Module for Attention { let q = qkv.i(0)?; let k = qkv.i(1)?; let v = qkv.i(2)?; - let attn = (&q * self.scale)?.matmul(&k.t()?)?; + let attn = { + let _enter = self.span_matmul.enter(); + (&q * self.scale)?.matmul(&k.t()?)? + }; let attn = { let _enter = self.span_rel_pos.enter(); self.add_decomposed_rel_pos(attn, &q, (h, w), (h, w))? @@ -158,7 +234,10 @@ impl Module for Attention { let _enter = self.span_softmax.enter(); candle_nn::ops::softmax_last_dim(&attn)? }; - let attn = attn.matmul(&v)?; + let attn = { + let _enter = self.span_matmul.enter(); + attn.matmul(&v)? + }; let attn = attn .reshape((b, self.num_heads, h, w, c / self.num_heads))? .permute((0, 2, 3, 1, 4))? From b7cd58473b4621268480d8bdba8812a38d510d5b Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 9 Sep 2023 15:10:06 +0100 Subject: [PATCH 047/150] TinyViT backbone for segment-anything. (#787) * TinyViT. * More TinyViT. * Add more to the tinyvit backbone. * Proper padding. * Plus ViT. * Add the tiniest vit spec. --- .../examples/segment-anything/main.rs | 1 + .../segment-anything/model_tiny_vit.rs | 563 ++++++++++++++++++ candle-nn/src/conv.rs | 26 +- 3 files changed, 577 insertions(+), 13 deletions(-) create mode 100644 candle-examples/examples/segment-anything/model_tiny_vit.rs diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index ce8e3bb4..3ef5762e 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -11,6 +11,7 @@ pub mod model_image_encoder; pub mod model_mask_decoder; pub mod model_prompt_encoder; pub mod model_sam; +pub mod model_tiny_vit; pub mod model_transformer; use candle::{DType, Result, Tensor}; diff --git a/candle-examples/examples/segment-anything/model_tiny_vit.rs b/candle-examples/examples/segment-anything/model_tiny_vit.rs new file mode 100644 index 00000000..36e4c578 --- /dev/null +++ b/candle-examples/examples/segment-anything/model_tiny_vit.rs @@ -0,0 +1,563 @@ +// Adapted from: +// https://github.com/ChaoningZhang/MobileSAM/blob/master/mobile_sam/modeling/tiny_vit_sam.py +#![allow(unused)] +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{Conv2dConfig, Module, VarBuilder}; + +const MBCONV_EXPAND_RATIO: usize = 4; +const MLP_RATIO: usize = 4; +const LOCAL_CONV_SIZE: usize = 3; +const IMG_SIZE: usize = 224; +const IN_CHANNELS: usize = 3; + +#[derive(Debug)] +struct Conv2dBN { + c: candle_nn::Conv2d, + bn: candle_nn::BatchNorm, +} + +impl Conv2dBN { + fn new(in_: usize, out: usize, ks: usize, cfg: Conv2dConfig, vb: VarBuilder) -> Result { + let c = candle_nn::conv2d(in_, out, ks, cfg, vb.pp("c"))?; + let bn = candle_nn::batch_norm(out, 1e-5, vb.pp("bn"))?; + Ok(Self { c, bn }) + } +} + +impl Module for Conv2dBN { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.c)?.apply(&self.bn) + } +} + +#[derive(Debug)] +struct PatchEmbed { + conv1: Conv2dBN, + conv2: Conv2dBN, +} + +impl PatchEmbed { + fn new(in_chans: usize, embed_dim: usize, vb: VarBuilder) -> Result { + let cfg = candle_nn::Conv2dConfig { + stride: 2, + padding: 1, + ..Default::default() + }; + let conv1 = Conv2dBN::new(in_chans, embed_dim / 2, 3, cfg, vb.pp("seq.0"))?; + let conv2 = Conv2dBN::new(embed_dim / 2, embed_dim, 3, cfg, vb.pp("seq.2"))?; + Ok(Self { conv1, conv2 }) + } +} + +impl Module for PatchEmbed { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.conv1)?.gelu()?.apply(&self.conv2) + } +} + +#[derive(Debug)] +struct MBConv { + conv1: Conv2dBN, + conv2: Conv2dBN, + conv3: Conv2dBN, +} + +impl MBConv { + fn new(in_: usize, out: usize, expand_ratio: usize, vb: VarBuilder) -> Result { + let hidden = in_ * expand_ratio; + let cfg2 = candle_nn::Conv2dConfig { + padding: 1, + groups: hidden, + ..Default::default() + }; + let conv1 = Conv2dBN::new(in_, hidden, 1, Default::default(), vb.pp("conv1"))?; + let conv2 = Conv2dBN::new(hidden, hidden, 3, cfg2, vb.pp("conv2"))?; + let conv3 = Conv2dBN::new(hidden, out, 1, Default::default(), vb.pp("conv3"))?; + Ok(Self { + conv1, + conv2, + conv3, + }) + } +} + +impl Module for MBConv { + fn forward(&self, xs: &Tensor) -> Result { + let shortcut = xs; + let xs = xs + .apply(&self.conv1)? + .gelu()? + .apply(&self.conv2)? + .gelu()? + .apply(&self.conv3)?; + (xs + shortcut)?.gelu() + } +} + +#[derive(Debug)] +struct PatchMerging { + conv1: Conv2dBN, + conv2: Conv2dBN, + conv3: Conv2dBN, + input_resolution: (usize, usize), +} + +impl PatchMerging { + fn new( + input_resolution: (usize, usize), + dim: usize, + out: usize, + vb: VarBuilder, + ) -> Result { + let stride = if [320, 448, 576].contains(&out) { 1 } else { 2 }; + let cfg2 = candle_nn::Conv2dConfig { + padding: 1, + stride, + groups: out, + ..Default::default() + }; + let conv1 = Conv2dBN::new(dim, out, 1, Default::default(), vb.pp("conv1"))?; + let conv2 = Conv2dBN::new(out, out, 3, cfg2, vb.pp("conv2"))?; + let conv3 = Conv2dBN::new(out, out, 1, Default::default(), vb.pp("conv3"))?; + Ok(Self { + conv1, + conv2, + conv3, + input_resolution, + }) + } +} + +impl Module for PatchMerging { + fn forward(&self, xs: &Tensor) -> Result { + let xs = if xs.rank() == 3 { + let (h, w) = self.input_resolution; + let b = xs.dim(0)?; + xs.reshape((b, h, w, ()))?.permute((0, 3, 1, 2))? + } else { + xs.clone() + }; + xs.apply(&self.conv1)? + .gelu()? + .apply(&self.conv2)? + .gelu()? + .apply(&self.conv3)? + .flatten_from(2)? + .transpose(1, 2) + } +} + +#[derive(Debug)] +struct ConvLayer { + blocks: Vec, + downsample: Option, +} + +impl ConvLayer { + fn new( + dim: usize, + out: usize, + input_resolution: (usize, usize), + depth: usize, + downsample: bool, + conv_expand_ratio: usize, + vb: VarBuilder, + ) -> Result { + let vb_b = vb.pp("blocks"); + let mut blocks = Vec::with_capacity(depth); + for index in 0..depth { + let block = MBConv::new(dim, dim, conv_expand_ratio, vb_b.pp(index))?; + blocks.push(block) + } + let downsample = if downsample { + let downsample = PatchMerging::new(input_resolution, dim, out, vb.pp("downsample"))?; + Some(downsample) + } else { + None + }; + Ok(Self { blocks, downsample }) + } +} + +impl Module for ConvLayer { + fn forward(&self, xs: &Tensor) -> Result { + let mut xs = xs.clone(); + for block in self.blocks.iter() { + xs = block.forward(&xs)? + } + match &self.downsample { + None => Ok(xs), + Some(downsample) => downsample.forward(&xs), + } + } +} + +#[derive(Debug)] +struct Mlp { + norm: candle_nn::LayerNorm, + fc1: candle_nn::Linear, + fc2: candle_nn::Linear, +} + +impl Mlp { + fn new(in_: usize, hidden: usize, vb: VarBuilder) -> Result { + let norm = candle_nn::layer_norm(in_, 1e-5, vb.pp("norm"))?; + let fc1 = candle_nn::linear(in_, hidden, vb.pp("fc1"))?; + let fc2 = candle_nn::linear(hidden, in_, vb.pp("fc2"))?; + Ok(Self { norm, fc1, fc2 }) + } +} + +impl Module for Mlp { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.norm)? + .apply(&self.fc1)? + .gelu()? + .apply(&self.fc2) + } +} + +#[derive(Debug)] +struct Attention { + norm: candle_nn::LayerNorm, + qkv: candle_nn::Linear, + proj: candle_nn::Linear, + attention_biases: Tensor, + ab: Tensor, + key_dim: usize, + num_heads: usize, + d: usize, + dh: usize, + scale: f64, +} + +impl Attention { + fn new( + dim: usize, + key_dim: usize, + num_heads: usize, + attn_ratio: usize, + resolution: (usize, usize), + vb: VarBuilder, + ) -> Result { + let d = attn_ratio * key_dim; + let dh = d * num_heads; + let nh_kd = key_dim * num_heads; + let h = dh + nh_kd * 2; + let norm = candle_nn::layer_norm(dim, 1e-5, vb.pp("norm"))?; + let qkv = candle_nn::linear(dim, h, vb.pp("qkv"))?; + let proj = candle_nn::linear(dh, dim, vb.pp("proj"))?; + + let points = (0..resolution.0) + .flat_map(|x| (0..resolution.1).map(move |y| (x as i64, y as i64))) + .collect::>(); + let mut idxs = Vec::with_capacity(points.len() * points.len()); + let mut attention_offsets = std::collections::HashMap::new(); + for &(x1, y1) in points.iter() { + for &(x2, y2) in points.iter() { + let offset = ((x2 - x1).abs(), (y2 - y1).abs()); + let l = attention_offsets.len(); + let idx = attention_offsets.entry(offset).or_insert(l); + idxs.push(*idx as u32) + } + } + let attention_biases = vb.get((num_heads, attention_offsets.len()), "attention_biases")?; + let idxs = Tensor::new(idxs, attention_biases.device())?; + let ab = attention_biases.index_select(&idxs, 1)?; + Ok(Self { + norm, + qkv, + proj, + attention_biases, + ab, + key_dim, + num_heads, + d, + dh, + scale: 1f64 / (key_dim as f64).sqrt(), + }) + } +} + +impl Module for Attention { + fn forward(&self, xs: &Tensor) -> Result { + let (b, n, _) = xs.dims3()?; + let xs = xs.apply(&self.norm)?; + let qkv = xs.apply(&self.qkv)?.reshape((b, n, self.num_heads, ()))?; + let q = qkv + .narrow(D::Minus1, 0, self.key_dim)? + .permute((0, 2, 1, 3))?; + let k = qkv + .narrow(D::Minus1, self.key_dim, self.key_dim)? + .permute((0, 2, 1, 3))?; + let v = qkv + .narrow(D::Minus1, 2 * self.key_dim, self.d)? + .permute((0, 2, 1, 3))?; + let attn = (q.matmul(&k.t()?)? * self.scale)?; + let attn = (attn + &self.ab)?; + let attn = candle_nn::ops::softmax_last_dim(&attn)?; + attn.matmul(&v)? + .transpose(1, 2)? + .reshape((b, n, self.dh))? + .apply(&self.proj) + } +} + +#[derive(Debug)] +struct TinyViTBlock { + attn: Attention, + local_conv: Conv2dBN, + mlp: Mlp, + window_size: usize, + input_resolution: (usize, usize), +} + +impl TinyViTBlock { + fn new( + dim: usize, + input_resolution: (usize, usize), + num_heads: usize, + window_size: usize, + vb: VarBuilder, + ) -> Result { + let head_dim = dim / num_heads; + let attn = Attention::new( + dim, + head_dim, + num_heads, + 1, + (window_size, window_size), + vb.pp("attn"), + )?; + let mlp = Mlp::new(dim, dim * MLP_RATIO, vb.pp("mlp"))?; + let cfg = candle_nn::Conv2dConfig { + padding: LOCAL_CONV_SIZE / 2, + ..Default::default() + }; + let local_conv = Conv2dBN::new(dim, dim, LOCAL_CONV_SIZE, cfg, vb.pp("local_conv"))?; + Ok(Self { + attn, + local_conv, + mlp, + window_size, + input_resolution, + }) + } +} + +impl Module for TinyViTBlock { + fn forward(&self, xs: &Tensor) -> Result { + let (h, w) = self.input_resolution; + let (b, l, c) = xs.dims3()?; + let res_x = xs; + let xs = if h == self.window_size && w == self.window_size { + self.attn.forward(xs)? + } else { + let xs = xs.reshape((b, h, w, c))?; + let pad_b = (self.window_size - h % self.window_size) % self.window_size; + let pad_r = (self.window_size - w % self.window_size) % self.window_size; + + let xs = if pad_b > 0 { + xs.pad_with_zeros(D::Minus2, 0, pad_b)? + } else { + xs + }; + let xs = if pad_r > 0 { + xs.pad_with_zeros(D::Minus1, 0, pad_r)? + } else { + xs + }; + let (p_h, p_w) = (h + pad_b, w + pad_r); + let n_h = p_h / self.window_size; + let n_w = p_w / self.window_size; + let xs = xs + .reshape((b, n_h, self.window_size, n_w, self.window_size, c))? + .transpose(2, 3)? + .reshape((b * n_h * n_w, self.window_size * self.window_size, c))?; + let xs = self.attn.forward(&xs)?; + let xs = xs + .reshape((b, n_h, n_w, self.window_size, self.window_size, c))? + .transpose(2, 3)? + .reshape((b, p_h, p_w, c))?; + let xs = if pad_r > 0 { + xs.i((.., .., ..w))?.contiguous()? + } else { + xs + }; + let xs = if pad_b > 0 { + xs.i((.., ..h, ..))?.contiguous()? + } else { + xs + }; + xs.reshape((b, l, c))? + }; + let xs = (xs + res_x)?; + let xs = xs + .transpose(1, 2)? + .reshape((b, c, h, w))? + .apply(&self.local_conv)? + .reshape((b, c, l))? + .transpose(1, 2)?; + &xs + self.mlp.forward(&xs)? + } +} + +#[derive(Debug)] +struct BasicLayer { + blocks: Vec, + downsample: Option, +} + +impl BasicLayer { + #[allow(clippy::too_many_arguments)] + fn new( + dim: usize, + input_resolution: (usize, usize), + depth: usize, + num_heads: usize, + window_size: usize, + downsample: bool, + out: usize, + vb: VarBuilder, + ) -> Result { + let vb_b = vb.pp("blocks"); + let mut blocks = Vec::with_capacity(depth); + for index in 0..depth { + let block = TinyViTBlock::new( + dim, + input_resolution, + num_heads, + window_size, + vb_b.pp(index), + )?; + blocks.push(block) + } + let downsample = if downsample { + let downsample = PatchMerging::new(input_resolution, dim, out, vb.pp("downsample"))?; + Some(downsample) + } else { + None + }; + Ok(Self { blocks, downsample }) + } +} + +impl Module for BasicLayer { + fn forward(&self, xs: &Tensor) -> Result { + let mut xs = xs.clone(); + for block in self.blocks.iter() { + xs = block.forward(&xs)? + } + match &self.downsample { + None => Ok(xs), + Some(downsample) => downsample.forward(&xs), + } + } +} + +#[derive(Debug)] +pub struct TinyViT { + patch_embed: PatchEmbed, + layer0: ConvLayer, + layers: Vec, + norm_head: candle_nn::LayerNorm, + head: candle_nn::Linear, + neck_conv1: candle_nn::Conv2d, + neck_ln1: crate::LayerNorm2d, + neck_conv2: candle_nn::Conv2d, + neck_ln2: crate::LayerNorm2d, +} + +impl TinyViT { + pub fn new( + embed_dims: &[usize], + depths: &[usize], + num_heads: &[usize], + window_sizes: &[usize], + num_classes: usize, + vb: VarBuilder, + ) -> Result { + let patch_embed = PatchEmbed::new(IN_CHANNELS, embed_dims[0], vb.pp("patch_embed"))?; + let patches_resolution = IMG_SIZE / 4; + + let vb_l = vb.pp("layers"); + let layer0 = ConvLayer::new( + /* dim */ embed_dims[0], + /* out */ embed_dims[1], + /* input_resolution */ (patches_resolution, patches_resolution), + /* depth */ depths[0], + /* downsample */ true, + /* conv_expand_ratio */ MBCONV_EXPAND_RATIO, + vb_l.pp(0), + )?; + + let num_layers = embed_dims.len(); + let mut layers = Vec::with_capacity(num_layers - 1); + for i_layer in 1..num_layers { + let patches_resolution = patches_resolution / (1 << usize::min(i_layer, 2)); + let layer = BasicLayer::new( + /* dim */ embed_dims[i_layer], + /* input_resolution */ (patches_resolution, patches_resolution), + /* depth */ depths[i_layer], + /* num_heads */ num_heads[i_layer], + /* window_size */ window_sizes[i_layer], + /* downsample */ i_layer < num_layers - 1, + /* out */ embed_dims[usize::min(i_layer + 1, num_layers - 1)], + vb_l.pp(i_layer), + )?; + layers.push(layer) + } + + let last_embed_dim = embed_dims[embed_dims.len() - 1]; + let norm_head = candle_nn::layer_norm(last_embed_dim, 1e-5, vb.pp("norm_head"))?; + let head = candle_nn::linear(last_embed_dim, num_classes, vb.pp("head"))?; + let neck_conv1 = + candle_nn::conv2d_no_bias(last_embed_dim, 256, 1, Default::default(), vb.pp("neck.0"))?; + let neck_ln1 = crate::LayerNorm2d::new(256, 1e-6, vb.pp("neck.1"))?; + let cfg = candle_nn::Conv2dConfig { + padding: 1, + ..Default::default() + }; + let neck_conv2 = candle_nn::conv2d_no_bias(256, 256, 3, cfg, vb.pp("neck.2"))?; + let neck_ln2 = crate::LayerNorm2d::new(256, 1e-6, vb.pp("neck.3"))?; + + Ok(Self { + patch_embed, + layer0, + layers, + norm_head, + head, + neck_conv1, + neck_ln1, + neck_conv2, + neck_ln2, + }) + } +} + +impl Module for TinyViT { + fn forward(&self, xs: &Tensor) -> Result { + let mut xs = self.patch_embed.forward(xs)?; + for layer in self.layers.iter() { + xs = layer.forward(&xs)? + } + let (b, _, c) = xs.dims3()?; + xs.reshape((b, 64, 64, c))? + .permute((0, 3, 1, 2))? + .apply(&self.neck_conv1)? + .apply(&self.neck_ln1)? + .apply(&self.neck_conv2)? + .apply(&self.neck_ln2) + } +} + +pub fn tiny_vit_5m_224(vb: VarBuilder) -> Result { + TinyViT::new( + /* embed_dims */ &[64, 128, 160, 320], + /* depths */ &[2, 2, 6, 2], + /* num_heads */ &[2, 4, 5, 10], + /* window_sizes */ &[7, 7, 14, 7], + /* num_classes */ 1000, + vb, + ) +} diff --git a/candle-nn/src/conv.rs b/candle-nn/src/conv.rs index b2483058..cfe86bfa 100644 --- a/candle-nn/src/conv.rs +++ b/candle-nn/src/conv.rs @@ -187,10 +187,10 @@ pub fn conv1d( out_channels: usize, kernel_size: usize, cfg: Conv1dConfig, - vs: crate::VarBuilder, + vb: crate::VarBuilder, ) -> Result { let init_ws = crate::init::DEFAULT_KAIMING_NORMAL; - let ws = vs.get_with_hints( + let ws = vb.get_with_hints( (out_channels, in_channels / cfg.groups, kernel_size), "weight", init_ws, @@ -200,7 +200,7 @@ pub fn conv1d( lo: -bound, up: bound, }; - let bs = vs.get_with_hints(out_channels, "bias", init_bs)?; + let bs = vb.get_with_hints(out_channels, "bias", init_bs)?; Ok(Conv1d::new(ws, Some(bs), cfg)) } @@ -209,10 +209,10 @@ pub fn conv2d( out_channels: usize, kernel_size: usize, cfg: Conv2dConfig, - vs: crate::VarBuilder, + vb: crate::VarBuilder, ) -> Result { let init_ws = crate::init::DEFAULT_KAIMING_NORMAL; - let ws = vs.get_with_hints( + let ws = vb.get_with_hints( ( out_channels, in_channels / cfg.groups, @@ -227,7 +227,7 @@ pub fn conv2d( lo: -bound, up: bound, }; - let bs = vs.get_with_hints(out_channels, "bias", init_bs)?; + let bs = vb.get_with_hints(out_channels, "bias", init_bs)?; Ok(Conv2d::new(ws, Some(bs), cfg)) } @@ -236,10 +236,10 @@ pub fn conv2d_no_bias( out_channels: usize, kernel_size: usize, cfg: Conv2dConfig, - vs: crate::VarBuilder, + vb: crate::VarBuilder, ) -> Result { let init_ws = crate::init::DEFAULT_KAIMING_NORMAL; - let ws = vs.get_with_hints( + let ws = vb.get_with_hints( ( out_channels, in_channels / cfg.groups, @@ -257,19 +257,19 @@ pub fn conv_transpose2d( out_channels: usize, kernel_size: usize, cfg: ConvTranspose2dConfig, - vs: crate::VarBuilder, + vb: crate::VarBuilder, ) -> Result { let bound = 1. / (out_channels as f64).sqrt() / kernel_size as f64; let init = crate::Init::Uniform { lo: -bound, up: bound, }; - let ws = vs.get_with_hints( + let ws = vb.get_with_hints( (in_channels, out_channels, kernel_size, kernel_size), "weight", init, )?; - let bs = vs.get_with_hints(out_channels, "bias", init)?; + let bs = vb.get_with_hints(out_channels, "bias", init)?; Ok(ConvTranspose2d::new(ws, Some(bs), cfg)) } @@ -278,14 +278,14 @@ pub fn conv_transpose2d_no_bias( out_channels: usize, kernel_size: usize, cfg: ConvTranspose2dConfig, - vs: crate::VarBuilder, + vb: crate::VarBuilder, ) -> Result { let bound = 1. / (out_channels as f64).sqrt() / kernel_size as f64; let init = crate::Init::Uniform { lo: -bound, up: bound, }; - let ws = vs.get_with_hints( + let ws = vb.get_with_hints( (out_channels, in_channels, kernel_size, kernel_size), "weight", init, From 74ad4deb42da71d4c47220e9595f58445f6f7298 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 9 Sep 2023 16:21:44 +0100 Subject: [PATCH 048/150] Get the MobileSAM TinyViT based version to work. (#789) * More TinyViT support in SA. * More mobilesam work. * Add the mobile-sam weights to the hub. --- .../examples/segment-anything/main.rs | 17 ++++++- .../examples/segment-anything/model_sam.rs | 51 ++++++++++++++++++- .../segment-anything/model_tiny_vit.rs | 47 +++++++++-------- 3 files changed, 89 insertions(+), 26 deletions(-) diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index 3ef5762e..9ce2f158 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -133,6 +133,10 @@ struct Args { /// Enable tracing (generates a trace-timestamp.json file). #[arg(long)] tracing: bool, + + /// Use the TinyViT based models from MobileSAM + #[arg(long)] + use_tiny: bool, } pub fn main() -> anyhow::Result<()> { @@ -179,13 +183,22 @@ pub fn main() -> anyhow::Result<()> { None => { let api = hf_hub::api::sync::Api::new()?; let api = api.model("lmz/candle-sam".to_string()); - api.get("sam_vit_b_01ec64.safetensors")? + let filename = if args.use_tiny { + "mobile_sam-tiny-vitt.safetensors" + } else { + "sam_vit_b_01ec64.safetensors" + }; + api.get(filename)? } }; let weights = unsafe { candle::safetensors::MmapedFile::new(model)? }; let weights = weights.deserialize()?; let vb = VarBuilder::from_safetensors(vec![weights], DType::F32, &device); - let sam = model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)?; // sam_vit_b + let sam = if args.use_tiny { + model_sam::Sam::new_tiny(vb)? // tiny vit_t + } else { + model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)? // sam_vit_b + }; if args.generate_masks { // Default options similar to the Python version. diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-examples/examples/segment-anything/model_sam.rs index ade976c1..b1a81af6 100644 --- a/candle-examples/examples/segment-anything/model_sam.rs +++ b/candle-examples/examples/segment-anything/model_sam.rs @@ -4,6 +4,7 @@ use candle_nn::{Module, VarBuilder}; use crate::model_image_encoder::ImageEncoderViT; use crate::model_mask_decoder::MaskDecoder; use crate::model_prompt_encoder::PromptEncoder; +use crate::model_tiny_vit::{tiny_vit_5m, TinyViT}; const PROMPT_EMBED_DIM: usize = 256; pub const IMAGE_SIZE: usize = 1024; @@ -14,9 +15,24 @@ const STABILITY_SCORE_THRESHOLD: f32 = 0.95; const MODEL_MASK_THRESHOLD: f32 = 0.0; const CROP_NMS_THRESH: f32 = 0.7; +#[derive(Debug)] +enum ImageEncoder { + Original(ImageEncoderViT), + TinyViT(TinyViT), +} + +impl Module for ImageEncoder { + fn forward(&self, xs: &Tensor) -> Result { + match self { + Self::Original(vit) => vit.forward(xs), + Self::TinyViT(vit) => vit.forward(xs), + } + } +} + #[derive(Debug)] pub struct Sam { - image_encoder: ImageEncoderViT, + image_encoder: ImageEncoder, prompt_encoder: PromptEncoder, mask_decoder: MaskDecoder, pixel_mean: Tensor, @@ -67,7 +83,38 @@ impl Sam { let pixel_std = Tensor::new(&[58.395f32, 57.12, 57.375], vb.device())?.reshape((3, 1, 1))?; Ok(Self { - image_encoder, + image_encoder: ImageEncoder::Original(image_encoder), + prompt_encoder, + mask_decoder, + pixel_std, + pixel_mean, + }) + } + + pub fn new_tiny(vb: VarBuilder) -> Result { + let image_embedding_size = IMAGE_SIZE / VIT_PATCH_SIZE; + + let image_encoder = tiny_vit_5m(vb.pp("image_encoder"))?; + let prompt_encoder = PromptEncoder::new( + PROMPT_EMBED_DIM, + (image_embedding_size, image_embedding_size), + (IMAGE_SIZE, IMAGE_SIZE), + 16, + vb.pp("prompt_encoder"), + )?; + let mask_decoder = MaskDecoder::new( + PROMPT_EMBED_DIM, + /* num_multitask_outputs */ 3, + /* iou_head_depth */ 3, + /* iou_head_hidden_dim */ 256, + vb.pp("mask_decoder"), + )?; + let pixel_mean = + Tensor::new(&[123.675f32, 116.28, 103.53], vb.device())?.reshape((3, 1, 1))?; + let pixel_std = + Tensor::new(&[58.395f32, 57.12, 57.375], vb.device())?.reshape((3, 1, 1))?; + Ok(Self { + image_encoder: ImageEncoder::TinyViT(image_encoder), prompt_encoder, mask_decoder, pixel_std, diff --git a/candle-examples/examples/segment-anything/model_tiny_vit.rs b/candle-examples/examples/segment-anything/model_tiny_vit.rs index 36e4c578..b3941ee1 100644 --- a/candle-examples/examples/segment-anything/model_tiny_vit.rs +++ b/candle-examples/examples/segment-anything/model_tiny_vit.rs @@ -1,13 +1,12 @@ // Adapted from: // https://github.com/ChaoningZhang/MobileSAM/blob/master/mobile_sam/modeling/tiny_vit_sam.py -#![allow(unused)] -use candle::{DType, IndexOp, Result, Tensor, D}; +use candle::{IndexOp, Result, Tensor, D}; use candle_nn::{Conv2dConfig, Module, VarBuilder}; const MBCONV_EXPAND_RATIO: usize = 4; const MLP_RATIO: usize = 4; const LOCAL_CONV_SIZE: usize = 3; -const IMG_SIZE: usize = 224; +const IMG_SIZE: usize = 1024; const IN_CHANNELS: usize = 3; #[derive(Debug)] @@ -18,7 +17,7 @@ struct Conv2dBN { impl Conv2dBN { fn new(in_: usize, out: usize, ks: usize, cfg: Conv2dConfig, vb: VarBuilder) -> Result { - let c = candle_nn::conv2d(in_, out, ks, cfg, vb.pp("c"))?; + let c = candle_nn::conv2d_no_bias(in_, out, ks, cfg, vb.pp("c"))?; let bn = candle_nn::batch_norm(out, 1e-5, vb.pp("bn"))?; Ok(Self { c, bn }) } @@ -222,7 +221,6 @@ struct Attention { norm: candle_nn::LayerNorm, qkv: candle_nn::Linear, proj: candle_nn::Linear, - attention_biases: Tensor, ab: Tensor, key_dim: usize, num_heads: usize, @@ -263,12 +261,14 @@ impl Attention { } let attention_biases = vb.get((num_heads, attention_offsets.len()), "attention_biases")?; let idxs = Tensor::new(idxs, attention_biases.device())?; - let ab = attention_biases.index_select(&idxs, 1)?; + let ab = + attention_biases + .index_select(&idxs, 1)? + .reshape(((), points.len(), points.len()))?; Ok(Self { norm, qkv, proj, - attention_biases, ab, key_dim, num_heads, @@ -286,15 +286,18 @@ impl Module for Attention { let qkv = xs.apply(&self.qkv)?.reshape((b, n, self.num_heads, ()))?; let q = qkv .narrow(D::Minus1, 0, self.key_dim)? - .permute((0, 2, 1, 3))?; + .permute((0, 2, 1, 3))? + .contiguous()?; let k = qkv .narrow(D::Minus1, self.key_dim, self.key_dim)? - .permute((0, 2, 1, 3))?; + .permute((0, 2, 1, 3))? + .contiguous()?; let v = qkv .narrow(D::Minus1, 2 * self.key_dim, self.d)? - .permute((0, 2, 1, 3))?; + .permute((0, 2, 1, 3))? + .contiguous()?; let attn = (q.matmul(&k.t()?)? * self.scale)?; - let attn = (attn + &self.ab)?; + let attn = attn.broadcast_add(&self.ab)?; let attn = candle_nn::ops::softmax_last_dim(&attn)?; attn.matmul(&v)? .transpose(1, 2)? @@ -332,6 +335,7 @@ impl TinyViTBlock { let mlp = Mlp::new(dim, dim * MLP_RATIO, vb.pp("mlp"))?; let cfg = candle_nn::Conv2dConfig { padding: LOCAL_CONV_SIZE / 2, + groups: dim, ..Default::default() }; let local_conv = Conv2dBN::new(dim, dim, LOCAL_CONV_SIZE, cfg, vb.pp("local_conv"))?; @@ -358,12 +362,12 @@ impl Module for TinyViTBlock { let pad_r = (self.window_size - w % self.window_size) % self.window_size; let xs = if pad_b > 0 { - xs.pad_with_zeros(D::Minus2, 0, pad_b)? + xs.pad_with_zeros(1, 0, pad_b)? } else { xs }; let xs = if pad_r > 0 { - xs.pad_with_zeros(D::Minus1, 0, pad_r)? + xs.pad_with_zeros(2, 0, pad_r)? } else { xs }; @@ -460,8 +464,8 @@ pub struct TinyViT { patch_embed: PatchEmbed, layer0: ConvLayer, layers: Vec, - norm_head: candle_nn::LayerNorm, - head: candle_nn::Linear, + // norm_head: candle_nn::LayerNorm, + // head: candle_nn::Linear, neck_conv1: candle_nn::Conv2d, neck_ln1: crate::LayerNorm2d, neck_conv2: candle_nn::Conv2d, @@ -474,7 +478,7 @@ impl TinyViT { depths: &[usize], num_heads: &[usize], window_sizes: &[usize], - num_classes: usize, + _num_classes: usize, vb: VarBuilder, ) -> Result { let patch_embed = PatchEmbed::new(IN_CHANNELS, embed_dims[0], vb.pp("patch_embed"))?; @@ -509,8 +513,8 @@ impl TinyViT { } let last_embed_dim = embed_dims[embed_dims.len() - 1]; - let norm_head = candle_nn::layer_norm(last_embed_dim, 1e-5, vb.pp("norm_head"))?; - let head = candle_nn::linear(last_embed_dim, num_classes, vb.pp("head"))?; + // let norm_head = candle_nn::layer_norm(last_embed_dim, 1e-5, vb.pp("norm_head"))?; + // let head = candle_nn::linear(last_embed_dim, num_classes, vb.pp("head"))?; let neck_conv1 = candle_nn::conv2d_no_bias(last_embed_dim, 256, 1, Default::default(), vb.pp("neck.0"))?; let neck_ln1 = crate::LayerNorm2d::new(256, 1e-6, vb.pp("neck.1"))?; @@ -525,8 +529,6 @@ impl TinyViT { patch_embed, layer0, layers, - norm_head, - head, neck_conv1, neck_ln1, neck_conv2, @@ -537,7 +539,8 @@ impl TinyViT { impl Module for TinyViT { fn forward(&self, xs: &Tensor) -> Result { - let mut xs = self.patch_embed.forward(xs)?; + let xs = self.patch_embed.forward(xs)?; + let mut xs = self.layer0.forward(&xs)?; for layer in self.layers.iter() { xs = layer.forward(&xs)? } @@ -551,7 +554,7 @@ impl Module for TinyViT { } } -pub fn tiny_vit_5m_224(vb: VarBuilder) -> Result { +pub fn tiny_vit_5m(vb: VarBuilder) -> Result { TinyViT::new( /* embed_dims */ &[64, 128, 160, 320], /* depths */ &[2, 2, 6, 2], From 31936c08fe26ad0bc401fb1ea5b4eac869491637 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 9 Sep 2023 17:26:39 +0100 Subject: [PATCH 049/150] ViT tracing. (#790) --- .../segment-anything/model_tiny_vit.rs | 101 +++++++++++++++--- 1 file changed, 84 insertions(+), 17 deletions(-) diff --git a/candle-examples/examples/segment-anything/model_tiny_vit.rs b/candle-examples/examples/segment-anything/model_tiny_vit.rs index b3941ee1..ff076773 100644 --- a/candle-examples/examples/segment-anything/model_tiny_vit.rs +++ b/candle-examples/examples/segment-anything/model_tiny_vit.rs @@ -13,18 +13,21 @@ const IN_CHANNELS: usize = 3; struct Conv2dBN { c: candle_nn::Conv2d, bn: candle_nn::BatchNorm, + span: tracing::Span, } impl Conv2dBN { fn new(in_: usize, out: usize, ks: usize, cfg: Conv2dConfig, vb: VarBuilder) -> Result { let c = candle_nn::conv2d_no_bias(in_, out, ks, cfg, vb.pp("c"))?; let bn = candle_nn::batch_norm(out, 1e-5, vb.pp("bn"))?; - Ok(Self { c, bn }) + let span = tracing::span!(tracing::Level::TRACE, "conv2d-bn"); + Ok(Self { c, bn, span }) } } impl Module for Conv2dBN { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); xs.apply(&self.c)?.apply(&self.bn) } } @@ -33,6 +36,7 @@ impl Module for Conv2dBN { struct PatchEmbed { conv1: Conv2dBN, conv2: Conv2dBN, + span: tracing::Span, } impl PatchEmbed { @@ -44,12 +48,14 @@ impl PatchEmbed { }; let conv1 = Conv2dBN::new(in_chans, embed_dim / 2, 3, cfg, vb.pp("seq.0"))?; let conv2 = Conv2dBN::new(embed_dim / 2, embed_dim, 3, cfg, vb.pp("seq.2"))?; - Ok(Self { conv1, conv2 }) + let span = tracing::span!(tracing::Level::TRACE, "patch-embed"); + Ok(Self { conv1, conv2, span }) } } impl Module for PatchEmbed { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); xs.apply(&self.conv1)?.gelu()?.apply(&self.conv2) } } @@ -59,6 +65,7 @@ struct MBConv { conv1: Conv2dBN, conv2: Conv2dBN, conv3: Conv2dBN, + span: tracing::Span, } impl MBConv { @@ -72,16 +79,19 @@ impl MBConv { let conv1 = Conv2dBN::new(in_, hidden, 1, Default::default(), vb.pp("conv1"))?; let conv2 = Conv2dBN::new(hidden, hidden, 3, cfg2, vb.pp("conv2"))?; let conv3 = Conv2dBN::new(hidden, out, 1, Default::default(), vb.pp("conv3"))?; + let span = tracing::span!(tracing::Level::TRACE, "mb-conv"); Ok(Self { conv1, conv2, conv3, + span, }) } } impl Module for MBConv { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let shortcut = xs; let xs = xs .apply(&self.conv1)? @@ -99,6 +109,7 @@ struct PatchMerging { conv2: Conv2dBN, conv3: Conv2dBN, input_resolution: (usize, usize), + span: tracing::Span, } impl PatchMerging { @@ -118,17 +129,20 @@ impl PatchMerging { let conv1 = Conv2dBN::new(dim, out, 1, Default::default(), vb.pp("conv1"))?; let conv2 = Conv2dBN::new(out, out, 3, cfg2, vb.pp("conv2"))?; let conv3 = Conv2dBN::new(out, out, 1, Default::default(), vb.pp("conv3"))?; + let span = tracing::span!(tracing::Level::TRACE, "patch-merging"); Ok(Self { conv1, conv2, conv3, input_resolution, + span, }) } } impl Module for PatchMerging { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let xs = if xs.rank() == 3 { let (h, w) = self.input_resolution; let b = xs.dim(0)?; @@ -150,6 +164,7 @@ impl Module for PatchMerging { struct ConvLayer { blocks: Vec, downsample: Option, + span: tracing::Span, } impl ConvLayer { @@ -174,12 +189,18 @@ impl ConvLayer { } else { None }; - Ok(Self { blocks, downsample }) + let span = tracing::span!(tracing::Level::TRACE, "conv-layer"); + Ok(Self { + blocks, + downsample, + span, + }) } } impl Module for ConvLayer { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let mut xs = xs.clone(); for block in self.blocks.iter() { xs = block.forward(&xs)? @@ -194,21 +215,29 @@ impl Module for ConvLayer { #[derive(Debug)] struct Mlp { norm: candle_nn::LayerNorm, - fc1: candle_nn::Linear, - fc2: candle_nn::Linear, + fc1: crate::Linear, + fc2: crate::Linear, + span: tracing::Span, } impl Mlp { fn new(in_: usize, hidden: usize, vb: VarBuilder) -> Result { let norm = candle_nn::layer_norm(in_, 1e-5, vb.pp("norm"))?; - let fc1 = candle_nn::linear(in_, hidden, vb.pp("fc1"))?; - let fc2 = candle_nn::linear(hidden, in_, vb.pp("fc2"))?; - Ok(Self { norm, fc1, fc2 }) + let fc1 = crate::linear(vb.pp("fc1"), in_, hidden, true)?; + let fc2 = crate::linear(vb.pp("fc2"), hidden, in_, true)?; + let span = tracing::span!(tracing::Level::TRACE, "mlp"); + Ok(Self { + norm, + fc1, + fc2, + span, + }) } } impl Module for Mlp { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); xs.apply(&self.norm)? .apply(&self.fc1)? .gelu()? @@ -219,14 +248,17 @@ impl Module for Mlp { #[derive(Debug)] struct Attention { norm: candle_nn::LayerNorm, - qkv: candle_nn::Linear, - proj: candle_nn::Linear, + qkv: crate::Linear, + proj: crate::Linear, ab: Tensor, key_dim: usize, num_heads: usize, d: usize, dh: usize, scale: f64, + span: tracing::Span, + span_matmul: tracing::Span, + span_softmax: tracing::Span, } impl Attention { @@ -243,8 +275,8 @@ impl Attention { let nh_kd = key_dim * num_heads; let h = dh + nh_kd * 2; let norm = candle_nn::layer_norm(dim, 1e-5, vb.pp("norm"))?; - let qkv = candle_nn::linear(dim, h, vb.pp("qkv"))?; - let proj = candle_nn::linear(dh, dim, vb.pp("proj"))?; + let qkv = crate::linear(vb.pp("qkv"), dim, h, true)?; + let proj = crate::linear(vb.pp("proj"), dh, dim, true)?; let points = (0..resolution.0) .flat_map(|x| (0..resolution.1).map(move |y| (x as i64, y as i64))) @@ -265,6 +297,9 @@ impl Attention { attention_biases .index_select(&idxs, 1)? .reshape(((), points.len(), points.len()))?; + let span = tracing::span!(tracing::Level::TRACE, "attention"); + let span_matmul = tracing::span!(tracing::Level::TRACE, "attn-matmul"); + let span_softmax = tracing::span!(tracing::Level::TRACE, "attn-sm"); Ok(Self { norm, qkv, @@ -275,12 +310,16 @@ impl Attention { d, dh, scale: 1f64 / (key_dim as f64).sqrt(), + span, + span_matmul, + span_softmax, }) } } impl Module for Attention { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let (b, n, _) = xs.dims3()?; let xs = xs.apply(&self.norm)?; let qkv = xs.apply(&self.qkv)?.reshape((b, n, self.num_heads, ()))?; @@ -296,11 +335,20 @@ impl Module for Attention { .narrow(D::Minus1, 2 * self.key_dim, self.d)? .permute((0, 2, 1, 3))? .contiguous()?; - let attn = (q.matmul(&k.t()?)? * self.scale)?; + let attn = { + let _enter = self.span_matmul.enter(); + (q.matmul(&k.t()?)? * self.scale)? + }; let attn = attn.broadcast_add(&self.ab)?; - let attn = candle_nn::ops::softmax_last_dim(&attn)?; - attn.matmul(&v)? - .transpose(1, 2)? + let attn = { + let _enter = self.span_softmax.enter(); + candle_nn::ops::softmax_last_dim(&attn)? + }; + let attn = { + let _enter = self.span_matmul.enter(); + attn.matmul(&v)? + }; + attn.transpose(1, 2)? .reshape((b, n, self.dh))? .apply(&self.proj) } @@ -313,6 +361,7 @@ struct TinyViTBlock { mlp: Mlp, window_size: usize, input_resolution: (usize, usize), + span: tracing::Span, } impl TinyViTBlock { @@ -339,18 +388,21 @@ impl TinyViTBlock { ..Default::default() }; let local_conv = Conv2dBN::new(dim, dim, LOCAL_CONV_SIZE, cfg, vb.pp("local_conv"))?; + let span = tracing::span!(tracing::Level::TRACE, "attention"); Ok(Self { attn, local_conv, mlp, window_size, input_resolution, + span, }) } } impl Module for TinyViTBlock { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let (h, w) = self.input_resolution; let (b, l, c) = xs.dims3()?; let res_x = xs; @@ -410,6 +462,7 @@ impl Module for TinyViTBlock { struct BasicLayer { blocks: Vec, downsample: Option, + span: tracing::Span, } impl BasicLayer { @@ -442,12 +495,18 @@ impl BasicLayer { } else { None }; - Ok(Self { blocks, downsample }) + let span = tracing::span!(tracing::Level::TRACE, "basic-layer"); + Ok(Self { + blocks, + downsample, + span, + }) } } impl Module for BasicLayer { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let mut xs = xs.clone(); for block in self.blocks.iter() { xs = block.forward(&xs)? @@ -470,6 +529,8 @@ pub struct TinyViT { neck_ln1: crate::LayerNorm2d, neck_conv2: candle_nn::Conv2d, neck_ln2: crate::LayerNorm2d, + span: tracing::Span, + span_neck: tracing::Span, } impl TinyViT { @@ -525,6 +586,8 @@ impl TinyViT { let neck_conv2 = candle_nn::conv2d_no_bias(256, 256, 3, cfg, vb.pp("neck.2"))?; let neck_ln2 = crate::LayerNorm2d::new(256, 1e-6, vb.pp("neck.3"))?; + let span = tracing::span!(tracing::Level::TRACE, "tiny-vit"); + let span_neck = tracing::span!(tracing::Level::TRACE, "neck"); Ok(Self { patch_embed, layer0, @@ -533,18 +596,22 @@ impl TinyViT { neck_ln1, neck_conv2, neck_ln2, + span, + span_neck, }) } } impl Module for TinyViT { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let xs = self.patch_embed.forward(xs)?; let mut xs = self.layer0.forward(&xs)?; for layer in self.layers.iter() { xs = layer.forward(&xs)? } let (b, _, c) = xs.dims3()?; + let _enter = self.span_neck.enter(); xs.reshape((b, 64, 64, c))? .permute((0, 3, 1, 2))? .apply(&self.neck_conv1)? From 258ac32c3868d4103e90df19af99a3e13c805c4e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 9 Sep 2023 18:44:21 +0100 Subject: [PATCH 050/150] Fix cuda randn when generating an odd number of values. (#793) --- candle-core/src/cuda_backend.rs | 11 +++++++++-- candle-core/tests/tensor_tests.rs | 9 +++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index cb00441f..7cc85489 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -312,6 +312,13 @@ impl BackendDevice for CudaDevice { // cudarc changes. let elem_count = shape.elem_count(); let curand = self.curand.lock().unwrap(); + // curand can only generate an odd number of values. + // https://github.com/huggingface/candle/issues/734 + let elem_count_round = if elem_count % 2 == 1 { + elem_count + 1 + } else { + elem_count + }; let slice = match dtype { DType::U8 | DType::U32 | DType::I64 | DType::F16 | DType::BF16 => { Err(CudaError::UnsupportedDtype { @@ -321,7 +328,7 @@ impl BackendDevice for CudaDevice { .w()? } DType::F32 => { - let mut data = unsafe { self.alloc::(elem_count) }.w()?; + let mut data = unsafe { self.alloc::(elem_count_round) }.w()?; curand .0 .fill_with_normal(&mut data, mean as f32, std as f32) @@ -329,7 +336,7 @@ impl BackendDevice for CudaDevice { CudaStorageSlice::F32(data) } DType::F64 => { - let mut data = unsafe { self.alloc::(elem_count) }.w()?; + let mut data = unsafe { self.alloc::(elem_count_round) }.w()?; curand.0.fill_with_normal(&mut data, mean, std).w()?; CudaStorageSlice::F64(data) } diff --git a/candle-core/tests/tensor_tests.rs b/candle-core/tests/tensor_tests.rs index 6af43196..cd68908f 100644 --- a/candle-core/tests/tensor_tests.rs +++ b/candle-core/tests/tensor_tests.rs @@ -877,6 +877,14 @@ fn broadcasting(device: &Device) -> Result<()> { Ok(()) } +fn randn(device: &Device) -> Result<()> { + let tensor = Tensor::randn(0f32, 1f32, (5, 3), device)?; + assert_eq!(tensor.dims(), [5, 3]); + let tensor = Tensor::rand(0f32, 1f32, (5, 3), device)?; + assert_eq!(tensor.dims(), [5, 3]); + Ok(()) +} + test_device!(zeros, zeros_cpu, zeros_gpu); test_device!(add_mul, add_mul_cpu, add_mul_gpu); test_device!(tensor_2d, tensor_2d_cpu, tensor_2d_gpu); @@ -899,6 +907,7 @@ test_device!(index_select, index_select_cpu, index_select_gpu); test_device!(index_add, index_add_cpu, index_add_gpu); test_device!(gather, gather_cpu, gather_gpu); test_device!(scatter_add, scatter_add_cpu, scatter_add_gpu); +test_device!(randn, randn_cpu, randn_gpu); // There was originally a bug on the CPU implementation for randn // https://github.com/huggingface/candle/issues/381 From d3f05eae8c4f2df186b46e433be101ac39fceca5 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 09:40:27 +0100 Subject: [PATCH 051/150] Move some models to candle-transformers so that it's easier to re-use. (#794) * Move some models to candle-transformers so that they can be shared. * Also move falcon. * Move Llama. * Move whisper (partial). --- candle-examples/Cargo.toml | 12 ++-- candle-examples/examples/bert/main.rs | 3 +- candle-examples/examples/bigcode/main.rs | 3 +- candle-examples/examples/falcon/main.rs | 3 +- candle-examples/examples/llama/main.rs | 3 +- candle-examples/examples/whisper/main.rs | 71 ++++++------------- .../examples/whisper/multilingual.rs | 2 +- candle-transformers/Cargo.toml | 4 ++ .../src/models/bert.rs | 0 .../src/models/bigcode.rs | 0 .../src/models/falcon.rs | 11 ++- .../src/models/llama.rs | 2 +- candle-transformers/src/models/mod.rs | 6 +- .../src/models}/whisper/audio.rs | 10 +-- candle-transformers/src/models/whisper/mod.rs | 26 +++++++ .../src/models}/whisper/model.rs | 0 16 files changed, 78 insertions(+), 78 deletions(-) rename candle-examples/examples/bert/model.rs => candle-transformers/src/models/bert.rs (100%) rename candle-examples/examples/bigcode/model.rs => candle-transformers/src/models/bigcode.rs (100%) rename candle-examples/examples/falcon/model.rs => candle-transformers/src/models/falcon.rs (98%) rename candle-examples/examples/llama/model.rs => candle-transformers/src/models/llama.rs (99%) rename {candle-examples/examples => candle-transformers/src/models}/whisper/audio.rs (97%) create mode 100644 candle-transformers/src/models/whisper/mod.rs rename {candle-examples/examples => candle-transformers/src/models}/whisper/model.rs (100%) diff --git a/candle-examples/Cargo.toml b/candle-examples/Cargo.toml index 6f8792a3..eb552b88 100644 --- a/candle-examples/Cargo.toml +++ b/candle-examples/Cargo.toml @@ -13,18 +13,18 @@ readme = "README.md" accelerate-src = { workspace = true, optional = true } candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } candle-datasets = { path = "../candle-datasets", version = "0.2.1" } +candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.1", optional = true } candle-nn = { path = "../candle-nn", version = "0.2.1" } candle-transformers = { path = "../candle-transformers", version = "0.2.1" } -candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.1", optional = true } -safetensors = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -num-traits = { workspace = true } -intel-mkl-src = { workspace = true, optional = true } cudarc = { workspace = true, optional = true } half = { workspace = true, optional = true } image = { workspace = true } +intel-mkl-src = { workspace = true, optional = true } +num-traits = { workspace = true } rayon = { workspace = true } +safetensors = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/candle-examples/examples/bert/main.rs b/candle-examples/examples/bert/main.rs index 6cee66ee..9d0eccdf 100644 --- a/candle-examples/examples/bert/main.rs +++ b/candle-examples/examples/bert/main.rs @@ -3,14 +3,13 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; -mod model; +use candle_transformers::models::bert::{BertModel, Config, DTYPE}; use anyhow::{anyhow, Error as E, Result}; use candle::Tensor; use candle_nn::VarBuilder; use clap::Parser; use hf_hub::{api::sync::Api, Cache, Repo, RepoType}; -use model::{BertModel, Config, DTYPE}; use tokenizers::{PaddingParams, Tokenizer}; #[derive(Parser, Debug)] diff --git a/candle-examples/examples/bigcode/main.rs b/candle-examples/examples/bigcode/main.rs index 652cd47f..3540f75d 100644 --- a/candle-examples/examples/bigcode/main.rs +++ b/candle-examples/examples/bigcode/main.rs @@ -7,8 +7,7 @@ extern crate accelerate_src; use anyhow::{Error as E, Result}; use clap::Parser; -mod model; -use model::{Config, GPTBigCode}; +use candle_transformers::models::bigcode::{Config, GPTBigCode}; use candle::{DType, Device, Tensor}; use candle_nn::VarBuilder; diff --git a/candle-examples/examples/falcon/main.rs b/candle-examples/examples/falcon/main.rs index 05507f08..c45fe545 100644 --- a/candle-examples/examples/falcon/main.rs +++ b/candle-examples/examples/falcon/main.rs @@ -14,8 +14,7 @@ use clap::Parser; use hf_hub::{api::sync::Api, Repo, RepoType}; use tokenizers::Tokenizer; -mod model; -use model::{Config, Falcon}; +use candle_transformers::models::falcon::{Config, Falcon}; struct TextGeneration { model: Falcon, diff --git a/candle-examples/examples/llama/main.rs b/candle-examples/examples/llama/main.rs index 6f8766d4..db3d216c 100644 --- a/candle-examples/examples/llama/main.rs +++ b/candle-examples/examples/llama/main.rs @@ -21,11 +21,10 @@ use candle_transformers::generation::LogitsProcessor; use hf_hub::{api::sync::Api, Repo, RepoType}; use std::io::Write; -mod model; +use candle_transformers::models::llama as model; use model::{Config, Llama, LlamaConfig}; const EOS_TOKEN: &str = ""; -const MAX_SEQ_LEN: usize = 4096; const DEFAULT_PROMPT: &str = "My favorite theorem is "; #[derive(Parser, Debug)] diff --git a/candle-examples/examples/whisper/main.rs b/candle-examples/examples/whisper/main.rs index dbe9cc8d..c71d562a 100644 --- a/candle-examples/examples/whisper/main.rs +++ b/candle-examples/examples/whisper/main.rs @@ -10,41 +10,16 @@ extern crate accelerate_src; extern crate intel_mkl_src; use anyhow::{Error as E, Result}; -use candle::{DType, Device, IndexOp, Tensor}; +use candle::{Device, IndexOp, Tensor}; use candle_nn::{ops::softmax, VarBuilder}; use clap::{Parser, ValueEnum}; use hf_hub::{api::sync::Api, Repo, RepoType}; use rand::{distributions::Distribution, SeedableRng}; use tokenizers::Tokenizer; -mod audio; -mod model; -use model::{Config, Whisper}; mod multilingual; - -const DTYPE: DType = DType::F32; - -// Audio parameters. -const SAMPLE_RATE: usize = 16000; -const N_FFT: usize = 400; -const N_MELS: usize = 80; -const HOP_LENGTH: usize = 160; -const CHUNK_LENGTH: usize = 30; -const N_SAMPLES: usize = CHUNK_LENGTH * SAMPLE_RATE; // 480000 samples in a 30-second chunk -const N_FRAMES: usize = N_SAMPLES / HOP_LENGTH; // 3000 frames in a mel spectrogram input - -const NO_SPEECH_THRESHOLD: f64 = 0.6; -const LOGPROB_THRESHOLD: f64 = -1.0; -const TEMPERATURES: [f64; 6] = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; -const COMPRESSION_RATIO_THRESHOLD: f64 = 2.4; - -// Tokenizer dependent bits. -const SOT_TOKEN: &str = "<|startoftranscript|>"; -const TRANSCRIBE_TOKEN: &str = "<|transcribe|>"; -const TRANSLATE_TOKEN: &str = "<|translate|>"; -const NO_TIMESTAMPS_TOKEN: &str = "<|notimestamps|>"; -const EOT_TOKEN: &str = "<|endoftext|>"; -const NO_SPEECH_TOKEN: &str = "<|nocaptions|>"; +use candle_transformers::models::whisper::{self as m, audio, model}; +use model::{Config, Whisper}; #[allow(dead_code)] #[derive(Debug, Clone)] @@ -94,7 +69,7 @@ impl Decoder { timestamps: bool, verbose: bool, ) -> Result { - let no_timestamps_token = token_id(&tokenizer, NO_TIMESTAMPS_TOKEN)?; + let no_timestamps_token = token_id(&tokenizer, m::NO_TIMESTAMPS_TOKEN)?; // Suppress the notimestamps token when in timestamps mode. // https://github.com/openai/whisper/blob/e8622f9afc4eba139bf796c210f5c01081000472/whisper/decoding.py#L452 let suppress_tokens: Vec = (0..model.config.vocab_size as u32) @@ -109,11 +84,11 @@ impl Decoder { }) .collect(); let suppress_tokens = Tensor::new(suppress_tokens.as_slice(), device)?; - let sot_token = token_id(&tokenizer, SOT_TOKEN)?; - let transcribe_token = token_id(&tokenizer, TRANSCRIBE_TOKEN)?; - let translate_token = token_id(&tokenizer, TRANSLATE_TOKEN)?; - let eot_token = token_id(&tokenizer, EOT_TOKEN)?; - let no_speech_token = token_id(&tokenizer, NO_SPEECH_TOKEN)?; + let sot_token = token_id(&tokenizer, m::SOT_TOKEN)?; + let transcribe_token = token_id(&tokenizer, m::TRANSCRIBE_TOKEN)?; + let translate_token = token_id(&tokenizer, m::TRANSLATE_TOKEN)?; + let eot_token = token_id(&tokenizer, m::EOT_TOKEN)?; + let no_speech_token = token_id(&tokenizer, m::NO_SPEECH_TOKEN)?; Ok(Self { model, rng: rand::rngs::StdRng::seed_from_u64(seed), @@ -220,17 +195,17 @@ impl Decoder { } fn decode_with_fallback(&mut self, segment: &Tensor) -> Result { - for (i, &t) in TEMPERATURES.iter().enumerate() { + for (i, &t) in m::TEMPERATURES.iter().enumerate() { let dr: Result = self.decode(segment, t); - if i == TEMPERATURES.len() - 1 { + if i == m::TEMPERATURES.len() - 1 { return dr; } // On errors, we try again with a different temperature. match dr { Ok(dr) => { - let needs_fallback = dr.compression_ratio > COMPRESSION_RATIO_THRESHOLD - || dr.avg_logprob < LOGPROB_THRESHOLD; - if !needs_fallback || dr.no_speech_prob > NO_SPEECH_THRESHOLD { + let needs_fallback = dr.compression_ratio > m::COMPRESSION_RATIO_THRESHOLD + || dr.avg_logprob < m::LOGPROB_THRESHOLD; + if !needs_fallback || dr.no_speech_prob > m::NO_SPEECH_THRESHOLD { return Ok(dr); } } @@ -248,13 +223,13 @@ impl Decoder { let mut segments = vec![]; while seek < content_frames { let start = std::time::Instant::now(); - let time_offset = (seek * HOP_LENGTH) as f64 / SAMPLE_RATE as f64; - let segment_size = usize::min(content_frames - seek, N_FRAMES); + let time_offset = (seek * m::HOP_LENGTH) as f64 / m::SAMPLE_RATE as f64; + let segment_size = usize::min(content_frames - seek, m::N_FRAMES); let mel_segment = mel.narrow(2, seek, segment_size)?; - let segment_duration = (segment_size * HOP_LENGTH) as f64 / SAMPLE_RATE as f64; + let segment_duration = (segment_size * m::HOP_LENGTH) as f64 / m::SAMPLE_RATE as f64; let dr = self.decode_with_fallback(&mel_segment)?; seek += segment_size; - if dr.no_speech_prob > NO_SPEECH_THRESHOLD && dr.avg_logprob < LOGPROB_THRESHOLD { + if dr.no_speech_prob > m::NO_SPEECH_THRESHOLD && dr.avg_logprob < m::LOGPROB_THRESHOLD { println!("no speech detected, skipping {seek} {dr:?}"); continue; } @@ -492,8 +467,8 @@ fn main() -> Result<()> { let mut input = std::fs::File::open(input)?; let (header, data) = wav::read(&mut input)?; println!("loaded wav data: {header:?}"); - if header.sampling_rate != SAMPLE_RATE as u32 { - anyhow::bail!("wav file must have a {} sampling rate", SAMPLE_RATE) + if header.sampling_rate != m::SAMPLE_RATE as u32 { + anyhow::bail!("wav file must have a {} sampling rate", m::SAMPLE_RATE) } let data = data.as_sixteen().expect("expected 16 bit wav file"); let pcm_data: Vec<_> = data[..data.len() / header.channel_count as usize] @@ -501,14 +476,14 @@ fn main() -> Result<()> { .map(|v| *v as f32 / 32768.) .collect(); println!("pcm data loaded {}", pcm_data.len()); - let mel = audio::pcm_to_mel(&pcm_data, &mel_filters)?; + let mel = audio::pcm_to_mel(&pcm_data, &mel_filters); let mel_len = mel.len(); - let mel = Tensor::from_vec(mel, (1, N_MELS, mel_len / N_MELS), &device)?; + let mel = Tensor::from_vec(mel, (1, m::N_MELS, mel_len / m::N_MELS), &device)?; println!("loaded mel: {:?}", mel.dims()); let weights = unsafe { candle::safetensors::MmapedFile::new(weights_filename)? }; let weights = weights.deserialize()?; - let vb = VarBuilder::from_safetensors(vec![weights], DTYPE, &device); + let vb = VarBuilder::from_safetensors(vec![weights], m::DTYPE, &device); let config: Config = serde_json::from_str(&std::fs::read_to_string(config_filename)?)?; let mut model = Whisper::load(&vb, config)?; diff --git a/candle-examples/examples/whisper/multilingual.rs b/candle-examples/examples/whisper/multilingual.rs index bc0bae1f..a82b09ef 100644 --- a/candle-examples/examples/whisper/multilingual.rs +++ b/candle-examples/examples/whisper/multilingual.rs @@ -113,7 +113,7 @@ pub fn detect_language(model: &mut Whisper, tokenizer: &Tokenizer, mel: &Tensor) .iter() .map(|(t, _)| crate::token_id(tokenizer, &format!("<|{t}|>"))) .collect::>>()?; - let sot_token = crate::token_id(tokenizer, crate::SOT_TOKEN)?; + let sot_token = crate::token_id(tokenizer, crate::m::SOT_TOKEN)?; let audio_features = model.encoder.forward(&mel, true)?; let tokens = Tensor::new(&[[sot_token]], device)?; let language_token_ids = Tensor::new(language_token_ids.as_slice(), device)?; diff --git a/candle-transformers/Cargo.toml b/candle-transformers/Cargo.toml index a05b9bb7..6b2087cb 100644 --- a/candle-transformers/Cargo.toml +++ b/candle-transformers/Cargo.toml @@ -14,7 +14,11 @@ accelerate-src = { workspace = true, optional = true } candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } candle-nn = { path = "../candle-nn", version = "0.2.1" } intel-mkl-src = { workspace = true, optional = true } +num-traits = { workspace = true } rand = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } wav = { workspace = true } [features] diff --git a/candle-examples/examples/bert/model.rs b/candle-transformers/src/models/bert.rs similarity index 100% rename from candle-examples/examples/bert/model.rs rename to candle-transformers/src/models/bert.rs diff --git a/candle-examples/examples/bigcode/model.rs b/candle-transformers/src/models/bigcode.rs similarity index 100% rename from candle-examples/examples/bigcode/model.rs rename to candle-transformers/src/models/bigcode.rs diff --git a/candle-examples/examples/falcon/model.rs b/candle-transformers/src/models/falcon.rs similarity index 98% rename from candle-examples/examples/falcon/model.rs rename to candle-transformers/src/models/falcon.rs index b638dd51..6ede136a 100644 --- a/candle-examples/examples/falcon/model.rs +++ b/candle-transformers/src/models/falcon.rs @@ -1,5 +1,4 @@ -use anyhow::Result; -use candle::{DType, Device, Tensor, D}; +use candle::{DType, Device, Result, Tensor, D}; use candle_nn::{Embedding, LayerNorm, Linear, Module, VarBuilder}; const MAX_SEQ_LEN: usize = 5000; @@ -21,7 +20,7 @@ fn layer_norm(size: usize, eps: f64, vb: VarBuilder) -> Result { if let (Ok(weight), Ok(bias)) = (vb.get(size, "gamma"), vb.get(size, "beta")) { (weight, bias) } else { - return Err(err.into()); + return Err(err); } } }; @@ -82,13 +81,13 @@ impl Default for Config { impl Config { pub fn validate(&self) -> Result<()> { if self.alibi { - anyhow::bail!("alibi is not supported"); + candle::bail!("alibi is not supported"); } if self.new_decoder_architecture { - anyhow::bail!("new_decoder_architecture is not supported"); + candle::bail!("new_decoder_architecture is not supported"); } if self.n_head_kv.is_some() { - anyhow::bail!("n_head_kv is not supported"); + candle::bail!("n_head_kv is not supported"); } Ok(()) } diff --git a/candle-examples/examples/llama/model.rs b/candle-transformers/src/models/llama.rs similarity index 99% rename from candle-examples/examples/llama/model.rs rename to candle-transformers/src/models/llama.rs index 275856e0..eed4df5e 100644 --- a/candle-examples/examples/llama/model.rs +++ b/candle-transformers/src/models/llama.rs @@ -4,7 +4,7 @@ use serde::Deserialize; use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use super::MAX_SEQ_LEN; +pub const MAX_SEQ_LEN: usize = 4096; #[derive(Deserialize)] pub struct LlamaConfig { diff --git a/candle-transformers/src/models/mod.rs b/candle-transformers/src/models/mod.rs index 8b137891..1b3dcf25 100644 --- a/candle-transformers/src/models/mod.rs +++ b/candle-transformers/src/models/mod.rs @@ -1 +1,5 @@ - +pub mod bert; +pub mod bigcode; +pub mod falcon; +pub mod llama; +pub mod whisper; diff --git a/candle-examples/examples/whisper/audio.rs b/candle-transformers/src/models/whisper/audio.rs similarity index 97% rename from candle-examples/examples/whisper/audio.rs rename to candle-transformers/src/models/whisper/audio.rs index 2ceed065..4e01de32 100644 --- a/candle-examples/examples/whisper/audio.rs +++ b/candle-transformers/src/models/whisper/audio.rs @@ -198,17 +198,13 @@ fn log_mel_spectrogram_( mel } -pub fn pcm_to_mel( - samples: &[T], - filters: &[T], -) -> anyhow::Result> { - let mel = log_mel_spectrogram_( +pub fn pcm_to_mel(samples: &[T], filters: &[T]) -> Vec { + log_mel_spectrogram_( samples, filters, super::N_FFT, super::HOP_LENGTH, super::N_MELS, false, - ); - Ok(mel) + ) } diff --git a/candle-transformers/src/models/whisper/mod.rs b/candle-transformers/src/models/whisper/mod.rs new file mode 100644 index 00000000..7dc8107b --- /dev/null +++ b/candle-transformers/src/models/whisper/mod.rs @@ -0,0 +1,26 @@ +pub mod audio; +pub mod model; + +pub const DTYPE: candle::DType = candle::DType::F32; + +// Audio parameters. +pub const SAMPLE_RATE: usize = 16000; +pub const N_FFT: usize = 400; +pub const N_MELS: usize = 80; +pub const HOP_LENGTH: usize = 160; +pub const CHUNK_LENGTH: usize = 30; +pub const N_SAMPLES: usize = CHUNK_LENGTH * SAMPLE_RATE; // 480000 samples in a 30-second chunk +pub const N_FRAMES: usize = N_SAMPLES / HOP_LENGTH; // 3000 frames in a mel spectrogram input + +pub const NO_SPEECH_THRESHOLD: f64 = 0.6; +pub const LOGPROB_THRESHOLD: f64 = -1.0; +pub const TEMPERATURES: [f64; 6] = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]; +pub const COMPRESSION_RATIO_THRESHOLD: f64 = 2.4; + +// Tokenizer dependent bits. +pub const SOT_TOKEN: &str = "<|startoftranscript|>"; +pub const TRANSCRIBE_TOKEN: &str = "<|transcribe|>"; +pub const TRANSLATE_TOKEN: &str = "<|translate|>"; +pub const NO_TIMESTAMPS_TOKEN: &str = "<|notimestamps|>"; +pub const EOT_TOKEN: &str = "<|endoftext|>"; +pub const NO_SPEECH_TOKEN: &str = "<|nocaptions|>"; diff --git a/candle-examples/examples/whisper/model.rs b/candle-transformers/src/models/whisper/model.rs similarity index 100% rename from candle-examples/examples/whisper/model.rs rename to candle-transformers/src/models/whisper/model.rs From 35f72514f59b3fa4bd321e3e88a75f5b43cf060f Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 10:20:18 +0100 Subject: [PATCH 052/150] Move more models to candle-transformers (#796) * Move dinov2. * Move efficientnet. * Move the quantized llama model. * Move segment-anything. --- candle-examples/examples/dinov2/main.rs | 283 +-------------- candle-examples/examples/efficientnet/main.rs | 335 +----------------- candle-examples/examples/quantized/main.rs | 2 +- .../examples/segment-anything/main.rs | 109 +----- candle-examples/examples/yolo-v3/main.rs | 2 +- candle-examples/examples/yolo-v8/main.rs | 2 +- candle-examples/src/lib.rs | 1 - candle-transformers/Cargo.toml | 1 + candle-transformers/src/lib.rs | 1 + candle-transformers/src/models/dinov2.rs | 279 +++++++++++++++ .../src/models/efficientnet.rs | 331 +++++++++++++++++ candle-transformers/src/models/mod.rs | 4 + .../src/models/quantized_llama.rs | 0 .../models/segment_anything/image_encoder.rs | 20 +- .../models/segment_anything/mask_decoder.rs | 10 +- .../src/models/segment_anything/mod.rs | 100 ++++++ .../models/segment_anything/prompt_encoder.rs | 8 +- .../src/models/segment_anything/sam.rs | 16 +- .../src/models/segment_anything/tiny_vit.rs | 24 +- .../models/segment_anything/transformer.rs | 4 +- .../src/object_detection.rs | 0 21 files changed, 773 insertions(+), 759 deletions(-) create mode 100644 candle-transformers/src/models/dinov2.rs create mode 100644 candle-transformers/src/models/efficientnet.rs rename candle-examples/examples/quantized/model.rs => candle-transformers/src/models/quantized_llama.rs (100%) rename candle-examples/examples/segment-anything/model_image_encoder.rs => candle-transformers/src/models/segment_anything/image_encoder.rs (96%) rename candle-examples/examples/segment-anything/model_mask_decoder.rs => candle-transformers/src/models/segment_anything/mask_decoder.rs (96%) create mode 100644 candle-transformers/src/models/segment_anything/mod.rs rename candle-examples/examples/segment-anything/model_prompt_encoder.rs => candle-transformers/src/models/segment_anything/prompt_encoder.rs (97%) rename candle-examples/examples/segment-anything/model_sam.rs => candle-transformers/src/models/segment_anything/sam.rs (96%) rename candle-examples/examples/segment-anything/model_tiny_vit.rs => candle-transformers/src/models/segment_anything/tiny_vit.rs (97%) rename candle-examples/examples/segment-anything/model_transformer.rs => candle-transformers/src/models/segment_anything/transformer.rs (99%) rename {candle-examples => candle-transformers}/src/object_detection.rs (100%) diff --git a/candle-examples/examples/dinov2/main.rs b/candle-examples/examples/dinov2/main.rs index e80c81e2..d3adb37c 100644 --- a/candle-examples/examples/dinov2/main.rs +++ b/candle-examples/examples/dinov2/main.rs @@ -9,285 +9,10 @@ extern crate accelerate_src; use clap::Parser; -use candle::{DType, IndexOp, Result, Tensor, D}; -use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; +use candle::{DType, IndexOp, D}; +use candle_nn::{Module, VarBuilder}; +use candle_transformers::models::dinov2; -const IMG_SIZE: usize = 518; -const PATCH_SIZE: usize = 14; -const NUM_CLASSES: usize = 1000; - -fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { - if bias { - candle_nn::linear(in_dim, out_dim, vb) - } else { - candle_nn::linear_no_bias(in_dim, out_dim, vb) - } -} - -#[derive(Debug)] -struct Attention { - qkv: Linear, - proj: Linear, - num_heads: usize, - scale: f64, -} - -impl Attention { - fn new( - vb: VarBuilder, - dim: usize, - num_heads: usize, - qkv_bias: bool, - proj_bias: bool, - ) -> Result { - let qkv = linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; - let proj = linear(vb.pp("proj"), dim, dim, proj_bias)?; - let scale = 1. / ((dim / num_heads) as f64).sqrt(); - Ok(Self { - qkv, - proj, - num_heads, - scale, - }) - } -} - -impl Module for Attention { - fn forward(&self, xs: &Tensor) -> Result { - let (b, n, c) = xs.dims3()?; - let qkv = self - .qkv - .forward(xs)? - .reshape((b, n, 3, self.num_heads, c / self.num_heads))? - .transpose(1, 2)? // 02134 - .transpose(0, 1)? // 20134 - .transpose(2, 3)?; // 20314 - let q = (qkv.i(0)? * self.scale)?; - let k = qkv.i(1)?; - let v = qkv.i(2)?; - let attn = candle_nn::ops::softmax(&q.matmul(&k.t()?)?, D::Minus1)?; - let attn = attn.matmul(&v)?.transpose(1, 2)?.reshape((b, n, c))?; - self.proj.forward(&attn) - } -} - -#[derive(Debug)] -struct LayerScale { - gamma: Tensor, -} - -impl LayerScale { - fn new(vb: VarBuilder, dim: usize) -> Result { - let gamma = vb.get(dim, "gamma")?; - Ok(Self { gamma }) - } -} - -impl Module for LayerScale { - fn forward(&self, xs: &Tensor) -> Result { - xs.broadcast_mul(&self.gamma) - } -} - -#[derive(Debug)] -struct Mlp { - fc1: Linear, - fc2: Linear, -} - -impl Mlp { - fn new(vb: VarBuilder, in_features: usize, hidden_features: usize, bias: bool) -> Result { - let out_features = in_features; - let fc1 = linear(vb.pp("fc1"), in_features, hidden_features, bias)?; - let fc2 = linear(vb.pp("fc2"), hidden_features, out_features, bias)?; - Ok(Self { fc1, fc2 }) - } -} - -impl Module for Mlp { - fn forward(&self, xs: &Tensor) -> Result { - let xs = self.fc1.forward(xs)?.gelu()?; - self.fc2.forward(&xs) - } -} - -#[derive(Debug)] -struct Block { - norm1: LayerNorm, - attn: Attention, - ls1: LayerScale, - norm2: LayerNorm, - mlp: Mlp, - ls2: LayerScale, -} - -impl Block { - fn new(vb: VarBuilder, dim: usize, num_heads: usize) -> Result { - let norm1 = layer_norm(dim, 1e-5, vb.pp("norm1"))?; - let attn = Attention::new(vb.pp("attn"), dim, num_heads, true, true)?; - let ls1 = LayerScale::new(vb.pp("ls1"), dim)?; - let norm2 = layer_norm(dim, 1e-5, vb.pp("norm2"))?; - let mlp = Mlp::new(vb.pp("mlp"), dim, dim * 4, true)?; - let ls2 = LayerScale::new(vb.pp("ls2"), dim)?; - Ok(Self { - norm1, - attn, - ls1, - norm2, - mlp, - ls2, - }) - } -} - -impl Module for Block { - fn forward(&self, xs: &Tensor) -> Result { - let residual = xs; - let xs = self - .ls1 - .forward(&self.attn.forward(&self.norm1.forward(xs)?)?)?; - let xs = (xs + residual)?; - let residual = &xs; - let xs = self - .ls2 - .forward(&self.mlp.forward(&self.norm2.forward(&xs)?)?)?; - xs + residual - } -} - -#[derive(Debug)] -struct PatchEmbed { - proj: candle_nn::Conv2d, - patch_size: (usize, usize), - num_patches: usize, -} - -impl PatchEmbed { - fn new( - vb: VarBuilder, - img_size: usize, - patch_size: usize, - in_chans: usize, - embed_dim: usize, - ) -> Result { - let config = candle_nn::Conv2dConfig { - stride: patch_size, - ..Default::default() - }; - let proj = candle_nn::conv2d(in_chans, embed_dim, patch_size, config, vb.pp("proj"))?; - let num_patches = (img_size / patch_size) * (img_size / patch_size); - Ok(Self { - proj, - patch_size: (patch_size, patch_size), - num_patches, - }) - } -} - -impl Module for PatchEmbed { - fn forward(&self, xs: &Tensor) -> Result { - let (_b, _c, h, w) = xs.dims4()?; - let (patch_h, patch_w) = self.patch_size; - if (h % patch_h) != 0 { - candle::bail!("image height {h} is not a multiple of patch height {patch_h}") - } - if (w % patch_w) != 0 { - candle::bail!("image width {w} is not a multiple of patch width {patch_w}") - } - let xs = self.proj.forward(xs)?; - let (b, c, h, w) = xs.dims4()?; - // flatten embeddings. - xs.reshape((b, c, h * w))?.transpose(1, 2) - } -} - -#[derive(Debug)] -pub struct DinoVisionTransformer { - patch_embed: PatchEmbed, - cls_token: Tensor, - pos_embed: Tensor, - blocks: Vec, - norm: LayerNorm, - head: Linear, -} - -impl DinoVisionTransformer { - pub fn new(vb: VarBuilder, depth: usize, embed_dim: usize, num_heads: usize) -> Result { - let patch_embed = - PatchEmbed::new(vb.pp("patch_embed"), IMG_SIZE, PATCH_SIZE, 3, embed_dim)?; - let cls_token = vb.get((1, 1, embed_dim), "cls_token")?; - let num_tokens = 1; - let pos_embed = vb.get( - (1, patch_embed.num_patches + num_tokens, embed_dim), - "pos_embed", - )?; - let head = linear(vb.pp("head"), 2 * embed_dim, NUM_CLASSES, true)?; - let norm = layer_norm(embed_dim, 1e-5, vb.pp("norm"))?; - let vb_b = vb.pp("blocks"); - let blocks = (0..depth) - .map(|i| Block::new(vb_b.pp(&i.to_string()), embed_dim, num_heads)) - .collect::>>()?; - Ok(Self { - patch_embed, - cls_token, - pos_embed, - blocks, - norm, - head, - }) - } - - fn interpolate_pos_encoding(&self, xs: &Tensor, w: usize, h: usize) -> Result { - let npatch = xs.dim(1)? - 1; - let n = self.pos_embed.dim(1)? - 1; - let sqrt_n = (n as f64).sqrt(); - if npatch == n && w == h { - return Ok(xs.clone()); - } - let class_pos_embed = self.pos_embed.i((.., ..1))?; - let patch_pos_embed = self.pos_embed.i((.., 1..))?; - let dim = xs.dim(D::Minus1)?; - let (w0, h0) = ((w / PATCH_SIZE) as f64 + 0.1, (h / PATCH_SIZE) as f64 + 0.1); - let patch_pos_embed = patch_pos_embed - .reshape((1, sqrt_n as usize, sqrt_n as usize, dim))? - .transpose(2, 3)? - .transpose(1, 2)?; - // This uses bicubic interpolation in the original implementation. - let patch_pos_embed = patch_pos_embed.upsample_nearest2d(h0 as usize, w0 as usize)?; - let el_count = patch_pos_embed.shape().elem_count(); - let patch_pos_embed = - patch_pos_embed - .transpose(1, 2)? - .transpose(2, 3)? - .reshape((1, el_count / dim, dim))?; - Tensor::cat(&[&class_pos_embed, &patch_pos_embed], 1) - } - - fn prepare_tokens_with_mask(&self, xs: &Tensor) -> Result { - let (_b, _nc, w, h) = xs.dims4()?; - let xs = self.patch_embed.forward(xs)?; - let xs = Tensor::cat(&[&self.cls_token, &xs], 1)?; - &xs + &self.interpolate_pos_encoding(&xs, w, h)? - } -} - -impl Module for DinoVisionTransformer { - fn forward(&self, xs: &Tensor) -> Result { - let mut xs = self.prepare_tokens_with_mask(xs)?; - for blk in self.blocks.iter() { - xs = blk.forward(&xs)? - } - let xs = self.norm.forward(&xs)?; - let xs_norm_clstoken = xs.i((.., 0))?; - let xs_norm_patchtokens = xs.i((.., 1..))?.mean(1)?; - let xs = Tensor::cat(&[xs_norm_clstoken, xs_norm_patchtokens], D::Minus1)?; - self.head.forward(&xs) - } -} - -pub fn vit_small(vb: VarBuilder) -> Result { - DinoVisionTransformer::new(vb, 12, 384, 6) -} #[derive(Parser)] struct Args { #[arg(long)] @@ -320,7 +45,7 @@ pub fn main() -> anyhow::Result<()> { let weights = unsafe { candle::safetensors::MmapedFile::new(model_file)? }; let weights = weights.deserialize()?; let vb = VarBuilder::from_safetensors(vec![weights], DType::F32, &device); - let model = vit_small(vb)?; + let model = dinov2::vit_small(vb)?; println!("model built"); let logits = model.forward(&image.unsqueeze(0)?)?; let prs = candle_nn::ops::softmax(&logits, D::Minus1)? diff --git a/candle-examples/examples/efficientnet/main.rs b/candle-examples/examples/efficientnet/main.rs index cbe2c90a..1e45e301 100644 --- a/candle-examples/examples/efficientnet/main.rs +++ b/candle-examples/examples/efficientnet/main.rs @@ -8,340 +8,11 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; +use candle::{DType, IndexOp, D}; +use candle_nn::{Module, VarBuilder}; +use candle_transformers::models::efficientnet::{EfficientNet, MBConvConfig}; use clap::{Parser, ValueEnum}; -use candle::{DType, IndexOp, Result, Tensor, D}; -use candle_nn as nn; -use nn::{Module, VarBuilder}; - -// Based on the Python version from torchvision. -// https://github.com/pytorch/vision/blob/0d75d9e5516f446c9c0ef93bd4ed9fea13992d06/torchvision/models/efficientnet.py#L47 -#[derive(Debug, Clone, Copy)] -pub struct MBConvConfig { - expand_ratio: f64, - kernel: usize, - stride: usize, - input_channels: usize, - out_channels: usize, - num_layers: usize, -} - -fn make_divisible(v: f64, divisor: usize) -> usize { - let min_value = divisor; - let new_v = usize::max( - min_value, - (v + divisor as f64 * 0.5) as usize / divisor * divisor, - ); - if (new_v as f64) < 0.9 * v { - new_v + divisor - } else { - new_v - } -} - -fn bneck_confs(width_mult: f64, depth_mult: f64) -> Vec { - let bneck_conf = |e, k, s, i, o, n| { - let input_channels = make_divisible(i as f64 * width_mult, 8); - let out_channels = make_divisible(o as f64 * width_mult, 8); - let num_layers = (n as f64 * depth_mult).ceil() as usize; - MBConvConfig { - expand_ratio: e, - kernel: k, - stride: s, - input_channels, - out_channels, - num_layers, - } - }; - vec![ - bneck_conf(1., 3, 1, 32, 16, 1), - bneck_conf(6., 3, 2, 16, 24, 2), - bneck_conf(6., 5, 2, 24, 40, 2), - bneck_conf(6., 3, 2, 40, 80, 3), - bneck_conf(6., 5, 1, 80, 112, 3), - bneck_conf(6., 5, 2, 112, 192, 4), - bneck_conf(6., 3, 1, 192, 320, 1), - ] -} - -impl MBConvConfig { - fn b0() -> Vec { - bneck_confs(1.0, 1.0) - } - fn b1() -> Vec { - bneck_confs(1.0, 1.1) - } - fn b2() -> Vec { - bneck_confs(1.1, 1.2) - } - fn b3() -> Vec { - bneck_confs(1.2, 1.4) - } - fn b4() -> Vec { - bneck_confs(1.4, 1.8) - } - fn b5() -> Vec { - bneck_confs(1.6, 2.2) - } - fn b6() -> Vec { - bneck_confs(1.8, 2.6) - } - fn b7() -> Vec { - bneck_confs(2.0, 3.1) - } -} - -/// Conv2D with same padding. -#[derive(Debug)] -struct Conv2DSame { - conv2d: nn::Conv2d, - s: usize, - k: usize, -} - -impl Conv2DSame { - fn new( - vb: VarBuilder, - i: usize, - o: usize, - k: usize, - stride: usize, - groups: usize, - bias: bool, - ) -> Result { - let conv_config = nn::Conv2dConfig { - stride, - groups, - ..Default::default() - }; - let conv2d = if bias { - nn::conv2d(i, o, k, conv_config, vb)? - } else { - nn::conv2d_no_bias(i, o, k, conv_config, vb)? - }; - Ok(Self { - conv2d, - s: stride, - k, - }) - } -} - -impl Module for Conv2DSame { - fn forward(&self, xs: &Tensor) -> Result { - let s = self.s; - let k = self.k; - let (_, _, ih, iw) = xs.dims4()?; - let oh = (ih + s - 1) / s; - let ow = (iw + s - 1) / s; - let pad_h = usize::max((oh - 1) * s + k - ih, 0); - let pad_w = usize::max((ow - 1) * s + k - iw, 0); - if pad_h > 0 || pad_w > 0 { - let xs = xs.pad_with_zeros(2, pad_h / 2, pad_h - pad_h / 2)?; - let xs = xs.pad_with_zeros(3, pad_w / 2, pad_w - pad_w / 2)?; - self.conv2d.forward(&xs) - } else { - self.conv2d.forward(xs) - } - } -} - -#[derive(Debug)] -struct ConvNormActivation { - conv2d: Conv2DSame, - bn2d: nn::BatchNorm, - activation: bool, -} - -impl ConvNormActivation { - fn new( - vb: VarBuilder, - i: usize, - o: usize, - k: usize, - stride: usize, - groups: usize, - ) -> Result { - let conv2d = Conv2DSame::new(vb.pp("0"), i, o, k, stride, groups, false)?; - let bn2d = nn::batch_norm(o, 1e-3, vb.pp("1"))?; - Ok(Self { - conv2d, - bn2d, - activation: true, - }) - } - - fn no_activation(self) -> Self { - Self { - activation: false, - ..self - } - } -} - -impl Module for ConvNormActivation { - fn forward(&self, xs: &Tensor) -> Result { - let xs = self.conv2d.forward(xs)?; - let xs = self.bn2d.forward(&xs)?; - if self.activation { - swish(&xs) - } else { - Ok(xs) - } - } -} - -#[derive(Debug)] -struct SqueezeExcitation { - fc1: Conv2DSame, - fc2: Conv2DSame, -} - -impl SqueezeExcitation { - fn new(vb: VarBuilder, in_channels: usize, squeeze_channels: usize) -> Result { - let fc1 = Conv2DSame::new(vb.pp("fc1"), in_channels, squeeze_channels, 1, 1, 1, true)?; - let fc2 = Conv2DSame::new(vb.pp("fc2"), squeeze_channels, in_channels, 1, 1, 1, true)?; - Ok(Self { fc1, fc2 }) - } -} - -impl Module for SqueezeExcitation { - fn forward(&self, xs: &Tensor) -> Result { - let residual = xs; - // equivalent to adaptive_avg_pool2d([1, 1]) - let xs = xs.mean_keepdim(D::Minus2)?.mean_keepdim(D::Minus1)?; - let xs = self.fc1.forward(&xs)?; - let xs = swish(&xs)?; - let xs = self.fc2.forward(&xs)?; - let xs = nn::ops::sigmoid(&xs)?; - residual.broadcast_mul(&xs) - } -} - -#[derive(Debug)] -struct MBConv { - expand_cna: Option, - depthwise_cna: ConvNormActivation, - squeeze_excitation: SqueezeExcitation, - project_cna: ConvNormActivation, - config: MBConvConfig, -} - -impl MBConv { - fn new(vb: VarBuilder, c: MBConvConfig) -> Result { - let vb = vb.pp("block"); - let exp = make_divisible(c.input_channels as f64 * c.expand_ratio, 8); - let expand_cna = if exp != c.input_channels { - Some(ConvNormActivation::new( - vb.pp("0"), - c.input_channels, - exp, - 1, - 1, - 1, - )?) - } else { - None - }; - let start_index = if expand_cna.is_some() { 1 } else { 0 }; - let depthwise_cna = - ConvNormActivation::new(vb.pp(start_index), exp, exp, c.kernel, c.stride, exp)?; - let squeeze_channels = usize::max(1, c.input_channels / 4); - let squeeze_excitation = - SqueezeExcitation::new(vb.pp(start_index + 1), exp, squeeze_channels)?; - let project_cna = - ConvNormActivation::new(vb.pp(start_index + 2), exp, c.out_channels, 1, 1, 1)? - .no_activation(); - Ok(Self { - expand_cna, - depthwise_cna, - squeeze_excitation, - project_cna, - config: c, - }) - } -} - -impl Module for MBConv { - fn forward(&self, xs: &Tensor) -> Result { - let use_res_connect = - self.config.stride == 1 && self.config.input_channels == self.config.out_channels; - let ys = match &self.expand_cna { - Some(expand_cna) => expand_cna.forward(xs)?, - None => xs.clone(), - }; - let ys = self.depthwise_cna.forward(&ys)?; - let ys = self.squeeze_excitation.forward(&ys)?; - let ys = self.project_cna.forward(&ys)?; - if use_res_connect { - ys + xs - } else { - Ok(ys) - } - } -} - -fn swish(s: &Tensor) -> Result { - s * nn::ops::sigmoid(s)? -} - -#[derive(Debug)] -struct EfficientNet { - init_cna: ConvNormActivation, - blocks: Vec, - final_cna: ConvNormActivation, - classifier: nn::Linear, -} - -impl EfficientNet { - fn new(p: VarBuilder, configs: Vec, nclasses: usize) -> Result { - let f_p = p.pp("features"); - let first_in_c = configs[0].input_channels; - let last_out_c = configs.last().unwrap().out_channels; - let final_out_c = 4 * last_out_c; - let init_cna = ConvNormActivation::new(f_p.pp(0), 3, first_in_c, 3, 2, 1)?; - let nconfigs = configs.len(); - let mut blocks = vec![]; - for (index, cnf) in configs.into_iter().enumerate() { - let f_p = f_p.pp(index + 1); - for r_index in 0..cnf.num_layers { - let cnf = if r_index == 0 { - cnf - } else { - MBConvConfig { - input_channels: cnf.out_channels, - stride: 1, - ..cnf - } - }; - blocks.push(MBConv::new(f_p.pp(r_index), cnf)?) - } - } - let final_cna = - ConvNormActivation::new(f_p.pp(nconfigs + 1), last_out_c, final_out_c, 1, 1, 1)?; - let classifier = nn::linear(final_out_c, nclasses, p.pp("classifier.1"))?; - Ok(Self { - init_cna, - blocks, - final_cna, - classifier, - }) - } -} - -impl Module for EfficientNet { - fn forward(&self, xs: &Tensor) -> Result { - let mut xs = self.init_cna.forward(xs)?; - for block in self.blocks.iter() { - xs = block.forward(&xs)? - } - let xs = self.final_cna.forward(&xs)?; - // Equivalent to adaptive_avg_pool2d([1, 1]) -> squeeze(-1) -> squeeze(-1) - let xs = xs.mean(D::Minus1)?.mean(D::Minus1)?; - self.classifier.forward(&xs) - } -} - #[derive(Clone, Copy, Debug, ValueEnum)] enum Which { B0, diff --git a/candle-examples/examples/quantized/main.rs b/candle-examples/examples/quantized/main.rs index a3f98d8e..c8179d33 100644 --- a/candle-examples/examples/quantized/main.rs +++ b/candle-examples/examples/quantized/main.rs @@ -12,7 +12,7 @@ use candle::quantized::{ggml_file, gguf_file}; use candle::{Device, Tensor}; use candle_transformers::generation::LogitsProcessor; -mod model; +use candle_transformers::models::quantized_llama as model; use model::ModelWeights; const DEFAULT_PROMPT: &str = "My favorite theorem is "; diff --git a/candle-examples/examples/segment-anything/main.rs b/candle-examples/examples/segment-anything/main.rs index 9ce2f158..21ba0415 100644 --- a/candle-examples/examples/segment-anything/main.rs +++ b/candle-examples/examples/segment-anything/main.rs @@ -7,108 +7,11 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; -pub mod model_image_encoder; -pub mod model_mask_decoder; -pub mod model_prompt_encoder; -pub mod model_sam; -pub mod model_tiny_vit; -pub mod model_transformer; - -use candle::{DType, Result, Tensor}; -use candle_nn::{Module, VarBuilder}; +use candle::DType; +use candle_nn::VarBuilder; +use candle_transformers::models::segment_anything::sam; use clap::Parser; -pub fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { - let inner = if bias { - candle_nn::linear(in_dim, out_dim, vb)? - } else { - candle_nn::linear_no_bias(in_dim, out_dim, vb)? - }; - let span = tracing::span!(tracing::Level::TRACE, "linear"); - Ok(Linear { inner, span }) -} - -#[derive(Debug)] -pub struct LayerNorm2d { - weight: Tensor, - bias: Tensor, - num_channels: usize, - eps: f64, -} - -impl LayerNorm2d { - pub fn new(num_channels: usize, eps: f64, vb: VarBuilder) -> Result { - let weight = vb.get(num_channels, "weight")?; - let bias = vb.get(num_channels, "bias")?; - Ok(Self { - weight, - bias, - num_channels, - eps, - }) - } -} - -impl Module for LayerNorm2d { - fn forward(&self, xs: &Tensor) -> Result { - let u = xs.mean_keepdim(1)?; - let xs = xs.broadcast_sub(&u)?; - let s = xs.sqr()?.mean_keepdim(1)?; - let xs = xs.broadcast_div(&(s + self.eps)?.sqrt()?)?; - xs.broadcast_mul(&self.weight.reshape((1, self.num_channels, 1, 1))?)? - .broadcast_add(&self.bias.reshape((1, self.num_channels, 1, 1))?) - } -} - -#[derive(Debug)] -pub struct MlpBlock { - lin1: Linear, - lin2: Linear, - activation: candle_nn::Activation, - span: tracing::Span, -} - -impl MlpBlock { - pub fn new( - embedding_dim: usize, - mlp_dim: usize, - activation: candle_nn::Activation, - vb: VarBuilder, - ) -> Result { - let lin1 = linear(vb.pp("lin1"), embedding_dim, mlp_dim, true)?; - let lin2 = linear(vb.pp("lin2"), mlp_dim, embedding_dim, true)?; - let span = tracing::span!(tracing::Level::TRACE, "mlp-block"); - Ok(Self { - lin1, - lin2, - activation, - span, - }) - } -} - -impl Module for MlpBlock { - fn forward(&self, xs: &Tensor) -> Result { - let _enter = self.span.enter(); - xs.apply(&self.lin1)? - .apply(&self.activation)? - .apply(&self.lin2) - } -} - -#[derive(Debug)] -pub struct Linear { - inner: candle_nn::Linear, - span: tracing::Span, -} - -impl Module for Linear { - fn forward(&self, x: &Tensor) -> Result { - let _enter = self.span.enter(); - self.inner.forward(x) - } -} - #[derive(Parser)] struct Args { #[arg(long)] @@ -173,7 +76,7 @@ pub fn main() -> anyhow::Result<()> { let (_c, h, w) = image.dims3()?; (image, h, w) } else { - let (image, h, w) = candle_examples::load_image(&args.image, Some(model_sam::IMAGE_SIZE))?; + let (image, h, w) = candle_examples::load_image(&args.image, Some(sam::IMAGE_SIZE))?; (image.to_device(&device)?, h, w) }; println!("loaded image {image:?}"); @@ -195,9 +98,9 @@ pub fn main() -> anyhow::Result<()> { let weights = weights.deserialize()?; let vb = VarBuilder::from_safetensors(vec![weights], DType::F32, &device); let sam = if args.use_tiny { - model_sam::Sam::new_tiny(vb)? // tiny vit_t + sam::Sam::new_tiny(vb)? // tiny vit_t } else { - model_sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)? // sam_vit_b + sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)? // sam_vit_b }; if args.generate_masks { diff --git a/candle-examples/examples/yolo-v3/main.rs b/candle-examples/examples/yolo-v3/main.rs index 20021b45..ecf75bdf 100644 --- a/candle-examples/examples/yolo-v3/main.rs +++ b/candle-examples/examples/yolo-v3/main.rs @@ -4,7 +4,7 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; -use candle_examples::object_detection::{non_maximum_suppression, Bbox}; +use candle_transformers::object_detection::{non_maximum_suppression, Bbox}; mod darknet; use anyhow::Result; diff --git a/candle-examples/examples/yolo-v8/main.rs b/candle-examples/examples/yolo-v8/main.rs index 2017b5be..d48bac35 100644 --- a/candle-examples/examples/yolo-v8/main.rs +++ b/candle-examples/examples/yolo-v8/main.rs @@ -8,8 +8,8 @@ mod model; use model::{Multiples, YoloV8, YoloV8Pose}; use candle::{DType, Device, IndexOp, Result, Tensor}; -use candle_examples::object_detection::{non_maximum_suppression, Bbox, KeyPoint}; use candle_nn::{Module, VarBuilder}; +use candle_transformers::object_detection::{non_maximum_suppression, Bbox, KeyPoint}; use clap::{Parser, ValueEnum}; use image::DynamicImage; diff --git a/candle-examples/src/lib.rs b/candle-examples/src/lib.rs index c14b2d6b..5e0b44fb 100644 --- a/candle-examples/src/lib.rs +++ b/candle-examples/src/lib.rs @@ -1,6 +1,5 @@ pub mod coco_classes; pub mod imagenet; -pub mod object_detection; use candle::{Device, Result, Tensor}; diff --git a/candle-transformers/Cargo.toml b/candle-transformers/Cargo.toml index 6b2087cb..86caf918 100644 --- a/candle-transformers/Cargo.toml +++ b/candle-transformers/Cargo.toml @@ -16,6 +16,7 @@ candle-nn = { path = "../candle-nn", version = "0.2.1" } intel-mkl-src = { workspace = true, optional = true } num-traits = { workspace = true } rand = { workspace = true } +rayon = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } tracing = { workspace = true } diff --git a/candle-transformers/src/lib.rs b/candle-transformers/src/lib.rs index a8890dc8..b83e5056 100644 --- a/candle-transformers/src/lib.rs +++ b/candle-transformers/src/lib.rs @@ -1,4 +1,5 @@ pub mod generation; pub mod models; +pub mod object_detection; pub mod pipelines; pub mod utils; diff --git a/candle-transformers/src/models/dinov2.rs b/candle-transformers/src/models/dinov2.rs new file mode 100644 index 00000000..0edc8494 --- /dev/null +++ b/candle-transformers/src/models/dinov2.rs @@ -0,0 +1,279 @@ +use candle::{IndexOp, Result, Tensor, D}; +use candle_nn::{layer_norm, LayerNorm, Linear, Module, VarBuilder}; + +const IMG_SIZE: usize = 518; +const PATCH_SIZE: usize = 14; +const NUM_CLASSES: usize = 1000; + +fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { + if bias { + candle_nn::linear(in_dim, out_dim, vb) + } else { + candle_nn::linear_no_bias(in_dim, out_dim, vb) + } +} + +#[derive(Debug)] +struct Attention { + qkv: Linear, + proj: Linear, + num_heads: usize, + scale: f64, +} + +impl Attention { + fn new( + vb: VarBuilder, + dim: usize, + num_heads: usize, + qkv_bias: bool, + proj_bias: bool, + ) -> Result { + let qkv = linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; + let proj = linear(vb.pp("proj"), dim, dim, proj_bias)?; + let scale = 1. / ((dim / num_heads) as f64).sqrt(); + Ok(Self { + qkv, + proj, + num_heads, + scale, + }) + } +} + +impl Module for Attention { + fn forward(&self, xs: &Tensor) -> Result { + let (b, n, c) = xs.dims3()?; + let qkv = self + .qkv + .forward(xs)? + .reshape((b, n, 3, self.num_heads, c / self.num_heads))? + .transpose(1, 2)? // 02134 + .transpose(0, 1)? // 20134 + .transpose(2, 3)?; // 20314 + let q = (qkv.i(0)? * self.scale)?; + let k = qkv.i(1)?; + let v = qkv.i(2)?; + let attn = candle_nn::ops::softmax(&q.matmul(&k.t()?)?, D::Minus1)?; + let attn = attn.matmul(&v)?.transpose(1, 2)?.reshape((b, n, c))?; + self.proj.forward(&attn) + } +} + +#[derive(Debug)] +struct LayerScale { + gamma: Tensor, +} + +impl LayerScale { + fn new(vb: VarBuilder, dim: usize) -> Result { + let gamma = vb.get(dim, "gamma")?; + Ok(Self { gamma }) + } +} + +impl Module for LayerScale { + fn forward(&self, xs: &Tensor) -> Result { + xs.broadcast_mul(&self.gamma) + } +} + +#[derive(Debug)] +struct Mlp { + fc1: Linear, + fc2: Linear, +} + +impl Mlp { + fn new(vb: VarBuilder, in_features: usize, hidden_features: usize, bias: bool) -> Result { + let out_features = in_features; + let fc1 = linear(vb.pp("fc1"), in_features, hidden_features, bias)?; + let fc2 = linear(vb.pp("fc2"), hidden_features, out_features, bias)?; + Ok(Self { fc1, fc2 }) + } +} + +impl Module for Mlp { + fn forward(&self, xs: &Tensor) -> Result { + let xs = self.fc1.forward(xs)?.gelu()?; + self.fc2.forward(&xs) + } +} + +#[derive(Debug)] +struct Block { + norm1: LayerNorm, + attn: Attention, + ls1: LayerScale, + norm2: LayerNorm, + mlp: Mlp, + ls2: LayerScale, +} + +impl Block { + fn new(vb: VarBuilder, dim: usize, num_heads: usize) -> Result { + let norm1 = layer_norm(dim, 1e-5, vb.pp("norm1"))?; + let attn = Attention::new(vb.pp("attn"), dim, num_heads, true, true)?; + let ls1 = LayerScale::new(vb.pp("ls1"), dim)?; + let norm2 = layer_norm(dim, 1e-5, vb.pp("norm2"))?; + let mlp = Mlp::new(vb.pp("mlp"), dim, dim * 4, true)?; + let ls2 = LayerScale::new(vb.pp("ls2"), dim)?; + Ok(Self { + norm1, + attn, + ls1, + norm2, + mlp, + ls2, + }) + } +} + +impl Module for Block { + fn forward(&self, xs: &Tensor) -> Result { + let residual = xs; + let xs = self + .ls1 + .forward(&self.attn.forward(&self.norm1.forward(xs)?)?)?; + let xs = (xs + residual)?; + let residual = &xs; + let xs = self + .ls2 + .forward(&self.mlp.forward(&self.norm2.forward(&xs)?)?)?; + xs + residual + } +} + +#[derive(Debug)] +struct PatchEmbed { + proj: candle_nn::Conv2d, + patch_size: (usize, usize), + num_patches: usize, +} + +impl PatchEmbed { + fn new( + vb: VarBuilder, + img_size: usize, + patch_size: usize, + in_chans: usize, + embed_dim: usize, + ) -> Result { + let config = candle_nn::Conv2dConfig { + stride: patch_size, + ..Default::default() + }; + let proj = candle_nn::conv2d(in_chans, embed_dim, patch_size, config, vb.pp("proj"))?; + let num_patches = (img_size / patch_size) * (img_size / patch_size); + Ok(Self { + proj, + patch_size: (patch_size, patch_size), + num_patches, + }) + } +} + +impl Module for PatchEmbed { + fn forward(&self, xs: &Tensor) -> Result { + let (_b, _c, h, w) = xs.dims4()?; + let (patch_h, patch_w) = self.patch_size; + if (h % patch_h) != 0 { + candle::bail!("image height {h} is not a multiple of patch height {patch_h}") + } + if (w % patch_w) != 0 { + candle::bail!("image width {w} is not a multiple of patch width {patch_w}") + } + let xs = self.proj.forward(xs)?; + let (b, c, h, w) = xs.dims4()?; + // flatten embeddings. + xs.reshape((b, c, h * w))?.transpose(1, 2) + } +} + +#[derive(Debug)] +pub struct DinoVisionTransformer { + patch_embed: PatchEmbed, + cls_token: Tensor, + pos_embed: Tensor, + blocks: Vec, + norm: LayerNorm, + head: Linear, +} + +impl DinoVisionTransformer { + pub fn new(vb: VarBuilder, depth: usize, embed_dim: usize, num_heads: usize) -> Result { + let patch_embed = + PatchEmbed::new(vb.pp("patch_embed"), IMG_SIZE, PATCH_SIZE, 3, embed_dim)?; + let cls_token = vb.get((1, 1, embed_dim), "cls_token")?; + let num_tokens = 1; + let pos_embed = vb.get( + (1, patch_embed.num_patches + num_tokens, embed_dim), + "pos_embed", + )?; + let head = linear(vb.pp("head"), 2 * embed_dim, NUM_CLASSES, true)?; + let norm = layer_norm(embed_dim, 1e-5, vb.pp("norm"))?; + let vb_b = vb.pp("blocks"); + let blocks = (0..depth) + .map(|i| Block::new(vb_b.pp(&i.to_string()), embed_dim, num_heads)) + .collect::>>()?; + Ok(Self { + patch_embed, + cls_token, + pos_embed, + blocks, + norm, + head, + }) + } + + fn interpolate_pos_encoding(&self, xs: &Tensor, w: usize, h: usize) -> Result { + let npatch = xs.dim(1)? - 1; + let n = self.pos_embed.dim(1)? - 1; + let sqrt_n = (n as f64).sqrt(); + if npatch == n && w == h { + return Ok(xs.clone()); + } + let class_pos_embed = self.pos_embed.i((.., ..1))?; + let patch_pos_embed = self.pos_embed.i((.., 1..))?; + let dim = xs.dim(D::Minus1)?; + let (w0, h0) = ((w / PATCH_SIZE) as f64 + 0.1, (h / PATCH_SIZE) as f64 + 0.1); + let patch_pos_embed = patch_pos_embed + .reshape((1, sqrt_n as usize, sqrt_n as usize, dim))? + .transpose(2, 3)? + .transpose(1, 2)?; + // This uses bicubic interpolation in the original implementation. + let patch_pos_embed = patch_pos_embed.upsample_nearest2d(h0 as usize, w0 as usize)?; + let el_count = patch_pos_embed.shape().elem_count(); + let patch_pos_embed = + patch_pos_embed + .transpose(1, 2)? + .transpose(2, 3)? + .reshape((1, el_count / dim, dim))?; + Tensor::cat(&[&class_pos_embed, &patch_pos_embed], 1) + } + + fn prepare_tokens_with_mask(&self, xs: &Tensor) -> Result { + let (_b, _nc, w, h) = xs.dims4()?; + let xs = self.patch_embed.forward(xs)?; + let xs = Tensor::cat(&[&self.cls_token, &xs], 1)?; + &xs + &self.interpolate_pos_encoding(&xs, w, h)? + } +} + +impl Module for DinoVisionTransformer { + fn forward(&self, xs: &Tensor) -> Result { + let mut xs = self.prepare_tokens_with_mask(xs)?; + for blk in self.blocks.iter() { + xs = blk.forward(&xs)? + } + let xs = self.norm.forward(&xs)?; + let xs_norm_clstoken = xs.i((.., 0))?; + let xs_norm_patchtokens = xs.i((.., 1..))?.mean(1)?; + let xs = Tensor::cat(&[xs_norm_clstoken, xs_norm_patchtokens], D::Minus1)?; + self.head.forward(&xs) + } +} + +pub fn vit_small(vb: VarBuilder) -> Result { + DinoVisionTransformer::new(vb, 12, 384, 6) +} diff --git a/candle-transformers/src/models/efficientnet.rs b/candle-transformers/src/models/efficientnet.rs new file mode 100644 index 00000000..ab51c76d --- /dev/null +++ b/candle-transformers/src/models/efficientnet.rs @@ -0,0 +1,331 @@ +use candle::{Result, Tensor, D}; +use candle_nn as nn; +use nn::{Module, VarBuilder}; + +// Based on the Python version from torchvision. +// https://github.com/pytorch/vision/blob/0d75d9e5516f446c9c0ef93bd4ed9fea13992d06/torchvision/models/efficientnet.py#L47 +#[derive(Debug, Clone, Copy)] +pub struct MBConvConfig { + expand_ratio: f64, + kernel: usize, + stride: usize, + input_channels: usize, + out_channels: usize, + num_layers: usize, +} + +fn make_divisible(v: f64, divisor: usize) -> usize { + let min_value = divisor; + let new_v = usize::max( + min_value, + (v + divisor as f64 * 0.5) as usize / divisor * divisor, + ); + if (new_v as f64) < 0.9 * v { + new_v + divisor + } else { + new_v + } +} + +fn bneck_confs(width_mult: f64, depth_mult: f64) -> Vec { + let bneck_conf = |e, k, s, i, o, n| { + let input_channels = make_divisible(i as f64 * width_mult, 8); + let out_channels = make_divisible(o as f64 * width_mult, 8); + let num_layers = (n as f64 * depth_mult).ceil() as usize; + MBConvConfig { + expand_ratio: e, + kernel: k, + stride: s, + input_channels, + out_channels, + num_layers, + } + }; + vec![ + bneck_conf(1., 3, 1, 32, 16, 1), + bneck_conf(6., 3, 2, 16, 24, 2), + bneck_conf(6., 5, 2, 24, 40, 2), + bneck_conf(6., 3, 2, 40, 80, 3), + bneck_conf(6., 5, 1, 80, 112, 3), + bneck_conf(6., 5, 2, 112, 192, 4), + bneck_conf(6., 3, 1, 192, 320, 1), + ] +} + +impl MBConvConfig { + pub fn b0() -> Vec { + bneck_confs(1.0, 1.0) + } + pub fn b1() -> Vec { + bneck_confs(1.0, 1.1) + } + pub fn b2() -> Vec { + bneck_confs(1.1, 1.2) + } + pub fn b3() -> Vec { + bneck_confs(1.2, 1.4) + } + pub fn b4() -> Vec { + bneck_confs(1.4, 1.8) + } + pub fn b5() -> Vec { + bneck_confs(1.6, 2.2) + } + pub fn b6() -> Vec { + bneck_confs(1.8, 2.6) + } + pub fn b7() -> Vec { + bneck_confs(2.0, 3.1) + } +} + +/// Conv2D with same padding. +#[derive(Debug)] +struct Conv2DSame { + conv2d: nn::Conv2d, + s: usize, + k: usize, +} + +impl Conv2DSame { + fn new( + vb: VarBuilder, + i: usize, + o: usize, + k: usize, + stride: usize, + groups: usize, + bias: bool, + ) -> Result { + let conv_config = nn::Conv2dConfig { + stride, + groups, + ..Default::default() + }; + let conv2d = if bias { + nn::conv2d(i, o, k, conv_config, vb)? + } else { + nn::conv2d_no_bias(i, o, k, conv_config, vb)? + }; + Ok(Self { + conv2d, + s: stride, + k, + }) + } +} + +impl Module for Conv2DSame { + fn forward(&self, xs: &Tensor) -> Result { + let s = self.s; + let k = self.k; + let (_, _, ih, iw) = xs.dims4()?; + let oh = (ih + s - 1) / s; + let ow = (iw + s - 1) / s; + let pad_h = usize::max((oh - 1) * s + k - ih, 0); + let pad_w = usize::max((ow - 1) * s + k - iw, 0); + if pad_h > 0 || pad_w > 0 { + let xs = xs.pad_with_zeros(2, pad_h / 2, pad_h - pad_h / 2)?; + let xs = xs.pad_with_zeros(3, pad_w / 2, pad_w - pad_w / 2)?; + self.conv2d.forward(&xs) + } else { + self.conv2d.forward(xs) + } + } +} + +#[derive(Debug)] +struct ConvNormActivation { + conv2d: Conv2DSame, + bn2d: nn::BatchNorm, + activation: bool, +} + +impl ConvNormActivation { + fn new( + vb: VarBuilder, + i: usize, + o: usize, + k: usize, + stride: usize, + groups: usize, + ) -> Result { + let conv2d = Conv2DSame::new(vb.pp("0"), i, o, k, stride, groups, false)?; + let bn2d = nn::batch_norm(o, 1e-3, vb.pp("1"))?; + Ok(Self { + conv2d, + bn2d, + activation: true, + }) + } + + fn no_activation(self) -> Self { + Self { + activation: false, + ..self + } + } +} + +impl Module for ConvNormActivation { + fn forward(&self, xs: &Tensor) -> Result { + let xs = self.conv2d.forward(xs)?; + let xs = self.bn2d.forward(&xs)?; + if self.activation { + swish(&xs) + } else { + Ok(xs) + } + } +} + +#[derive(Debug)] +struct SqueezeExcitation { + fc1: Conv2DSame, + fc2: Conv2DSame, +} + +impl SqueezeExcitation { + fn new(vb: VarBuilder, in_channels: usize, squeeze_channels: usize) -> Result { + let fc1 = Conv2DSame::new(vb.pp("fc1"), in_channels, squeeze_channels, 1, 1, 1, true)?; + let fc2 = Conv2DSame::new(vb.pp("fc2"), squeeze_channels, in_channels, 1, 1, 1, true)?; + Ok(Self { fc1, fc2 }) + } +} + +impl Module for SqueezeExcitation { + fn forward(&self, xs: &Tensor) -> Result { + let residual = xs; + // equivalent to adaptive_avg_pool2d([1, 1]) + let xs = xs.mean_keepdim(D::Minus2)?.mean_keepdim(D::Minus1)?; + let xs = self.fc1.forward(&xs)?; + let xs = swish(&xs)?; + let xs = self.fc2.forward(&xs)?; + let xs = nn::ops::sigmoid(&xs)?; + residual.broadcast_mul(&xs) + } +} + +#[derive(Debug)] +struct MBConv { + expand_cna: Option, + depthwise_cna: ConvNormActivation, + squeeze_excitation: SqueezeExcitation, + project_cna: ConvNormActivation, + config: MBConvConfig, +} + +impl MBConv { + fn new(vb: VarBuilder, c: MBConvConfig) -> Result { + let vb = vb.pp("block"); + let exp = make_divisible(c.input_channels as f64 * c.expand_ratio, 8); + let expand_cna = if exp != c.input_channels { + Some(ConvNormActivation::new( + vb.pp("0"), + c.input_channels, + exp, + 1, + 1, + 1, + )?) + } else { + None + }; + let start_index = if expand_cna.is_some() { 1 } else { 0 }; + let depthwise_cna = + ConvNormActivation::new(vb.pp(start_index), exp, exp, c.kernel, c.stride, exp)?; + let squeeze_channels = usize::max(1, c.input_channels / 4); + let squeeze_excitation = + SqueezeExcitation::new(vb.pp(start_index + 1), exp, squeeze_channels)?; + let project_cna = + ConvNormActivation::new(vb.pp(start_index + 2), exp, c.out_channels, 1, 1, 1)? + .no_activation(); + Ok(Self { + expand_cna, + depthwise_cna, + squeeze_excitation, + project_cna, + config: c, + }) + } +} + +impl Module for MBConv { + fn forward(&self, xs: &Tensor) -> Result { + let use_res_connect = + self.config.stride == 1 && self.config.input_channels == self.config.out_channels; + let ys = match &self.expand_cna { + Some(expand_cna) => expand_cna.forward(xs)?, + None => xs.clone(), + }; + let ys = self.depthwise_cna.forward(&ys)?; + let ys = self.squeeze_excitation.forward(&ys)?; + let ys = self.project_cna.forward(&ys)?; + if use_res_connect { + ys + xs + } else { + Ok(ys) + } + } +} + +fn swish(s: &Tensor) -> Result { + s * nn::ops::sigmoid(s)? +} + +#[derive(Debug)] +pub struct EfficientNet { + init_cna: ConvNormActivation, + blocks: Vec, + final_cna: ConvNormActivation, + classifier: nn::Linear, +} + +impl EfficientNet { + pub fn new(p: VarBuilder, configs: Vec, nclasses: usize) -> Result { + let f_p = p.pp("features"); + let first_in_c = configs[0].input_channels; + let last_out_c = configs.last().unwrap().out_channels; + let final_out_c = 4 * last_out_c; + let init_cna = ConvNormActivation::new(f_p.pp(0), 3, first_in_c, 3, 2, 1)?; + let nconfigs = configs.len(); + let mut blocks = vec![]; + for (index, cnf) in configs.into_iter().enumerate() { + let f_p = f_p.pp(index + 1); + for r_index in 0..cnf.num_layers { + let cnf = if r_index == 0 { + cnf + } else { + MBConvConfig { + input_channels: cnf.out_channels, + stride: 1, + ..cnf + } + }; + blocks.push(MBConv::new(f_p.pp(r_index), cnf)?) + } + } + let final_cna = + ConvNormActivation::new(f_p.pp(nconfigs + 1), last_out_c, final_out_c, 1, 1, 1)?; + let classifier = nn::linear(final_out_c, nclasses, p.pp("classifier.1"))?; + Ok(Self { + init_cna, + blocks, + final_cna, + classifier, + }) + } +} + +impl Module for EfficientNet { + fn forward(&self, xs: &Tensor) -> Result { + let mut xs = self.init_cna.forward(xs)?; + for block in self.blocks.iter() { + xs = block.forward(&xs)? + } + let xs = self.final_cna.forward(&xs)?; + // Equivalent to adaptive_avg_pool2d([1, 1]) -> squeeze(-1) -> squeeze(-1) + let xs = xs.mean(D::Minus1)?.mean(D::Minus1)?; + self.classifier.forward(&xs) + } +} diff --git a/candle-transformers/src/models/mod.rs b/candle-transformers/src/models/mod.rs index 1b3dcf25..76e13b2a 100644 --- a/candle-transformers/src/models/mod.rs +++ b/candle-transformers/src/models/mod.rs @@ -1,5 +1,9 @@ pub mod bert; pub mod bigcode; +pub mod dinov2; +pub mod efficientnet; pub mod falcon; pub mod llama; +pub mod quantized_llama; +pub mod segment_anything; pub mod whisper; diff --git a/candle-examples/examples/quantized/model.rs b/candle-transformers/src/models/quantized_llama.rs similarity index 100% rename from candle-examples/examples/quantized/model.rs rename to candle-transformers/src/models/quantized_llama.rs diff --git a/candle-examples/examples/segment-anything/model_image_encoder.rs b/candle-transformers/src/models/segment_anything/image_encoder.rs similarity index 96% rename from candle-examples/examples/segment-anything/model_image_encoder.rs rename to candle-transformers/src/models/segment_anything/image_encoder.rs index 76cd15d0..0b313830 100644 --- a/candle-examples/examples/segment-anything/model_image_encoder.rs +++ b/candle-transformers/src/models/segment_anything/image_encoder.rs @@ -100,8 +100,8 @@ impl candle::CustomOp3 for Add3 { #[derive(Debug)] struct Attention { - qkv: crate::Linear, - proj: crate::Linear, + qkv: super::Linear, + proj: super::Linear, num_heads: usize, scale: f64, rel_pos_hw: Option<(Tensor, Tensor)>, @@ -124,8 +124,8 @@ impl Attention { let span_matmul = tracing::span!(tracing::Level::TRACE, "attn-matmul"); let span_rel_pos = tracing::span!(tracing::Level::TRACE, "attn-rel-pos"); let span_softmax = tracing::span!(tracing::Level::TRACE, "attn-sm"); - let qkv = crate::linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; - let proj = crate::linear(vb.pp("proj"), dim, dim, true)?; + let qkv = super::linear(vb.pp("qkv"), dim, dim * 3, qkv_bias)?; + let proj = super::linear(vb.pp("proj"), dim, dim, true)?; let head_dim = dim / num_heads; let scale = 1. / (head_dim as f64).sqrt(); let rel_pos_hw = if use_rel_pos { @@ -251,7 +251,7 @@ struct Block { norm1: LayerNorm, attn: Attention, norm2: LayerNorm, - mlp: crate::MlpBlock, + mlp: super::MlpBlock, window_size: usize, span: tracing::Span, } @@ -281,7 +281,7 @@ impl Block { input_size_attn, vb.pp("attn"), )?; - let mlp = crate::MlpBlock::new(dim, dim * 4, candle_nn::Activation::Gelu, vb.pp("mlp"))?; + let mlp = super::MlpBlock::new(dim, dim * 4, candle_nn::Activation::Gelu, vb.pp("mlp"))?; let span = tracing::span!(tracing::Level::TRACE, "ie-block"); Ok(Self { norm1, @@ -375,9 +375,9 @@ pub struct ImageEncoderViT { patch_embed: PatchEmbed, blocks: Vec, neck_conv1: candle_nn::Conv2d, - neck_ln1: crate::LayerNorm2d, + neck_ln1: super::LayerNorm2d, neck_conv2: candle_nn::Conv2d, - neck_ln2: crate::LayerNorm2d, + neck_ln2: super::LayerNorm2d, pos_embed: Option, span: tracing::Span, } @@ -433,13 +433,13 @@ impl ImageEncoderViT { Default::default(), vb.pp("neck.0"), )?; - let neck_ln1 = crate::LayerNorm2d::new(out_chans, 1e-6, vb.pp("neck.1"))?; + let neck_ln1 = super::LayerNorm2d::new(out_chans, 1e-6, vb.pp("neck.1"))?; let cfg = candle_nn::Conv2dConfig { padding: 1, ..Default::default() }; let neck_conv2 = candle_nn::conv2d_no_bias(out_chans, out_chans, 3, cfg, vb.pp("neck.2"))?; - let neck_ln2 = crate::LayerNorm2d::new(out_chans, 1e-6, vb.pp("neck.3"))?; + let neck_ln2 = super::LayerNorm2d::new(out_chans, 1e-6, vb.pp("neck.3"))?; let pos_embed = if use_abs_pos { let p = vb.get( (1, img_size / patch_size, img_size / patch_size, embed_dim), diff --git a/candle-examples/examples/segment-anything/model_mask_decoder.rs b/candle-transformers/src/models/segment_anything/mask_decoder.rs similarity index 96% rename from candle-examples/examples/segment-anything/model_mask_decoder.rs rename to candle-transformers/src/models/segment_anything/mask_decoder.rs index c02b44a7..2a91cd44 100644 --- a/candle-examples/examples/segment-anything/model_mask_decoder.rs +++ b/candle-transformers/src/models/segment_anything/mask_decoder.rs @@ -1,11 +1,11 @@ use candle::{IndexOp, Result, Tensor}; use candle_nn::{Module, VarBuilder}; -use crate::model_transformer::TwoWayTransformer; +use super::transformer::TwoWayTransformer; #[derive(Debug)] struct MlpMaskDecoder { - layers: Vec, + layers: Vec, sigmoid_output: bool, span: tracing::Span, } @@ -28,7 +28,7 @@ impl MlpMaskDecoder { } else { hidden_dim }; - let layer = crate::linear(vb.pp(i), in_dim, out_dim, true)?; + let layer = super::linear(vb.pp(i), in_dim, out_dim, true)?; layers.push(layer) } let span = tracing::span!(tracing::Level::TRACE, "mlp-mask-decoder"); @@ -64,7 +64,7 @@ pub struct MaskDecoder { mask_tokens: candle_nn::Embedding, iou_prediction_head: MlpMaskDecoder, output_upscaling_conv1: candle_nn::ConvTranspose2d, - output_upscaling_ln: crate::LayerNorm2d, + output_upscaling_ln: super::LayerNorm2d, output_upscaling_conv2: candle_nn::ConvTranspose2d, num_mask_tokens: usize, output_hypernetworks_mlps: Vec, @@ -104,7 +104,7 @@ impl MaskDecoder { vb.pp("output_upscaling.0"), )?; let output_upscaling_ln = - crate::LayerNorm2d::new(transformer_dim / 4, 1e-6, vb.pp("output_upscaling.1"))?; + super::LayerNorm2d::new(transformer_dim / 4, 1e-6, vb.pp("output_upscaling.1"))?; let output_upscaling_conv2 = candle_nn::conv_transpose2d( transformer_dim / 4, transformer_dim / 8, diff --git a/candle-transformers/src/models/segment_anything/mod.rs b/candle-transformers/src/models/segment_anything/mod.rs new file mode 100644 index 00000000..c29db70a --- /dev/null +++ b/candle-transformers/src/models/segment_anything/mod.rs @@ -0,0 +1,100 @@ +use candle::{Result, Tensor}; +use candle_nn::{Module, VarBuilder}; + +pub mod image_encoder; +pub mod mask_decoder; +pub mod prompt_encoder; +pub mod sam; +pub mod tiny_vit; +pub mod transformer; + +pub fn linear(vb: VarBuilder, in_dim: usize, out_dim: usize, bias: bool) -> Result { + let inner = if bias { + candle_nn::linear(in_dim, out_dim, vb)? + } else { + candle_nn::linear_no_bias(in_dim, out_dim, vb)? + }; + let span = tracing::span!(tracing::Level::TRACE, "linear"); + Ok(Linear { inner, span }) +} + +#[derive(Debug)] +pub struct LayerNorm2d { + weight: Tensor, + bias: Tensor, + num_channels: usize, + eps: f64, +} + +impl LayerNorm2d { + pub fn new(num_channels: usize, eps: f64, vb: VarBuilder) -> Result { + let weight = vb.get(num_channels, "weight")?; + let bias = vb.get(num_channels, "bias")?; + Ok(Self { + weight, + bias, + num_channels, + eps, + }) + } +} + +impl Module for LayerNorm2d { + fn forward(&self, xs: &Tensor) -> Result { + let u = xs.mean_keepdim(1)?; + let xs = xs.broadcast_sub(&u)?; + let s = xs.sqr()?.mean_keepdim(1)?; + let xs = xs.broadcast_div(&(s + self.eps)?.sqrt()?)?; + xs.broadcast_mul(&self.weight.reshape((1, self.num_channels, 1, 1))?)? + .broadcast_add(&self.bias.reshape((1, self.num_channels, 1, 1))?) + } +} + +#[derive(Debug)] +pub struct MlpBlock { + lin1: Linear, + lin2: Linear, + activation: candle_nn::Activation, + span: tracing::Span, +} + +impl MlpBlock { + pub fn new( + embedding_dim: usize, + mlp_dim: usize, + activation: candle_nn::Activation, + vb: VarBuilder, + ) -> Result { + let lin1 = linear(vb.pp("lin1"), embedding_dim, mlp_dim, true)?; + let lin2 = linear(vb.pp("lin2"), mlp_dim, embedding_dim, true)?; + let span = tracing::span!(tracing::Level::TRACE, "mlp-block"); + Ok(Self { + lin1, + lin2, + activation, + span, + }) + } +} + +impl Module for MlpBlock { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + xs.apply(&self.lin1)? + .apply(&self.activation)? + .apply(&self.lin2) + } +} + +#[derive(Debug)] +pub struct Linear { + inner: candle_nn::Linear, + span: tracing::Span, +} + +impl Module for Linear { + fn forward(&self, x: &Tensor) -> Result { + let _enter = self.span.enter(); + self.inner.forward(x) + } +} diff --git a/candle-examples/examples/segment-anything/model_prompt_encoder.rs b/candle-transformers/src/models/segment_anything/prompt_encoder.rs similarity index 97% rename from candle-examples/examples/segment-anything/model_prompt_encoder.rs rename to candle-transformers/src/models/segment_anything/prompt_encoder.rs index 7bbe8419..9d0074b1 100644 --- a/candle-examples/examples/segment-anything/model_prompt_encoder.rs +++ b/candle-transformers/src/models/segment_anything/prompt_encoder.rs @@ -56,9 +56,9 @@ pub struct PromptEncoder { point_embeddings: Vec, not_a_point_embed: candle_nn::Embedding, mask_downscaling_conv1: candle_nn::Conv2d, - mask_downscaling_ln1: crate::LayerNorm2d, + mask_downscaling_ln1: super::LayerNorm2d, mask_downscaling_conv2: candle_nn::Conv2d, - mask_downscaling_ln2: crate::LayerNorm2d, + mask_downscaling_ln2: super::LayerNorm2d, mask_downscaling_conv3: candle_nn::Conv2d, no_mask_embed: candle_nn::Embedding, image_embedding_size: (usize, usize), @@ -100,9 +100,9 @@ impl PromptEncoder { vb.pp("mask_downscaling.6"), )?; let mask_downscaling_ln1 = - crate::LayerNorm2d::new(mask_in_chans / 4, 1e-6, vb.pp("mask_downscaling.1"))?; + super::LayerNorm2d::new(mask_in_chans / 4, 1e-6, vb.pp("mask_downscaling.1"))?; let mask_downscaling_ln2 = - crate::LayerNorm2d::new(mask_in_chans, 1e-6, vb.pp("mask_downscaling.4"))?; + super::LayerNorm2d::new(mask_in_chans, 1e-6, vb.pp("mask_downscaling.4"))?; let mut point_embeddings = Vec::with_capacity(num_points_embeddings); let vb_e = vb.pp("point_embeddings"); for i in 0..num_points_embeddings { diff --git a/candle-examples/examples/segment-anything/model_sam.rs b/candle-transformers/src/models/segment_anything/sam.rs similarity index 96% rename from candle-examples/examples/segment-anything/model_sam.rs rename to candle-transformers/src/models/segment_anything/sam.rs index b1a81af6..c40473e3 100644 --- a/candle-examples/examples/segment-anything/model_sam.rs +++ b/candle-transformers/src/models/segment_anything/sam.rs @@ -1,10 +1,10 @@ use candle::{DType, IndexOp, Result, Tensor}; use candle_nn::{Module, VarBuilder}; -use crate::model_image_encoder::ImageEncoderViT; -use crate::model_mask_decoder::MaskDecoder; -use crate::model_prompt_encoder::PromptEncoder; -use crate::model_tiny_vit::{tiny_vit_5m, TinyViT}; +use super::image_encoder::ImageEncoderViT; +use super::mask_decoder::MaskDecoder; +use super::prompt_encoder::PromptEncoder; +use super::tiny_vit::{tiny_vit_5m, TinyViT}; const PROMPT_EMBED_DIM: usize = 256; pub const IMAGE_SIZE: usize = 1024; @@ -186,7 +186,7 @@ impl Sam { img: &Tensor, cb: CropBox, point_grids: &[(f64, f64)], - ) -> Result>> { + ) -> Result>> { // Crop the image and calculate embeddings. let img = img.i((.., cb.y0..cb.y1, cb.x0..cb.x1))?; let img = self.preprocess(&img)?.unsqueeze(0)?; @@ -259,7 +259,7 @@ impl Sam { let min_max_x = min_max_indexes(&low_res_mask_per_x); let min_max_y = min_max_indexes(&low_res_mask_per_y); if let Some(((x0, x1), (y0, y1))) = min_max_x.zip(min_max_y) { - let bbox = candle_examples::object_detection::Bbox { + let bbox = crate::object_detection::Bbox { xmin: x0 as f32, ymin: y0 as f32, xmax: x1 as f32, @@ -277,7 +277,7 @@ impl Sam { let mut bboxes = vec![bboxes]; // Remove duplicates within this crop. - candle_examples::object_detection::non_maximum_suppression(&mut bboxes, CROP_NMS_THRESH); + crate::object_detection::non_maximum_suppression(&mut bboxes, CROP_NMS_THRESH); // TODO: Return to the original image frame. Ok(bboxes.remove(0)) @@ -290,7 +290,7 @@ impl Sam { crop_n_layer: usize, crop_overlap_ratio: f64, crop_n_points_downscale_factor: usize, - ) -> Result>> { + ) -> Result>> { let (_c, h, w) = img.dims3()?; let point_grids = build_all_layer_point_grids( points_per_side, diff --git a/candle-examples/examples/segment-anything/model_tiny_vit.rs b/candle-transformers/src/models/segment_anything/tiny_vit.rs similarity index 97% rename from candle-examples/examples/segment-anything/model_tiny_vit.rs rename to candle-transformers/src/models/segment_anything/tiny_vit.rs index ff076773..cd2936ab 100644 --- a/candle-examples/examples/segment-anything/model_tiny_vit.rs +++ b/candle-transformers/src/models/segment_anything/tiny_vit.rs @@ -215,16 +215,16 @@ impl Module for ConvLayer { #[derive(Debug)] struct Mlp { norm: candle_nn::LayerNorm, - fc1: crate::Linear, - fc2: crate::Linear, + fc1: super::Linear, + fc2: super::Linear, span: tracing::Span, } impl Mlp { fn new(in_: usize, hidden: usize, vb: VarBuilder) -> Result { let norm = candle_nn::layer_norm(in_, 1e-5, vb.pp("norm"))?; - let fc1 = crate::linear(vb.pp("fc1"), in_, hidden, true)?; - let fc2 = crate::linear(vb.pp("fc2"), hidden, in_, true)?; + let fc1 = super::linear(vb.pp("fc1"), in_, hidden, true)?; + let fc2 = super::linear(vb.pp("fc2"), hidden, in_, true)?; let span = tracing::span!(tracing::Level::TRACE, "mlp"); Ok(Self { norm, @@ -248,8 +248,8 @@ impl Module for Mlp { #[derive(Debug)] struct Attention { norm: candle_nn::LayerNorm, - qkv: crate::Linear, - proj: crate::Linear, + qkv: super::Linear, + proj: super::Linear, ab: Tensor, key_dim: usize, num_heads: usize, @@ -275,8 +275,8 @@ impl Attention { let nh_kd = key_dim * num_heads; let h = dh + nh_kd * 2; let norm = candle_nn::layer_norm(dim, 1e-5, vb.pp("norm"))?; - let qkv = crate::linear(vb.pp("qkv"), dim, h, true)?; - let proj = crate::linear(vb.pp("proj"), dh, dim, true)?; + let qkv = super::linear(vb.pp("qkv"), dim, h, true)?; + let proj = super::linear(vb.pp("proj"), dh, dim, true)?; let points = (0..resolution.0) .flat_map(|x| (0..resolution.1).map(move |y| (x as i64, y as i64))) @@ -526,9 +526,9 @@ pub struct TinyViT { // norm_head: candle_nn::LayerNorm, // head: candle_nn::Linear, neck_conv1: candle_nn::Conv2d, - neck_ln1: crate::LayerNorm2d, + neck_ln1: super::LayerNorm2d, neck_conv2: candle_nn::Conv2d, - neck_ln2: crate::LayerNorm2d, + neck_ln2: super::LayerNorm2d, span: tracing::Span, span_neck: tracing::Span, } @@ -578,13 +578,13 @@ impl TinyViT { // let head = candle_nn::linear(last_embed_dim, num_classes, vb.pp("head"))?; let neck_conv1 = candle_nn::conv2d_no_bias(last_embed_dim, 256, 1, Default::default(), vb.pp("neck.0"))?; - let neck_ln1 = crate::LayerNorm2d::new(256, 1e-6, vb.pp("neck.1"))?; + let neck_ln1 = super::LayerNorm2d::new(256, 1e-6, vb.pp("neck.1"))?; let cfg = candle_nn::Conv2dConfig { padding: 1, ..Default::default() }; let neck_conv2 = candle_nn::conv2d_no_bias(256, 256, 3, cfg, vb.pp("neck.2"))?; - let neck_ln2 = crate::LayerNorm2d::new(256, 1e-6, vb.pp("neck.3"))?; + let neck_ln2 = super::LayerNorm2d::new(256, 1e-6, vb.pp("neck.3"))?; let span = tracing::span!(tracing::Level::TRACE, "tiny-vit"); let span_neck = tracing::span!(tracing::Level::TRACE, "neck"); diff --git a/candle-examples/examples/segment-anything/model_transformer.rs b/candle-transformers/src/models/segment_anything/transformer.rs similarity index 99% rename from candle-examples/examples/segment-anything/model_transformer.rs rename to candle-transformers/src/models/segment_anything/transformer.rs index e12aac08..80efb38c 100644 --- a/candle-examples/examples/segment-anything/model_transformer.rs +++ b/candle-transformers/src/models/segment_anything/transformer.rs @@ -68,7 +68,7 @@ struct TwoWayAttentionBlock { norm1: LayerNorm, cross_attn_token_to_image: Attention, norm2: LayerNorm, - mlp: crate::MlpBlock, + mlp: super::MlpBlock, norm3: LayerNorm, norm4: LayerNorm, cross_attn_image_to_token: Attention, @@ -100,7 +100,7 @@ impl TwoWayAttentionBlock { 2, vb.pp("cross_attn_image_to_token"), )?; - let mlp = crate::MlpBlock::new( + let mlp = super::MlpBlock::new( embedding_dim, mlp_dim, candle_nn::Activation::Relu, diff --git a/candle-examples/src/object_detection.rs b/candle-transformers/src/object_detection.rs similarity index 100% rename from candle-examples/src/object_detection.rs rename to candle-transformers/src/object_detection.rs From 6c58fc59fd828492021cfd0f4518ae5ae3b03f56 Mon Sep 17 00:00:00 2001 From: Ssslakter <67190162+Ssslakter@users.noreply.github.com> Date: Sun, 10 Sep 2023 18:02:52 +0700 Subject: [PATCH 053/150] Little docs changes (#791) * Little doc fixes * change imports in lib * rename candle_core to candle * revert "rename candle_core to candle" --- candle-book/src/guide/hello_world.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/candle-book/src/guide/hello_world.md b/candle-book/src/guide/hello_world.md index fc4af0e1..74a147e7 100644 --- a/candle-book/src/guide/hello_world.md +++ b/candle-book/src/guide/hello_world.md @@ -25,8 +25,8 @@ fn main() -> Result<()> { // Use Device::new_cuda(0)?; to use the GPU. let device = Device::Cpu; - let first = Tensor::zeros((784, 100), DType::F32, &device)?; - let second = Tensor::zeros((100, 10), DType::F32, &device)?; + let first = Tensor::randn(0f32, 1.0, (784, 100), &device)?; + let second = Tensor::randn(0f32, 1.0, (100, 10), &device)?; let model = Model { first, second }; let dummy_image = Tensor::zeros((1, 784), DType::F32, &device)?; @@ -110,15 +110,15 @@ fn main() -> Result<()> { let device = Device::cuda_if_available(0)?; // Creating a dummy model - let weight = Tensor::zeros((784, 100), DType::F32, &device)?; + let weight = Tensor::randn(0f32, 1.0, (784, 100), &device)?; let bias = Tensor::zeros((100, ), DType::F32, &device)?; let first = Linear{weight, bias}; - let weight = Tensor::zeros((100, 10), DType::F32, &device)?; - let bias = Tensor::zeros((10, ), DType::F32, &device)?; + let weight = Tensor::randn(0f32, 1.0, (100, 10), &device)?; + let bias = Tensor::randn(0f32, 1.0, (10, ), &device)?; let second = Linear{weight, bias}; let model = Model { first, second }; - let dummy_image = Tensor::zeros((1, 784), DType::F32, &device)?; + let dummy_image = Tensor::randn(0f32, 1.0, (1, 784), &device)?; // Inference on the model let digit = model.forward(&dummy_image)?; @@ -167,15 +167,15 @@ fn main() -> Result<()> { let device = Device::Cpu; // This has changed (784, 100) -> (100, 784) ! - let weight = Tensor::zeros((100, 784), DType::F32, &device)?; - let bias = Tensor::zeros((100, ), DType::F32, &device)?; + let weight = Tensor::randn(0f32, 1.0, (100, 784), &device)?; + let bias = Tensor::randn(0f32, 1.0, (100, ), &device)?; let first = Linear::new(weight, Some(bias)); - let weight = Tensor::zeros((10, 100), DType::F32, &device)?; - let bias = Tensor::zeros((10, ), DType::F32, &device)?; + let weight = Tensor::randn(0f32, 1.0, (10, 100), &device)?; + let bias = Tensor::randn(0f32, 1.0, (10, ), &device)?; let second = Linear::new(weight, Some(bias)); let model = Model { first, second }; - let dummy_image = Tensor::zeros((1, 784), DType::F32, &device)?; + let dummy_image = Tensor::randn(0f32, 1.0, (1, 784), &device)?; let digit = model.forward(&dummy_image)?; println!("Digit {digit:?} digit"); @@ -188,8 +188,8 @@ Feel free to modify this example to use `Conv2d` to create a classical convnet i Now that we have the running dummy code we can get to more advanced topics: -- [For PyTorch users](./guide/cheatsheet.md) -- [Running existing models](./inference/README.md) -- [Training models](./training/README.md) +- [For PyTorch users](../guide/cheatsheet.md) +- [Running existing models](../inference/README.md) +- [Training models](../training/README.md) From 584171cae18923450e029eb04245f70c7e7e5fc4 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 12:29:37 +0100 Subject: [PATCH 054/150] Add a wasm module for the segment anything example. (#797) --- Cargo.toml | 1 + .../src/models/segment_anything/sam.rs | 28 ++++- .../segment-anything/Cargo.toml | 29 +++++ .../segment-anything/build-lib.sh | 2 + .../segment-anything/src/bin/m.rs | 113 ++++++++++++++++++ .../segment-anything/src/lib.rs | 19 +++ 6 files changed, 189 insertions(+), 3 deletions(-) create mode 100644 candle-wasm-examples/segment-anything/Cargo.toml create mode 100644 candle-wasm-examples/segment-anything/build-lib.sh create mode 100644 candle-wasm-examples/segment-anything/src/bin/m.rs create mode 100644 candle-wasm-examples/segment-anything/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index ce41876a..b45a2ab6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "candle-pyo3", "candle-transformers", "candle-wasm-examples/llama2-c", + "candle-wasm-examples/segment-anything", "candle-wasm-examples/whisper", "candle-wasm-examples/yolo", ] diff --git a/candle-transformers/src/models/segment_anything/sam.rs b/candle-transformers/src/models/segment_anything/sam.rs index c40473e3..92756591 100644 --- a/candle-transformers/src/models/segment_anything/sam.rs +++ b/candle-transformers/src/models/segment_anything/sam.rs @@ -122,6 +122,11 @@ impl Sam { }) } + pub fn embeddings(&self, img: &Tensor) -> Result { + let img = self.preprocess(img)?.unsqueeze(0)?; + self.image_encoder.forward(&img) + } + pub fn forward( &self, img: &Tensor, @@ -131,15 +136,32 @@ impl Sam { let (_c, original_h, original_w) = img.dims3()?; let img = self.preprocess(img)?.unsqueeze(0)?; let img_embeddings = self.image_encoder.forward(&img)?; + self.forward_for_embeddings( + &img_embeddings, + original_h, + original_w, + point, + multimask_output, + ) + } + + pub fn forward_for_embeddings( + &self, + img_embeddings: &Tensor, + original_h: usize, + original_w: usize, + point: Option<(f64, f64)>, + multimask_output: bool, + ) -> Result<(Tensor, Tensor)> { let image_pe = self.prompt_encoder.get_dense_pe()?; let points = match point { None => None, Some((x, y)) => { let points = Tensor::new( &[[[x as f32 * original_w as f32, y as f32 * original_h as f32]]], - img.device(), + img_embeddings.device(), )?; - let labels = Tensor::ones((1, 1), DType::F32, img.device())?; + let labels = Tensor::ones((1, 1), DType::F32, img_embeddings.device())?; Some((points, labels)) } }; @@ -147,7 +169,7 @@ impl Sam { let (sparse_prompt_embeddings, dense_prompt_embeddings) = self.prompt_encoder.forward(points, None, None)?; let (low_res_mask, iou_predictions) = self.mask_decoder.forward( - &img_embeddings, + img_embeddings, &image_pe, &sparse_prompt_embeddings, &dense_prompt_embeddings, diff --git a/candle-wasm-examples/segment-anything/Cargo.toml b/candle-wasm-examples/segment-anything/Cargo.toml new file mode 100644 index 00000000..ab82ab1f --- /dev/null +++ b/candle-wasm-examples/segment-anything/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "candle-wasm-example-sam" +version.workspace = true +edition.workspace = true +description.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true +license.workspace = true + +[dependencies] +candle = { path = "../../candle-core", version = "0.2.1", package = "candle-core" } +candle-nn = { path = "../../candle-nn", version = "0.2.1" } +candle-transformers = { path = "../../candle-transformers", version = "0.2.1" } +num-traits = { workspace = true } + +# App crates. +anyhow = { workspace = true } +byteorder = { workspace = true } +getrandom = { version = "0.2", features = ["js"] } +image = { workspace = true } +log = { workspace = true } +safetensors = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } + +# Wasm specific crates. +console_error_panic_hook = "0.1.7" +wasm-bindgen = "0.2.87" diff --git a/candle-wasm-examples/segment-anything/build-lib.sh b/candle-wasm-examples/segment-anything/build-lib.sh new file mode 100644 index 00000000..b0ebb182 --- /dev/null +++ b/candle-wasm-examples/segment-anything/build-lib.sh @@ -0,0 +1,2 @@ +cargo build --target wasm32-unknown-unknown --release +wasm-bindgen ../../target/wasm32-unknown-unknown/release/m.wasm --out-dir build --target web diff --git a/candle-wasm-examples/segment-anything/src/bin/m.rs b/candle-wasm-examples/segment-anything/src/bin/m.rs new file mode 100644 index 00000000..c4c79fe0 --- /dev/null +++ b/candle-wasm-examples/segment-anything/src/bin/m.rs @@ -0,0 +1,113 @@ +use candle::{DType, Device, Tensor}; +use candle_nn::VarBuilder; +use candle_wasm_example_sam as sam; +use wasm_bindgen::prelude::*; + +#[allow(unused)] +struct Embeddings { + original_width: u32, + original_height: u32, + width: u32, + height: u32, + data: Tensor, +} + +#[wasm_bindgen] +pub struct Model { + sam: sam::Sam, + embeddings: Option, +} + +#[wasm_bindgen] +impl Model { + #[wasm_bindgen(constructor)] + pub fn new(weights: &[u8], use_tiny: bool) -> Result { + console_error_panic_hook::set_once(); + let dev = &Device::Cpu; + let weights = safetensors::tensor::SafeTensors::deserialize(weights)?; + let vb = VarBuilder::from_safetensors(vec![weights], DType::F32, dev); + let sam = if use_tiny { + sam::Sam::new_tiny(vb)? // tiny vit_t + } else { + sam::Sam::new(768, 12, 12, &[2, 5, 8, 11], vb)? // sam_vit_b + }; + Ok(Self { + sam, + embeddings: None, + }) + } + + pub fn set_image_embeddings(&mut self, image_data: Vec) -> Result<(), JsError> { + sam::console_log!("image data: {}", image_data.len()); + let image_data = std::io::Cursor::new(image_data); + let image = image::io::Reader::new(image_data) + .with_guessed_format()? + .decode() + .map_err(candle::Error::wrap)?; + let (original_height, original_width) = (image.height(), image.width()); + let (height, width) = (original_height, original_width); + let resize_longest = sam::IMAGE_SIZE as u32; + let (height, width) = if height < width { + let h = (resize_longest * height) / width; + (h, resize_longest) + } else { + let w = (resize_longest * width) / height; + (resize_longest, w) + }; + let image_t = { + let img = image.resize_exact(width, height, image::imageops::FilterType::CatmullRom); + let data = img.to_rgb8().into_raw(); + Tensor::from_vec( + data, + (img.height() as usize, img.width() as usize, 3), + &Device::Cpu, + )? + .permute((2, 0, 1))? + }; + let data = self.sam.embeddings(&image_t)?; + self.embeddings = Some(Embeddings { + original_width, + original_height, + width, + height, + data, + }); + Ok(()) + } + + // x and y have to be between 0 and 1 + pub fn mask_for_point(&self, x: f64, y: f64) -> Result { + let embeddings = match &self.embeddings { + None => todo!(), + Some(embeddings) => embeddings, + }; + let (mask, iou_predictions) = self.sam.forward_for_embeddings( + &embeddings.data, + embeddings.height as usize, + embeddings.width as usize, + Some((x, y)), + false, + )?; + let iou = iou_predictions.to_vec1::()?[0]; + let mask_shape = mask.dims().to_vec(); + let mask_data = mask.ge(0f32)?.flatten_all()?.to_vec1::()?; + let mask = Mask { + iou, + mask_shape, + mask_data, + }; + let json = serde_json::to_string(&mask)?; + Ok(json) + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct Mask { + iou: f32, + mask_shape: Vec, + mask_data: Vec, +} + +fn main() { + console_error_panic_hook::set_once(); +} diff --git a/candle-wasm-examples/segment-anything/src/lib.rs b/candle-wasm-examples/segment-anything/src/lib.rs new file mode 100644 index 00000000..0f4f96fd --- /dev/null +++ b/candle-wasm-examples/segment-anything/src/lib.rs @@ -0,0 +1,19 @@ +use candle_transformers::models::segment_anything::sam; +use wasm_bindgen::prelude::*; + +pub use sam::{Sam, IMAGE_SIZE}; + +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + pub fn log(s: &str); +} + +#[macro_export] +macro_rules! console_log { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string())) +} From 90e077e4093510917e5762de92384ae312162f23 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 13:03:02 +0100 Subject: [PATCH 055/150] Return the low res mask in the wasm segment-anything module. (#798) * Return the low res mask. * Add some validations. --- .../src/models/segment_anything/sam.rs | 18 +++++++++--------- .../segment-anything/src/bin/m.rs | 12 +++++++++++- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/candle-transformers/src/models/segment_anything/sam.rs b/candle-transformers/src/models/segment_anything/sam.rs index 92756591..07e9a759 100644 --- a/candle-transformers/src/models/segment_anything/sam.rs +++ b/candle-transformers/src/models/segment_anything/sam.rs @@ -136,13 +136,18 @@ impl Sam { let (_c, original_h, original_w) = img.dims3()?; let img = self.preprocess(img)?.unsqueeze(0)?; let img_embeddings = self.image_encoder.forward(&img)?; - self.forward_for_embeddings( + let (low_res_mask, iou) = self.forward_for_embeddings( &img_embeddings, original_h, original_w, point, multimask_output, - ) + )?; + let mask = low_res_mask + .upsample_nearest2d(IMAGE_SIZE, IMAGE_SIZE)? + .get(0)? + .i((.., ..original_h, ..original_w))?; + Ok((mask, iou)) } pub fn forward_for_embeddings( @@ -168,18 +173,13 @@ impl Sam { let points = points.as_ref().map(|(x, y)| (x, y)); let (sparse_prompt_embeddings, dense_prompt_embeddings) = self.prompt_encoder.forward(points, None, None)?; - let (low_res_mask, iou_predictions) = self.mask_decoder.forward( + self.mask_decoder.forward( img_embeddings, &image_pe, &sparse_prompt_embeddings, &dense_prompt_embeddings, multimask_output, - )?; - let mask = low_res_mask - .upsample_nearest2d(IMAGE_SIZE, IMAGE_SIZE)? - .get(0)? - .i((.., ..original_h, ..original_w))?; - Ok((mask, iou_predictions)) + ) } pub fn unpreprocess(&self, img: &Tensor) -> Result { diff --git a/candle-wasm-examples/segment-anything/src/bin/m.rs b/candle-wasm-examples/segment-anything/src/bin/m.rs index c4c79fe0..b53f5b9b 100644 --- a/candle-wasm-examples/segment-anything/src/bin/m.rs +++ b/candle-wasm-examples/segment-anything/src/bin/m.rs @@ -77,8 +77,18 @@ impl Model { // x and y have to be between 0 and 1 pub fn mask_for_point(&self, x: f64, y: f64) -> Result { + if !(0. ..=1.).contains(&x) { + Err(JsError::new(&format!( + "x has to be between 0 and 1, got {x}" + )))? + } + if !(0. ..=1.).contains(&y) { + Err(JsError::new(&format!( + "y has to be between 0 and 1, got {y}" + )))? + } let embeddings = match &self.embeddings { - None => todo!(), + None => Err(JsError::new("image embeddings have not been set"))?, Some(embeddings) => embeddings, }; let (mask, iou_predictions) = self.sam.forward_for_embeddings( From 3dd58042991786e698573a95932a108a3cf1f7db Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 13:49:47 +0100 Subject: [PATCH 056/150] Fix typo in readme. (#799) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e5f938a..01c2bb8b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Check out our [examples](./candle-examples/examples/): - [yolo-v3](./candle-examples/examples/yolo-v3/) and [yolo-v8](./candle-examples/examples/yolo-v8/): object detection and pose estimation models. - [segment-anything](./candle-examples/examples/segment-anything/): image +- [segment-anything](./candle-examples/examples/segment-anything/): image segmentation model with prompt. Run them using the following commands: ``` From 559944146fea5ba0baa0f23fcf67062f04ca22da Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 16:56:28 +0100 Subject: [PATCH 057/150] Add an im2col based benchmark. (#800) * Add an im2col based benchmark. * Reshape the final result. --- candle-nn/examples/cpu_benchmarks.rs | 73 +++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/candle-nn/examples/cpu_benchmarks.rs b/candle-nn/examples/cpu_benchmarks.rs index 20c92dbb..012456ec 100644 --- a/candle-nn/examples/cpu_benchmarks.rs +++ b/candle-nn/examples/cpu_benchmarks.rs @@ -6,7 +6,7 @@ extern crate intel_mkl_src; extern crate accelerate_src; use candle::quantized::GgmlType; -use candle::{Device, Result, Tensor, D}; +use candle::{CpuStorage, Device, Layout, Result, Shape, Tensor, D}; use clap::{Parser, Subcommand}; trait Benchmark { @@ -19,6 +19,48 @@ trait Benchmark { const ITERS: usize; } +struct Im2Col(usize, usize); +impl candle::CustomOp1 for Im2Col { + fn name(&self) -> &'static str { + "im2col" + } + + fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)> { + let &Self(h_k, w_k) = self; + let (b, c, h, w) = layout.shape().dims4()?; + let (h_out, w_out) = (h - h_k + 1, w - w_k + 1); + let slice = storage.as_slice::()?; + let src = match layout.contiguous_offsets() { + None => candle::bail!("input has to be contiguous"), + Some((o1, o2)) => &slice[o1..o2], + }; + let mut dst = vec![0f32; b * h_out * w_out * c * h_k * w_k]; + let (s_b, s_c, s_h) = (c * h * w, h * w, w); + for b_idx in 0..b { + let src_idx = b_idx * s_b; + let dst_idx = b_idx * h_out * w_out * c * h_k * w_k; + for h_idx in 0..h_out { + let dst_idx = dst_idx + h_idx * w_out * c * h_k * w_k; + for w_idx in 0..w_out { + let dst_idx = dst_idx + w_idx * c * h_k * w_k; + for c_idx in 0..c { + let dst_idx = dst_idx + c_idx * h_k * w_k; + let src_idx = c_idx * s_c + src_idx; + for h_k_idx in 0..h_k { + let src_idx = src_idx + (h_idx + h_k_idx) * s_h + w_idx; + let dst_idx = dst_idx + h_k_idx * w_k; + dst[dst_idx..dst_idx + w_k] + .copy_from_slice(&src[src_idx..src_idx + w_k]) + } + } + } + } + } + let storage = candle::WithDType::to_cpu_storage_owned(dst); + Ok((storage, (b * h_out * w_out, c * h_k * w_k).into())) + } +} + // Conv1d example as used in whisper. struct Conv1d; impl Benchmark for Conv1d { @@ -53,7 +95,32 @@ impl Benchmark for Conv2d { d.0.conv2d(&d.1, 0, 1, 1, 1) } - const ITERS: usize = 1; + const ITERS: usize = 5; +} + +// Conv2d example as used in stable-diffusion, im2col implementation. +struct Conv2dIm2Col; +impl Benchmark for Conv2dIm2Col { + type PreProcessData = (Tensor, Tensor); + type RunResult = Tensor; + + fn preprocess() -> Result { + let inp = Tensor::randn(0f32, 1., (2, 320, 96, 96), &Device::Cpu)?; + let w = Tensor::randn(0f32, 1., (320, 320, 3, 3), &Device::Cpu)?; + Ok((inp, w)) + } + + fn run_one(d: &Self::PreProcessData) -> Result { + // d.0.conv2d(&d.1, 0, 1, 1, 1) + let (b, _, h, w) = d.0.dims4()?; + let (h_k, w_k) = (3, 3); + let (h_out, w_out) = (h - h_k + 1, w - w_k + 1); + let col = d.0.apply_op1_no_bwd(&Im2Col(h_k, w_k))?; + let res = col.matmul(&d.1.flatten_from(1)?.t()?)?; + res.reshape((b, (), h_out, w_out)) + } + + const ITERS: usize = 5; } struct Matmul; @@ -145,6 +212,7 @@ fn run(iters: Option) -> Result<()> { enum Task { Conv1d, Conv2d, + Conv2dIm2Col, Matmul, Qmatmul, Softmax, @@ -167,6 +235,7 @@ fn main() -> Result<()> { match args.task { Task::Conv1d => run::(args.iters)?, Task::Conv2d => run::(args.iters)?, + Task::Conv2dIm2Col => run::(args.iters)?, Task::Matmul => run::(args.iters)?, Task::Softmax => run::(args.iters)?, Task::SoftmaxLastDim => run::(args.iters)?, From 4f18180fc7564b9a02cab13c4acda0ac7b17a799 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 16:59:46 +0100 Subject: [PATCH 058/150] Bugfix so that im2col produce the same results as conv2d. (#801) --- candle-nn/examples/cpu_benchmarks.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/candle-nn/examples/cpu_benchmarks.rs b/candle-nn/examples/cpu_benchmarks.rs index 012456ec..3ba30f94 100644 --- a/candle-nn/examples/cpu_benchmarks.rs +++ b/candle-nn/examples/cpu_benchmarks.rs @@ -117,7 +117,11 @@ impl Benchmark for Conv2dIm2Col { let (h_out, w_out) = (h - h_k + 1, w - w_k + 1); let col = d.0.apply_op1_no_bwd(&Im2Col(h_k, w_k))?; let res = col.matmul(&d.1.flatten_from(1)?.t()?)?; - res.reshape((b, (), h_out, w_out)) + let res = res + .reshape((b, h_out, w_out, ()))? + .permute((0, 3, 1, 2))? + .contiguous()?; + Ok(res) } const ITERS: usize = 5; From 18d6db2180800dcc134ffabe8523a774c6a7f9a3 Mon Sep 17 00:00:00 2001 From: Ssslakter <67190162+Ssslakter@users.noreply.github.com> Date: Mon, 11 Sep 2023 02:36:29 +0700 Subject: [PATCH 059/150] more doc fixes (#804) --- candle-book/src/SUMMARY.md | 4 ++-- candle-book/src/guide/hello_world.md | 16 ++++++++-------- .../src/inference/{README.md => inference.md} | 0 .../src/training/{README.md => training.md} | 0 4 files changed, 10 insertions(+), 10 deletions(-) rename candle-book/src/inference/{README.md => inference.md} (100%) rename candle-book/src/training/{README.md => training.md} (100%) diff --git a/candle-book/src/SUMMARY.md b/candle-book/src/SUMMARY.md index 8228da22..e92f298f 100644 --- a/candle-book/src/SUMMARY.md +++ b/candle-book/src/SUMMARY.md @@ -10,10 +10,10 @@ # Reference Guide -- [Running a model](inference/README.md) +- [Running a model](inference/inference.md) - [Using the hub](inference/hub.md) - [Error management](error_manage.md) -- [Training](training/README.md) +- [Training](training/training.md) - [MNIST](training/mnist.md) - [Fine-tuning]() - [Serialization]() diff --git a/candle-book/src/guide/hello_world.md b/candle-book/src/guide/hello_world.md index 74a147e7..b5b8d7b4 100644 --- a/candle-book/src/guide/hello_world.md +++ b/candle-book/src/guide/hello_world.md @@ -6,7 +6,7 @@ Open `src/main.rs` and fill in this content: ```rust # extern crate candle_core; -use candle_core::{DType, Device, Result, Tensor}; +use candle_core::{Device, Result, Tensor}; struct Model { first: Tensor, @@ -29,7 +29,7 @@ fn main() -> Result<()> { let second = Tensor::randn(0f32, 1.0, (100, 10), &device)?; let model = Model { first, second }; - let dummy_image = Tensor::zeros((1, 784), DType::F32, &device)?; + let dummy_image = Tensor::randn(0f32, 1.0, (1, 784), &device)?; let digit = model.forward(&dummy_image)?; println!("Digit {digit:?} digit"); @@ -50,7 +50,7 @@ the classical `Linear` layer. We can do as such ```rust # extern crate candle_core; -# use candle_core::{DType, Device, Result, Tensor}; +# use candle_core::{Device, Result, Tensor}; struct Linear{ weight: Tensor, bias: Tensor, @@ -80,7 +80,7 @@ This will change the model running code into a new function ```rust # extern crate candle_core; -# use candle_core::{DType, Device, Result, Tensor}; +# use candle_core::{Device, Result, Tensor}; # struct Linear{ # weight: Tensor, # bias: Tensor, @@ -111,7 +111,7 @@ fn main() -> Result<()> { // Creating a dummy model let weight = Tensor::randn(0f32, 1.0, (784, 100), &device)?; - let bias = Tensor::zeros((100, ), DType::F32, &device)?; + let bias = Tensor::randn(0f32, 1.0, (100, ), &device)?; let first = Linear{weight, bias}; let weight = Tensor::randn(0f32, 1.0, (100, 10), &device)?; let bias = Tensor::randn(0f32, 1.0, (10, ), &device)?; @@ -146,7 +146,7 @@ And rewrite our examples using it ```rust # extern crate candle_core; # extern crate candle_nn; -use candle_core::{DType, Device, Result, Tensor}; +use candle_core::{Device, Result, Tensor}; use candle_nn::{Linear, Module}; struct Model { @@ -189,7 +189,7 @@ Feel free to modify this example to use `Conv2d` to create a classical convnet i Now that we have the running dummy code we can get to more advanced topics: - [For PyTorch users](../guide/cheatsheet.md) -- [Running existing models](../inference/README.md) -- [Training models](../training/README.md) +- [Running existing models](../inference/inference.md) +- [Training models](../training/training.md) diff --git a/candle-book/src/inference/README.md b/candle-book/src/inference/inference.md similarity index 100% rename from candle-book/src/inference/README.md rename to candle-book/src/inference/inference.md diff --git a/candle-book/src/training/README.md b/candle-book/src/training/training.md similarity index 100% rename from candle-book/src/training/README.md rename to candle-book/src/training/training.md From 98d1242b8fd917baa95c9143252962f8fad3ebf7 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 10 Sep 2023 21:02:42 +0100 Subject: [PATCH 060/150] im2col based conv2d (#802) * im2col implementation for conv2d. * Fix for the im2col implementation to match the current conv2d. * Small optimization. * Add a cuda kernel. * Handle arbitrary layouts. * Im2Col cuda code. --- candle-core/src/cuda_backend.rs | 52 ++++++++++++++++ candle-kernels/src/conv.cu | 89 ++++++++++++++++++++++++++++ candle-nn/examples/cpu_benchmarks.rs | 85 +++++++++++++++++++++----- 3 files changed, 210 insertions(+), 16 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index 7cc85489..b4bdc6cf 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -600,6 +600,58 @@ impl Map1 for Elu { } } +struct Im2Col { + h_k: usize, + w_k: usize, + stride: usize, + dilation: usize, + padding: usize, +} + +impl Im2Col { + fn hw_out(&self, h: usize, w: usize) -> (usize, usize) { + let h_out = (h + 2 * self.padding - self.dilation * (self.h_k - 1) - 1) / self.stride + 1; + let w_out = (w + 2 * self.padding - self.dilation * (self.w_k - 1) - 1) / self.stride + 1; + (h_out, w_out) + } +} + +impl Map1 for Im2Col { + fn f( + &self, + src: &CudaSlice, + dev: &CudaDevice, + layout: &Layout, + ) -> Result> { + let shape = layout.shape(); + let dims = shape.dims(); + let (h_out, w_out) = self.hw_out(dims[2], dims[3]); + let dst_el = dims[0] * h_out * w_out * dims[1] * self.h_k * self.w_k; + let cfg = LaunchConfig::for_num_elems(dst_el as u32); + let ds = dev.htod_copy([dims, layout.stride()].concat()).w()?; + let src = &src.slice(layout.start_offset()..); + let func = dev.get_or_load_func(&kernel_name::("im2col"), kernels::CONV)?; + // SAFETY: Set later by running the kernel. + let dst = unsafe { dev.alloc::(dst_el) }.w()?; + let params = ( + dst_el, + h_out, + w_out, + self.h_k, + self.w_k, + self.stride, + self.padding, + self.dilation, + &ds, + src, + &dst, + ); + // SAFETY: ffi. + unsafe { func.launch(cfg, params) }.w()?; + Ok(dst) + } +} + struct Powf(f64); impl Map1 for Powf { fn f( diff --git a/candle-kernels/src/conv.cu b/candle-kernels/src/conv.cu index ba2fa1ad..51c393cb 100644 --- a/candle-kernels/src/conv.cu +++ b/candle-kernels/src/conv.cu @@ -51,6 +51,71 @@ __device__ void conv1d( dst[dst_i] = static_cast(d); } +template +__device__ void im2col( + const size_t dst_numel, + const size_t h_out, + const size_t w_out, + const size_t h_k, + const size_t w_k, + const size_t stride, + const size_t padding, + const size_t dilation, + const size_t *info, + const T *src, + T *dst +) { + const size_t dst_i = blockIdx.x * blockDim.x + threadIdx.x; + // dst: (b_size, h_out, w_out, c_in, h_k, w_k) + // src: (b_size, c_in, h_in, w_in) + if (dst_i >= dst_numel) { + return; + } + const size_t *src_dims = info; + const size_t *src_s = info + 4; + const size_t b_in = src_dims[0]; + const size_t c_in = src_dims[1]; + const size_t h_in = src_dims[2]; + const size_t w_in = src_dims[3]; + + const size_t dst_s4 = w_k; + const size_t dst_s3 = h_k * dst_s4; + const size_t dst_s2 = c_in * dst_s3; + const size_t dst_s1 = w_out * dst_s2; + const size_t dst_s0 = h_out * dst_s1; + + size_t tmp_dst_i = dst_i; + const size_t b_idx = tmp_dst_i / dst_s0; + tmp_dst_i -= b_idx * dst_s0; + const size_t h_idx = tmp_dst_i / dst_s1; + tmp_dst_i -= h_idx * dst_s1; + const size_t w_idx = tmp_dst_i / dst_s2; + tmp_dst_i -= w_idx * dst_s2; + const size_t c_idx = tmp_dst_i / dst_s3; + tmp_dst_i -= c_idx * dst_s3; + const size_t h_k_idx = tmp_dst_i / dst_s4; + tmp_dst_i -= h_k_idx * dst_s4; + const size_t w_k_idx = tmp_dst_i; + size_t src_h_idx = h_idx * stride + h_k_idx * dilation; + size_t src_w_idx = w_idx * stride + w_k_idx * dilation; + if (src_h_idx < padding || src_h_idx >= h_in + padding) { + dst[dst_i] = static_cast(0); + } + else if (src_w_idx < padding || src_w_idx >= w_in + padding) { + dst[dst_i] = static_cast(0); + } + else { + src_h_idx -= padding; + src_w_idx -= padding; + const size_t src_i = + b_idx * src_s[0] + + c_idx * src_s[1] + + src_h_idx * src_s[2] + + src_w_idx * src_s[3]; + dst[dst_i] = src[src_i]; + } +} + // Naive implementation of conv2d. template __device__ void conv2d( @@ -363,6 +428,23 @@ extern "C" __global__ void FN_NAME( \ conv2d(src_numel, w_out, h_out, stride, padding, dilation, info, src, kernel, dst); \ } \ +#define IM2COL_OP(TYPENAME, FN_NAME) \ +extern "C" __global__ void FN_NAME( \ + const size_t dst_numel, \ + const size_t h_out, \ + const size_t w_out, \ + const size_t h_k, \ + const size_t w_k, \ + const size_t stride, \ + const size_t padding, \ + const size_t dilation, \ + const size_t *info, \ + const TYPENAME *src, \ + TYPENAME *dst \ +) { \ + im2col(dst_numel, h_out, w_out, h_k, w_k, stride, padding, dilation, info, src, dst); \ +} \ + #define CONVT2D_OP(TYPENAME, TYPEACC, FN_NAME) \ extern "C" __global__ void FN_NAME( \ const size_t src_numel, \ @@ -428,6 +510,7 @@ CONVT2D_OP(__nv_bfloat16, float, conv_transpose2d_bf16) AVG_POOL2D_OP(__nv_bfloat16, float, avg_pool2d_bf16) MAX_POOL2D_OP(__nv_bfloat16, max_pool2d_bf16) UPSAMPLE_NEAREST2D_OP(__nv_bfloat16, upsample_nearest2d_bf16) +IM2COL_OP(__nv_bfloat16, im2col_bf16) #endif #if __CUDA_ARCH__ >= 530 @@ -437,6 +520,7 @@ CONVT2D_OP(__half, float, conv_transpose2d_f16) AVG_POOL2D_OP(__half, float, avg_pool2d_f16) MAX_POOL2D_OP(__half, max_pool2d_f16) UPSAMPLE_NEAREST2D_OP(__half, upsample_nearest2d_f16) +IM2COL_OP(__half, im2col_f16) #endif CONV1D_OP(float, float, conv1d_f32) @@ -468,3 +552,8 @@ UPSAMPLE_NEAREST2D_OP(float, upsample_nearest2d_f32) UPSAMPLE_NEAREST2D_OP(double, upsample_nearest2d_f64) UPSAMPLE_NEAREST2D_OP(uint8_t, upsample_nearest2d_u8) UPSAMPLE_NEAREST2D_OP(uint32_t, upsample_nearest2d_u32) + +IM2COL_OP(float, im2col_f32) +IM2COL_OP(double, im2col_f64) +IM2COL_OP(uint8_t, im2col_u8) +IM2COL_OP(uint32_t, im2col_u32) diff --git a/candle-nn/examples/cpu_benchmarks.rs b/candle-nn/examples/cpu_benchmarks.rs index 3ba30f94..5fb99625 100644 --- a/candle-nn/examples/cpu_benchmarks.rs +++ b/candle-nn/examples/cpu_benchmarks.rs @@ -9,6 +9,8 @@ use candle::quantized::GgmlType; use candle::{CpuStorage, Device, Layout, Result, Shape, Tensor, D}; use clap::{Parser, Subcommand}; +const CHECK_CONV2D: bool = false; + trait Benchmark { type PreProcessData; type RunResult; @@ -19,25 +21,51 @@ trait Benchmark { const ITERS: usize; } -struct Im2Col(usize, usize); +struct Im2Col { + h_k: usize, + w_k: usize, + stride: usize, + dilation: usize, + padding: usize, +} + +impl Im2Col { + fn hw_out(&self, h: usize, w: usize) -> (usize, usize) { + let h_out = (h + 2 * self.padding - self.dilation * (self.h_k - 1) - 1) / self.stride + 1; + let w_out = (w + 2 * self.padding - self.dilation * (self.w_k - 1) - 1) / self.stride + 1; + (h_out, w_out) + } +} + impl candle::CustomOp1 for Im2Col { fn name(&self) -> &'static str { "im2col" } fn cpu_fwd(&self, storage: &CpuStorage, layout: &Layout) -> Result<(CpuStorage, Shape)> { - let &Self(h_k, w_k) = self; + let &Self { + h_k, + w_k, + stride, + dilation, + padding, + } = self; let (b, c, h, w) = layout.shape().dims4()?; - let (h_out, w_out) = (h - h_k + 1, w - w_k + 1); + let (h_out, w_out) = self.hw_out(h, w); let slice = storage.as_slice::()?; - let src = match layout.contiguous_offsets() { - None => candle::bail!("input has to be contiguous"), - Some((o1, o2)) => &slice[o1..o2], - }; + let src = &slice[layout.start_offset()..]; let mut dst = vec![0f32; b * h_out * w_out * c * h_k * w_k]; - let (s_b, s_c, s_h) = (c * h * w, h * w, w); + let (src_s0, src_s1, src_s2, src_s3) = { + let s = layout.stride(); + (s[0], s[1], s[2], s[3]) + }; + // TODO: provide specialized kernels for the common use cases. + // - h_k = w_k = 1 + // - padding = 0 + // - stride = 1 + // - dilation = 1 for b_idx in 0..b { - let src_idx = b_idx * s_b; + let src_idx = b_idx * src_s0; let dst_idx = b_idx * h_out * w_out * c * h_k * w_k; for h_idx in 0..h_out { let dst_idx = dst_idx + h_idx * w_out * c * h_k * w_k; @@ -45,12 +73,25 @@ impl candle::CustomOp1 for Im2Col { let dst_idx = dst_idx + w_idx * c * h_k * w_k; for c_idx in 0..c { let dst_idx = dst_idx + c_idx * h_k * w_k; - let src_idx = c_idx * s_c + src_idx; + let src_idx = c_idx * src_s1 + src_idx; for h_k_idx in 0..h_k { - let src_idx = src_idx + (h_idx + h_k_idx) * s_h + w_idx; + let src_h = h_idx * stride + h_k_idx * dilation; + if padding != 0 && (src_h < padding || src_h >= h + padding) { + continue; + } + let src_h = src_h - padding; + let src_idx = src_idx + src_h * src_s2; let dst_idx = dst_idx + h_k_idx * w_k; - dst[dst_idx..dst_idx + w_k] - .copy_from_slice(&src[src_idx..src_idx + w_k]) + for w_k_idx in 0..w_k { + let src_w = w_idx * stride + w_k_idx * dilation; + if padding != 0 && (src_w < padding || src_w >= h + padding) { + continue; + } + let src_w = src_w - padding; + let src_idx = src_idx + src_w * src_s3; + let dst_idx = dst_idx + w_k_idx; + dst[dst_idx] = src[src_idx] + } } } } @@ -113,14 +154,26 @@ impl Benchmark for Conv2dIm2Col { fn run_one(d: &Self::PreProcessData) -> Result { // d.0.conv2d(&d.1, 0, 1, 1, 1) let (b, _, h, w) = d.0.dims4()?; - let (h_k, w_k) = (3, 3); - let (h_out, w_out) = (h - h_k + 1, w - w_k + 1); - let col = d.0.apply_op1_no_bwd(&Im2Col(h_k, w_k))?; + let (_, _, h_k, w_k) = d.1.dims4()?; + let op = Im2Col { + h_k, + w_k, + stride: 1, + dilation: 1, + padding: 0, + }; + let (h_out, w_out) = op.hw_out(h, w); + let col = d.0.apply_op1_no_bwd(&op)?; let res = col.matmul(&d.1.flatten_from(1)?.t()?)?; let res = res .reshape((b, h_out, w_out, ()))? .permute((0, 3, 1, 2))? .contiguous()?; + if CHECK_CONV2D { + let res2 = d.0.conv2d(&d.1, op.padding, op.stride, op.dilation, 1); + let diff = (&res - res2)?.sqr()?.mean_all()?; + println!("{diff}"); + } Ok(res) } From 1cd74129d47745a292c5f54ae3f9a1a1348cdf3e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 08:52:33 +0100 Subject: [PATCH 061/150] Add Im2Col support on the gpu side. (#808) * Add Im2Col support on the gpu side. * Actually enable. --- candle-core/src/cuda_backend.rs | 35 +++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index b4bdc6cf..dbfaa928 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -11,6 +11,8 @@ use cudarc::driver::{ use half::{bf16, f16}; use std::sync::{Arc, Mutex}; +const USE_IM2COL_CONV2D: bool = true; + /// cudarc related errors #[derive(thiserror::Error, Debug)] pub enum CudaError { @@ -1723,8 +1725,37 @@ impl BackendStorage for CudaStorage { params: &crate::conv::ParamsConv2D, ) -> Result { let device = self.device().clone(); - let slice = Conv2D(params).map(&self.slice, l, &kernel.slice, kernel_l, &device)?; - Ok(Self { slice, device }) + if !USE_IM2COL_CONV2D { + let slice = Conv2D(params).map(&self.slice, l, &kernel.slice, kernel_l, &device)?; + return Ok(Self { slice, device }); + } + + let col = Im2Col { + h_k: params.k_h, + w_k: params.k_w, + stride: params.stride, + dilation: params.dilation, + padding: params.padding, + } + .map(&self.slice, &device, l)?; + let col = Self { slice: col, device }; + let h_out = params.out_h(); + let w_out = params.out_w(); + let b = params.b_size; + let n = params.c_out; + let k = params.k_h * params.k_w * params.c_in; + let m = h_out * w_out; + let col_l = Layout::contiguous((b, m, k)); + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + let res = col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)?; + let res_l = Layout::contiguous((b, h_out, w_out, n)) + .transpose(1, 2)? + .transpose(1, 3)?; + let mut res_t = self.device().zeros_impl(res_l.shape(), res.dtype())?; + res.copy_strided_src(&mut res_t, 0, &res_l)?; + Ok(res_t) } #[cfg(feature = "cudnn")] From 6fb665004ca340808c049514e738ac1e814d9a23 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 09:28:13 +0100 Subject: [PATCH 062/150] Enable im2col on the cpu side. (#805) * Enable im2col on the cpu side. * Hook im2col on the cpu backend. * Use the kernel offset. * Avoid an unnecessary copy. * Handle non-contiguous kernels. * Add a const to select the conv2d kernel. --- candle-core/src/cpu_backend.rs | 115 ++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/candle-core/src/cpu_backend.rs b/candle-core/src/cpu_backend.rs index 01ccfde7..3cdc538a 100644 --- a/candle-core/src/cpu_backend.rs +++ b/candle-core/src/cpu_backend.rs @@ -4,6 +4,8 @@ use crate::{DType, Error, IntDType, Layout, Result, Shape, WithDType}; use half::{bf16, f16}; use rayon::prelude::*; +const USE_IM2COL_CONV2D: bool = true; + // TODO: Maybe we should not implement [Clone] here and instead have an explicit allocator + // intercept the oom errors to avoid panicking and provide a proper error. #[derive(Debug, Clone)] @@ -1089,6 +1091,81 @@ impl<'a> Map2 for Conv1D<'a> { } } +struct Im2Col { + h_k: usize, + w_k: usize, + stride: usize, + dilation: usize, + padding: usize, +} + +impl Im2Col { + fn hw_out(&self, h: usize, w: usize) -> (usize, usize) { + let h_out = (h + 2 * self.padding - self.dilation * (self.h_k - 1) - 1) / self.stride + 1; + let w_out = (w + 2 * self.padding - self.dilation * (self.w_k - 1) - 1) / self.stride + 1; + (h_out, w_out) + } +} + +impl Map1 for Im2Col { + fn f(&self, vs: &[T], layout: &Layout) -> Result> { + let &Self { + h_k, + w_k, + stride, + dilation, + padding, + } = self; + let (b, c, h, w) = layout.shape().dims4()?; + let (h_out, w_out) = self.hw_out(h, w); + let src = &vs[layout.start_offset()..]; + let mut dst = vec![T::zero(); b * h_out * w_out * c * h_k * w_k]; + let (src_s0, src_s1, src_s2, src_s3) = { + let s = layout.stride(); + (s[0], s[1], s[2], s[3]) + }; + // TODO: provide specialized kernels for the common use cases. + // - h_k = w_k = 1 + // - padding = 0 + // - stride = 1 + // - dilation = 1 + for b_idx in 0..b { + let src_idx = b_idx * src_s0; + let dst_idx = b_idx * h_out * w_out * c * h_k * w_k; + for h_idx in 0..h_out { + let dst_idx = dst_idx + h_idx * w_out * c * h_k * w_k; + for w_idx in 0..w_out { + let dst_idx = dst_idx + w_idx * c * h_k * w_k; + for c_idx in 0..c { + let dst_idx = dst_idx + c_idx * h_k * w_k; + let src_idx = c_idx * src_s1 + src_idx; + for h_k_idx in 0..h_k { + let src_h = h_idx * stride + h_k_idx * dilation; + if padding != 0 && (src_h < padding || src_h >= h + padding) { + continue; + } + let src_h = src_h - padding; + let src_idx = src_idx + src_h * src_s2; + let dst_idx = dst_idx + h_k_idx * w_k; + for w_k_idx in 0..w_k { + let src_w = w_idx * stride + w_k_idx * dilation; + if padding != 0 && (src_w < padding || src_w >= h + padding) { + continue; + } + let src_w = src_w - padding; + let src_idx = src_idx + src_w * src_s3; + let dst_idx = dst_idx + w_k_idx; + dst[dst_idx] = src[src_idx] + } + } + } + } + } + } + Ok(dst) + } +} + struct Conv2D<'a>(&'a crate::conv::ParamsConv2D); impl<'a> Map2 for Conv2D<'a> { @@ -2237,7 +2314,43 @@ impl BackendStorage for CpuStorage { kernel_l: &Layout, params: &crate::conv::ParamsConv2D, ) -> Result { - Conv2D(params).map(self, l, kernel, kernel_l) + if !USE_IM2COL_CONV2D { + return Conv2D(params).map(self, l, kernel, kernel_l); + } + let op = Im2Col { + h_k: params.k_h, + w_k: params.k_w, + padding: params.padding, + stride: params.stride, + dilation: params.dilation, + }; + let col = op.map(self, l)?; + let b = params.b_size; + let n = params.c_out; + let (h_out, w_out) = (params.out_h(), params.out_w()); + let k = op.h_k * op.w_k * params.c_in; + let m = h_out * w_out; + let col_l = Layout::contiguous((b, m, k)); + let res = if kernel_l.is_contiguous() { + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + } else { + // Make the kernel contiguous if not already the case. + let mut kernel_c = self.device().zeros_impl(kernel_l.shape(), kernel.dtype())?; + kernel.copy_strided_src(&mut kernel_c, 0, kernel_l)?; + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + }; + let res_l = Layout::contiguous((b, h_out, w_out, params.c_out)) + .transpose(1, 2)? + .transpose(1, 3)?; + let mut res_t = self.device().zeros_impl(res_l.shape(), res.dtype())?; + res.copy_strided_src(&mut res_t, 0, &res_l)?; + Ok(res_t) } fn conv_transpose2d( From df712ecf64daeb77eed4a368e670050617ed5d26 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 09:48:31 +0100 Subject: [PATCH 063/150] Handle the case where the kernel is not contiguous in the cuda backend. (#809) --- candle-core/src/cuda_backend.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index dbfaa928..20cab125 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -1746,10 +1746,20 @@ impl BackendStorage for CudaStorage { let k = params.k_h * params.k_w * params.c_in; let m = h_out * w_out; let col_l = Layout::contiguous((b, m, k)); - let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) - .transpose(1, 2)? - .broadcast_as((b, k, n))?; - let res = col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)?; + let res = if kernel_l.is_contiguous() { + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + } else { + // Make the kernel contiguous if not already the case. + let mut kernel_c = self.device().zeros_impl(kernel_l.shape(), kernel.dtype())?; + kernel.copy_strided_src(&mut kernel_c, 0, kernel_l)?; + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + }; let res_l = Layout::contiguous((b, h_out, w_out, n)) .transpose(1, 2)? .transpose(1, 3)?; From 84ee870efda4fbbadafa67ee8615c76a8947ad24 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 11:05:05 +0100 Subject: [PATCH 064/150] Use softmax-last-dim in whisper. (#810) --- candle-transformers/src/models/whisper/model.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/candle-transformers/src/models/whisper/model.rs b/candle-transformers/src/models/whisper/model.rs index e58ab2ca..d2eda796 100644 --- a/candle-transformers/src/models/whisper/model.rs +++ b/candle-transformers/src/models/whisper/model.rs @@ -1,5 +1,5 @@ use candle::{Device, IndexOp, Result, Tensor, D}; -use candle_nn::{ops::softmax, Conv1d, Conv1dConfig, Embedding, LayerNorm, Module, VarBuilder}; +use candle_nn::{Conv1d, Conv1dConfig, Embedding, LayerNorm, Module, VarBuilder}; use serde::Deserialize; // The names in comments correspond to the original implementation: @@ -166,7 +166,7 @@ impl MultiHeadAttention { } let w = { let _enter = self.softmax_span.enter(); - softmax(&qk, D::Minus1)? + candle_nn::ops::softmax_last_dim(&qk)? }; let wv = { let _enter = self.matmul_span.enter(); From d7b9fec849ca8ebc224410d409ea1ea5a366174d Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 11:45:57 +0100 Subject: [PATCH 065/150] Move the stable-diffusion modeling code so that it's easier to re-use. (#812) --- .../examples/stable-diffusion/main.rs | 12 +---------- candle-transformers/src/models/mod.rs | 1 + .../src/models/stable_diffusion}/attention.rs | 0 .../src/models/stable_diffusion}/clip.rs | 0 .../src/models/stable_diffusion}/ddim.rs | 6 +++--- .../models/stable_diffusion}/embeddings.rs | 0 .../src/models/stable_diffusion/mod.rs | 21 +++++++++++++------ .../src/models/stable_diffusion}/resnet.rs | 2 +- .../models/stable_diffusion}/schedulers.rs | 0 .../src/models/stable_diffusion}/unet_2d.rs | 6 +++--- .../stable_diffusion}/unet_2d_blocks.rs | 6 +++--- .../src/models/stable_diffusion}/utils.rs | 0 .../src/models/stable_diffusion}/vae.rs | 2 +- 13 files changed, 28 insertions(+), 28 deletions(-) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/attention.rs (100%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/clip.rs (100%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/ddim.rs (97%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/embeddings.rs (100%) rename candle-examples/examples/stable-diffusion/stable_diffusion.rs => candle-transformers/src/models/stable_diffusion/mod.rs (95%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/resnet.rs (99%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/schedulers.rs (100%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/unet_2d.rs (99%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/unet_2d_blocks.rs (99%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/utils.rs (100%) rename {candle-examples/examples/stable-diffusion => candle-transformers/src/models/stable_diffusion}/vae.rs (99%) diff --git a/candle-examples/examples/stable-diffusion/main.rs b/candle-examples/examples/stable-diffusion/main.rs index 70e1e92c..6bce2917 100644 --- a/candle-examples/examples/stable-diffusion/main.rs +++ b/candle-examples/examples/stable-diffusion/main.rs @@ -4,17 +4,7 @@ extern crate accelerate_src; #[cfg(feature = "mkl")] extern crate intel_mkl_src; -mod attention; -mod clip; -mod ddim; -mod embeddings; -mod resnet; -mod schedulers; -mod stable_diffusion; -mod unet_2d; -mod unet_2d_blocks; -mod utils; -mod vae; +use candle_transformers::models::stable_diffusion; use anyhow::{Error as E, Result}; use candle::{DType, Device, IndexOp, Tensor, D}; diff --git a/candle-transformers/src/models/mod.rs b/candle-transformers/src/models/mod.rs index 76e13b2a..f33d01e6 100644 --- a/candle-transformers/src/models/mod.rs +++ b/candle-transformers/src/models/mod.rs @@ -6,4 +6,5 @@ pub mod falcon; pub mod llama; pub mod quantized_llama; pub mod segment_anything; +pub mod stable_diffusion; pub mod whisper; diff --git a/candle-examples/examples/stable-diffusion/attention.rs b/candle-transformers/src/models/stable_diffusion/attention.rs similarity index 100% rename from candle-examples/examples/stable-diffusion/attention.rs rename to candle-transformers/src/models/stable_diffusion/attention.rs diff --git a/candle-examples/examples/stable-diffusion/clip.rs b/candle-transformers/src/models/stable_diffusion/clip.rs similarity index 100% rename from candle-examples/examples/stable-diffusion/clip.rs rename to candle-transformers/src/models/stable_diffusion/clip.rs diff --git a/candle-examples/examples/stable-diffusion/ddim.rs b/candle-transformers/src/models/stable_diffusion/ddim.rs similarity index 97% rename from candle-examples/examples/stable-diffusion/ddim.rs rename to candle-transformers/src/models/stable_diffusion/ddim.rs index 260a4965..916b7349 100644 --- a/candle-examples/examples/stable-diffusion/ddim.rs +++ b/candle-transformers/src/models/stable_diffusion/ddim.rs @@ -7,7 +7,7 @@ //! //! Denoising Diffusion Implicit Models, J. Song et al, 2020. //! https://arxiv.org/abs/2010.02502 -use crate::schedulers::{betas_for_alpha_bar, BetaSchedule, PredictionType}; +use super::schedulers::{betas_for_alpha_bar, BetaSchedule, PredictionType}; use candle::{Result, Tensor}; /// The configuration for the DDIM scheduler. @@ -67,14 +67,14 @@ impl DDIMScheduler { .rev() .collect(); let betas = match config.beta_schedule { - BetaSchedule::ScaledLinear => crate::utils::linspace( + BetaSchedule::ScaledLinear => super::utils::linspace( config.beta_start.sqrt(), config.beta_end.sqrt(), config.train_timesteps, )? .sqr()?, BetaSchedule::Linear => { - crate::utils::linspace(config.beta_start, config.beta_end, config.train_timesteps)? + super::utils::linspace(config.beta_start, config.beta_end, config.train_timesteps)? } BetaSchedule::SquaredcosCapV2 => betas_for_alpha_bar(config.train_timesteps, 0.999)?, }; diff --git a/candle-examples/examples/stable-diffusion/embeddings.rs b/candle-transformers/src/models/stable_diffusion/embeddings.rs similarity index 100% rename from candle-examples/examples/stable-diffusion/embeddings.rs rename to candle-transformers/src/models/stable_diffusion/embeddings.rs diff --git a/candle-examples/examples/stable-diffusion/stable_diffusion.rs b/candle-transformers/src/models/stable_diffusion/mod.rs similarity index 95% rename from candle-examples/examples/stable-diffusion/stable_diffusion.rs rename to candle-transformers/src/models/stable_diffusion/mod.rs index cffc00d8..d9721532 100644 --- a/candle-examples/examples/stable-diffusion/stable_diffusion.rs +++ b/candle-transformers/src/models/stable_diffusion/mod.rs @@ -1,5 +1,14 @@ -use crate::schedulers::PredictionType; -use crate::{clip, ddim, unet_2d, vae}; +pub mod attention; +pub mod clip; +pub mod ddim; +pub mod embeddings; +pub mod resnet; +pub mod schedulers; +pub mod unet_2d; +pub mod unet_2d_blocks; +pub mod utils; +pub mod vae; + use candle::{DType, Device, Result}; use candle_nn as nn; @@ -80,7 +89,7 @@ impl StableDiffusionConfig { sliced_attention_size: Option, height: Option, width: Option, - prediction_type: PredictionType, + prediction_type: schedulers::PredictionType, ) -> Self { let bc = |out_channels, use_cross_attn, attention_head_dim| unet_2d::BlockConfig { out_channels, @@ -154,7 +163,7 @@ impl StableDiffusionConfig { sliced_attention_size, height, width, - PredictionType::VPrediction, + schedulers::PredictionType::VPrediction, ) } @@ -162,7 +171,7 @@ impl StableDiffusionConfig { sliced_attention_size: Option, height: Option, width: Option, - prediction_type: PredictionType, + prediction_type: schedulers::PredictionType, ) -> Self { let bc = |out_channels, use_cross_attn, attention_head_dim| unet_2d::BlockConfig { out_channels, @@ -235,7 +244,7 @@ impl StableDiffusionConfig { height, width, // https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/blob/main/scheduler/scheduler_config.json - PredictionType::Epsilon, + schedulers::PredictionType::Epsilon, ) } diff --git a/candle-examples/examples/stable-diffusion/resnet.rs b/candle-transformers/src/models/stable_diffusion/resnet.rs similarity index 99% rename from candle-examples/examples/stable-diffusion/resnet.rs rename to candle-transformers/src/models/stable_diffusion/resnet.rs index 4cfd386d..0d818115 100644 --- a/candle-examples/examples/stable-diffusion/resnet.rs +++ b/candle-transformers/src/models/stable_diffusion/resnet.rs @@ -4,7 +4,7 @@ //! //! Denoising Diffusion Implicit Models, K. He and al, 2015. //! https://arxiv.org/abs/1512.03385 -use crate::utils::{conv2d, Conv2d}; +use super::utils::{conv2d, Conv2d}; use candle::{Result, Tensor, D}; use candle_nn as nn; use candle_nn::Module; diff --git a/candle-examples/examples/stable-diffusion/schedulers.rs b/candle-transformers/src/models/stable_diffusion/schedulers.rs similarity index 100% rename from candle-examples/examples/stable-diffusion/schedulers.rs rename to candle-transformers/src/models/stable_diffusion/schedulers.rs diff --git a/candle-examples/examples/stable-diffusion/unet_2d.rs b/candle-transformers/src/models/stable_diffusion/unet_2d.rs similarity index 99% rename from candle-examples/examples/stable-diffusion/unet_2d.rs rename to candle-transformers/src/models/stable_diffusion/unet_2d.rs index 81bd9547..a3ed136e 100644 --- a/candle-examples/examples/stable-diffusion/unet_2d.rs +++ b/candle-transformers/src/models/stable_diffusion/unet_2d.rs @@ -2,9 +2,9 @@ //! //! The 2D Unet models take as input a noisy sample and the current diffusion //! timestep and return a denoised version of the input. -use crate::embeddings::{TimestepEmbedding, Timesteps}; -use crate::unet_2d_blocks::*; -use crate::utils::{conv2d, Conv2d}; +use super::embeddings::{TimestepEmbedding, Timesteps}; +use super::unet_2d_blocks::*; +use super::utils::{conv2d, Conv2d}; use candle::{Result, Tensor}; use candle_nn as nn; use candle_nn::Module; diff --git a/candle-examples/examples/stable-diffusion/unet_2d_blocks.rs b/candle-transformers/src/models/stable_diffusion/unet_2d_blocks.rs similarity index 99% rename from candle-examples/examples/stable-diffusion/unet_2d_blocks.rs rename to candle-transformers/src/models/stable_diffusion/unet_2d_blocks.rs index be258acb..c53bd542 100644 --- a/candle-examples/examples/stable-diffusion/unet_2d_blocks.rs +++ b/candle-transformers/src/models/stable_diffusion/unet_2d_blocks.rs @@ -1,10 +1,10 @@ //! 2D UNet Building Blocks //! -use crate::attention::{ +use super::attention::{ AttentionBlock, AttentionBlockConfig, SpatialTransformer, SpatialTransformerConfig, }; -use crate::resnet::{ResnetBlock2D, ResnetBlock2DConfig}; -use crate::utils::{conv2d, Conv2d}; +use super::resnet::{ResnetBlock2D, ResnetBlock2DConfig}; +use super::utils::{conv2d, Conv2d}; use candle::{Result, Tensor, D}; use candle_nn as nn; diff --git a/candle-examples/examples/stable-diffusion/utils.rs b/candle-transformers/src/models/stable_diffusion/utils.rs similarity index 100% rename from candle-examples/examples/stable-diffusion/utils.rs rename to candle-transformers/src/models/stable_diffusion/utils.rs diff --git a/candle-examples/examples/stable-diffusion/vae.rs b/candle-transformers/src/models/stable_diffusion/vae.rs similarity index 99% rename from candle-examples/examples/stable-diffusion/vae.rs rename to candle-transformers/src/models/stable_diffusion/vae.rs index aa8e13a0..48155ada 100644 --- a/candle-examples/examples/stable-diffusion/vae.rs +++ b/candle-transformers/src/models/stable_diffusion/vae.rs @@ -4,7 +4,7 @@ //! Auto-encoder models compress their input to a usually smaller latent space //! before expanding it back to its original shape. This results in the latent values //! compressing the original information. -use crate::unet_2d_blocks::{ +use super::unet_2d_blocks::{ DownEncoderBlock2D, DownEncoderBlock2DConfig, UNetMidBlock2D, UNetMidBlock2DConfig, UpDecoderBlock2D, UpDecoderBlock2DConfig, }; From 70f38c20693ad26c6fffa359e734bde92124c0dd Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 12:10:51 +0100 Subject: [PATCH 066/150] Proper error on unsupported dtypes when using gemm. (#813) --- candle-core/src/cpu_backend.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/candle-core/src/cpu_backend.rs b/candle-core/src/cpu_backend.rs index 3cdc538a..fa96ba18 100644 --- a/candle-core/src/cpu_backend.rs +++ b/candle-core/src/cpu_backend.rs @@ -1371,8 +1371,9 @@ impl Map2 for MatMul { ) -> Result> { use gemm::{gemm, Parallelism}; - if T::DTYPE == DType::BF16 { - return Err(Error::UnsupportedDTypeForOp(T::DTYPE, "matmul").bt())?; + match T::DTYPE { + DType::F16 | DType::F32 | DType::F64 => {} + _ => Err(Error::UnsupportedDTypeForOp(T::DTYPE, "matmul").bt())?, } let (b, m, n, k) = self.0; From 5c35fbbb1316b6676c74e0889314802c76261bcf Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 13:06:29 +0100 Subject: [PATCH 067/150] Stable-Diffusion readme (#814) * Stable Diffusion readme. * Fix the image path. * Move the assets. * Resize the sample image. * Lower resolution. --- .../examples/stable-diffusion/README.md | 63 ++++++++++++++++++ .../assets/stable-diffusion-xl.jpg | Bin 0 -> 36573 bytes 2 files changed, 63 insertions(+) create mode 100644 candle-examples/examples/stable-diffusion/README.md create mode 100644 candle-examples/examples/stable-diffusion/assets/stable-diffusion-xl.jpg diff --git a/candle-examples/examples/stable-diffusion/README.md b/candle-examples/examples/stable-diffusion/README.md new file mode 100644 index 00000000..ee83b3f9 --- /dev/null +++ b/candle-examples/examples/stable-diffusion/README.md @@ -0,0 +1,63 @@ +# candle-stable-diffusion: A Diffusers API in Rust/Candle + +![rusty robot holding a candle](./assets/stable-diffusion-xl.jpg) + +_A rusty robot holding a fire torch in its hand_, generated by Stable Diffusion +XL using Rust and [candle](https://github.com/huggingface/candle). + +The `stable-diffusion` example is a conversion of +[diffusers-rs](https://github.com/LaurentMazare/diffusers-rs) using candle +rather than libtorch. This implementation supports Stable Diffusion v1.5, v2.1, +as well as Stable Diffusion XL 1.0. + +## Getting the weights + +The weights are automatically downloaded for you from the [HuggingFace +Hub](https://huggingface.co/) on the first run. There are various command line +flags to use local files instead, run with `--help` to learn about them. + +## Running some example. + +```bash +cargo run --example stable-diffusion --release --features=cuda,cudnn \ + -- --prompt "a cosmonaut on a horse (hd, realistic, high-def)" +``` + +The final image is named `sd_final.png` by default. +The default scheduler is the Denoising Diffusion Implicit Model scheduler (DDIM). The +original paper and some code can be found in the [associated repo](https://github.com/ermongroup/ddim). + +### Command-line flags + +- `--prompt`: the prompt to be used to generate the image. +- `--uncond-prompt`: the optional unconditional prompt. +- `--sd-version`: the Stable Diffusion version to use, can be `v1-5`, `v2-1`, or + `xl`. +- `--cpu`: use the cpu rather than the gpu (much slower). +- `--height`, `--width`: set the height and width for the generated image. +- `--n-steps`: the number of steps to be used in the diffusion process. +- `--num-samples`: the number of samples to generate. +- `--final-image`: the filename for the generated image(s). + +### Using flash-attention + +Using flash attention makes image generation a lot faster and uses less memory. +The downside is some long compilation time. You can set the +`CANDLE_FLASH_ATTN_BUILD_DIR` environment variable to something like +`/home/user/.candle` to ensures that the compilation artifacts are properly +cached. + +Enabling flash-attention requires both a feature flag, `--feature flash-attn` +and using the command line flag `--use-flash-attn`. + +## Image to Image Pipeline +... + +## FAQ + +### Memory Issues + +This requires a GPU with more than 8GB of memory, as a fallback the CPU version can be used +with the `--cpu` flag but is much slower. +Alternatively, reducing the height and width with the `--height` and `--width` +flag is likely to reduce memory usage significantly. diff --git a/candle-examples/examples/stable-diffusion/assets/stable-diffusion-xl.jpg b/candle-examples/examples/stable-diffusion/assets/stable-diffusion-xl.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a6f7b6c65ec0e0a0343ef228180926441d780887 GIT binary patch literal 36573 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 literal 0 HcmV?d00001 From dbd4561416647a01e75709adf51fff04bc5db95c Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 14:40:09 +0100 Subject: [PATCH 068/150] im2col version of the conv1d kernel. (#815) * im2col version of the cuda conv1d kernel. * im2col version of the conv1d cpu kernel. --- candle-core/src/cpu_backend.rs | 95 ++++++++++++++++++++++++++++++++- candle-core/src/cuda_backend.rs | 87 +++++++++++++++++++++++++++++- candle-kernels/src/conv.cu | 71 +++++++++++++++++++++++- 3 files changed, 249 insertions(+), 4 deletions(-) diff --git a/candle-core/src/cpu_backend.rs b/candle-core/src/cpu_backend.rs index fa96ba18..ee227ef9 100644 --- a/candle-core/src/cpu_backend.rs +++ b/candle-core/src/cpu_backend.rs @@ -4,6 +4,7 @@ use crate::{DType, Error, IntDType, Layout, Result, Shape, WithDType}; use half::{bf16, f16}; use rayon::prelude::*; +const USE_IM2COL_CONV1D: bool = true; const USE_IM2COL_CONV2D: bool = true; // TODO: Maybe we should not implement [Clone] here and instead have an explicit allocator + @@ -1091,6 +1092,65 @@ impl<'a> Map2 for Conv1D<'a> { } } +struct Im2Col1D { + l_k: usize, + stride: usize, + dilation: usize, + padding: usize, +} + +impl Im2Col1D { + fn l_out(&self, l: usize) -> usize { + (l + 2 * self.padding - self.dilation * (self.l_k - 1) - 1) / self.stride + 1 + } +} + +impl Map1 for Im2Col1D { + fn f(&self, vs: &[T], layout: &Layout) -> Result> { + let &Self { + l_k, + stride, + dilation, + padding, + } = self; + let (b, c, l) = layout.shape().dims3()?; + let l_out = self.l_out(l); + let src = &vs[layout.start_offset()..]; + let mut dst = vec![T::zero(); b * l_out * c * l_k]; + let (src_s0, src_s1, src_s2) = { + let s = layout.stride(); + (s[0], s[1], s[2]) + }; + // TODO: provide specialized kernels for the common use cases. + // - l_k = 1 + // - padding = 0 + // - stride = 1 + // - dilation = 1 + for b_idx in 0..b { + let src_idx = b_idx * src_s0; + let dst_idx = b_idx * l_out * c * l_k; + for l_idx in 0..l_out { + let dst_idx = dst_idx + l_idx * c * l_k; + for c_idx in 0..c { + let dst_idx = dst_idx + c_idx * l_k; + let src_idx = c_idx * src_s1 + src_idx; + for l_k_idx in 0..l_k { + let src_l = l_idx * stride + l_k_idx * dilation; + if padding != 0 && (src_l < padding || src_l >= l + padding) { + continue; + } + let src_l = src_l - padding; + let src_idx = src_idx + src_l * src_s2; + let dst_idx = dst_idx + l_k_idx; + dst[dst_idx] = src[src_idx] + } + } + } + } + Ok(dst) + } +} + struct Im2Col { h_k: usize, w_k: usize, @@ -2305,7 +2365,40 @@ impl BackendStorage for CpuStorage { kernel_l: &Layout, params: &crate::conv::ParamsConv1D, ) -> Result { - Conv1D(params).map(self, l, kernel, kernel_l) + if !USE_IM2COL_CONV1D { + return Conv1D(params).map(self, l, kernel, kernel_l); + } + let op = Im2Col1D { + l_k: params.k_size, + padding: params.padding, + stride: params.stride, + dilation: params.dilation, + }; + let col = op.map(self, l)?; + let b = params.b_size; + let n = params.c_out; + let l_out = params.l_out(); + let k = op.l_k * params.c_in; + let m = l_out; + let col_l = Layout::contiguous((b, m, k)); + let res = if kernel_l.is_contiguous() { + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + } else { + // Make the kernel contiguous if not already the case. + let mut kernel_c = self.device().zeros_impl(kernel_l.shape(), kernel.dtype())?; + kernel.copy_strided_src(&mut kernel_c, 0, kernel_l)?; + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + }; + let res_l = Layout::contiguous((b, l_out, params.c_out)).transpose(1, 2)?; + let mut res_t = self.device().zeros_impl(res_l.shape(), res.dtype())?; + res.copy_strided_src(&mut res_t, 0, &res_l)?; + Ok(res_t) } fn conv2d( diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index 20cab125..55443068 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -11,6 +11,7 @@ use cudarc::driver::{ use half::{bf16, f16}; use std::sync::{Arc, Mutex}; +const USE_IM2COL_CONV1D: bool = true; const USE_IM2COL_CONV2D: bool = true; /// cudarc related errors @@ -602,6 +603,53 @@ impl Map1 for Elu { } } +struct Im2Col1D { + l_k: usize, + stride: usize, + dilation: usize, + padding: usize, +} + +impl Im2Col1D { + fn l_out(&self, l: usize) -> usize { + (l + 2 * self.padding - self.dilation * (self.l_k - 1) - 1) / self.stride + 1 + } +} + +impl Map1 for Im2Col1D { + fn f( + &self, + src: &CudaSlice, + dev: &CudaDevice, + layout: &Layout, + ) -> Result> { + let shape = layout.shape(); + let dims = shape.dims(); + let l_out = self.l_out(dims[2]); + let dst_el = dims[0] * l_out * dims[1] * self.l_k; + let cfg = LaunchConfig::for_num_elems(dst_el as u32); + let ds = dev.htod_copy([dims, layout.stride()].concat()).w()?; + let src = &src.slice(layout.start_offset()..); + let func = dev.get_or_load_func(&kernel_name::("im2col1d"), kernels::CONV)?; + // SAFETY: Set later by running the kernel. + let dst = unsafe { dev.alloc::(dst_el) }.w()?; + let params = ( + dst_el, + l_out, + self.l_k, + self.stride, + self.padding, + self.dilation, + &ds, + src, + &dst, + ); + // SAFETY: ffi. + unsafe { func.launch(cfg, params) }.w()?; + Ok(dst) + } +} + struct Im2Col { h_k: usize, w_k: usize, @@ -1712,8 +1760,43 @@ impl BackendStorage for CudaStorage { params: &crate::conv::ParamsConv1D, ) -> Result { let device = self.device().clone(); - let slice = Conv1D(params).map(&self.slice, l, &kernel.slice, kernel_l, &device)?; - Ok(Self { slice, device }) + if !USE_IM2COL_CONV1D { + let slice = Conv1D(params).map(&self.slice, l, &kernel.slice, kernel_l, &device)?; + return Ok(Self { slice, device }); + } + + let col = Im2Col1D { + l_k: params.k_size, + stride: params.stride, + dilation: params.dilation, + padding: params.padding, + } + .map(&self.slice, &device, l)?; + let col = Self { slice: col, device }; + let l_out = params.l_out(); + let b = params.b_size; + let n = params.c_out; + let k = params.k_size * params.c_in; + let m = l_out; + let col_l = Layout::contiguous((b, m, k)); + let res = if kernel_l.is_contiguous() { + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + } else { + // Make the kernel contiguous if not already the case. + let mut kernel_c = self.device().zeros_impl(kernel_l.shape(), kernel.dtype())?; + kernel.copy_strided_src(&mut kernel_c, 0, kernel_l)?; + let kernel_l = Layout::contiguous_with_offset((1, n, k), kernel_l.start_offset()) + .transpose(1, 2)? + .broadcast_as((b, k, n))?; + col.matmul(kernel, (b, m, n, k), &col_l, &kernel_l)? + }; + let res_l = Layout::contiguous((b, l_out, n)).transpose(1, 2)?; + let mut res_t = self.device().zeros_impl(res_l.shape(), res.dtype())?; + res.copy_strided_src(&mut res_t, 0, &res_l)?; + Ok(res_t) } #[cfg(not(feature = "cudnn"))] diff --git a/candle-kernels/src/conv.cu b/candle-kernels/src/conv.cu index 51c393cb..9c8ce00f 100644 --- a/candle-kernels/src/conv.cu +++ b/candle-kernels/src/conv.cu @@ -51,6 +51,53 @@ __device__ void conv1d( dst[dst_i] = static_cast(d); } +template +__device__ void im2col1d( + const size_t dst_numel, + const size_t l_out, + const size_t l_k, + const size_t stride, + const size_t padding, + const size_t dilation, + const size_t *info, + const T *src, + T *dst +) { + const size_t dst_i = blockIdx.x * blockDim.x + threadIdx.x; + // dst: (b_size, l_out, c_in, l_k) + // src: (b_size, c_in, l_in) + if (dst_i >= dst_numel) { + return; + } + const size_t *src_dims = info; + const size_t *src_s = info + 3; + const size_t b_in = src_dims[0]; + const size_t c_in = src_dims[1]; + const size_t l_in = src_dims[2]; + + const size_t dst_s2 = l_k; + const size_t dst_s1 = c_in * dst_s2; + const size_t dst_s0 = l_out * dst_s1; + + size_t tmp_dst_i = dst_i; + const size_t b_idx = tmp_dst_i / dst_s0; + tmp_dst_i -= b_idx * dst_s0; + const size_t l_idx = tmp_dst_i / dst_s1; + tmp_dst_i -= l_idx * dst_s1; + const size_t c_idx = tmp_dst_i / dst_s2; + tmp_dst_i -= c_idx * dst_s2; + const size_t l_k_idx = tmp_dst_i; + size_t src_l_idx = l_idx * stride + l_k_idx * dilation; + if (src_l_idx < padding || src_l_idx >= l_in + padding) { + dst[dst_i] = static_cast(0); + } + else { + src_l_idx -= padding; + const size_t src_i = b_idx * src_s[0] + c_idx * src_s[1] + src_l_idx * src_s[2]; + dst[dst_i] = src[src_i]; + } +} + template __device__ void im2col( const size_t dst_numel, @@ -78,7 +125,7 @@ __device__ void im2col( const size_t h_in = src_dims[2]; const size_t w_in = src_dims[3]; - const size_t dst_s4 = w_k; + const size_t dst_s4 = w_k; const size_t dst_s3 = h_k * dst_s4; const size_t dst_s2 = c_in * dst_s3; const size_t dst_s1 = w_out * dst_s2; @@ -428,6 +475,21 @@ extern "C" __global__ void FN_NAME( \ conv2d(src_numel, w_out, h_out, stride, padding, dilation, info, src, kernel, dst); \ } \ +#define IM2COL1D_OP(TYPENAME, FN_NAME) \ +extern "C" __global__ void FN_NAME( \ + const size_t dst_numel, \ + const size_t l_out, \ + const size_t l_k, \ + const size_t stride, \ + const size_t padding, \ + const size_t dilation, \ + const size_t *info, \ + const TYPENAME *src, \ + TYPENAME *dst \ +) { \ + im2col1d(dst_numel, l_out, l_k, stride, padding, dilation, info, src, dst); \ +} \ + #define IM2COL_OP(TYPENAME, FN_NAME) \ extern "C" __global__ void FN_NAME( \ const size_t dst_numel, \ @@ -511,6 +573,7 @@ AVG_POOL2D_OP(__nv_bfloat16, float, avg_pool2d_bf16) MAX_POOL2D_OP(__nv_bfloat16, max_pool2d_bf16) UPSAMPLE_NEAREST2D_OP(__nv_bfloat16, upsample_nearest2d_bf16) IM2COL_OP(__nv_bfloat16, im2col_bf16) +IM2COL1D_OP(__nv_bfloat16, im2col1d_bf16) #endif #if __CUDA_ARCH__ >= 530 @@ -521,6 +584,7 @@ AVG_POOL2D_OP(__half, float, avg_pool2d_f16) MAX_POOL2D_OP(__half, max_pool2d_f16) UPSAMPLE_NEAREST2D_OP(__half, upsample_nearest2d_f16) IM2COL_OP(__half, im2col_f16) +IM2COL1D_OP(__half, im2col1d_f16) #endif CONV1D_OP(float, float, conv1d_f32) @@ -557,3 +621,8 @@ IM2COL_OP(float, im2col_f32) IM2COL_OP(double, im2col_f64) IM2COL_OP(uint8_t, im2col_u8) IM2COL_OP(uint32_t, im2col_u32) + +IM2COL1D_OP(float, im2col1d_f32) +IM2COL1D_OP(double, im2col1d_f64) +IM2COL1D_OP(uint8_t, im2col1d_u8) +IM2COL1D_OP(uint32_t, im2col1d_u32) From 59e63d690c95e58526bd41144823c6e63f9f2916 Mon Sep 17 00:00:00 2001 From: Eric Buehler <65165915+EricLBuehler@users.noreply.github.com> Date: Mon, 11 Sep 2023 11:01:11 -0400 Subject: [PATCH 069/150] Add weight, bias, and hidden_size methods (#816) * Add weight, bias methods to Conv(1|2) * Add hidden_size method to Embedding * Expose hidden_size --- candle-nn/src/conv.rs | 16 ++++++++++++++++ candle-nn/src/embedding.rs | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/candle-nn/src/conv.rs b/candle-nn/src/conv.rs index cfe86bfa..309a5f37 100644 --- a/candle-nn/src/conv.rs +++ b/candle-nn/src/conv.rs @@ -39,6 +39,14 @@ impl Conv1d { pub fn config(&self) -> &Conv1dConfig { &self.config } + + pub fn weight(&self) -> &Tensor { + &self.weight + } + + pub fn bias(&self) -> Option<&Tensor> { + self.bias.as_ref() + } } impl crate::Module for Conv1d { @@ -99,6 +107,14 @@ impl Conv2d { pub fn config(&self) -> &Conv2dConfig { &self.config } + + pub fn weight(&self) -> &Tensor { + &self.weight + } + + pub fn bias(&self) -> Option<&Tensor> { + self.bias.as_ref() + } } impl crate::Module for Conv2d { diff --git a/candle-nn/src/embedding.rs b/candle-nn/src/embedding.rs index d84f9f53..fccc8a17 100644 --- a/candle-nn/src/embedding.rs +++ b/candle-nn/src/embedding.rs @@ -18,6 +18,11 @@ impl Embedding { pub fn embeddings(&self) -> &Tensor { &self.embeddings } + + /// Get the hidden size of the embedding matrix + pub fn hidden_size(&self) -> usize { + self.hidden_size + } } impl crate::Module for Embedding { From c5a058b16954154a68899d26d681adfba003babf Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 20:40:07 +0100 Subject: [PATCH 070/150] Use the module trait in stable-diffusion. (#817) --- candle-core/src/safetensors.rs | 8 ++------ candle-examples/examples/stable-diffusion/main.rs | 2 +- .../src/models/stable_diffusion/attention.rs | 8 +++++--- .../src/models/stable_diffusion/clip.rs | 8 ++++---- .../src/models/stable_diffusion/embeddings.rs | 8 ++++---- .../src/models/stable_diffusion/unet_2d_blocks.rs | 12 ++++++------ .../src/models/stable_diffusion/vae.rs | 15 ++++++++------- 7 files changed, 30 insertions(+), 31 deletions(-) diff --git a/candle-core/src/safetensors.rs b/candle-core/src/safetensors.rs index f37bb8ef..d588ea67 100644 --- a/candle-core/src/safetensors.rs +++ b/candle-core/src/safetensors.rs @@ -78,11 +78,7 @@ impl st::View for &Tensor { } impl Tensor { - pub fn save_safetensors>( - &self, - name: &str, - filename: P, - ) -> Result<()> { + pub fn save_safetensors>(&self, name: &str, filename: P) -> Result<()> { let data = [(name, self.clone())]; Ok(st::serialize_to_file(data, &None, filename.as_ref())?) } @@ -267,7 +263,7 @@ impl MmapedFile { /// # Safety /// /// The unsafe is inherited from [`memmap2::MmapOptions`]. - pub unsafe fn new>(p: P) -> Result { + pub unsafe fn new>(p: P) -> Result { let p = p.as_ref(); let file = std::fs::File::open(p).map_err(|e| Error::from(e).with_path(p))?; let inner = memmap2::MmapOptions::new() diff --git a/candle-examples/examples/stable-diffusion/main.rs b/candle-examples/examples/stable-diffusion/main.rs index 6bce2917..c8b771a0 100644 --- a/candle-examples/examples/stable-diffusion/main.rs +++ b/candle-examples/examples/stable-diffusion/main.rs @@ -7,7 +7,7 @@ extern crate intel_mkl_src; use candle_transformers::models::stable_diffusion; use anyhow::{Error as E, Result}; -use candle::{DType, Device, IndexOp, Tensor, D}; +use candle::{DType, Device, IndexOp, Module, Tensor, D}; use clap::Parser; use tokenizers::Tokenizer; diff --git a/candle-transformers/src/models/stable_diffusion/attention.rs b/candle-transformers/src/models/stable_diffusion/attention.rs index 000cd2fe..2b925cee 100644 --- a/candle-transformers/src/models/stable_diffusion/attention.rs +++ b/candle-transformers/src/models/stable_diffusion/attention.rs @@ -17,7 +17,7 @@ impl GeGlu { } } -impl GeGlu { +impl Module for GeGlu { fn forward(&self, xs: &Tensor) -> Result { let _enter = self.span.enter(); let hidden_states_and_gate = self.proj.forward(xs)?.chunk(2, D::Minus1)?; @@ -53,7 +53,7 @@ impl FeedForward { } } -impl FeedForward { +impl Module for FeedForward { fn forward(&self, xs: &Tensor) -> Result { let _enter = self.span.enter(); let xs = self.project_in.forward(xs)?; @@ -501,8 +501,10 @@ impl AttentionBlock { xs.reshape((batch, t, self.num_heads, h_times_d / self.num_heads))? .transpose(1, 2) } +} - pub fn forward(&self, xs: &Tensor) -> Result { +impl Module for AttentionBlock { + fn forward(&self, xs: &Tensor) -> Result { let _enter = self.span.enter(); let in_dtype = xs.dtype(); let residual = xs; diff --git a/candle-transformers/src/models/stable_diffusion/clip.rs b/candle-transformers/src/models/stable_diffusion/clip.rs index d26c1c46..397a1cef 100644 --- a/candle-transformers/src/models/stable_diffusion/clip.rs +++ b/candle-transformers/src/models/stable_diffusion/clip.rs @@ -14,7 +14,7 @@ pub enum Activation { Gelu, } -impl Activation { +impl Module for Activation { fn forward(&self, xs: &Tensor) -> Result { match self { Activation::QuickGelu => xs * nn::ops::sigmoid(&(xs * 1.702f64)?)?, @@ -129,7 +129,7 @@ impl ClipTextEmbeddings { } } -impl ClipTextEmbeddings { +impl Module for ClipTextEmbeddings { fn forward(&self, xs: &Tensor) -> Result { let token_embedding = self.token_embedding.forward(xs)?; let position_embedding = self.position_embedding.forward(&self.position_ids)?; @@ -328,8 +328,8 @@ impl ClipTextTransformer { } } -impl ClipTextTransformer { - pub fn forward(&self, xs: &Tensor) -> Result { +impl Module for ClipTextTransformer { + fn forward(&self, xs: &Tensor) -> Result { let (bsz, seq_len) = xs.dims2()?; let xs = self.embeddings.forward(xs)?; let causal_attention_mask = Self::build_causal_attention_mask(bsz, seq_len, xs.device())?; diff --git a/candle-transformers/src/models/stable_diffusion/embeddings.rs b/candle-transformers/src/models/stable_diffusion/embeddings.rs index 97bc61f1..0de5f9a7 100644 --- a/candle-transformers/src/models/stable_diffusion/embeddings.rs +++ b/candle-transformers/src/models/stable_diffusion/embeddings.rs @@ -17,8 +17,8 @@ impl TimestepEmbedding { } } -impl TimestepEmbedding { - pub fn forward(&self, xs: &Tensor) -> Result { +impl Module for TimestepEmbedding { + fn forward(&self, xs: &Tensor) -> Result { let xs = nn::ops::silu(&self.linear_1.forward(xs)?)?; self.linear_2.forward(&xs) } @@ -41,8 +41,8 @@ impl Timesteps { } } -impl Timesteps { - pub fn forward(&self, xs: &Tensor) -> Result { +impl Module for Timesteps { + fn forward(&self, xs: &Tensor) -> Result { let half_dim = (self.num_channels / 2) as u32; let exponent = (Tensor::arange(0, half_dim, xs.device())?.to_dtype(candle::DType::F32)? * -f64::ln(10000.))?; diff --git a/candle-transformers/src/models/stable_diffusion/unet_2d_blocks.rs b/candle-transformers/src/models/stable_diffusion/unet_2d_blocks.rs index c53bd542..29510cef 100644 --- a/candle-transformers/src/models/stable_diffusion/unet_2d_blocks.rs +++ b/candle-transformers/src/models/stable_diffusion/unet_2d_blocks.rs @@ -5,7 +5,7 @@ use super::attention::{ }; use super::resnet::{ResnetBlock2D, ResnetBlock2DConfig}; use super::utils::{conv2d, Conv2d}; -use candle::{Result, Tensor, D}; +use candle::{Module, Result, Tensor, D}; use candle_nn as nn; #[derive(Debug)] @@ -43,7 +43,7 @@ impl Downsample2D { } } -impl Downsample2D { +impl Module for Downsample2D { fn forward(&self, xs: &Tensor) -> Result { let _enter = self.span.enter(); match &self.conv { @@ -172,8 +172,8 @@ impl DownEncoderBlock2D { } } -impl DownEncoderBlock2D { - pub fn forward(&self, xs: &Tensor) -> Result { +impl Module for DownEncoderBlock2D { + fn forward(&self, xs: &Tensor) -> Result { let _enter = self.span.enter(); let mut xs = xs.clone(); for resnet in self.resnets.iter() { @@ -256,8 +256,8 @@ impl UpDecoderBlock2D { } } -impl UpDecoderBlock2D { - pub fn forward(&self, xs: &Tensor) -> Result { +impl Module for UpDecoderBlock2D { + fn forward(&self, xs: &Tensor) -> Result { let _enter = self.span.enter(); let mut xs = xs.clone(); for resnet in self.resnets.iter() { diff --git a/candle-transformers/src/models/stable_diffusion/vae.rs b/candle-transformers/src/models/stable_diffusion/vae.rs index 48155ada..21709afe 100644 --- a/candle-transformers/src/models/stable_diffusion/vae.rs +++ b/candle-transformers/src/models/stable_diffusion/vae.rs @@ -132,14 +132,15 @@ impl Encoder { impl Encoder { fn forward(&self, xs: &Tensor) -> Result { - let mut xs = self.conv_in.forward(xs)?; + let mut xs = xs.apply(&self.conv_in)?; for down_block in self.down_blocks.iter() { - xs = down_block.forward(&xs)? + xs = xs.apply(down_block)? } - let xs = self.mid_block.forward(&xs, None)?; - let xs = self.conv_norm_out.forward(&xs)?; - let xs = nn::ops::silu(&xs)?; - self.conv_out.forward(&xs) + let xs = self + .mid_block + .forward(&xs, None)? + .apply(&self.conv_norm_out)?; + nn::ops::silu(&xs)?.apply(&self.conv_out) } } @@ -302,7 +303,7 @@ impl DiagonalGaussianDistribution { } pub fn sample(&self) -> Result { - let sample = Tensor::randn(0., 1f32, self.mean.shape(), self.mean.device()); + let sample = self.mean.randn_like(0., 1.); &self.mean + &self.std * sample } } From 871efc0307e39441236327643abb397b3ab200d0 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 11 Sep 2023 23:11:27 +0100 Subject: [PATCH 071/150] Bugfix for the conv2d cpu kernel. (#820) --- candle-core/src/cpu_backend.rs | 2 +- candle-nn/examples/cpu_benchmarks.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/candle-core/src/cpu_backend.rs b/candle-core/src/cpu_backend.rs index ee227ef9..544ce32d 100644 --- a/candle-core/src/cpu_backend.rs +++ b/candle-core/src/cpu_backend.rs @@ -1209,7 +1209,7 @@ impl Map1 for Im2Col { let dst_idx = dst_idx + h_k_idx * w_k; for w_k_idx in 0..w_k { let src_w = w_idx * stride + w_k_idx * dilation; - if padding != 0 && (src_w < padding || src_w >= h + padding) { + if padding != 0 && (src_w < padding || src_w >= w + padding) { continue; } let src_w = src_w - padding; diff --git a/candle-nn/examples/cpu_benchmarks.rs b/candle-nn/examples/cpu_benchmarks.rs index 5fb99625..204a7109 100644 --- a/candle-nn/examples/cpu_benchmarks.rs +++ b/candle-nn/examples/cpu_benchmarks.rs @@ -84,7 +84,7 @@ impl candle::CustomOp1 for Im2Col { let dst_idx = dst_idx + h_k_idx * w_k; for w_k_idx in 0..w_k { let src_w = w_idx * stride + w_k_idx * dilation; - if padding != 0 && (src_w < padding || src_w >= h + padding) { + if padding != 0 && (src_w < padding || src_w >= w + padding) { continue; } let src_w = src_w - padding; From 2257f4d475c676e33dbd8dbafabf95e821d27f62 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 12 Sep 2023 06:39:24 +0100 Subject: [PATCH 072/150] Bump the crate version + update the changelog. (#822) --- CHANGELOG.md | 31 ++++++++++++++++++- Cargo.toml | 2 +- candle-book/Cargo.toml | 10 +++--- candle-core/Cargo.toml | 2 +- candle-datasets/Cargo.toml | 4 +-- candle-examples/Cargo.toml | 10 +++--- candle-flash-attn/Cargo.toml | 6 ++-- candle-kernels/Cargo.toml | 2 +- candle-nn/Cargo.toml | 2 +- candle-pyo3/Cargo.toml | 4 +-- candle-transformers/Cargo.toml | 4 +-- candle-wasm-examples/llama2-c/Cargo.toml | 6 ++-- .../segment-anything/Cargo.toml | 6 ++-- candle-wasm-examples/whisper/Cargo.toml | 4 +-- candle-wasm-examples/yolo/Cargo.toml | 4 +-- 15 files changed, 63 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a52429cf..a0275c57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,42 @@ # Changelog This documents the main changes to the `candle` crate. -## v0.2.1 - Unreleased +## v0.2.2 - Unreleased ### Added +### Modified + +## v0.2.1 - 2023-09-11 + +### Added +- Add some RNNs (GRU and LSTM) in `candle-nn` + [674](https://github.com/huggingface/candle/pull/674), + [688](https://github.com/huggingface/candle/pull/688). +- gguf v2 support + [725](https://github.com/huggingface/candle/pull/725). +- Quantized llama example in Python using the pyo3 api + [716](https://github.com/huggingface/candle/pull/716). +- `candle-nn` layer for conv2d-transposed + [760](https://github.com/huggingface/candle/pull/760). +- Add the Segment-Anything Model (SAM) as an example + [773](https://github.com/huggingface/candle/pull/773). +- TinyViT backbone for the segemnt anything example + [787](https://github.com/huggingface/candle/pull/787). +- Shape with holes support + [770](https://github.com/huggingface/candle/pull/770). + ### Modified - Dilations are now supported in conv-transpose2d. [671](https://github.com/huggingface/candle/pull/671). +- Interactive mode for the quantized model + [690](https://github.com/huggingface/candle/pull/690). +- Faster softmax operation + [747](https://github.com/huggingface/candle/pull/747). +- Faster convolution operations on CPU and CUDA via im2col + [802](https://github.com/huggingface/candle/pull/802). +- Moving some models to a more central location + [796](https://github.com/huggingface/candle/pull/796). ## v0.2.0 - 2023-08-30 diff --git a/Cargo.toml b/Cargo.toml index b45a2ab6..cb6bf3cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ exclude = [ resolver = "2" [workspace.package] -version = "0.2.1" +version = "0.2.2" edition = "2021" description = "Minimalist ML framework." repository = "https://github.com/huggingface/candle" diff --git a/candle-book/Cargo.toml b/candle-book/Cargo.toml index 320fb887..ac179cb9 100644 --- a/candle-book/Cargo.toml +++ b/candle-book/Cargo.toml @@ -11,11 +11,11 @@ readme = "README.md" [dependencies] accelerate-src = { workspace = true, optional = true } -candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } -candle-datasets = { path = "../candle-datasets", version = "0.2.1" } -candle-nn = { path = "../candle-nn", version = "0.2.1" } -candle-transformers = { path = "../candle-transformers", version = "0.2.1" } -candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.1", optional = true } +candle = { path = "../candle-core", version = "0.2.2", package = "candle-core" } +candle-datasets = { path = "../candle-datasets", version = "0.2.2" } +candle-nn = { path = "../candle-nn", version = "0.2.2" } +candle-transformers = { path = "../candle-transformers", version = "0.2.2" } +candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.2", optional = true } safetensors = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/candle-core/Cargo.toml b/candle-core/Cargo.toml index e7213919..fad6d0bd 100644 --- a/candle-core/Cargo.toml +++ b/candle-core/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" [dependencies] accelerate-src = { workspace = true, optional = true } byteorder = { workspace = true } -candle-kernels = { path = "../candle-kernels", version = "0.2.1", optional = true } +candle-kernels = { path = "../candle-kernels", version = "0.2.2", optional = true } cudarc = { workspace = true, optional = true } gemm = { workspace = true } half = { workspace = true } diff --git a/candle-datasets/Cargo.toml b/candle-datasets/Cargo.toml index d69318e1..e34aa9be 100644 --- a/candle-datasets/Cargo.toml +++ b/candle-datasets/Cargo.toml @@ -11,8 +11,8 @@ readme = "README.md" [dependencies] byteorder = { workspace = true } -candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } -candle-nn = { path = "../candle-nn", version = "0.2.1" } +candle = { path = "../candle-core", version = "0.2.2", package = "candle-core" } +candle-nn = { path = "../candle-nn", version = "0.2.2" } hf-hub = { workspace = true} intel-mkl-src = { workspace = true, optional = true } memmap2 = { workspace = true } diff --git a/candle-examples/Cargo.toml b/candle-examples/Cargo.toml index eb552b88..a52ad88f 100644 --- a/candle-examples/Cargo.toml +++ b/candle-examples/Cargo.toml @@ -11,11 +11,11 @@ readme = "README.md" [dependencies] accelerate-src = { workspace = true, optional = true } -candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } -candle-datasets = { path = "../candle-datasets", version = "0.2.1" } -candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.1", optional = true } -candle-nn = { path = "../candle-nn", version = "0.2.1" } -candle-transformers = { path = "../candle-transformers", version = "0.2.1" } +candle = { path = "../candle-core", version = "0.2.2", package = "candle-core" } +candle-datasets = { path = "../candle-datasets", version = "0.2.2" } +candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.2", optional = true } +candle-nn = { path = "../candle-nn", version = "0.2.2" } +candle-transformers = { path = "../candle-transformers", version = "0.2.2" } cudarc = { workspace = true, optional = true } half = { workspace = true, optional = true } image = { workspace = true } diff --git a/candle-flash-attn/Cargo.toml b/candle-flash-attn/Cargo.toml index 0d130519..f92383ea 100644 --- a/candle-flash-attn/Cargo.toml +++ b/candle-flash-attn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candle-flash-attn" -version = "0.2.1" +version = "0.2.2" edition = "2021" description = "Flash attention layer for the candle ML framework." @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" readme = "README.md" [dependencies] -candle = { path = "../candle-core", features = ["cuda"], version = "0.2.1", package = "candle-core" } +candle = { path = "../candle-core", features = ["cuda"], version = "0.2.2", package = "candle-core" } half = { version = "2.3.1", features = ["num-traits"] } [build-dependencies] @@ -21,4 +21,4 @@ rayon = "1.7.0" [dev-dependencies] anyhow = { version = "1", features = ["backtrace"] } -candle-nn = { path = "../candle-nn", version = "0.2.1", features = ["cuda"] } +candle-nn = { path = "../candle-nn", version = "0.2.2", features = ["cuda"] } diff --git a/candle-kernels/Cargo.toml b/candle-kernels/Cargo.toml index 576c52ea..d6924c76 100644 --- a/candle-kernels/Cargo.toml +++ b/candle-kernels/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "candle-kernels" -version = "0.2.1" +version = "0.2.2" edition = "2021" description = "CUDA kernels for Candle" diff --git a/candle-nn/Cargo.toml b/candle-nn/Cargo.toml index db0f6a8f..5062a717 100644 --- a/candle-nn/Cargo.toml +++ b/candle-nn/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" [dependencies] accelerate-src = { workspace = true, optional = true } -candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } +candle = { path = "../candle-core", version = "0.2.2", package = "candle-core" } half = { workspace = true } thiserror = { workspace = true } intel-mkl-src = { workspace = true, optional = true } diff --git a/candle-pyo3/Cargo.toml b/candle-pyo3/Cargo.toml index 97631b0a..1d431cfb 100644 --- a/candle-pyo3/Cargo.toml +++ b/candle-pyo3/Cargo.toml @@ -15,8 +15,8 @@ crate-type = ["cdylib"] doc = false [dependencies] -candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } -candle-nn = { path = "../candle-nn", version = "0.2.1" } +candle = { path = "../candle-core", version = "0.2.2", package = "candle-core" } +candle-nn = { path = "../candle-nn", version = "0.2.2" } half = { workspace = true } pyo3 = { version = "0.19.0", features = ["extension-module"] } diff --git a/candle-transformers/Cargo.toml b/candle-transformers/Cargo.toml index 86caf918..fd42060f 100644 --- a/candle-transformers/Cargo.toml +++ b/candle-transformers/Cargo.toml @@ -11,8 +11,8 @@ readme = "README.md" [dependencies] accelerate-src = { workspace = true, optional = true } -candle = { path = "../candle-core", version = "0.2.1", package = "candle-core" } -candle-nn = { path = "../candle-nn", version = "0.2.1" } +candle = { path = "../candle-core", version = "0.2.2", package = "candle-core" } +candle-nn = { path = "../candle-nn", version = "0.2.2" } intel-mkl-src = { workspace = true, optional = true } num-traits = { workspace = true } rand = { workspace = true } diff --git a/candle-wasm-examples/llama2-c/Cargo.toml b/candle-wasm-examples/llama2-c/Cargo.toml index 51eac694..7209a648 100644 --- a/candle-wasm-examples/llama2-c/Cargo.toml +++ b/candle-wasm-examples/llama2-c/Cargo.toml @@ -9,9 +9,9 @@ categories.workspace = true license.workspace = true [dependencies] -candle = { path = "../../candle-core", version = "0.2.1", package = "candle-core" } -candle-nn = { path = "../../candle-nn", version = "0.2.1" } -candle-transformers = { path = "../../candle-transformers", version = "0.2.1" } +candle = { path = "../../candle-core", version = "0.2.2", package = "candle-core" } +candle-nn = { path = "../../candle-nn", version = "0.2.2" } +candle-transformers = { path = "../../candle-transformers", version = "0.2.2" } num-traits = { workspace = true } tokenizers = { workspace = true, features = ["unstable_wasm"] } diff --git a/candle-wasm-examples/segment-anything/Cargo.toml b/candle-wasm-examples/segment-anything/Cargo.toml index ab82ab1f..b67de112 100644 --- a/candle-wasm-examples/segment-anything/Cargo.toml +++ b/candle-wasm-examples/segment-anything/Cargo.toml @@ -9,9 +9,9 @@ categories.workspace = true license.workspace = true [dependencies] -candle = { path = "../../candle-core", version = "0.2.1", package = "candle-core" } -candle-nn = { path = "../../candle-nn", version = "0.2.1" } -candle-transformers = { path = "../../candle-transformers", version = "0.2.1" } +candle = { path = "../../candle-core", version = "0.2.2", package = "candle-core" } +candle-nn = { path = "../../candle-nn", version = "0.2.2" } +candle-transformers = { path = "../../candle-transformers", version = "0.2.2" } num-traits = { workspace = true } # App crates. diff --git a/candle-wasm-examples/whisper/Cargo.toml b/candle-wasm-examples/whisper/Cargo.toml index 47e7e094..10d3356e 100644 --- a/candle-wasm-examples/whisper/Cargo.toml +++ b/candle-wasm-examples/whisper/Cargo.toml @@ -9,8 +9,8 @@ categories.workspace = true license.workspace = true [dependencies] -candle = { path = "../../candle-core", version = "0.2.1", package = "candle-core" } -candle-nn = { path = "../../candle-nn", version = "0.2.1" } +candle = { path = "../../candle-core", version = "0.2.2", package = "candle-core" } +candle-nn = { path = "../../candle-nn", version = "0.2.2" } num-traits = { workspace = true } tokenizers = { workspace = true, features = ["unstable_wasm"] } diff --git a/candle-wasm-examples/yolo/Cargo.toml b/candle-wasm-examples/yolo/Cargo.toml index b4daf6e6..9d9e60cc 100644 --- a/candle-wasm-examples/yolo/Cargo.toml +++ b/candle-wasm-examples/yolo/Cargo.toml @@ -9,8 +9,8 @@ categories.workspace = true license.workspace = true [dependencies] -candle = { path = "../../candle-core", version = "0.2.1", package = "candle-core" } -candle-nn = { path = "../../candle-nn", version = "0.2.1" } +candle = { path = "../../candle-core", version = "0.2.2", package = "candle-core" } +candle-nn = { path = "../../candle-nn", version = "0.2.2" } num-traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } From bb23b90b1df684471e21b9133a1008c2604e1738 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 12 Sep 2023 10:17:31 +0100 Subject: [PATCH 073/150] Add a small readme for the quantized example. (#823) --- candle-examples/examples/quantized/README.md | 35 ++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 candle-examples/examples/quantized/README.md diff --git a/candle-examples/examples/quantized/README.md b/candle-examples/examples/quantized/README.md new file mode 100644 index 00000000..f3159493 --- /dev/null +++ b/candle-examples/examples/quantized/README.md @@ -0,0 +1,35 @@ +# candle-quantized-llama: Fast Inference of quantized LLaMA models + +This example provides a quantized LLaMA model similar to +[llama.cpp](https://github.com/ggerganov/llama.cpp). This is based on candle +built-in quantization methods. Supported features include: + +- 2-bit, 3-bit, 4-bit, 5-bit, 6-bit and 8-bit integer quantization support. +- SIMD optimizations on Apple Silicon and x86. +- Support using the `gguf` and `ggml` file formats. + +The weights are automatically downloaded for you from the [HuggingFace +Hub](https://huggingface.co/) on the first run. There are various command line +flags to use local files instead, run with `--help` to learn about them. + +## Running some example. + +```bash +cargo run --example quantized --release -- --prompt "The best thing about coding in rust is " + +> avx: true, neon: false, simd128: false, f16c: true +> temp: 0.80 repeat-penalty: 1.10 repeat-last-n: 64 +> loaded 291 tensors (3.79GB) in 2.17s +> params: HParams { n_vocab: 32000, n_embd: 4096, n_mult: 256, n_head: 32, n_layer: 32, n_rot: 128, ftype: 2 } +> The best thing about coding in rust is 1.) that I don’t need to worry about memory leaks, 2.) speed and 3.) my program will compile even on old machines. +``` + +### Command-line flags + +Run with `--help` to see all options. + +- `--which`: specify the model to use, e.g. `7b`, `13-chat`, `7b-code`. +- `--prompt interactive`: interactive mode where multiple prompts can be + entered. +- `--model mymodelfile.gguf`: use a local model file rather than getting one + from the hub. From 7a62aad24a5d8b1d8cb351e5ef77a7b3457a74b8 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 12 Sep 2023 11:01:06 +0100 Subject: [PATCH 074/150] Add a readme for yolo-v8. (#824) --- candle-examples/examples/yolo-v8/README.md | 47 ++++++++++++++++++ .../examples/yolo-v8/assets/bike.jpg | Bin 0 -> 182991 bytes .../examples/yolo-v8/assets/bike.od.jpg | Bin 0 -> 179024 bytes .../examples/yolo-v8/assets/bike.pose.jpg | Bin 0 -> 193397 bytes .../examples/yolo-v8/assets/peoples.pp.jpg | Bin 0 -> 81845 bytes 5 files changed, 47 insertions(+) create mode 100644 candle-examples/examples/yolo-v8/README.md create mode 100644 candle-examples/examples/yolo-v8/assets/bike.jpg create mode 100644 candle-examples/examples/yolo-v8/assets/bike.od.jpg create mode 100644 candle-examples/examples/yolo-v8/assets/bike.pose.jpg create mode 100644 candle-examples/examples/yolo-v8/assets/peoples.pp.jpg diff --git a/candle-examples/examples/yolo-v8/README.md b/candle-examples/examples/yolo-v8/README.md new file mode 100644 index 00000000..8590a8a9 --- /dev/null +++ b/candle-examples/examples/yolo-v8/README.md @@ -0,0 +1,47 @@ +# candle-yolo-v8: Object Detection and Pose Estimation + +This is a port of [Ultralytics +YOLOv8](https://github.com/ultralytics/ultralytics). The implementation is based +on the [tinygrad +version](https://github.com/tinygrad/tinygrad/blob/master/examples/yolov8.py) +and on the model architecture described in this +[issue](https://github.com/ultralytics/ultralytics/issues/189). The supported +tasks are object detection and pose estimation. + +You can try this model online on the [Candle YOLOv8 +Space](https://huggingface.co/spaces/lmz/candle-yolo). The model then fully runs +in your browser using WebAssembly - if you use a custom image it will never +leave your phone/computer! + +## Running some example. + +### Object Detection +```bash +cargo run --example yolo-v8 --release -- candle-examples/examples/yolo-v8/assets/bike.jpg +``` + +This prints details about the detected objects and generates a `bike.pp.jpg` file. + +![Leading group, Giro d'Italia 2021](./assets/bike.jpg) + +Image source: +[wikimedia](https://commons.wikimedia.org/wiki/File:Leading_group,_Giro_d%27Italia_2021,_Stage_15.jpg). + +![Leading group, Giro d'Italia 2021](./assets/bike.od.jpg) + +### Pose Estimation +```bash +cargo run --example yolo-v8 --release -- \ + candle-examples/examples/yolo-v8/assets/peoples.jpeg --task pose +``` + +![Leading group, Giro d'Italia 2021](./assets/bike.pose.jpg) + +### Command-line flags + +- `--which`: select the model variant to be used, `n`, `s` , `m`, `l`, or `x` by + increasing size and quality. +- `--task`: `detect` for object detection and `pose` for pose estimation. +- `--legend-size`: the size of the characters to print. +- `--model`: use a local model file rather than downloading it from the hub. + diff --git a/candle-examples/examples/yolo-v8/assets/bike.jpg b/candle-examples/examples/yolo-v8/assets/bike.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05d1faaf3eeb8fa1dfe6601a2351fdd7c0e7bc5d GIT binary patch literal 182991 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 literal 0 HcmV?d00001 diff --git a/candle-examples/examples/yolo-v8/assets/bike.od.jpg b/candle-examples/examples/yolo-v8/assets/bike.od.jpg new file mode 100644 index 0000000000000000000000000000000000000000..111b92867bb31f5d9872789df5b6a11e8c2144ea GIT binary patch literal 179024 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
yDSb$MaoU?f;YE%kUv>xzWr8y|uZk^% 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
-+yDEZNbbNOG9!ae5G?v^t)l1L!H8CkbF; zggEBwYU8S%nE>&j2i%Zlr|uWBgT=p`&81iy(o8!LnO`h?;BBj`{}N}!bE?IDXY9PI zeJ?D4?dT?mbm3>^TxMIYWDFzqcd(Gs>d4-d|7;?%HIU6r!!M(Ih1KXp97J(;2Cqx& z#|XtN55_;AKZ%u;@>^H!)}W!(oD_Yb*FTj3bg+Q@3l;Er&O}byBo27ZxIHk9)s_RY zkX1i%Nq^4zl;)&mw=$^z13;?c(Ix7EXI3mb9{)pP0nmp zM#FtC0ko#KLzujw#ox`Bl5P|a^zz6-jvUR(15%Aa57$9TLThC>KlsFaNi1isi1DvY zk%Itdo%;{a6({}&oa81WHpW#(pP;4OeY@mpmQ*B15)!g!36kWj<#ZclZc0(e2!&T| zM3B*!_X0UQ@nwrC{nHFHQXowjb%=jD1m5iheo73}rK9aK1y zK8^knF`SVz=?Z)muyiB3h#EDs-olrd-HNjW@#TiJ4?@x7P9s&dWabg9a=7daU>7%=$xQjT$M2Gi^R#FrdQ_`BoEy zli@WjB9=xg&MoU>3JwqCm>+jqFWC1!u+4=2aQ(N{Dr(%~^PQ9XhXay6UVv)h9u&_{ zcu5%3;$x;!F?Y}RXXh*hi99Pla4>{~RQGLaG^4p8*7@Li_VJl8tq&qU8mLI8Pig6K z{M(jMOr2 z4YGFZ`ynn4i_bpM9X8?}+j1ap(c39Zer#9M06}44y9ZmU5c&MNv)|3mhn+!%_{Pv3 zT_?3`kj)d>D}@hM+bl@-f21Yokii1{iJ2qsl8p*#8gfGF4rFhZ=m%}M^!1NEg^r$d;-FE3J5(Z_7 zhvBWj*iV5~e0-mDS=2oTFM~FF>dvoFpMfL^7IzmD!X+0r9ZT zsVal3Hgz3mo0Z)x`=hozr2iRy%; zkk*&uV44Lszb5Q0k_eYNt0V9Wa>jHbU;Mc-l5u~{l6F`C3H$d&?;gQ5&~*edN|$sS zpdo%-A$Cruep$uMB%1;bhoo`G^W8CyZHv6kSpuJwJEdD(f6)wMHL*n*0V5oGG#rbo z;kuknDtr}CNu>!9#f9|lP#SKbNKw%E7Ex;hir5zn{nenjsx&jfT z()Bj@OyoZuVcd;ZK5yK0I)XOqhw1BzY#WQ<@0IUp$kZ0~_$6lcrT@eW)tB0{>`mkk8uCkEnuh5ea)EhQsV%aAa*WrG~zlarwQRtUK9UnmXMyI4mbQk#QYgYZ0Fp zqSg>{*E|BNc0&5*cr;ZS$g4FQ@Dt{a+G$YrnAFflj`rbT)eZ-=fj^<1-Ey*MHKK z;a5nvhxTnUuTMydo{f)v2Mjr>`@izcG@j7qsMVasJIJrOj{NM&ZY~(m@4>HmBOo*e{HdyviKxK@YkRM?ZUI9Nx zu6UK~x+;;Mrw!4zjNBHFBg%I%6;93GsU zA5LHHH&4c%%hTC@u^rt79{qrImUKm`ScxH}D1PEk;%hx1%XJ3bI_Y6?abs>x5mpf| zEYKN7ujN5i(nhjf`vHksvG8h1g8twzvuA0uSD+l?Qk5BDg%V)AOiagv*V}p_H?G=5 zd(T*{t|Eta^MIT;-!CXYO@X%|qobAhmz9DO?HlFk9nFI9Mo1z6PUhj6UCYv$s)%s6 z{=PaYp{yROsd74|CETJl&6`r6M^#wdKrAdcL{S^Fx`y^&sRS^7+{HWgpa z5uHMxY0ds%y6~-=fDy*AV+}a3!g}WNy+|-vSiA&C=IR%OL+AE(ytKF|XSb_?F^wGB z4v~Y{4t$tZhu$JTDn;vAZ9sPqqt!EGoVS}>Vl?k-t0AJ)K!Nf#mu*rqZXXo_!}~-{ zH&RO3^Mni+VhF2t59gYMYVI&>g&nAnX$%d~5gmE`ZE=xFFIg3_D;O5LS|(wo-?S(d z7KbXm|8V>}h)oQc+38qLdGm1kS5lEm14NUYo^5!E4^yK{5P_(< z?!zMC0lM1sf8%Mq39#o4^~Qv?M`62_!y^)slJbBI(s@X}*Cmg39B*>>7;q%B<|gNo z=f&|__%F+RA+eZ;o6BF6eMr^=-I!*ruz+`N_b z6)Ul;E|rRU6|ZY7ZfnpG^0smLsPVgJA+c>69{~e{yCP$<#vl*$Z^tzLxw~MCrb+EI z!hb2HC;M!&DHJCfEY)I4&uZRrxI0IXS2k%*NN>MWs6nFZ=^%wVuiJBO2;JsUB(^NWlIk-%4ro9ETt+pf7)1e%rfi{zL)>-yhGI@j8;(huPY($ov? z3EjUH|5`^h&}?=i2x$Uuv9^t&wI% zBGT8u0hXh+;0>)w@{kz;NsCWR3>DO%SIzLEf}YAdb>Zs=VI|*Y6g66HMa`RWN>m(z zGWQP3+(f_n5;NH`e6#E1+IVkD=37z7qJt-7Qkk>1inlZcyyyqH(t>GIny)U#;z7+( zI(L4u?$;exlO{2vEoPxGK?6Q=H;K~xwCE6fqdOiz-cr@8?MfmxBP9Hpy9tFre0@uF z(c`f3Dcm?CKKam5;fzAewIkP=_f>V(=8+FX=4i=W8WGjvKdIEoN9t?&I#_{&8g-Vl z>C=0speNg@OuC?H*H8u9)(fBwsXFbld(@^rq~T+G+J#a zzuxMX!(G7~^l&@iocP^!j0)tgvV~J;YV^*agVip6MK|FyxFTyBH2M62A2eb6Fp~vX zjW*m|wMGue5Wn?JIK~jFdTvpGYbkY9;D&l`!mSawY!$x%icoR5^oC|5s!6lHXVhN& z;vs%%mVmKyII&wF_sb+&jlxY#H2RtXF)~K!mwf56HzTdV=4J`9R};)?PouVrHs%N!!J(b?3U8)EcW@m% zR~UVr@+CRt^CpvxXeS*S%1GNkwBYLC78t>Rqn5Kzn23z)J&h!m!q&cun}F~&DTImAaWKUWVQIMHTJzd z)$+N>xb3?>%Tm<|o(*QXWv5-rcm4k{(AT(ls%qyENzF)U^9yFggz8W9OWlyolJ%2pkh#&!YImg4)idrJ~YtV;p5WG?Q+85 z!*M9p8kq89?G}t38F{YZrqcc9r%lCg0^ZKG`){wpUiVhq)F&1!5SCHaK2JWoPmsOL zUfeTGTozfJdq??tG=8sse)%2p5IrxGiV%vn)PAT9N$D6Le0D^fmZRrqUdw&^bdev> zFFZEB6+M-A_dOJJQxi*s&|@!JUBw!cmP^W%Es9kCdzNYUIDzU_5*)4p1Uad`ayvd6 z&M*H>{rEH5>}#)l-0Ab4G zvnQ(%e*Gjd-!g7`ZO)Ug6b{?-%aNNXq<#-u^sHTdW>j}e){>iZL;BTlR^}tNf~5}6 zr3V^rdkcLXbWx4#PzZaZo<^`G)Uo_kw&(( zd@TkMGnY~lDm{3t9trBd##C@00aIM@ZFO$%J4Y@CO^_-5UR=W$Q>usuG95z4R^(QS zWQH=uw3dk$C*~&({`gI03*XZ4!g}~X_%o4TQ=Lmr-MmhPM67OTrR10_9LTF+Betgx z18W7JkwF~I)YWyijXKUi3K^*qsv`}s04z#%Wz!wPfK(=~4$BfsA+)W`=ktBH{A>0Oa&lI(*8*Bmxkb72guPV9f!oN( z)roQ4@uF%3lYE4vrAMd^GDgO{l`+3<3I0u|ppNuo&+Ab2kmiydCgF;mka7#3{4!20 z<{gCeDAStKhS!n)UN7x{h2TMkj7!WhjYvVI5}f47*KV_9-(_Vo7(o)>STgfPO}V3Kj=b?*Ie}A~bW;?Id-S>y`qq4N>?*g*#?a1&WTVD&2iyGtkk zXcq!Ee`-3xYsBs*x$eDvIpy6?{v1n0L^^<1~#j(j#=9DUoTd*Fau=N|#Q&P-0)Jo#Or|EK3!! z(^<5gsDJZx;oahBQGc!m-Ct)Nb+dIQf8_H3V7NF43r0*z>?z@b`7WN7aVm z=%RrO0UA|3c6+98);)Y1&?J*v6yLA-p@SjxvdMR>I8 z#N|N^>(k6!W?yglj;*KlX|01law8QmCMSC6(stG(e3C17!1=}Q4u}M^OtLerEOWPg m6>|A$m^PlX`*N;0FY9p5+aGc|UY!Dro#d>jUgPWk`|^JS%L~K+ literal 0 HcmV?d00001 diff --git a/candle-examples/examples/yolo-v8/assets/peoples.pp.jpg b/candle-examples/examples/yolo-v8/assets/peoples.pp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1707dbfac2e54d374c020b98d1481d029803a598 GIT binary patch literal 81845 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

mXK$^6{Xu^kT5vvUC!wQ3j-rsdH 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 Date: Tue, 12 Sep 2023 06:06:21 -0400 Subject: [PATCH 075/150] Add useful libraries section (#825) * Add useful libraries section * Add link --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 01c2bb8b..c3e35d33 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,16 @@ trunk serve --release --port 8081 And then head over to [http://localhost:8081/](http://localhost:8081/). + + +## Useful Libraries +- `candle-lora` + - [`candle-lora`](https://github.com/EricLBuehler/candle-lora) provides a LoRA implementation that conforms to the official `peft` implementation. + +If you have an addition to this list, please submit a pull request. + + + ## Features From 42da17694a4214a3e39e0d64afc22635ce83f557 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 12 Sep 2023 14:35:55 +0100 Subject: [PATCH 076/150] Segment Anything readme (#827) * Add a readme for the segment-anything model. * Add the original image. * Clean-up the segment anything cli example. * Also print the mask id in the outputs. --- .../examples/segment-anything/README.md | 40 +++++++ .../segment-anything/assets/sam_merged.jpg | Bin 0 -> 160984 bytes .../examples/segment-anything/main.rs | 108 +++++++----------- .../examples/yolo-v8/assets/peoples.pp.jpg | Bin 81845 -> 0 bytes 4 files changed, 81 insertions(+), 67 deletions(-) create mode 100644 candle-examples/examples/segment-anything/README.md create mode 100644 candle-examples/examples/segment-anything/assets/sam_merged.jpg delete mode 100644 candle-examples/examples/yolo-v8/assets/peoples.pp.jpg diff --git a/candle-examples/examples/segment-anything/README.md b/candle-examples/examples/segment-anything/README.md new file mode 100644 index 00000000..3c5b034f --- /dev/null +++ b/candle-examples/examples/segment-anything/README.md @@ -0,0 +1,40 @@ +# candle-segment-anything: Segment-Anything Model + +This example is based on Meta AI [Segment-Anything +Model](https://github.com/facebookresearch/segment-anything). This model +provides a robust and fast image segmentation pipeline that can be tweaked via +some prompting (requesting some points to be in the target mask, requesting some +points to be part of the background so _not_ in the target mask, specifying some +bounding box). + +The default backbone can be replaced by the smaller and faster TinyViT model +based on [MobileSAM](https://github.com/ChaoningZhang/MobileSAM). + +## Running some example. + +```bash +cargo run --example segment-anything --release -- \ + --image candle-examples/examples/yolo-v8/assets/bike.jpg + --use-tiny + --point-x 0.4 + --point-y 0.3 +``` + +Running this command generates a `sam_merged.jpg` file containing the original +image with a blue overlay of the selected mask. The red dot represents the prompt +specified by `--point-x 0.4 --point-y 0.3`, this prompt is assumed to be part +of the target mask. + +The values used for `--point-x` and `--point-y` should be between 0 and 1 and +are proportional to the image dimension, i.e. use 0.5 for the image center. + +![Leading group, Giro d'Italia 2021](../yolo-v8/assets/bike.jpg) + +![Leading group, Giro d'Italia 2021](./assets/sam_merged.jpg) + +### Command-line flags +- `--use-tiny`: use the TinyViT based MobileSAM backbone rather than the default + one. +- `--point-x`, `--point-y`: specifies the location of the target point. +- `--threshold`: sets the threshold value to be part of the mask, a negative + value results in a larger mask and can be specified via `--threshold=-1.2`. diff --git a/candle-examples/examples/segment-anything/assets/sam_merged.jpg b/candle-examples/examples/segment-anything/assets/sam_merged.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a5f64e5ef18852716fd69537c3005f80811bbc20 GIT binary patch literal 160984 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

Ijdm5rF>?myl*&V3I` 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%0DLgJl4oZ#$xeU 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%0AsTeI|D`kWAq=Ya8Ri*oT+*_%;^>*#STo4!pG2V! zd+bLWXWI#ccccmvzZA8~gRpi{qGQ|7J=1YxV2rm5`dZoC_CeITIWg3qPr^xqrr9o2 z8%b6I^Gq~0fC|VR&mai{#|Rm}yFllLt6-A7)Sw!zBLo2Z?iNGwKx(Lrms!8#NHVsz z-+vKXc{;CAWpbM&Fdd2#>{8Cqzt8No%I8E-{-lg)nz1|kImdAFZ|MtIPq|}1CPlfC zBs@S&-|9D6u;zY3QEkp?`}X~IYX>mJU3U~A2A1W(^)TdZXGk2HDZ??1pqgtu)QukGp#URTG$&JFJaTFf|dDmiFcKBLi^p?B0BGaX_?69-7Grp8!3q}Z=cxMP}u z&lc3}V1`g9TTlQ*+@=F{!9Kwt;jgd6Vbspf;jPrj--XjaAV|1FpRf$*f=Z$4W*aGH zDABk=-vP4jHqDPi!K=FA{2IUmD1NpM(CO_!pObd^{aF&U%fK9+?oyX3%;Khnk;l!D z&ExJcvL@DpFziq#oL5#5AtKci&V`ZxORq17Y_?>6hOg6vBoKCx_b!wOVx>cbK^)yi z0AyixTz10}wF*G~p?^yGuj*XD+gaTOdX*Gq4N9EsHD*^{H1v-Bzd>*!G$hl+z%!1$ z7^~tT;nCZ+7Y~m-jP2J3!#*s24e$SJI@>MZ?{9@?b`gT9 zi>Qq}x?yWJk3EOHc>gz92X_py9#{0vcyIer-YtUc;vP97F zzzURgpR^$%^l29g5gybwDH}mUp=tMp;CGC`E@fn9|CP*{Q@f|qU1Z5%`@+rfyL;?6 z))h|*)ZRwCcTblKPz>uCr2}1rR(AbDmv2<&51Vc8oayUAGY*|}2^8rtD!;Sa{h{~@ zt10LApvB$CexDOp9U?dM$vD6RSAECC{asAj$X8jOS8$MK=2hgDQK>IEWSiqjPnYY% zaK+wm=3vTPfA0?#f$m`u`!TgGZh|2OAJ9;}olQ7|%m-@dI2>i9ZG-@Cd!gvM>T8CbWb0TtV1QAFey+0Zy z7r8ZdF03b^5F)tKE!)t-jd17yaD&>vwMSLwc$6VR;r#~p&<9B%w8)&cNoxT_cdQ%X z3qc5Co@uy05Vr#|2=zIfN{a6_$hljk_hiWe19B$l(x_<1;G?1uIGx^`ez|!Ua2^jp=-P zEiY|bA=ST8dV#9%t}{fux1li)GUNjB(T?*u>#%N}!7=7}0I%1wWlKbe$kYm)q0kJ8 zqwjWsz0?9GnzUp&@dRCzlwj<05M3ke3;ZINgjzehSz=iWlYGDLo&7@)rt`Y#HVY;F zYgo)m+Vke`D>?yrb}{xPo}+)oM9|+u0>K+sp%M@>;FTW9T{R@gBDH)AVeD7dq1|G+ z?tYSve%+8H(0W2=H?S&M1eq3cn5b^z0V!)f2Wx=qi_$Og;EOxPhkU^TYJfvCK=)dk<-DZgurIWb{Sr*btUIQAL#ubTQi4_SN*8^p*LZ-M@?T(bC*Bw;V!td}@ofnn{xrQ9n7xG7WE*5N~FXeVmv z-!_1@RBc>kMozbpC)1G)iVE1gZ00h@L@eX!r#4yTyllLC6E_$>g$S|v$URZF{w6`% z^>055vzp{OT;&^lu}~0d(~jS>JB7x{og?``q;dW7`Dc*cE|V=qOAHrw;a%x}b8+Jt zeOYDk;x?Mtm{L+mLLW%KeDHH-Z_vAB{k^QMQg3j@Cf|!QSO3YobN9;>8@|SfsHY+w zR#WcDWP%i{76yuE;sgsTa^u`z$JG(Filg?n*n>pLO|Gn{W*Hq3!_ExrsAIaU!jmAs zUjH9A6!vM@8zE!?km^+{gSdQ4%_$EJI1?>X!J_s@n$00Ka#Dkzm?&@$bwZ0HGhV%c zw93iZ`U@35=cioJ8rYf*!j#LN{$hS_K|A3Kk2r32Rxr%BJe;np4K^V0$;a53iYBv2 z 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 anyhow::Result<()> { let device = candle_examples::device(args.cpu)?; - let (image, initial_h, initial_w) = if args.image.ends_with(".safetensors") { - let mut tensors = candle::safetensors::load(&args.image, &device)?; - let image = match tensors.remove("image") { - Some(image) => image, - None => { - if tensors.len() != 1 { - anyhow::bail!("multiple tensors in '{}'", args.image) - } - tensors.into_values().next().unwrap() - } - }; - let image = if image.rank() == 4 { - image.get(0)? - } else { - image - }; - let (_c, h, w) = image.dims3()?; - (image, h, w) - } else { - let (image, h, w) = candle_examples::load_image(&args.image, Some(sam::IMAGE_SIZE))?; - (image.to_device(&device)?, h, w) - }; + let (image, initial_h, initial_w) = + candle_examples::load_image(&args.image, Some(sam::IMAGE_SIZE))?; + let image = image.to_device(&device)?; println!("loaded image {image:?}"); let model = match args.model { @@ -113,7 +101,7 @@ pub fn main() -> anyhow::Result<()> { /* crop_n_points_downscale_factor */ 1, )?; for (idx, bbox) in bboxes.iter().enumerate() { - println!("{bbox:?}"); + println!("{idx} {bbox:?}"); let mask = (&bbox.data.to_dtype(DType::U8)? * 255.)?; let (h, w) = mask.dims2()?; let mask = mask.broadcast_as((3, h, w))?; @@ -135,56 +123,42 @@ pub fn main() -> anyhow::Result<()> { println!("mask:\n{mask}"); println!("iou_predictions: {iou_predictions:?}"); - // Save the mask as an image. - let mask = (mask.ge(0f32)? * 255.)?; + let mask = (mask.ge(args.threshold)? * 255.)?; let (_one, h, w) = mask.dims3()?; let mask = mask.expand((3, h, w))?; - candle_examples::save_image_resize(&mask, "sam_mask.png", initial_h, initial_w)?; - if !args.image.ends_with(".safetensors") { - let mut img = image::io::Reader::open(&args.image)? - .decode() - .map_err(candle::Error::wrap)?; - let mask_pixels = mask.permute((1, 2, 0))?.flatten_all()?.to_vec1::()?; - let mask_img: image::ImageBuffer, Vec> = - match image::ImageBuffer::from_raw(w as u32, h as u32, mask_pixels) { - Some(image) => image, - None => anyhow::bail!("error saving merged image"), - }; - let mask_img = image::DynamicImage::from(mask_img).resize_to_fill( - img.width(), - img.height(), - image::imageops::FilterType::CatmullRom, - ); - for x in 0..img.width() { - for y in 0..img.height() { - let mask_p = imageproc::drawing::Canvas::get_pixel(&mask_img, x, y); - if mask_p.0[0] > 100 { - let mut img_p = imageproc::drawing::Canvas::get_pixel(&img, x, y); - img_p.0[2] = 255 - (255 - img_p.0[2]) / 2; - img_p.0[1] /= 2; - img_p.0[0] /= 2; - imageproc::drawing::Canvas::draw_pixel(&mut img, x, y, img_p) - } + let mut img = image::io::Reader::open(&args.image)? + .decode() + .map_err(candle::Error::wrap)?; + let mask_pixels = mask.permute((1, 2, 0))?.flatten_all()?.to_vec1::()?; + let mask_img: image::ImageBuffer, Vec> = + match image::ImageBuffer::from_raw(w as u32, h as u32, mask_pixels) { + Some(image) => image, + None => anyhow::bail!("error saving merged image"), + }; + let mask_img = image::DynamicImage::from(mask_img).resize_to_fill( + img.width(), + img.height(), + image::imageops::FilterType::CatmullRom, + ); + for x in 0..img.width() { + for y in 0..img.height() { + let mask_p = imageproc::drawing::Canvas::get_pixel(&mask_img, x, y); + if mask_p.0[0] > 100 { + let mut img_p = imageproc::drawing::Canvas::get_pixel(&img, x, y); + img_p.0[2] = 255 - (255 - img_p.0[2]) / 2; + img_p.0[1] /= 2; + img_p.0[0] /= 2; + imageproc::drawing::Canvas::draw_pixel(&mut img, x, y, img_p) } } - match point { - Some((x, y)) => { - let (x, y) = ( - (x * img.width() as f64) as i32, - (y * img.height() as f64) as i32, - ); - imageproc::drawing::draw_filled_circle( - &img, - (x, y), - 3, - image::Rgba([255, 0, 0, 200]), - ) - .save("sam_merged.jpg")? - } - None => img.save("sam_merged.jpg")?, - }; } + let (x, y) = ( + (args.point_x * img.width() as f64) as i32, + (args.point_y * img.height() as f64) as i32, + ); + imageproc::drawing::draw_filled_circle(&img, (x, y), 3, image::Rgba([255, 0, 0, 200])) + .save("sam_merged.jpg")? } Ok(()) } diff --git a/candle-examples/examples/yolo-v8/assets/peoples.pp.jpg b/candle-examples/examples/yolo-v8/assets/peoples.pp.jpg deleted file mode 100644 index 1707dbfac2e54d374c020b98d1481d029803a598..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81845 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

mXK$^6{Xu^kT5vvUC!wQ3j-rsdH z=A0&!%b8U^C-^l3-`}qT)$IZ8tPeN=Bc5+Cz{u|A!o8f)fpZykp5?D!ay4_sK!2oW z{JrKtF~X&z_16zBf2g^$KAy{RX7_Ai>A!usHm`>7RSgn53H80EVkj|5sGCEqafzky z>7E%Z_ZqPV#ooUN+2&JrO?84(buWhU<91K~mq{AvYTY*f!w1jL;U?!0TPqnit+g`T~QmZTE2V;m`|3-;Yg5A zt(nl+naHf!;6BopqFA6}E^`!s`@z6*6A+gKiqC$*<378z06ZXvmpbm-FF_;xD$>cd z?C^Iw4A9faH3Z!QV1UKWKZPog=~dGIyMy=re{{2d%VK&At(=TlSjT(MfyYmd!Zjb; z;)`4k*N;;PCrU{yadeC~kc`lXY}}TM$$x0u?&0WXPLkf%qhkEs94mg!GYH7X*WOf| z(dU9c4uGlYyblq%4kKQ$h4>Cn@TgRdgA|#M=#H?p92|~_T3JGEnOITvgLwY>gH+Vl z8`j9mdWhYPSLNIa7X&x3NmhRn+6={oNDgpO{%?b2woiW?sFm7%xe}+QB{aO&clfDG*D6kg;3B{b|+OL%BiNxD*zX(s|L(dTl+=saSnAw2_ z=O}?V?u9mgw!>6_QFk(Y8Yy|k{_ZU2jz8#X?CbQ$S@gS?bKa zY_M5%AcuL$nhXch3DBf$r#@D$dkZRleX*2p55O!!1hqI;;&F!E&6xQFwJwD9dvfJm z;kYyD0IY`nR)m%Cc+>n2I#J^-$dxy8vneC(v6`-B66p-6 zv&x!8{Wpt&S@L5a>!$Y2l{KRFFWi-)i`b)C#Wj%K^%^kx#~k}?Kg|5se%R02l_$MU znfk*4wfGRda{F$OV9r*__1HXZv_@8OPd`7valAK;{#QzB($l2L4wtcua8P5&s`U#)~6)wK_&iHftsE z4$7o3BvuvKs*u8!V1bfu7LY`9=B7o#2!AL&_Si=!tx z=CzH*>v}mq=6pZGva!l=*8a)qxLm$}h{{-l1J37@i*87-1y(-fhzbHvNyjilBK921 z%^aEiw)h&4PMrkrcZN7==ET^n?;A=LzrG%gL55R8n+7l!1~5Oyp$_EEdMKg3828C3 zsOCO|U?YD$Z~E~RLc7DT=EpReEf-w~$alNq?Q^8? zWN%$RZZf-)nB`B<6ZD1__#yj`0cummi)UI{Dx7gbq(#9u>Fn^Gq0`3}f=y^V`p=%DpyfWxK$DAL{idH2%OW%6Bpfkj9`-Zho3>38h?3I-_`_zO{jU z=c%2ZK<4`B1sr))@-P(fnnLnex5r;f+PNj8n9?qJw>aXmg=4yV{A z@CAQhdyG1gddSBiqK!99adtKKc#)K8zw zvq;mgBgnLi^iTrC0z$Cvn>zgj=@-N5^RAOl9q?Z&rfXeWU0vCM#Ovon(T{81&k*O| zrq|eE<6p*OZl=_z0m}rISj#F33KVH!$aypt$W|BJ?Km zH6%u@u+$nTd$U$I3?(K~BX7?-5;ir~dCObAeCLI#A3Lb?h+4UEQwh2@AZC#tm?3bH zsd*W9rJk|nVnBoOtv>*heF1#>4E*_ObrwclBk|0LP{jw(N;idAg8)jaK}9^0>J+zC z36^NvQwRfCP77JPL~p2+-#27VZ|R8QpzA%zSo3be7_f6`twX*tC2LI6%yIa$>AZL= z+en(3GPe5py6T*nUT)N#%5)DD=~lN|?t5~-t?ml&W887UTV!6Sr%%6#PieH4oD7esr*{gn%9~S@t(ZYOtVHz+%NOS+t%V z<+JT5TYOBvP-&AnCKOsqLHDH`uA=_?z!02#wn z&371cmB2L5EZ==@6xVGbXaspM;Sv9OO77tYPb(5&Q_G-z`GtKK8XTO>mFIQT2Rdsr z`$;0k=em7FSHzFyDD8wi)2IH*MGmAI^xXo3-Ic+DizDUy?*XOx*T-A8$YN-_K*gk34m@2(S`Vk1?RzacAFllDYdv{bKq6Ab_%M_5wh*=cRGqk zxp1NUq!M#(Wmj@h$(PqDjtT&GP+yI3+p_i}SnOi#rh|i5Hf|yw#Zlz_(6|yVoeFl{ zIh<%2<46{^IASghTqgcgXa&G+=35R-jh#2xZp{3wyO#(l;J&S;Q_Jm-1dGPs7R}Fa z_D=OE4)+J~s1s*S0KpVYAecI=7@H%ZwJ+Lp_POiS8cDfCjFoByz`+%;1OIMwzJLy} z8}+U=(twE)uwkFWjsIoCv{8F9&WAmR@4lgi>>I820C5FMvQuzSD{cJ^02r2tsi-*> zeS4x}b=Y&)>Ui4>TWdHqzS!jOiBV?3jtS#^Uy3{D1TSwQ{9?s&2uChpVZp}}^AK7<{8NYwaV8K@Gop(myhHR!SG^8c}p8M%5b0($p%3vH?;}LdC z{m!~Y@MGIF(`)8;Db95nCO-Z)?mtl4-Bu9#Py2aKt(UrkJ0sp9=EHXs6?f7v>X~x_ zU|X4}SwGUwdJ7Wyl;eE{nmG8uV)vGW?Lb96X!DgbKq5or(A>EPkR3~~=Ip1>hBCA&Zq2_#q7o3h7TS2SXzR#)oj3bFPCjfHtr}vs z=A1a}8TpQUk8P($BDU|ua4!6i%z!)AjOUNsL)y~j8iEGoanl!CHo@o5(HcQvMglnrsh=58&#!^jdDQ3Do_ar1+FwuNc$-p9D5FfAKxHYI z)dI8xD&zD&PKf5$^BM6zx2Gd7i6pS@`tg4r{k}PKszR*)P6E&`mxA5@{irde!~qo) zrEBwt!SL}vD*nhOG`6Vp>2N+OJ}cWQt{zkPJtP`UN6U6VuQ zv32wKzvT}^tYkg0*s^E>T!bAhPr@Cd?c`7a{ehO6zli}N)p9fG#B-TuodcYBzzAo~ zgUFvR0ZnsCJnmPEJ9i(pcXrBAxw3l1)0)3g4qmLW0@HHIAjj)=mvp{U56KICvTRA* zDxXxmspe^>yPEG}?l2r@W^-d4dgYA=$m7oYU}skqqMfO3Wd$1L=sUIGl4Z>!h=Mvfe%xill! zH3ZfJ6v(!{)^*dtA=qwB+vS3HO4%5hYngGB)iM&b{;~V{+|vZwtMA#kmHgC&c2@uH z0o;b7paARo7VM}=MQ|h=&A2**E)K_yHF+4PNk>JY)eLf6s1Y#h`gPQM|1dsZE;=$VwF8Mm8O9|k%7+O2mZySK;Tb=(+nXhb>Awo6OiY5%bN*@yDL-0*& zJPOcJ%H3-SF>LDs=l5Q7hFnmt(Au*+DIDo_yb&7~z<1%`)<%bz1wWNaSP))C`9-d< za7~Wgr}JUfrdNAQ6@>TSt3LZm;Z{uA-!pu+E9~h*s-=|&vI>Z}=D7I7(GgVKR=v2* zNn{60G2vJ8wq_X@{v}BvS?=E?g^D}Q0q60jJ?FR-1=2EfWHI9Ese<*%j-#$&<2^Mm?KF3NgTPq z`|ADj7!#qfHep*kWDXsgJK#HqQCi)-u^LpH(BoHWZA$jZ<>Pa+c%?*YP6ibtp%|+- zT9Zy9_oq1;Q9TP{Xy0u?!)&sRl2fo!-2UD0;YRFfC~66$KjouuCe2JMyexJkmq3si zt%#P5+V^8E2b6Z)-i;N~r25^ioKL=8TJn%f4qRhL+HIjV^YRyKQ7#<0ijwgI{gnno zz+Q@r!A|eBD!n_bT6(S5s(k`3P6AlhADrwokcpUeO2I>T%k5>dKoo{%&9L)^!@&~$ z+3F)?iwsX;0yLJ;AR8_VQjC}JIG`jHWMdg5P7mFW)T$aiImIh`U13e4s4wn-*v*~fpi z>DYvuD(po4G_sE?@`IyCm_4UlxzGPXfD2{s5m6$pz+v&75+D{%-jN;7wn5DaO5|UT zZR5*DRMj`$_s-iZjAgiz=QVhnh$~T886PJ4k50>YqnxFV6>Cz}2Lt*!{kU(_76NZB zKr}vjIExz}RW#uHYD-_#|2ji`GsHi>Y-49P38GqirTZ)TTb;oNgyhsblbsl4>OW33 zid^TEf5XaY3p3#2#Sc}cQ`?o}$>I$=q|#j*3>0c97^Qz`qs}hbgg4zlEj?7JkykWd z=htA{!@N&9I8<``KtV-V^#UgcVEId%=2*^}H~q}Z3Qxn|MkZ=Y#>9Z6zv5Z*_iIG+ z4WQXaPyIEDW&$5n(|VI_y_V>^j3Bo|vac1_NFUBrg2D~{_fD_R<+dx%>-whfWl(+h zmz3D03qlzMSwX-lw-9cw;~rL_1qav59%aZb7-9m(EWK#u(--;Q)CV8f$!%CAOU-{9 z*4+9>JZ`lp4fe*ausi@nUy1zNy(&33p&@Hy+olD~-=ljLCxC8gPpJOiX&`ke6#%@N z)0AELLiqIFWtqS7{;zO_Ijx!=kOz$Gffw{PX!7H8lN)Q^892sdO^YVyHIVX4TD{&k z0{eceP?fT$TNPhJ_c}T4$D^d$0N}<$?iUrVLm!=G-6#oh4xH2r^kznqwsTGi$iphd ze_<&;4ZAwZ-Sb5)6D_rk#R0vaUNb=q}C zR4eBSM?t8+Ih?W%0cUao@w8i%?L@@OJBDI?H@kf%$FXd#tkfrcTjN*fVrYuBwJs#H z{d(OC)k1fv{>rzcd&-TX4ydLRB}z(642bWD!|{ecsUA@i&drTaK@v$gp}&-CgbU3R zGQWjsw3d0{VZlVZR6IKsb}+a!Z{mPnQ7pK*(FRWUGi;vIxR<8M@piM~$6;xjfZJ2z z6q^Xi4X<4oJ?nU7dV@5>jKfV%ZrWs+=gOfyvJLuPz<*s&?a95>xRAaop9fnPSCB1w z%mn3TCKZWuZQWcyZefb93|?zd{imx231O_^-?Yobaa&S$N(hifakmea@P&ZoNN#7g}*~e@t&iIWz%?LncjAsnC+~-j4 z3Y2hSPr0gS?0wQ>1NDfQ3xA!t{io<{2bioCG+t>v75WYYp5Z{egF@llggaAAJLtrS zX8Bd~x#(=Iq?87;AJkmxlJq!)IzO9ZX6$h3!ykqA+p@a5bbn6UeryR8{fA62w}0}pq&m09Sh%uK3=)`$@n2~Ao*YgjcNFt zvo}1gMV+5&xh%lewL_)3Hx_}A>Fvhc-=i2NNRIzSHG0J8Mty^~{Jm#zML*9EWMo=A zaEA}e9yM2!#ES?y)uq9r8mot{HMDjSHX|l+K>wA)O{C`y(LxPr_EC0F(1PwY1tv^S<8e8a=%CNQJ5Zldl8ASNB%0yXn19Xw3 z8CcmvS*86s+PltwU2At86X`PIaWo`;jxE#Ij|*ZUjiAjPR7rEC`NY-Z<>$vLps|&D zH{=@LjX3HbCH%i*-y@J2;%e=VmdUMJqU#@ZfOLx&_svxSRb$gMpO6|Wu*6wGHgMGh zxSNTiuKhEd!NSw4we*b0XB8cm$&cpg5Z)z;9Qy-aB@He+f`C$q&S|{ng9kJku}%)j z#I+bHC3asr(}i~D<&}~Nb^hvo{S-Rtuw?=0OG`F`Za2gW9l!m-beglcDxO!r!R@iN z3F;SQpv@5R42h_=V8@)&pn&g4E^Vrj2FCe5L;v|!%3M3X2|nX65pIdl0CQzzbipOd zTOe^7gml#vOc{C~p%phoZikt(&I8blX->f8 zA;P?2(^?$?(5%`km?V`*q4(5v*cWH@Y1=|HN~`&FhILxhEughX0*xOnwN`cqsD0)}?_gh1WP7P&JTJ zKh&$9kbpTuZ?Og}6HocOp9AY9DD0m-@gW1Of!(zTh$2^7z=OUDlu&7nJ1G0jrYd2< znFoGqTQIvEd5)QYmK=tKn#(O7sMMF{?gK-}qV`0f-d6)z`IWR%6w$d)Dk?x$7bM&Uy|N7ngOvCqiL|{5&u}Rm*c~HazZ+#ND&4lvTVupThTRNiAtt*xT z6N#4l^Ps0cqskz1xk>8{&(zS<30W zZyU&^&^$p>YopY2CR|+=Zcg;l#os>@-k(NJPxcd#xa{rDhi9S)nSH%MGncnd6<>gKs`|!ymuFLk zqLG`PY#aXfRx(ezq#X-PsgvUQ)sLp&Z`twZs*19kTD>s=A?9=Q+B55E+`U-)<&{!G z;nGf>oZ2OE*Q8%M(?3>n5~3uw+oL!rs@tF&V)HPl$9uV(^SjUMiAncL_bU6Yv9ebC z8+CJJN9kt|s}|L@JVz7Xv*Qnx5#uxR$-Lz$Wz40x29tQLcgTplyEnWid(TytGwrQnAp2o}dwQgjdv<#56PVVZI8fd$yKrI`&{q z%JsH$7E*&lQREYxJjEl|cYGj!|FQCeJuP%&g&(PK65i6}X=>2h)2sGIKd$rZHT6Z*CgE#ZLSd2J&_Yc zT-x+It#W_drZIo0M1e3TsZyPFwa^_#VmI1AWd=xf1W?VDXP2UzXX?vq08LeQhK?pA zBj+RL;;mX8Yj8?!CmY|4ZXJQm@o-z2p>5bsA;eJ5GZB^=a1enDmV_T;uc_y?&LM-k zo5l9*Fz3YDI%ccga)NTUCg-vQg6Lu7Gz{DCd)R2IlR7HRzEd|+mSc*Ww0TxcDU{LIzOZ(h8{7FChyIR5brWRHwvw z^y>I`Wc%xkF0r)*cff>zEhD=G3?M*dak zHdAqmY@+{$dw9fTN*no_-AX`80^^>bFLITrrh7Ek(E9uTTS~*!GB4Wd%%*gvW;e&Q zPW77&X6Jq)*DXwI@|w?lEgdKV4gfQBc@}0;1>NlPfZ~>gPqgnxG~*_*{VFT)D4W=b zbIc$O%2{wv7@MrZ+H4MR>Ivkr%F0I=y*Bufs&73$Y~{xSAsyL z@C}E!FvP?vB8DW!G$vnuQ7=Qa&fuZ~1}ezc=E$XSvP~R{YOFm~Nc-S=PhFAz&hw-1 zFIm;s9Apig04;=7==Jd;?$G#EAny1q0o5xVc(yfZej{n8Yl>>Xw{n-XCOdZjVJMM{ zESOq+OKv|cl2rRth$wBtPIo6AZ2$;5Ad z-N)TWfTg-w`I!QALjHmMum;vG)b}zAN;ur%S$kR39*A7Sc$qVC4fg_lH}%u>-@fn1 zsQ+RL*{zE=8h;(Ej8<&)LkRnyALojM9o*aaJ;ww^R|Iq3#{WPMw(ZX5K$`gY5UmYIp>?*qiR=TD~GVeY5>nWVDvESZZ;7Frwi*+3L7JJ0aD z0bvx1NI3{QrucK!kldl=z^wPZzcrA=l6Uvo#IitSZCp}Tb_kj60vPzJciWz!<95UL z#=}mAiddK1fn}5|-?}M}S*)4E6oayDmog)h#F3Iehk)GAEN#X)w|dKC{E-kmNaG2p z9XE03z@99mybX|~8|+@tP!z6G7?aIzO?@ByLr@fKR$POJngX+qnpDWsPqrWQSKE)! zK~&GnS72QB1hOlCU(jRpGXX{^2DELKa=0gI$5H*@&4=U4T*_KG+s>*0FLDSPQgr%A zQ}H$IdlCVPKLopPl?aHBMj`<|rR@=hJG$?0B+CoxNY$jJW==9qPoXM@Z63gt5l2_R zs@Tw2Mr=>J11p4rjM%5#VA8*X`&jDGLsDtdmE${MvITeQg7EWKY>6p(YWNBMv-;az zw~by<2tXNmC=GYN%B2C_e;b5NC%`fDX-e(uGPkcyz+8y3=cZ# zO+UXmc7sI5a47AL$7Uofa;gi&eKSwmJgequqVnvzkE4KtHpF>7z0bUmVXzo_Qzbcz zZ%BU*TLb4?o@<%SSGP#df7^F}_(+5+r4ikgGNkHJOFP&4iAZIrO`Oc>Uw470e{^Go z9WEz?`AB2_pTbJ&)un7dz5e;8x zrWtMfd8$7faMVLAGqgO{W6G|4w`X>;iAM+V=6Xsg5fc(e&N);vUxg|DULpO=bL~49 z33FSf`WiA>lzXjiKUWzc%qoYk&)9VbrRC5v?w~oH$V~G&Ewua~jUFHydzbmmgYjP5 zpY+84bG%1o;7syHnVd>>qZ`&_l@L#+J>In}*R9`|V`eV&(;~)qVu>`|VRzp3(M`$}h{(wH zrONn@YW-5pD-iMf+wPjjxrLiCYVZrc)=!SlIl%@>eXfbR4-ojzca(y)2iwa${{)kh z*#U#Wv$w+y3w;LO({NirO5gU@Z@!3<#GM#gW&d()oVmcZ&@!}tU^W$NcbdUM@2P0v zTxbXoom#vXuotN?_A!@He(9e*fZA~!c$*+mU~<~Qc?^+aN`O1XcnjNU51e>*@JHuE zgpDfYl3b3Or=1gHVft;V=;bineLD2adB4AX&Oaz++W3M)JquvrQgD3iUvND(Cnn*% zw=463R#X(%#C6{<9uIpVoT=*v0c~Gpy#3sL7)>Jo)tX;SS@e%lEq{FNWLRfFB`&E@&Deh(nRJ9KtkP z!JlkK=&EPrCFvA9wk~yr9ezu8o7N$uaSXv~{RUgEfMZAkj*a{ms%iXFJlF6?bH=8< zob?aOE2~phAB5X~et4oTb8i4B?pl9^Z3jv>iM?M^xK2;yF(k&ucD2tOKNW9};)WYS zKu3C4A1<^p1g=S^(}fienEKU|)@s>y3v>7GHrWKyL%V4wA+X8iFaB{mt{Ev=o#B)U zW$?@3oQ1VD{sb=)&Do zEheX(e3AV4^xO3$nUu-K8bVNnjhFAV!W&Ybg}ioo^nnsll9{M1gcit{v+wNi-3 zRwL}oG0t!J$6Z9^E`7j7f1jnVf#!e5q!8m7&qClR;Po7B2mSNICQUYYYf#X{MkeGyrsn6hSMKUe-R{V0YrT-@Uq9=qu+)rW zyPsS`^OnuqIh3RMKEkQ6C)|Gg#{nW^+VkFINjW^$@*5;2vKMI%K!e zko=IhqRaP6R?w3Li^xg&d|Wc0Zq9lF_3O+;U(d2uDjlSgekNiy4SQ2{<3}pGBwuCf zd_^jiGt^x)T=A0Ts94Kq6)u5lOoZytnR47`?w*e2G?Wlg%~U5~M) z28mIGI7kaVuj)e}kHT+6Y`Xq_mGh*`))bQvm#TCG4TL~MWMarsSEIB$PKsltV`Y1- z1YDo-QsqAIr0l*9*WtTas$flR_M_jJeLvCnxideR&yK~>ZH`dCJ|7Z(Sba3qeZ~T1 za^vV#kz=V3l%~GE^fLaI2otmbPYJzqX)jF-aOWf3irjbMm?CiUC`g3x;@Z3$!lRDQ9?%fktq^mR zTzU0PZc{99G`PRbJsmV1^IzEzp}8+A#0U3Ji%|$?!v@A!56Rt)uD)tOs9Ii;RBQ5W zvQr>a>dKaWUCvwTv#O(cp9+kv0TO;y$TpRCRrBFkZnf$GW{)<0?@L`uPi0C3gZ_CtuVg}A%t!waMB;5 zq5%VNw8aa|u(?V|m>uuFQ||Bdy2!*C&?^R&g^QrWCk=XdK?Wun)dl)f>=hH#UlEkJ zQ?)zuG}XR;sK{jGsPh@FconYng@^_f@npW{xKW?FVSIdl`1?*by*VYHH#3OwaFPsx z(x1YG^$gPxZuHowi4_V!O-BsJ8R!jn-#MI`qjb9xqcQ>oQj$uhwb4S3j<70-_PE1W zZW(aSbGe61incHAn=Td@vYv(u-+T1&AQ=%VBWAI`3|lxI+#(`2ncImzFJ#zX>Vr(s z#AsSpvEh%^m^zO_$BhjG{tg((meQ%Kjpr?L4q$1`|RwID;mK8r#9V34z@#J9K!$~h@6_nVrs zfjim9hOc+q*S(l8#}~`KhYqps;;7c*!%SyLCI9s)!1lmko9A)K^1Y}#z4sH z^|8w=a`NskPCGH)O75p-b|$3Owt@vj;)Kl_+%C@b$%yF~*p$t7`S1xP;aa@xhs^jo z7OxrTS3Qf}JI`SK9WH{3f1@%699R#RjW*k9Aqs`T6BAS6bkLdf`Wj-7=d8`WKuv{H zwE<01pIHxi#hrqY+%Z;bdFvfSYAWRfWTj7l(N2QF(=??#?)1@CwYCPqD?$Z=;{g&; zYLx@kad2W=Tv@mk39$m3pB)BqnNQDvL`Keil8<~j`_1cQcpojc{dw3Y;hg_89=VV?!RaHrJqYcyAlFmQS zIN6jnEEa81UQ|mlt>Z8qE}5HlT}SR;l@-WJJ}@Y{`ISeYeEBvTF7HD-0h)BtlyA7n z9@+@NRXBx%$rGu`B&q%y_!!&3XAaY&?xk!JXlGeb(9zvunr&B7m1};N?dcbTr4%;-{}Zf77~lETxOhi> z8Siz?Rcb6CIwEkAmaW#n>V-g16jPi68{R9gy2uy^eXn^CaI zi6S(8$C~cmRSi*2tqE@|m23ro|6_yMi*k1sILosuRL>!chW1=*yN-u}OsY2@o3tY* z=0O?X2W;Uz{0|{91Yf+C4bhg(H{xB9%DRP}KLzTQq!B3?-tJr>N7uIU;*bn$>&hyp zc8-s?%5PQ1+39xW-g+hAqkt^(khSZOTghIGLEHKv2j>MHYHH?*qec=_CmU2wzN29* z_BM|hNM;5XUZpoUNPcfE{9M0;4_M`+31(?Xyg;j7h}9~t9!R%5&v-6UTHK#aY*?zC zrD)pQE6hA3Ks4W6UzJaN0eA?y0cJD#fL~?o{K>F%5V3@n_ZOZ(IyY|Sg2S^^Aa>uS zQX=5{$X;R4n^in*S2#Q^vrmVvN7}QVB1?yWPS^oWO8fGSj#RK6f2C5JVMYAJ`95x? zumVxCj3;hDTS<2!AKuq-!ihV6f5tygAWS>^ofFs?2jc*c0P^4e_bEz6{+Qy@j&{YO zqA+WoC7I!imC2F10blc!JBzs`uOHkIbKMW!Aih?JvDi1W+W~eqNPoW zUnE?(d>edE(tNUpJu^;ScGb8#pzbluK8Pg=)Ap*_p4UM(fl@|9f!VFs+s5;|4W7E1 z^p&hu#hN=5&3m>-hkWuatshA`C>ydI)%)gy;!yPpEn+ZL@98J0dVE~G{Hnu4_a4D0 zGt>Bp<(=x~|M8v*a6=z@hKD1C1{7JF7No4~Q0$)-IK z6g4_yf8l<&_^IYE`B>%*?l9In`R;tk!d-JGZmz_EVo-{#@L{cArC}yL=p5Hx7*yy+ ztKs@%EiC5oVm}$gZD3;vrHv?)&4yI+6y@@aCFp9d-JSc%m0e_r0_jxs2$kB!QiafB zD+)a}cjBBlf(T(18*Id>t2&8(>T3#fu*5slce(m2g#(MiDH$4$%C$>v<<8kPtZ|1v zlr&!S&qBuQ)gec*3WdT&cOSQ3e2b>e_%v4f%G+=+bw6zCyi{#Z*`Sqn+!PH#{g^7Y z?VcTzeof;ph(D1)`p63fuc>g+lhd*_$5IQYL*7F5>GVAyPPt`C;Q3mu5Zy#QnOD49 zXTfGyF`~5u)l1C`#R5t}YbGZ6k&qoQR$eL_C!r78xZQ3}wjFr9l%{c&?@WxtDaTid zE;VpEe`&-!8_#y-M6;#Rwa|My1ul;;dMB%U{5kRr8 z<$4bp=RFeKW{s!u=7!t}@3khZq8svm6ICHSDVKKgcF(s}zS$-ZpYG^?mZ!#)#a|Yk zX_u8c@S?wTurhSazo*c3JdB{$#jHHr5B*phVsCj0pHN<|B-wGop*%0i^f^=q})_hy@li z^Ti=-r>FTfBQE=9dIH{uPSYdy0KtV@;prQJrpAH11Ng3aA9^W-q~q0^VbgIP4|*wr z#;+oJK5Dq8IuiYw4?B!TOQkd5D0g>|O+MbYnh+Oq#zr8)k?Z9~YS!(rt`4ZO+>~vbmE*Ke|Us+x~UKHyhznz6Xhfp;`Vm z?K)EiPE!WGo(qVQlEgvSrDm!|%}c^ywZjCL6d|O$O|HXv(a|3}FYvy!u&Jv&v(jXq z4xma}4%pu({Rr~r7HssO9ffSRb{OEtci%t&igY`xr?Bp_WjqJ=I~q-&y>h`)mgg$0 zy9wWvqgLofg6i4r?f{Arn%G8xmw%imBMQx~oX?)bItZo`ST0Ph3>qo>UvEyB1Iqk~ ztNN*@(O#$|+!8oE`pjHRmM$<{2tIJL!(6)*+4@ofpTnPhRcE%2Kt3BLyYIvbRT>(x z16GDS?Ap?LVVaFdpV6$bb?80^SJJt+q@%D2o?8|;u5t_7W{p*|U=QuknKuv6(d&LuJo%wvPean@rtn$M}H;FTyNynm- zvIst|W#5RXPtQN>bb3R-;Y-GidED(2N5SS?p=nOI?@tEGr8-llW!xkp7s6LN(#4Hd)G7E;`js^dVig9fL;qypmX?!u z-RnrcE-p+ZIsRm5c@QwW5TgBKH@d&Ht2P#h)|8Rv)73*=#Z_$lWQ*z#QvC6W=21+X4xI6 zTFag~O23K@_m^yCI#iqR-AAm8ihA|!t3x}jaqSYQ(y)Ld_ZVEjCDY;K7Hd@q;@Gb@ zNnJ)fCb(&^sT;ca(Ohy~`y*J7z^FMQS z>cZ7WyPfgD6`z#(1=3s1O7ePQj2yY~-xm!rqM&CmL)vd?yBE@S+B3*Jy11}fYx!A& zB?o=ywAW<3zR4a-q^PpXzM~9IhG9?MYUOeqHTCwOq{Q5~c^sRqlT;9`AEoM(D_9A4 zm;fSd|In;}({@wrE;C{BIv*W>wAG`TQ+~HKbH?E9?mNM%S&27!%MB^!2(6-GdChL^*`h$bonS{l*ZR5^0m!8@)*_1LlXAkMu9X{u?1$ntEE9 z%Dnh)Ll;ch9eFHRQ=#Fc(UVxNm&X{1Dg9H0H>~)@r7JTL^&HW~7vM#@d3U3V?TvLz zvQdL5R%D#FBPh&+Dw3@(Jo3r=%h84mQ!!q>ds&-}w^Jj$yVd;l7pI^ImlmgkQQ6G0 zPwXXa>f+Qr>*k9&0%{#Xi$G~{N@Fa38rDU{&tWpvvb+nc>HC`bEBK)1%3l)sTv?ZR z#YuOh&AY-)MZh`X+H88VVAS4h6QD#Depd&bRK#;=nlBrW+?Hp;cwk#)Tv~qxr;re| z1JKG_Kae%G!DIuG1%Xlz@AwzAQl1TBV=Zlt>vLMguN!g&`VBb@q*-wk2Un=X^2>*a z=+)MG^_Es9y03W%_MF(oaEw8V@$xPP(d)gMwU>`MxK=o@+BM|khHX={*$c9l=F}oD z38m&3VZTO7?kZ+`b1*yl<-U_zDvG^Dxx0U$<0jol=DM;C60+yJV}l8XrKR8eCgwB* zbW5(Vvnj-}o0AQ+BA|7D4a_>w@jEC^2;vGv2A) z(fz~&CHwY8QFYE#+dfV*4;iaWn^WdjXm=FzuE>`#FsqqEISjQ{yQt??#$&BFdaGe^ zUL&Sdzd6WOsS;8v@WvAf%gS&qfcd9|L5OiPt<8O~-jDZfd~0Fg_+iN-BDzhs~24ez6Asqc$br{4o? zk@S3wjFSJMA(Wmu=RWe~Rr-^SeR>AAVMfC)E-;5z*t-iXLuqTP^EEARUEf6Mj;bF% zo_Wf&!EQlyK7&e<;Sji7)iJoaqwC|+z4On_2tM z^QK*V3vrIMkc-q005}`giyyiKGz9TSNE&XE6S1vob=t2~mj~Xn+lUlS3me?{r1?t( zT+=+I+Dtx7odW)!n1Y(4Ha<=yR*#}~*&nWv{YJ$SyLYQTm1kf{deaeA4LyTCPotCd zvRRm%RE2`|j)44+XT6+zzdnf6=1#TqT=`N{`zcEr8O_Y_It>;lwKJEeR@bTfM-}c1 z6p`#ZcT%sws3Hq%!RO(yxME-HvvB|RKbY;9^<%ggR0DkA6@jve*gDgF1-%y>C2TOv zK;%7evi3Y5#7})ny~P3O4&Z~w2qKKER3rbB;W7vK{0u)pgvqw$?*!SJa2zt=%*uDT_e(ORan87iYk9f36CgRHF{g?Lv z7T@Od{eN6+{g3{|`{2fouUkUkcUkZ+e)fSmw(f^izhpItWn Date: Tue, 12 Sep 2023 09:10:16 -0700 Subject: [PATCH 077/150] Implement top_p / nucleus sampling (#819) * Implement top_p / nucleus sampling * Update changelog * rustfmt * Add tests * Fix clippy warning * Fix another clippy error --- CHANGELOG.md | 2 + candle-examples/examples/bigcode/main.rs | 16 ++++- candle-examples/examples/falcon/main.rs | 37 ++++++---- candle-examples/examples/llama/main.rs | 6 +- candle-examples/examples/llama2-c/main.rs | 7 +- candle-examples/examples/quantized/main.rs | 6 +- candle-transformers/src/generation/mod.rs | 70 +++++++++++++++---- candle-transformers/tests/generation_tests.rs | 29 ++++++++ .../llama2-c/lib-example.html | 20 +++++- candle-wasm-examples/llama2-c/src/app.rs | 23 ++++-- candle-wasm-examples/llama2-c/src/bin/m.rs | 10 ++- candle-wasm-examples/llama2-c/src/worker.rs | 16 +++-- 12 files changed, 199 insertions(+), 43 deletions(-) create mode 100644 candle-transformers/tests/generation_tests.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a0275c57..06041294 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ This documents the main changes to the `candle` crate. ## v0.2.2 - Unreleased ### Added +- Support for `top_p` sampling + [819](https://github.com/huggingface/candle/pull/819). ### Modified diff --git a/candle-examples/examples/bigcode/main.rs b/candle-examples/examples/bigcode/main.rs index 3540f75d..5f17109e 100644 --- a/candle-examples/examples/bigcode/main.rs +++ b/candle-examples/examples/bigcode/main.rs @@ -28,9 +28,10 @@ impl TextGeneration { tokenizer: Tokenizer, seed: u64, temp: Option, + top_p: Option, device: &Device, ) -> Self { - let logits_processor = LogitsProcessor::new(seed, temp); + let logits_processor = LogitsProcessor::new(seed, temp, top_p); Self { model, tokenizer, @@ -94,6 +95,10 @@ struct Args { #[arg(long)] temperature: Option, + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + /// The seed to use when generating random samples. #[arg(long, default_value_t = 299792458)] seed: u64, @@ -149,7 +154,14 @@ fn main() -> Result<()> { let model = GPTBigCode::load(vb, config)?; println!("loaded the model in {:?}", start.elapsed()); - let mut pipeline = TextGeneration::new(model, tokenizer, args.seed, args.temperature, &device); + let mut pipeline = TextGeneration::new( + model, + tokenizer, + args.seed, + args.temperature, + args.top_p, + &device, + ); pipeline.run(&args.prompt, args.sample_len)?; Ok(()) } diff --git a/candle-examples/examples/falcon/main.rs b/candle-examples/examples/falcon/main.rs index c45fe545..b0973d64 100644 --- a/candle-examples/examples/falcon/main.rs +++ b/candle-examples/examples/falcon/main.rs @@ -25,17 +25,25 @@ struct TextGeneration { repeat_last_n: usize, } +struct GenerationOptions { + temp: Option, + top_p: Option, + repeat_penalty: f32, + repeat_last_n: usize, +} + impl TextGeneration { fn new( model: Falcon, tokenizer: Tokenizer, + generation_options: GenerationOptions, seed: u64, - temp: Option, device: &Device, - repeat_penalty: f32, - repeat_last_n: usize, ) -> Self { - let logits_processor = LogitsProcessor::new(seed, temp); + let logits_processor = + LogitsProcessor::new(seed, generation_options.temp, generation_options.top_p); + let repeat_penalty = generation_options.repeat_penalty; + let repeat_last_n = generation_options.repeat_last_n; Self { model, tokenizer, @@ -118,6 +126,10 @@ struct Args { #[arg(long)] temperature: Option, + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + /// The seed to use when generating random samples. #[arg(long, default_value_t = 299792458)] seed: u64, @@ -185,15 +197,14 @@ fn main() -> Result<()> { let model = Falcon::load(vb, config)?; println!("loaded the model in {:?}", start.elapsed()); - let mut pipeline = TextGeneration::new( - model, - tokenizer, - args.seed, - args.temperature, - &device, - args.repeat_penalty, - args.repeat_last_n, - ); + let generation_options = GenerationOptions { + temp: args.temperature, + top_p: args.top_p, + repeat_penalty: args.repeat_penalty, + repeat_last_n: args.repeat_last_n, + }; + let mut pipeline = + TextGeneration::new(model, tokenizer, generation_options, args.seed, &device); pipeline.run(&args.prompt, args.sample_len)?; Ok(()) } diff --git a/candle-examples/examples/llama/main.rs b/candle-examples/examples/llama/main.rs index db3d216c..b2d7d938 100644 --- a/candle-examples/examples/llama/main.rs +++ b/candle-examples/examples/llama/main.rs @@ -42,6 +42,10 @@ struct Args { #[arg(long)] temperature: Option, + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + /// The seed to use when generating random samples. #[arg(long, default_value_t = 299792458)] seed: u64, @@ -193,7 +197,7 @@ fn main() -> Result<()> { println!("starting the inference loop"); print!("{prompt}"); - let mut logits_processor = LogitsProcessor::new(args.seed, args.temperature); + let mut logits_processor = LogitsProcessor::new(args.seed, args.temperature, args.top_p); let start_gen = std::time::Instant::now(); let mut index_pos = 0; let mut token_generated = 0; diff --git a/candle-examples/examples/llama2-c/main.rs b/candle-examples/examples/llama2-c/main.rs index e0ade322..e752a494 100644 --- a/candle-examples/examples/llama2-c/main.rs +++ b/candle-examples/examples/llama2-c/main.rs @@ -27,6 +27,10 @@ struct InferenceCmd { #[arg(long)] temperature: Option, + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + #[arg(long, default_value = "")] prompt: String, @@ -133,6 +137,7 @@ fn main() -> anyhow::Result<()> { None => { let cmd = InferenceCmd { temperature: None, + top_p: None, prompt: "".to_string(), config: None, model_id: "karpathy/tinyllamas".to_string(), @@ -256,7 +261,7 @@ fn run_inference(args: &InferenceCmd, common_args: &Args) -> Result<()> { let model = Llama::load(vb, &cache, config)?; println!("starting the inference loop"); - let mut logits_processor = LogitsProcessor::new(299792458, args.temperature); + let mut logits_processor = LogitsProcessor::new(299792458, args.temperature, args.top_p); let mut index_pos = 0; print!("{}", args.prompt); diff --git a/candle-examples/examples/quantized/main.rs b/candle-examples/examples/quantized/main.rs index c8179d33..a80ad420 100644 --- a/candle-examples/examples/quantized/main.rs +++ b/candle-examples/examples/quantized/main.rs @@ -71,6 +71,10 @@ struct Args { #[arg(long, default_value_t = 0.8)] temperature: f64, + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + /// The seed to use when generating random samples. #[arg(long, default_value_t = 299792458)] seed: u64, @@ -310,7 +314,7 @@ fn main() -> anyhow::Result<()> { prompt_tokens }; let mut all_tokens = vec![]; - let mut logits_processor = LogitsProcessor::new(args.seed, temperature); + let mut logits_processor = LogitsProcessor::new(args.seed, temperature, args.top_p); let start_prompt_processing = std::time::Instant::now(); let mut next_token = { diff --git a/candle-transformers/src/generation/mod.rs b/candle-transformers/src/generation/mod.rs index b1d20168..6c8c8ae4 100644 --- a/candle-transformers/src/generation/mod.rs +++ b/candle-transformers/src/generation/mod.rs @@ -4,32 +4,76 @@ use rand::{distributions::Distribution, SeedableRng}; pub struct LogitsProcessor { rng: rand::rngs::StdRng, temperature: Option, + top_p: Option, } impl LogitsProcessor { - pub fn new(seed: u64, temperature: Option) -> Self { + pub fn new(seed: u64, temperature: Option, top_p: Option) -> Self { Self { rng: rand::rngs::StdRng::seed_from_u64(seed), temperature, + top_p, } } + fn sample_argmax(&mut self, logits: Tensor) -> Result { + let logits_v: Vec = logits.to_vec1()?; + let next_token = logits_v + .iter() + .enumerate() + .max_by(|(_, u), (_, v)| u.total_cmp(v)) + .map(|(i, _)| i as u32) + .unwrap(); + Ok(next_token) + } + + fn sample_multi(&mut self, prs: &Vec) -> Result { + let distr = rand::distributions::WeightedIndex::new(prs).map_err(Error::wrap)?; + let next_token = distr.sample(&mut self.rng) as u32; + Ok(next_token) + } + + fn sample_topp(&mut self, prs: &mut Vec, top_p: f32) -> Result { + // top-p sampling (or "nucleus sampling") samples from the smallest set of + // tokens that exceed probability top_p. This way we never sample tokens that + // have very low probabilities and are less likely to go "off the rails". + let mut argsort_indices = (0..prs.len()).collect::>(); + + // Sort by descending probability. + argsort_indices.sort_by(|&i, &j| prs[j].partial_cmp(&prs[i]).unwrap()); + + // Clamp smaller probabilities to zero. + let mut cumsum = 0.; + for index in &argsort_indices { + if cumsum >= top_p { + prs[*index] = 0.0; + } else { + cumsum += prs[*index]; + } + } + + // Sample with clamped probabilities. + let next_token = self.sample_multi(prs)?; + Ok(next_token) + } + pub fn sample(&mut self, logits: &Tensor) -> Result { let logits = logits.to_dtype(DType::F32)?; let temperature = self.temperature.unwrap_or(0.); - let next_token = if temperature > 0. { - let prs = candle_nn::ops::softmax(&(&logits / temperature)?, D::Minus1)?; - let prs: Vec = prs.to_vec1()?; - let distr = rand::distributions::WeightedIndex::new(prs).map_err(Error::wrap)?; - distr.sample(&mut self.rng) as u32 + let top_p = self.top_p.unwrap_or(1.); + let next_token = if temperature == 0. { + self.sample_argmax(logits)? } else { - let logits_v: Vec = logits.to_vec1()?; - logits_v - .iter() - .enumerate() - .max_by(|(_, u), (_, v)| u.total_cmp(v)) - .map(|(i, _)| i as u32) - .unwrap() + let logits = &(&logits / temperature)?; + let prs = candle_nn::ops::softmax(logits, D::Minus1)?; + let mut prs: Vec = prs.to_vec1()?; + if top_p <= 0.0 || top_p >= 1.0 { + // simply sample from the predicted probability distribution + self.sample_multi(&prs)? + } else { + // top-p (nucleus) sampling, clamping the least likely tokens to zero + self.sample_topp(&mut prs, top_p as f32)? + } }; Ok(next_token) } diff --git a/candle-transformers/tests/generation_tests.rs b/candle-transformers/tests/generation_tests.rs new file mode 100644 index 00000000..76f994d0 --- /dev/null +++ b/candle-transformers/tests/generation_tests.rs @@ -0,0 +1,29 @@ +use candle::{Device, Result, Tensor}; +use candle_transformers::generation::LogitsProcessor; + +#[test] +fn sample_with_zero_temperature() -> Result<()> { + let mut logits_process = LogitsProcessor::new(1337, None, None); + let logits = Tensor::new(&[0.1, 0.2, 0.3, 0.4], &Device::Cpu)?; + let token = logits_process.sample(&logits)?; + assert_eq!(token, 3); + Ok(()) +} + +#[test] +fn sample_with_temperature() -> Result<()> { + let mut logits_process = LogitsProcessor::new(42, Some(0.9), None); + let logits = Tensor::new(&[0.1, 0.2, 0.3, 0.4], &Device::Cpu)?; + let token = logits_process.sample(&logits)?; + assert_eq!(token, 0); + Ok(()) +} + +#[test] +fn sample_with_top_p() -> Result<()> { + let mut logits_process = LogitsProcessor::new(42, Some(1.0), Some(0.5)); + let logits = Tensor::new(&[0.1, 0.2, 0.3, 0.4], &Device::Cpu)?; + let token = logits_process.sample(&logits)?; + assert_eq!(token, 2); + Ok(()) +} diff --git a/candle-wasm-examples/llama2-c/lib-example.html b/candle-wasm-examples/llama2-c/lib-example.html index b5033c54..22b12517 100644 --- a/candle-wasm-examples/llama2-c/lib-example.html +++ b/candle-wasm-examples/llama2-c/lib-example.html @@ -56,6 +56,7 @@ const weightsURL = `${MODELS_BASE_URL}/${model.url}`; const prompt = getValue("prompt"); const temperature = getValue("temperature"); + const topP = getValue("top-p"); const repeatPenalty = getValue("repeat_penalty"); const seed = getValue("seed"); const maxSeqLen = getValue("max-seq"); @@ -99,6 +100,7 @@ tokenizerURL: "tokenizer.json", prompt, temp: temperature, + top_p: topP, repeatPenalty, seed: BigInt(seed), maxSeqLen, @@ -251,7 +253,7 @@ 0.50 + + + + 1.00 >, + top_p: std::rc::Rc>, prompt: std::rc::Rc>, generated: String, n_tokens: usize, @@ -81,6 +82,7 @@ impl Component for App { status, n_tokens: 0, temperature: std::rc::Rc::new(std::cell::RefCell::new(0.)), + top_p: std::rc::Rc::new(std::cell::RefCell::new(1.0)), prompt: std::rc::Rc::new(std::cell::RefCell::new("".to_string())), generated: String::new(), current_decode: None, @@ -122,10 +124,11 @@ impl Component for App { self.n_tokens = 0; self.generated.clear(); let temp = *self.temperature.borrow(); + let top_p = *self.top_p.borrow(); let prompt = self.prompt.borrow().clone(); - console_log!("temp: {}, prompt: {}", temp, prompt); + console_log!("temp: {}, top_p: {}, prompt: {}", temp, top_p, prompt); ctx.link() - .send_message(Msg::WorkerInMsg(WorkerInput::Run(temp, prompt))) + .send_message(Msg::WorkerInMsg(WorkerInput::Run(temp, top_p, prompt))) } true } @@ -177,13 +180,21 @@ impl Component for App { fn view(&self, ctx: &Context) -> Html { use yew::TargetCast; let temperature = self.temperature.clone(); - let oninput = ctx.link().callback(move |e: yew::InputEvent| { + let oninput_temperature = ctx.link().callback(move |e: yew::InputEvent| { let input: web_sys::HtmlInputElement = e.target_unchecked_into(); if let Ok(temp) = f64::from_str(&input.value()) { *temperature.borrow_mut() = temp } Msg::Refresh }); + let top_p = self.top_p.clone(); + let oninput_top_p = ctx.link().callback(move |e: yew::InputEvent| { + let input: web_sys::HtmlInputElement = e.target_unchecked_into(); + if let Ok(top_p_input) = f64::from_str(&input.value()) { + *top_p.borrow_mut() = top_p_input + } + Msg::Refresh + }); let prompt = self.prompt.clone(); let oninput_prompt = ctx.link().callback(move |e: yew::InputEvent| { let input: web_sys::HtmlInputElement = e.target_unchecked_into(); @@ -201,9 +212,13 @@ impl Component for App {

{"temperature \u{00a0} "} - + {format!(" \u{00a0} {}", self.temperature.borrow())}
+ {"top_p \u{00a0} "} + + {format!(" \u{00a0} {}", self.top_p.borrow())} +
{"prompt: "}
{ diff --git a/candle-wasm-examples/llama2-c/src/bin/m.rs b/candle-wasm-examples/llama2-c/src/bin/m.rs index 6628ab7e..61de9d7f 100644 --- a/candle-wasm-examples/llama2-c/src/bin/m.rs +++ b/candle-wasm-examples/llama2-c/src/bin/m.rs @@ -47,7 +47,7 @@ impl Model { tokenizer, model: weights, }); - let logits_processor = LogitsProcessor::new(299792458, None); + let logits_processor = LogitsProcessor::new(299792458, None, None); match model { Ok(inner) => Ok(Self { inner, @@ -69,6 +69,7 @@ impl Model { &mut self, prompt: String, temp: f64, + top_p: f64, repeat_penalty: f32, seed: u64, ) -> Result { @@ -80,7 +81,12 @@ impl Model { } } let temp = if temp <= 0. { None } else { Some(temp) }; - self.logits_processor = LogitsProcessor::new(seed, temp); + let top_p = if top_p <= 0. || top_p >= 1. { + None + } else { + Some(top_p) + }; + self.logits_processor = LogitsProcessor::new(seed, temp, top_p); self.repeat_penalty = repeat_penalty; self.tokens.clear(); let tokens = self diff --git a/candle-wasm-examples/llama2-c/src/worker.rs b/candle-wasm-examples/llama2-c/src/worker.rs index 7e97b5da..79dd2f32 100644 --- a/candle-wasm-examples/llama2-c/src/worker.rs +++ b/candle-wasm-examples/llama2-c/src/worker.rs @@ -62,12 +62,18 @@ impl Model { link: &WorkerLink, id: HandlerId, temp: f64, + top_p: f64, prompt: String, ) -> Result<()> { let dev = Device::Cpu; let temp = if temp <= 0. { None } else { Some(temp) }; - console_log!("{temp:?} {prompt}"); - let mut logits_processor = LogitsProcessor::new(299792458, temp); + let top_p = if top_p <= 0. || top_p >= 1.0 { + None + } else { + Some(top_p) + }; + console_log!("temp: {temp:?} top_p: {top_p:?} prompt: {prompt}"); + let mut logits_processor = LogitsProcessor::new(299792458, temp, top_p); let mut index_pos = 0; let mut tokens = self .tokenizer @@ -268,7 +274,7 @@ pub struct Worker { #[derive(Serialize, Deserialize)] pub enum WorkerInput { ModelData(ModelData), - Run(f64, String), + Run(f64, f64, String), } #[derive(Serialize, Deserialize)] @@ -301,7 +307,7 @@ impl yew_agent::Worker for Worker { } Err(err) => Err(format!("model creation error {err:?}")), }, - WorkerInput::Run(temp, prompt) => match &mut self.model { + WorkerInput::Run(temp, top_p, prompt) => match &mut self.model { None => Err("model has not been set yet".to_string()), Some(model) => { { @@ -311,7 +317,7 @@ impl yew_agent::Worker for Worker { } } let result = model - .run(&self.link, id, temp, prompt) + .run(&self.link, id, temp, top_p, prompt) .map_err(|e| e.to_string()); Ok(WorkerOutput::GenerationDone(result)) } From e82fcf1c594b54c105f1a3979a09f3d2e044a2e0 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 12 Sep 2023 18:21:24 +0200 Subject: [PATCH 078/150] Add more example readmes. (#828) * Add more readmes. * Add a readme for dinov2. * Add some skeleton files for a couple more examples. * More whisper details. --- candle-examples/examples/bert/README.md | 44 ++++++++++++++++++++ candle-examples/examples/bigcode/README.md | 7 ++++ candle-examples/examples/dinov2/README.md | 19 +++++++++ candle-examples/examples/falcon/README.md | 3 ++ candle-examples/examples/quantized/README.md | 2 +- candle-examples/examples/whisper/README.md | 39 +++++++++++++++++ 6 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 candle-examples/examples/bert/README.md create mode 100644 candle-examples/examples/bigcode/README.md create mode 100644 candle-examples/examples/dinov2/README.md create mode 100644 candle-examples/examples/falcon/README.md create mode 100644 candle-examples/examples/whisper/README.md diff --git a/candle-examples/examples/bert/README.md b/candle-examples/examples/bert/README.md new file mode 100644 index 00000000..82ca5f40 --- /dev/null +++ b/candle-examples/examples/bert/README.md @@ -0,0 +1,44 @@ +# candle-bert + +Bert is a general large language model. In this example it can be used for two +different tasks: +- Compute sentence embeddings for a prompt. +- Compute similarities between a set of sentences. + + +## Sentence embeddings + +Bert is used to compute the sentence embeddings for a prompt. The model weights +are downloaded from the hub on the first run. + +```bash +cargo run --example bert --release -- --prompt "Here is a test sentence" + +> [[[ 0.0798, -0.0665, -0.0247, ..., -0.1082, -0.1000, -0.2751], +> [ 0.4218, 0.2690, 0.2740, ..., 0.3889, 1.3503, 0.9908], +> [ 0.0466, 0.3041, -0.1143, ..., 0.4427, 0.6926, -0.1515], +> ... +> [ 0.3396, 0.4320, -0.4408, ..., 0.9212, 0.2331, -0.6777], +> [ 0.2789, 0.7539, 0.4306, ..., -0.0095, 0.3375, -1.7529], +> [ 0.6737, 0.7882, 0.0548, ..., 0.1836, 0.7299, -0.6617]]] +> Tensor[[1, 7, 384], f32] +``` + +## Similarities + +In this example, Bert is used to compute the sentence embeddings for a set of +sentences (hardcoded in the examples). Then cosine similarities are computed for +each sentence pair and they are reported by decreasing values, hence the first +reported pair contains the two sentences that have the highest similarity score. +The sentence embeddings are computed using average pooling through all the +sentence tokens, including some potential padding. + +```bash +cargo run --example bert --release + +> score: 0.85 'The new movie is awesome' 'The new movie is so great' +> score: 0.61 'The cat sits outside' 'The cat plays in the garden' +> score: 0.52 'I love pasta' 'Do you like pizza?' +> score: 0.23 'The new movie is awesome' 'Do you like pizza?' +> score: 0.22 'I love pasta' 'The new movie is awesome' +``` diff --git a/candle-examples/examples/bigcode/README.md b/candle-examples/examples/bigcode/README.md new file mode 100644 index 00000000..0b593674 --- /dev/null +++ b/candle-examples/examples/bigcode/README.md @@ -0,0 +1,7 @@ +# candle-starcoder: code generation model + +StarCoder/BigCode is a LLM model specialized to code generation. + +```bash +cargo run --example bigcode --release -- --prompt "fn fact(n: u64) -> u64 " +``` diff --git a/candle-examples/examples/dinov2/README.md b/candle-examples/examples/dinov2/README.md new file mode 100644 index 00000000..10d4ac1f --- /dev/null +++ b/candle-examples/examples/dinov2/README.md @@ -0,0 +1,19 @@ +# candle-dinov2 + +[DINOv2](https://github.com/facebookresearch/dinov2) is a computer vision model. +In this example, it is used as an ImageNet classifier: the model returns the +probability for the image to belong to each of the 1000 ImageNet categories. + +## Running some example + +```bash +cargo run --example dinov2 --release -- --image candle-examples/examples/yolo-v8/assets/bike.jpg + +> mountain bike, all-terrain bike, off-roader: 43.67% +> bicycle-built-for-two, tandem bicycle, tandem: 33.20% +> crash helmet : 13.23% +> unicycle, monocycle : 2.44% +> maillot : 2.42% +``` + +![Leading group, Giro d'Italia 2021](../yolo-v8/assets/bike.jpg) diff --git a/candle-examples/examples/falcon/README.md b/candle-examples/examples/falcon/README.md new file mode 100644 index 00000000..267c78c2 --- /dev/null +++ b/candle-examples/examples/falcon/README.md @@ -0,0 +1,3 @@ +# candle-falcon + +Falcon is a general large language model. diff --git a/candle-examples/examples/quantized/README.md b/candle-examples/examples/quantized/README.md index f3159493..ee4f3420 100644 --- a/candle-examples/examples/quantized/README.md +++ b/candle-examples/examples/quantized/README.md @@ -24,7 +24,7 @@ cargo run --example quantized --release -- --prompt "The best thing about coding > The best thing about coding in rust is 1.) that I don’t need to worry about memory leaks, 2.) speed and 3.) my program will compile even on old machines. ``` -### Command-line flags +## Command-line flags Run with `--help` to see all options. diff --git a/candle-examples/examples/whisper/README.md b/candle-examples/examples/whisper/README.md new file mode 100644 index 00000000..124cd182 --- /dev/null +++ b/candle-examples/examples/whisper/README.md @@ -0,0 +1,39 @@ +# candle-whisper: speech recognition + +An implementation of [OpenAI Whisper](https://github.com/openai/whisper) using +candle. Whisper is a general purpose speech recognition model, it can be used to +convert audio files (in the `.wav` format) to text. Supported features include +language detection as well as multilingual speech recognition. + +## Running some example + +If no audio file is passed as input, a [sample +file](https://huggingface.co/datasets/Narsil/candle-examples/resolve/main/samples_jfk.wav) is automatically downloaded +from the hub. + +```bash + cargo run --example whisper --release + +> No audio file submitted: Downloading https://huggingface.co/datasets/Narsil/candle_demo/blob/main/samples_jfk.wav +> loaded wav data: Header { audio_format: 1, channel_count: 1, sampling_rate: 16000, bytes_per_second: 32000, bytes_per_sample: 2, bits_per_sample: 16 } +> pcm data loaded 176000 +> loaded mel: [1, 80, 3000] +> 0.0s -- 30.0s: And so my fellow Americans ask not what your country can do for you ask what you can do for your country + ``` + + In order to use the multilingual mode, specify a multilingual model via the + `--model` flag, see the details below. + +## Command line flags + +- `--input`: the audio file to be converted to text, in wav format. +- `--language`: force the language to some specific value rather than being + detected, e.g. `en`. +- `--task`: the task to be performed, can be `transcribe` (return the text data + in the original language) or `translate` (translate the text to English). +- `--timestamps`: enable the timestamp mode where some timestamps are reported + for each recognized audio extracts. +- `--model`: the model to be used. Models that do not end with `-en` are + multilingual models, other ones are English only models. The supported models + are `tiny`, `tiny.en`, `base`, `base.en`, `small`, `small.en`, `medium`, + `medium.en`, `large`, and `large-v2`. From 9daa6dbe87a6cb11496941acb4d7d5fb785183f8 Mon Sep 17 00:00:00 2001 From: Juarez Bochi Date: Tue, 12 Sep 2023 23:14:05 -0700 Subject: [PATCH 079/150] Extract T5 module and add main function to use it (#829) * Extract t5 out of musicgen * Add main for t5 module --- candle-examples/examples/musicgen/main.rs | 1 - .../examples/musicgen/musicgen_model.rs | 11 +- candle-examples/examples/t5/README.md | 17 +++ candle-examples/examples/t5/main.rs | 134 ++++++++++++++++++ candle-nn/Cargo.toml | 1 + candle-nn/src/activation.rs | 5 +- candle-transformers/src/models/mod.rs | 1 + .../src/models/t5.rs | 35 +++-- 8 files changed, 184 insertions(+), 21 deletions(-) create mode 100644 candle-examples/examples/t5/README.md create mode 100644 candle-examples/examples/t5/main.rs rename candle-examples/examples/musicgen/t5_model.rs => candle-transformers/src/models/t5.rs (94%) diff --git a/candle-examples/examples/musicgen/main.rs b/candle-examples/examples/musicgen/main.rs index 3794c22d..df8c3135 100644 --- a/candle-examples/examples/musicgen/main.rs +++ b/candle-examples/examples/musicgen/main.rs @@ -13,7 +13,6 @@ extern crate accelerate_src; mod encodec_model; mod musicgen_model; mod nn; -mod t5_model; use musicgen_model::{GenConfig, MusicgenForConditionalGeneration}; diff --git a/candle-examples/examples/musicgen/musicgen_model.rs b/candle-examples/examples/musicgen/musicgen_model.rs index 7e272fd7..d6d8ae15 100644 --- a/candle-examples/examples/musicgen/musicgen_model.rs +++ b/candle-examples/examples/musicgen/musicgen_model.rs @@ -1,9 +1,10 @@ -use crate::{encodec_model, t5_model}; +use crate::encodec_model; use candle::{DType, Device, Result, Tensor, D}; use candle_nn::{ embedding, layer_norm, linear_no_bias, Activation, Embedding, LayerNorm, Linear, Module, VarBuilder, }; +use candle_transformers::models::t5; // https://github.com/huggingface/transformers/blob/cd4584e3c809bb9e1392ccd3fe38b40daba5519a/src/transformers/models/musicgen/configuration_musicgen.py#L83 #[derive(Debug, Clone, PartialEq)] @@ -370,7 +371,7 @@ impl MusicgenForCausalLM { #[derive(Debug)] pub struct MusicgenForConditionalGeneration { - pub text_encoder: crate::t5_model::T5EncoderModel, + pub text_encoder: t5::T5EncoderModel, pub audio_encoder: crate::encodec_model::EncodecModel, pub decoder: MusicgenForCausalLM, cfg: GenConfig, @@ -379,7 +380,7 @@ pub struct MusicgenForConditionalGeneration { #[derive(Debug, Clone, PartialEq)] pub struct GenConfig { musicgen: Config, - t5: crate::t5_model::Config, + t5: t5::Config, encodec: crate::encodec_model::Config, } @@ -387,7 +388,7 @@ impl GenConfig { pub fn small() -> Self { Self { musicgen: Config::musicgen_small(), - t5: t5_model::Config::musicgen_small(), + t5: t5::Config::musicgen_small(), encodec: encodec_model::Config::musicgen_small(), } } @@ -399,7 +400,7 @@ impl MusicgenForConditionalGeneration { } pub fn load(vb: VarBuilder, cfg: GenConfig) -> Result { - let text_encoder = t5_model::T5EncoderModel::load(vb.pp("text_encoder"), &cfg.t5)?; + let text_encoder = t5::T5EncoderModel::load(vb.pp("text_encoder"), &cfg.t5)?; let audio_encoder = encodec_model::EncodecModel::load(vb.pp("audio_encoder"), &cfg.encodec)?; let decoder = MusicgenForCausalLM::load(vb.pp("decoder"), &cfg.musicgen)?; diff --git a/candle-examples/examples/t5/README.md b/candle-examples/examples/t5/README.md new file mode 100644 index 00000000..66952395 --- /dev/null +++ b/candle-examples/examples/t5/README.md @@ -0,0 +1,17 @@ +# candle-t5 + +Generates embeddings using a T5 model. It doesn't support generation yet. + +```bash +$ cargo run --example t5 -- --model-id t5-large --prompt 'how tall is obama' --n 1 +Loaded and encoded 2.014244792s +[[[-0.3174, -0.1462, 0.0065, ..., -0.0579, -0.0581, 0.1387], + [-0.2905, -0.1945, -0.0685, ..., -0.2457, -0.5137, -0.1760], + [-0.0591, -0.0213, -0.0241, ..., -0.0210, 0.0491, -0.0300], + ... + [-0.4333, 0.0027, -0.0609, ..., 0.3069, -0.2252, 0.3306], + [-0.1458, 0.1323, -0.0138, ..., 0.3000, -0.4550, -0.0384], + [ 0.0397, 0.0485, -0.2373, ..., 0.2578, -0.2650, -0.4356]]] +Tensor[[1, 9, 1024], f32] +Took 2.1363425s +``` \ No newline at end of file diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs new file mode 100644 index 00000000..bcba846d --- /dev/null +++ b/candle-examples/examples/t5/main.rs @@ -0,0 +1,134 @@ +#[cfg(feature = "mkl")] +extern crate intel_mkl_src; + +#[cfg(feature = "accelerate")] +extern crate accelerate_src; +use candle_transformers::models::t5; + +use anyhow::{anyhow, Error as E, Result}; +use candle::{DType, Tensor}; +use candle_nn::VarBuilder; +use clap::Parser; +use hf_hub::{api::sync::Api, Cache, Repo, RepoType}; +use tokenizers::Tokenizer; + +const DTYPE: DType = DType::F32; +const DEFAULT_PROMPT: &str = "Translate English to German: That is good."; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Run on CPU rather than on GPU. + #[arg(long)] + cpu: bool, + + /// Run offline (you must have the files already cached) + #[arg(long)] + offline: bool, + + /// Enable tracing (generates a trace-timestamp.json file). + #[arg(long)] + tracing: bool, + + /// The model to use, check out available models: https://huggingface.co/models?library=sentence-transformers&sort=trending + #[arg(long)] + model_id: Option, + + #[arg(long)] + revision: Option, + + /// Compute embeddings for this prompt or use the DEFAULT_PROMPT. + #[arg(long)] + prompt: Option, + + /// The number of times to run the prompt. + #[arg(long, default_value = "1")] + n: usize, +} + +impl Args { + fn build_model_and_tokenizer(&self) -> Result<(t5::T5EncoderModel, Tokenizer)> { + let device = candle_examples::device(self.cpu)?; + let default_model = "t5-small".to_string(); + let default_revision = "refs/pr/15".to_string(); + let (model_id, revision) = match (self.model_id.to_owned(), self.revision.to_owned()) { + (Some(model_id), Some(revision)) => (model_id, revision), + (Some(model_id), None) => (model_id, "main".to_string()), + (None, Some(revision)) => (default_model, revision), + (None, None) => (default_model, default_revision), + }; + + let repo = Repo::with_revision(model_id, RepoType::Model, revision); + let (config_filename, tokenizer_filename, weights_filename) = if self.offline { + let cache = Cache::default().repo(repo); + ( + cache + .get("config.json") + .ok_or(anyhow!("Missing config file in cache"))?, + cache + .get("tokenizer.json") + .ok_or(anyhow!("Missing tokenizer file in cache"))?, + cache + .get("model.safetensors") + .ok_or(anyhow!("Missing weights file in cache"))?, + ) + } else { + let api = Api::new()?; + let api = api.repo(repo); + ( + api.get("config.json")?, + api.get("tokenizer.json")?, + api.get("model.safetensors")?, + ) + }; + let config = std::fs::read_to_string(config_filename)?; + let config: t5::Config = serde_json::from_str(&config)?; + let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?; + + let weights = unsafe { candle::safetensors::MmapedFile::new(weights_filename)? }; + let weights = weights.deserialize()?; + let vb = VarBuilder::from_safetensors(vec![weights], DTYPE, &device); + let model = t5::T5EncoderModel::load(vb, &config)?; + Ok((model, tokenizer)) + } +} + +fn main() -> Result<()> { + use tracing_chrome::ChromeLayerBuilder; + use tracing_subscriber::prelude::*; + + let args = Args::parse(); + let _guard = if args.tracing { + println!("tracing..."); + let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); + tracing_subscriber::registry().with(chrome_layer).init(); + Some(guard) + } else { + None + }; + let start = std::time::Instant::now(); + + let (model, mut tokenizer) = args.build_model_and_tokenizer()?; + let device = &model.device; + let prompt = args.prompt.unwrap_or_else(|| DEFAULT_PROMPT.to_string()); + let tokenizer = tokenizer + .with_padding(None) + .with_truncation(None) + .map_err(E::msg)?; + let tokens = tokenizer + .encode(prompt, true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + let token_ids = Tensor::new(&tokens[..], device)?.unsqueeze(0)?; + println!("Loaded and encoded {:?}", start.elapsed()); + for idx in 0..args.n { + let start = std::time::Instant::now(); + let ys = model.forward(&token_ids)?; + if idx == 0 { + println!("{ys}"); + } + println!("Took {:?}", start.elapsed()); + } + Ok(()) +} diff --git a/candle-nn/Cargo.toml b/candle-nn/Cargo.toml index 5062a717..2a8ec8ce 100644 --- a/candle-nn/Cargo.toml +++ b/candle-nn/Cargo.toml @@ -18,6 +18,7 @@ intel-mkl-src = { workspace = true, optional = true } num-traits = { workspace = true } rayon = { workspace = true } safetensors = { workspace = true } +serde = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/candle-nn/src/activation.rs b/candle-nn/src/activation.rs index 0db3edc9..6e442458 100644 --- a/candle-nn/src/activation.rs +++ b/candle-nn/src/activation.rs @@ -1,7 +1,10 @@ use candle::Tensor; +use serde::Deserialize; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Default)] +#[serde(rename_all = "lowercase")] pub enum Activation { + #[default] Gelu, Relu, Elu(f64), diff --git a/candle-transformers/src/models/mod.rs b/candle-transformers/src/models/mod.rs index f33d01e6..e2e0bf81 100644 --- a/candle-transformers/src/models/mod.rs +++ b/candle-transformers/src/models/mod.rs @@ -7,4 +7,5 @@ pub mod llama; pub mod quantized_llama; pub mod segment_anything; pub mod stable_diffusion; +pub mod t5; pub mod whisper; diff --git a/candle-examples/examples/musicgen/t5_model.rs b/candle-transformers/src/models/t5.rs similarity index 94% rename from candle-examples/examples/musicgen/t5_model.rs rename to candle-transformers/src/models/t5.rs index 22f0a4f5..1454b7cc 100644 --- a/candle-examples/examples/musicgen/t5_model.rs +++ b/candle-transformers/src/models/t5.rs @@ -1,11 +1,12 @@ // T5 Text Encoder // https://github.com/huggingface/transformers/blob/main/src/transformers/models/t5/modeling_t5.py -use candle::{DType, Result, Tensor, D}; +use candle::{DType, Device, Result, Tensor, D}; use candle_nn::{embedding, linear_no_bias, Activation, Embedding, Linear, Module, VarBuilder}; +use serde::Deserialize; use std::sync::Arc; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Config { vocab_size: usize, d_model: usize, @@ -15,14 +16,15 @@ pub struct Config { num_decoder_layers: Option, num_heads: usize, relative_attention_num_buckets: usize, - relative_attention_max_distance: usize, + relative_attention_max_distance: Option, dropout_rate: f64, layer_norm_epsilon: f64, initializer_factor: f64, + #[serde(default)] feed_forward_proj: Activation, - is_decoder: bool, + is_decoder: Option, is_encoder_decoder: bool, - use_cache: bool, + use_cache: Option, pad_token_id: usize, eos_token_id: usize, } @@ -38,14 +40,14 @@ impl Default for Config { num_decoder_layers: None, num_heads: 8, relative_attention_num_buckets: 32, - relative_attention_max_distance: 128, + relative_attention_max_distance: Some(128), dropout_rate: 0.1, layer_norm_epsilon: 1e-6, initializer_factor: 1.0, feed_forward_proj: Activation::Relu, - is_decoder: false, + is_decoder: Some(false), is_encoder_decoder: true, - use_cache: true, + use_cache: Some(true), pad_token_id: 0, eos_token_id: 1, } @@ -63,16 +65,16 @@ impl Config { eos_token_id: 1, feed_forward_proj: Activation::Relu, initializer_factor: 1.0, - is_decoder: false, + is_decoder: Some(false), is_encoder_decoder: true, layer_norm_epsilon: 1e-6, num_decoder_layers: Some(12), num_heads: 12, num_layers: 12, pad_token_id: 0, - relative_attention_max_distance: 128, + relative_attention_max_distance: Some(128), relative_attention_num_buckets: 32, - use_cache: true, + use_cache: Some(true), vocab_size: 32128, } } @@ -197,7 +199,7 @@ impl T5Attention { d_kv: cfg.d_kv, relative_attention_bias, relative_attention_num_buckets: cfg.relative_attention_num_buckets, - relative_attention_max_distance: cfg.relative_attention_max_distance, + relative_attention_max_distance: cfg.relative_attention_max_distance.unwrap_or(128), inner_dim, }) } @@ -343,7 +345,7 @@ impl T5Block { fn load(has_relative_attention_bias: bool, vb: VarBuilder, cfg: &Config) -> Result { let vb = vb.pp("layer"); let self_attn = T5LayerSelfAttention::load(has_relative_attention_bias, vb.pp("0"), cfg)?; - let cross_attn = if cfg.is_decoder { + let cross_attn = if cfg.is_decoder.unwrap_or(false) { Some(T5LayerCrossAttention::load(vb.pp("1"), cfg)?) } else { None @@ -417,6 +419,7 @@ impl T5Stack { pub struct T5EncoderModel { shared: Arc, encoder: T5Stack, + pub device: Device, } impl T5EncoderModel { @@ -424,7 +427,11 @@ impl T5EncoderModel { let shared = embedding(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; let shared = Arc::new(shared); let encoder = T5Stack::load(vb.pp("encoder"), &shared, cfg)?; - Ok(Self { shared, encoder }) + Ok(Self { + shared, + encoder, + device: vb.device().clone(), + }) } pub fn forward(&self, input_ids: &Tensor) -> Result { From d801e1d564c5a6560680ff085e31dc4322627542 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 08:16:20 +0200 Subject: [PATCH 080/150] Clippy fix. (#830) --- candle-transformers/src/models/t5.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index 1454b7cc..3700f1e0 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -417,7 +417,6 @@ impl T5Stack { #[derive(Debug)] pub struct T5EncoderModel { - shared: Arc, encoder: T5Stack, pub device: Device, } @@ -428,7 +427,6 @@ impl T5EncoderModel { let shared = Arc::new(shared); let encoder = T5Stack::load(vb.pp("encoder"), &shared, cfg)?; Ok(Self { - shared, encoder, device: vb.device().clone(), }) From e4553fb355ffebe6781ea2d35ba0734a310cab9b Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 08:37:04 +0200 Subject: [PATCH 081/150] T5 tweaks (#831) * Use default values rather than options. * Avoid exposing the device field. * More tweaks. --- candle-examples/examples/t5/main.rs | 16 ++------- candle-transformers/src/models/t5.rs | 51 ++++++++++++++++++---------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index bcba846d..84be0204 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -30,7 +30,7 @@ struct Args { #[arg(long)] tracing: bool, - /// The model to use, check out available models: https://huggingface.co/models?library=sentence-transformers&sort=trending + /// The model repository to use on the HuggingFace hub. #[arg(long)] model_id: Option, @@ -94,22 +94,10 @@ impl Args { } fn main() -> Result<()> { - use tracing_chrome::ChromeLayerBuilder; - use tracing_subscriber::prelude::*; - let args = Args::parse(); - let _guard = if args.tracing { - println!("tracing..."); - let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); - tracing_subscriber::registry().with(chrome_layer).init(); - Some(guard) - } else { - None - }; let start = std::time::Instant::now(); let (model, mut tokenizer) = args.build_model_and_tokenizer()?; - let device = &model.device; let prompt = args.prompt.unwrap_or_else(|| DEFAULT_PROMPT.to_string()); let tokenizer = tokenizer .with_padding(None) @@ -120,7 +108,7 @@ fn main() -> Result<()> { .map_err(E::msg)? .get_ids() .to_vec(); - let token_ids = Tensor::new(&tokens[..], device)?.unsqueeze(0)?; + let token_ids = Tensor::new(&tokens[..], model.device())?.unsqueeze(0)?; println!("Loaded and encoded {:?}", start.elapsed()); for idx in 0..args.n { let start = std::time::Instant::now(); diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index 3700f1e0..691817d1 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -6,6 +6,18 @@ use candle_nn::{embedding, linear_no_bias, Activation, Embedding, Linear, Module use serde::Deserialize; use std::sync::Arc; +fn default_relative_attention_max_distance() -> usize { + 128 +} + +fn default_is_decoder() -> bool { + false +} + +fn default_use_cache() -> bool { + true +} + #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Config { vocab_size: usize, @@ -16,15 +28,18 @@ pub struct Config { num_decoder_layers: Option, num_heads: usize, relative_attention_num_buckets: usize, - relative_attention_max_distance: Option, + #[serde(default = "default_relative_attention_max_distance")] + relative_attention_max_distance: usize, dropout_rate: f64, layer_norm_epsilon: f64, initializer_factor: f64, #[serde(default)] feed_forward_proj: Activation, - is_decoder: Option, + #[serde(default = "default_is_decoder")] + is_decoder: bool, is_encoder_decoder: bool, - use_cache: Option, + #[serde(default = "default_use_cache")] + use_cache: bool, pad_token_id: usize, eos_token_id: usize, } @@ -40,14 +55,14 @@ impl Default for Config { num_decoder_layers: None, num_heads: 8, relative_attention_num_buckets: 32, - relative_attention_max_distance: Some(128), + relative_attention_max_distance: 128, dropout_rate: 0.1, layer_norm_epsilon: 1e-6, initializer_factor: 1.0, feed_forward_proj: Activation::Relu, - is_decoder: Some(false), + is_decoder: false, is_encoder_decoder: true, - use_cache: Some(true), + use_cache: true, pad_token_id: 0, eos_token_id: 1, } @@ -65,16 +80,16 @@ impl Config { eos_token_id: 1, feed_forward_proj: Activation::Relu, initializer_factor: 1.0, - is_decoder: Some(false), + is_decoder: false, is_encoder_decoder: true, layer_norm_epsilon: 1e-6, num_decoder_layers: Some(12), num_heads: 12, num_layers: 12, pad_token_id: 0, - relative_attention_max_distance: Some(128), + relative_attention_max_distance: 128, relative_attention_num_buckets: 32, - use_cache: Some(true), + use_cache: true, vocab_size: 32128, } } @@ -199,7 +214,7 @@ impl T5Attention { d_kv: cfg.d_kv, relative_attention_bias, relative_attention_num_buckets: cfg.relative_attention_num_buckets, - relative_attention_max_distance: cfg.relative_attention_max_distance.unwrap_or(128), + relative_attention_max_distance: cfg.relative_attention_max_distance, inner_dim, }) } @@ -345,7 +360,7 @@ impl T5Block { fn load(has_relative_attention_bias: bool, vb: VarBuilder, cfg: &Config) -> Result { let vb = vb.pp("layer"); let self_attn = T5LayerSelfAttention::load(has_relative_attention_bias, vb.pp("0"), cfg)?; - let cross_attn = if cfg.is_decoder.unwrap_or(false) { + let cross_attn = if cfg.is_decoder { Some(T5LayerCrossAttention::load(vb.pp("1"), cfg)?) } else { None @@ -402,23 +417,20 @@ impl T5Stack { fn forward(&self, input_ids: &Tensor) -> Result { let input_embeds = self.shared.as_ref().forward(input_ids)?; - let (_b_sz, _seq_len) = (input_embeds.dim(0)?, input_embeds.dim(1)?); - let mut hidden_states = input_embeds; let mut position_bias = None; for block in self.block.iter() { (hidden_states, position_bias) = block.forward(&hidden_states, position_bias.as_ref())? } - let hidden_states = self.final_layer_norm.forward(&hidden_states)?; - Ok(hidden_states) + self.final_layer_norm.forward(&hidden_states) } } #[derive(Debug)] pub struct T5EncoderModel { encoder: T5Stack, - pub device: Device, + device: Device, } impl T5EncoderModel { @@ -433,7 +445,10 @@ impl T5EncoderModel { } pub fn forward(&self, input_ids: &Tensor) -> Result { - let encoder_outputs = self.encoder.forward(input_ids)?; - Ok(encoder_outputs) + self.encoder.forward(input_ids) + } + + pub fn device(&self) -> &Device { + &self.device } } From 18d3c803a8d2b80a14e7e4f33c47a92c4a35fa6d Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 09:24:58 +0200 Subject: [PATCH 082/150] Scalar support in minimum/maximum. (#832) * Scalar support in minimum/maximum. * Add a clamp method to tensors. --- candle-core/src/tensor.rs | 31 +++++++++++++++++++++++++++++-- candle-core/tests/tensor_tests.rs | 12 ++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/candle-core/src/tensor.rs b/candle-core/src/tensor.rs index 8ad9322b..59a23c39 100644 --- a/candle-core/src/tensor.rs +++ b/candle-core/src/tensor.rs @@ -105,6 +105,28 @@ macro_rules! binary_op { }; } +macro_rules! binary_op_scalar { + ($fn_name:ident, $op_name:ident) => { + pub fn $fn_name(&self, rhs: T) -> Result { + let rhs = match rhs.to_tensor_scalar()? { + crate::scalar::TensorScalar::Tensor(rhs) => rhs, + crate::scalar::TensorScalar::Scalar(rhs) => rhs + .to_dtype(self.dtype())? + .to_device(self.device())? + .broadcast_as(self.shape())?, + }; + let shape = self.same_shape_binary_op(&rhs, stringify!($fn_name))?; + let storage = self.storage().binary_impl::( + &*rhs.storage(), + self.layout(), + rhs.layout(), + )?; + let op = BackpropOp::new2(self, &rhs, |t1, t2| Op::Binary(t1, t2, BinaryOp::$op_name)); + Ok(from_storage(storage, shape.clone(), op, false)) + } + }; +} + macro_rules! broadcast_binary_op { ($fn_name:ident, $inner_fn_name:ident) => { pub fn $fn_name(&self, rhs: &Self) -> Result { @@ -447,8 +469,8 @@ impl Tensor { binary_op!(mul, Mul); binary_op!(sub, Sub); binary_op!(div, Div); - binary_op!(maximum, Maximum); - binary_op!(minimum, Minimum); + binary_op_scalar!(maximum, Maximum); + binary_op_scalar!(minimum, Minimum); broadcast_binary_op!(broadcast_add, add); broadcast_binary_op!(broadcast_mul, mul); broadcast_binary_op!(broadcast_sub, sub); @@ -827,6 +849,11 @@ impl Tensor { self.cmp(rhs, CmpOp::Le) } + /// Clamp the tensor values to be between `min` and `max`. + pub fn clamp(&self, min: T1, max: T2) -> Result { + self.maximum(min)?.minimum(max) + } + /// Upsample the input tensor to the `(target_h, target_w)` size, taking the value of the /// nearest element. /// diff --git a/candle-core/tests/tensor_tests.rs b/candle-core/tests/tensor_tests.rs index cd68908f..f1c204ea 100644 --- a/candle-core/tests/tensor_tests.rs +++ b/candle-core/tests/tensor_tests.rs @@ -33,6 +33,17 @@ fn tensor_2d(device: &Device) -> Result<()> { Ok(()) } +fn clamp(device: &Device) -> Result<()> { + let data = &[[3f32, 1., 4., 1., 5.], [2., 1., 7., 8., 2.]]; + let tensor = Tensor::new(data, device)?; + let tensor = tensor.clamp(1.5, 6.2)?; + assert_eq!( + tensor.to_vec2::()?, + [[3.0, 1.5, 4.0, 1.5, 5.0], [2.0, 1.5, 6.2, 6.2, 2.0]], + ); + Ok(()) +} + fn binary_op(device: &Device) -> Result<()> { let data = &[[3f32, 1., 4., 1., 5.], [2., 1., 7., 8., 2.]]; let tensor1 = Tensor::new(data, device)?; @@ -908,6 +919,7 @@ test_device!(index_add, index_add_cpu, index_add_gpu); test_device!(gather, gather_cpu, gather_gpu); test_device!(scatter_add, scatter_add_cpu, scatter_add_gpu); test_device!(randn, randn_cpu, randn_gpu); +test_device!(clamp, clamp_cpu, clamp_gpu); // There was originally a bug on the CPU implementation for randn // https://github.com/huggingface/candle/issues/381 From cbd36157acb9852b11ce94ab15317c2a1b2bd6c4 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 09:43:52 +0200 Subject: [PATCH 083/150] Add a gif to the quantized readme. (#833) * Add a gif to the quantized readme. * gif update. --- candle-examples/examples/quantized/README.md | 2 ++ .../examples/quantized/assets/aoc.gif | Bin 0 -> 121923 bytes 2 files changed, 2 insertions(+) create mode 100644 candle-examples/examples/quantized/assets/aoc.gif diff --git a/candle-examples/examples/quantized/README.md b/candle-examples/examples/quantized/README.md index ee4f3420..bed09243 100644 --- a/candle-examples/examples/quantized/README.md +++ b/candle-examples/examples/quantized/README.md @@ -12,6 +12,8 @@ The weights are automatically downloaded for you from the [HuggingFace Hub](https://huggingface.co/) on the first run. There are various command line flags to use local files instead, run with `--help` to learn about them. +![Axiom of Choice](./assets/aoc.gif) + ## Running some example. ```bash diff --git a/candle-examples/examples/quantized/assets/aoc.gif b/candle-examples/examples/quantized/assets/aoc.gif new file mode 100644 index 0000000000000000000000000000000000000000..686074af5b538f6e8dac45b7d613bfd64107e92e GIT binary patch literal 121923 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 _3 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 literal 0 HcmV?d00001 From e6f040d6e36b29ad8ff643981e632238c0f78298 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 10:05:47 +0200 Subject: [PATCH 084/150] Readme gallery (#834) * More readme tweaks. * Update README.md --- README.md | 46 +++++++++++++--------- candle-examples/examples/bigcode/README.md | 14 ++++++- candle-examples/examples/yolo-v8/README.md | 2 +- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c3e35d33..533e7492 100644 --- a/README.md +++ b/README.md @@ -45,40 +45,48 @@ For more advanced examples, please have a look at the following section. ## Check out our examples -Check out our [examples](./candle-examples/examples/): +These online demos run entirely in your browser: +- [yolo](https://huggingface.co/spaces/lmz/candle-yolo): pose estimation and + object recognition. +- [whisper](https://huggingface.co/spaces/lmz/candle-whisper): text to speech. +- [LLaMA2](https://huggingface.co/spaces/lmz/candle-llama2): text generation. + +We also provide a some command line based examples using state of the art models: -- [Whisper](./candle-examples/examples/whisper/): speech recognition model. - [LLaMA and LLaMA-v2](./candle-examples/examples/llama/): general LLM. - [Falcon](./candle-examples/examples/falcon/): general LLM. -- [Bert](./candle-examples/examples/bert/): useful for sentence embeddings. - [StarCoder](./candle-examples/examples/bigcode/): LLM specialized to code generation. -- [Stable Diffusion](./candle-examples/examples/stable-diffusion/): text to - image generative model, support for the 1.5, 2.1, and SDXL 1.0 versions. -- [DINOv2](./candle-examples/examples/dinov2/): computer vision model trained - using self-supervision (can be used for imagenet classification, depth - evaluation, segmentation). - [Quantized LLaMA](./candle-examples/examples/quantized/): quantized version of the LLaMA model using the same quantization techniques as [llama.cpp](https://github.com/ggerganov/llama.cpp). + + + +- [Stable Diffusion](./candle-examples/examples/stable-diffusion/): text to + image generative model, support for the 1.5, 2.1, and SDXL 1.0 versions. + + + - [yolo-v3](./candle-examples/examples/yolo-v3/) and [yolo-v8](./candle-examples/examples/yolo-v8/): object detection and pose estimation models. + + - [segment-anything](./candle-examples/examples/segment-anything/): image segmentation model with prompt. -Run them using the following commands: + + + +- [Whisper](./candle-examples/examples/whisper/): speech recognition model. +- [Bert](./candle-examples/examples/bert/): useful for sentence embeddings. +- [DINOv2](./candle-examples/examples/dinov2/): computer vision model trained + using self-supervision (can be used for imagenet classification, depth + evaluation, segmentation). + +Run them using commands like: ``` -cargo run --example whisper --release -cargo run --example llama --release -cargo run --example falcon --release -cargo run --example bert --release -cargo run --example bigcode --release -cargo run --example stable-diffusion --release -- --prompt "a rusty robot holding a fire torch" -cargo run --example dinov2 --release -- --image path/to/myinput.jpg cargo run --example quantized --release -cargo run --example yolo-v3 --release -- myimage.jpg -cargo run --example yolo-v8 --release -- myimage.jpg # for pose estimation, add --task pose -cargo run --example segment-anything --release -- --image myimage.jpg ``` In order to use **CUDA** add `--features cuda` to the example command line. If diff --git a/candle-examples/examples/bigcode/README.md b/candle-examples/examples/bigcode/README.md index 0b593674..cb4e79b1 100644 --- a/candle-examples/examples/bigcode/README.md +++ b/candle-examples/examples/bigcode/README.md @@ -1,7 +1,19 @@ # candle-starcoder: code generation model -StarCoder/BigCode is a LLM model specialized to code generation. +[StarCoder/BigCode](https://huggingface.co/bigcode/starcoderbase-1b) is a LLM +model specialized to code generation. The initial model was trained on 80 +programming languages. + +## Running some example ```bash cargo run --example bigcode --release -- --prompt "fn fact(n: u64) -> u64 " + +> fn fact(n: u64) -> u64 { +> if n == 0 { +> 1 +> } else { +> n * fact(n - 1) +> } +> } ``` diff --git a/candle-examples/examples/yolo-v8/README.md b/candle-examples/examples/yolo-v8/README.md index 8590a8a9..938dea13 100644 --- a/candle-examples/examples/yolo-v8/README.md +++ b/candle-examples/examples/yolo-v8/README.md @@ -13,7 +13,7 @@ Space](https://huggingface.co/spaces/lmz/candle-yolo). The model then fully runs in your browser using WebAssembly - if you use a custom image it will never leave your phone/computer! -## Running some example. +## Running some example ### Object Detection ```bash From 3e9432401206953e83d53d943e0dd06ce72d4ce8 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 11:44:02 +0200 Subject: [PATCH 085/150] Add some sentence similarity part to the t5 example. (#835) * Add some sentence similarity part to the t5 example. * Clippy fix. --- candle-examples/examples/t5/main.rs | 106 ++++++++++++++++++++++----- candle-transformers/src/models/t5.rs | 7 +- 2 files changed, 93 insertions(+), 20 deletions(-) diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index 84be0204..03c861c1 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -13,7 +13,6 @@ use hf_hub::{api::sync::Api, Cache, Repo, RepoType}; use tokenizers::Tokenizer; const DTYPE: DType = DType::F32; -const DEFAULT_PROMPT: &str = "Translate English to German: That is good."; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -37,13 +36,17 @@ struct Args { #[arg(long)] revision: Option, - /// Compute embeddings for this prompt or use the DEFAULT_PROMPT. + /// Compute embeddings for this prompt, otherwise compute sentence similarities. #[arg(long)] prompt: Option, /// The number of times to run the prompt. #[arg(long, default_value = "1")] n: usize, + + /// L2 normalization for embeddings. + #[arg(long, default_value = "true")] + normalize_embeddings: bool, } impl Args { @@ -95,28 +98,95 @@ impl Args { fn main() -> Result<()> { let args = Args::parse(); - let start = std::time::Instant::now(); - let (model, mut tokenizer) = args.build_model_and_tokenizer()?; - let prompt = args.prompt.unwrap_or_else(|| DEFAULT_PROMPT.to_string()); let tokenizer = tokenizer .with_padding(None) .with_truncation(None) .map_err(E::msg)?; - let tokens = tokenizer - .encode(prompt, true) - .map_err(E::msg)? - .get_ids() - .to_vec(); - let token_ids = Tensor::new(&tokens[..], model.device())?.unsqueeze(0)?; - println!("Loaded and encoded {:?}", start.elapsed()); - for idx in 0..args.n { - let start = std::time::Instant::now(); - let ys = model.forward(&token_ids)?; - if idx == 0 { - println!("{ys}"); + match args.prompt { + Some(prompt) => { + let tokens = tokenizer + .encode(prompt, true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + let token_ids = Tensor::new(&tokens[..], model.device())?.unsqueeze(0)?; + for idx in 0..args.n { + let start = std::time::Instant::now(); + let ys = model.forward(&token_ids)?; + if idx == 0 { + println!("{ys}"); + } + println!("Took {:?}", start.elapsed()); + } + } + None => { + let sentences = [ + "The cat sits outside", + "A man is playing guitar", + "I love pasta", + "The new movie is awesome", + "The cat plays in the garden", + "A woman watches TV", + "The new movie is so great", + "Do you like pizza?", + ]; + let n_sentences = sentences.len(); + if let Some(pp) = tokenizer.get_padding_mut() { + pp.strategy = tokenizers::PaddingStrategy::BatchLongest + } else { + let pp = tokenizers::PaddingParams { + strategy: tokenizers::PaddingStrategy::BatchLongest, + ..Default::default() + }; + tokenizer.with_padding(Some(pp)); + } + let tokens = tokenizer + .encode_batch(sentences.to_vec(), true) + .map_err(E::msg)?; + let token_ids = tokens + .iter() + .map(|tokens| { + let tokens = tokens.get_ids().to_vec(); + Ok(Tensor::new(tokens.as_slice(), model.device())?) + }) + .collect::>>()?; + + let token_ids = Tensor::stack(&token_ids, 0)?; + println!("running inference on batch {:?}", token_ids.shape()); + let embeddings = model.forward(&token_ids)?; + println!("generated embeddings {:?}", embeddings.shape()); + // Apply some avg-pooling by taking the mean embedding value for all tokens (including padding) + let (_n_sentence, n_tokens, _hidden_size) = embeddings.dims3()?; + let embeddings = (embeddings.sum(1)? / (n_tokens as f64))?; + let embeddings = if args.normalize_embeddings { + normalize_l2(&embeddings)? + } else { + embeddings + }; + println!("pooled embeddings {:?}", embeddings.shape()); + + let mut similarities = vec![]; + for i in 0..n_sentences { + let e_i = embeddings.get(i)?; + for j in (i + 1)..n_sentences { + let e_j = embeddings.get(j)?; + let sum_ij = (&e_i * &e_j)?.sum_all()?.to_scalar::()?; + let sum_i2 = (&e_i * &e_i)?.sum_all()?.to_scalar::()?; + let sum_j2 = (&e_j * &e_j)?.sum_all()?.to_scalar::()?; + let cosine_similarity = sum_ij / (sum_i2 * sum_j2).sqrt(); + similarities.push((cosine_similarity, i, j)) + } + } + similarities.sort_by(|u, v| v.0.total_cmp(&u.0)); + for &(score, i, j) in similarities[..5].iter() { + println!("score: {score:.2} '{}' '{}'", sentences[i], sentences[j]) + } } - println!("Took {:?}", start.elapsed()); } Ok(()) } + +pub fn normalize_l2(v: &Tensor) -> Result { + Ok(v.broadcast_div(&v.sqr()?.sum_keepdim(1)?.sqrt()?)?) +} diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index 691817d1..325eb752 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -245,7 +245,10 @@ impl T5Attention { let scores = q.matmul(&k.t()?)?; let (scores, position_bias) = match position_bias { - Some(position_bias) => ((scores + position_bias)?, Some(position_bias.clone())), + Some(position_bias) => ( + scores.broadcast_add(position_bias)?, + Some(position_bias.clone()), + ), None => match &self.relative_attention_bias { None => (scores, None), Some(relative_attention_bias) => { @@ -291,7 +294,7 @@ impl T5Attention { .forward(&relative_buckets)? .permute((2, 0, 1))? .unsqueeze(0)?; - ((scores + &position_bias)?, Some(position_bias)) + (scores.broadcast_add(&position_bias)?, Some(position_bias)) // TODO: position_bias_masked? } }, From 1c0916402179f8d5da849065219cfcf657bf2714 Mon Sep 17 00:00:00 2001 From: Charles Lew Date: Wed, 13 Sep 2023 18:39:22 +0800 Subject: [PATCH 086/150] Add `CANDLE_NVCC_CCBIN` support for `candle-kernels`, and eliminate warning. (#836) --- candle-core/src/cuda_backend.rs | 1 + candle-kernels/build.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index 55443068..07c5dfa8 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -12,6 +12,7 @@ use half::{bf16, f16}; use std::sync::{Arc, Mutex}; const USE_IM2COL_CONV1D: bool = true; +#[cfg(not(feature = "cudnn"))] const USE_IM2COL_CONV2D: bool = true; /// cudarc related errors diff --git a/candle-kernels/build.rs b/candle-kernels/build.rs index 3c8e96a9..ad084671 100644 --- a/candle-kernels/build.rs +++ b/candle-kernels/build.rs @@ -164,6 +164,8 @@ mod cuda { println!("cargo:rustc-env=CUDA_COMPUTE_CAP=sm_{compute_cap}"); + let ccbin_env = std::env::var("CANDLE_NVCC_CCBIN"); + println!("cargo:rerun-if-env-changed=CANDLE_NVCC_CCBIN"); let children = kernel_paths .par_iter() .flat_map(|p| { @@ -188,8 +190,13 @@ mod cuda { .args(["--output-directory", &out_dir]) // Flash attention only // .arg("--expt-relaxed-constexpr") - .args(&include_options) - .arg(p); + .args(&include_options); + if let Ok(ccbin_path) = &ccbin_env { + command + .arg("-allow-unsupported-compiler") + .args(["-ccbin", ccbin_path]); + } + command.arg(p); Some((p, command.spawn() .expect("nvcc failed to start. Ensure that you have CUDA installed and that `nvcc` is in your PATH.").wait_with_output())) }}) From b11a2a7b9db802d7b7e833cf25c5b1d86d70937c Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 12:56:53 +0200 Subject: [PATCH 087/150] Move the constant to avoid some unused warning. (#837) --- candle-core/src/cuda_backend.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index 07c5dfa8..4ec41b87 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -11,10 +11,6 @@ use cudarc::driver::{ use half::{bf16, f16}; use std::sync::{Arc, Mutex}; -const USE_IM2COL_CONV1D: bool = true; -#[cfg(not(feature = "cudnn"))] -const USE_IM2COL_CONV2D: bool = true; - /// cudarc related errors #[derive(thiserror::Error, Debug)] pub enum CudaError { @@ -1760,6 +1756,8 @@ impl BackendStorage for CudaStorage { kernel_l: &Layout, params: &crate::conv::ParamsConv1D, ) -> Result { + const USE_IM2COL_CONV1D: bool = true; + let device = self.device().clone(); if !USE_IM2COL_CONV1D { let slice = Conv1D(params).map(&self.slice, l, &kernel.slice, kernel_l, &device)?; @@ -1808,6 +1806,8 @@ impl BackendStorage for CudaStorage { kernel_l: &Layout, params: &crate::conv::ParamsConv2D, ) -> Result { + const USE_IM2COL_CONV2D: bool = true; + let device = self.device().clone(); if !USE_IM2COL_CONV2D { let slice = Conv2D(params).map(&self.slice, l, &kernel.slice, kernel_l, &device)?; From 31ab2ddaebe632f06f647c470259364eb53ca9e8 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 14:00:59 +0200 Subject: [PATCH 088/150] Remove the padding. (#838) --- candle-examples/examples/t5/main.rs | 67 +++++++++++++---------------- 1 file changed, 29 insertions(+), 38 deletions(-) diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index 03c861c1..1e182974 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -132,48 +132,39 @@ fn main() -> Result<()> { "Do you like pizza?", ]; let n_sentences = sentences.len(); - if let Some(pp) = tokenizer.get_padding_mut() { - pp.strategy = tokenizers::PaddingStrategy::BatchLongest - } else { - let pp = tokenizers::PaddingParams { - strategy: tokenizers::PaddingStrategy::BatchLongest, - ..Default::default() + let mut all_embeddings = Vec::with_capacity(n_sentences); + for sentence in sentences { + let tokens = tokenizer + .encode(sentence, true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + let token_ids = Tensor::new(&tokens[..], model.device())?.unsqueeze(0)?; + let embeddings = model.forward(&token_ids)?; + println!("generated embeddings {:?}", embeddings.shape()); + // Apply some avg-pooling by taking the mean embedding value for all tokens (including padding) + let (_n_sentence, n_tokens, _hidden_size) = embeddings.dims3()?; + let embeddings = (embeddings.sum(1)? / (n_tokens as f64))?; + let embeddings = if args.normalize_embeddings { + normalize_l2(&embeddings)? + } else { + embeddings }; - tokenizer.with_padding(Some(pp)); + println!("pooled embeddings {:?}", embeddings.shape()); + all_embeddings.push(embeddings) } - let tokens = tokenizer - .encode_batch(sentences.to_vec(), true) - .map_err(E::msg)?; - let token_ids = tokens - .iter() - .map(|tokens| { - let tokens = tokens.get_ids().to_vec(); - Ok(Tensor::new(tokens.as_slice(), model.device())?) - }) - .collect::>>()?; - - let token_ids = Tensor::stack(&token_ids, 0)?; - println!("running inference on batch {:?}", token_ids.shape()); - let embeddings = model.forward(&token_ids)?; - println!("generated embeddings {:?}", embeddings.shape()); - // Apply some avg-pooling by taking the mean embedding value for all tokens (including padding) - let (_n_sentence, n_tokens, _hidden_size) = embeddings.dims3()?; - let embeddings = (embeddings.sum(1)? / (n_tokens as f64))?; - let embeddings = if args.normalize_embeddings { - normalize_l2(&embeddings)? - } else { - embeddings - }; - println!("pooled embeddings {:?}", embeddings.shape()); let mut similarities = vec![]; - for i in 0..n_sentences { - let e_i = embeddings.get(i)?; - for j in (i + 1)..n_sentences { - let e_j = embeddings.get(j)?; - let sum_ij = (&e_i * &e_j)?.sum_all()?.to_scalar::()?; - let sum_i2 = (&e_i * &e_i)?.sum_all()?.to_scalar::()?; - let sum_j2 = (&e_j * &e_j)?.sum_all()?.to_scalar::()?; + for (i, e_i) in all_embeddings.iter().enumerate() { + for (j, e_j) in all_embeddings + .iter() + .enumerate() + .take(n_sentences) + .skip(i + 1) + { + let sum_ij = (e_i * e_j)?.sum_all()?.to_scalar::()?; + let sum_i2 = (e_i * e_i)?.sum_all()?.to_scalar::()?; + let sum_j2 = (e_j * e_j)?.sum_all()?.to_scalar::()?; let cosine_similarity = sum_ij / (sum_i2 * sum_j2).sqrt(); similarities.push((cosine_similarity, i, j)) } From 9a465e1b2601195b65f1422f16793d6825252231 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 13 Sep 2023 17:50:39 +0200 Subject: [PATCH 089/150] Add 1d upsampling. (#839) * Add 1d upsampling. * Add the interpolate functions. --- candle-core/src/backend.rs | 1 + candle-core/src/backprop.rs | 4 ++++ candle-core/src/cpu_backend.rs | 34 +++++++++++++++++++++++++++ candle-core/src/cuda_backend.rs | 4 ++++ candle-core/src/dummy_cuda_backend.rs | 4 ++++ candle-core/src/op.rs | 1 + candle-core/src/storage.rs | 13 ++++++++++ candle-core/src/tensor.rs | 27 +++++++++++++++++++-- 8 files changed, 86 insertions(+), 2 deletions(-) diff --git a/candle-core/src/backend.rs b/candle-core/src/backend.rs index 67a08714..03a07434 100644 --- a/candle-core/src/backend.rs +++ b/candle-core/src/backend.rs @@ -57,6 +57,7 @@ pub trait BackendStorage: Sized { fn avg_pool2d(&self, _: &Layout, _: (usize, usize), _: (usize, usize)) -> Result; fn max_pool2d(&self, _: &Layout, _: (usize, usize), _: (usize, usize)) -> Result; + fn upsample_nearest1d(&self, _: &Layout, _: usize) -> Result; fn upsample_nearest2d(&self, _: &Layout, _: usize, _: usize) -> Result; fn gather(&self, _: &Layout, _: &Self, _: &Layout, _: usize) -> Result; diff --git a/candle-core/src/backprop.rs b/candle-core/src/backprop.rs index d2099df7..b930a9f4 100644 --- a/candle-core/src/backprop.rs +++ b/candle-core/src/backprop.rs @@ -91,6 +91,7 @@ impl Tensor { } } Op::Reshape(node) + | Op::UpsampleNearest1D(node) | Op::UpsampleNearest2D(node) | Op::AvgPool2D { arg: node, .. } | Op::MaxPool2D { arg: node, .. } @@ -262,6 +263,9 @@ impl Tensor { let sum_grad = grads.or_insert(arg)?; *sum_grad = sum_grad.add(&grad_arg)?; } + Op::UpsampleNearest1D { .. } => Err(Error::BackwardNotSupported { + op: "upsample-nearest1d", + })?, Op::UpsampleNearest2D { .. } => Err(Error::BackwardNotSupported { op: "upsample-nearest2d", })?, diff --git a/candle-core/src/cpu_backend.rs b/candle-core/src/cpu_backend.rs index 544ce32d..4e808b34 100644 --- a/candle-core/src/cpu_backend.rs +++ b/candle-core/src/cpu_backend.rs @@ -727,6 +727,36 @@ impl Map1 for MaxPool2D { } } +struct UpsampleNearest1D(usize); + +impl Map1 for UpsampleNearest1D { + fn f(&self, src: &[T], layout: &Layout) -> Result> { + // TODO: Specialized implementation for the case 2*sz? + let dst_sz = self.0; + let (b_sz, c, src_sz) = layout.shape().dims3()?; + let stride = layout.stride(); + let stride_sz = stride[2]; + let src_index = layout.start_offset(); + let scale_sz = src_sz as f64 / dst_sz as f64; + let mut dst = vec![T::zero(); b_sz * c * dst_sz]; + let src_idxs = (0..dst_sz) + .map(|idx| usize::min(src_sz - 1, (idx as f64 * scale_sz) as usize)) + .collect::>(); + for b_idx in 0..b_sz { + let dst = &mut dst[b_idx * c * dst_sz..]; + let src_index = src_index + b_idx * stride[0]; + for c_idx in 0..c { + let dst = &mut dst[c_idx * dst_sz..]; + let src_index = src_index + c_idx * stride[1]; + for (idx, src_idx) in src_idxs.iter().enumerate() { + dst[idx] = src[src_index + src_idx * stride_sz] + } + } + } + Ok(dst) + } +} + struct UpsampleNearest2D(usize, usize); impl Map1 for UpsampleNearest2D { @@ -2137,6 +2167,10 @@ impl BackendStorage for CpuStorage { MaxPool2D(kernel_size, stride).map(self, layout) } + fn upsample_nearest1d(&self, layout: &Layout, sz: usize) -> Result { + UpsampleNearest1D(sz).map(self, layout) + } + fn upsample_nearest2d(&self, layout: &Layout, h: usize, w: usize) -> Result { UpsampleNearest2D(h, w).map(self, layout) } diff --git a/candle-core/src/cuda_backend.rs b/candle-core/src/cuda_backend.rs index 4ec41b87..00fd1d04 100644 --- a/candle-core/src/cuda_backend.rs +++ b/candle-core/src/cuda_backend.rs @@ -1954,6 +1954,10 @@ impl BackendStorage for CudaStorage { Ok(Self { slice, device }) } + fn upsample_nearest1d(&self, _: &Layout, _out_sz: usize) -> Result { + crate::bail!("upsample-nearest1d is not supported on cuda") + } + fn upsample_nearest2d(&self, l: &Layout, out_w: usize, out_h: usize) -> Result { let device = self.device().clone(); let slice = UpsampleNearest2D(out_w, out_h).map(&self.slice, &device, l)?; diff --git a/candle-core/src/dummy_cuda_backend.rs b/candle-core/src/dummy_cuda_backend.rs index 6c896653..5cc9c6d8 100644 --- a/candle-core/src/dummy_cuda_backend.rs +++ b/candle-core/src/dummy_cuda_backend.rs @@ -152,6 +152,10 @@ impl crate::backend::BackendStorage for CudaStorage { Err(Error::NotCompiledWithCudaSupport) } + fn upsample_nearest1d(&self, _: &Layout, _: usize) -> Result { + Err(Error::NotCompiledWithCudaSupport) + } + fn upsample_nearest2d(&self, _: &Layout, _: usize, _: usize) -> Result { Err(Error::NotCompiledWithCudaSupport) } diff --git a/candle-core/src/op.rs b/candle-core/src/op.rs index 9382b217..7940739c 100644 --- a/candle-core/src/op.rs +++ b/candle-core/src/op.rs @@ -116,6 +116,7 @@ pub enum Op { stride: (usize, usize), }, + UpsampleNearest1D(Tensor), UpsampleNearest2D(Tensor), Cat(Vec, usize), diff --git a/candle-core/src/storage.rs b/candle-core/src/storage.rs index 8bd14ea9..9bd1fed6 100644 --- a/candle-core/src/storage.rs +++ b/candle-core/src/storage.rs @@ -369,6 +369,19 @@ impl Storage { } } + pub(crate) fn upsample_nearest1d(&self, layout: &Layout, sz: usize) -> Result { + match self { + Storage::Cpu(storage) => { + let storage = storage.upsample_nearest1d(layout, sz)?; + Ok(Self::Cpu(storage)) + } + Self::Cuda(storage) => { + let storage = storage.upsample_nearest1d(layout, sz)?; + Ok(Self::Cuda(storage)) + } + } + } + pub(crate) fn upsample_nearest2d(&self, layout: &Layout, h: usize, w: usize) -> Result { match self { Storage::Cpu(storage) => { diff --git a/candle-core/src/tensor.rs b/candle-core/src/tensor.rs index 59a23c39..4388bf77 100644 --- a/candle-core/src/tensor.rs +++ b/candle-core/src/tensor.rs @@ -854,12 +854,30 @@ impl Tensor { self.maximum(min)?.minimum(max) } - /// Upsample the input tensor to the `(target_h, target_w)` size, taking the value of the + /// Interpolate the input tensor to the `target_size` size, taking the value of the nearest element. + /// + /// The input tensor should have three dimensions, `(batch, channels, l)`, the returned + /// tensor also has three dimensions, `(batch, channels, target_size)`. + pub fn interpolate1d(&self, target_size: usize) -> Result { + let (n, c, _l) = self.dims3()?; + let op = BackpropOp::new1(self, Op::UpsampleNearest1D); + let storage = self + .storage() + .upsample_nearest1d(self.layout(), target_size)?; + Ok(from_storage(storage, (n, c, target_size), op, false)) + } + + /// Alias for `interpolate1d`. + pub fn upsample_nearest1d(&self, target_size: usize) -> Result { + self.interpolate1d(target_size) + } + + /// Interpolate the input tensor to the `(target_h, target_w)` size, taking the value of the /// nearest element. /// /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned /// tensor also has four dimensions, `(batch, channels, target_h, target_w)`. - pub fn upsample_nearest2d(&self, target_h: usize, target_w: usize) -> Result { + pub fn interpolate2d(&self, target_h: usize, target_w: usize) -> Result { let (n, c, _h, _w) = self.dims4()?; let op = BackpropOp::new1(self, Op::UpsampleNearest2D); let storage = self @@ -868,6 +886,11 @@ impl Tensor { Ok(from_storage(storage, (n, c, target_h, target_w), op, false)) } + /// Alias for `interpolate2d`. + pub fn upsample_nearest2d(&self, target_h: usize, target_w: usize) -> Result { + self.interpolate2d(target_h, target_w) + } + /// 2D average pooling over an input tensor with multiple channels. /// /// The input tensor should have four dimensions, `(batch, channels, h, w)`, the returned From 49d3f7f70814bd0e8b569f93bb76419306359251 Mon Sep 17 00:00:00 2001 From: Juarez Bochi Date: Wed, 13 Sep 2023 10:27:20 -0700 Subject: [PATCH 090/150] Add support to flan-t5 (#840) --- candle-nn/src/activation.rs | 6 ++++ candle-transformers/src/models/t5.rs | 54 +++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/candle-nn/src/activation.rs b/candle-nn/src/activation.rs index 6e442458..22e062b0 100644 --- a/candle-nn/src/activation.rs +++ b/candle-nn/src/activation.rs @@ -6,6 +6,8 @@ use serde::Deserialize; pub enum Activation { #[default] Gelu, + #[serde(rename = "gated-gelu")] + NewGelu, Relu, Elu(f64), } @@ -14,6 +16,10 @@ impl super::Module for Activation { fn forward(&self, xs: &Tensor) -> candle::Result { match self { Self::Gelu => xs.gelu(), + // TODO: This is "gelu_new", not the original "gelu". + // There's some small numerical difference: + // https://github.com/huggingface/transformers/blob/12f043eaeaabfef6f6efea411d98e6f6d3c094b7/src/transformers/activations.py#L49-L78 + Self::NewGelu => xs.gelu(), Self::Relu => xs.relu(), &Self::Elu(alpha) => xs.elu(alpha), } diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index 325eb752..de7de496 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -148,27 +148,71 @@ impl T5DenseActDense { } } +#[derive(Debug)] +struct T5DenseGatedActDense { + wi_0: Linear, + wi_1: Linear, + wo: Linear, + act: Activation, +} + +impl T5DenseGatedActDense { + fn load(vb: VarBuilder, cfg: &Config) -> Result { + let wi_0 = linear_no_bias(cfg.d_model, cfg.d_ff, vb.pp("wi_0"))?; + let wi_1 = linear_no_bias(cfg.d_model, cfg.d_ff, vb.pp("wi_1"))?; + let wo = linear_no_bias(cfg.d_ff, cfg.d_model, vb.pp("wo"))?; + Ok(Self { + wi_0, + wi_1, + wo, + act: Activation::NewGelu, + }) + } + + fn forward(&self, xs: &Tensor) -> Result { + let hidden_gelu = self.act.forward(&self.wi_0.forward(xs)?)?; + let hidden_linear = self.wi_1.forward(xs)?; + let xs = hidden_gelu.broadcast_mul(&hidden_linear)?; + let xs = self.wo.forward(&xs)?; + Ok(xs) + } +} + #[derive(Debug)] struct T5LayerFF { - dense_relu_dense: T5DenseActDense, + dense_act: Option, + gated_dense_act: Option, layer_norm: T5LayerNorm, } impl T5LayerFF { fn load(vb: VarBuilder, cfg: &Config) -> Result { - // is_gated_act is not supported. - let dense_relu_dense = T5DenseActDense::load(vb.pp("DenseReluDense"), cfg)?; let layer_norm = T5LayerNorm::load(cfg.d_model, cfg.layer_norm_epsilon, vb.pp("layer_norm"))?; + let (dense_act, gated_dense_act) = if cfg.feed_forward_proj == Activation::NewGelu { + ( + None, + Some(T5DenseGatedActDense::load(vb.pp("DenseReluDense"), cfg)?), + ) + } else { + ( + Some(T5DenseActDense::load(vb.pp("DenseReluDense"), cfg)?), + None, + ) + }; Ok(Self { - dense_relu_dense, + dense_act, + gated_dense_act, layer_norm, }) } fn forward(&self, xs: &Tensor) -> Result { let ys = self.layer_norm.forward(xs)?; - let ys = self.dense_relu_dense.forward(&ys)?; + let ys = match &self.dense_act { + Some(dense_act) => dense_act.forward(&ys)?, + None => self.gated_dense_act.as_ref().unwrap().forward(&ys)?, + }; let xs = (xs + ys)?; Ok(xs) } From d6447ad635bc450ef1f15ca7a4424c0f86e7a90a Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 14 Sep 2023 08:47:07 +0200 Subject: [PATCH 091/150] Tensor based indexing. (#842) --- candle-core/src/indexer.rs | 39 +++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/candle-core/src/indexer.rs b/candle-core/src/indexer.rs index 2b6d694b..7b84d316 100644 --- a/candle-core/src/indexer.rs +++ b/candle-core/src/indexer.rs @@ -46,19 +46,31 @@ impl Tensor { current_dim += 1; out } + TensorIndexer::IndexSelect(indexes) => { + if indexes.rank() != 1 { + crate::bail!("multi-dimensional tensor indexing is not supported") + } + let out = x.index_select(&indexes.to_device(x.device())?, current_dim)?; + current_dim += 1; + out + } + TensorIndexer::Err(e) => crate::bail!("indexing error {e:?}"), }; } Ok(x) } } -#[derive(Debug, Clone)] +#[derive(Debug)] /// Generic structure used to index a slice of the tensor pub enum TensorIndexer { /// This selects the elemnts for which an index has some specific value. Select(usize), /// This is a regular slice, purely indexing a chunk of the tensor Narrow(Bound, Bound), + /// Indexing via a 1d tensor + IndexSelect(Tensor), + Err(Error), } impl From for TensorIndexer { @@ -67,6 +79,31 @@ impl From for TensorIndexer { } } +impl From<&[u32]> for TensorIndexer { + fn from(index: &[u32]) -> Self { + match Tensor::new(index, &crate::Device::Cpu) { + Ok(tensor) => TensorIndexer::IndexSelect(tensor), + Err(e) => TensorIndexer::Err(e), + } + } +} + +impl From> for TensorIndexer { + fn from(index: Vec) -> Self { + let len = index.len(); + match Tensor::from_vec(index, len, &crate::Device::Cpu) { + Ok(tensor) => TensorIndexer::IndexSelect(tensor), + Err(e) => TensorIndexer::Err(e), + } + } +} + +impl From<&Tensor> for TensorIndexer { + fn from(tensor: &Tensor) -> Self { + TensorIndexer::IndexSelect(tensor.clone()) + } +} + macro_rules! impl_from_range { ($range_type:ty) => { impl From<$range_type> for TensorIndexer { From 286f01db1471a48b6050d9eddb6cf4f28ed7d8cb Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 14 Sep 2023 11:56:07 +0200 Subject: [PATCH 092/150] Start adding the Wuerstchen diffusion pipeline (#843) * Wuerstchen common bits. * Add the prior layer. * Start adding diffnext. --- candle-transformers/src/models/mod.rs | 1 + .../src/models/wuerstchen/common.rs | 126 ++++++++++++++++++ .../src/models/wuerstchen/diffnext.rs | 55 ++++++++ .../src/models/wuerstchen/mod.rs | 3 + .../src/models/wuerstchen/prior.rs | 94 +++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 candle-transformers/src/models/wuerstchen/common.rs create mode 100644 candle-transformers/src/models/wuerstchen/diffnext.rs create mode 100644 candle-transformers/src/models/wuerstchen/mod.rs create mode 100644 candle-transformers/src/models/wuerstchen/prior.rs diff --git a/candle-transformers/src/models/mod.rs b/candle-transformers/src/models/mod.rs index e2e0bf81..a20254d9 100644 --- a/candle-transformers/src/models/mod.rs +++ b/candle-transformers/src/models/mod.rs @@ -9,3 +9,4 @@ pub mod segment_anything; pub mod stable_diffusion; pub mod t5; pub mod whisper; +pub mod wuerstchen; diff --git a/candle-transformers/src/models/wuerstchen/common.rs b/candle-transformers/src/models/wuerstchen/common.rs new file mode 100644 index 00000000..fc731a59 --- /dev/null +++ b/candle-transformers/src/models/wuerstchen/common.rs @@ -0,0 +1,126 @@ +use candle::{Module, Result, Tensor, D}; +use candle_nn::VarBuilder; + +// https://github.com/huggingface/diffusers/blob/19edca82f1ff194c07317369a92b470dbae97f34/src/diffusers/pipelines/wuerstchen/modeling_wuerstchen_common.py#L22 +#[derive(Debug)] +pub struct WLayerNorm { + inner: candle_nn::LayerNorm, +} + +impl WLayerNorm { + pub fn new(size: usize, vb: VarBuilder) -> Result { + let cfg = candle_nn::layer_norm::LayerNormConfig { + eps: 1e-6, + remove_mean: true, + affine: false, + }; + let inner = candle_nn::layer_norm(size, cfg, vb)?; + Ok(Self { inner }) + } +} + +impl Module for WLayerNorm { + fn forward(&self, xs: &Tensor) -> Result { + xs.permute((0, 2, 3, 1))? + .apply(&self.inner)? + .permute((0, 3, 1, 2)) + } +} + +#[derive(Debug)] +pub struct TimestepBlock { + mapper: candle_nn::Linear, +} + +impl TimestepBlock { + pub fn new(c: usize, c_timestep: usize, vb: VarBuilder) -> Result { + let mapper = candle_nn::linear(c_timestep, c * 2, vb.pp("mapper"))?; + Ok(Self { mapper }) + } + + pub fn forward(&self, xs: &Tensor, t: &Tensor) -> Result { + let ab = self + .mapper + .forward(t)? + .unsqueeze(2)? + .unsqueeze(3)? + .chunk(2, 1)?; + xs.broadcast_mul(&(&ab[0] + 1.)?)?.broadcast_add(&ab[1]) + } +} + +#[derive(Debug)] +pub struct GlobalResponseNorm { + gamma: Tensor, + beta: Tensor, +} + +impl GlobalResponseNorm { + pub fn new(dim: usize, vb: VarBuilder) -> Result { + let gamma = vb.get((1, 1, 1, 1, dim), "gamma")?; + let beta = vb.get((1, 1, 1, 1, dim), "beta")?; + Ok(Self { gamma, beta }) + } +} + +impl Module for GlobalResponseNorm { + fn forward(&self, xs: &Tensor) -> Result { + let agg_norm = xs.sqr()?.sum_keepdim((1, 2))?; + let stand_div_norm = + agg_norm.broadcast_div(&(agg_norm.mean_keepdim(D::Minus1)? + 1e-6)?)?; + (xs.broadcast_mul(&stand_div_norm)? + .broadcast_mul(&self.gamma) + + &self.beta)? + + xs + } +} + +#[derive(Debug)] +pub struct ResBlock { + depthwise: candle_nn::Conv2d, + norm: WLayerNorm, + channelwise_lin1: candle_nn::Linear, + channelwise_grn: GlobalResponseNorm, + channelwise_lin2: candle_nn::Linear, +} + +impl ResBlock { + pub fn new(c: usize, c_skip: usize, ksize: usize, vb: VarBuilder) -> Result { + let cfg = candle_nn::Conv2dConfig { + padding: ksize / 2, + groups: c, + ..Default::default() + }; + let depthwise = candle_nn::conv2d(c + c_skip, c, ksize, cfg, vb.pp("depthwise"))?; + let norm = WLayerNorm::new(c, vb.pp("norm"))?; + let channelwise_lin1 = candle_nn::linear(c, c * 4, vb.pp("channelwise.0"))?; + let channelwise_grn = GlobalResponseNorm::new(c * 4, vb.pp("channelwise.2"))?; + let channelwise_lin2 = candle_nn::linear(c * 4, c, vb.pp("channelwise.4"))?; + Ok(Self { + depthwise, + norm, + channelwise_lin1, + channelwise_grn, + channelwise_lin2, + }) + } + + pub fn forward(&self, xs: &Tensor, x_skip: Option<&Tensor>) -> Result { + let x_res = xs; + let xs = match x_skip { + None => xs.clone(), + Some(x_skip) => Tensor::cat(&[xs, x_skip], 1)?, + }; + let xs = xs + .apply(&self.depthwise)? + .apply(&self.norm)? + .permute((0, 2, 3, 1))?; + let xs = xs + .apply(&self.channelwise_lin1)? + .gelu()? + .apply(&self.channelwise_grn)? + .apply(&self.channelwise_lin2)? + .permute((0, 3, 1, 2))?; + xs + x_res + } +} diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs new file mode 100644 index 00000000..82c973a1 --- /dev/null +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -0,0 +1,55 @@ +#![allow(unused)] +use super::common::{GlobalResponseNorm, ResBlock, TimestepBlock, WLayerNorm}; +use candle::{DType, Module, Result, Tensor, D}; +use candle_nn::VarBuilder; + +#[derive(Debug)] +pub struct ResBlockStageB { + depthwise: candle_nn::Conv2d, + norm: WLayerNorm, + channelwise_lin1: candle_nn::Linear, + channelwise_grn: GlobalResponseNorm, + channelwise_lin2: candle_nn::Linear, +} + +impl ResBlockStageB { + pub fn new(c: usize, c_skip: usize, ksize: usize, vb: VarBuilder) -> Result { + let cfg = candle_nn::Conv2dConfig { + groups: c, + padding: ksize / 2, + ..Default::default() + }; + let depthwise = candle_nn::conv2d(c, c, ksize, cfg, vb.pp("depthwise"))?; + let norm = WLayerNorm::new(c, vb.pp("norm"))?; + let channelwise_lin1 = candle_nn::linear(c + c_skip, c * 4, vb.pp("channelwise.0"))?; + let channelwise_grn = GlobalResponseNorm::new(4 * c, vb.pp("channelwise.2"))?; + let channelwise_lin2 = candle_nn::linear(c * 4, c, vb.pp("channelwise.4"))?; + Ok(Self { + depthwise, + norm, + channelwise_lin1, + channelwise_grn, + channelwise_lin2, + }) + } + + pub fn forward(&self, xs: &Tensor, x_skip: Option<&Tensor>) -> Result { + let x_res = xs; + let xs = xs.apply(&self.depthwise)?.apply(&self.norm)?; + let xs = match x_skip { + None => xs.clone(), + Some(x_skip) => Tensor::cat(&[&xs, x_skip], 1)?, + }; + let xs = xs + .permute((0, 2, 3, 1))? + .apply(&self.channelwise_lin1)? + .gelu()? + .apply(&self.channelwise_grn)? + .apply(&self.channelwise_lin2)? + .permute((0, 3, 1, 2))?; + xs + x_res + } +} + +#[derive(Debug)] +pub struct WDiffNeXt {} diff --git a/candle-transformers/src/models/wuerstchen/mod.rs b/candle-transformers/src/models/wuerstchen/mod.rs new file mode 100644 index 00000000..81755dd1 --- /dev/null +++ b/candle-transformers/src/models/wuerstchen/mod.rs @@ -0,0 +1,3 @@ +pub mod common; +pub mod diffnext; +pub mod prior; diff --git a/candle-transformers/src/models/wuerstchen/prior.rs b/candle-transformers/src/models/wuerstchen/prior.rs new file mode 100644 index 00000000..a4e0300c --- /dev/null +++ b/candle-transformers/src/models/wuerstchen/prior.rs @@ -0,0 +1,94 @@ +#![allow(unused)] +use super::common::{ResBlock, TimestepBlock}; +use candle::{DType, Module, Result, Tensor, D}; +use candle_nn::VarBuilder; + +#[derive(Debug)] +struct Block { + res_block: ResBlock, + ts_block: TimestepBlock, + // TODO: attn_block: super::common::AttnBlock, +} + +#[derive(Debug)] +pub struct WPrior { + projection: candle_nn::Conv2d, + cond_mapper_lin1: candle_nn::Linear, + cond_mapper_lin2: candle_nn::Linear, + blocks: Vec, + out_ln: super::common::WLayerNorm, + out_conv: candle_nn::Conv2d, + c_r: usize, +} + +impl WPrior { + pub fn new( + c_in: usize, + c: usize, + c_cond: usize, + c_r: usize, + depth: usize, + _nhead: usize, + vb: VarBuilder, + ) -> Result { + let projection = candle_nn::conv2d(c_in, c, 1, Default::default(), vb.pp("projection"))?; + let cond_mapper_lin1 = candle_nn::linear(c_cond, c, vb.pp("cond_mapper.0"))?; + let cond_mapper_lin2 = candle_nn::linear(c, c, vb.pp("cond_mapper.2"))?; + let out_ln = super::common::WLayerNorm::new(c, vb.pp("out.0"))?; + let out_conv = candle_nn::conv2d(c, c_in * 2, 1, Default::default(), vb.pp("out.1"))?; + let mut blocks = Vec::with_capacity(depth); + for index in 0..depth { + let res_block = ResBlock::new(c, 0, 3, vb.pp(format!("blocks.{}", 3 * index)))?; + let ts_block = TimestepBlock::new(c, c_r, vb.pp(format!("blocks.{}", 3 * index + 1)))?; + blocks.push(Block { + res_block, + ts_block, + }) + } + Ok(Self { + projection, + cond_mapper_lin1, + cond_mapper_lin2, + blocks, + out_ln, + out_conv, + c_r, + }) + } + + pub fn gen_r_embedding(&self, r: &Tensor) -> Result { + const MAX_POSITIONS: usize = 10000; + let r = (r * MAX_POSITIONS as f64)?; + let half_dim = self.c_r / 2; + let emb = (MAX_POSITIONS as f64).ln() / (half_dim - 1) as f64; + let emb = (Tensor::arange(0u32, half_dim as u32, r.device())?.to_dtype(DType::F32)? + * -emb)? + .exp()?; + let emb = r.unsqueeze(1)?.broadcast_mul(&emb.unsqueeze(0)?)?; + let emb = Tensor::cat(&[emb.sin()?, emb.cos()?], 1)?; + let emb = if self.c_r % 2 == 1 { + emb.pad_with_zeros(D::Minus1, 0, 1)? + } else { + emb + }; + emb.to_dtype(r.dtype()) + } + + pub fn forward(&self, xs: &Tensor, r: &Tensor, c: &Tensor) -> Result { + let x_in = xs; + let mut xs = xs.apply(&self.projection)?; + // TODO: leaky relu + let c_embed = c + .apply(&self.cond_mapper_lin1)? + .relu()? + .apply(&self.cond_mapper_lin2)?; + let r_embed = self.gen_r_embedding(r)?; + for block in self.blocks.iter() { + xs = block.res_block.forward(&xs, None)?; + xs = block.ts_block.forward(&xs, &r_embed)?; + // TODO: attn + } + let ab = xs.apply(&self.out_ln)?.apply(&self.out_conv)?.chunk(1, 2)?; + (x_in - &ab[0])? / ((&ab[1] - 1.)?.abs()? + 1e-5) + } +} From a0c6d5548cec20bc0313b1dc3597c1c94abd0896 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 14 Sep 2023 16:40:09 +0200 Subject: [PATCH 093/150] Add the attention block. (#846) * Add the attention block. * Add more to clipnext. --- .../src/models/stable_diffusion/attention.rs | 6 +-- .../src/models/wuerstchen/common.rs | 41 ++++++++++++++++++ .../src/models/wuerstchen/diffnext.rs | 43 ++++++++++++++++++- .../src/models/wuerstchen/prior.rs | 16 +++++-- 4 files changed, 98 insertions(+), 8 deletions(-) diff --git a/candle-transformers/src/models/stable_diffusion/attention.rs b/candle-transformers/src/models/stable_diffusion/attention.rs index 2b925cee..b3ea91f9 100644 --- a/candle-transformers/src/models/stable_diffusion/attention.rs +++ b/candle-transformers/src/models/stable_diffusion/attention.rs @@ -78,7 +78,7 @@ fn flash_attn(_: &Tensor, _: &Tensor, _: &Tensor, _: f32, _: bool) -> Result, @@ -205,7 +205,7 @@ impl CrossAttention { self.reshape_batch_dim_to_heads(&xs) } - fn forward(&self, xs: &Tensor, context: Option<&Tensor>) -> Result { + pub fn forward(&self, xs: &Tensor, context: Option<&Tensor>) -> Result { let _enter = self.span.enter(); let query = self.to_q.forward(xs)?; let context = context.unwrap_or(xs).contiguous()?; diff --git a/candle-transformers/src/models/wuerstchen/common.rs b/candle-transformers/src/models/wuerstchen/common.rs index fc731a59..10e7b19f 100644 --- a/candle-transformers/src/models/wuerstchen/common.rs +++ b/candle-transformers/src/models/wuerstchen/common.rs @@ -124,3 +124,44 @@ impl ResBlock { xs + x_res } } +use crate::models::stable_diffusion::attention::CrossAttention as Attention; +#[derive(Debug)] +pub struct AttnBlock { + self_attn: bool, + norm: WLayerNorm, + attention: Attention, + kv_mapper_lin: candle_nn::Linear, +} + +impl AttnBlock { + pub fn new( + c: usize, + c_cond: usize, + nhead: usize, + self_attn: bool, + vb: VarBuilder, + ) -> Result { + let norm = WLayerNorm::new(c, vb.pp("norm"))?; + let attention = Attention::new(vb.pp("attention"), c, None, nhead, c / nhead, None, false)?; + let kv_mapper_lin = candle_nn::linear(c_cond, c, vb.pp("kv_mapper.1"))?; + Ok(Self { + self_attn, + norm, + attention, + kv_mapper_lin, + }) + } + + pub fn forward(&self, xs: &Tensor, kv: &Tensor) -> Result { + let kv = candle_nn::ops::silu(kv)?.apply(&self.kv_mapper_lin)?; + let norm_xs = self.norm.forward(xs)?; + let kv = if self.self_attn { + let (b_size, channel, _, _) = xs.dims4()?; + let norm_xs = norm_xs.reshape((b_size, channel, ()))?.transpose(1, 2)?; + Tensor::cat(&[&norm_xs, &kv], 1)? + } else { + kv + }; + xs + self.attention.forward(&norm_xs, Some(&kv)) + } +} diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 82c973a1..8e5099f6 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -52,4 +52,45 @@ impl ResBlockStageB { } #[derive(Debug)] -pub struct WDiffNeXt {} +pub struct WDiffNeXt { + clip_mapper: candle_nn::Linear, + effnet_mappers: Vec, + seq_norm: candle_nn::LayerNorm, + embedding_conv: candle_nn::Conv2d, + embedding_ln: WLayerNorm, +} + +impl WDiffNeXt { + pub fn new( + c_in: usize, + c_out: usize, + vb: VarBuilder, + c_cond: usize, + clip_embd: usize, + patch_size: usize, + ) -> Result { + const C_HIDDEN: [usize; 4] = [320, 640, 1280, 1280]; + + let clip_mapper = candle_nn::linear(clip_embd, c_cond, vb.pp("clip_mapper"))?; + let effnet_mappers = vec![]; + let cfg = candle_nn::layer_norm::LayerNormConfig { + ..Default::default() + }; + let seq_norm = candle_nn::layer_norm(c_cond, cfg, vb.pp("seq_norm"))?; + let embedding_ln = WLayerNorm::new(C_HIDDEN[0], vb.pp("embedding.1"))?; + let embedding_conv = candle_nn::conv2d( + c_in * patch_size * patch_size, + C_HIDDEN[1], + 1, + Default::default(), + vb.pp("embedding.2"), + )?; + Ok(Self { + clip_mapper, + effnet_mappers, + seq_norm, + embedding_conv, + embedding_ln, + }) + } +} diff --git a/candle-transformers/src/models/wuerstchen/prior.rs b/candle-transformers/src/models/wuerstchen/prior.rs index a4e0300c..eea70a02 100644 --- a/candle-transformers/src/models/wuerstchen/prior.rs +++ b/candle-transformers/src/models/wuerstchen/prior.rs @@ -1,5 +1,5 @@ #![allow(unused)] -use super::common::{ResBlock, TimestepBlock}; +use super::common::{AttnBlock, ResBlock, TimestepBlock}; use candle::{DType, Module, Result, Tensor, D}; use candle_nn::VarBuilder; @@ -7,7 +7,7 @@ use candle_nn::VarBuilder; struct Block { res_block: ResBlock, ts_block: TimestepBlock, - // TODO: attn_block: super::common::AttnBlock, + attn_block: AttnBlock, } #[derive(Debug)] @@ -28,7 +28,7 @@ impl WPrior { c_cond: usize, c_r: usize, depth: usize, - _nhead: usize, + nhead: usize, vb: VarBuilder, ) -> Result { let projection = candle_nn::conv2d(c_in, c, 1, Default::default(), vb.pp("projection"))?; @@ -40,9 +40,17 @@ impl WPrior { for index in 0..depth { let res_block = ResBlock::new(c, 0, 3, vb.pp(format!("blocks.{}", 3 * index)))?; let ts_block = TimestepBlock::new(c, c_r, vb.pp(format!("blocks.{}", 3 * index + 1)))?; + let attn_block = AttnBlock::new( + c, + c, + nhead, + true, + vb.pp(format!("blocks.{}", 3 * index + 2)), + )?; blocks.push(Block { res_block, ts_block, + attn_block, }) } Ok(Self { @@ -86,7 +94,7 @@ impl WPrior { for block in self.blocks.iter() { xs = block.res_block.forward(&xs, None)?; xs = block.ts_block.forward(&xs, &r_embed)?; - // TODO: attn + xs = block.attn_block.forward(&xs, &c_embed)?; } let ab = xs.apply(&self.out_ln)?.apply(&self.out_conv)?.chunk(1, 2)?; (x_in - &ab[0])? / ((&ab[1] - 1.)?.abs()? + 1e-5) From 0a647875ec7f9861ede7fa54713af50b4207ffb7 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 14 Sep 2023 18:29:24 +0200 Subject: [PATCH 094/150] Use softmax-last-dim in the quantized example. (#848) --- candle-transformers/src/generation/mod.rs | 41 ++++++++++--------- .../src/models/quantized_llama.rs | 2 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/candle-transformers/src/generation/mod.rs b/candle-transformers/src/generation/mod.rs index 6c8c8ae4..b1a567c3 100644 --- a/candle-transformers/src/generation/mod.rs +++ b/candle-transformers/src/generation/mod.rs @@ -1,4 +1,4 @@ -use candle::{DType, Error, Result, Tensor, D}; +use candle::{DType, Error, Result, Tensor}; use rand::{distributions::Distribution, SeedableRng}; pub struct LogitsProcessor { @@ -9,6 +9,11 @@ pub struct LogitsProcessor { impl LogitsProcessor { pub fn new(seed: u64, temperature: Option, top_p: Option) -> Self { + let temperature = if temperature.map_or(true, |v| v < 1e-7) { + None + } else { + temperature + }; Self { rng: rand::rngs::StdRng::seed_from_u64(seed), temperature, @@ -27,7 +32,7 @@ impl LogitsProcessor { Ok(next_token) } - fn sample_multi(&mut self, prs: &Vec) -> Result { + fn sample_multinomial(&mut self, prs: &Vec) -> Result { let distr = rand::distributions::WeightedIndex::new(prs).map_err(Error::wrap)?; let next_token = distr.sample(&mut self.rng) as u32; Ok(next_token) @@ -51,28 +56,26 @@ impl LogitsProcessor { cumsum += prs[*index]; } } - // Sample with clamped probabilities. - let next_token = self.sample_multi(prs)?; - Ok(next_token) + self.sample_multinomial(prs) } pub fn sample(&mut self, logits: &Tensor) -> Result { let logits = logits.to_dtype(DType::F32)?; - let temperature = self.temperature.unwrap_or(0.); - let top_p = self.top_p.unwrap_or(1.); - let next_token = if temperature == 0. { - self.sample_argmax(logits)? - } else { - let logits = &(&logits / temperature)?; - let prs = candle_nn::ops::softmax(logits, D::Minus1)?; - let mut prs: Vec = prs.to_vec1()?; - if top_p <= 0.0 || top_p >= 1.0 { - // simply sample from the predicted probability distribution - self.sample_multi(&prs)? - } else { - // top-p (nucleus) sampling, clamping the least likely tokens to zero - self.sample_topp(&mut prs, top_p as f32)? + let next_token = match self.temperature { + None => self.sample_argmax(logits)?, + Some(temperature) => { + let logits = &(&logits / temperature)?; + let prs = candle_nn::ops::softmax_last_dim(logits)?; + let mut prs: Vec = prs.to_vec1()?; + let top_p = self.top_p.unwrap_or(1.); + if top_p <= 0.0 || top_p >= 1.0 { + // simply sample from the predicted probability distribution + self.sample_multinomial(&prs)? + } else { + // top-p (nucleus) sampling, clamping the least likely tokens to zero + self.sample_topp(&mut prs, top_p as f32)? + } } }; Ok(next_token) diff --git a/candle-transformers/src/models/quantized_llama.rs b/candle-transformers/src/models/quantized_llama.rs index da0bd0b0..2988b0fb 100644 --- a/candle-transformers/src/models/quantized_llama.rs +++ b/candle-transformers/src/models/quantized_llama.rs @@ -144,7 +144,7 @@ impl LayerWeights { let att = (q.matmul(&k.t()?)? / (self.head_dim as f64).sqrt())?; let mask = mask.broadcast_as(att.shape())?; let att = masked_fill(&att, &mask, f32::NEG_INFINITY)?; - let att = candle_nn::ops::softmax(&att, D::Minus1)?; + let att = candle_nn::ops::softmax_last_dim(&att)?; // Convert to contiguous as matmul doesn't support strided vs for now. let y = att.matmul(&v.contiguous()?)?; let y = y.transpose(1, 2)?.reshape(&[b_sz, seq_len, n_embd])?; From 91ec546febee4c6333cd65d95e8fd09e94499024 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 14 Sep 2023 22:16:31 +0200 Subject: [PATCH 095/150] More DiffNeXt. (#847) * More DiffNeXt. * Down blocks. --- .../src/models/wuerstchen/diffnext.rs | 146 +++++++++++++++++- 1 file changed, 144 insertions(+), 2 deletions(-) diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 8e5099f6..5e49437c 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -1,5 +1,5 @@ #![allow(unused)] -use super::common::{GlobalResponseNorm, ResBlock, TimestepBlock, WLayerNorm}; +use super::common::{AttnBlock, GlobalResponseNorm, ResBlock, TimestepBlock, WLayerNorm}; use candle::{DType, Module, Result, Tensor, D}; use candle_nn::VarBuilder; @@ -51,6 +51,27 @@ impl ResBlockStageB { } } +#[derive(Debug)] +struct SubBlock { + res_block: ResBlockStageB, + ts_block: TimestepBlock, + attn_block: Option, +} + +#[derive(Debug)] +struct DownBlock { + layer_norm: Option, + conv: Option, + sub_blocks: Vec, +} + +#[derive(Debug)] +struct UpBlock { + sub_blocks: Vec, + layer_norm: Option, + conv: Option, +} + #[derive(Debug)] pub struct WDiffNeXt { clip_mapper: candle_nn::Linear, @@ -58,18 +79,27 @@ pub struct WDiffNeXt { seq_norm: candle_nn::LayerNorm, embedding_conv: candle_nn::Conv2d, embedding_ln: WLayerNorm, + down_blocks: Vec, + up_blocks: Vec, + clf_ln: WLayerNorm, + clf_conv: candle_nn::Conv2d, + c_r: usize, } impl WDiffNeXt { pub fn new( c_in: usize, c_out: usize, - vb: VarBuilder, + c_r: usize, c_cond: usize, clip_embd: usize, patch_size: usize, + vb: VarBuilder, ) -> Result { const C_HIDDEN: [usize; 4] = [320, 640, 1280, 1280]; + const BLOCKS: [usize; 4] = [4, 4, 14, 4]; + const NHEAD: [usize; 4] = [0, 10, 20, 20]; + const INJECT_EFFNET: [bool; 4] = [false, true, true, true]; let clip_mapper = candle_nn::linear(clip_embd, c_cond, vb.pp("clip_mapper"))?; let effnet_mappers = vec![]; @@ -85,12 +115,124 @@ impl WDiffNeXt { Default::default(), vb.pp("embedding.2"), )?; + + let mut down_blocks = Vec::with_capacity(C_HIDDEN.len()); + for (i, &c_hidden) in C_HIDDEN.iter().enumerate() { + let vb = vb.pp("down_blocks").pp(i); + let (layer_norm, conv, start_layer_i) = if i > 0 { + let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1], vb.pp(0))?; + let cfg = candle_nn::Conv2dConfig { + stride: 2, + ..Default::default() + }; + let conv = candle_nn::conv2d(C_HIDDEN[i - 1], c_hidden, 2, cfg, vb.pp(1))?; + (Some(layer_norm), Some(conv), 2) + } else { + (None, None, 0) + }; + let mut sub_blocks = Vec::with_capacity(BLOCKS[i]); + let mut layer_i = start_layer_i; + for j in 0..BLOCKS[i] { + let c_skip = if INJECT_EFFNET[i] { c_cond } else { 0 }; + let res_block = ResBlockStageB::new(c_hidden, c_skip, 3, vb.pp(layer_i))?; + layer_i += 1; + let ts_block = TimestepBlock::new(c_hidden, c_r, vb.pp(layer_i))?; + layer_i += 1; + let attn_block = if j == 0 { + None + } else { + let attn_block = + AttnBlock::new(c_hidden, c_cond, NHEAD[i], true, vb.pp(layer_i))?; + layer_i += 1; + Some(attn_block) + }; + let sub_block = SubBlock { + res_block, + ts_block, + attn_block, + }; + sub_blocks.push(sub_block) + } + let down_block = DownBlock { + layer_norm, + conv, + sub_blocks, + }; + down_blocks.push(down_block) + } + + // TODO: populate. + let up_blocks = Vec::with_capacity(C_HIDDEN.len()); + + let clf_ln = WLayerNorm::new(C_HIDDEN[0], vb.pp("clf.0"))?; + let clf_conv = candle_nn::conv2d( + C_HIDDEN[0], + 2 * c_out * patch_size * patch_size, + 1, + Default::default(), + vb.pp("clf.1"), + )?; Ok(Self { clip_mapper, effnet_mappers, seq_norm, embedding_conv, embedding_ln, + down_blocks, + up_blocks, + clf_ln, + clf_conv, + c_r, }) } + + fn gen_r_embedding(&self, r: &Tensor) -> Result { + const MAX_POSITIONS: usize = 10000; + let r = (r * MAX_POSITIONS as f64)?; + let half_dim = self.c_r / 2; + let emb = (MAX_POSITIONS as f64).ln() / (half_dim - 1) as f64; + let emb = (Tensor::arange(0u32, half_dim as u32, r.device())?.to_dtype(DType::F32)? + * -emb)? + .exp()?; + let emb = r.unsqueeze(1)?.broadcast_mul(&emb.unsqueeze(0)?)?; + let emb = Tensor::cat(&[emb.sin()?, emb.cos()?], 1)?; + let emb = if self.c_r % 2 == 1 { + emb.pad_with_zeros(D::Minus1, 0, 1)? + } else { + emb + }; + emb.to_dtype(r.dtype()) + } + + fn gen_c_embeddings(&self, clip: &Tensor) -> Result { + clip.apply(&self.clip_mapper)?.apply(&self.seq_norm) + } + + pub fn forward( + &self, + xs: &Tensor, + r: &Tensor, + effnet: &Tensor, + clip: Option<&Tensor>, + ) -> Result { + const EPS: f64 = 1e-3; + + let r_embed = self.gen_r_embedding(r)?; + let clip = match clip { + None => None, + Some(clip) => Some(self.gen_c_embeddings(clip)?), + }; + let x_in = xs; + + // TODO: pixel unshuffle. + let xs = xs.apply(&self.embedding_conv)?.apply(&self.embedding_ln)?; + // TODO: down blocks + let level_outputs = xs.clone(); + // TODO: up blocks + let xs = level_outputs; + // TODO: pxel shuffle + let ab = xs.apply(&self.clf_ln)?.apply(&self.clf_conv)?.chunk(1, 2)?; + let b = ((candle_nn::ops::sigmoid(&ab[1])? * (1. - EPS * 2.))? + EPS)?; + (x_in - &ab[0])? / b + } } From 130fe5a087715fc4d7bf9b581ca7c11378736ac5 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 14 Sep 2023 23:24:56 +0200 Subject: [PATCH 096/150] Add the upblocks. (#853) --- candle-core/src/lib.rs | 8 ++- candle-nn/src/ops.rs | 4 ++ .../src/models/wuerstchen/diffnext.rs | 53 ++++++++++++++++++- .../src/models/wuerstchen/prior.rs | 3 +- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/candle-core/src/lib.rs b/candle-core/src/lib.rs index 0a64a5a6..52effdcf 100644 --- a/candle-core/src/lib.rs +++ b/candle-core/src/lib.rs @@ -110,7 +110,7 @@ impl ToUsize2 for (usize, usize) { } // A simple trait defining a module with forward method using a single argument. -pub trait Module: std::fmt::Debug { +pub trait Module { fn forward(&self, xs: &Tensor) -> Result; } @@ -119,3 +119,9 @@ impl Module for quantized::QMatMul { self.forward(xs) } } + +impl Result> Module for T { + fn forward(&self, xs: &Tensor) -> Result { + self(xs) + } +} diff --git a/candle-nn/src/ops.rs b/candle-nn/src/ops.rs index adf1451c..c4055792 100644 --- a/candle-nn/src/ops.rs +++ b/candle-nn/src/ops.rs @@ -44,6 +44,10 @@ pub fn sigmoid(xs: &Tensor) -> Result { (xs.neg()?.exp()? + 1.0)?.recip() } +pub fn leaky_relu(xs: &Tensor, negative_slope: f64) -> Result { + xs.relu()?.minimum(&(xs * negative_slope)?) +} + pub fn dropout(xs: &Tensor, drop_p: f32) -> Result { // This implementation is inefficient as it stores the full mask for the backward pass. // Instead we could just store the seed and have a specialized kernel that would both diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 5e49437c..7289a54d 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -161,8 +161,57 @@ impl WDiffNeXt { down_blocks.push(down_block) } - // TODO: populate. - let up_blocks = Vec::with_capacity(C_HIDDEN.len()); + let mut up_blocks = Vec::with_capacity(C_HIDDEN.len()); + for (i, &c_hidden) in C_HIDDEN.iter().enumerate().rev() { + let vb = vb.pp("up_blocks").pp(i); + let mut sub_blocks = Vec::with_capacity(BLOCKS[i]); + let mut layer_i = 0; + for j in 0..BLOCKS[i] { + let c_skip = if INJECT_EFFNET[i] { c_cond } else { 0 }; + let c_skip_res = if i < BLOCKS.len() - 1 && j == 0 { + c_hidden + c_skip + } else { + c_skip + }; + let res_block = ResBlockStageB::new(c_hidden, c_skip_res, 3, vb.pp(layer_i))?; + layer_i += 1; + let ts_block = TimestepBlock::new(c_hidden, c_r, vb.pp(layer_i))?; + layer_i += 1; + let attn_block = if j == 0 { + None + } else { + let attn_block = + AttnBlock::new(c_hidden, c_cond, NHEAD[i], true, vb.pp(layer_i))?; + layer_i += 1; + Some(attn_block) + }; + let sub_block = SubBlock { + res_block, + ts_block, + attn_block, + }; + sub_blocks.push(sub_block) + } + let (layer_norm, conv, start_layer_i) = if i > 0 { + let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1], vb.pp(layer_i))?; + layer_i += 1; + let cfg = candle_nn::Conv2dConfig { + stride: 2, + ..Default::default() + }; + let conv = candle_nn::conv2d(C_HIDDEN[i - 1], c_hidden, 2, cfg, vb.pp(layer_i))?; + layer_i += 1; + (Some(layer_norm), Some(conv), 2) + } else { + (None, None, 0) + }; + let up_block = UpBlock { + layer_norm, + conv, + sub_blocks, + }; + up_blocks.push(up_block) + } let clf_ln = WLayerNorm::new(C_HIDDEN[0], vb.pp("clf.0"))?; let clf_conv = candle_nn::conv2d( diff --git a/candle-transformers/src/models/wuerstchen/prior.rs b/candle-transformers/src/models/wuerstchen/prior.rs index eea70a02..5dd03778 100644 --- a/candle-transformers/src/models/wuerstchen/prior.rs +++ b/candle-transformers/src/models/wuerstchen/prior.rs @@ -85,10 +85,9 @@ impl WPrior { pub fn forward(&self, xs: &Tensor, r: &Tensor, c: &Tensor) -> Result { let x_in = xs; let mut xs = xs.apply(&self.projection)?; - // TODO: leaky relu let c_embed = c .apply(&self.cond_mapper_lin1)? - .relu()? + .apply(&|xs: &_| candle_nn::ops::leaky_relu(xs, 0.2))? .apply(&self.cond_mapper_lin2)?; let r_embed = self.gen_r_embedding(r)?; for block in self.blocks.iter() { From 5cefbba75777547f97abd92affcf9ef10ac36163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radam=C3=A9s=20Ajna?= Date: Thu, 14 Sep 2023 22:30:50 -0700 Subject: [PATCH 097/150] minor UI fixes (#856) * fixes * remove listener * remove event listener --- candle-wasm-examples/llama2-c/lib-example.html | 12 +++++++++--- candle-wasm-examples/whisper/lib-example.html | 9 ++++++--- candle-wasm-examples/yolo/lib-example.html | 17 ++++++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/candle-wasm-examples/llama2-c/lib-example.html b/candle-wasm-examples/llama2-c/lib-example.html index 22b12517..86fe9811 100644 --- a/candle-wasm-examples/llama2-c/lib-example.html +++ b/candle-wasm-examples/llama2-c/lib-example.html @@ -6,7 +6,7 @@ - + @@ -113,8 +113,14 @@ const handleMessage = (event) => { const { status, error, message, prompt, sentence } = event.data; if (status) updateStatus(event.data); - if (error) reject(new Error(error)); - if (status === "complete") resolve(event.data); + if (error) { + llamaWorker.removeEventListener("message", handleMessage); + reject(new Error(error)); + } + if (status === "complete") { + llamaWorker.removeEventListener("message", handleMessage); + resolve(event.data); + } }; controller.signal.addEventListener("abort", handleAbort); diff --git a/candle-wasm-examples/whisper/lib-example.html b/candle-wasm-examples/whisper/lib-example.html index ad48072b..3cfd87a7 100644 --- a/candle-wasm-examples/whisper/lib-example.html +++ b/candle-wasm-examples/whisper/lib-example.html @@ -6,7 +6,7 @@ - + @@ -51,18 +51,21 @@ mel_filtersURL, audioURL, }); - whisperWorker.addEventListener("message", (event) => { + function messageHandler(event) { console.log(event.data); if ("status" in event.data) { updateStatus(event.data); } if ("error" in event.data) { + whisperWorker.removeEventListener("message", messageHandler); reject(new Error(event.data.error)); } if (event.data.status === "complete") { + whisperWorker.removeEventListener("message", messageHandler); resolve(event.data); } - }); + } + whisperWorker.addEventListener("message", messageHandler); }); } diff --git a/candle-wasm-examples/yolo/lib-example.html b/candle-wasm-examples/yolo/lib-example.html index 8f7d07c7..d9f18975 100644 --- a/candle-wasm-examples/yolo/lib-example.html +++ b/candle-wasm-examples/yolo/lib-example.html @@ -6,7 +6,7 @@ - + @@ -199,17 +199,21 @@ confidence, iou_threshold, }); - yoloWorker.addEventListener("message", (event) => { + function handleMessage(event) { + console.log("message", event.data); if ("status" in event.data) { updateStatus(event.data.status); } if ("error" in event.data) { + yoloWorker.removeEventListener("message", handleMessage); reject(new Error(event.data.error)); } if (event.data.status === "complete") { + yoloWorker.removeEventListener("message", handleMessage); resolve(event.data); } - }); + } + yoloWorker.addEventListener("message", handleMessage); }); } // add event listener to detect button @@ -393,7 +397,7 @@ -
+
-
+

Examples:

Date: Thu, 14 Sep 2023 22:31:58 -0700 Subject: [PATCH 098/150] Add SAM UI Demo (#854) * fix tensor flattening * send image data back * sam ui worker example * SAM example * resize container * no need for this --- .../segment-anything/README.md | 26 ++ .../segment-anything/lib-example.html | 407 ++++++++++++++++++ .../segment-anything/samWorker.js | 156 +++++++ .../segment-anything/src/bin/m.rs | 22 +- 4 files changed, 609 insertions(+), 2 deletions(-) create mode 100644 candle-wasm-examples/segment-anything/README.md create mode 100644 candle-wasm-examples/segment-anything/lib-example.html create mode 100644 candle-wasm-examples/segment-anything/samWorker.js diff --git a/candle-wasm-examples/segment-anything/README.md b/candle-wasm-examples/segment-anything/README.md new file mode 100644 index 00000000..04ff2033 --- /dev/null +++ b/candle-wasm-examples/segment-anything/README.md @@ -0,0 +1,26 @@ +## Running Segment Anything Example + +Here, we provide two examples of how to run Whisper using a Candle-compiled WASM binary and runtimes. + +### Vanilla JS and WebWorkers + +To build and test the UI made in Vanilla JS and WebWorkers, first we need to build the WASM library: + +```bash +sh build-lib.sh +``` + +This will bundle the library under `./build` and we can import it inside our WebWorker like a normal JS module: + +```js +import init, { Model } from "./build/m.js"; +``` + +The full example can be found under `./lib-example.html`. All needed assets are fetched from the web, so no need to download anything. +Finally, you can preview the example by running a local HTTP server. For example: + +```bash +python -m http.server +``` + +Then open `http://localhost:8000/lib-example.html` in your browser. diff --git a/candle-wasm-examples/segment-anything/lib-example.html b/candle-wasm-examples/segment-anything/lib-example.html new file mode 100644 index 00000000..127b9152 --- /dev/null +++ b/candle-wasm-examples/segment-anything/lib-example.html @@ -0,0 +1,407 @@ + + + + Candle Segment Anything Model (SAM) Rust/WASM + + + + + + + + + + + + + + +
+ 🕯️ + +
+ + +
+
+

+ Note: + The model's first run may take a few seconds as it loads and caches + the model in the browser, and then creates the image embeddings. Any + subsequent clicks on points will be significantly faster. +

+
+
+
+
+ +
+ +
+
+
+ + + +
+ +
+ +
+ + +
+
+ +
+
+
+
+

Examples:

+ + + + +
+
+
+ + diff --git a/candle-wasm-examples/segment-anything/samWorker.js b/candle-wasm-examples/segment-anything/samWorker.js new file mode 100644 index 00000000..b90498de --- /dev/null +++ b/candle-wasm-examples/segment-anything/samWorker.js @@ -0,0 +1,156 @@ +//load the candle SAM Model wasm module +import init, { Model } from "./build/m.js"; + +async function fetchArrayBuffer(url, cacheModel = true) { + if (!cacheModel) + return new Uint8Array(await (await fetch(url)).arrayBuffer()); + const cacheName = "sam-candle-cache"; + const cache = await caches.open(cacheName); + const cachedResponse = await cache.match(url); + if (cachedResponse) { + const data = await cachedResponse.arrayBuffer(); + return new Uint8Array(data); + } + const res = await fetch(url, { cache: "force-cache" }); + cache.put(url, res.clone()); + return new Uint8Array(await res.arrayBuffer()); +} +class SAMModel { + static instance = {}; + // keep current image embeddings state + static imageArrayHash = {}; + // Add a new property to hold the current modelID + static currentModelID = null; + + static async getInstance(modelURL, modelID) { + if (!this.instance[modelID]) { + await init(); + + self.postMessage({ + status: "loading", + message: `Loading Model ${modelID}`, + }); + const weightsArrayU8 = await fetchArrayBuffer(modelURL); + this.instance[modelID] = new Model( + weightsArrayU8, + /tiny|mobile/.test(modelID) + ); + } else { + self.postMessage({ status: "loading", message: "Model Already Loaded" }); + } + // Set the current modelID to the modelID that was passed in + this.currentModelID = modelID; + return this.instance[modelID]; + } + + // Remove the modelID parameter from setImageEmbeddings + static setImageEmbeddings(imageArrayU8) { + // check if image embeddings are already set for this image and model + const imageArrayHash = this.getSimpleHash(imageArrayU8); + if ( + this.imageArrayHash[this.currentModelID] === imageArrayHash && + this.instance[this.currentModelID] + ) { + self.postMessage({ + status: "embedding", + message: "Embeddings Already Set", + }); + return; + } + this.imageArrayHash[this.currentModelID] = imageArrayHash; + this.instance[this.currentModelID].set_image_embeddings(imageArrayU8); + self.postMessage({ status: "embedding", message: "Embeddings Set" }); + } + + static getSimpleHash(imageArrayU8) { + // get simple hash of imageArrayU8 + let imageArrayHash = 0; + for (let i = 0; i < imageArrayU8.length; i += 100) { + imageArrayHash ^= imageArrayU8[i]; + } + return imageArrayHash.toString(16); + } +} + +async function createImageCanvas( + { mask_shape, mask_data }, // mask + { original_width, original_height, width, height } // original image +) { + const [_, __, shape_width, shape_height] = mask_shape; + const maskCanvas = new OffscreenCanvas(shape_width, shape_height); // canvas for mask + const maskCtx = maskCanvas.getContext("2d"); + const canvas = new OffscreenCanvas(original_width, original_height); // canvas for creating mask with original image size + const ctx = canvas.getContext("2d"); + + const imageData = maskCtx.createImageData( + maskCanvas.width, + maskCanvas.height + ); + const data = imageData.data; + + for (let p = 0; p < data.length; p += 4) { + data[p] = 0; + data[p + 1] = 0; + data[p + 2] = 0; + data[p + 3] = mask_data[p / 4] * 255; + } + maskCtx.putImageData(imageData, 0, 0); + + let sx, sy; + if (original_height < original_width) { + sy = original_height / original_width; + sx = 1; + } else { + sy = 1; + sx = original_width / original_height; + } + ctx.drawImage( + maskCanvas, + 0, + 0, + maskCanvas.width * sx, + maskCanvas.height * sy, + 0, + 0, + original_width, + original_height + ); + + const blob = await canvas.convertToBlob(); + return URL.createObjectURL(blob); +} + +self.addEventListener("message", async (event) => { + const { modelURL, modelID, imageURL, points } = event.data; + try { + self.postMessage({ status: "loading", message: "Starting SAM" }); + const sam = await SAMModel.getInstance(modelURL, modelID); + + self.postMessage({ status: "loading", message: "Loading Image" }); + const imageArrayU8 = await fetchArrayBuffer(imageURL, false); + + self.postMessage({ status: "embedding", message: "Creating Embeddings" }); + SAMModel.setImageEmbeddings(imageArrayU8); + if (!points) { + // no points only do the embeddings + self.postMessage({ + status: "complete-embedding", + message: "Embeddings Complete", + }); + return; + } + + self.postMessage({ status: "segmenting", message: "Segmenting" }); + const result = sam.mask_for_point(points.x, points.y); + const { mask, image } = JSON.parse(result); + const maskDataURL = await createImageCanvas(mask, image); + // Send the segment back to the main thread as JSON + self.postMessage({ + status: "complete", + message: "Segmentation Complete", + output: { maskURL: maskDataURL }, + }); + } catch (e) { + self.postMessage({ error: e }); + } +}); diff --git a/candle-wasm-examples/segment-anything/src/bin/m.rs b/candle-wasm-examples/segment-anything/src/bin/m.rs index b53f5b9b..949c18a0 100644 --- a/candle-wasm-examples/segment-anything/src/bin/m.rs +++ b/candle-wasm-examples/segment-anything/src/bin/m.rs @@ -98,7 +98,7 @@ impl Model { Some((x, y)), false, )?; - let iou = iou_predictions.to_vec1::()?[0]; + let iou = iou_predictions.flatten(0, 1)?.to_vec1::()?[0]; let mask_shape = mask.dims().to_vec(); let mask_data = mask.ge(0f32)?.flatten_all()?.to_vec1::()?; let mask = Mask { @@ -106,7 +106,13 @@ impl Model { mask_shape, mask_data, }; - let json = serde_json::to_string(&mask)?; + let image = Image { + original_width: embeddings.original_width, + original_height: embeddings.original_height, + width: embeddings.width, + height: embeddings.height, + }; + let json = serde_json::to_string(&MaskImage { mask, image })?; Ok(json) } } @@ -117,6 +123,18 @@ struct Mask { mask_shape: Vec, mask_data: Vec, } +#[derive(serde::Serialize, serde::Deserialize)] +struct Image { + original_width: u32, + original_height: u32, + width: u32, + height: u32, +} +#[derive(serde::Serialize, serde::Deserialize)] +struct MaskImage { + mask: Mask, + image: Image, +} fn main() { console_error_panic_hook::set_once(); From 0633c85514891141f5d8ded4e1a935ca430c97fe Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 15 Sep 2023 08:05:38 +0200 Subject: [PATCH 099/150] Add leaky-relu in the activation enum. (#858) --- candle-nn/src/activation.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/candle-nn/src/activation.rs b/candle-nn/src/activation.rs index 22e062b0..17467b31 100644 --- a/candle-nn/src/activation.rs +++ b/candle-nn/src/activation.rs @@ -10,6 +10,7 @@ pub enum Activation { NewGelu, Relu, Elu(f64), + LeakyRelu(f64), } impl super::Module for Activation { @@ -22,6 +23,7 @@ impl super::Module for Activation { Self::NewGelu => xs.gelu(), Self::Relu => xs.relu(), &Self::Elu(alpha) => xs.elu(alpha), + &Self::LeakyRelu(negative_slope) => crate::ops::leaky_relu(xs, negative_slope), } } } From 81a36b8713721e2cfd098fad04972bf823ef0d5d Mon Sep 17 00:00:00 2001 From: Ssslakter <67190162+Ssslakter@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:25:10 +0700 Subject: [PATCH 100/150] Add link error info (#851) * add link error info * grammar fix --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 533e7492..a2bb371c 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,24 @@ This is a bug in gcc-11 triggered by the Cuda compiler. To fix this, install a d env CANDLE_NVCC_CCBIN=/usr/lib/gcc/x86_64-linux-gnu/10 cargo ... ``` +#### Linking error on windows when running rustdoc or mdbook tests + +``` +Couldn't compile the test. +---- .\candle-book\src\inference\hub.md - Using_the_hub::Using_in_a_real_model_ (line 50) stdout ---- +error: linking with `link.exe` failed: exit code: 1181 +//very long chain of linking + = note: LINK : fatal error LNK1181: cannot open input file 'windows.0.48.5.lib' +``` + +Make sure you link all native libraries that might be located outside a project target, e.g., to run mdbook tests, you should run: + +``` +mdbook test candle-book -L .\target\debug\deps\ ` +-L native=$env:USERPROFILE\.cargo\registry\src\index.crates.io-6f17d22bba15001f\windows_x86_64_msvc-0.42.2\lib ` +-L native=$env:USERPROFILE\.cargo\registry\src\index.crates.io-6f17d22bba15001f\windows_x86_64_msvc-0.48.5\lib +``` + #### Tracking down errors You can set `RUST_BACKTRACE=1` to be provided with backtraces when a candle From 2746f2c4bec61d8f2c7947a297c46a74a3f799db Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 15 Sep 2023 11:14:02 +0200 Subject: [PATCH 101/150] DiffNeXt/unet (#859) * DiffNeXt/unet * Start adding the vae. * VAE residual block. * VAE forward pass. * Add pixel shuffling. * Actually use pixel shuffling. --- candle-core/src/shape.rs | 12 ++ candle-nn/src/ops.rs | 24 ++++ .../src/models/wuerstchen/diffnext.rs | 77 ++++++++++-- .../src/models/wuerstchen/mod.rs | 1 + .../src/models/wuerstchen/paella_vq.rs | 111 ++++++++++++++++++ 5 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 candle-transformers/src/models/wuerstchen/paella_vq.rs diff --git a/candle-core/src/shape.rs b/candle-core/src/shape.rs index b1f56817..4d500e7f 100644 --- a/candle-core/src/shape.rs +++ b/candle-core/src/shape.rs @@ -444,6 +444,18 @@ impl Dims for (D1, D2, D3, D4, D5) } } +impl Dims for (D1, D2, D3, D4, D5, D6) { + fn to_indexes_internal(self, shape: &Shape, op: &'static str) -> Result> { + let d0 = self.0.to_index(shape, op)?; + let d1 = self.1.to_index(shape, op)?; + let d2 = self.2.to_index(shape, op)?; + let d3 = self.3.to_index(shape, op)?; + let d4 = self.4.to_index(shape, op)?; + let d5 = self.5.to_index(shape, op)?; + Ok(vec![d0, d1, d2, d3, d4, d5]) + } +} + extract_dims!(dims0, 0, |_: &[usize]| (), ()); extract_dims!(dims1, 1, |d: &[usize]| d[0], usize); extract_dims!(dims2, 2, |d: &[usize]| (d[0], d[1]), (usize, usize)); diff --git a/candle-nn/src/ops.rs b/candle-nn/src/ops.rs index c4055792..16b2e924 100644 --- a/candle-nn/src/ops.rs +++ b/candle-nn/src/ops.rs @@ -189,3 +189,27 @@ impl candle::CustomOp1 for SoftmaxLastDim { pub fn softmax_last_dim(xs: &Tensor) -> Result { xs.apply_op1_no_bwd(&SoftmaxLastDim) } + +// https://pytorch.org/docs/stable/generated/torch.nn.PixelShuffle.html +pub fn pixel_shuffle(xs: &Tensor, upscale_factor: usize) -> Result { + let (b_size, c, h, w) = xs.dims4()?; + let out_c = c / upscale_factor / upscale_factor; + xs.reshape((b_size, out_c, upscale_factor, upscale_factor, h, w))? + .permute((0, 1, 4, 2, 5, 3))? + .reshape((b_size, out_c, h * upscale_factor, w * upscale_factor)) +} + +pub fn pixel_unshuffle(xs: &Tensor, downscale_factor: usize) -> Result { + let (b_size, c, h, w) = xs.dims4()?; + let out_c = c * downscale_factor * downscale_factor; + xs.reshape(( + b_size, + c, + h / downscale_factor, + downscale_factor, + w / downscale_factor, + downscale_factor, + ))? + .permute((0, 1, 3, 5, 2, 4))? + .reshape((b_size, out_c, h / downscale_factor, w / downscale_factor)) +} diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 7289a54d..33ca192e 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -75,7 +75,7 @@ struct UpBlock { #[derive(Debug)] pub struct WDiffNeXt { clip_mapper: candle_nn::Linear, - effnet_mappers: Vec, + effnet_mappers: Vec>, seq_norm: candle_nn::LayerNorm, embedding_conv: candle_nn::Conv2d, embedding_ln: WLayerNorm, @@ -84,6 +84,7 @@ pub struct WDiffNeXt { clf_ln: WLayerNorm, clf_conv: candle_nn::Conv2d, c_r: usize, + patch_size: usize, } impl WDiffNeXt { @@ -102,6 +103,7 @@ impl WDiffNeXt { const INJECT_EFFNET: [bool; 4] = [false, true, true, true]; let clip_mapper = candle_nn::linear(clip_embd, c_cond, vb.pp("clip_mapper"))?; + // TODO: populate effnet_mappers let effnet_mappers = vec![]; let cfg = candle_nn::layer_norm::LayerNormConfig { ..Default::default() @@ -232,6 +234,7 @@ impl WDiffNeXt { clf_ln, clf_conv, c_r, + patch_size, }) } @@ -273,14 +276,70 @@ impl WDiffNeXt { }; let x_in = xs; - // TODO: pixel unshuffle. - let xs = xs.apply(&self.embedding_conv)?.apply(&self.embedding_ln)?; - // TODO: down blocks - let level_outputs = xs.clone(); - // TODO: up blocks - let xs = level_outputs; - // TODO: pxel shuffle - let ab = xs.apply(&self.clf_ln)?.apply(&self.clf_conv)?.chunk(1, 2)?; + let mut xs = xs + .apply(&|xs: &_| candle_nn::ops::pixel_unshuffle(xs, self.patch_size))? + .apply(&self.embedding_conv)? + .apply(&self.embedding_ln)?; + + let mut level_outputs = Vec::new(); + for (i, down_block) in self.down_blocks.iter().enumerate() { + if let Some(ln) = &down_block.layer_norm { + xs = xs.apply(ln)? + } + if let Some(conv) = &down_block.conv { + xs = xs.apply(conv)? + } + let skip = match &self.effnet_mappers[i] { + None => None, + Some(m) => { + let effnet = effnet.interpolate2d(xs.dim(D::Minus2)?, xs.dim(D::Minus1)?)?; + Some(m.forward(&effnet)?) + } + }; + for block in down_block.sub_blocks.iter() { + xs = block.res_block.forward(&xs, skip.as_ref())?; + xs = block.ts_block.forward(&xs, &r_embed)?; + if let Some(attn_block) = &block.attn_block { + xs = attn_block.forward(&xs, clip.as_ref().unwrap())?; + } + } + level_outputs.push(xs.clone()) + } + level_outputs.reverse(); + + for (i, up_block) in self.up_blocks.iter().enumerate() { + let skip = match &self.effnet_mappers[self.down_blocks.len() + i] { + None => None, + Some(m) => { + let effnet = effnet.interpolate2d(xs.dim(D::Minus2)?, xs.dim(D::Minus1)?)?; + Some(m.forward(&effnet)?) + } + }; + for (j, block) in up_block.sub_blocks.iter().enumerate() { + let skip = if j == 0 && i > 0 { + Some(&level_outputs[i]) + } else { + None + }; + xs = block.res_block.forward(&xs, skip)?; + xs = block.ts_block.forward(&xs, &r_embed)?; + if let Some(attn_block) = &block.attn_block { + xs = attn_block.forward(&xs, clip.as_ref().unwrap())?; + } + } + if let Some(ln) = &up_block.layer_norm { + xs = xs.apply(ln)? + } + if let Some(conv) = &up_block.conv { + xs = xs.apply(conv)? + } + } + + let ab = xs + .apply(&self.clf_ln)? + .apply(&self.clf_conv)? + .apply(&|xs: &_| candle_nn::ops::pixel_shuffle(xs, self.patch_size))? + .chunk(1, 2)?; let b = ((candle_nn::ops::sigmoid(&ab[1])? * (1. - EPS * 2.))? + EPS)?; (x_in - &ab[0])? / b } diff --git a/candle-transformers/src/models/wuerstchen/mod.rs b/candle-transformers/src/models/wuerstchen/mod.rs index 81755dd1..435bdac2 100644 --- a/candle-transformers/src/models/wuerstchen/mod.rs +++ b/candle-transformers/src/models/wuerstchen/mod.rs @@ -1,3 +1,4 @@ pub mod common; pub mod diffnext; +pub mod paella_vq; pub mod prior; diff --git a/candle-transformers/src/models/wuerstchen/paella_vq.rs b/candle-transformers/src/models/wuerstchen/paella_vq.rs new file mode 100644 index 00000000..6301b7a1 --- /dev/null +++ b/candle-transformers/src/models/wuerstchen/paella_vq.rs @@ -0,0 +1,111 @@ +#![allow(unused)] +use super::common::{AttnBlock, ResBlock, TimestepBlock}; +use candle::{DType, Module, Result, Tensor, D}; +use candle_nn::VarBuilder; + +#[derive(Debug)] +struct MixingResidualBlock { + norm1: candle_nn::LayerNorm, + depthwise_conv: candle_nn::Conv2d, + norm2: candle_nn::LayerNorm, + channelwise_lin1: candle_nn::Linear, + channelwise_lin2: candle_nn::Linear, + gammas: Vec, +} + +impl MixingResidualBlock { + pub fn new(inp: usize, embed_dim: usize, vb: VarBuilder) -> Result { + let cfg = candle_nn::LayerNormConfig { + affine: false, + eps: 1e-6, + remove_mean: true, + }; + let norm1 = candle_nn::layer_norm(inp, cfg, vb.pp("norm1"))?; + let norm2 = candle_nn::layer_norm(inp, cfg, vb.pp("norm1"))?; + let cfg = candle_nn::Conv2dConfig { + groups: inp, + ..Default::default() + }; + let depthwise_conv = candle_nn::conv2d(inp, inp, 3, cfg, vb.pp("depthwise.1"))?; + let channelwise_lin1 = candle_nn::linear(inp, embed_dim, vb.pp("channelwise.0"))?; + let channelwise_lin2 = candle_nn::linear(embed_dim, inp, vb.pp("channelwise.2"))?; + let gammas = vb.get(6, "gammas")?.to_vec1::()?; + Ok(Self { + norm1, + depthwise_conv, + norm2, + channelwise_lin1, + channelwise_lin2, + gammas, + }) + } +} + +impl Module for MixingResidualBlock { + fn forward(&self, xs: &Tensor) -> Result { + let mods = &self.gammas; + let x_temp = xs + .permute((0, 2, 3, 1))? + .apply(&self.norm1)? + .permute((0, 3, 1, 2))? + .affine(1. + mods[0] as f64, mods[1] as f64)?; + // TODO: Add the ReplicationPad2d + let xs = (xs + x_temp.apply(&self.depthwise_conv)? * mods[2] as f64)?; + let x_temp = xs + .permute((0, 2, 3, 1))? + .apply(&self.norm2)? + .permute((0, 3, 1, 2))? + .affine(1. + mods[3] as f64, mods[4] as f64)?; + let x_temp = x_temp + .permute((0, 2, 3, 1))? + .apply(&self.channelwise_lin1)? + .gelu()? + .apply(&self.channelwise_lin2)? + .permute((0, 3, 1, 2))?; + xs + x_temp * mods[5] as f64 + } +} + +#[derive(Debug)] +struct PaellaVQ { + in_block_conv: candle_nn::Conv2d, + out_block_conv: candle_nn::Conv2d, + down_blocks: Vec<(Option, MixingResidualBlock)>, + down_blocks_conv: candle_nn::Conv2d, + down_blocks_bn: candle_nn::BatchNorm, + up_blocks_conv: candle_nn::Conv2d, + up_blocks: Vec<(MixingResidualBlock, Option)>, +} + +impl PaellaVQ { + pub fn encode(&self, xs: &Tensor) -> Result { + let mut xs = candle_nn::ops::pixel_unshuffle(xs, 2)?.apply(&self.in_block_conv)?; + for down_block in self.down_blocks.iter() { + if let Some(conv) = &down_block.0 { + xs = xs.apply(conv)? + } + xs = xs.apply(&down_block.1)? + } + xs.apply(&self.down_blocks_conv)? + .apply(&self.down_blocks_bn) + // TODO: quantizer + } + + pub fn decode(&self, xs: &Tensor) -> Result { + let mut xs = xs.apply(&self.up_blocks_conv)?; + for up_block in self.up_blocks.iter() { + xs = xs.apply(&up_block.0)?; + if let Some(conv) = &up_block.1 { + xs = xs.apply(conv)? + } + } + xs.apply(&self.out_block_conv)? + .apply(&|xs: &_| candle_nn::ops::pixel_shuffle(xs, 2)) + } +} + +impl Module for PaellaVQ { + fn forward(&self, xs: &Tensor) -> Result { + self.decode(&self.encode(xs)?) + } +} From 107d3d953070f7817b3aaac9ed8ca0fed7030d01 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 15 Sep 2023 11:38:38 +0200 Subject: [PATCH 102/150] Add the embed mapper convolutions. (#860) --- .../src/models/wuerstchen/diffnext.rs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 33ca192e..74e1836c 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -101,10 +101,39 @@ impl WDiffNeXt { const BLOCKS: [usize; 4] = [4, 4, 14, 4]; const NHEAD: [usize; 4] = [0, 10, 20, 20]; const INJECT_EFFNET: [bool; 4] = [false, true, true, true]; + const EFFNET_EMBD: usize = 16; let clip_mapper = candle_nn::linear(clip_embd, c_cond, vb.pp("clip_mapper"))?; - // TODO: populate effnet_mappers - let effnet_mappers = vec![]; + let mut effnet_mappers = Vec::with_capacity(2 * INJECT_EFFNET.len()); + let vb_e = vb.pp("effnet_mappers"); + for (i, &inject) in INJECT_EFFNET.iter().enumerate() { + let c = if inject { + Some(candle_nn::conv2d( + EFFNET_EMBD, + c_cond, + 1, + Default::default(), + vb_e.pp(i), + )?) + } else { + None + }; + effnet_mappers.push(c) + } + for (i, &inject) in INJECT_EFFNET.iter().rev().enumerate() { + let c = if inject { + Some(candle_nn::conv2d( + EFFNET_EMBD, + c_cond, + 1, + Default::default(), + vb_e.pp(i + INJECT_EFFNET.len()), + )?) + } else { + None + }; + effnet_mappers.push(c) + } let cfg = candle_nn::layer_norm::LayerNormConfig { ..Default::default() }; From 30be5b6660ca86f8ddd2cca88890cf4e40e45e12 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 15 Sep 2023 15:06:21 +0200 Subject: [PATCH 103/150] Replication pad (#861) * Add the embed mapper convolutions. * Add the replication pad layer. * Use the replication-pad op. * Tweak a todo. --- candle-nn/src/ops.rs | 15 +++++++++++++++ .../src/models/wuerstchen/paella_vq.rs | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/candle-nn/src/ops.rs b/candle-nn/src/ops.rs index 16b2e924..1256a076 100644 --- a/candle-nn/src/ops.rs +++ b/candle-nn/src/ops.rs @@ -213,3 +213,18 @@ pub fn pixel_unshuffle(xs: &Tensor, downscale_factor: usize) -> Result { .permute((0, 1, 3, 5, 2, 4))? .reshape((b_size, out_c, h / downscale_factor, w / downscale_factor)) } + +// https://pytorch.org/docs/stable/generated/torch.nn.ReplicationPad2d.html +pub fn replication_pad2d(xs: &Tensor, pad: usize) -> Result { + match pad { + 0 => Ok(xs.clone()), + 1 => { + let (_b_size, _c, h, w) = xs.dims4()?; + let (first, last) = (xs.narrow(3, 0, 1)?, xs.narrow(3, w - 1, 1)?); + let xs = Tensor::cat(&[&first, xs, &last], 3)?; + let (first, last) = (xs.narrow(2, 0, 1)?, xs.narrow(2, h - 1, 1)?); + Tensor::cat(&[&first, &xs, &last], 2) + } + n => candle::bail!("replication-pad with a size of {n} is not supported"), + } +} diff --git a/candle-transformers/src/models/wuerstchen/paella_vq.rs b/candle-transformers/src/models/wuerstchen/paella_vq.rs index 6301b7a1..6589a07d 100644 --- a/candle-transformers/src/models/wuerstchen/paella_vq.rs +++ b/candle-transformers/src/models/wuerstchen/paella_vq.rs @@ -49,7 +49,7 @@ impl Module for MixingResidualBlock { .apply(&self.norm1)? .permute((0, 3, 1, 2))? .affine(1. + mods[0] as f64, mods[1] as f64)?; - // TODO: Add the ReplicationPad2d + let x_temp = candle_nn::ops::replication_pad2d(&x_temp, 1)?; let xs = (xs + x_temp.apply(&self.depthwise_conv)? * mods[2] as f64)?; let x_temp = xs .permute((0, 2, 3, 1))? @@ -88,10 +88,10 @@ impl PaellaVQ { } xs.apply(&self.down_blocks_conv)? .apply(&self.down_blocks_bn) - // TODO: quantizer } pub fn decode(&self, xs: &Tensor) -> Result { + // TODO: quantizer if we want to support `force_not_quantize=False`. let mut xs = xs.apply(&self.up_blocks_conv)?; for up_block in self.up_blocks.iter() { xs = xs.apply(&up_block.0)?; From c2007ac88fb0dd6fa6f82f6624693a0095db2edb Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 15 Sep 2023 16:11:11 +0200 Subject: [PATCH 104/150] W fixes. (#862) --- .../src/models/wuerstchen/diffnext.rs | 19 +++++++++++-------- .../src/models/wuerstchen/paella_vq.rs | 6 ++---- .../src/models/wuerstchen/prior.rs | 3 +-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 74e1836c..001b35d7 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -1,5 +1,4 @@ -#![allow(unused)] -use super::common::{AttnBlock, GlobalResponseNorm, ResBlock, TimestepBlock, WLayerNorm}; +use super::common::{AttnBlock, GlobalResponseNorm, TimestepBlock, WLayerNorm}; use candle::{DType, Module, Result, Tensor, D}; use candle_nn::VarBuilder; @@ -223,7 +222,7 @@ impl WDiffNeXt { }; sub_blocks.push(sub_block) } - let (layer_norm, conv, start_layer_i) = if i > 0 { + let (layer_norm, conv) = if i > 0 { let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1], vb.pp(layer_i))?; layer_i += 1; let cfg = candle_nn::Conv2dConfig { @@ -231,10 +230,9 @@ impl WDiffNeXt { ..Default::default() }; let conv = candle_nn::conv2d(C_HIDDEN[i - 1], c_hidden, 2, cfg, vb.pp(layer_i))?; - layer_i += 1; - (Some(layer_norm), Some(conv), 2) + (Some(layer_norm), Some(conv)) } else { - (None, None, 0) + (None, None) }; let up_block = UpBlock { layer_norm, @@ -337,7 +335,7 @@ impl WDiffNeXt { level_outputs.reverse(); for (i, up_block) in self.up_blocks.iter().enumerate() { - let skip = match &self.effnet_mappers[self.down_blocks.len() + i] { + let effnet_c = match &self.effnet_mappers[self.down_blocks.len() + i] { None => None, Some(m) => { let effnet = effnet.interpolate2d(xs.dim(D::Minus2)?, xs.dim(D::Minus1)?)?; @@ -350,7 +348,12 @@ impl WDiffNeXt { } else { None }; - xs = block.res_block.forward(&xs, skip)?; + let skip = match (skip, effnet_c.as_ref()) { + (Some(skip), Some(effnet_c)) => Some(Tensor::cat(&[skip, effnet_c], 1)?), + (None, Some(skip)) | (Some(skip), None) => Some(skip.clone()), + (None, None) => None, + }; + xs = block.res_block.forward(&xs, skip.as_ref())?; xs = block.ts_block.forward(&xs, &r_embed)?; if let Some(attn_block) = &block.attn_block { xs = attn_block.forward(&xs, clip.as_ref().unwrap())?; diff --git a/candle-transformers/src/models/wuerstchen/paella_vq.rs b/candle-transformers/src/models/wuerstchen/paella_vq.rs index 6589a07d..1268047a 100644 --- a/candle-transformers/src/models/wuerstchen/paella_vq.rs +++ b/candle-transformers/src/models/wuerstchen/paella_vq.rs @@ -1,10 +1,8 @@ -#![allow(unused)] -use super::common::{AttnBlock, ResBlock, TimestepBlock}; -use candle::{DType, Module, Result, Tensor, D}; +use candle::{Module, Result, Tensor}; use candle_nn::VarBuilder; #[derive(Debug)] -struct MixingResidualBlock { +pub struct MixingResidualBlock { norm1: candle_nn::LayerNorm, depthwise_conv: candle_nn::Conv2d, norm2: candle_nn::LayerNorm, diff --git a/candle-transformers/src/models/wuerstchen/prior.rs b/candle-transformers/src/models/wuerstchen/prior.rs index 5dd03778..a9e3e793 100644 --- a/candle-transformers/src/models/wuerstchen/prior.rs +++ b/candle-transformers/src/models/wuerstchen/prior.rs @@ -1,6 +1,5 @@ -#![allow(unused)] use super::common::{AttnBlock, ResBlock, TimestepBlock}; -use candle::{DType, Module, Result, Tensor, D}; +use candle::{DType, Result, Tensor, D}; use candle_nn::VarBuilder; #[derive(Debug)] From 3e49f8fce52c6b8f361bfd37d541a99b5e1f8c63 Mon Sep 17 00:00:00 2001 From: Juarez Bochi Date: Fri, 15 Sep 2023 13:05:12 -0700 Subject: [PATCH 105/150] Implement T5 decoding (#864) * Load t5 decoder * Run enc, dec, and lm head, but no cross attn * Cross-attention over key_value_states * New arg for decoder input ids * Add mask, don't forward position biases through decoder * Update t5 examples * Clippy + rustfmt --- candle-examples/examples/t5/README.md | 34 +++-- candle-examples/examples/t5/main.rs | 106 ++++++++++++--- candle-transformers/src/models/t5.rs | 181 ++++++++++++++++++++++---- 3 files changed, 260 insertions(+), 61 deletions(-) diff --git a/candle-examples/examples/t5/README.md b/candle-examples/examples/t5/README.md index 66952395..c6ea2125 100644 --- a/candle-examples/examples/t5/README.md +++ b/candle-examples/examples/t5/README.md @@ -1,17 +1,25 @@ # candle-t5 -Generates embeddings using a T5 model. It doesn't support generation yet. +## Encoder-decoder example: ```bash -$ cargo run --example t5 -- --model-id t5-large --prompt 'how tall is obama' --n 1 -Loaded and encoded 2.014244792s -[[[-0.3174, -0.1462, 0.0065, ..., -0.0579, -0.0581, 0.1387], - [-0.2905, -0.1945, -0.0685, ..., -0.2457, -0.5137, -0.1760], - [-0.0591, -0.0213, -0.0241, ..., -0.0210, 0.0491, -0.0300], - ... - [-0.4333, 0.0027, -0.0609, ..., 0.3069, -0.2252, 0.3306], - [-0.1458, 0.1323, -0.0138, ..., 0.3000, -0.4550, -0.0384], - [ 0.0397, 0.0485, -0.2373, ..., 0.2578, -0.2650, -0.4356]]] -Tensor[[1, 9, 1024], f32] -Took 2.1363425s -``` \ No newline at end of file +$ cargo run --example t5 -- --model-id "t5-small" --prompt "translate to German: A beautiful candle." --decode +... +Running on CPU, to run on GPU, build this example with `--features cuda` + Eine schöne Kerze. +9 tokens generated (2.42 token/s) +``` + +## Sentence embedding example: + +```bash +$ cargo run --example t5 -- --model-id "t5-small" --prompt "A beautiful candle." +... +[[[ 0.0515, -0.0541, -0.0761, ..., -0.0392, 0.1511, -0.0265], + [-0.0974, 0.0998, -0.1659, ..., -0.2450, 0.1738, -0.0164], + [ 0.0624, -0.1024, 0.0430, ..., -0.1388, 0.0564, -0.2962], + [-0.0389, -0.1173, 0.0026, ..., 0.1064, -0.1065, 0.0990], + [ 0.1300, 0.0027, -0.0326, ..., 0.0026, -0.0317, 0.0851]]] +Tensor[[1, 5, 512], f32] +Took 303.766583ms +``` diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index 1e182974..00291609 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -3,18 +3,22 @@ extern crate intel_mkl_src; #[cfg(feature = "accelerate")] extern crate accelerate_src; +use std::io::Write; +use std::path::PathBuf; + use candle_transformers::models::t5; use anyhow::{anyhow, Error as E, Result}; -use candle::{DType, Tensor}; +use candle::{DType, Device, Tensor}; use candle_nn::VarBuilder; +use candle_transformers::generation::LogitsProcessor; use clap::Parser; use hf_hub::{api::sync::Api, Cache, Repo, RepoType}; use tokenizers::Tokenizer; const DTYPE: DType = DType::F32; -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Clone)] #[command(author, version, about, long_about = None)] struct Args { /// Run on CPU rather than on GPU. @@ -36,7 +40,11 @@ struct Args { #[arg(long)] revision: Option, - /// Compute embeddings for this prompt, otherwise compute sentence similarities. + /// Enable decoding. + #[arg(long)] + decode: bool, + + /// Use this prompt, otherwise compute sentence similarities. #[arg(long)] prompt: Option, @@ -49,12 +57,18 @@ struct Args { normalize_embeddings: bool, } -impl Args { - fn build_model_and_tokenizer(&self) -> Result<(t5::T5EncoderModel, Tokenizer)> { - let device = candle_examples::device(self.cpu)?; +struct T5ModelBuilder { + device: Device, + config: t5::Config, + weights_filename: PathBuf, +} + +impl T5ModelBuilder { + pub fn load(args: &Args) -> Result<(Self, Tokenizer)> { + let device = candle_examples::device(args.cpu)?; let default_model = "t5-small".to_string(); let default_revision = "refs/pr/15".to_string(); - let (model_id, revision) = match (self.model_id.to_owned(), self.revision.to_owned()) { + let (model_id, revision) = match (args.model_id.to_owned(), args.revision.to_owned()) { (Some(model_id), Some(revision)) => (model_id, revision), (Some(model_id), None) => (model_id, "main".to_string()), (None, Some(revision)) => (default_model, revision), @@ -62,7 +76,7 @@ impl Args { }; let repo = Repo::with_revision(model_id, RepoType::Model, revision); - let (config_filename, tokenizer_filename, weights_filename) = if self.offline { + let (config_filename, tokenizer_filename, weights_filename) = if args.offline { let cache = Cache::default().repo(repo); ( cache @@ -87,18 +101,36 @@ impl Args { let config = std::fs::read_to_string(config_filename)?; let config: t5::Config = serde_json::from_str(&config)?; let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?; + Ok(( + Self { + device, + config, + weights_filename, + }, + tokenizer, + )) + } - let weights = unsafe { candle::safetensors::MmapedFile::new(weights_filename)? }; + pub fn build_encoder(&self) -> Result { + let weights = + unsafe { candle::safetensors::MmapedFile::new(self.weights_filename.clone())? }; let weights = weights.deserialize()?; - let vb = VarBuilder::from_safetensors(vec![weights], DTYPE, &device); - let model = t5::T5EncoderModel::load(vb, &config)?; - Ok((model, tokenizer)) + let vb = VarBuilder::from_safetensors(vec![weights], DTYPE, &self.device); + Ok(t5::T5EncoderModel::load(vb, &self.config)?) + } + + pub fn build_conditional_generation(&self) -> Result { + let weights = + unsafe { candle::safetensors::MmapedFile::new(self.weights_filename.clone())? }; + let weights = weights.deserialize()?; + let vb = VarBuilder::from_safetensors(vec![weights], DTYPE, &self.device); + Ok(t5::T5ForConditionalGeneration::load(vb, &self.config)?) } } fn main() -> Result<()> { let args = Args::parse(); - let (model, mut tokenizer) = args.build_model_and_tokenizer()?; + let (builder, mut tokenizer) = T5ModelBuilder::load(&args)?; let tokenizer = tokenizer .with_padding(None) .with_truncation(None) @@ -110,17 +142,51 @@ fn main() -> Result<()> { .map_err(E::msg)? .get_ids() .to_vec(); - let token_ids = Tensor::new(&tokens[..], model.device())?.unsqueeze(0)?; - for idx in 0..args.n { - let start = std::time::Instant::now(); - let ys = model.forward(&token_ids)?; - if idx == 0 { - println!("{ys}"); + let input_token_ids = Tensor::new(&tokens[..], &builder.device)?.unsqueeze(0)?; + if !args.decode { + let model = builder.build_encoder()?; + for idx in 0..args.n { + let start = std::time::Instant::now(); + let ys = model.forward(&input_token_ids)?; + if idx == 0 { + println!("{ys}"); + } + println!("Took {:?}", start.elapsed()); } - println!("Took {:?}", start.elapsed()); + } else { + let model = builder.build_conditional_generation()?; + let mut output_token_ids = [builder.config.pad_token_id as u32].to_vec(); + let mut logits_processor = LogitsProcessor::new(299792458, None, None); + let start = std::time::Instant::now(); + + for _index in 0.. { + if output_token_ids.len() > 512 { + break; + } + let decoder_token_ids = + Tensor::new(&output_token_ids[..], &builder.device)?.unsqueeze(0)?; + let logits = model.forward(&input_token_ids, &decoder_token_ids)?; + let next_token_id = logits_processor.sample(&logits.flatten_to(1)?)?; + if (next_token_id as usize) == builder.config.eos_token_id { + break; + } + output_token_ids.push(next_token_id); + if let Some(text) = tokenizer.id_to_token(next_token_id) { + let text = text.replace('▁', " ").replace("<0x0A>", "\n"); + print!("{text}"); + std::io::stdout().flush()?; + } + } + let dt = start.elapsed(); + println!( + "\n{} tokens generated ({:.2} token/s)\n", + tokens.len(), + tokens.len() as f64 / dt.as_secs_f64(), + ); } } None => { + let model = builder.build_encoder()?; let sentences = [ "The cat sits outside", "A man is playing guitar", diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index de7de496..c35dea0b 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -18,6 +18,21 @@ fn default_use_cache() -> bool { true } +fn get_mask(size: usize, device: &Device) -> Result { + let mask: Vec<_> = (0..size) + .flat_map(|i| (0..size).map(move |j| u8::from(j > i))) + .collect(); + let result = Tensor::from_slice(&mask, (size, size), device)?; + Ok(result) +} + +fn masked_fill(on_false: &Tensor, mask: &Tensor, on_true: f32) -> Result { + let shape = mask.shape(); + let on_true = Tensor::new(on_true, on_false.device())?.broadcast_as(shape.dims())?; + let m = mask.where_cond(&on_true, on_false)?; + Ok(m) +} + #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Config { vocab_size: usize, @@ -40,8 +55,8 @@ pub struct Config { is_encoder_decoder: bool, #[serde(default = "default_use_cache")] use_cache: bool, - pad_token_id: usize, - eos_token_id: usize, + pub pad_token_id: usize, + pub eos_token_id: usize, } impl Default for Config { @@ -233,13 +248,13 @@ struct T5Attention { } impl T5Attention { - fn load(h: bool, vb: VarBuilder, cfg: &Config) -> Result { + fn load(has_relative_attention_bias: bool, vb: VarBuilder, cfg: &Config) -> Result { let inner_dim = cfg.num_heads * cfg.d_kv; let q = linear_no_bias(cfg.d_model, inner_dim, vb.pp("q"))?; let k = linear_no_bias(cfg.d_model, inner_dim, vb.pp("k"))?; let v = linear_no_bias(cfg.d_model, inner_dim, vb.pp("v"))?; let o = linear_no_bias(inner_dim, cfg.d_model, vb.pp("o"))?; - let relative_attention_bias = if h { + let relative_attention_bias = if has_relative_attention_bias { let emb = embedding( cfg.relative_attention_num_buckets, cfg.num_heads, @@ -267,26 +282,46 @@ impl T5Attention { &self, xs: &Tensor, position_bias: Option<&Tensor>, + key_value_states: Option<&Tensor>, + mask: Option<&Tensor>, ) -> Result<(Tensor, Option)> { - // TODO: Apply the mask(s)? + // Performs Self-attention (if key_value_states is None) or attention + // over source sentence (provided by key_value_states). // TODO: kv caching. - let (b_sz, seq_len) = (xs.dim(0)?, xs.dim(1)?); + let kv_input = match key_value_states { + None => xs, + Some(key_value_states) => key_value_states, + }; + let (b_sz, q_len) = (xs.dim(0)?, xs.dim(1)?); + let kv_len = kv_input.dim(1)?; let q = self.q.forward(xs)?; - let k = self.k.forward(xs)?; - let v = self.v.forward(xs)?; + let k = self.k.forward(kv_input)?; + let v = self.v.forward(kv_input)?; let q = q - .reshape((b_sz, seq_len, self.n_heads, self.d_kv))? + .reshape((b_sz, q_len, self.n_heads, self.d_kv))? .transpose(1, 2)? .contiguous()?; let k = k - .reshape((b_sz, seq_len, self.n_heads, self.d_kv))? + .reshape((b_sz, kv_len, self.n_heads, self.d_kv))? .transpose(1, 2)? .contiguous()?; let v = v - .reshape((b_sz, seq_len, self.n_heads, self.d_kv))? + .reshape((b_sz, kv_len, self.n_heads, self.d_kv))? .transpose(1, 2)? .contiguous()?; + // TODO: Use flash_attn. let scores = q.matmul(&k.t()?)?; + let scores = match mask { + None => scores, + Some(mask) => masked_fill( + &scores, + &mask + .unsqueeze(0)? + .unsqueeze(0)? + .repeat((b_sz, self.n_heads))?, + f32::NEG_INFINITY, + )?, + }; let (scores, position_bias) = match position_bias { Some(position_bias) => ( @@ -296,14 +331,12 @@ impl T5Attention { None => match &self.relative_attention_bias { None => (scores, None), Some(relative_attention_bias) => { - let query_length = seq_len; - let key_length = seq_len; // This only handles the bidirectional case. let num_buckets = self.relative_attention_num_buckets as u32 / 2; let max_exact = num_buckets / 2; - let relative_position = (0..query_length as u32) + let relative_position = (0..q_len as u32) .map(|i| { - (0..key_length as u32) + (0..kv_len as u32) .map(|j| { if i < j { if j - i < max_exact { @@ -348,7 +381,7 @@ impl T5Attention { let attn_output = attn_weights.matmul(&v)?; let attn_output = attn_output .transpose(1, 2)? - .reshape((b_sz, seq_len, self.inner_dim))?; + .reshape((b_sz, q_len, self.inner_dim))?; let attn_output = self.o.forward(&attn_output)?; Ok((attn_output, position_bias)) } @@ -375,24 +408,49 @@ impl T5LayerSelfAttention { &self, xs: &Tensor, position_bias: Option<&Tensor>, + mask: Option<&Tensor>, ) -> Result<(Tensor, Option)> { let normed_xs = self.layer_norm.forward(xs)?; - let (ys, position_bias) = self.self_attention.forward(&normed_xs, position_bias)?; + let (ys, position_bias) = + self.self_attention + .forward(&normed_xs, position_bias, None, mask)?; let ys = (xs + ys)?; Ok((ys, position_bias)) } } #[derive(Debug)] -struct T5LayerCrossAttention {} +struct T5LayerCrossAttention { + cross_attention: T5Attention, + layer_norm: T5LayerNorm, +} impl T5LayerCrossAttention { - fn load(_vb: VarBuilder, _cfg: &Config) -> Result { - todo!() + fn load(vb: VarBuilder, cfg: &Config) -> Result { + let cross_attention = T5Attention::load(false, vb.pp("EncDecAttention"), cfg)?; + let layer_norm = + T5LayerNorm::load(cfg.d_model, cfg.layer_norm_epsilon, vb.pp("layer_norm"))?; + Ok(Self { + cross_attention, + layer_norm, + }) } - fn forward(&self, _xs: &Tensor) -> Result { - todo!() + fn forward( + &self, + hidden_states: &Tensor, + position_bias: Option<&Tensor>, + key_value_states: &Tensor, + ) -> Result<(Tensor, Option)> { + let normed_hidden_states = self.layer_norm.forward(hidden_states)?; + let (ys, position_bias) = self.cross_attention.forward( + &normed_hidden_states, + position_bias, + Some(key_value_states), + None, + )?; + let ys = (hidden_states + ys)?; + Ok((ys, position_bias)) } } @@ -425,11 +483,17 @@ impl T5Block { &self, xs: &Tensor, position_bias: Option<&Tensor>, + encoder_hidden_states: Option<&Tensor>, ) -> Result<(Tensor, Option)> { - let (mut xs, position_bias) = self.self_attn.forward(xs, position_bias)?; + // TODO: Cache masks + let mask = match self.cross_attn.is_some() { + true => Some(get_mask(xs.dim(1)?, xs.device())?), + false => None, + }; + let (mut xs, position_bias) = self.self_attn.forward(xs, position_bias, mask.as_ref())?; // TODO: clamp for f16? if let Some(cross_attn) = &self.cross_attn { - xs = cross_attn.forward(&xs)?; + (xs, _) = cross_attn.forward(&xs, None, encoder_hidden_states.unwrap())?; // TODO: clamp for f16? } let xs = self.ff.forward(&xs)?; @@ -462,13 +526,20 @@ impl T5Stack { }) } - fn forward(&self, input_ids: &Tensor) -> Result { + fn forward( + &self, + input_ids: &Tensor, + encoder_hidden_states: Option<&Tensor>, + ) -> Result { let input_embeds = self.shared.as_ref().forward(input_ids)?; let mut hidden_states = input_embeds; let mut position_bias = None; for block in self.block.iter() { - (hidden_states, position_bias) = - block.forward(&hidden_states, position_bias.as_ref())? + (hidden_states, position_bias) = block.forward( + &hidden_states, + position_bias.as_ref(), + encoder_hidden_states, + )? } self.final_layer_norm.forward(&hidden_states) } @@ -492,7 +563,61 @@ impl T5EncoderModel { } pub fn forward(&self, input_ids: &Tensor) -> Result { - self.encoder.forward(input_ids) + self.encoder.forward(input_ids, None) + } + + pub fn device(&self) -> &Device { + &self.device + } +} + +#[derive(Debug)] +pub struct T5ForConditionalGeneration { + encoder: T5Stack, + decoder: T5Stack, + shared: Arc, + device: Device, +} + +impl T5ForConditionalGeneration { + pub fn load(vb: VarBuilder, cfg: &Config) -> Result { + assert!(cfg.is_encoder_decoder); + let shared = embedding(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; + let shared = Arc::new(shared); + + let mut encoder_cfg = cfg.clone(); + encoder_cfg.is_decoder = false; + encoder_cfg.use_cache = false; + encoder_cfg.is_encoder_decoder = false; + let encoder = T5Stack::load(vb.pp("encoder"), &shared, &encoder_cfg)?; + + let mut decoder_cfg = cfg.clone(); + decoder_cfg.is_decoder = true; + decoder_cfg.is_encoder_decoder = false; + decoder_cfg.num_layers = cfg.num_decoder_layers.unwrap_or(cfg.num_layers); + let decoder = T5Stack::load(vb.pp("decoder"), &shared, &decoder_cfg)?; + + Ok(Self { + encoder, + decoder, + shared, + device: vb.device().clone(), + }) + } + + pub fn forward(&self, input_ids: &Tensor, decoder_input_ids: &Tensor) -> Result { + let encoder_output = self.encoder.forward(input_ids, None)?; + let decoder_output = self + .decoder + .forward(decoder_input_ids, Some(&encoder_output))?; + let sequence_output = decoder_output + .narrow(1, decoder_output.dim(1)? - 1, 1)? + .squeeze(1)?; + // TODO: check cfg.tie_word_embeddings to load from model instead. + let lm_head_weights = self.shared.embeddings().t()?; + let output = sequence_output.matmul(&lm_head_weights)?; + // TODO: Rescale output before projecting on vocab? * (self.model_dim**-0.5) + Ok(output) } pub fn device(&self) -> &Device { From 635012d770a75033081008a22044804d277fafa8 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Fri, 15 Sep 2023 23:15:40 +0200 Subject: [PATCH 106/150] Do not backprop through argmin/argmax. (#865) --- candle-core/src/backprop.rs | 4 +++- candle-core/src/tensor.rs | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/candle-core/src/backprop.rs b/candle-core/src/backprop.rs index b930a9f4..9c8f685f 100644 --- a/candle-core/src/backprop.rs +++ b/candle-core/src/backprop.rs @@ -98,7 +98,7 @@ impl Tensor { | Op::Copy(node) | Op::Broadcast(node) | Op::Cmp(node, _) - | Op::Reduce(node, _, _) + | Op::Reduce(node, ReduceOp::Min | ReduceOp::Sum | ReduceOp::Max, _) | Op::ToDType(node) | Op::ToDevice(node) | Op::Transpose(node, _, _) @@ -112,6 +112,7 @@ impl Tensor { track_grad |= tg; nodes } + Op::Reduce(_, ReduceOp::ArgMin | ReduceOp::ArgMax, _) => nodes, } } else { nodes @@ -521,6 +522,7 @@ impl Tensor { } } +#[derive(Debug)] pub struct GradStore(HashMap); impl GradStore { diff --git a/candle-core/src/tensor.rs b/candle-core/src/tensor.rs index 4388bf77..61f576cf 100644 --- a/candle-core/src/tensor.rs +++ b/candle-core/src/tensor.rs @@ -666,7 +666,12 @@ impl Tensor { let storage = self.storage().reduce_op(op, self.layout(), &[dim])?; let mut dims = self.dims().to_vec(); dims[dim] = 1; - let op = BackpropOp::new1(self, |arg| Op::Reduce(arg, op, dims.to_vec())); + let op = match op { + ReduceOp::Sum | ReduceOp::Min | ReduceOp::Max => { + BackpropOp::new1(self, |arg| Op::Reduce(arg, op, dims.to_vec())) + } + ReduceOp::ArgMin | ReduceOp::ArgMax => BackpropOp::none(), + }; let res = from_storage(storage, dims, op, false); if keepdim { Ok(res) From 04ca2b9ebd75b065641512d4254850e065699030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radam=C3=A9s=20Ajna?= Date: Fri, 15 Sep 2023 22:34:13 -0700 Subject: [PATCH 107/150] Update README + SAM (#866) * use serde-wasm-bindgen, faster serialization * update readme with demos --- README.md | 4 +++- candle-wasm-examples/segment-anything/Cargo.toml | 1 + candle-wasm-examples/segment-anything/samWorker.js | 3 +-- candle-wasm-examples/segment-anything/src/bin/m.rs | 5 ++--- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a2bb371c..610ef555 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ These online demos run entirely in your browser: object recognition. - [whisper](https://huggingface.co/spaces/lmz/candle-whisper): text to speech. - [LLaMA2](https://huggingface.co/spaces/lmz/candle-llama2): text generation. +- [Segment Anything Model](https://huggingface.co/spaces/radames/candle-segment-anything-wasm): Image segmentation. We also provide a some command line based examples using state of the art models: @@ -96,7 +97,8 @@ There are also some wasm examples for whisper and [llama2.c](https://github.com/karpathy/llama2.c). You can either build them with `trunk` or try them online: [whisper](https://huggingface.co/spaces/lmz/candle-whisper), -[llama2](https://huggingface.co/spaces/lmz/candle-llama2). +[llama2](https://huggingface.co/spaces/lmz/candle-llama2), +[Segment Anything Model](https://huggingface.co/spaces/radames/candle-segment-anything-wasm). For LLaMA2, run the following command to retrieve the weight files and start a test server: diff --git a/candle-wasm-examples/segment-anything/Cargo.toml b/candle-wasm-examples/segment-anything/Cargo.toml index b67de112..a847424d 100644 --- a/candle-wasm-examples/segment-anything/Cargo.toml +++ b/candle-wasm-examples/segment-anything/Cargo.toml @@ -27,3 +27,4 @@ serde_json = { workspace = true } # Wasm specific crates. console_error_panic_hook = "0.1.7" wasm-bindgen = "0.2.87" +serde-wasm-bindgen = "0.6.0" diff --git a/candle-wasm-examples/segment-anything/samWorker.js b/candle-wasm-examples/segment-anything/samWorker.js index b90498de..c1a152ef 100644 --- a/candle-wasm-examples/segment-anything/samWorker.js +++ b/candle-wasm-examples/segment-anything/samWorker.js @@ -141,8 +141,7 @@ self.addEventListener("message", async (event) => { } self.postMessage({ status: "segmenting", message: "Segmenting" }); - const result = sam.mask_for_point(points.x, points.y); - const { mask, image } = JSON.parse(result); + const { mask, image } = sam.mask_for_point(points.x, points.y); const maskDataURL = await createImageCanvas(mask, image); // Send the segment back to the main thread as JSON self.postMessage({ diff --git a/candle-wasm-examples/segment-anything/src/bin/m.rs b/candle-wasm-examples/segment-anything/src/bin/m.rs index 949c18a0..5140b979 100644 --- a/candle-wasm-examples/segment-anything/src/bin/m.rs +++ b/candle-wasm-examples/segment-anything/src/bin/m.rs @@ -76,7 +76,7 @@ impl Model { } // x and y have to be between 0 and 1 - pub fn mask_for_point(&self, x: f64, y: f64) -> Result { + pub fn mask_for_point(&self, x: f64, y: f64) -> Result { if !(0. ..=1.).contains(&x) { Err(JsError::new(&format!( "x has to be between 0 and 1, got {x}" @@ -112,8 +112,7 @@ impl Model { width: embeddings.width, height: embeddings.height, }; - let json = serde_json::to_string(&MaskImage { mask, image })?; - Ok(json) + Ok(serde_wasm_bindgen::to_value(&MaskImage { mask, image })?) } } From 7cafca835a4bb9a21f3c8111e2f61b7a6b1270fd Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sat, 16 Sep 2023 08:22:24 +0200 Subject: [PATCH 108/150] readme tweaks. (#867) --- README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 610ef555..d897de89 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ Candle is a minimalist ML framework for Rust with a focus on performance (includ and ease of use. Try our online demos: [whisper](https://huggingface.co/spaces/lmz/candle-whisper), [LLaMA2](https://huggingface.co/spaces/lmz/candle-llama2), -[yolo](https://huggingface.co/spaces/lmz/candle-yolo). +[yolo](https://huggingface.co/spaces/lmz/candle-yolo), +[Segment +Anything](https://huggingface.co/spaces/radames/candle-segment-anything-wasm). ## Get started @@ -114,8 +116,7 @@ And then head over to ## Useful Libraries -- `candle-lora` - - [`candle-lora`](https://github.com/EricLBuehler/candle-lora) provides a LoRA implementation that conforms to the official `peft` implementation. +- [`candle-lora`](https://github.com/EricLBuehler/candle-lora) provides a LoRA implementation that conforms to the official `peft` implementation. If you have an addition to this list, please submit a pull request. @@ -133,10 +134,20 @@ If you have an addition to this list, please submit a pull request. - CUDA backend for efficiently running on GPUs, multiple GPU distribution via NCCL. - WASM support, run your models in a browser. - Included models. - - LLMs: LLaMA v1 and v2, Falcon, StarCoder. + - Language Models. + - LLaMA v1 and v2. + - Falcon. + - StarCoder. + - T5. + - Bert. - Whisper (multi-lingual support). - - Stable Diffusion. - - Computer Vision: DINOv2, EfficientNet, yolo-v3, yolo-v8. + - Stable Diffusion v1.5, v2.1, XL v1.0. + - Computer Vision Models. + - DINOv2. + - EfficientNet. + - yolo-v3. + - yolo-v8. + - Segment-Anything Model (SAM). - File formats: load models from safetensors, npz, ggml, or PyTorch files. - Serverless (on CPU), small and fast deployments. - Quantization support using the llama.cpp quantized types. From 8658df348527cabcd722bfe2e9e48aba3c7f8e96 Mon Sep 17 00:00:00 2001 From: Lukas Kreussel <65088241+LLukas22@users.noreply.github.com> Date: Sat, 16 Sep 2023 18:23:38 +0200 Subject: [PATCH 109/150] Generate `*.pyi` stubs for PyO3 wrapper (#870) * Begin to generate typehints. * generate correct stubs * Correctly include stubs * Add comments and typhints to static functions * ensure candle-pyo3 directory * Make `llama.rope.freq_base` optional * `fmt` --- candle-pyo3/.gitignore | 160 ++++++++++++ candle-pyo3/Cargo.toml | 1 - candle-pyo3/README.md | 21 +- candle-pyo3/py_src/candle/__init__.py | 1 + candle-pyo3/py_src/candle/__init__.pyi | 248 +++++++++++++++++++ candle-pyo3/py_src/candle/nn/__init__.py | 5 + candle-pyo3/py_src/candle/nn/__init__.pyi | 19 ++ candle-pyo3/py_src/candle/typing/__init__.py | 16 ++ candle-pyo3/py_src/candle/utils/__init__.py | 11 + candle-pyo3/py_src/candle/utils/__init__.pyi | 63 +++++ candle-pyo3/pyproject.toml | 30 +++ candle-pyo3/quant-llama.py | 7 +- candle-pyo3/src/lib.rs | 89 ++++--- candle-pyo3/stub.py | 217 ++++++++++++++++ candle-pyo3/test.py | 9 +- 15 files changed, 857 insertions(+), 40 deletions(-) create mode 100644 candle-pyo3/.gitignore create mode 100644 candle-pyo3/py_src/candle/__init__.py create mode 100644 candle-pyo3/py_src/candle/__init__.pyi create mode 100644 candle-pyo3/py_src/candle/nn/__init__.py create mode 100644 candle-pyo3/py_src/candle/nn/__init__.pyi create mode 100644 candle-pyo3/py_src/candle/typing/__init__.py create mode 100644 candle-pyo3/py_src/candle/utils/__init__.py create mode 100644 candle-pyo3/py_src/candle/utils/__init__.pyi create mode 100644 candle-pyo3/pyproject.toml create mode 100644 candle-pyo3/stub.py diff --git a/candle-pyo3/.gitignore b/candle-pyo3/.gitignore new file mode 100644 index 00000000..68bc17f9 --- /dev/null +++ b/candle-pyo3/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/candle-pyo3/Cargo.toml b/candle-pyo3/Cargo.toml index 1d431cfb..c96681bd 100644 --- a/candle-pyo3/Cargo.toml +++ b/candle-pyo3/Cargo.toml @@ -12,7 +12,6 @@ readme = "README.md" [lib] name = "candle" crate-type = ["cdylib"] -doc = false [dependencies] candle = { path = "../candle-core", version = "0.2.2", package = "candle-core" } diff --git a/candle-pyo3/README.md b/candle-pyo3/README.md index 07dff468..be6d4f68 100644 --- a/candle-pyo3/README.md +++ b/candle-pyo3/README.md @@ -1,7 +1,26 @@ +## Installation + From the `candle-pyo3` directory, enable a virtual env where you will want the candle package to be installed then run. ```bash -maturin develop +maturin develop -r python test.py ``` + +## Generating Stub Files for Type Hinting + +For type hinting support, the `candle-pyo3` package requires `*.pyi` files. You can automatically generate these files using the `stub.py` script. + +### Steps: +1. Install the package using `maturin`. +2. Generate the stub files by running: + ``` + python stub.py + ``` + +### Validation: +To ensure that the stub files match the current implementation, execute: +``` +python stub.py --check +``` diff --git a/candle-pyo3/py_src/candle/__init__.py b/candle-pyo3/py_src/candle/__init__.py new file mode 100644 index 00000000..49c96122 --- /dev/null +++ b/candle-pyo3/py_src/candle/__init__.py @@ -0,0 +1 @@ +from .candle import * \ No newline at end of file diff --git a/candle-pyo3/py_src/candle/__init__.pyi b/candle-pyo3/py_src/candle/__init__.pyi new file mode 100644 index 00000000..c21e6738 --- /dev/null +++ b/candle-pyo3/py_src/candle/__init__.pyi @@ -0,0 +1,248 @@ +# Generated content DO NOT EDIT +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Sequence +from os import PathLike +from candle.typing import _ArrayLike, Device + +class bf16(DType): + pass + +@staticmethod +def cat(tensors: List[Tensor], dim: int): + """ + Concatenate the tensors across one axis. + """ + pass + +class f16(DType): + pass + +class f32(DType): + pass + +class f64(DType): + pass + +class i64(DType): + pass + +@staticmethod +def ones(shape: Sequence[int], dtype: Optional[DType] = None, device: Optional[Device] = None): + """ """ + pass + +@staticmethod +def rand(shape: Sequence[int], device: Optional[Device] = None): + """ + Creates a new tensor with random values. + """ + pass + +@staticmethod +def randn(shape: Sequence[int], device: Optional[Device] = None): + """ """ + pass + +@staticmethod +def stack(tensors: List[Tensor], dim: int): + """ + Stack the tensors along a new axis. + """ + pass + +@staticmethod +def tensor(data: _ArrayLike): + """ + Creates a new tensor from a Python value. The value can be a scalar or array-like object. + """ + pass + +class u32(DType): + pass + +class u8(DType): + pass + +@staticmethod +def zeros(shape: Sequence[int], dtype: Optional[DType] = None, device: Optional[Device] = None): + """ """ + pass + +class DType: + pass + +class QTensor: + def dequantize(self): + """ """ + pass + @property + def ggml_dtype(self): + """ """ + pass + def matmul_t(self, lhs): + """ """ + pass + @property + def rank(self): + """ """ + pass + @property + def shape(self): + """ """ + pass + +class Tensor: + def __init__(data: _ArrayLike): + pass + def argmax_keepdim(self, dim): + """ """ + pass + def argmin_keepdim(self, dim): + """ """ + pass + def broadcast_add(self, rhs): + """ """ + pass + def broadcast_as(self, shape): + """ """ + pass + def broadcast_div(self, rhs): + """ """ + pass + def broadcast_left(self, shape): + """ """ + pass + def broadcast_mul(self, rhs): + """ """ + pass + def broadcast_sub(self, rhs): + """ """ + pass + def contiguous(self): + """ """ + pass + def copy(self): + """ """ + pass + def cos(self): + """ """ + pass + def detach(self): + """ """ + pass + @property + def device(self): + """ """ + pass + @property + def dtype(self): + """ """ + pass + def exp(self): + """ """ + pass + def flatten_all(self): + """ """ + pass + def flatten_from(self, dim): + """ """ + pass + def flatten_to(self, dim): + """ """ + pass + def get(self, index): + """ """ + pass + def index_select(self, rhs, dim): + """ """ + pass + def is_contiguous(self): + """ """ + pass + def is_fortran_contiguous(self): + """ """ + pass + def log(self): + """ """ + pass + def matmul(self, rhs): + """ """ + pass + def max_keepdim(self, dim): + """ """ + pass + def mean_all(self): + """ """ + pass + def min_keepdim(self, dim): + """ """ + pass + def narrow(self, dim, start, len): + """ """ + pass + def powf(self, p): + """ """ + pass + def quantize(self, quantized_dtype): + """ """ + pass + @property + def rank(self): + """ """ + pass + def recip(self): + """ """ + pass + def reshape(self, shape): + """ """ + pass + @property + def shape(self): + """ + Gets the tensor shape as a Python tuple. + """ + pass + def sin(self): + """ """ + pass + def sqr(self): + """ """ + pass + def sqrt(self): + """ """ + pass + def squeeze(self, dim): + """ """ + pass + @property + def stride(self): + """ """ + pass + def sum_all(self): + """ """ + pass + def sum_keepdim(self, dims): + """ """ + pass + def t(self): + """ """ + pass + def to_device(self, device): + """ """ + pass + def to_dtype(self, dtype): + """ """ + pass + def transpose(self, dim1, dim2): + """ """ + pass + def unsqueeze(self, dim): + """ """ + pass + def values(self): + """ + Gets the tensor's data as a Python scalar or array-like object. + """ + pass + def where_cond(self, on_true, on_false): + """ """ + pass diff --git a/candle-pyo3/py_src/candle/nn/__init__.py b/candle-pyo3/py_src/candle/nn/__init__.py new file mode 100644 index 00000000..b8c5cfb7 --- /dev/null +++ b/candle-pyo3/py_src/candle/nn/__init__.py @@ -0,0 +1,5 @@ +# Generated content DO NOT EDIT +from .. import nn + +silu = nn.silu +softmax = nn.softmax diff --git a/candle-pyo3/py_src/candle/nn/__init__.pyi b/candle-pyo3/py_src/candle/nn/__init__.pyi new file mode 100644 index 00000000..821cd052 --- /dev/null +++ b/candle-pyo3/py_src/candle/nn/__init__.pyi @@ -0,0 +1,19 @@ +# Generated content DO NOT EDIT +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Sequence +from os import PathLike +from candle.typing import _ArrayLike, Device +from candle import Tensor, DType + +@staticmethod +def silu(tensor: Tensor): + """ + Applies the Sigmoid Linear Unit (SiLU) function to a given tensor. + """ + pass + +@staticmethod +def softmax(tensor: Tensor, dim: int): + """ + Applies the Softmax function to a given tensor. + """ + pass diff --git a/candle-pyo3/py_src/candle/typing/__init__.py b/candle-pyo3/py_src/candle/typing/__init__.py new file mode 100644 index 00000000..ea85d2a3 --- /dev/null +++ b/candle-pyo3/py_src/candle/typing/__init__.py @@ -0,0 +1,16 @@ +from typing import TypeVar, Union, Sequence + +_T = TypeVar("_T") + +_ArrayLike = Union[ + _T, + Sequence[_T], + Sequence[Sequence[_T]], + Sequence[Sequence[Sequence[_T]]], + Sequence[Sequence[Sequence[Sequence[_T]]]], +] + +CPU:str = "cpu" +CUDA:str = "cuda" + +Device = TypeVar("Device", CPU, CUDA) \ No newline at end of file diff --git a/candle-pyo3/py_src/candle/utils/__init__.py b/candle-pyo3/py_src/candle/utils/__init__.py new file mode 100644 index 00000000..2ead6d84 --- /dev/null +++ b/candle-pyo3/py_src/candle/utils/__init__.py @@ -0,0 +1,11 @@ +# Generated content DO NOT EDIT +from .. import utils + +cuda_is_available = utils.cuda_is_available +get_num_threads = utils.get_num_threads +has_accelerate = utils.has_accelerate +has_mkl = utils.has_mkl +load_ggml = utils.load_ggml +load_gguf = utils.load_gguf +load_safetensors = utils.load_safetensors +save_safetensors = utils.save_safetensors diff --git a/candle-pyo3/py_src/candle/utils/__init__.pyi b/candle-pyo3/py_src/candle/utils/__init__.pyi new file mode 100644 index 00000000..7a0a5231 --- /dev/null +++ b/candle-pyo3/py_src/candle/utils/__init__.pyi @@ -0,0 +1,63 @@ +# Generated content DO NOT EDIT +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Sequence +from os import PathLike +from candle.typing import _ArrayLike, Device +from candle import Tensor, DType + +@staticmethod +def cuda_is_available(): + """ + Returns true if the 'cuda' backend is available. + """ + pass + +@staticmethod +def get_num_threads(): + """ + Returns the number of threads used by the candle. + """ + pass + +@staticmethod +def has_accelerate(): + """ + Returns true if candle was compiled with 'accelerate' support. + """ + pass + +@staticmethod +def has_mkl(): + """ + Returns true if candle was compiled with MKL support. + """ + pass + +@staticmethod +def load_ggml(path: Union[str, PathLike]): + """ + Load a GGML file. Returns a tuple of three objects: a dictionary mapping tensor names to tensors, + a dictionary mapping hyperparameter names to hyperparameter values, and a vocabulary. + """ + pass + +@staticmethod +def load_gguf(path: Union[str, PathLike]): + """ + Loads a GGUF file. Returns a tuple of two dictionaries: the first maps tensor names to tensors, + and the second maps metadata keys to metadata values. + """ + pass + +@staticmethod +def load_safetensors(path: Union[str, PathLike]): + """ + Loads a safetensors file. Returns a dictionary mapping tensor names to tensors. + """ + pass + +@staticmethod +def save_safetensors(path: Union[str, PathLike], tensors: Dict[str, Tensor]): + """ + Saves a dictionary of tensors to a safetensors file. + """ + pass diff --git a/candle-pyo3/pyproject.toml b/candle-pyo3/pyproject.toml new file mode 100644 index 00000000..b4e372d7 --- /dev/null +++ b/candle-pyo3/pyproject.toml @@ -0,0 +1,30 @@ +[project] +name = 'candle-pyo3' +requires-python = '>=3.7' +authors = [ + {name = 'Laurent Mazare', email = ''}, +] + +dynamic = [ + 'description', + 'license', + 'readme', +] + +[project.urls] +Homepage = 'https://github.com/huggingface/candle' +Source = 'https://github.com/huggingface/candle' + +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[tool.maturin] +python-source = "py_src" +module-name = "candle.candle" +bindings = 'pyo3' +features = ["pyo3/extension-module"] + +[tool.black] +line-length = 119 +target-version = ['py35'] \ No newline at end of file diff --git a/candle-pyo3/quant-llama.py b/candle-pyo3/quant-llama.py index 0f7a51c6..020d525d 100644 --- a/candle-pyo3/quant-llama.py +++ b/candle-pyo3/quant-llama.py @@ -1,6 +1,7 @@ # This example shows how the candle Python api can be used to replicate llama.cpp. import sys import candle +from candle.utils import load_ggml,load_gguf MAX_SEQ_LEN = 4096 @@ -154,7 +155,7 @@ def main(): filename = sys.argv[1] print(f"reading model file {filename}") if filename.endswith("gguf"): - all_tensors, metadata = candle.load_gguf(sys.argv[1]) + all_tensors, metadata = load_gguf(sys.argv[1]) vocab = metadata["tokenizer.ggml.tokens"] for i, v in enumerate(vocab): vocab[i] = '\n' if v == '<0x0A>' else v.replace('▁', ' ') @@ -168,13 +169,13 @@ def main(): 'n_head_kv': metadata['llama.attention.head_count_kv'], 'n_layer': metadata['llama.block_count'], 'n_rot': metadata['llama.rope.dimension_count'], - 'rope_freq': metadata['llama.rope.freq_base'], + 'rope_freq': metadata.get('llama.rope.freq_base', 10000.), 'ftype': metadata['general.file_type'], } all_tensors = { gguf_rename(k): v for k, v in all_tensors.items() } else: - all_tensors, hparams, vocab = candle.load_ggml(sys.argv[1]) + all_tensors, hparams, vocab = load_ggml(sys.argv[1]) print(hparams) model = QuantizedLlama(hparams, all_tensors) print("model built, starting inference") diff --git a/candle-pyo3/src/lib.rs b/candle-pyo3/src/lib.rs index eddc0fda..1df78ec6 100644 --- a/candle-pyo3/src/lib.rs +++ b/candle-pyo3/src/lib.rs @@ -197,38 +197,40 @@ trait MapDType { #[pymethods] impl PyTensor { #[new] + #[pyo3(text_signature = "(data:_ArrayLike)")] // TODO: Handle arbitrary input dtype and shape. - fn new(py: Python<'_>, vs: PyObject) -> PyResult { + /// Creates a new tensor from a Python value. The value can be a scalar or array-like object. + fn new(py: Python<'_>, data: PyObject) -> PyResult { use Device::Cpu; - let tensor = if let Ok(vs) = vs.extract::(py) { + let tensor = if let Ok(vs) = data.extract::(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::(py) { + } else if let Ok(vs) = data.extract::(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::(py) { + } else if let Ok(vs) = data.extract::(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>(py) { + } else if let Ok(vs) = data.extract::>(py) { let len = vs.len(); Tensor::from_vec(vs, len, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>(py) { + } else if let Ok(vs) = data.extract::>(py) { let len = vs.len(); Tensor::from_vec(vs, len, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>(py) { + } else if let Ok(vs) = data.extract::>(py) { let len = vs.len(); Tensor::from_vec(vs, len, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>>(py) { + } else if let Ok(vs) = data.extract::>>(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>>(py) { + } else if let Ok(vs) = data.extract::>>(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>>(py) { + } else if let Ok(vs) = data.extract::>>(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>>>(py) { + } else if let Ok(vs) = data.extract::>>>(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>>>(py) { + } else if let Ok(vs) = data.extract::>>>(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? - } else if let Ok(vs) = vs.extract::>>>(py) { + } else if let Ok(vs) = data.extract::>>>(py) { Tensor::new(vs, &Cpu).map_err(wrap_err)? } else { - let ty = vs.as_ref(py).get_type(); + let ty = data.as_ref(py).get_type(); Err(PyTypeError::new_err(format!( "incorrect type {ty} for tensor" )))? @@ -236,7 +238,7 @@ impl PyTensor { Ok(Self(tensor)) } - /// Gets the tensor data as a Python value/array/array of array/... + /// Gets the tensor's data as a Python scalar or array-like object. fn values(&self, py: Python<'_>) -> PyResult { struct M<'a>(Python<'a>); impl<'a> MapDType for M<'a> { @@ -280,6 +282,7 @@ impl PyTensor { } #[getter] + /// Gets the tensor shape as a Python tuple. fn shape(&self, py: Python<'_>) -> PyObject { PyTuple::new(py, self.0.dims()).to_object(py) } @@ -580,8 +583,9 @@ impl PyTensor { } } -/// Concatenate the tensors across one axis. #[pyfunction] +#[pyo3(text_signature = "(tensors:List[Tensor], dim:int )")] +/// Concatenate the tensors across one axis. fn cat(tensors: Vec, dim: i64) -> PyResult { if tensors.is_empty() { return Err(PyErr::new::("empty input to cat")); @@ -593,6 +597,8 @@ fn cat(tensors: Vec, dim: i64) -> PyResult { } #[pyfunction] +#[pyo3(text_signature = "(tensors:List[Tensor], dim:int)")] +/// Stack the tensors along a new axis. fn stack(tensors: Vec, dim: usize) -> PyResult { let tensors = tensors.into_iter().map(|t| t.0).collect::>(); let tensor = Tensor::stack(&tensors, dim).map_err(wrap_err)?; @@ -600,12 +606,15 @@ fn stack(tensors: Vec, dim: usize) -> PyResult { } #[pyfunction] -fn tensor(py: Python<'_>, vs: PyObject) -> PyResult { - PyTensor::new(py, vs) +#[pyo3(text_signature = "(data:_ArrayLike)")] +/// Creates a new tensor from a Python value. The value can be a scalar or array-like object. +fn tensor(py: Python<'_>, data: PyObject) -> PyResult { + PyTensor::new(py, data) } #[pyfunction] -#[pyo3(signature = (shape, *, device=None))] +#[pyo3(signature = (shape, *, device=None), text_signature = "(shape:Sequence[int], device:Optional[Device]=None)")] +/// Creates a new tensor with random values. fn rand(_py: Python<'_>, shape: PyShape, device: Option) -> PyResult { let device = device.unwrap_or(PyDevice::Cpu).as_device()?; let tensor = Tensor::rand(0f32, 1f32, shape.0, &device).map_err(wrap_err)?; @@ -613,7 +622,7 @@ fn rand(_py: Python<'_>, shape: PyShape, device: Option) -> PyResult

, shape: PyShape, device: Option) -> PyResult { let device = device.unwrap_or(PyDevice::Cpu).as_device()?; let tensor = Tensor::randn(0f32, 1f32, shape.0, &device).map_err(wrap_err)?; @@ -621,7 +630,7 @@ fn randn(_py: Python<'_>, shape: PyShape, device: Option) -> PyResult< } #[pyfunction] -#[pyo3(signature = (shape, *, dtype=None, device=None))] +#[pyo3(signature = (shape, *, dtype=None, device=None),text_signature = "(shape:Sequence[int], dtype:Optional[DType]=None, device:Optional[Device]=None)")] fn ones( py: Python<'_>, shape: PyShape, @@ -638,7 +647,7 @@ fn ones( } #[pyfunction] -#[pyo3(signature = (shape, *, dtype=None, device=None))] +#[pyo3(signature = (shape, *, dtype=None, device=None), text_signature = "(shape:Sequence[int], dtype:Optional[DType]=None, device:Optional[Device]=None)")] fn zeros( py: Python<'_>, shape: PyShape, @@ -704,6 +713,8 @@ impl PyQTensor { } #[pyfunction] +#[pyo3(text_signature = "(path:Union[str,PathLike])")] +/// Loads a safetensors file. Returns a dictionary mapping tensor names to tensors. fn load_safetensors(path: &str, py: Python<'_>) -> PyResult { let res = ::candle::safetensors::load(path, &Device::Cpu).map_err(wrap_err)?; let res = res @@ -714,6 +725,8 @@ fn load_safetensors(path: &str, py: Python<'_>) -> PyResult { } #[pyfunction] +#[pyo3(text_signature = "(path:Union[str,PathLike], tensors:Dict[str,Tensor])")] +/// Saves a dictionary of tensors to a safetensors file. fn save_safetensors( path: &str, tensors: std::collections::HashMap, @@ -726,6 +739,9 @@ fn save_safetensors( } #[pyfunction] +#[pyo3(text_signature = "(path:Union[str,PathLike])")] +/// Load a GGML file. Returns a tuple of three objects: a dictionary mapping tensor names to tensors, +/// a dictionary mapping hyperparameter names to hyperparameter values, and a vocabulary. fn load_ggml(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject, PyObject)> { let mut file = std::fs::File::open(path)?; let ggml = ::candle::quantized::ggml_file::Content::read(&mut file).map_err(wrap_err)?; @@ -757,6 +773,9 @@ fn load_ggml(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject, PyObje } #[pyfunction] +#[pyo3(text_signature = "(path:Union[str,PathLike])")] +/// Loads a GGUF file. Returns a tuple of two dictionaries: the first maps tensor names to tensors, +/// and the second maps metadata keys to metadata values. fn load_gguf(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject)> { use ::candle::quantized::gguf_file; fn gguf_value_to_pyobject(v: &gguf_file::Value, py: Python<'_>) -> PyResult { @@ -806,21 +825,25 @@ fn load_gguf(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject)> { } #[pyfunction] +/// Returns true if the 'cuda' backend is available. fn cuda_is_available() -> bool { ::candle::utils::cuda_is_available() } #[pyfunction] +/// Returns true if candle was compiled with 'accelerate' support. fn has_accelerate() -> bool { ::candle::utils::has_accelerate() } #[pyfunction] +/// Returns true if candle was compiled with MKL support. fn has_mkl() -> bool { ::candle::utils::has_mkl() } #[pyfunction] +/// Returns the number of threads used by the candle. fn get_num_threads() -> usize { ::candle::utils::get_num_threads() } @@ -830,19 +853,27 @@ fn candle_utils(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(get_num_threads, m)?)?; m.add_function(wrap_pyfunction!(has_accelerate, m)?)?; m.add_function(wrap_pyfunction!(has_mkl, m)?)?; + m.add_function(wrap_pyfunction!(load_ggml, m)?)?; + m.add_function(wrap_pyfunction!(load_gguf, m)?)?; + m.add_function(wrap_pyfunction!(load_safetensors, m)?)?; + m.add_function(wrap_pyfunction!(save_safetensors, m)?)?; Ok(()) } #[pyfunction] -fn softmax(t: PyTensor, dim: i64) -> PyResult { - let dim = actual_dim(&t, dim).map_err(wrap_err)?; - let sm = candle_nn::ops::softmax(&t.0, dim).map_err(wrap_err)?; +#[pyo3(text_signature = "(tensor:Tensor, dim:int)")] +/// Applies the Softmax function to a given tensor. +fn softmax(tensor: PyTensor, dim: i64) -> PyResult { + let dim = actual_dim(&tensor, dim).map_err(wrap_err)?; + let sm = candle_nn::ops::softmax(&tensor.0, dim).map_err(wrap_err)?; Ok(PyTensor(sm)) } #[pyfunction] -fn silu(t: PyTensor) -> PyResult { - let s = candle_nn::ops::silu(&t.0).map_err(wrap_err)?; +#[pyo3(text_signature = "(tensor:Tensor)")] +/// Applies the Sigmoid Linear Unit (SiLU) function to a given tensor. +fn silu(tensor: PyTensor) -> PyResult { + let s = candle_nn::ops::silu(&tensor.0).map_err(wrap_err)?; Ok(PyTensor(s)) } @@ -871,14 +902,10 @@ fn candle(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add("f32", PyDType(DType::F32))?; m.add("f64", PyDType(DType::F64))?; m.add_function(wrap_pyfunction!(cat, m)?)?; - m.add_function(wrap_pyfunction!(load_ggml, m)?)?; - m.add_function(wrap_pyfunction!(load_gguf, m)?)?; - m.add_function(wrap_pyfunction!(load_safetensors, m)?)?; m.add_function(wrap_pyfunction!(ones, m)?)?; m.add_function(wrap_pyfunction!(rand, m)?)?; m.add_function(wrap_pyfunction!(randn, m)?)?; m.add_function(wrap_pyfunction!(tensor, m)?)?; - m.add_function(wrap_pyfunction!(save_safetensors, m)?)?; m.add_function(wrap_pyfunction!(stack, m)?)?; m.add_function(wrap_pyfunction!(zeros, m)?)?; Ok(()) diff --git a/candle-pyo3/stub.py b/candle-pyo3/stub.py new file mode 100644 index 00000000..b5b9256f --- /dev/null +++ b/candle-pyo3/stub.py @@ -0,0 +1,217 @@ +#See: https://raw.githubusercontent.com/huggingface/tokenizers/main/bindings/python/stub.py +import argparse +import inspect +import os +from typing import Optional +import black +from pathlib import Path + + +INDENT = " " * 4 +GENERATED_COMMENT = "# Generated content DO NOT EDIT\n" +TYPING = """from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Sequence +from os import PathLike +""" +CANDLE_SPECIFIC_TYPING = "from candle.typing import _ArrayLike, Device\n" +CANDLE_TENSOR_IMPORTS = "from candle import Tensor,DType\n" + + + +def do_indent(text: Optional[str], indent: str): + if text is None: + return "" + return text.replace("\n", f"\n{indent}") + + +def function(obj, indent:str, text_signature:str=None): + if text_signature is None: + text_signature = obj.__text_signature__ + + text_signature = text_signature.replace("$self", "self").lstrip().rstrip() + string = "" + string += f"{indent}def {obj.__name__}{text_signature}:\n" + indent += INDENT + string += f'{indent}"""\n' + string += f"{indent}{do_indent(obj.__doc__, indent)}\n" + string += f'{indent}"""\n' + string += f"{indent}pass\n" + string += "\n" + string += "\n" + return string + + +def member_sort(member): + if inspect.isclass(member): + value = 10 + len(inspect.getmro(member)) + else: + value = 1 + return value + + +def fn_predicate(obj): + value = inspect.ismethoddescriptor(obj) or inspect.isbuiltin(obj) + if value: + return obj.__text_signature__ and not obj.__name__.startswith("_") + if inspect.isgetsetdescriptor(obj): + return not obj.__name__.startswith("_") + return False + + +def get_module_members(module): + members = [ + member + for name, member in inspect.getmembers(module) + if not name.startswith("_") and not inspect.ismodule(member) + ] + members.sort(key=member_sort) + return members + + +def pyi_file(obj, indent=""): + string = "" + if inspect.ismodule(obj): + string += GENERATED_COMMENT + string += TYPING + string += CANDLE_SPECIFIC_TYPING + if obj.__name__ != "candle.candle": + string += CANDLE_TENSOR_IMPORTS + members = get_module_members(obj) + for member in members: + string += pyi_file(member, indent) + + elif inspect.isclass(obj): + indent += INDENT + mro = inspect.getmro(obj) + if len(mro) > 2: + inherit = f"({mro[1].__name__})" + else: + inherit = "" + string += f"class {obj.__name__}{inherit}:\n" + + body = "" + if obj.__doc__: + body += f'{indent}"""\n{indent}{do_indent(obj.__doc__, indent)}\n{indent}"""\n' + + fns = inspect.getmembers(obj, fn_predicate) + + # Init + if obj.__text_signature__: + body += f"{indent}def __init__{obj.__text_signature__}:\n" + body += f"{indent+INDENT}pass\n" + body += "\n" + + for (name, fn) in fns: + body += pyi_file(fn, indent=indent) + + if not body: + body += f"{indent}pass\n" + + string += body + string += "\n\n" + + elif inspect.isbuiltin(obj): + string += f"{indent}@staticmethod\n" + string += function(obj, indent) + + elif inspect.ismethoddescriptor(obj): + string += function(obj, indent) + + elif inspect.isgetsetdescriptor(obj): + # TODO it would be interesing to add the setter maybe ? + string += f"{indent}@property\n" + string += function(obj, indent, text_signature="(self)") + + elif obj.__class__.__name__ == "DType": + string += f"class {str(obj).lower()}(DType):\n" + string += f"{indent+INDENT}pass\n" + else: + raise Exception(f"Object {obj} is not supported") + return string + + +def py_file(module, origin): + members = get_module_members(module) + + string = GENERATED_COMMENT + string += f"from .. import {origin}\n" + string += "\n" + for member in members: + if hasattr(member, "__name__"): + name = member.__name__ + else: + name = str(member) + string += f"{name} = {origin}.{name}\n" + return string + + +def do_black(content, is_pyi): + mode = black.Mode( + target_versions={black.TargetVersion.PY35}, + line_length=119, + is_pyi=is_pyi, + string_normalization=True, + experimental_string_processing=False, + ) + try: + return black.format_file_contents(content, fast=True, mode=mode) + except black.NothingChanged: + return content + + +def write(module, directory, origin, check=False): + submodules = [(name, member) for name, member in inspect.getmembers(module) if inspect.ismodule(member)] + + filename = os.path.join(directory, "__init__.pyi") + pyi_content = pyi_file(module) + pyi_content = do_black(pyi_content, is_pyi=True) + os.makedirs(directory, exist_ok=True) + if check: + with open(filename, "r") as f: + data = f.read() + assert data == pyi_content, f"The content of {filename} seems outdated, please run `python stub.py`" + else: + with open(filename, "w") as f: + f.write(pyi_content) + + filename = os.path.join(directory, "__init__.py") + py_content = py_file(module, origin) + py_content = do_black(py_content, is_pyi=False) + os.makedirs(directory, exist_ok=True) + + is_auto = False + if not os.path.exists(filename): + is_auto = True + else: + with open(filename, "r") as f: + line = f.readline() + if line == GENERATED_COMMENT: + is_auto = True + + if is_auto: + if check: + with open(filename, "r") as f: + data = f.read() + assert data == py_content, f"The content of {filename} seems outdated, please run `python stub.py`" + else: + with open(filename, "w") as f: + f.write(py_content) + + for name, submodule in submodules: + write(submodule, os.path.join(directory, name), f"{name}", check=check) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true") + + args = parser.parse_args() + + #Enable execution from the candle and candle-pyo3 directories + cwd = Path.cwd() + directory = "py_src/candle/" + if cwd.name != "candle-pyo3": + directory = f"candle-pyo3/{directory}" + + import candle + + write(candle.candle, directory, "candle", check=args.check) diff --git a/candle-pyo3/test.py b/candle-pyo3/test.py index 7f24b49d..c78ffc41 100644 --- a/candle-pyo3/test.py +++ b/candle-pyo3/test.py @@ -1,4 +1,5 @@ import candle +from candle import Tensor, QTensor t = candle.Tensor(42.0) print(t) @@ -9,7 +10,7 @@ t = candle.Tensor([3.0, 1, 4, 1, 5, 9, 2, 6]) print(t) print(t+t) -t = t.reshape([2, 4]) +t:Tensor = t.reshape([2, 4]) print(t.matmul(t.t())) print(t.to_dtype(candle.u8)) @@ -20,7 +21,7 @@ print(t) print(t.dtype) t = candle.randn((16, 256)) -quant_t = t.quantize("q6k") -dequant_t = quant_t.dequantize() -diff2 = (t - dequant_t).sqr() +quant_t:QTensor = t.quantize("q6k") +dequant_t:Tensor = quant_t.dequantize() +diff2:Tensor = (t - dequant_t).sqr() print(diff2.mean_all()) From 1a276b5da79a4bb2305dde7368b800d165599819 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 17 Sep 2023 09:00:45 +0200 Subject: [PATCH 110/150] Add a KV cache to T5. (#873) * Add a KV cache to T5. * Suggest using release mode. * Use the kv cache in decoding. * Add a comment. --- candle-examples/examples/musicgen/main.rs | 2 +- candle-examples/examples/t5/README.md | 4 +- candle-examples/examples/t5/main.rs | 37 +- candle-examples/examples/wuerstchen/main.rs | 499 ++++++++++++++++++++ candle-transformers/src/models/t5.rs | 85 ++-- 5 files changed, 577 insertions(+), 50 deletions(-) create mode 100644 candle-examples/examples/wuerstchen/main.rs diff --git a/candle-examples/examples/musicgen/main.rs b/candle-examples/examples/musicgen/main.rs index df8c3135..0fae67b5 100644 --- a/candle-examples/examples/musicgen/main.rs +++ b/candle-examples/examples/musicgen/main.rs @@ -77,7 +77,7 @@ fn main() -> Result<()> { let model = model.deserialize()?; let vb = VarBuilder::from_safetensors(vec![model], DTYPE, &device); let config = GenConfig::small(); - let model = MusicgenForConditionalGeneration::load(vb, config)?; + let mut model = MusicgenForConditionalGeneration::load(vb, config)?; let tokens = tokenizer .encode(args.prompt.as_str(), true) diff --git a/candle-examples/examples/t5/README.md b/candle-examples/examples/t5/README.md index c6ea2125..6a406467 100644 --- a/candle-examples/examples/t5/README.md +++ b/candle-examples/examples/t5/README.md @@ -3,7 +3,7 @@ ## Encoder-decoder example: ```bash -$ cargo run --example t5 -- --model-id "t5-small" --prompt "translate to German: A beautiful candle." --decode +$ cargo run --example t5 --release -- --model-id "t5-small" --prompt "translate to German: A beautiful candle." --decode ... Running on CPU, to run on GPU, build this example with `--features cuda` Eine schöne Kerze. @@ -13,7 +13,7 @@ Running on CPU, to run on GPU, build this example with `--features cuda` ## Sentence embedding example: ```bash -$ cargo run --example t5 -- --model-id "t5-small" --prompt "A beautiful candle." +$ cargo run --example t5 --release -- --model-id "t5-small" --prompt "A beautiful candle." ... [[[ 0.0515, -0.0541, -0.0761, ..., -0.0392, 0.1511, -0.0265], [-0.0974, 0.0998, -0.1659, ..., -0.2450, 0.1738, -0.0164], diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index 00291609..c432e004 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -48,10 +48,6 @@ struct Args { #[arg(long)] prompt: Option, - /// The number of times to run the prompt. - #[arg(long, default_value = "1")] - n: usize, - /// L2 normalization for embeddings. #[arg(long, default_value = "true")] normalize_embeddings: bool, @@ -131,6 +127,7 @@ impl T5ModelBuilder { fn main() -> Result<()> { let args = Args::parse(); let (builder, mut tokenizer) = T5ModelBuilder::load(&args)?; + let device = &builder.device; let tokenizer = tokenizer .with_padding(None) .with_truncation(None) @@ -142,32 +139,32 @@ fn main() -> Result<()> { .map_err(E::msg)? .get_ids() .to_vec(); - let input_token_ids = Tensor::new(&tokens[..], &builder.device)?.unsqueeze(0)?; + let input_token_ids = Tensor::new(&tokens[..], device)?.unsqueeze(0)?; if !args.decode { - let model = builder.build_encoder()?; - for idx in 0..args.n { - let start = std::time::Instant::now(); - let ys = model.forward(&input_token_ids)?; - if idx == 0 { - println!("{ys}"); - } - println!("Took {:?}", start.elapsed()); - } + let mut model = builder.build_encoder()?; + let start = std::time::Instant::now(); + let ys = model.forward(&input_token_ids)?; + println!("{ys}"); + println!("Took {:?}", start.elapsed()); } else { - let model = builder.build_conditional_generation()?; + let mut model = builder.build_conditional_generation()?; let mut output_token_ids = [builder.config.pad_token_id as u32].to_vec(); let mut logits_processor = LogitsProcessor::new(299792458, None, None); let start = std::time::Instant::now(); - for _index in 0.. { + for index in 0.. { if output_token_ids.len() > 512 { break; } - let decoder_token_ids = - Tensor::new(&output_token_ids[..], &builder.device)?.unsqueeze(0)?; + let decoder_token_ids = if index == 0 || !builder.config.use_cache { + Tensor::new(output_token_ids.as_slice(), device)?.unsqueeze(0)? + } else { + let last_token = *output_token_ids.last().unwrap(); + Tensor::new(&[last_token], device)?.unsqueeze(0)? + }; let logits = model.forward(&input_token_ids, &decoder_token_ids)?; let next_token_id = logits_processor.sample(&logits.flatten_to(1)?)?; - if (next_token_id as usize) == builder.config.eos_token_id { + if next_token_id as usize == builder.config.eos_token_id { break; } output_token_ids.push(next_token_id); @@ -186,7 +183,7 @@ fn main() -> Result<()> { } } None => { - let model = builder.build_encoder()?; + let mut model = builder.build_encoder()?; let sentences = [ "The cat sits outside", "A man is playing guitar", diff --git a/candle-examples/examples/wuerstchen/main.rs b/candle-examples/examples/wuerstchen/main.rs new file mode 100644 index 00000000..c8b771a0 --- /dev/null +++ b/candle-examples/examples/wuerstchen/main.rs @@ -0,0 +1,499 @@ +#[cfg(feature = "accelerate")] +extern crate accelerate_src; + +#[cfg(feature = "mkl")] +extern crate intel_mkl_src; + +use candle_transformers::models::stable_diffusion; + +use anyhow::{Error as E, Result}; +use candle::{DType, Device, IndexOp, Module, Tensor, D}; +use clap::Parser; +use tokenizers::Tokenizer; + +const GUIDANCE_SCALE: f64 = 7.5; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +struct Args { + /// The prompt to be used for image generation. + #[arg( + long, + default_value = "A very realistic photo of a rusty robot walking on a sandy beach" + )] + prompt: String, + + #[arg(long, default_value = "")] + uncond_prompt: String, + + /// Run on CPU rather than on GPU. + #[arg(long)] + cpu: bool, + + /// Enable tracing (generates a trace-timestamp.json file). + #[arg(long)] + tracing: bool, + + /// The height in pixels of the generated image. + #[arg(long)] + height: Option, + + /// The width in pixels of the generated image. + #[arg(long)] + width: Option, + + /// The UNet weight file, in .safetensors format. + #[arg(long, value_name = "FILE")] + unet_weights: Option, + + /// The CLIP weight file, in .safetensors format. + #[arg(long, value_name = "FILE")] + clip_weights: Option, + + /// The VAE weight file, in .safetensors format. + #[arg(long, value_name = "FILE")] + vae_weights: Option, + + #[arg(long, value_name = "FILE")] + /// The file specifying the tokenizer to used for tokenization. + tokenizer: Option, + + /// The size of the sliced attention or 0 for automatic slicing (disabled by default) + #[arg(long)] + sliced_attention_size: Option, + + /// The number of steps to run the diffusion for. + #[arg(long, default_value_t = 30)] + n_steps: usize, + + /// The number of samples to generate. + #[arg(long, default_value_t = 1)] + num_samples: i64, + + /// The name of the final image to generate. + #[arg(long, value_name = "FILE", default_value = "sd_final.png")] + final_image: String, + + #[arg(long, value_enum, default_value = "v2-1")] + sd_version: StableDiffusionVersion, + + /// Generate intermediary images at each step. + #[arg(long, action)] + intermediary_images: bool, + + #[arg(long)] + use_flash_attn: bool, + + #[arg(long)] + use_f16: bool, + + #[arg(long, value_name = "FILE")] + img2img: Option, + + /// The strength, indicates how much to transform the initial image. The + /// value must be between 0 and 1, a value of 1 discards the initial image + /// information. + #[arg(long, default_value_t = 0.8)] + img2img_strength: f64, +} + +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +enum StableDiffusionVersion { + V1_5, + V2_1, + Xl, +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ModelFile { + Tokenizer, + Tokenizer2, + Clip, + Clip2, + Unet, + Vae, +} + +impl StableDiffusionVersion { + fn repo(&self) -> &'static str { + match self { + Self::Xl => "stabilityai/stable-diffusion-xl-base-1.0", + Self::V2_1 => "stabilityai/stable-diffusion-2-1", + Self::V1_5 => "runwayml/stable-diffusion-v1-5", + } + } + + fn unet_file(&self, use_f16: bool) -> &'static str { + match self { + Self::V1_5 | Self::V2_1 | Self::Xl => { + if use_f16 { + "unet/diffusion_pytorch_model.fp16.safetensors" + } else { + "unet/diffusion_pytorch_model.safetensors" + } + } + } + } + + fn vae_file(&self, use_f16: bool) -> &'static str { + match self { + Self::V1_5 | Self::V2_1 | Self::Xl => { + if use_f16 { + "vae/diffusion_pytorch_model.fp16.safetensors" + } else { + "vae/diffusion_pytorch_model.safetensors" + } + } + } + } + + fn clip_file(&self, use_f16: bool) -> &'static str { + match self { + Self::V1_5 | Self::V2_1 | Self::Xl => { + if use_f16 { + "text_encoder/model.fp16.safetensors" + } else { + "text_encoder/model.safetensors" + } + } + } + } + + fn clip2_file(&self, use_f16: bool) -> &'static str { + match self { + Self::V1_5 | Self::V2_1 | Self::Xl => { + if use_f16 { + "text_encoder_2/model.fp16.safetensors" + } else { + "text_encoder_2/model.safetensors" + } + } + } + } +} + +impl ModelFile { + fn get( + &self, + filename: Option, + version: StableDiffusionVersion, + use_f16: bool, + ) -> Result { + use hf_hub::api::sync::Api; + match filename { + Some(filename) => Ok(std::path::PathBuf::from(filename)), + None => { + let (repo, path) = match self { + Self::Tokenizer => { + let tokenizer_repo = match version { + StableDiffusionVersion::V1_5 | StableDiffusionVersion::V2_1 => { + "openai/clip-vit-base-patch32" + } + StableDiffusionVersion::Xl => { + // This seems similar to the patch32 version except some very small + // difference in the split regex. + "openai/clip-vit-large-patch14" + } + }; + (tokenizer_repo, "tokenizer.json") + } + Self::Tokenizer2 => { + ("laion/CLIP-ViT-bigG-14-laion2B-39B-b160k", "tokenizer.json") + } + Self::Clip => (version.repo(), version.clip_file(use_f16)), + Self::Clip2 => (version.repo(), version.clip2_file(use_f16)), + Self::Unet => (version.repo(), version.unet_file(use_f16)), + Self::Vae => (version.repo(), version.vae_file(use_f16)), + }; + let filename = Api::new()?.model(repo.to_string()).get(path)?; + Ok(filename) + } + } + } +} + +fn output_filename( + basename: &str, + sample_idx: i64, + num_samples: i64, + timestep_idx: Option, +) -> String { + let filename = if num_samples > 1 { + match basename.rsplit_once('.') { + None => format!("{basename}.{sample_idx}.png"), + Some((filename_no_extension, extension)) => { + format!("{filename_no_extension}.{sample_idx}.{extension}") + } + } + } else { + basename.to_string() + }; + match timestep_idx { + None => filename, + Some(timestep_idx) => match filename.rsplit_once('.') { + None => format!("{filename}-{timestep_idx}.png"), + Some((filename_no_extension, extension)) => { + format!("{filename_no_extension}-{timestep_idx}.{extension}") + } + }, + } +} + +#[allow(clippy::too_many_arguments)] +fn text_embeddings( + prompt: &str, + uncond_prompt: &str, + tokenizer: Option, + clip_weights: Option, + sd_version: StableDiffusionVersion, + sd_config: &stable_diffusion::StableDiffusionConfig, + use_f16: bool, + device: &Device, + dtype: DType, + first: bool, +) -> Result { + let tokenizer_file = if first { + ModelFile::Tokenizer + } else { + ModelFile::Tokenizer2 + }; + let tokenizer = tokenizer_file.get(tokenizer, sd_version, use_f16)?; + let tokenizer = Tokenizer::from_file(tokenizer).map_err(E::msg)?; + let pad_id = match &sd_config.clip.pad_with { + Some(padding) => *tokenizer.get_vocab(true).get(padding.as_str()).unwrap(), + None => *tokenizer.get_vocab(true).get("<|endoftext|>").unwrap(), + }; + println!("Running with prompt \"{prompt}\"."); + let mut tokens = tokenizer + .encode(prompt, true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + while tokens.len() < sd_config.clip.max_position_embeddings { + tokens.push(pad_id) + } + let tokens = Tensor::new(tokens.as_slice(), device)?.unsqueeze(0)?; + + let mut uncond_tokens = tokenizer + .encode(uncond_prompt, true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + while uncond_tokens.len() < sd_config.clip.max_position_embeddings { + uncond_tokens.push(pad_id) + } + let uncond_tokens = Tensor::new(uncond_tokens.as_slice(), device)?.unsqueeze(0)?; + + println!("Building the Clip transformer."); + let clip_weights_file = if first { + ModelFile::Clip + } else { + ModelFile::Clip2 + }; + let clip_weights = clip_weights_file.get(clip_weights, sd_version, false)?; + let clip_config = if first { + &sd_config.clip + } else { + sd_config.clip2.as_ref().unwrap() + }; + let text_model = + stable_diffusion::build_clip_transformer(clip_config, clip_weights, device, DType::F32)?; + let text_embeddings = text_model.forward(&tokens)?; + let uncond_embeddings = text_model.forward(&uncond_tokens)?; + let text_embeddings = Tensor::cat(&[uncond_embeddings, text_embeddings], 0)?.to_dtype(dtype)?; + Ok(text_embeddings) +} + +fn image_preprocess>(path: T) -> anyhow::Result { + let img = image::io::Reader::open(path)?.decode()?; + let (height, width) = (img.height() as usize, img.width() as usize); + let height = height - height % 32; + let width = width - width % 32; + let img = img.resize_to_fill( + width as u32, + height as u32, + image::imageops::FilterType::CatmullRom, + ); + let img = img.to_rgb8(); + let img = img.into_raw(); + let img = Tensor::from_vec(img, (height, width, 3), &Device::Cpu)? + .permute((2, 0, 1))? + .to_dtype(DType::F32)? + .affine(2. / 255., -1.)? + .unsqueeze(0)?; + Ok(img) +} + +fn run(args: Args) -> Result<()> { + use tracing_chrome::ChromeLayerBuilder; + use tracing_subscriber::prelude::*; + + let Args { + prompt, + uncond_prompt, + cpu, + height, + width, + n_steps, + tokenizer, + final_image, + sliced_attention_size, + num_samples, + sd_version, + clip_weights, + vae_weights, + unet_weights, + tracing, + use_f16, + use_flash_attn, + img2img, + img2img_strength, + .. + } = args; + + if !(0. ..=1.).contains(&img2img_strength) { + anyhow::bail!("img2img-strength should be between 0 and 1, got {img2img_strength}") + } + + let _guard = if tracing { + let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); + tracing_subscriber::registry().with(chrome_layer).init(); + Some(guard) + } else { + None + }; + + let dtype = if use_f16 { DType::F16 } else { DType::F32 }; + let sd_config = match sd_version { + StableDiffusionVersion::V1_5 => { + stable_diffusion::StableDiffusionConfig::v1_5(sliced_attention_size, height, width) + } + StableDiffusionVersion::V2_1 => { + stable_diffusion::StableDiffusionConfig::v2_1(sliced_attention_size, height, width) + } + StableDiffusionVersion::Xl => { + stable_diffusion::StableDiffusionConfig::sdxl(sliced_attention_size, height, width) + } + }; + + let scheduler = sd_config.build_scheduler(n_steps)?; + let device = candle_examples::device(cpu)?; + + let which = match sd_version { + StableDiffusionVersion::Xl => vec![true, false], + _ => vec![true], + }; + let text_embeddings = which + .iter() + .map(|first| { + text_embeddings( + &prompt, + &uncond_prompt, + tokenizer.clone(), + clip_weights.clone(), + sd_version, + &sd_config, + use_f16, + &device, + dtype, + *first, + ) + }) + .collect::>>()?; + let text_embeddings = Tensor::cat(&text_embeddings, D::Minus1)?; + println!("{text_embeddings:?}"); + + println!("Building the autoencoder."); + let vae_weights = ModelFile::Vae.get(vae_weights, sd_version, use_f16)?; + let vae = sd_config.build_vae(&vae_weights, &device, dtype)?; + let init_latent_dist = match &img2img { + None => None, + Some(image) => { + let image = image_preprocess(image)?.to_device(&device)?; + Some(vae.encode(&image)?) + } + }; + println!("Building the unet."); + let unet_weights = ModelFile::Unet.get(unet_weights, sd_version, use_f16)?; + let unet = sd_config.build_unet(&unet_weights, &device, 4, use_flash_attn, dtype)?; + + let t_start = if img2img.is_some() { + n_steps - (n_steps as f64 * img2img_strength) as usize + } else { + 0 + }; + let bsize = 1; + for idx in 0..num_samples { + let timesteps = scheduler.timesteps(); + let latents = match &init_latent_dist { + Some(init_latent_dist) => { + let latents = (init_latent_dist.sample()? * 0.18215)?.to_device(&device)?; + if t_start < timesteps.len() { + let noise = latents.randn_like(0f64, 1f64)?; + scheduler.add_noise(&latents, noise, timesteps[t_start])? + } else { + latents + } + } + None => { + let latents = Tensor::randn( + 0f32, + 1f32, + (bsize, 4, sd_config.height / 8, sd_config.width / 8), + &device, + )?; + // scale the initial noise by the standard deviation required by the scheduler + (latents * scheduler.init_noise_sigma())? + } + }; + let mut latents = latents.to_dtype(dtype)?; + + println!("starting sampling"); + for (timestep_index, ×tep) in timesteps.iter().enumerate() { + if timestep_index < t_start { + continue; + } + let start_time = std::time::Instant::now(); + let latent_model_input = Tensor::cat(&[&latents, &latents], 0)?; + + let latent_model_input = scheduler.scale_model_input(latent_model_input, timestep)?; + let noise_pred = + unet.forward(&latent_model_input, timestep as f64, &text_embeddings)?; + let noise_pred = noise_pred.chunk(2, 0)?; + let (noise_pred_uncond, noise_pred_text) = (&noise_pred[0], &noise_pred[1]); + let noise_pred = + (noise_pred_uncond + ((noise_pred_text - noise_pred_uncond)? * GUIDANCE_SCALE)?)?; + latents = scheduler.step(&noise_pred, timestep, &latents)?; + let dt = start_time.elapsed().as_secs_f32(); + println!("step {}/{n_steps} done, {:.2}s", timestep_index + 1, dt); + + if args.intermediary_images { + let image = vae.decode(&(&latents / 0.18215)?)?; + let image = ((image / 2.)? + 0.5)?.to_device(&Device::Cpu)?; + let image = (image * 255.)?.to_dtype(DType::U8)?.i(0)?; + let image_filename = + output_filename(&final_image, idx + 1, num_samples, Some(timestep_index + 1)); + candle_examples::save_image(&image, image_filename)? + } + } + + println!( + "Generating the final image for sample {}/{}.", + idx + 1, + num_samples + ); + let image = vae.decode(&(&latents / 0.18215)?)?; + // TODO: Add the clamping between 0 and 1. + let image = ((image / 2.)? + 0.5)?.to_device(&Device::Cpu)?; + let image = (image * 255.)?.to_dtype(DType::U8)?.i(0)?; + let image_filename = output_filename(&final_image, idx + 1, num_samples, None); + candle_examples::save_image(&image, image_filename)? + } + Ok(()) +} + +fn main() -> Result<()> { + let args = Args::parse(); + run(args) +} diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index c35dea0b..8b621f64 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -54,7 +54,7 @@ pub struct Config { is_decoder: bool, is_encoder_decoder: bool, #[serde(default = "default_use_cache")] - use_cache: bool, + pub use_cache: bool, pub pad_token_id: usize, pub eos_token_id: usize, } @@ -245,10 +245,17 @@ struct T5Attention { relative_attention_num_buckets: usize, relative_attention_max_distance: usize, inner_dim: usize, + use_cache: bool, + kv_cache: Option<(Tensor, Tensor)>, } impl T5Attention { - fn load(has_relative_attention_bias: bool, vb: VarBuilder, cfg: &Config) -> Result { + fn load( + has_relative_attention_bias: bool, + decoder: bool, + vb: VarBuilder, + cfg: &Config, + ) -> Result { let inner_dim = cfg.num_heads * cfg.d_kv; let q = linear_no_bias(cfg.d_model, inner_dim, vb.pp("q"))?; let k = linear_no_bias(cfg.d_model, inner_dim, vb.pp("k"))?; @@ -275,11 +282,13 @@ impl T5Attention { relative_attention_num_buckets: cfg.relative_attention_num_buckets, relative_attention_max_distance: cfg.relative_attention_max_distance, inner_dim, + use_cache: cfg.use_cache && decoder, + kv_cache: None, }) } fn forward( - &self, + &mut self, xs: &Tensor, position_bias: Option<&Tensor>, key_value_states: Option<&Tensor>, @@ -287,7 +296,6 @@ impl T5Attention { ) -> Result<(Tensor, Option)> { // Performs Self-attention (if key_value_states is None) or attention // over source sentence (provided by key_value_states). - // TODO: kv caching. let kv_input = match key_value_states { None => xs, Some(key_value_states) => key_value_states, @@ -301,14 +309,22 @@ impl T5Attention { .reshape((b_sz, q_len, self.n_heads, self.d_kv))? .transpose(1, 2)? .contiguous()?; - let k = k + let mut k = k .reshape((b_sz, kv_len, self.n_heads, self.d_kv))? .transpose(1, 2)? .contiguous()?; - let v = v + let mut v = v .reshape((b_sz, kv_len, self.n_heads, self.d_kv))? .transpose(1, 2)? .contiguous()?; + + if self.use_cache { + if let Some((kv_cache_k, kv_cache_v)) = &self.kv_cache { + k = Tensor::cat(&[kv_cache_k, &k], 2)?.contiguous()?; + v = Tensor::cat(&[kv_cache_v, &v], 2)?.contiguous()?; + }; + self.kv_cache = Some((k.clone(), v.clone())); + }; // TODO: Use flash_attn. let scores = q.matmul(&k.t()?)?; let scores = match mask { @@ -394,8 +410,8 @@ struct T5LayerSelfAttention { } impl T5LayerSelfAttention { - fn load(h: bool, vb: VarBuilder, cfg: &Config) -> Result { - let self_attention = T5Attention::load(h, vb.pp("SelfAttention"), cfg)?; + fn load(h: bool, d: bool, vb: VarBuilder, cfg: &Config) -> Result { + let self_attention = T5Attention::load(h, d, vb.pp("SelfAttention"), cfg)?; let layer_norm = T5LayerNorm::load(cfg.d_model, cfg.layer_norm_epsilon, vb.pp("layer_norm"))?; Ok(Self { @@ -405,7 +421,7 @@ impl T5LayerSelfAttention { } fn forward( - &self, + &mut self, xs: &Tensor, position_bias: Option<&Tensor>, mask: Option<&Tensor>, @@ -426,8 +442,8 @@ struct T5LayerCrossAttention { } impl T5LayerCrossAttention { - fn load(vb: VarBuilder, cfg: &Config) -> Result { - let cross_attention = T5Attention::load(false, vb.pp("EncDecAttention"), cfg)?; + fn load(decoder: bool, vb: VarBuilder, cfg: &Config) -> Result { + let cross_attention = T5Attention::load(false, decoder, vb.pp("EncDecAttention"), cfg)?; let layer_norm = T5LayerNorm::load(cfg.d_model, cfg.layer_norm_epsilon, vb.pp("layer_norm"))?; Ok(Self { @@ -437,7 +453,7 @@ impl T5LayerCrossAttention { } fn forward( - &self, + &mut self, hidden_states: &Tensor, position_bias: Option<&Tensor>, key_value_states: &Tensor, @@ -462,11 +478,17 @@ struct T5Block { } impl T5Block { - fn load(has_relative_attention_bias: bool, vb: VarBuilder, cfg: &Config) -> Result { + fn load( + has_relative_attention_bias: bool, + decoder: bool, + vb: VarBuilder, + cfg: &Config, + ) -> Result { let vb = vb.pp("layer"); - let self_attn = T5LayerSelfAttention::load(has_relative_attention_bias, vb.pp("0"), cfg)?; + let self_attn = + T5LayerSelfAttention::load(has_relative_attention_bias, decoder, vb.pp("0"), cfg)?; let cross_attn = if cfg.is_decoder { - Some(T5LayerCrossAttention::load(vb.pp("1"), cfg)?) + Some(T5LayerCrossAttention::load(decoder, vb.pp("1"), cfg)?) } else { None }; @@ -480,19 +502,28 @@ impl T5Block { } fn forward( - &self, + &mut self, xs: &Tensor, position_bias: Option<&Tensor>, encoder_hidden_states: Option<&Tensor>, ) -> Result<(Tensor, Option)> { // TODO: Cache masks let mask = match self.cross_attn.is_some() { - true => Some(get_mask(xs.dim(1)?, xs.device())?), + true => { + let mask_len = xs.dim(1)?; + // If the input seq length is 1, no need for a mask, this is also helpful to avoid shape + // issues when using the KV cache in the decoder. + if mask_len <= 1 { + None + } else { + Some(get_mask(mask_len, xs.device())?) + } + } false => None, }; let (mut xs, position_bias) = self.self_attn.forward(xs, position_bias, mask.as_ref())?; // TODO: clamp for f16? - if let Some(cross_attn) = &self.cross_attn { + if let Some(cross_attn) = &mut self.cross_attn { (xs, _) = cross_attn.forward(&xs, None, encoder_hidden_states.unwrap())?; // TODO: clamp for f16? } @@ -510,9 +541,9 @@ struct T5Stack { } impl T5Stack { - fn load(vb: VarBuilder, shared: &Arc, cfg: &Config) -> Result { + fn load(decoder: bool, vb: VarBuilder, shared: &Arc, cfg: &Config) -> Result { let block = (0..cfg.num_layers) - .map(|i| T5Block::load(i == 0, vb.pp(&format!("block.{i}")), cfg)) + .map(|i| T5Block::load(i == 0, decoder, vb.pp(&format!("block.{i}")), cfg)) .collect::>>()?; let final_layer_norm = T5LayerNorm::load( cfg.d_model, @@ -527,14 +558,14 @@ impl T5Stack { } fn forward( - &self, + &mut self, input_ids: &Tensor, encoder_hidden_states: Option<&Tensor>, ) -> Result { let input_embeds = self.shared.as_ref().forward(input_ids)?; let mut hidden_states = input_embeds; let mut position_bias = None; - for block in self.block.iter() { + for block in self.block.iter_mut() { (hidden_states, position_bias) = block.forward( &hidden_states, position_bias.as_ref(), @@ -555,14 +586,14 @@ impl T5EncoderModel { pub fn load(vb: VarBuilder, cfg: &Config) -> Result { let shared = embedding(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; let shared = Arc::new(shared); - let encoder = T5Stack::load(vb.pp("encoder"), &shared, cfg)?; + let encoder = T5Stack::load(false, vb.pp("encoder"), &shared, cfg)?; Ok(Self { encoder, device: vb.device().clone(), }) } - pub fn forward(&self, input_ids: &Tensor) -> Result { + pub fn forward(&mut self, input_ids: &Tensor) -> Result { self.encoder.forward(input_ids, None) } @@ -589,13 +620,13 @@ impl T5ForConditionalGeneration { encoder_cfg.is_decoder = false; encoder_cfg.use_cache = false; encoder_cfg.is_encoder_decoder = false; - let encoder = T5Stack::load(vb.pp("encoder"), &shared, &encoder_cfg)?; + let encoder = T5Stack::load(false, vb.pp("encoder"), &shared, &encoder_cfg)?; let mut decoder_cfg = cfg.clone(); decoder_cfg.is_decoder = true; decoder_cfg.is_encoder_decoder = false; decoder_cfg.num_layers = cfg.num_decoder_layers.unwrap_or(cfg.num_layers); - let decoder = T5Stack::load(vb.pp("decoder"), &shared, &decoder_cfg)?; + let decoder = T5Stack::load(true, vb.pp("decoder"), &shared, &decoder_cfg)?; Ok(Self { encoder, @@ -605,7 +636,7 @@ impl T5ForConditionalGeneration { }) } - pub fn forward(&self, input_ids: &Tensor, decoder_input_ids: &Tensor) -> Result { + pub fn forward(&mut self, input_ids: &Tensor, decoder_input_ids: &Tensor) -> Result { let encoder_output = self.encoder.forward(input_ids, None)?; let decoder_output = self .decoder From eeb54716dd2635aa3dd9943fd3009eae863df71b Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 17 Sep 2023 11:05:15 +0200 Subject: [PATCH 111/150] Tweaks for the T5 example. (#874) --- README.md | 2 +- candle-examples/examples/t5/main.rs | 40 ++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d897de89..3d1b10fe 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ We also provide a some command line based examples using state of the art models - [Whisper](./candle-examples/examples/whisper/): speech recognition model. -- [Bert](./candle-examples/examples/bert/): useful for sentence embeddings. +- [T5](./candle-examples/examples/t5), [Bert](./candle-examples/examples/bert/): useful for sentence embeddings. - [DINOv2](./candle-examples/examples/dinov2/): computer vision model trained using self-supervision (can be used for imagenet classification, depth evaluation, segmentation). diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index c432e004..72be23bc 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -51,6 +51,22 @@ struct Args { /// L2 normalization for embeddings. #[arg(long, default_value = "true")] normalize_embeddings: bool, + + /// The temperature used to generate samples. + #[arg(long, default_value_t = 0.8)] + temperature: f64, + + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + + /// Penalty to be applied for repeating tokens, 1. means no penalty. + #[arg(long, default_value_t = 1.1)] + repeat_penalty: f32, + + /// The context size to consider for the repeat penalty. + #[arg(long, default_value_t = 64)] + repeat_last_n: usize, } struct T5ModelBuilder { @@ -149,7 +165,12 @@ fn main() -> Result<()> { } else { let mut model = builder.build_conditional_generation()?; let mut output_token_ids = [builder.config.pad_token_id as u32].to_vec(); - let mut logits_processor = LogitsProcessor::new(299792458, None, None); + let temperature = if args.temperature <= 0. { + None + } else { + Some(args.temperature) + }; + let mut logits_processor = LogitsProcessor::new(299792458, temperature, args.top_p); let start = std::time::Instant::now(); for index in 0.. { @@ -162,8 +183,21 @@ fn main() -> Result<()> { let last_token = *output_token_ids.last().unwrap(); Tensor::new(&[last_token], device)?.unsqueeze(0)? }; - let logits = model.forward(&input_token_ids, &decoder_token_ids)?; - let next_token_id = logits_processor.sample(&logits.flatten_to(1)?)?; + let logits = model + .forward(&input_token_ids, &decoder_token_ids)? + .squeeze(0)?; + let logits = if args.repeat_penalty == 1. { + logits + } else { + let start_at = tokens.len().saturating_sub(args.repeat_last_n); + candle_transformers::utils::apply_repeat_penalty( + &logits, + args.repeat_penalty, + &tokens[start_at..], + )? + }; + + let next_token_id = logits_processor.sample(&logits)?; if next_token_id as usize == builder.config.eos_token_id { break; } From 7f65af1f0dc518ef17623d718f3901f58c3aab06 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 17 Sep 2023 11:25:54 +0200 Subject: [PATCH 112/150] Avoid re-encoding the input in the T5 example. (#875) --- candle-examples/examples/t5/main.rs | 3 ++- candle-transformers/src/models/t5.rs | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index 72be23bc..36cbee7c 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -171,6 +171,7 @@ fn main() -> Result<()> { Some(args.temperature) }; let mut logits_processor = LogitsProcessor::new(299792458, temperature, args.top_p); + let encoder_output = model.encode(&input_token_ids)?; let start = std::time::Instant::now(); for index in 0.. { @@ -184,7 +185,7 @@ fn main() -> Result<()> { Tensor::new(&[last_token], device)?.unsqueeze(0)? }; let logits = model - .forward(&input_token_ids, &decoder_token_ids)? + .decode(&decoder_token_ids, &encoder_output)? .squeeze(0)?; let logits = if args.repeat_penalty == 1. { logits diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index 8b621f64..2ffc2ee1 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -636,11 +636,18 @@ impl T5ForConditionalGeneration { }) } - pub fn forward(&mut self, input_ids: &Tensor, decoder_input_ids: &Tensor) -> Result { - let encoder_output = self.encoder.forward(input_ids, None)?; + pub fn encode(&mut self, input_ids: &Tensor) -> Result { + self.encoder.forward(input_ids, None) + } + + pub fn decode( + &mut self, + decoder_input_ids: &Tensor, + encoder_output: &Tensor, + ) -> Result { let decoder_output = self .decoder - .forward(decoder_input_ids, Some(&encoder_output))?; + .forward(decoder_input_ids, Some(encoder_output))?; let sequence_output = decoder_output .narrow(1, decoder_output.dim(1)? - 1, 1)? .squeeze(1)?; @@ -651,6 +658,11 @@ impl T5ForConditionalGeneration { Ok(output) } + pub fn forward(&mut self, input_ids: &Tensor, decoder_input_ids: &Tensor) -> Result { + let encoder_output = self.encode(input_ids)?; + self.decode(decoder_input_ids, &encoder_output) + } + pub fn device(&self) -> &Device { &self.device } From db3e9dae048db2272ec6e4478f8f503c4b6745b6 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 17 Sep 2023 13:46:38 +0200 Subject: [PATCH 113/150] Wuerstchen main (#876) * Wuerstchen main. * More of the wuerstchen cli example. * Paella creation. * Build the prior model. * Fix the weight file names. --- candle-examples/examples/wuerstchen/main.rs | 359 +++++------------- .../src/models/stable_diffusion/clip.rs | 15 + .../src/models/wuerstchen/paella_vq.rs | 112 +++++- 3 files changed, 213 insertions(+), 273 deletions(-) diff --git a/candle-examples/examples/wuerstchen/main.rs b/candle-examples/examples/wuerstchen/main.rs index c8b771a0..32c7d158 100644 --- a/candle-examples/examples/wuerstchen/main.rs +++ b/candle-examples/examples/wuerstchen/main.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + #[cfg(feature = "accelerate")] extern crate accelerate_src; @@ -5,6 +7,7 @@ extern crate accelerate_src; extern crate intel_mkl_src; use candle_transformers::models::stable_diffusion; +use candle_transformers::models::wuerstchen; use anyhow::{Error as E, Result}; use candle::{DType, Device, IndexOp, Module, Tensor, D}; @@ -42,17 +45,21 @@ struct Args { #[arg(long)] width: Option, - /// The UNet weight file, in .safetensors format. + /// The decoder weight file, in .safetensors format. #[arg(long, value_name = "FILE")] - unet_weights: Option, + decoder_weights: Option, /// The CLIP weight file, in .safetensors format. #[arg(long, value_name = "FILE")] clip_weights: Option, - /// The VAE weight file, in .safetensors format. + /// The prior weight file, in .safetensors format. #[arg(long, value_name = "FILE")] - vae_weights: Option, + prior_weights: Option, + + /// The VQGAN weight file, in .safetensors format. + #[arg(long, value_name = "FILE")] + vqgan_weights: Option, #[arg(long, value_name = "FILE")] /// The file specifying the tokenizer to used for tokenization. @@ -73,138 +80,31 @@ struct Args { /// The name of the final image to generate. #[arg(long, value_name = "FILE", default_value = "sd_final.png")] final_image: String, - - #[arg(long, value_enum, default_value = "v2-1")] - sd_version: StableDiffusionVersion, - - /// Generate intermediary images at each step. - #[arg(long, action)] - intermediary_images: bool, - - #[arg(long)] - use_flash_attn: bool, - - #[arg(long)] - use_f16: bool, - - #[arg(long, value_name = "FILE")] - img2img: Option, - - /// The strength, indicates how much to transform the initial image. The - /// value must be between 0 and 1, a value of 1 discards the initial image - /// information. - #[arg(long, default_value_t = 0.8)] - img2img_strength: f64, } -#[derive(Debug, Clone, Copy, clap::ValueEnum)] -enum StableDiffusionVersion { - V1_5, - V2_1, - Xl, -} - -#[allow(unused)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ModelFile { Tokenizer, - Tokenizer2, Clip, - Clip2, - Unet, - Vae, -} - -impl StableDiffusionVersion { - fn repo(&self) -> &'static str { - match self { - Self::Xl => "stabilityai/stable-diffusion-xl-base-1.0", - Self::V2_1 => "stabilityai/stable-diffusion-2-1", - Self::V1_5 => "runwayml/stable-diffusion-v1-5", - } - } - - fn unet_file(&self, use_f16: bool) -> &'static str { - match self { - Self::V1_5 | Self::V2_1 | Self::Xl => { - if use_f16 { - "unet/diffusion_pytorch_model.fp16.safetensors" - } else { - "unet/diffusion_pytorch_model.safetensors" - } - } - } - } - - fn vae_file(&self, use_f16: bool) -> &'static str { - match self { - Self::V1_5 | Self::V2_1 | Self::Xl => { - if use_f16 { - "vae/diffusion_pytorch_model.fp16.safetensors" - } else { - "vae/diffusion_pytorch_model.safetensors" - } - } - } - } - - fn clip_file(&self, use_f16: bool) -> &'static str { - match self { - Self::V1_5 | Self::V2_1 | Self::Xl => { - if use_f16 { - "text_encoder/model.fp16.safetensors" - } else { - "text_encoder/model.safetensors" - } - } - } - } - - fn clip2_file(&self, use_f16: bool) -> &'static str { - match self { - Self::V1_5 | Self::V2_1 | Self::Xl => { - if use_f16 { - "text_encoder_2/model.fp16.safetensors" - } else { - "text_encoder_2/model.safetensors" - } - } - } - } + Decoder, + VqGan, + Prior, } impl ModelFile { - fn get( - &self, - filename: Option, - version: StableDiffusionVersion, - use_f16: bool, - ) -> Result { + fn get(&self, filename: Option) -> Result { use hf_hub::api::sync::Api; match filename { Some(filename) => Ok(std::path::PathBuf::from(filename)), None => { + let repo_main = "warp-ai/wuerstchen"; + let repo_prior = "warp-ai/wuerstchen-prior"; let (repo, path) = match self { - Self::Tokenizer => { - let tokenizer_repo = match version { - StableDiffusionVersion::V1_5 | StableDiffusionVersion::V2_1 => { - "openai/clip-vit-base-patch32" - } - StableDiffusionVersion::Xl => { - // This seems similar to the patch32 version except some very small - // difference in the split regex. - "openai/clip-vit-large-patch14" - } - }; - (tokenizer_repo, "tokenizer.json") - } - Self::Tokenizer2 => { - ("laion/CLIP-ViT-bigG-14-laion2B-39B-b160k", "tokenizer.json") - } - Self::Clip => (version.repo(), version.clip_file(use_f16)), - Self::Clip2 => (version.repo(), version.clip2_file(use_f16)), - Self::Unet => (version.repo(), version.unet_file(use_f16)), - Self::Vae => (version.repo(), version.vae_file(use_f16)), + Self::Tokenizer => (repo_main, "tokenizer/tokenizer.json"), + Self::Clip => (repo_main, "text_encoder/model.safetensors"), + Self::Decoder => (repo_main, "decoder/diffusion_pytorch_model.safetensors"), + Self::VqGan => (repo_main, "vqgan/diffusion_pytorch_model.safetensors"), + Self::Prior => (repo_prior, "prior/diffusion_pytorch_model.safetensors"), }; let filename = Api::new()?.model(repo.to_string()).get(path)?; Ok(filename) @@ -240,27 +140,17 @@ fn output_filename( } } -#[allow(clippy::too_many_arguments)] -fn text_embeddings( +fn encode_prompt( prompt: &str, uncond_prompt: &str, tokenizer: Option, clip_weights: Option, - sd_version: StableDiffusionVersion, - sd_config: &stable_diffusion::StableDiffusionConfig, - use_f16: bool, + clip_config: stable_diffusion::clip::Config, device: &Device, - dtype: DType, - first: bool, ) -> Result { - let tokenizer_file = if first { - ModelFile::Tokenizer - } else { - ModelFile::Tokenizer2 - }; - let tokenizer = tokenizer_file.get(tokenizer, sd_version, use_f16)?; + let tokenizer = ModelFile::Tokenizer.get(tokenizer)?; let tokenizer = Tokenizer::from_file(tokenizer).map_err(E::msg)?; - let pad_id = match &sd_config.clip.pad_with { + let pad_id = match &clip_config.pad_with { Some(padding) => *tokenizer.get_vocab(true).get(padding.as_str()).unwrap(), None => *tokenizer.get_vocab(true).get("<|endoftext|>").unwrap(), }; @@ -270,7 +160,7 @@ fn text_embeddings( .map_err(E::msg)? .get_ids() .to_vec(); - while tokens.len() < sd_config.clip.max_position_embeddings { + while tokens.len() < clip_config.max_position_embeddings { tokens.push(pad_id) } let tokens = Tensor::new(tokens.as_slice(), device)?.unsqueeze(0)?; @@ -280,51 +170,21 @@ fn text_embeddings( .map_err(E::msg)? .get_ids() .to_vec(); - while uncond_tokens.len() < sd_config.clip.max_position_embeddings { + while uncond_tokens.len() < clip_config.max_position_embeddings { uncond_tokens.push(pad_id) } let uncond_tokens = Tensor::new(uncond_tokens.as_slice(), device)?.unsqueeze(0)?; println!("Building the Clip transformer."); - let clip_weights_file = if first { - ModelFile::Clip - } else { - ModelFile::Clip2 - }; - let clip_weights = clip_weights_file.get(clip_weights, sd_version, false)?; - let clip_config = if first { - &sd_config.clip - } else { - sd_config.clip2.as_ref().unwrap() - }; + let clip_weights = ModelFile::Clip.get(clip_weights)?; let text_model = - stable_diffusion::build_clip_transformer(clip_config, clip_weights, device, DType::F32)?; + stable_diffusion::build_clip_transformer(&clip_config, clip_weights, device, DType::F32)?; let text_embeddings = text_model.forward(&tokens)?; let uncond_embeddings = text_model.forward(&uncond_tokens)?; - let text_embeddings = Tensor::cat(&[uncond_embeddings, text_embeddings], 0)?.to_dtype(dtype)?; + let text_embeddings = Tensor::cat(&[uncond_embeddings, text_embeddings], 0)?; Ok(text_embeddings) } -fn image_preprocess>(path: T) -> anyhow::Result { - let img = image::io::Reader::open(path)?.decode()?; - let (height, width) = (img.height() as usize, img.width() as usize); - let height = height - height % 32; - let width = width - width % 32; - let img = img.resize_to_fill( - width as u32, - height as u32, - image::imageops::FilterType::CatmullRom, - ); - let img = img.to_rgb8(); - let img = img.into_raw(); - let img = Tensor::from_vec(img, (height, width, 3), &Device::Cpu)? - .permute((2, 0, 1))? - .to_dtype(DType::F32)? - .affine(2. / 255., -1.)? - .unsqueeze(0)?; - Ok(img) -} - fn run(args: Args) -> Result<()> { use tracing_chrome::ChromeLayerBuilder; use tracing_subscriber::prelude::*; @@ -340,22 +200,14 @@ fn run(args: Args) -> Result<()> { final_image, sliced_attention_size, num_samples, - sd_version, clip_weights, - vae_weights, - unet_weights, + prior_weights, + vqgan_weights, + decoder_weights, tracing, - use_f16, - use_flash_attn, - img2img, - img2img_strength, .. } = args; - if !(0. ..=1.).contains(&img2img_strength) { - anyhow::bail!("img2img-strength should be between 0 and 1, got {img2img_strength}") - } - let _guard = if tracing { let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); tracing_subscriber::registry().with(chrome_layer).init(); @@ -364,102 +216,75 @@ fn run(args: Args) -> Result<()> { None }; - let dtype = if use_f16 { DType::F16 } else { DType::F32 }; - let sd_config = match sd_version { - StableDiffusionVersion::V1_5 => { - stable_diffusion::StableDiffusionConfig::v1_5(sliced_attention_size, height, width) - } - StableDiffusionVersion::V2_1 => { - stable_diffusion::StableDiffusionConfig::v2_1(sliced_attention_size, height, width) - } - StableDiffusionVersion::Xl => { - stable_diffusion::StableDiffusionConfig::sdxl(sliced_attention_size, height, width) - } - }; - - let scheduler = sd_config.build_scheduler(n_steps)?; let device = candle_examples::device(cpu)?; - let which = match sd_version { - StableDiffusionVersion::Xl => vec![true, false], - _ => vec![true], - }; - let text_embeddings = which - .iter() - .map(|first| { - text_embeddings( - &prompt, - &uncond_prompt, - tokenizer.clone(), - clip_weights.clone(), - sd_version, - &sd_config, - use_f16, - &device, - dtype, - *first, - ) - }) - .collect::>>()?; - let text_embeddings = Tensor::cat(&text_embeddings, D::Minus1)?; + let text_embeddings = encode_prompt( + &prompt, + &uncond_prompt, + tokenizer.clone(), + clip_weights.clone(), + stable_diffusion::clip::Config::wuerstchen(), + &device, + ); println!("{text_embeddings:?}"); - println!("Building the autoencoder."); - let vae_weights = ModelFile::Vae.get(vae_weights, sd_version, use_f16)?; - let vae = sd_config.build_vae(&vae_weights, &device, dtype)?; - let init_latent_dist = match &img2img { - None => None, - Some(image) => { - let image = image_preprocess(image)?.to_device(&device)?; - Some(vae.encode(&image)?) - } + println!("Building the prior."); + // https://huggingface.co/warp-ai/wuerstchen-prior/blob/main/prior/config.json + let _prior = { + let prior_weights = ModelFile::Prior.get(prior_weights)?; + let weights = unsafe { candle::safetensors::MmapedFile::new(prior_weights)? }; + let weights = weights.deserialize()?; + let vb = candle_nn::VarBuilder::from_safetensors(vec![weights], DType::F32, &device); + wuerstchen::prior::WPrior::new( + /* c_in */ 16, /* c */ 1536, /* c_cond */ 1280, /* c_r */ 64, + /* depth */ 32, /* nhead */ 24, vb, + ) }; - println!("Building the unet."); - let unet_weights = ModelFile::Unet.get(unet_weights, sd_version, use_f16)?; - let unet = sd_config.build_unet(&unet_weights, &device, 4, use_flash_attn, dtype)?; - let t_start = if img2img.is_some() { - n_steps - (n_steps as f64 * img2img_strength) as usize - } else { - 0 + println!("Building the vqgan."); + let _vqgan = { + let vqgan_weights = ModelFile::VqGan.get(vqgan_weights)?; + let weights = unsafe { candle::safetensors::MmapedFile::new(vqgan_weights)? }; + let weights = weights.deserialize()?; + let vb = candle_nn::VarBuilder::from_safetensors(vec![weights], DType::F32, &device); + wuerstchen::paella_vq::PaellaVQ::new(vb)? }; - let bsize = 1; + + println!("Building the decoder."); + + // https://huggingface.co/warp-ai/wuerstchen/blob/main/decoder/config.json + let _decoder = { + let decoder_weights = ModelFile::Decoder.get(decoder_weights)?; + let weights = unsafe { candle::safetensors::MmapedFile::new(decoder_weights)? }; + let weights = weights.deserialize()?; + let vb = candle_nn::VarBuilder::from_safetensors(vec![weights], DType::F32, &device); + wuerstchen::diffnext::WDiffNeXt::new( + /* c_in */ 4, /* c_out */ 4, /* c_r */ 64, /* c_cond */ 1024, + /* clip_embd */ 1024, /* patch_size */ 2, vb, + )? + }; + + let _bsize = 1; for idx in 0..num_samples { + /* let timesteps = scheduler.timesteps(); - let latents = match &init_latent_dist { - Some(init_latent_dist) => { - let latents = (init_latent_dist.sample()? * 0.18215)?.to_device(&device)?; - if t_start < timesteps.len() { - let noise = latents.randn_like(0f64, 1f64)?; - scheduler.add_noise(&latents, noise, timesteps[t_start])? - } else { - latents - } - } - None => { - let latents = Tensor::randn( - 0f32, - 1f32, - (bsize, 4, sd_config.height / 8, sd_config.width / 8), - &device, - )?; - // scale the initial noise by the standard deviation required by the scheduler - (latents * scheduler.init_noise_sigma())? - } - }; - let mut latents = latents.to_dtype(dtype)?; + let latents = Tensor::randn( + 0f32, + 1f32, + (bsize, 4, sd_config.height / 8, sd_config.width / 8), + &device, + )?; + // scale the initial noise by the standard deviation required by the scheduler + let mut latents = latents * scheduler.init_noise_sigma()?; println!("starting sampling"); for (timestep_index, ×tep) in timesteps.iter().enumerate() { - if timestep_index < t_start { - continue; - } let start_time = std::time::Instant::now(); let latent_model_input = Tensor::cat(&[&latents, &latents], 0)?; let latent_model_input = scheduler.scale_model_input(latent_model_input, timestep)?; let noise_pred = - unet.forward(&latent_model_input, timestep as f64, &text_embeddings)?; + decoder.forward(&latent_model_input, timestep as f64, &text_embeddings)?; let noise_pred = noise_pred.chunk(2, 0)?; let (noise_pred_uncond, noise_pred_text) = (&noise_pred[0], &noise_pred[1]); let noise_pred = @@ -467,28 +292,22 @@ fn run(args: Args) -> Result<()> { latents = scheduler.step(&noise_pred, timestep, &latents)?; let dt = start_time.elapsed().as_secs_f32(); println!("step {}/{n_steps} done, {:.2}s", timestep_index + 1, dt); - - if args.intermediary_images { - let image = vae.decode(&(&latents / 0.18215)?)?; - let image = ((image / 2.)? + 0.5)?.to_device(&Device::Cpu)?; - let image = (image * 255.)?.to_dtype(DType::U8)?.i(0)?; - let image_filename = - output_filename(&final_image, idx + 1, num_samples, Some(timestep_index + 1)); - candle_examples::save_image(&image, image_filename)? - } } + */ println!( "Generating the final image for sample {}/{}.", idx + 1, num_samples ); + /* let image = vae.decode(&(&latents / 0.18215)?)?; // TODO: Add the clamping between 0 and 1. let image = ((image / 2.)? + 0.5)?.to_device(&Device::Cpu)?; let image = (image * 255.)?.to_dtype(DType::U8)?.i(0)?; let image_filename = output_filename(&final_image, idx + 1, num_samples, None); candle_examples::save_image(&image, image_filename)? + */ } Ok(()) } diff --git a/candle-transformers/src/models/stable_diffusion/clip.rs b/candle-transformers/src/models/stable_diffusion/clip.rs index 397a1cef..31d025b3 100644 --- a/candle-transformers/src/models/stable_diffusion/clip.rs +++ b/candle-transformers/src/models/stable_diffusion/clip.rs @@ -99,6 +99,21 @@ impl Config { activation: Activation::Gelu, } } + + // https://huggingface.co/warp-ai/wuerstchen/blob/main/text_encoder/config.json + pub fn wuerstchen() -> Self { + Self { + vocab_size: 49408, + embed_dim: 1024, + intermediate_size: 4096, + max_position_embeddings: 77, + pad_with: Some("!".to_string()), + num_hidden_layers: 24, + num_attention_heads: 16, + projection_dim: 1024, + activation: Activation::Gelu, + } + } } // CLIP Text Model diff --git a/candle-transformers/src/models/wuerstchen/paella_vq.rs b/candle-transformers/src/models/wuerstchen/paella_vq.rs index 1268047a..a60f8e8a 100644 --- a/candle-transformers/src/models/wuerstchen/paella_vq.rs +++ b/candle-transformers/src/models/wuerstchen/paella_vq.rs @@ -65,17 +65,121 @@ impl Module for MixingResidualBlock { } #[derive(Debug)] -struct PaellaVQ { +pub struct PaellaVQ { in_block_conv: candle_nn::Conv2d, out_block_conv: candle_nn::Conv2d, down_blocks: Vec<(Option, MixingResidualBlock)>, down_blocks_conv: candle_nn::Conv2d, down_blocks_bn: candle_nn::BatchNorm, up_blocks_conv: candle_nn::Conv2d, - up_blocks: Vec<(MixingResidualBlock, Option)>, + up_blocks: Vec<(Vec, Option)>, } impl PaellaVQ { + pub fn new(vb: VarBuilder) -> Result { + const IN_CHANNELS: usize = 3; + const OUT_CHANNELS: usize = 3; + const LATENT_CHANNELS: usize = 4; + const EMBED_DIM: usize = 384; + const BOTTLENECK_BLOCKS: usize = 12; + const C_LEVELS: [usize; 2] = [EMBED_DIM / 2, EMBED_DIM]; + + let in_block_conv = candle_nn::conv2d( + IN_CHANNELS * 4, + C_LEVELS[0], + 1, + Default::default(), + vb.pp("in_block.1"), + )?; + let out_block_conv = candle_nn::conv2d( + C_LEVELS[0], + OUT_CHANNELS * 4, + 1, + Default::default(), + vb.pp("out_block.0"), + )?; + + let mut down_blocks = Vec::new(); + let vb_d = vb.pp("down_blocks"); + let mut d_idx = 0; + for (i, &c_level) in C_LEVELS.iter().enumerate() { + let conv_block = if i > 0 { + let cfg = candle_nn::Conv2dConfig { + padding: 1, + stride: 2, + ..Default::default() + }; + let block = + candle_nn::conv2d_no_bias(C_LEVELS[i - 1], c_level, 4, cfg, vb_d.pp(d_idx))?; + d_idx += 1; + Some(block) + } else { + None + }; + let res_block = MixingResidualBlock::new(c_level, c_level * 4, vb_d.pp(d_idx))?; + d_idx += 1; + down_blocks.push((conv_block, res_block)) + } + let down_blocks_conv = candle_nn::conv2d_no_bias( + C_LEVELS[1], + LATENT_CHANNELS, + 1, + Default::default(), + vb_d.pp(d_idx), + )?; + d_idx += 1; + let down_blocks_bn = candle_nn::batch_norm(LATENT_CHANNELS, 1e-5, vb_d.pp(d_idx))?; + + let mut up_blocks = Vec::new(); + let vb_u = vb.pp("up_blocks"); + let mut u_idx = 0; + let up_blocks_conv = candle_nn::conv2d_no_bias( + LATENT_CHANNELS, + C_LEVELS[1], + 1, + Default::default(), + vb_u.pp(u_idx), + )?; + u_idx += 1; + for (i, &c_level) in C_LEVELS.iter().rev().enumerate() { + let mut res_blocks = Vec::new(); + let n_bottleneck_blocks = if i == 0 { BOTTLENECK_BLOCKS } else { 1 }; + for _j in 0..n_bottleneck_blocks { + let res_block = MixingResidualBlock::new(c_level, c_level * 4, vb_u.pp(u_idx))?; + u_idx += 1; + res_blocks.push(res_block) + } + let conv_block = if i < C_LEVELS.len() - 1 { + let cfg = candle_nn::ConvTranspose2dConfig { + padding: 1, + stride: 2, + ..Default::default() + }; + let block = candle_nn::conv_transpose2d_no_bias( + c_level, + C_LEVELS[i - 1], + 4, + cfg, + vb_u.pp(u_idx), + )?; + u_idx += 1; + Some(block) + } else { + None + }; + up_blocks.push((res_blocks, conv_block)) + } + Ok(Self { + in_block_conv, + down_blocks, + down_blocks_conv, + down_blocks_bn, + up_blocks, + up_blocks_conv, + out_block_conv, + }) + } + pub fn encode(&self, xs: &Tensor) -> Result { let mut xs = candle_nn::ops::pixel_unshuffle(xs, 2)?.apply(&self.in_block_conv)?; for down_block in self.down_blocks.iter() { @@ -92,7 +196,9 @@ impl PaellaVQ { // TODO: quantizer if we want to support `force_not_quantize=False`. let mut xs = xs.apply(&self.up_blocks_conv)?; for up_block in self.up_blocks.iter() { - xs = xs.apply(&up_block.0)?; + for b in up_block.0.iter() { + xs = xs.apply(b)?; + } if let Some(conv) = &up_block.1 { xs = xs.apply(conv)? } From 5f83c13f17a7b16955c9b649424aca276d5e930d Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 17 Sep 2023 15:03:01 +0100 Subject: [PATCH 114/150] Add the DDPM scheduler. (#877) * Add the DDPM scheduler. * Minor tweaks. --- candle-examples/examples/wuerstchen/main.rs | 24 +- .../src/models/stable_diffusion/ddpm.rs | 205 ++++++++++++++++++ .../src/models/stable_diffusion/mod.rs | 1 + 3 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 candle-transformers/src/models/stable_diffusion/ddpm.rs diff --git a/candle-examples/examples/wuerstchen/main.rs b/candle-examples/examples/wuerstchen/main.rs index 32c7d158..7e4d2360 100644 --- a/candle-examples/examples/wuerstchen/main.rs +++ b/candle-examples/examples/wuerstchen/main.rs @@ -15,6 +15,7 @@ use clap::Parser; use tokenizers::Tokenizer; const GUIDANCE_SCALE: f64 = 7.5; +const RESOLUTION_MULTIPLE: f64 = 42.67; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -217,6 +218,8 @@ fn run(args: Args) -> Result<()> { }; let device = candle_examples::device(cpu)?; + let height = height.unwrap_or(1024); + let width = width.unwrap_or(1024); let text_embeddings = encode_prompt( &prompt, @@ -225,12 +228,12 @@ fn run(args: Args) -> Result<()> { clip_weights.clone(), stable_diffusion::clip::Config::wuerstchen(), &device, - ); + )?; println!("{text_embeddings:?}"); println!("Building the prior."); // https://huggingface.co/warp-ai/wuerstchen-prior/blob/main/prior/config.json - let _prior = { + let prior = { let prior_weights = ModelFile::Prior.get(prior_weights)?; let weights = unsafe { candle::safetensors::MmapedFile::new(prior_weights)? }; let weights = weights.deserialize()?; @@ -238,7 +241,7 @@ fn run(args: Args) -> Result<()> { wuerstchen::prior::WPrior::new( /* c_in */ 16, /* c */ 1536, /* c_cond */ 1280, /* c_r */ 64, /* depth */ 32, /* nhead */ 24, vb, - ) + )? }; println!("Building the vqgan."); @@ -264,8 +267,21 @@ fn run(args: Args) -> Result<()> { )? }; - let _bsize = 1; + let latent_height = (height as f64 / RESOLUTION_MULTIPLE).ceil() as usize; + let latent_width = (width as f64 / RESOLUTION_MULTIPLE).ceil() as usize; + let b_size = 1; for idx in 0..num_samples { + let latents = Tensor::randn( + 0f32, + 1f32, + (b_size, 4, latent_height, latent_width), + &device, + )?; + // TODO: latents denoising loop, use the scheduler values. + let ratio = Tensor::ones(1, DType::F32, &device)?; + let prior = prior.forward(&latents, &ratio, &text_embeddings)?; + + let latents = ((latents * 42.)? - 1.)?; /* let timesteps = scheduler.timesteps(); let latents = Tensor::randn( diff --git a/candle-transformers/src/models/stable_diffusion/ddpm.rs b/candle-transformers/src/models/stable_diffusion/ddpm.rs new file mode 100644 index 00000000..d393f39a --- /dev/null +++ b/candle-transformers/src/models/stable_diffusion/ddpm.rs @@ -0,0 +1,205 @@ +use super::schedulers::{betas_for_alpha_bar, BetaSchedule, PredictionType}; +use candle::{Result, Tensor}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DDPMVarianceType { + FixedSmall, + FixedSmallLog, + FixedLarge, + FixedLargeLog, + Learned, +} + +impl Default for DDPMVarianceType { + fn default() -> Self { + Self::FixedSmall + } +} + +#[derive(Debug, Clone)] +pub struct DDPMSchedulerConfig { + /// The value of beta at the beginning of training. + pub beta_start: f64, + /// The value of beta at the end of training. + pub beta_end: f64, + /// How beta evolved during training. + pub beta_schedule: BetaSchedule, + /// Option to predicted sample between -1 and 1 for numerical stability. + pub clip_sample: bool, + /// Option to clip the variance used when adding noise to the denoised sample. + pub variance_type: DDPMVarianceType, + /// prediction type of the scheduler function + pub prediction_type: PredictionType, + /// number of diffusion steps used to train the model. + pub train_timesteps: usize, +} + +impl Default for DDPMSchedulerConfig { + fn default() -> Self { + Self { + beta_start: 0.00085, + beta_end: 0.012, + beta_schedule: BetaSchedule::ScaledLinear, + clip_sample: false, + variance_type: DDPMVarianceType::FixedSmall, + prediction_type: PredictionType::Epsilon, + train_timesteps: 1000, + } + } +} + +pub struct DDPMScheduler { + alphas_cumprod: Vec, + init_noise_sigma: f64, + timesteps: Vec, + step_ratio: usize, + pub config: DDPMSchedulerConfig, +} + +impl DDPMScheduler { + pub fn new(inference_steps: usize, config: DDPMSchedulerConfig) -> Result { + let betas = match config.beta_schedule { + BetaSchedule::ScaledLinear => super::utils::linspace( + config.beta_start.sqrt(), + config.beta_end.sqrt(), + config.train_timesteps, + )? + .sqr()?, + BetaSchedule::Linear => { + super::utils::linspace(config.beta_start, config.beta_end, config.train_timesteps)? + } + BetaSchedule::SquaredcosCapV2 => betas_for_alpha_bar(config.train_timesteps, 0.999)?, + }; + + let betas = betas.to_vec1::()?; + let mut alphas_cumprod = Vec::with_capacity(betas.len()); + for &beta in betas.iter() { + let alpha = 1.0 - beta; + alphas_cumprod.push(alpha * *alphas_cumprod.last().unwrap_or(&1f64)) + } + + // min(train_timesteps, inference_steps) + // https://github.com/huggingface/diffusers/blob/8331da46837be40f96fbd24de6a6fb2da28acd11/src/diffusers/schedulers/scheduling_ddpm.py#L187 + let inference_steps = inference_steps.min(config.train_timesteps); + // arange the number of the scheduler's timesteps + let step_ratio = config.train_timesteps / inference_steps; + let timesteps: Vec = (0..inference_steps).map(|s| s * step_ratio).rev().collect(); + + Ok(Self { + alphas_cumprod, + init_noise_sigma: 1.0, + timesteps, + step_ratio, + config, + }) + } + + fn get_variance(&self, timestep: usize) -> f64 { + let prev_t = timestep as isize - self.step_ratio as isize; + let alpha_prod_t = self.alphas_cumprod[timestep]; + let alpha_prod_t_prev = if prev_t >= 0 { + self.alphas_cumprod[prev_t as usize] + } else { + 1.0 + }; + let current_beta_t = 1. - alpha_prod_t / alpha_prod_t_prev; + + // For t > 0, compute predicted variance βt (see formula (6) and (7) from https://arxiv.org/pdf/2006.11239.pdf) + // and sample from it to get previous sample + // x_{t-1} ~ N(pred_prev_sample, variance) == add variance to pred_sample + let variance = (1. - alpha_prod_t_prev) / (1. - alpha_prod_t) * current_beta_t; + + // retrieve variance + match self.config.variance_type { + DDPMVarianceType::FixedSmall => variance.max(1e-20), + // for rl-diffuser https://arxiv.org/abs/2205.09991 + DDPMVarianceType::FixedSmallLog => { + let variance = variance.max(1e-20).ln(); + (variance * 0.5).exp() + } + DDPMVarianceType::FixedLarge => current_beta_t, + DDPMVarianceType::FixedLargeLog => current_beta_t.ln(), + DDPMVarianceType::Learned => variance, + } + } + + pub fn timesteps(&self) -> &[usize] { + self.timesteps.as_slice() + } + + /// Ensures interchangeability with schedulers that need to scale the denoising model input + /// depending on the current timestep. + pub fn scale_model_input(&self, sample: Tensor, _timestep: usize) -> Tensor { + sample + } + + pub fn step(&self, model_output: &Tensor, timestep: usize, sample: &Tensor) -> Result { + let prev_t = timestep as isize - self.step_ratio as isize; + + // https://github.com/huggingface/diffusers/blob/df2b548e893ccb8a888467c2508756680df22821/src/diffusers/schedulers/scheduling_ddpm.py#L272 + // 1. compute alphas, betas + let alpha_prod_t = self.alphas_cumprod[timestep]; + let alpha_prod_t_prev = if prev_t >= 0 { + self.alphas_cumprod[prev_t as usize] + } else { + 1.0 + }; + let beta_prod_t = 1. - alpha_prod_t; + let beta_prod_t_prev = 1. - alpha_prod_t_prev; + let current_alpha_t = alpha_prod_t / alpha_prod_t_prev; + let current_beta_t = 1. - current_alpha_t; + + // 2. compute predicted original sample from predicted noise also called "predicted x_0" of formula (15) + let mut pred_original_sample = match self.config.prediction_type { + PredictionType::Epsilon => { + ((sample - model_output * beta_prod_t.sqrt())? / alpha_prod_t.sqrt())? + } + PredictionType::Sample => model_output.clone(), + PredictionType::VPrediction => { + ((sample * alpha_prod_t.sqrt())? - model_output * beta_prod_t.sqrt())? + } + }; + + // 3. clip predicted x_0 + if self.config.clip_sample { + pred_original_sample = pred_original_sample.clamp(-1f32, 1f32)?; + } + + // 4. Compute coefficients for pred_original_sample x_0 and current sample x_t + // See formula (7) from https://arxiv.org/pdf/2006.11239.pdf + let pred_original_sample_coeff = (alpha_prod_t_prev.sqrt() * current_beta_t) / beta_prod_t; + let current_sample_coeff = current_alpha_t.sqrt() * beta_prod_t_prev / beta_prod_t; + + // 5. Compute predicted previous sample µ_t + // See formula (7) from https://arxiv.org/pdf/2006.11239.pdf + let pred_prev_sample = ((&pred_original_sample * pred_original_sample_coeff)? + + sample * current_sample_coeff)?; + + // https://github.com/huggingface/diffusers/blob/df2b548e893ccb8a888467c2508756680df22821/src/diffusers/schedulers/scheduling_ddpm.py#L305 + // 6. Add noise + let mut variance = model_output.zeros_like()?; + if timestep > 0 { + let variance_noise = model_output.randn_like(0., 1.)?; + if self.config.variance_type == DDPMVarianceType::FixedSmallLog { + variance = (variance_noise * self.get_variance(timestep))?; + } else { + variance = (variance_noise * self.get_variance(timestep).sqrt())?; + } + } + &pred_prev_sample + variance + } + + pub fn add_noise( + &self, + original_samples: &Tensor, + noise: Tensor, + timestep: usize, + ) -> Result { + (original_samples * self.alphas_cumprod[timestep].sqrt())? + + noise * (1. - self.alphas_cumprod[timestep]).sqrt() + } + + pub fn init_noise_sigma(&self) -> f64 { + self.init_noise_sigma + } +} diff --git a/candle-transformers/src/models/stable_diffusion/mod.rs b/candle-transformers/src/models/stable_diffusion/mod.rs index d9721532..c6f1b904 100644 --- a/candle-transformers/src/models/stable_diffusion/mod.rs +++ b/candle-transformers/src/models/stable_diffusion/mod.rs @@ -1,6 +1,7 @@ pub mod attention; pub mod clip; pub mod ddim; +pub mod ddpm; pub mod embeddings; pub mod resnet; pub mod schedulers; From 06cc329e715cbb820343f9849a4a45c818cb8c5e Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 17 Sep 2023 15:59:27 +0100 Subject: [PATCH 115/150] Remove the parameters for the Wuerstchen layer-norm. (#879) * Remove the parameters for the Wuerstchen layer-norm. * Fixes. * More fixes (including conv-transpose2d. * More fixes. * Again more fixes. --- candle-nn/src/conv.rs | 2 +- .../src/models/wuerstchen/common.rs | 39 +++++++++++-------- .../src/models/wuerstchen/diffnext.rs | 23 +++++------ .../src/models/wuerstchen/paella_vq.rs | 24 +++++------- .../src/models/wuerstchen/prior.rs | 2 +- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/candle-nn/src/conv.rs b/candle-nn/src/conv.rs index 309a5f37..31bf9af0 100644 --- a/candle-nn/src/conv.rs +++ b/candle-nn/src/conv.rs @@ -302,7 +302,7 @@ pub fn conv_transpose2d_no_bias( up: bound, }; let ws = vb.get_with_hints( - (out_channels, in_channels, kernel_size, kernel_size), + (in_channels, out_channels, kernel_size, kernel_size), "weight", init, )?; diff --git a/candle-transformers/src/models/wuerstchen/common.rs b/candle-transformers/src/models/wuerstchen/common.rs index 10e7b19f..ee318d27 100644 --- a/candle-transformers/src/models/wuerstchen/common.rs +++ b/candle-transformers/src/models/wuerstchen/common.rs @@ -1,28 +1,35 @@ -use candle::{Module, Result, Tensor, D}; +use candle::{DType, Module, Result, Tensor, D}; use candle_nn::VarBuilder; // https://github.com/huggingface/diffusers/blob/19edca82f1ff194c07317369a92b470dbae97f34/src/diffusers/pipelines/wuerstchen/modeling_wuerstchen_common.py#L22 #[derive(Debug)] pub struct WLayerNorm { - inner: candle_nn::LayerNorm, + eps: f64, } impl WLayerNorm { - pub fn new(size: usize, vb: VarBuilder) -> Result { - let cfg = candle_nn::layer_norm::LayerNormConfig { - eps: 1e-6, - remove_mean: true, - affine: false, - }; - let inner = candle_nn::layer_norm(size, cfg, vb)?; - Ok(Self { inner }) + pub fn new(_size: usize) -> Result { + Ok(Self { eps: 1e-6 }) } } impl Module for WLayerNorm { fn forward(&self, xs: &Tensor) -> Result { - xs.permute((0, 2, 3, 1))? - .apply(&self.inner)? + let xs = xs.permute((0, 2, 3, 1))?; + + let x_dtype = xs.dtype(); + let internal_dtype = match x_dtype { + DType::F16 | DType::BF16 => DType::F32, + d => d, + }; + + let hidden_size = xs.dim(D::Minus1)?; + let xs = xs.to_dtype(internal_dtype)?; + let mean_x = (xs.sum_keepdim(D::Minus1)? / hidden_size as f64)?; + let xs = xs.broadcast_sub(&mean_x)?; + let norm_x = (xs.sqr()?.sum_keepdim(D::Minus1)? / hidden_size as f64)?; + xs.broadcast_div(&(norm_x + self.eps)?.sqrt()?)? + .to_dtype(x_dtype)? .permute((0, 3, 1, 2)) } } @@ -57,8 +64,8 @@ pub struct GlobalResponseNorm { impl GlobalResponseNorm { pub fn new(dim: usize, vb: VarBuilder) -> Result { - let gamma = vb.get((1, 1, 1, 1, dim), "gamma")?; - let beta = vb.get((1, 1, 1, 1, dim), "beta")?; + let gamma = vb.get((1, 1, 1, dim), "gamma")?; + let beta = vb.get((1, 1, 1, dim), "beta")?; Ok(Self { gamma, beta }) } } @@ -92,7 +99,7 @@ impl ResBlock { ..Default::default() }; let depthwise = candle_nn::conv2d(c + c_skip, c, ksize, cfg, vb.pp("depthwise"))?; - let norm = WLayerNorm::new(c, vb.pp("norm"))?; + let norm = WLayerNorm::new(c)?; let channelwise_lin1 = candle_nn::linear(c, c * 4, vb.pp("channelwise.0"))?; let channelwise_grn = GlobalResponseNorm::new(c * 4, vb.pp("channelwise.2"))?; let channelwise_lin2 = candle_nn::linear(c * 4, c, vb.pp("channelwise.4"))?; @@ -141,7 +148,7 @@ impl AttnBlock { self_attn: bool, vb: VarBuilder, ) -> Result { - let norm = WLayerNorm::new(c, vb.pp("norm"))?; + let norm = WLayerNorm::new(c)?; let attention = Attention::new(vb.pp("attention"), c, None, nhead, c / nhead, None, false)?; let kv_mapper_lin = candle_nn::linear(c_cond, c, vb.pp("kv_mapper.1"))?; Ok(Self { diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 001b35d7..70e4ba34 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -19,7 +19,7 @@ impl ResBlockStageB { ..Default::default() }; let depthwise = candle_nn::conv2d(c, c, ksize, cfg, vb.pp("depthwise"))?; - let norm = WLayerNorm::new(c, vb.pp("norm"))?; + let norm = WLayerNorm::new(c)?; let channelwise_lin1 = candle_nn::linear(c + c_skip, c * 4, vb.pp("channelwise.0"))?; let channelwise_grn = GlobalResponseNorm::new(4 * c, vb.pp("channelwise.2"))?; let channelwise_lin2 = candle_nn::linear(c * 4, c, vb.pp("channelwise.4"))?; @@ -75,7 +75,7 @@ struct UpBlock { pub struct WDiffNeXt { clip_mapper: candle_nn::Linear, effnet_mappers: Vec>, - seq_norm: candle_nn::LayerNorm, + seq_norm: WLayerNorm, embedding_conv: candle_nn::Conv2d, embedding_ln: WLayerNorm, down_blocks: Vec, @@ -98,7 +98,7 @@ impl WDiffNeXt { ) -> Result { const C_HIDDEN: [usize; 4] = [320, 640, 1280, 1280]; const BLOCKS: [usize; 4] = [4, 4, 14, 4]; - const NHEAD: [usize; 4] = [0, 10, 20, 20]; + const NHEAD: [usize; 4] = [1, 10, 20, 20]; const INJECT_EFFNET: [bool; 4] = [false, true, true, true]; const EFFNET_EMBD: usize = 16; @@ -133,24 +133,21 @@ impl WDiffNeXt { }; effnet_mappers.push(c) } - let cfg = candle_nn::layer_norm::LayerNormConfig { - ..Default::default() - }; - let seq_norm = candle_nn::layer_norm(c_cond, cfg, vb.pp("seq_norm"))?; - let embedding_ln = WLayerNorm::new(C_HIDDEN[0], vb.pp("embedding.1"))?; + let seq_norm = WLayerNorm::new(c_cond)?; + let embedding_ln = WLayerNorm::new(C_HIDDEN[0])?; let embedding_conv = candle_nn::conv2d( c_in * patch_size * patch_size, - C_HIDDEN[1], + C_HIDDEN[0], 1, Default::default(), - vb.pp("embedding.2"), + vb.pp("embedding.1"), )?; let mut down_blocks = Vec::with_capacity(C_HIDDEN.len()); for (i, &c_hidden) in C_HIDDEN.iter().enumerate() { let vb = vb.pp("down_blocks").pp(i); let (layer_norm, conv, start_layer_i) = if i > 0 { - let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1], vb.pp(0))?; + let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1])?; let cfg = candle_nn::Conv2dConfig { stride: 2, ..Default::default() @@ -223,7 +220,7 @@ impl WDiffNeXt { sub_blocks.push(sub_block) } let (layer_norm, conv) = if i > 0 { - let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1], vb.pp(layer_i))?; + let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1])?; layer_i += 1; let cfg = candle_nn::Conv2dConfig { stride: 2, @@ -242,7 +239,7 @@ impl WDiffNeXt { up_blocks.push(up_block) } - let clf_ln = WLayerNorm::new(C_HIDDEN[0], vb.pp("clf.0"))?; + let clf_ln = WLayerNorm::new(C_HIDDEN[0])?; let clf_conv = candle_nn::conv2d( C_HIDDEN[0], 2 * c_out * patch_size * patch_size, diff --git a/candle-transformers/src/models/wuerstchen/paella_vq.rs b/candle-transformers/src/models/wuerstchen/paella_vq.rs index a60f8e8a..faf2d2b4 100644 --- a/candle-transformers/src/models/wuerstchen/paella_vq.rs +++ b/candle-transformers/src/models/wuerstchen/paella_vq.rs @@ -1,11 +1,12 @@ +use super::common::WLayerNorm; use candle::{Module, Result, Tensor}; use candle_nn::VarBuilder; #[derive(Debug)] pub struct MixingResidualBlock { - norm1: candle_nn::LayerNorm, + norm1: WLayerNorm, depthwise_conv: candle_nn::Conv2d, - norm2: candle_nn::LayerNorm, + norm2: WLayerNorm, channelwise_lin1: candle_nn::Linear, channelwise_lin2: candle_nn::Linear, gammas: Vec, @@ -13,13 +14,8 @@ pub struct MixingResidualBlock { impl MixingResidualBlock { pub fn new(inp: usize, embed_dim: usize, vb: VarBuilder) -> Result { - let cfg = candle_nn::LayerNormConfig { - affine: false, - eps: 1e-6, - remove_mean: true, - }; - let norm1 = candle_nn::layer_norm(inp, cfg, vb.pp("norm1"))?; - let norm2 = candle_nn::layer_norm(inp, cfg, vb.pp("norm1"))?; + let norm1 = WLayerNorm::new(inp)?; + let norm2 = WLayerNorm::new(inp)?; let cfg = candle_nn::Conv2dConfig { groups: inp, ..Default::default() @@ -120,15 +116,15 @@ impl PaellaVQ { d_idx += 1; down_blocks.push((conv_block, res_block)) } + let vb_d = vb_d.pp(d_idx); let down_blocks_conv = candle_nn::conv2d_no_bias( C_LEVELS[1], LATENT_CHANNELS, 1, Default::default(), - vb_d.pp(d_idx), + vb_d.pp(0), )?; - d_idx += 1; - let down_blocks_bn = candle_nn::batch_norm(LATENT_CHANNELS, 1e-5, vb_d.pp(d_idx))?; + let down_blocks_bn = candle_nn::batch_norm(LATENT_CHANNELS, 1e-5, vb_d.pp(1))?; let mut up_blocks = Vec::new(); let vb_u = vb.pp("up_blocks"); @@ -138,7 +134,7 @@ impl PaellaVQ { C_LEVELS[1], 1, Default::default(), - vb_u.pp(u_idx), + vb_u.pp(u_idx).pp(0), )?; u_idx += 1; for (i, &c_level) in C_LEVELS.iter().rev().enumerate() { @@ -157,7 +153,7 @@ impl PaellaVQ { }; let block = candle_nn::conv_transpose2d_no_bias( c_level, - C_LEVELS[i - 1], + C_LEVELS[C_LEVELS.len() - i - 2], 4, cfg, vb_u.pp(u_idx), diff --git a/candle-transformers/src/models/wuerstchen/prior.rs b/candle-transformers/src/models/wuerstchen/prior.rs index a9e3e793..93385a32 100644 --- a/candle-transformers/src/models/wuerstchen/prior.rs +++ b/candle-transformers/src/models/wuerstchen/prior.rs @@ -33,7 +33,7 @@ impl WPrior { let projection = candle_nn::conv2d(c_in, c, 1, Default::default(), vb.pp("projection"))?; let cond_mapper_lin1 = candle_nn::linear(c_cond, c, vb.pp("cond_mapper.0"))?; let cond_mapper_lin2 = candle_nn::linear(c, c, vb.pp("cond_mapper.2"))?; - let out_ln = super::common::WLayerNorm::new(c, vb.pp("out.0"))?; + let out_ln = super::common::WLayerNorm::new(c)?; let out_conv = candle_nn::conv2d(c, c_in * 2, 1, Default::default(), vb.pp("out.1"))?; let mut blocks = Vec::with_capacity(depth); for index in 0..depth { From c2b866172abaf1d4b8d75273c4f4e28a16d872f0 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 17 Sep 2023 22:08:11 +0100 Subject: [PATCH 116/150] More Wuerstchen fixes. (#882) * More Weurstchen fixes. * More shape fixes. * Add more of the prior specific bits. * Broadcast add. * Fix the clip config. * Add some masking options to the clip model. --- candle-examples/examples/wuerstchen/main.rs | 57 ++++++++++++------- .../src/models/stable_diffusion/clip.rs | 49 +++++++++++++--- .../src/models/wuerstchen/common.rs | 6 +- .../src/models/wuerstchen/diffnext.rs | 25 ++++---- 4 files changed, 96 insertions(+), 41 deletions(-) diff --git a/candle-examples/examples/wuerstchen/main.rs b/candle-examples/examples/wuerstchen/main.rs index 7e4d2360..b3231360 100644 --- a/candle-examples/examples/wuerstchen/main.rs +++ b/candle-examples/examples/wuerstchen/main.rs @@ -16,6 +16,7 @@ use tokenizers::Tokenizer; const GUIDANCE_SCALE: f64 = 7.5; const RESOLUTION_MULTIPLE: f64 = 42.67; +const PRIOR_CIN: usize = 16; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -54,6 +55,10 @@ struct Args { #[arg(long, value_name = "FILE")] clip_weights: Option, + /// The CLIP weight file used by the prior model, in .safetensors format. + #[arg(long, value_name = "FILE")] + prior_clip_weights: Option, + /// The prior weight file, in .safetensors format. #[arg(long, value_name = "FILE")] prior_weights: Option, @@ -66,6 +71,10 @@ struct Args { /// The file specifying the tokenizer to used for tokenization. tokenizer: Option, + #[arg(long, value_name = "FILE")] + /// The file specifying the tokenizer to used for prior tokenization. + prior_tokenizer: Option, + /// The size of the sliced attention or 0 for automatic slicing (disabled by default) #[arg(long)] sliced_attention_size: Option, @@ -86,7 +95,9 @@ struct Args { #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ModelFile { Tokenizer, + PriorTokenizer, Clip, + PriorClip, Decoder, VqGan, Prior, @@ -102,7 +113,9 @@ impl ModelFile { let repo_prior = "warp-ai/wuerstchen-prior"; let (repo, path) = match self { Self::Tokenizer => (repo_main, "tokenizer/tokenizer.json"), + Self::PriorTokenizer => (repo_prior, "tokenizer/tokenizer.json"), Self::Clip => (repo_main, "text_encoder/model.safetensors"), + Self::PriorClip => (repo_prior, "text_encoder/model.safetensors"), Self::Decoder => (repo_main, "decoder/diffusion_pytorch_model.safetensors"), Self::VqGan => (repo_main, "vqgan/diffusion_pytorch_model.safetensors"), Self::Prior => (repo_prior, "prior/diffusion_pytorch_model.safetensors"), @@ -144,12 +157,11 @@ fn output_filename( fn encode_prompt( prompt: &str, uncond_prompt: &str, - tokenizer: Option, - clip_weights: Option, + tokenizer: std::path::PathBuf, + clip_weights: std::path::PathBuf, clip_config: stable_diffusion::clip::Config, device: &Device, ) -> Result { - let tokenizer = ModelFile::Tokenizer.get(tokenizer)?; let tokenizer = Tokenizer::from_file(tokenizer).map_err(E::msg)?; let pad_id = match &clip_config.pad_with { Some(padding) => *tokenizer.get_vocab(true).get(padding.as_str()).unwrap(), @@ -161,6 +173,7 @@ fn encode_prompt( .map_err(E::msg)? .get_ids() .to_vec(); + let tokens_len = tokens.len(); while tokens.len() < clip_config.max_position_embeddings { tokens.push(pad_id) } @@ -171,17 +184,17 @@ fn encode_prompt( .map_err(E::msg)? .get_ids() .to_vec(); + let uncond_tokens_len = uncond_tokens.len(); while uncond_tokens.len() < clip_config.max_position_embeddings { uncond_tokens.push(pad_id) } let uncond_tokens = Tensor::new(uncond_tokens.as_slice(), device)?.unsqueeze(0)?; - println!("Building the Clip transformer."); - let clip_weights = ModelFile::Clip.get(clip_weights)?; + println!("Building the clip transformer."); let text_model = stable_diffusion::build_clip_transformer(&clip_config, clip_weights, device, DType::F32)?; - let text_embeddings = text_model.forward(&tokens)?; - let uncond_embeddings = text_model.forward(&uncond_tokens)?; + let text_embeddings = text_model.forward_with_mask(&tokens, tokens_len)?; + let uncond_embeddings = text_model.forward_with_mask(&uncond_tokens, uncond_tokens_len)?; let text_embeddings = Tensor::cat(&[uncond_embeddings, text_embeddings], 0)?; Ok(text_embeddings) } @@ -221,15 +234,19 @@ fn run(args: Args) -> Result<()> { let height = height.unwrap_or(1024); let width = width.unwrap_or(1024); - let text_embeddings = encode_prompt( - &prompt, - &uncond_prompt, - tokenizer.clone(), - clip_weights.clone(), - stable_diffusion::clip::Config::wuerstchen(), - &device, - )?; - println!("{text_embeddings:?}"); + let prior_text_embeddings = { + let tokenizer = ModelFile::PriorTokenizer.get(args.prior_tokenizer)?; + let weights = ModelFile::PriorClip.get(args.prior_clip_weights)?; + encode_prompt( + &prompt, + &uncond_prompt, + tokenizer.clone(), + weights, + stable_diffusion::clip::Config::wuerstchen_prior(), + &device, + )? + }; + println!("{prior_text_embeddings}"); println!("Building the prior."); // https://huggingface.co/warp-ai/wuerstchen-prior/blob/main/prior/config.json @@ -239,8 +256,8 @@ fn run(args: Args) -> Result<()> { let weights = weights.deserialize()?; let vb = candle_nn::VarBuilder::from_safetensors(vec![weights], DType::F32, &device); wuerstchen::prior::WPrior::new( - /* c_in */ 16, /* c */ 1536, /* c_cond */ 1280, /* c_r */ 64, - /* depth */ 32, /* nhead */ 24, vb, + /* c_in */ PRIOR_CIN, /* c */ 1536, /* c_cond */ 1280, + /* c_r */ 64, /* depth */ 32, /* nhead */ 24, vb, )? }; @@ -274,12 +291,12 @@ fn run(args: Args) -> Result<()> { let latents = Tensor::randn( 0f32, 1f32, - (b_size, 4, latent_height, latent_width), + (b_size, PRIOR_CIN, latent_height, latent_width), &device, )?; // TODO: latents denoising loop, use the scheduler values. let ratio = Tensor::ones(1, DType::F32, &device)?; - let prior = prior.forward(&latents, &ratio, &text_embeddings)?; + let prior = prior.forward(&latents, &ratio, &prior_text_embeddings)?; let latents = ((latents * 42.)? - 1.)?; /* diff --git a/candle-transformers/src/models/stable_diffusion/clip.rs b/candle-transformers/src/models/stable_diffusion/clip.rs index 31d025b3..7f86cf31 100644 --- a/candle-transformers/src/models/stable_diffusion/clip.rs +++ b/candle-transformers/src/models/stable_diffusion/clip.rs @@ -107,13 +107,28 @@ impl Config { embed_dim: 1024, intermediate_size: 4096, max_position_embeddings: 77, - pad_with: Some("!".to_string()), + pad_with: None, num_hidden_layers: 24, num_attention_heads: 16, projection_dim: 1024, activation: Activation::Gelu, } } + + // https://huggingface.co/warp-ai/wuerstchen-prior/blob/main/text_encoder/config.json + pub fn wuerstchen_prior() -> Self { + Self { + vocab_size: 49408, + embed_dim: 1280, + intermediate_size: 5120, + max_position_embeddings: 77, + pad_with: None, + num_hidden_layers: 32, + num_attention_heads: 20, + projection_dim: 512, + activation: Activation::Gelu, + } + } } // CLIP Text Model @@ -334,21 +349,39 @@ impl ClipTextTransformer { } // https://github.com/huggingface/transformers/blob/674f750a57431222fa2832503a108df3badf1564/src/transformers/models/clip/modeling_clip.py#L678 - fn build_causal_attention_mask(bsz: usize, seq_len: usize, device: &Device) -> Result { + fn build_causal_attention_mask( + bsz: usize, + seq_len: usize, + mask_after: usize, + device: &Device, + ) -> Result { let mask: Vec<_> = (0..seq_len) - .flat_map(|i| (0..seq_len).map(move |j| if j > i { f32::MIN } else { 0. })) + .flat_map(|i| { + (0..seq_len).map(move |j| { + if j > i || j > mask_after { + f32::MIN + } else { + 0. + } + }) + }) .collect(); let mask = Tensor::from_slice(&mask, (seq_len, seq_len), device)?; mask.broadcast_as((bsz, seq_len, seq_len)) } + + pub fn forward_with_mask(&self, xs: &Tensor, mask_after: usize) -> Result { + let (bsz, seq_len) = xs.dims2()?; + let xs = self.embeddings.forward(xs)?; + let causal_attention_mask = + Self::build_causal_attention_mask(bsz, seq_len, mask_after, xs.device())?; + let xs = self.encoder.forward(&xs, &causal_attention_mask)?; + self.final_layer_norm.forward(&xs) + } } impl Module for ClipTextTransformer { fn forward(&self, xs: &Tensor) -> Result { - let (bsz, seq_len) = xs.dims2()?; - let xs = self.embeddings.forward(xs)?; - let causal_attention_mask = Self::build_causal_attention_mask(bsz, seq_len, xs.device())?; - let xs = self.encoder.forward(&xs, &causal_attention_mask)?; - self.final_layer_norm.forward(&xs) + self.forward_with_mask(xs, usize::MAX) } } diff --git a/candle-transformers/src/models/wuerstchen/common.rs b/candle-transformers/src/models/wuerstchen/common.rs index ee318d27..5337fdc6 100644 --- a/candle-transformers/src/models/wuerstchen/common.rs +++ b/candle-transformers/src/models/wuerstchen/common.rs @@ -75,9 +75,9 @@ impl Module for GlobalResponseNorm { let agg_norm = xs.sqr()?.sum_keepdim((1, 2))?; let stand_div_norm = agg_norm.broadcast_div(&(agg_norm.mean_keepdim(D::Minus1)? + 1e-6)?)?; - (xs.broadcast_mul(&stand_div_norm)? - .broadcast_mul(&self.gamma) - + &self.beta)? + xs.broadcast_mul(&stand_div_norm)? + .broadcast_mul(&self.gamma)? + .broadcast_add(&self.beta)? + xs } } diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 70e4ba34..664251ed 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -68,7 +68,7 @@ struct DownBlock { struct UpBlock { sub_blocks: Vec, layer_norm: Option, - conv: Option, + conv: Option, } #[derive(Debug)] @@ -152,20 +152,20 @@ impl WDiffNeXt { stride: 2, ..Default::default() }; - let conv = candle_nn::conv2d(C_HIDDEN[i - 1], c_hidden, 2, cfg, vb.pp(1))?; - (Some(layer_norm), Some(conv), 2) + let conv = candle_nn::conv2d(C_HIDDEN[i - 1], c_hidden, 2, cfg, vb.pp("0.1"))?; + (Some(layer_norm), Some(conv), 1) } else { (None, None, 0) }; let mut sub_blocks = Vec::with_capacity(BLOCKS[i]); let mut layer_i = start_layer_i; - for j in 0..BLOCKS[i] { + for _j in 0..BLOCKS[i] { let c_skip = if INJECT_EFFNET[i] { c_cond } else { 0 }; let res_block = ResBlockStageB::new(c_hidden, c_skip, 3, vb.pp(layer_i))?; layer_i += 1; let ts_block = TimestepBlock::new(c_hidden, c_r, vb.pp(layer_i))?; layer_i += 1; - let attn_block = if j == 0 { + let attn_block = if i == 0 { None } else { let attn_block = @@ -190,7 +190,7 @@ impl WDiffNeXt { let mut up_blocks = Vec::with_capacity(C_HIDDEN.len()); for (i, &c_hidden) in C_HIDDEN.iter().enumerate().rev() { - let vb = vb.pp("up_blocks").pp(i); + let vb = vb.pp("up_blocks").pp(C_HIDDEN.len() - 1 - i); let mut sub_blocks = Vec::with_capacity(BLOCKS[i]); let mut layer_i = 0; for j in 0..BLOCKS[i] { @@ -204,7 +204,7 @@ impl WDiffNeXt { layer_i += 1; let ts_block = TimestepBlock::new(c_hidden, c_r, vb.pp(layer_i))?; layer_i += 1; - let attn_block = if j == 0 { + let attn_block = if i == 0 { None } else { let attn_block = @@ -221,12 +221,17 @@ impl WDiffNeXt { } let (layer_norm, conv) = if i > 0 { let layer_norm = WLayerNorm::new(C_HIDDEN[i - 1])?; - layer_i += 1; - let cfg = candle_nn::Conv2dConfig { + let cfg = candle_nn::ConvTranspose2dConfig { stride: 2, ..Default::default() }; - let conv = candle_nn::conv2d(C_HIDDEN[i - 1], c_hidden, 2, cfg, vb.pp(layer_i))?; + let conv = candle_nn::conv_transpose2d( + c_hidden, + C_HIDDEN[i - 1], + 2, + cfg, + vb.pp(layer_i).pp(1), + )?; (Some(layer_norm), Some(conv)) } else { (None, None) From 03e194123d743ca73b10797a315b8d47734735e8 Mon Sep 17 00:00:00 2001 From: Lukas Kreussel <65088241+LLukas22@users.noreply.github.com> Date: Sun, 17 Sep 2023 23:11:01 +0200 Subject: [PATCH 117/150] Add return types to `*.pyi` stubs (#880) * Start generating return types * Finish tensor type hinting * Add `save_gguf` to `utils` * Typehint `quant-llama.py` --- candle-pyo3/py_src/candle/__init__.py | 6 +- candle-pyo3/py_src/candle/__init__.pyi | 435 ++++++++++++------- candle-pyo3/py_src/candle/nn/__init__.pyi | 8 +- candle-pyo3/py_src/candle/utils/__init__.py | 1 + candle-pyo3/py_src/candle/utils/__init__.pyi | 25 +- candle-pyo3/quant-llama.py | 31 +- candle-pyo3/src/lib.rs | 270 +++++++++++- candle-pyo3/stub.py | 23 +- candle-pyo3/test.py | 9 +- 9 files changed, 611 insertions(+), 197 deletions(-) diff --git a/candle-pyo3/py_src/candle/__init__.py b/candle-pyo3/py_src/candle/__init__.py index 49c96122..951609cc 100644 --- a/candle-pyo3/py_src/candle/__init__.py +++ b/candle-pyo3/py_src/candle/__init__.py @@ -1 +1,5 @@ -from .candle import * \ No newline at end of file +from .candle import * + +__doc__ = candle.__doc__ +if hasattr(candle, "__all__"): + __all__ = candle.__all__ \ No newline at end of file diff --git a/candle-pyo3/py_src/candle/__init__.pyi b/candle-pyo3/py_src/candle/__init__.pyi index c21e6738..414f0bc4 100644 --- a/candle-pyo3/py_src/candle/__init__.pyi +++ b/candle-pyo3/py_src/candle/__init__.pyi @@ -7,7 +7,7 @@ class bf16(DType): pass @staticmethod -def cat(tensors: List[Tensor], dim: int): +def cat(tensors: List[Tensor], dim: int) -> Tensor: """ Concatenate the tensors across one axis. """ @@ -26,31 +26,35 @@ class i64(DType): pass @staticmethod -def ones(shape: Sequence[int], dtype: Optional[DType] = None, device: Optional[Device] = None): - """ """ +def ones(shape: Sequence[int], dtype: Optional[DType] = None, device: Optional[Device] = None) -> Tensor: + """ + Creates a new tensor filled with ones. + """ pass @staticmethod -def rand(shape: Sequence[int], device: Optional[Device] = None): +def rand(shape: Sequence[int], device: Optional[Device] = None) -> Tensor: """ Creates a new tensor with random values. """ pass @staticmethod -def randn(shape: Sequence[int], device: Optional[Device] = None): - """ """ +def randn(shape: Sequence[int], device: Optional[Device] = None) -> Tensor: + """ + Creates a new tensor with random values from a normal distribution. + """ pass @staticmethod -def stack(tensors: List[Tensor], dim: int): +def stack(tensors: List[Tensor], dim: int) -> Tensor: """ Stack the tensors along a new axis. """ pass @staticmethod -def tensor(data: _ArrayLike): +def tensor(data: _ArrayLike) -> Tensor: """ Creates a new tensor from a Python value. The value can be a scalar or array-like object. """ @@ -63,186 +67,309 @@ class u8(DType): pass @staticmethod -def zeros(shape: Sequence[int], dtype: Optional[DType] = None, device: Optional[Device] = None): - """ """ +def zeros(shape: Sequence[int], dtype: Optional[DType] = None, device: Optional[Device] = None) -> Tensor: + """ + Creates a new tensor filled with zeros. + """ pass class DType: - pass + """ + A `candle` dtype. + """ class QTensor: - def dequantize(self): - """ """ + """ + A quantized tensor. + """ + + def dequantize(self) -> Tensor: + """ + Dequantizes the tensor. + """ pass @property - def ggml_dtype(self): - """ """ + def ggml_dtype(self) -> str: + """ + Gets the tensors quantized dtype. + """ pass - def matmul_t(self, lhs): - """ """ + def matmul_t(self, lhs: Tensor) -> Tensor: + """ + Performs a quantized matrix multiplication, with the quantized tensor as the right hand side. + """ pass @property - def rank(self): - """ """ + def rank(self) -> int: + """ + Gets the rank of the tensor. + """ pass @property - def shape(self): - """ """ + def shape(self) -> Tuple[int]: + """ + Gets the shape of the tensor. + """ pass class Tensor: - def __init__(data: _ArrayLike): + """ + A `candle` tensor. + """ + + def __init__(self, data: _ArrayLike): pass - def argmax_keepdim(self, dim): - """ """ - pass - def argmin_keepdim(self, dim): - """ """ - pass - def broadcast_add(self, rhs): - """ """ - pass - def broadcast_as(self, shape): - """ """ - pass - def broadcast_div(self, rhs): - """ """ - pass - def broadcast_left(self, shape): - """ """ - pass - def broadcast_mul(self, rhs): - """ """ - pass - def broadcast_sub(self, rhs): - """ """ - pass - def contiguous(self): - """ """ - pass - def copy(self): - """ """ - pass - def cos(self): - """ """ - pass - def detach(self): - """ """ - pass - @property - def device(self): - """ """ - pass - @property - def dtype(self): - """ """ - pass - def exp(self): - """ """ - pass - def flatten_all(self): - """ """ - pass - def flatten_from(self, dim): - """ """ - pass - def flatten_to(self, dim): - """ """ - pass - def get(self, index): - """ """ - pass - def index_select(self, rhs, dim): - """ """ - pass - def is_contiguous(self): - """ """ - pass - def is_fortran_contiguous(self): - """ """ - pass - def log(self): - """ """ - pass - def matmul(self, rhs): - """ """ - pass - def max_keepdim(self, dim): - """ """ - pass - def mean_all(self): - """ """ - pass - def min_keepdim(self, dim): - """ """ - pass - def narrow(self, dim, start, len): - """ """ - pass - def powf(self, p): - """ """ - pass - def quantize(self, quantized_dtype): - """ """ - pass - @property - def rank(self): - """ """ - pass - def recip(self): - """ """ - pass - def reshape(self, shape): - """ """ - pass - @property - def shape(self): + def argmax_keepdim(self, dim: int) -> Tensor: """ - Gets the tensor shape as a Python tuple. + Returns the indices of the maximum value(s) across the selected dimension. """ pass - def sin(self): - """ """ + def argmin_keepdim(self, dim: int) -> Tensor: + """ + Returns the indices of the minimum value(s) across the selected dimension. + """ pass - def sqr(self): - """ """ + def broadcast_add(self, rhs: Tensor) -> Tensor: + """ + Adds the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + """ pass - def sqrt(self): - """ """ + def broadcast_as(self, shape: Sequence[int]) -> Tensor: + """ + Broadcasts the tensor to the given shape. + """ pass - def squeeze(self, dim): - """ """ + def broadcast_div(self, rhs: Tensor) -> Tensor: + """ + Divides the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + """ + pass + def broadcast_left(self, shape: Sequence[int]) -> Tensor: + """ + Broadcasts the tensor to the given shape, adding new dimensions on the left. + """ + pass + def broadcast_mul(self, rhs: Tensor) -> Tensor: + """ + Multiplies the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + """ + pass + def broadcast_sub(self, rhs: Tensor) -> Tensor: + """ + Subtracts the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + """ + pass + def contiguous(self) -> Tensor: + """ + Makes the tensor contiguous in memory. + """ + pass + def copy(self) -> Tensor: + """ + Returns a copy of the tensor. + """ + pass + def cos(self) -> Tensor: + """ + Performs the `cos` operation on the tensor. + """ + pass + def detach(self) -> Tensor: + """ + Detach the tensor from the computation graph. + """ pass @property - def stride(self): - """ """ + def device(self) -> Device: + """ + Gets the tensor's device. + """ pass - def sum_all(self): - """ """ + @property + def dtype(self) -> DType: + """ + Gets the tensor's dtype. + """ pass - def sum_keepdim(self, dims): - """ """ + def exp(self) -> Tensor: + """ + Performs the `exp` operation on the tensor. + """ pass - def t(self): - """ """ + def flatten_all(self) -> Tensor: + """ + Flattens the tensor into a 1D tensor. + """ pass - def to_device(self, device): - """ """ + def flatten_from(self, dim: int) -> Tensor: + """ + Flattens the tensor on the dimension indexes from `dim` (inclusive) to the last dimension. + """ pass - def to_dtype(self, dtype): - """ """ + def flatten_to(self, dim: int) -> Tensor: + """ + Flattens the tensor on the dimension indexes from `0` to `dim` (inclusive). + """ pass - def transpose(self, dim1, dim2): - """ """ + def get(self, index: int) -> Tensor: + """ + Gets the value at the specified index. + """ pass - def unsqueeze(self, dim): - """ """ + def index_select(self, rhs: Tensor, dim: int) -> Tensor: + """ + Select values for the input tensor at the target indexes across the specified dimension. + + The `indexes` is argument is an int tensor with a single dimension. + The output has the same number of dimension as the `self` input. The target dimension of + the output has length the length of `indexes` and the values are taken from `self` using + the index from `indexes`. Other dimensions have the same number of elements as the input + tensor. + """ pass - def values(self): + def is_contiguous(self) -> bool: + """ + Returns true if the tensor is contiguous in C order. + """ + pass + def is_fortran_contiguous(self) -> bool: + """ + Returns true if the tensor is contiguous in Fortran order. + """ + pass + def log(self) -> Tensor: + """ + Performs the `log` operation on the tensor. + """ + pass + def matmul(self, rhs: Tensor) -> Tensor: + """ + Performs a matrix multiplication between the two tensors. + """ + pass + def max_keepdim(self, dim: int) -> Tensor: + """ + Gathers the maximum value across the selected dimension. + """ + pass + def mean_all(self) -> Tensor: + """ + Returns the mean of the tensor. + """ + pass + def min_keepdim(self, dim: int) -> Tensor: + """ + Gathers the minimum value across the selected dimension. + """ + pass + def narrow(self, dim: int, start: int, len: int) -> Tensor: + """ + Returns a new tensor that is a narrowed version of the input, the dimension `dim` + ranges from `start` to `start + len`. + """ + pass + def powf(self, p: float) -> Tensor: + """ + Performs the `pow` operation on the tensor with the given exponent. + """ + pass + def quantize(self, quantized_dtype: str) -> QTensor: + """ + Quantize the tensor. + """ + pass + @property + def rank(self) -> int: + """ + Gets the tensor's rank. + """ + pass + def recip(self) -> Tensor: + """ + Get the `recip` of the tensor. + """ + pass + def reshape(self, shape: Sequence[int]) -> Tensor: + """ + Reshapes the tensor to the given shape. + """ + pass + @property + def shape(self) -> Tuple[int]: + """ + Gets the tensor's shape. + """ + pass + def sin(self) -> Tensor: + """ + Performs the `sin` operation on the tensor. + """ + pass + def sqr(self) -> Tensor: + """ + Squares the tensor. + """ + pass + def sqrt(self) -> Tensor: + """ + Calculates the square root of the tensor. + """ + pass + def squeeze(self, dim: int) -> Tensor: + """ + Creates a new tensor with the specified dimension removed if its size was one. + """ + pass + @property + def stride(self) -> Tuple[int]: + """ + Gets the tensor's strides. + """ + pass + def sum_all(self) -> Tensor: + """ + Returns the sum of the tensor. + """ + pass + def sum_keepdim(self, dim: Union[int, List[int]]) -> Tensor: + """ + Returns the sum of all elements in the input tensor. The sum is performed over all the input dimensions. + """ + pass + def t(self) -> Tensor: + """ + Transposes the tensor. + """ + pass + def to_device(self, device: Union[str, Device]) -> Tensor: + """ + Move the tensor to a new device. + """ + pass + def to_dtype(self, dtype: Union[str, DType]) -> Tensor: + """ + Convert the tensor to a new dtype. + """ + pass + def transpose(self, dim1: int, dim2: int) -> Tensor: + """ + Returns a tensor that is a transposed version of the input, the given dimensions are swapped. + """ + pass + def unsqueeze(self, dim: int) -> Tensor: + """ + Creates a new tensor with a dimension of size one inserted at the specified position. + """ + pass + def values(self) -> _ArrayLike: """ Gets the tensor's data as a Python scalar or array-like object. """ pass - def where_cond(self, on_true, on_false): - """ """ + def where_cond(self, on_true: Tensor, on_false: Tensor) -> Tensor: + """ + Returns a tensor with the same shape as the input tensor, the values are taken from + `on_true` if the input tensor value is not zero, and `on_false` at the positions where the + input tensor is equal to zero. + """ pass diff --git a/candle-pyo3/py_src/candle/nn/__init__.pyi b/candle-pyo3/py_src/candle/nn/__init__.pyi index 821cd052..01b30fce 100644 --- a/candle-pyo3/py_src/candle/nn/__init__.pyi +++ b/candle-pyo3/py_src/candle/nn/__init__.pyi @@ -2,18 +2,18 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Sequence from os import PathLike from candle.typing import _ArrayLike, Device -from candle import Tensor, DType +from candle import Tensor, DType, QTensor @staticmethod -def silu(tensor: Tensor): +def silu(tensor: Tensor) -> Tensor: """ Applies the Sigmoid Linear Unit (SiLU) function to a given tensor. """ pass @staticmethod -def softmax(tensor: Tensor, dim: int): +def softmax(tensor: Tensor, dim: int) -> Tensor: """ - Applies the Softmax function to a given tensor. + Applies the Softmax function to a given tensor.# """ pass diff --git a/candle-pyo3/py_src/candle/utils/__init__.py b/candle-pyo3/py_src/candle/utils/__init__.py index 2ead6d84..62d85dc9 100644 --- a/candle-pyo3/py_src/candle/utils/__init__.py +++ b/candle-pyo3/py_src/candle/utils/__init__.py @@ -8,4 +8,5 @@ has_mkl = utils.has_mkl load_ggml = utils.load_ggml load_gguf = utils.load_gguf load_safetensors = utils.load_safetensors +save_gguf = utils.save_gguf save_safetensors = utils.save_safetensors diff --git a/candle-pyo3/py_src/candle/utils/__init__.pyi b/candle-pyo3/py_src/candle/utils/__init__.pyi index 7a0a5231..61964ffc 100644 --- a/candle-pyo3/py_src/candle/utils/__init__.pyi +++ b/candle-pyo3/py_src/candle/utils/__init__.pyi @@ -2,38 +2,38 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Sequence from os import PathLike from candle.typing import _ArrayLike, Device -from candle import Tensor, DType +from candle import Tensor, DType, QTensor @staticmethod -def cuda_is_available(): +def cuda_is_available() -> bool: """ Returns true if the 'cuda' backend is available. """ pass @staticmethod -def get_num_threads(): +def get_num_threads() -> int: """ Returns the number of threads used by the candle. """ pass @staticmethod -def has_accelerate(): +def has_accelerate() -> bool: """ Returns true if candle was compiled with 'accelerate' support. """ pass @staticmethod -def has_mkl(): +def has_mkl() -> bool: """ Returns true if candle was compiled with MKL support. """ pass @staticmethod -def load_ggml(path: Union[str, PathLike]): +def load_ggml(path: Union[str, PathLike]) -> Tuple[Dict[str, QTensor], Dict[str, Any], List[str]]: """ Load a GGML file. Returns a tuple of three objects: a dictionary mapping tensor names to tensors, a dictionary mapping hyperparameter names to hyperparameter values, and a vocabulary. @@ -41,7 +41,7 @@ def load_ggml(path: Union[str, PathLike]): pass @staticmethod -def load_gguf(path: Union[str, PathLike]): +def load_gguf(path: Union[str, PathLike]) -> Tuple[Dict[str, QTensor], Dict[str, Any]]: """ Loads a GGUF file. Returns a tuple of two dictionaries: the first maps tensor names to tensors, and the second maps metadata keys to metadata values. @@ -49,14 +49,21 @@ def load_gguf(path: Union[str, PathLike]): pass @staticmethod -def load_safetensors(path: Union[str, PathLike]): +def load_safetensors(path: Union[str, PathLike]) -> Dict[str, Tensor]: """ Loads a safetensors file. Returns a dictionary mapping tensor names to tensors. """ pass @staticmethod -def save_safetensors(path: Union[str, PathLike], tensors: Dict[str, Tensor]): +def save_gguf(path: Union[str, PathLike], tensors: Dict[str, QTensor], metadata: Dict[str, Any]): + """ + Save quanitzed tensors and metadata to a GGUF file. + """ + pass + +@staticmethod +def save_safetensors(path: Union[str, PathLike], tensors: Dict[str, Tensor]) -> None: """ Saves a dictionary of tensors to a safetensors file. """ diff --git a/candle-pyo3/quant-llama.py b/candle-pyo3/quant-llama.py index 020d525d..46d9ff62 100644 --- a/candle-pyo3/quant-llama.py +++ b/candle-pyo3/quant-llama.py @@ -1,27 +1,28 @@ # This example shows how the candle Python api can be used to replicate llama.cpp. import sys +from typing import Dict, Tuple, Any import candle -from candle.utils import load_ggml,load_gguf +from candle import Tensor, QTensor, utils, nn MAX_SEQ_LEN = 4096 -def masked_fill(on_false, mask, on_true): +def masked_fill(on_false:Tensor, mask:Tensor, on_true:Tensor): shape = mask.shape on_true = candle.tensor(on_true).broadcast_as(shape) return mask.where_cond(on_true, on_false) class RmsNorm: - def __init__(self, qtensor): + def __init__(self, qtensor:QTensor): self.weight = qtensor.dequantize() - def __call__(self, x): + def __call__(self, x:Tensor): b_size, seq_len, hidden_size = x.shape norm_x = x.sqr().sum_keepdim(2) / hidden_size x_normed = x.broadcast_div((norm_x + 1e-5).sqrt()) return x_normed.broadcast_mul(self.weight) class QuantizedLayer: - def __init__(self, layer_idx, hparams, all_tensors, cos_sin): + def __init__(self, layer_idx:int, hparams:Dict[str,Any], all_tensors:Dict[str,QTensor], cos_sin:Tuple[Tensor,Tensor]): p = f"layers.{layer_idx}" self.attention_wq = all_tensors[f"{p}.attention.wq.weight"] self.attention_wk = all_tensors[f"{p}.attention.wk.weight"] @@ -41,7 +42,7 @@ class QuantizedLayer: self.cos = cos_sin[0] self.sin = cos_sin[1] - def __call__(self, x, mask, index_pos): + def __call__(self, x:Tensor, mask:Tensor, index_pos:int): residual = x x = self.attn_norm(x) attn = self.forward_attn(x, mask, index_pos) @@ -51,11 +52,11 @@ class QuantizedLayer: x = self.ffn_norm(x) w1 = self.ffw1.matmul_t(x) w3 = self.ffw3.matmul_t(x) - mlp = self.ffw2.matmul_t(candle.nn.silu(w1) * w3) + mlp = self.ffw2.matmul_t(nn.silu(w1) * w3) return mlp + residual - def forward_attn(self, x, mask, index_pos): + def forward_attn(self, x:Tensor, mask:Tensor, index_pos:int): b_size, seq_len, n_embd = x.shape q = self.attention_wq.matmul_t(x) k = self.attention_wk.matmul_t(x) @@ -80,12 +81,12 @@ class QuantizedLayer: att = q.matmul(k.t()) / self.head_dim**0.5 mask = mask.broadcast_as(att.shape) att = masked_fill(att, mask, float("-inf")) - att = candle.nn.softmax(att, -1) + att = nn.softmax(att, -1) y = att.matmul(v.contiguous()) y = y.transpose(1, 2).reshape((b_size, seq_len, n_embd)) return self.attention_wo.matmul_t(y) - def apply_rotary_emb(self, x, index_pos): + def apply_rotary_emb(self, x:Tensor, index_pos:int): (b_size, n_head, seq_len, n_embd) = x.shape cos = self.cos.narrow(0, index_pos, seq_len).reshape((seq_len, n_embd//2, 1)) sin = self.sin.narrow(0, index_pos, seq_len).reshape((seq_len, n_embd//2, 1)) @@ -107,7 +108,7 @@ def precompute_freqs_cis(hparams, freq_base): return (m.cos(), m.sin()) class QuantizedLlama: - def __init__(self, hparams, all_tensors): + def __init__(self, hparams:Dict[str,Any], all_tensors:Dict[str,QTensor]): self.tok_embeddings = all_tensors["tok_embeddings.weight"].dequantize() self.norm = RmsNorm(all_tensors["norm.weight"]) self.output = all_tensors["output.weight"] @@ -118,7 +119,7 @@ class QuantizedLlama: layer = QuantizedLayer(layer_idx, hparams, all_tensors, cos_sin) self.layers.append(layer) - def __call__(self, token, index_pos): + def __call__(self, token:Tensor, index_pos:int): b_size, seq_len = token.shape vocab_size, hidden_size = self.tok_embeddings.shape token = token.reshape((b_size * seq_len,)) @@ -135,7 +136,7 @@ class QuantizedLlama: x = self.output.matmul_t(x) return x -def gguf_rename(tensor_name): +def gguf_rename(tensor_name:str): if tensor_name == 'token_embd.weight': return 'tok_embeddings.weight' if tensor_name == 'output_norm.weight': return 'norm.weight' tensor_name = tensor_name.replace('blk.', 'layers.') @@ -155,7 +156,7 @@ def main(): filename = sys.argv[1] print(f"reading model file {filename}") if filename.endswith("gguf"): - all_tensors, metadata = load_gguf(sys.argv[1]) + all_tensors, metadata = utils.load_gguf(sys.argv[1]) vocab = metadata["tokenizer.ggml.tokens"] for i, v in enumerate(vocab): vocab[i] = '\n' if v == '<0x0A>' else v.replace('▁', ' ') @@ -175,7 +176,7 @@ def main(): all_tensors = { gguf_rename(k): v for k, v in all_tensors.items() } else: - all_tensors, hparams, vocab = load_ggml(sys.argv[1]) + all_tensors, hparams, vocab = utils.load_ggml(sys.argv[1]) print(hparams) model = QuantizedLlama(hparams, all_tensors) print("model built, starting inference") diff --git a/candle-pyo3/src/lib.rs b/candle-pyo3/src/lib.rs index 1df78ec6..55b7a888 100644 --- a/candle-pyo3/src/lib.rs +++ b/candle-pyo3/src/lib.rs @@ -1,7 +1,7 @@ #![allow(clippy::redundant_closure_call)] use pyo3::exceptions::{PyTypeError, PyValueError}; use pyo3::prelude::*; -use pyo3::types::{IntoPyDict, PyTuple}; +use pyo3::types::{IntoPyDict, PyDict, PyTuple}; use pyo3::ToPyObject; use std::sync::Arc; @@ -31,6 +31,7 @@ impl From for ::candle::Shape { #[derive(Clone, Debug)] #[pyclass(name = "Tensor")] +/// A `candle` tensor. struct PyTensor(Tensor); impl std::ops::Deref for PyTensor { @@ -43,6 +44,7 @@ impl std::ops::Deref for PyTensor { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[pyclass(name = "DType")] +/// A `candle` dtype. struct PyDType(DType); #[pymethods] @@ -197,7 +199,7 @@ trait MapDType { #[pymethods] impl PyTensor { #[new] - #[pyo3(text_signature = "(data:_ArrayLike)")] + #[pyo3(text_signature = "(self, data:_ArrayLike)")] // TODO: Handle arbitrary input dtype and shape. /// Creates a new tensor from a Python value. The value can be a scalar or array-like object. fn new(py: Python<'_>, data: PyObject) -> PyResult { @@ -239,6 +241,7 @@ impl PyTensor { } /// Gets the tensor's data as a Python scalar or array-like object. + /// &RETURNS&: _ArrayLike fn values(&self, py: Python<'_>) -> PyResult { struct M<'a>(Python<'a>); impl<'a> MapDType for M<'a> { @@ -282,27 +285,36 @@ impl PyTensor { } #[getter] - /// Gets the tensor shape as a Python tuple. + /// Gets the tensor's shape. + /// &RETURNS&: Tuple[int] fn shape(&self, py: Python<'_>) -> PyObject { PyTuple::new(py, self.0.dims()).to_object(py) } #[getter] + /// Gets the tensor's strides. + /// &RETURNS&: Tuple[int] fn stride(&self, py: Python<'_>) -> PyObject { PyTuple::new(py, self.0.stride()).to_object(py) } #[getter] + /// Gets the tensor's dtype. + /// &RETURNS&: DType fn dtype(&self) -> PyDType { PyDType(self.0.dtype()) } #[getter] + /// Gets the tensor's device. + /// &RETURNS&: Device fn device(&self, py: Python<'_>) -> PyObject { PyDevice::from_device(self.0.device()).to_object(py) } #[getter] + /// Gets the tensor's rank. + /// &RETURNS&: int fn rank(&self) -> usize { self.0.rank() } @@ -315,69 +327,117 @@ impl PyTensor { self.__repr__() } + /// Performs the `sin` operation on the tensor. + /// &RETURNS&: Tensor fn sin(&self) -> PyResult { Ok(PyTensor(self.0.sin().map_err(wrap_err)?)) } + /// Performs the `cos` operation on the tensor. + /// &RETURNS&: Tensor fn cos(&self) -> PyResult { Ok(PyTensor(self.0.cos().map_err(wrap_err)?)) } + /// Performs the `log` operation on the tensor. + /// &RETURNS&: Tensor fn log(&self) -> PyResult { Ok(PyTensor(self.0.log().map_err(wrap_err)?)) } + /// Squares the tensor. + /// &RETURNS&: Tensor fn sqr(&self) -> PyResult { Ok(PyTensor(self.0.sqr().map_err(wrap_err)?)) } + /// Calculates the square root of the tensor. + /// &RETURNS&: Tensor fn sqrt(&self) -> PyResult { Ok(PyTensor(self.0.sqrt().map_err(wrap_err)?)) } + /// Get the `recip` of the tensor. + /// &RETURNS&: Tensor fn recip(&self) -> PyResult { Ok(PyTensor(self.0.recip().map_err(wrap_err)?)) } + /// Performs the `exp` operation on the tensor. + /// &RETURNS&: Tensor fn exp(&self) -> PyResult { Ok(PyTensor(self.0.exp().map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, p:float)")] + /// Performs the `pow` operation on the tensor with the given exponent. + /// &RETURNS&: Tensor fn powf(&self, p: f64) -> PyResult { Ok(PyTensor(self.0.powf(p).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, rhs:Tensor, dim:int)")] + /// Select values for the input tensor at the target indexes across the specified dimension. + /// + /// The `indexes` is argument is an int tensor with a single dimension. + /// The output has the same number of dimension as the `self` input. The target dimension of + /// the output has length the length of `indexes` and the values are taken from `self` using + /// the index from `indexes`. Other dimensions have the same number of elements as the input + /// tensor. + /// &RETURNS&: Tensor fn index_select(&self, rhs: &Self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.index_select(rhs, dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, rhs:Tensor)")] + /// Performs a matrix multiplication between the two tensors. + /// &RETURNS&: Tensor fn matmul(&self, rhs: &Self) -> PyResult { Ok(PyTensor(self.0.matmul(rhs).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, rhs:Tensor)")] + /// Adds the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + /// &RETURNS&: Tensor fn broadcast_add(&self, rhs: &Self) -> PyResult { Ok(PyTensor(self.0.broadcast_add(rhs).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, rhs:Tensor)")] + /// Subtracts the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + /// &RETURNS&: Tensor fn broadcast_sub(&self, rhs: &Self) -> PyResult { Ok(PyTensor(self.0.broadcast_sub(rhs).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, rhs:Tensor)")] + /// Multiplies the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + /// &RETURNS&: Tensor fn broadcast_mul(&self, rhs: &Self) -> PyResult { Ok(PyTensor(self.0.broadcast_mul(rhs).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, rhs:Tensor)")] + /// Divides the two tensors, while broadcasting the right-hand-side tensor to match the shape of the left-hand-side tensor. + /// &RETURNS&: Tensor fn broadcast_div(&self, rhs: &Self) -> PyResult { Ok(PyTensor(self.0.broadcast_div(rhs).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, on_true:Tensor, on_false:Tensor)")] + /// Returns a tensor with the same shape as the input tensor, the values are taken from + /// `on_true` if the input tensor value is not zero, and `on_false` at the positions where the + /// input tensor is equal to zero. + /// &RETURNS&: Tensor fn where_cond(&self, on_true: &Self, on_false: &Self) -> PyResult { Ok(PyTensor( self.0.where_cond(on_true, on_false).map_err(wrap_err)?, )) } + /// Add two tensors. + /// &RETURNS&: Tensor fn __add__(&self, rhs: &PyAny) -> PyResult { let tensor = if let Ok(rhs) = rhs.extract::() { (&self.0 + &rhs.0).map_err(wrap_err)? @@ -393,6 +453,8 @@ impl PyTensor { self.__add__(rhs) } + /// Multiply two tensors. + /// &RETURNS&: Tensor fn __mul__(&self, rhs: &PyAny) -> PyResult { let tensor = if let Ok(rhs) = rhs.extract::() { (&self.0 * &rhs.0).map_err(wrap_err)? @@ -408,6 +470,8 @@ impl PyTensor { self.__mul__(rhs) } + /// Subtract two tensors. + /// &RETURNS&: Tensor fn __sub__(&self, rhs: &PyAny) -> PyResult { let tensor = if let Ok(rhs) = rhs.extract::() { (&self.0 - &rhs.0).map_err(wrap_err)? @@ -419,6 +483,8 @@ impl PyTensor { Ok(Self(tensor)) } + /// Divide two tensors. + /// &RETURNS&: Tensor fn __truediv__(&self, rhs: &PyAny) -> PyResult { let tensor = if let Ok(rhs) = rhs.extract::() { (&self.0 / &rhs.0).map_err(wrap_err)? @@ -430,62 +496,102 @@ impl PyTensor { Ok(Self(tensor)) } + #[pyo3(text_signature = "(self, shape:Sequence[int])")] + /// Reshapes the tensor to the given shape. + /// &RETURNS&: Tensor fn reshape(&self, shape: PyShape) -> PyResult { Ok(PyTensor(self.0.reshape(shape).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, shape:Sequence[int])")] + /// Broadcasts the tensor to the given shape. + /// &RETURNS&: Tensor fn broadcast_as(&self, shape: PyShape) -> PyResult { Ok(PyTensor(self.0.broadcast_as(shape).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, shape:Sequence[int])")] + /// Broadcasts the tensor to the given shape, adding new dimensions on the left. + /// &RETURNS&: Tensor fn broadcast_left(&self, shape: PyShape) -> PyResult { Ok(PyTensor(self.0.broadcast_left(shape).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int)")] + /// Creates a new tensor with the specified dimension removed if its size was one. + /// &RETURNS&: Tensor fn squeeze(&self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.squeeze(dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int)")] + /// Creates a new tensor with a dimension of size one inserted at the specified position. + /// &RETURNS&: Tensor fn unsqueeze(&self, dim: usize) -> PyResult { Ok(PyTensor(self.0.unsqueeze(dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, index:int)")] + /// Gets the value at the specified index. + /// &RETURNS&: Tensor fn get(&self, index: i64) -> PyResult { let index = actual_index(self, 0, index).map_err(wrap_err)?; Ok(PyTensor(self.0.get(index).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim1:int, dim2:int)")] + /// Returns a tensor that is a transposed version of the input, the given dimensions are swapped. + /// &RETURNS&: Tensor fn transpose(&self, dim1: usize, dim2: usize) -> PyResult { Ok(PyTensor(self.0.transpose(dim1, dim2).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int, start:int, len:int)")] + /// Returns a new tensor that is a narrowed version of the input, the dimension `dim` + /// ranges from `start` to `start + len`. + /// &RETURNS&: Tensor fn narrow(&self, dim: i64, start: i64, len: usize) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; let start = actual_index(self, dim, start).map_err(wrap_err)?; Ok(PyTensor(self.0.narrow(dim, start, len).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int)")] + /// Returns the indices of the maximum value(s) across the selected dimension. + /// &RETURNS&: Tensor fn argmax_keepdim(&self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.argmax_keepdim(dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int)")] + /// Returns the indices of the minimum value(s) across the selected dimension. + /// &RETURNS&: Tensor fn argmin_keepdim(&self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.argmin_keepdim(dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int)")] + /// Gathers the maximum value across the selected dimension. + /// &RETURNS&: Tensor fn max_keepdim(&self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.max_keepdim(dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int)")] + /// Gathers the minimum value across the selected dimension. + /// &RETURNS&: Tensor fn min_keepdim(&self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.min_keepdim(dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:Union[int, List[int]])")] + /// Returns the sum of all elements in the input tensor. The sum is performed over all the input dimensions. + /// &RETURNS&: Tensor fn sum_keepdim(&self, dims: PyObject, py: Python<'_>) -> PyResult { let dims = if let Ok(dim) = dims.extract::(py) { vec![dim] @@ -497,10 +603,14 @@ impl PyTensor { )) } + /// Returns the sum of the tensor. + /// &RETURNS&: Tensor fn sum_all(&self) -> PyResult { Ok(PyTensor(self.0.sum_all().map_err(wrap_err)?)) } + /// Returns the mean of the tensor. + /// &RETURNS&: Tensor fn mean_all(&self) -> PyResult { let elements = self.0.elem_count(); let sum = self.0.sum_all().map_err(wrap_err)?; @@ -508,54 +618,83 @@ impl PyTensor { Ok(PyTensor(mean)) } + #[pyo3(text_signature = "(self, dim:int)")] + /// Flattens the tensor on the dimension indexes from `dim` (inclusive) to the last dimension. + /// &RETURNS&: Tensor fn flatten_from(&self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.flatten_from(dim).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dim:int)")] + ///Flattens the tensor on the dimension indexes from `0` to `dim` (inclusive). + /// &RETURNS&: Tensor fn flatten_to(&self, dim: i64) -> PyResult { let dim = actual_dim(self, dim).map_err(wrap_err)?; Ok(PyTensor(self.0.flatten_to(dim).map_err(wrap_err)?)) } + /// Flattens the tensor into a 1D tensor. + /// &RETURNS&: Tensor fn flatten_all(&self) -> PyResult { Ok(PyTensor(self.0.flatten_all().map_err(wrap_err)?)) } + /// Transposes the tensor. + /// &RETURNS&: Tensor fn t(&self) -> PyResult { Ok(PyTensor(self.0.t().map_err(wrap_err)?)) } + /// Makes the tensor contiguous in memory. + /// &RETURNS&: Tensor fn contiguous(&self) -> PyResult { Ok(PyTensor(self.0.contiguous().map_err(wrap_err)?)) } + /// Returns true if the tensor is contiguous in C order. + /// &RETURNS&: bool fn is_contiguous(&self) -> bool { self.0.is_contiguous() } + /// Returns true if the tensor is contiguous in Fortran order. + /// &RETURNS&: bool fn is_fortran_contiguous(&self) -> bool { self.0.is_fortran_contiguous() } + /// Detach the tensor from the computation graph. + /// &RETURNS&: Tensor fn detach(&self) -> PyResult { Ok(PyTensor(self.0.detach().map_err(wrap_err)?)) } + /// Returns a copy of the tensor. + /// &RETURNS&: Tensor fn copy(&self) -> PyResult { Ok(PyTensor(self.0.copy().map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, dtype:Union[str,DType])")] + /// Convert the tensor to a new dtype. + /// &RETURNS&: Tensor fn to_dtype(&self, dtype: PyObject, py: Python<'_>) -> PyResult { let dtype = PyDType::from_pyobject(dtype, py)?; Ok(PyTensor(self.0.to_dtype(dtype.0).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, device:Union[str,Device])")] + /// Move the tensor to a new device. + /// &RETURNS&: Tensor fn to_device(&self, device: PyDevice) -> PyResult { let device = device.as_device()?; Ok(PyTensor(self.0.to_device(&device).map_err(wrap_err)?)) } + #[pyo3(text_signature = "(self, quantized_dtype:str)")] + /// Quantize the tensor. + /// &RETURNS&: QTensor fn quantize(&self, quantized_dtype: &str) -> PyResult { use ::candle::quantized; let res = match quantized_dtype { @@ -586,6 +725,7 @@ impl PyTensor { #[pyfunction] #[pyo3(text_signature = "(tensors:List[Tensor], dim:int )")] /// Concatenate the tensors across one axis. +/// &RETURNS&: Tensor fn cat(tensors: Vec, dim: i64) -> PyResult { if tensors.is_empty() { return Err(PyErr::new::("empty input to cat")); @@ -599,6 +739,7 @@ fn cat(tensors: Vec, dim: i64) -> PyResult { #[pyfunction] #[pyo3(text_signature = "(tensors:List[Tensor], dim:int)")] /// Stack the tensors along a new axis. +/// &RETURNS&: Tensor fn stack(tensors: Vec, dim: usize) -> PyResult { let tensors = tensors.into_iter().map(|t| t.0).collect::>(); let tensor = Tensor::stack(&tensors, dim).map_err(wrap_err)?; @@ -608,6 +749,7 @@ fn stack(tensors: Vec, dim: usize) -> PyResult { #[pyfunction] #[pyo3(text_signature = "(data:_ArrayLike)")] /// Creates a new tensor from a Python value. The value can be a scalar or array-like object. +/// &RETURNS&: Tensor fn tensor(py: Python<'_>, data: PyObject) -> PyResult { PyTensor::new(py, data) } @@ -615,6 +757,7 @@ fn tensor(py: Python<'_>, data: PyObject) -> PyResult { #[pyfunction] #[pyo3(signature = (shape, *, device=None), text_signature = "(shape:Sequence[int], device:Optional[Device]=None)")] /// Creates a new tensor with random values. +/// &RETURNS&: Tensor fn rand(_py: Python<'_>, shape: PyShape, device: Option) -> PyResult { let device = device.unwrap_or(PyDevice::Cpu).as_device()?; let tensor = Tensor::rand(0f32, 1f32, shape.0, &device).map_err(wrap_err)?; @@ -623,6 +766,8 @@ fn rand(_py: Python<'_>, shape: PyShape, device: Option) -> PyResult

, shape: PyShape, device: Option) -> PyResult { let device = device.unwrap_or(PyDevice::Cpu).as_device()?; let tensor = Tensor::randn(0f32, 1f32, shape.0, &device).map_err(wrap_err)?; @@ -631,6 +776,8 @@ fn randn(_py: Python<'_>, shape: PyShape, device: Option) -> PyResult< #[pyfunction] #[pyo3(signature = (shape, *, dtype=None, device=None),text_signature = "(shape:Sequence[int], dtype:Optional[DType]=None, device:Optional[Device]=None)")] +/// Creates a new tensor filled with ones. +/// &RETURNS&: Tensor fn ones( py: Python<'_>, shape: PyShape, @@ -648,6 +795,8 @@ fn ones( #[pyfunction] #[pyo3(signature = (shape, *, dtype=None, device=None), text_signature = "(shape:Sequence[int], dtype:Optional[DType]=None, device:Optional[Device]=None)")] +/// Creates a new tensor filled with zeros. +/// &RETURNS&: Tensor fn zeros( py: Python<'_>, shape: PyShape, @@ -663,8 +812,9 @@ fn zeros( Ok(PyTensor(tensor)) } -#[derive(Debug)] +#[derive(Debug, Clone)] #[pyclass(name = "QTensor")] +/// A quantized tensor. struct PyQTensor(Arc); impl std::ops::Deref for PyQTensor { @@ -678,16 +828,22 @@ impl std::ops::Deref for PyQTensor { #[pymethods] impl PyQTensor { #[getter] + ///Gets the tensors quantized dtype. + /// &RETURNS&: str fn ggml_dtype(&self) -> String { format!("{:?}", self.0.dtype()) } #[getter] + ///Gets the rank of the tensor. + /// &RETURNS&: int fn rank(&self) -> usize { self.0.rank() } #[getter] + ///Gets the shape of the tensor. + /// &RETURNS&: Tuple[int] fn shape(&self, py: Python<'_>) -> PyObject { PyTuple::new(py, self.0.shape().dims()).to_object(py) } @@ -700,11 +856,16 @@ impl PyQTensor { self.__repr__() } + /// Dequantizes the tensor. + /// &RETURNS&: Tensor fn dequantize(&self) -> PyResult { let tensor = self.0.dequantize(&Device::Cpu).map_err(wrap_err)?; Ok(PyTensor(tensor)) } + #[pyo3(text_signature = "(self, lhs:Tensor)")] + /// Performs a quantized matrix multiplication, with the quantized tensor as the right hand side. + /// &RETURNS&: Tensor fn matmul_t(&self, lhs: &PyTensor) -> PyResult { let qmatmul = ::candle::quantized::QMatMul::from_arc(self.0.clone()); let res = qmatmul.forward(lhs).map_err(wrap_err)?; @@ -715,6 +876,7 @@ impl PyQTensor { #[pyfunction] #[pyo3(text_signature = "(path:Union[str,PathLike])")] /// Loads a safetensors file. Returns a dictionary mapping tensor names to tensors. +/// &RETURNS&: Dict[str,Tensor] fn load_safetensors(path: &str, py: Python<'_>) -> PyResult { let res = ::candle::safetensors::load(path, &Device::Cpu).map_err(wrap_err)?; let res = res @@ -727,6 +889,7 @@ fn load_safetensors(path: &str, py: Python<'_>) -> PyResult { #[pyfunction] #[pyo3(text_signature = "(path:Union[str,PathLike], tensors:Dict[str,Tensor])")] /// Saves a dictionary of tensors to a safetensors file. +/// &RETURNS&: None fn save_safetensors( path: &str, tensors: std::collections::HashMap, @@ -742,6 +905,7 @@ fn save_safetensors( #[pyo3(text_signature = "(path:Union[str,PathLike])")] /// Load a GGML file. Returns a tuple of three objects: a dictionary mapping tensor names to tensors, /// a dictionary mapping hyperparameter names to hyperparameter values, and a vocabulary. +/// &RETURNS&: Tuple[Dict[str,QTensor], Dict[str,Any], List[str]] fn load_ggml(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject, PyObject)> { let mut file = std::fs::File::open(path)?; let ggml = ::candle::quantized::ggml_file::Content::read(&mut file).map_err(wrap_err)?; @@ -776,6 +940,7 @@ fn load_ggml(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject, PyObje #[pyo3(text_signature = "(path:Union[str,PathLike])")] /// Loads a GGUF file. Returns a tuple of two dictionaries: the first maps tensor names to tensors, /// and the second maps metadata keys to metadata values. +/// &RETURNS&: Tuple[Dict[str,QTensor], Dict[str,Any]] fn load_gguf(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject)> { use ::candle::quantized::gguf_file; fn gguf_value_to_pyobject(v: &gguf_file::Value, py: Python<'_>) -> PyResult { @@ -824,26 +989,118 @@ fn load_gguf(path: &str, py: Python<'_>) -> PyResult<(PyObject, PyObject)> { Ok((tensors, metadata)) } +#[pyfunction] +#[pyo3( + text_signature = "(path:Union[str,PathLike], tensors:Dict[str,QTensor], metadata:Dict[str,Any])" +)] +/// Save quanitzed tensors and metadata to a GGUF file. +fn save_gguf(path: &str, tensors: PyObject, metadata: PyObject, py: Python<'_>) -> PyResult<()> { + use ::candle::quantized::gguf_file; + + fn pyobject_to_gguf_value(v: &PyAny, py: Python<'_>) -> PyResult { + let v: gguf_file::Value = if let Ok(x) = v.extract::() { + gguf_file::Value::U8(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::I8(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::U16(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::I16(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::U32(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::I32(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::U64(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::I64(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::F32(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::F64(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::Bool(x) + } else if let Ok(x) = v.extract::() { + gguf_file::Value::String(x) + } else if let Ok(x) = v.extract::>() { + let x = x + .into_iter() + .map(|f| pyobject_to_gguf_value(f.as_ref(py), py)) + .collect::>>()?; + gguf_file::Value::Array(x) + } else { + return Err(PyErr::new::(format!( + "unsupported type {:?}", + v + ))); + }; + Ok(v) + } + let tensors = tensors + .extract::<&PyDict>(py) + .map_err(|_| PyErr::new::("expected a dict"))? + .iter() + .map(|(key, value)| { + Ok(( + key.extract::() + .map_err(|_| PyErr::new::("keys must be strings"))?, + value.extract::()?.0, + )) + }) + .collect::>>()?; + + let metadata = metadata + .extract::<&PyDict>(py) + .map_err(|_| PyErr::new::("expected a dict"))? + .iter() + .map(|(key, value)| { + Ok(( + key.extract::() + .map_err(|_| PyErr::new::("keys must be strings"))?, + pyobject_to_gguf_value(value, py)?, + )) + }) + .collect::>>()?; + + let converted_metadata: Vec<_> = metadata + .iter() + .map(|(name, value)| (name.as_str(), value)) + .collect(); + + let converted_tensors: Vec<_> = tensors + .iter() + .map(|(name, tensor)| (name.as_str(), tensor.as_ref())) + .collect(); + + let mut file = std::fs::File::create(path)?; + + gguf_file::write(&mut file, &converted_metadata, &converted_tensors).map_err(wrap_err) +} + #[pyfunction] /// Returns true if the 'cuda' backend is available. +/// &RETURNS&: bool fn cuda_is_available() -> bool { ::candle::utils::cuda_is_available() } #[pyfunction] /// Returns true if candle was compiled with 'accelerate' support. +/// &RETURNS&: bool fn has_accelerate() -> bool { ::candle::utils::has_accelerate() } #[pyfunction] /// Returns true if candle was compiled with MKL support. +/// &RETURNS&: bool fn has_mkl() -> bool { ::candle::utils::has_mkl() } #[pyfunction] /// Returns the number of threads used by the candle. +/// &RETURNS&: int fn get_num_threads() -> usize { ::candle::utils::get_num_threads() } @@ -855,6 +1112,7 @@ fn candle_utils(_py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_function(wrap_pyfunction!(has_mkl, m)?)?; m.add_function(wrap_pyfunction!(load_ggml, m)?)?; m.add_function(wrap_pyfunction!(load_gguf, m)?)?; + m.add_function(wrap_pyfunction!(save_gguf, m)?)?; m.add_function(wrap_pyfunction!(load_safetensors, m)?)?; m.add_function(wrap_pyfunction!(save_safetensors, m)?)?; Ok(()) @@ -862,7 +1120,8 @@ fn candle_utils(_py: Python<'_>, m: &PyModule) -> PyResult<()> { #[pyfunction] #[pyo3(text_signature = "(tensor:Tensor, dim:int)")] -/// Applies the Softmax function to a given tensor. +/// Applies the Softmax function to a given tensor.# +/// &RETURNS&: Tensor fn softmax(tensor: PyTensor, dim: i64) -> PyResult { let dim = actual_dim(&tensor, dim).map_err(wrap_err)?; let sm = candle_nn::ops::softmax(&tensor.0, dim).map_err(wrap_err)?; @@ -872,6 +1131,7 @@ fn softmax(tensor: PyTensor, dim: i64) -> PyResult { #[pyfunction] #[pyo3(text_signature = "(tensor:Tensor)")] /// Applies the Sigmoid Linear Unit (SiLU) function to a given tensor. +/// &RETURNS&: Tensor fn silu(tensor: PyTensor) -> PyResult { let s = candle_nn::ops::silu(&tensor.0).map_err(wrap_err)?; Ok(PyTensor(s)) diff --git a/candle-pyo3/stub.py b/candle-pyo3/stub.py index b5b9256f..149715c2 100644 --- a/candle-pyo3/stub.py +++ b/candle-pyo3/stub.py @@ -13,8 +13,8 @@ TYPING = """from typing import Any, Callable, Dict, List, Optional, Tuple, Union from os import PathLike """ CANDLE_SPECIFIC_TYPING = "from candle.typing import _ArrayLike, Device\n" -CANDLE_TENSOR_IMPORTS = "from candle import Tensor,DType\n" - +CANDLE_TENSOR_IMPORTS = "from candle import Tensor,DType,QTensor\n" +RETURN_TYPE_MARKER = "&RETURNS&: " def do_indent(text: Optional[str], indent: str): @@ -28,11 +28,26 @@ def function(obj, indent:str, text_signature:str=None): text_signature = obj.__text_signature__ text_signature = text_signature.replace("$self", "self").lstrip().rstrip() + doc_string = obj.__doc__ + if doc_string is None: + doc_string = "" + + # Check if we have a return type annotation in the docstring + return_type = None + doc_lines = doc_string.split("\n") + if doc_lines[-1].lstrip().startswith(RETURN_TYPE_MARKER): + # Extract the return type and remove it from the docstring + return_type = doc_lines[-1].lstrip()[len(RETURN_TYPE_MARKER):].strip() + doc_string = "\n".join(doc_lines[:-1]) + string = "" - string += f"{indent}def {obj.__name__}{text_signature}:\n" + if return_type: + string += f"{indent}def {obj.__name__}{text_signature} -> {return_type}:\n" + else: + string += f"{indent}def {obj.__name__}{text_signature}:\n" indent += INDENT string += f'{indent}"""\n' - string += f"{indent}{do_indent(obj.__doc__, indent)}\n" + string += f"{indent}{do_indent(doc_string, indent)}\n" string += f'{indent}"""\n' string += f"{indent}pass\n" string += "\n" diff --git a/candle-pyo3/test.py b/candle-pyo3/test.py index c78ffc41..7f24b49d 100644 --- a/candle-pyo3/test.py +++ b/candle-pyo3/test.py @@ -1,5 +1,4 @@ import candle -from candle import Tensor, QTensor t = candle.Tensor(42.0) print(t) @@ -10,7 +9,7 @@ t = candle.Tensor([3.0, 1, 4, 1, 5, 9, 2, 6]) print(t) print(t+t) -t:Tensor = t.reshape([2, 4]) +t = t.reshape([2, 4]) print(t.matmul(t.t())) print(t.to_dtype(candle.u8)) @@ -21,7 +20,7 @@ print(t) print(t.dtype) t = candle.randn((16, 256)) -quant_t:QTensor = t.quantize("q6k") -dequant_t:Tensor = quant_t.dequantize() -diff2:Tensor = (t - dequant_t).sqr() +quant_t = t.quantize("q6k") +dequant_t = quant_t.dequantize() +diff2 = (t - dequant_t).sqr() print(diff2.mean_all()) From ef8cd8fea04240e9cd59b003236e33d584aaf768 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Mon, 18 Sep 2023 09:36:20 +0100 Subject: [PATCH 118/150] Update the candle-gemm version. (#885) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cb6bf3cb..fb03d93b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ byteorder = "1.4.3" clap = { version = "4.2.4", features = ["derive"] } cudarc = { version = "0.9.14", features = ["f16"] } # TODO: Switch back to the official gemm implementation once it has caught up. -gemm = { version = "0.15.6", package = "candle-gemm" } +gemm = { version = "0.16.0", package = "candle-gemm" } hf-hub = "0.3.0" half = { version = "2.3.1", features = ["num-traits", "use-intrinsics", "rand_distr"] } image = { version = "0.24.7", default-features = false, features = ["jpeg", "png"] } From 12696b7b2df829fee05231d40a4f00e480ad5376 Mon Sep 17 00:00:00 2001 From: Aaron Sun Date: Mon, 18 Sep 2023 16:41:50 +0800 Subject: [PATCH 119/150] Fix typos in SAM WASM example (#884) --- candle-wasm-examples/segment-anything/lib-example.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/candle-wasm-examples/segment-anything/lib-example.html b/candle-wasm-examples/segment-anything/lib-example.html index 127b9152..5060f073 100644 --- a/candle-wasm-examples/segment-anything/lib-example.html +++ b/candle-wasm-examples/segment-anything/lib-example.html @@ -20,7 +20,7 @@ + + + + +

+ 🕯️ +
+

Candle BERT

+

Rust/WASM Demo

+

+ Running sentence embeddings and similarity search in the browser using + the Bert Model written with + Candle + + and compiled to Wasm. Embeddings models from are from + + Sentence Transformers + + and + + Liang Wang - e5 Models + +

+
+ +
+ + +
+
+

Examples:

+
+ + + + + + + +
+
+
+ + + +
+
+

Input text:

+
+
+ +
+
+
+ +

+ Input text to perform semantic similarity search... +

+
+
+
+ + diff --git a/candle-wasm-examples/bert/src/bin/m.rs b/candle-wasm-examples/bert/src/bin/m.rs new file mode 100644 index 00000000..f5521abd --- /dev/null +++ b/candle-wasm-examples/bert/src/bin/m.rs @@ -0,0 +1,92 @@ +use candle::{DType, Device, Tensor}; +use candle_nn::VarBuilder; +use candle_transformers::models::bert::{BertModel, Config}; +use candle_wasm_example_bert::console_log; +use tokenizers::{PaddingParams, Tokenizer}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct Model { + bert: BertModel, + tokenizer: Tokenizer, +} + +#[wasm_bindgen] +impl Model { + #[wasm_bindgen(constructor)] + pub fn load(weights: Vec, tokenizer: Vec, config: Vec) -> Result { + console_error_panic_hook::set_once(); + console_log!("loading model"); + let device = &Device::Cpu; + let weights = safetensors::tensor::SafeTensors::deserialize(&weights)?; + let vb = VarBuilder::from_safetensors(vec![weights], DType::F64, device); + let config: Config = serde_json::from_slice(&config)?; + let tokenizer = + Tokenizer::from_bytes(&tokenizer).map_err(|m| JsError::new(&m.to_string()))?; + let bert = BertModel::load(vb, &config)?; + + Ok(Self { bert, tokenizer }) + } + + pub fn get_embeddings(&mut self, input: JsValue) -> Result { + let input: Params = + serde_wasm_bindgen::from_value(input).map_err(|m| JsError::new(&m.to_string()))?; + let sentences = input.sentences; + let normalize_embeddings = input.normalize_embeddings; + + let device = &Device::Cpu; + if let Some(pp) = self.tokenizer.get_padding_mut() { + pp.strategy = tokenizers::PaddingStrategy::BatchLongest + } else { + let pp = PaddingParams { + strategy: tokenizers::PaddingStrategy::BatchLongest, + ..Default::default() + }; + self.tokenizer.with_padding(Some(pp)); + } + let tokens = self + .tokenizer + .encode_batch(sentences.to_vec(), true) + .map_err(|m| JsError::new(&m.to_string()))?; + + let token_ids: Vec = tokens + .iter() + .map(|tokens| { + let tokens = tokens.get_ids().to_vec(); + Tensor::new(tokens.as_slice(), device) + }) + .collect::, _>>()?; + + let token_ids = Tensor::stack(&token_ids, 0)?; + let token_type_ids = token_ids.zeros_like()?; + console_log!("running inference on batch {:?}", token_ids.shape()); + let embeddings = self.bert.forward(&token_ids, &token_type_ids)?; + console_log!("generated embeddings {:?}", embeddings.shape()); + // Apply some avg-pooling by taking the mean embedding value for all tokens (including padding) + let (_n_sentence, n_tokens, _hidden_size) = embeddings.dims3()?; + let embeddings = (embeddings.sum(1)? / (n_tokens as f64))?; + let embeddings = if normalize_embeddings { + embeddings.broadcast_div(&embeddings.sqr()?.sum_keepdim(1)?.sqrt()?)? + } else { + embeddings + }; + let embeddings_data = embeddings.to_vec2()?; + Ok(serde_wasm_bindgen::to_value(&Embeddings { + data: embeddings_data, + })?) + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct Embeddings { + data: Vec>, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Params { + sentences: Vec, + normalize_embeddings: bool, +} +fn main() { + console_error_panic_hook::set_once(); +} diff --git a/candle-wasm-examples/bert/src/lib.rs b/candle-wasm-examples/bert/src/lib.rs new file mode 100644 index 00000000..1e3657be --- /dev/null +++ b/candle-wasm-examples/bert/src/lib.rs @@ -0,0 +1,20 @@ +use candle_transformers::models::bert; +use wasm_bindgen::prelude::*; + +pub use bert::{BertModel, Config, DTYPE}; +pub use tokenizers::{PaddingParams, Tokenizer}; + +#[wasm_bindgen] +extern "C" { + // Use `js_namespace` here to bind `console.log(..)` instead of just + // `log(..)` + #[wasm_bindgen(js_namespace = console)] + pub fn log(s: &str); +} + +#[macro_export] +macro_rules! console_log { + // Note that this is using the `log` function imported above during + // `bare_bones` + ($($t:tt)*) => ($crate::log(&format_args!($($t)*).to_string())) +} diff --git a/candle-wasm-examples/bert/utils.js b/candle-wasm-examples/bert/utils.js new file mode 100644 index 00000000..9d8bd7bd --- /dev/null +++ b/candle-wasm-examples/bert/utils.js @@ -0,0 +1,99 @@ +export async function getEmbeddings( + worker, + weightsURL, + tokenizerURL, + configURL, + modelID, + sentences, + updateStatus = null +) { + return new Promise((resolve, reject) => { + worker.postMessage({ + weightsURL, + tokenizerURL, + configURL, + modelID, + sentences, + }); + function messageHandler(event) { + if ("error" in event.data) { + worker.removeEventListener("message", messageHandler); + reject(new Error(event.data.error)); + } + if (event.data.status === "complete") { + worker.removeEventListener("message", messageHandler); + resolve(event.data); + } + if (updateStatus) updateStatus(event.data); + } + worker.addEventListener("message", messageHandler); + }); +} + +const MODELS = { + intfloat_e5_small_v2: { + base_url: "https://huggingface.co/intfloat/e5-small-v2/resolve/main/", + search_prefix: "query: ", + document_prefix: "passage: ", + }, + intfloat_e5_base_v2: { + base_url: "https://huggingface.co/intfloat/e5-base-v2/resolve/main/", + search_prefix: "query: ", + document_prefix: "passage:", + }, + intfloat_multilingual_e5_small: { + base_url: + "https://huggingface.co/intfloat/multilingual-e5-small/resolve/main/", + search_prefix: "query: ", + document_prefix: "passage: ", + }, + sentence_transformers_all_MiniLM_L6_v2: { + base_url: + "https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/refs%2Fpr%2F21/", + search_prefix: "", + document_prefix: "", + }, + sentence_transformers_all_MiniLM_L12_v2: { + base_url: + "https://huggingface.co/sentence-transformers/all-MiniLM-L12-v2/resolve/refs%2Fpr%2F4/", + search_prefix: "", + document_prefix: "", + }, +}; +export function getModelInfo(id) { + return { + modelURL: MODELS[id].base_url + "model.safetensors", + configURL: MODELS[id].base_url + "config.json", + tokenizerURL: MODELS[id].base_url + "tokenizer.json", + search_prefix: MODELS[id].search_prefix, + document_prefix: MODELS[id].document_prefix, + }; +} + +export function cosineSimilarity(vec1, vec2) { + const dot = vec1.reduce((acc, val, i) => acc + val * vec2[i], 0); + const a = Math.sqrt(vec1.reduce((acc, val) => acc + val * val, 0)); + const b = Math.sqrt(vec2.reduce((acc, val) => acc + val * val, 0)); + return dot / (a * b); +} +export async function getWikiText(article) { + // thanks to wikipedia for the API + const URL = `https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exlimit=1&titles=${article}&explaintext=1&exsectionformat=plain&format=json&origin=*`; + return fetch(URL, { + method: "GET", + headers: { + Accept: "application/json", + }, + }) + .then((r) => r.json()) + .then((data) => { + const pages = data.query.pages; + const pageId = Object.keys(pages)[0]; + const extract = pages[pageId].extract; + if (extract === undefined || extract === "") { + throw new Error("No article found"); + } + return extract; + }) + .catch((error) => console.error("Error:", error)); +} From 67a486d18d47a276b5b5beb422fed2a0932e7ac9 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Tue, 19 Sep 2023 21:59:44 +0100 Subject: [PATCH 133/150] Line-up the wuerstchen model with the python implementation. (#901) * Line-up the wuerstchen model with the python implementation. * Missing cos. * Fix the picture denormalization. --- candle-examples/examples/wuerstchen/main.rs | 1 - candle-transformers/src/models/stable_diffusion/clip.rs | 6 ++++-- candle-transformers/src/models/wuerstchen/common.rs | 4 ++-- candle-transformers/src/models/wuerstchen/ddpm.rs | 6 ++++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/candle-examples/examples/wuerstchen/main.rs b/candle-examples/examples/wuerstchen/main.rs index 8064f87f..bce68114 100644 --- a/candle-examples/examples/wuerstchen/main.rs +++ b/candle-examples/examples/wuerstchen/main.rs @@ -373,7 +373,6 @@ fn run(args: Args) -> Result<()> { ); let image = vqgan.decode(&(&latents * 0.3764)?)?; // TODO: Add the clamping between 0 and 1. - let image = ((image / 2.)? + 0.5)?.to_device(&Device::Cpu)?; let image = (image * 255.)?.to_dtype(DType::U8)?.i(0)?; let image_filename = output_filename(&final_image, idx + 1, num_samples, None); candle_examples::save_image(&image, image_filename)? diff --git a/candle-transformers/src/models/stable_diffusion/clip.rs b/candle-transformers/src/models/stable_diffusion/clip.rs index 7f86cf31..e7a20270 100644 --- a/candle-transformers/src/models/stable_diffusion/clip.rs +++ b/candle-transformers/src/models/stable_diffusion/clip.rs @@ -12,6 +12,7 @@ use candle_nn::Module; pub enum Activation { QuickGelu, Gelu, + GeluErf, } impl Module for Activation { @@ -19,6 +20,7 @@ impl Module for Activation { match self { Activation::QuickGelu => xs * nn::ops::sigmoid(&(xs * 1.702f64)?)?, Activation::Gelu => xs.gelu(), + Activation::GeluErf => xs.gelu_erf(), } } } @@ -111,7 +113,7 @@ impl Config { num_hidden_layers: 24, num_attention_heads: 16, projection_dim: 1024, - activation: Activation::Gelu, + activation: Activation::GeluErf, } } @@ -126,7 +128,7 @@ impl Config { num_hidden_layers: 32, num_attention_heads: 20, projection_dim: 512, - activation: Activation::Gelu, + activation: Activation::GeluErf, } } } diff --git a/candle-transformers/src/models/wuerstchen/common.rs b/candle-transformers/src/models/wuerstchen/common.rs index 3cac2a59..8416a1f1 100644 --- a/candle-transformers/src/models/wuerstchen/common.rs +++ b/candle-transformers/src/models/wuerstchen/common.rs @@ -100,7 +100,7 @@ impl GlobalResponseNorm { impl Module for GlobalResponseNorm { fn forward(&self, xs: &Tensor) -> Result { - let agg_norm = xs.sqr()?.sum_keepdim((1, 2))?; + let agg_norm = xs.sqr()?.sum_keepdim((1, 2))?.sqrt()?; let stand_div_norm = agg_norm.broadcast_div(&(agg_norm.mean_keepdim(D::Minus1)? + 1e-6)?)?; xs.broadcast_mul(&stand_div_norm)? @@ -152,7 +152,7 @@ impl ResBlock { .permute((0, 2, 3, 1))?; let xs = xs .apply(&self.channelwise_lin1)? - .gelu()? + .gelu_erf()? .apply(&self.channelwise_grn)? .apply(&self.channelwise_lin2)? .permute((0, 3, 1, 2))?; diff --git a/candle-transformers/src/models/wuerstchen/ddpm.rs b/candle-transformers/src/models/wuerstchen/ddpm.rs index 80640072..9e69b868 100644 --- a/candle-transformers/src/models/wuerstchen/ddpm.rs +++ b/candle-transformers/src/models/wuerstchen/ddpm.rs @@ -52,8 +52,10 @@ impl DDPMWScheduler { } else { t }; - let alpha_cumprod = - ((t + s) / (1. + s) * std::f64::consts::PI * 0.5).powi(2) / self.init_alpha_cumprod; + let alpha_cumprod = ((t + s) / (1. + s) * std::f64::consts::PI * 0.5) + .cos() + .powi(2) + / self.init_alpha_cumprod; alpha_cumprod.clamp(0.0001, 0.9999) } From 05626ef492300a4f99c87555304ec863071722d5 Mon Sep 17 00:00:00 2001 From: Juarez Bochi Date: Tue, 19 Sep 2023 14:36:47 -0700 Subject: [PATCH 134/150] Flan T5: Read lm_head when word embeddings are not tied (#903) * Read lm_head when word embeddings are not tied * Fix formatting * Address comments --- candle-transformers/src/models/t5.rs | 50 ++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index b1f3a3aa..fd2720d3 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -18,12 +18,15 @@ fn default_use_cache() -> bool { true } +fn default_tie_word_embeddings() -> bool { + true +} + fn get_mask(size: usize, device: &Device) -> Result { let mask: Vec<_> = (0..size) .flat_map(|i| (0..size).map(move |j| u8::from(j > i))) .collect(); - let result = Tensor::from_slice(&mask, (size, size), device)?; - Ok(result) + Tensor::from_slice(&mask, (size, size), device) } fn masked_fill(on_false: &Tensor, mask: &Tensor, on_true: f32) -> Result { @@ -50,6 +53,8 @@ pub struct Config { initializer_factor: f64, #[serde(default)] feed_forward_proj: Activation, + #[serde(default = "default_tie_word_embeddings")] + tie_word_embeddings: bool, #[serde(default = "default_is_decoder")] is_decoder: bool, is_encoder_decoder: bool, @@ -75,6 +80,7 @@ impl Default for Config { layer_norm_epsilon: 1e-6, initializer_factor: 1.0, feed_forward_proj: Activation::Relu, + tie_word_embeddings: true, is_decoder: false, is_encoder_decoder: true, use_cache: true, @@ -94,6 +100,7 @@ impl Config { dropout_rate: 0.1, eos_token_id: 1, feed_forward_proj: Activation::Relu, + tie_word_embeddings: true, initializer_factor: 1.0, is_decoder: false, is_encoder_decoder: true, @@ -611,6 +618,9 @@ impl T5EncoderModel { pub struct T5ForConditionalGeneration { encoder: T5Stack, decoder: T5Stack, + d_model: usize, + tie_word_embeddings: bool, + lm_head: Option, shared: Arc, device: Device, } @@ -618,6 +628,7 @@ pub struct T5ForConditionalGeneration { impl T5ForConditionalGeneration { pub fn load(vb: VarBuilder, cfg: &Config) -> Result { assert!(cfg.is_encoder_decoder); + let d_model = cfg.d_model; let shared = embedding(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; let shared = Arc::new(shared); @@ -633,9 +644,23 @@ impl T5ForConditionalGeneration { decoder_cfg.num_layers = cfg.num_decoder_layers.unwrap_or(cfg.num_layers); let decoder = T5Stack::load(true, vb.pp("decoder"), &shared, &decoder_cfg)?; + let tie_word_embeddings = cfg.tie_word_embeddings; + let lm_head = if tie_word_embeddings { + None + } else { + Some(linear_no_bias( + cfg.d_model, + cfg.vocab_size, + vb.pp("lm_head"), + )?) + }; + Ok(Self { encoder, decoder, + d_model, + tie_word_embeddings, + lm_head, shared, device: vb.device().clone(), }) @@ -653,12 +678,23 @@ impl T5ForConditionalGeneration { let decoder_output = self .decoder .forward(decoder_input_ids, Some(encoder_output))?; - let sequence_output = decoder_output + + let scaling_factor = if self.tie_word_embeddings { + // Rescale output before projecting on vocab + // See https://github.com/tensorflow/mesh/blob/fa19d69eafc9a482aff0b59ddd96b025c0cb207d/mesh_tensorflow/transformer/transformer.py#L586 + (self.d_model as f64).sqrt() + } else { + 1.0 + }; + let sequence_output = ((decoder_output .narrow(1, decoder_output.dim(1)? - 1, 1)? - .squeeze(1)?; - // TODO: check cfg.tie_word_embeddings to load from model instead. - let lm_head_weights = self.shared.embeddings().t()?; - let output = sequence_output.matmul(&lm_head_weights)?; + .squeeze(1)?) + * scaling_factor)?; + let output = match self.lm_head { + None => sequence_output.matmul(&self.shared.embeddings().t()?)?, + Some(ref lm_head) => lm_head.forward(&sequence_output)?, + }; + // TODO: Rescale output before projecting on vocab? * (self.model_dim**-0.5) Ok(output) } From 098dd0d1e9cc2b1ca902e4e0d77a9abe3de72a9c Mon Sep 17 00:00:00 2001 From: Mahmoud Date: Wed, 20 Sep 2023 00:54:56 -0700 Subject: [PATCH 135/150] fix: add missing`top_p` in llama_multiprocess (#905) --- candle-examples/examples/llama_multiprocess/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/candle-examples/examples/llama_multiprocess/main.rs b/candle-examples/examples/llama_multiprocess/main.rs index 17dc90e2..8a13ce6c 100644 --- a/candle-examples/examples/llama_multiprocess/main.rs +++ b/candle-examples/examples/llama_multiprocess/main.rs @@ -89,6 +89,10 @@ struct Args { #[arg(long)] temperature: Option, + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + /// The seed to use when generating random samples. #[arg(long, default_value_t = 299792458)] seed: u64, @@ -222,7 +226,7 @@ fn main() -> Result<()> { .to_vec(); println!("starting the inference loop"); - let mut logits_processor = LogitsProcessor::new(args.seed, args.temperature); + let mut logits_processor = LogitsProcessor::new(args.seed, args.temperature, args.top_p); let mut new_tokens = vec![]; let start_gen = std::time::Instant::now(); let mut index_pos = 0; From c0b49d5a50a23e7fc466deb8dd52e8a2b8266fa4 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 09:26:24 +0100 Subject: [PATCH 136/150] Wuerstchen parameter tweaks. (#907) --- candle-examples/examples/wuerstchen/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/candle-examples/examples/wuerstchen/main.rs b/candle-examples/examples/wuerstchen/main.rs index bce68114..aaa9b78a 100644 --- a/candle-examples/examples/wuerstchen/main.rs +++ b/candle-examples/examples/wuerstchen/main.rs @@ -14,7 +14,7 @@ use candle::{DType, Device, IndexOp, Module, Tensor, D}; use clap::Parser; use tokenizers::Tokenizer; -const PRIOR_GUIDANCE_SCALE: f64 = 8.0; +const PRIOR_GUIDANCE_SCALE: f64 = 4.0; const RESOLUTION_MULTIPLE: f64 = 42.67; const LATENT_DIM_SCALE: f64 = 10.67; const PRIOR_CIN: usize = 16; @@ -354,7 +354,7 @@ fn run(args: Args) -> Result<()> { )?; println!("diffusion process with prior {image_embeddings:?}"); - let scheduler = wuerstchen::ddpm::DDPMWScheduler::new(60, Default::default())?; + let scheduler = wuerstchen::ddpm::DDPMWScheduler::new(12, Default::default())?; let timesteps = scheduler.timesteps(); let timesteps = ×teps[..timesteps.len() - 1]; for (index, &t) in timesteps.iter().enumerate() { From f685b2231cad71e98257b62618b364c255feacd7 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 10:14:51 +0100 Subject: [PATCH 137/150] Add some missing biases. (#908) --- candle-transformers/src/models/wuerstchen/paella_vq.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/candle-transformers/src/models/wuerstchen/paella_vq.rs b/candle-transformers/src/models/wuerstchen/paella_vq.rs index 6da7362c..4a69cca0 100644 --- a/candle-transformers/src/models/wuerstchen/paella_vq.rs +++ b/candle-transformers/src/models/wuerstchen/paella_vq.rs @@ -106,8 +106,7 @@ impl PaellaVQ { stride: 2, ..Default::default() }; - let block = - candle_nn::conv2d_no_bias(C_LEVELS[i - 1], c_level, 4, cfg, vb_d.pp(d_idx))?; + let block = candle_nn::conv2d(C_LEVELS[i - 1], c_level, 4, cfg, vb_d.pp(d_idx))?; d_idx += 1; Some(block) } else { @@ -130,7 +129,7 @@ impl PaellaVQ { let mut up_blocks = Vec::new(); let vb_u = vb.pp("up_blocks"); let mut u_idx = 0; - let up_blocks_conv = candle_nn::conv2d_no_bias( + let up_blocks_conv = candle_nn::conv2d( LATENT_CHANNELS, C_LEVELS[1], 1, @@ -152,7 +151,7 @@ impl PaellaVQ { stride: 2, ..Default::default() }; - let block = candle_nn::conv_transpose2d_no_bias( + let block = candle_nn::conv_transpose2d( c_level, C_LEVELS[C_LEVELS.len() - i - 2], 4, From 7b1ddcff47b14f80c3d11e65aa9b452ecc17d59a Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 11:33:51 +0100 Subject: [PATCH 138/150] Add clone to various nn layers. (#910) --- candle-nn/src/batch_norm.rs | 2 +- candle-nn/src/conv.rs | 6 +++--- candle-nn/src/embedding.rs | 2 +- candle-nn/src/group_norm.rs | 2 +- candle-nn/src/layer_norm.rs | 4 ++-- candle-nn/src/linear.rs | 2 +- candle-nn/src/rnn.rs | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/candle-nn/src/batch_norm.rs b/candle-nn/src/batch_norm.rs index 2dac0758..27ef15f7 100644 --- a/candle-nn/src/batch_norm.rs +++ b/candle-nn/src/batch_norm.rs @@ -38,7 +38,7 @@ impl From for BatchNormConfig { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct BatchNorm { running_mean: Tensor, running_var: Tensor, diff --git a/candle-nn/src/conv.rs b/candle-nn/src/conv.rs index 31bf9af0..89e9f42d 100644 --- a/candle-nn/src/conv.rs +++ b/candle-nn/src/conv.rs @@ -20,7 +20,7 @@ impl Default for Conv1dConfig { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Conv1d { weight: Tensor, bias: Option, @@ -88,7 +88,7 @@ impl Default for Conv2dConfig { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Conv2d { weight: Tensor, bias: Option, @@ -157,7 +157,7 @@ impl Default for ConvTranspose2dConfig { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ConvTranspose2d { weight: Tensor, bias: Option, diff --git a/candle-nn/src/embedding.rs b/candle-nn/src/embedding.rs index fccc8a17..52968bc2 100644 --- a/candle-nn/src/embedding.rs +++ b/candle-nn/src/embedding.rs @@ -1,7 +1,7 @@ //! Embedding Layer. use candle::{Result, Tensor}; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Embedding { embeddings: Tensor, hidden_size: usize, diff --git a/candle-nn/src/group_norm.rs b/candle-nn/src/group_norm.rs index eb1b889f..5b80b970 100644 --- a/candle-nn/src/group_norm.rs +++ b/candle-nn/src/group_norm.rs @@ -4,7 +4,7 @@ use candle::{DType, Result, Tensor}; // This group norm version handles both weight and bias so removes the mean. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct GroupNorm { weight: Tensor, bias: Tensor, diff --git a/candle-nn/src/layer_norm.rs b/candle-nn/src/layer_norm.rs index d2e80a82..7617fc6c 100644 --- a/candle-nn/src/layer_norm.rs +++ b/candle-nn/src/layer_norm.rs @@ -60,7 +60,7 @@ impl From for LayerNormConfig { } // This layer norm version handles both weight and bias so removes the mean. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct LayerNorm { weight: Tensor, bias: Option, @@ -143,7 +143,7 @@ pub fn layer_norm>( } /// RmsNorm is a specialized version of the LayerNorm module. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct RmsNorm(LayerNorm); impl RmsNorm { diff --git a/candle-nn/src/linear.rs b/candle-nn/src/linear.rs index de335964..94632296 100644 --- a/candle-nn/src/linear.rs +++ b/candle-nn/src/linear.rs @@ -19,7 +19,7 @@ //! ``` use candle::{Result, Tensor}; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Linear { weight: Tensor, bias: Option, diff --git a/candle-nn/src/rnn.rs b/candle-nn/src/rnn.rs index d52a9082..18a4a71c 100644 --- a/candle-nn/src/rnn.rs +++ b/candle-nn/src/rnn.rs @@ -85,7 +85,7 @@ impl LSTMConfig { /// /// #[allow(clippy::upper_case_acronyms, unused)] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct LSTM { w_ih: Tensor, w_hh: Tensor, @@ -235,7 +235,7 @@ impl GRUConfig { /// /// #[allow(clippy::upper_case_acronyms, unused)] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct GRU { w_ih: Tensor, w_hh: Tensor, From 728e16733406791c43bd545518393fae408c33ef Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 13:09:35 +0100 Subject: [PATCH 139/150] Add details on wuerstchen. (#911) --- README.md | 6 ++++ candle-examples/examples/wuerstchen/README.md | 27 ++++++++++++++++++ .../examples/wuerstchen/assets/cat.jpg | Bin 0 -> 38638 bytes 3 files changed, 33 insertions(+) create mode 100644 candle-examples/examples/wuerstchen/README.md create mode 100644 candle-examples/examples/wuerstchen/assets/cat.jpg diff --git a/README.md b/README.md index 3d1b10fe..93a47082 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,11 @@ We also provide a some command line based examples using state of the art models +- [Wuerstchen](./candle-examples/examples/wuerstchen/): another text to + image generative model. + + + - [yolo-v3](./candle-examples/examples/yolo-v3/) and [yolo-v8](./candle-examples/examples/yolo-v8/): object detection and pose estimation models. @@ -142,6 +147,7 @@ If you have an addition to this list, please submit a pull request. - Bert. - Whisper (multi-lingual support). - Stable Diffusion v1.5, v2.1, XL v1.0. + - Wurstchen v2. - Computer Vision Models. - DINOv2. - EfficientNet. diff --git a/candle-examples/examples/wuerstchen/README.md b/candle-examples/examples/wuerstchen/README.md new file mode 100644 index 00000000..1b8accd1 --- /dev/null +++ b/candle-examples/examples/wuerstchen/README.md @@ -0,0 +1,27 @@ +# candle-wuerstchen: Efficient Pretraining of Text-to-Image Models + +![anthropomorphic cat dressed as a fire fighter](./assets/cat.jpg) + +The `wuerstchen` example is a port of the [diffusers +implementation](https://github.com/huggingface/diffusers/tree/19edca82f1ff194c07317369a92b470dbae97f34/src/diffusers/pipelines/wuerstchen) for Würstchen v2. +The candle implementation reproduces the same structure/files for models and +pipelines. Useful resources: + +- [Official implementation](https://github.com/dome272/Wuerstchen). +- [Arxiv paper](https://arxiv.org/abs/2306.00637). +- Blog post: [Introducing Würstchen: Fast Diffusion for Image Generation](https://huggingface.co/blog/wuerstchen). + +## Getting the weights + +The weights are automatically downloaded for you from the [HuggingFace +Hub](https://huggingface.co/) on the first run. There are various command line +flags to use local files instead, run with `--help` to learn about them. + +## Running some example. + +```bash +cargo run --example wuerstchen --release --features cuda,cudnn -- \ + --prompt "Anthropomorphic cat dressed as a fire fighter" +``` + +The final image is named `sd_final.png` by default. diff --git a/candle-examples/examples/wuerstchen/assets/cat.jpg b/candle-examples/examples/wuerstchen/assets/cat.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9ff67183f57fc7947c3cd3ff831f1a68c3dec81f GIT binary patch literal 38638 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 z zZI1g|lg+pjSR+a?tp$C8lqGfZeyB|RJ$9BF*qyLh%S zDLg1+ovjc}gG#F(r&?)zcylb*Cfb6*lBLoHwRLMv6Kuya11!=&Ncp;VtV5JjnYu6Qg5htq6Ud^Tl1)6#hc!1d{)CaQ|FP`L zHnk6Vbvdia?{4y0O#BZ3nLuX0DcjsusB&x2C&G-jeDrOcb~S{Fm9jziuIE45{1asO zYm?kk>Hf_D2a`1Q$cdHkTJYoYKvxI1O3Ep#_Nl;k_OYK1GT?hvT74ew*@%)CTzZOP z$r*SqTzN!Z6tTb*_er&7Ac2C%6$O=`E#?(v)2+WAHB=`gbfrprp-vRGbUqljWU^Kv zRCLdJCtTE43sxnGQP6st^d1ehqH_(^Ko}e6-l}@!vubz3-rWh$#B-bqnEq0>XP%WH zbi6u+;lki!2Nhtl)8mN)a%B4VtZ4i`wy_3q3w7Aj*epnJ2gBN@CzPwC0^{-PqEw#vlgm;5cgVpBCZh@~v2 z4J2fC%9Z_THRZSkyeUajxj;FW`iis7bi@=sBW;ZAG4;(Syn$pK%4IF-&bxg{r$$0t zO7lX}5G0B6IyUY90QMiwh>qG)ys`MF9e}|8CX9~DpR2)<)tCZ*LTJAP#(?<1k3aLS0G zSoygl8Ty(nt(1;-9Q)t_lfU()jzuEtNN9=hfCHC2qjQ|&KTWF;+d6WIWC$1%T=pIQ zb$++g1;hE&awsa;9Rcf#NyQ&$+KuL)FwyU04%>+5Jh*RAN3~i_hMHc9bgn$m?5>v9 zH1jKR2|04Z>+9-j?lsLa;?X5QgcHgf^%<@YHv?;YH)S>6s~CzwCcrpgN3KDm;^G$? zLymFIB}VI4B-W73ZAn~_ys-}0RiQ4;qJhwF&6Day5;+h8 zN|HTB;-OIpJ!Ft|8(?OOibw$gP~`SKsuyM!MV>ZJRV(tUrlDvouY`jfjCzf+S6)tW z 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 literal 0 HcmV?d00001 From fb1c2ac535236ef5c08ad241588057499d0b117d Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 14:07:55 +0100 Subject: [PATCH 140/150] Add flash-attn support. (#912) * Add flash-attn support. * Add the use-flash-attn flag. * Re-enable flash-attn. --- candle-examples/Cargo.toml | 3 +- candle-examples/examples/wuerstchen/main.rs | 14 +++++- candle-transformers/Cargo.toml | 2 + .../models/wuerstchen/attention_processor.rs | 50 +++++++++++++++++-- .../src/models/wuerstchen/common.rs | 3 +- .../src/models/wuerstchen/diffnext.rs | 22 ++++++-- .../src/models/wuerstchen/prior.rs | 3 ++ 7 files changed, 85 insertions(+), 12 deletions(-) diff --git a/candle-examples/Cargo.toml b/candle-examples/Cargo.toml index cf8f0021..0e2e8093 100644 --- a/candle-examples/Cargo.toml +++ b/candle-examples/Cargo.toml @@ -13,7 +13,6 @@ readme = "README.md" accelerate-src = { workspace = true, optional = true } candle = { path = "../candle-core", version = "0.2.3", package = "candle-core" } candle-datasets = { path = "../candle-datasets", version = "0.2.3" } -candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.3", optional = true } candle-nn = { path = "../candle-nn", version = "0.2.3" } candle-transformers = { path = "../candle-transformers", version = "0.2.3" } cudarc = { workspace = true, optional = true } @@ -51,7 +50,7 @@ default = [] accelerate = ["dep:accelerate-src", "candle/accelerate", "candle-nn/accelerate", "candle-transformers/accelerate"] cuda = ["candle/cuda", "candle-nn/cuda", "candle-transformers/cuda"] cudnn = ["candle/cudnn"] -flash-attn = ["cuda", "dep:candle-flash-attn"] +flash-attn = ["cuda", "candle-transformers/flash-attn"] mkl = ["dep:intel-mkl-src", "candle/mkl", "candle-nn/mkl", "candle-transformers/mkl"] nccl = ["cuda", "cudarc/nccl", "dep:half"] diff --git a/candle-examples/examples/wuerstchen/main.rs b/candle-examples/examples/wuerstchen/main.rs index aaa9b78a..95f3b8f4 100644 --- a/candle-examples/examples/wuerstchen/main.rs +++ b/candle-examples/examples/wuerstchen/main.rs @@ -41,6 +41,9 @@ struct Args { #[arg(long)] tracing: bool, + #[arg(long)] + use_flash_attn: bool, + /// The height in pixels of the generated image. #[arg(long)] height: Option, @@ -289,8 +292,14 @@ fn run(args: Args) -> Result<()> { let weights = weights.deserialize()?; let vb = candle_nn::VarBuilder::from_safetensors(vec![weights], DType::F32, &device); wuerstchen::prior::WPrior::new( - /* c_in */ PRIOR_CIN, /* c */ 1536, /* c_cond */ 1280, - /* c_r */ 64, /* depth */ 32, /* nhead */ 24, vb, + /* c_in */ PRIOR_CIN, + /* c */ 1536, + /* c_cond */ 1280, + /* c_r */ 64, + /* depth */ 32, + /* nhead */ 24, + args.use_flash_attn, + vb, )? }; let prior_scheduler = wuerstchen::ddpm::DDPMWScheduler::new(60, Default::default())?; @@ -337,6 +346,7 @@ fn run(args: Args) -> Result<()> { /* c_cond */ 1024, /* clip_embd */ 1024, /* patch_size */ 2, + args.use_flash_attn, vb, )? }; diff --git a/candle-transformers/Cargo.toml b/candle-transformers/Cargo.toml index 2faadad9..a3115c2b 100644 --- a/candle-transformers/Cargo.toml +++ b/candle-transformers/Cargo.toml @@ -12,6 +12,7 @@ readme = "README.md" [dependencies] accelerate-src = { workspace = true, optional = true } candle = { path = "../candle-core", version = "0.2.3", package = "candle-core" } +candle-flash-attn = { path = "../candle-flash-attn", version = "0.2.3", optional = true } candle-nn = { path = "../candle-nn", version = "0.2.3" } intel-mkl-src = { workspace = true, optional = true } num-traits = { workspace = true } @@ -26,4 +27,5 @@ wav = { workspace = true } default = [] accelerate = ["dep:accelerate-src", "candle/accelerate", "candle-nn/accelerate"] cuda = ["candle/cuda", "candle-nn/cuda"] +flash-attn = ["cuda", "dep:candle-flash-attn"] mkl = ["dep:intel-mkl-src", "candle/mkl", "candle-nn/mkl"] diff --git a/candle-transformers/src/models/wuerstchen/attention_processor.rs b/candle-transformers/src/models/wuerstchen/attention_processor.rs index 3f1a72eb..0b90cb9d 100644 --- a/candle-transformers/src/models/wuerstchen/attention_processor.rs +++ b/candle-transformers/src/models/wuerstchen/attention_processor.rs @@ -11,10 +11,33 @@ pub struct Attention { to_out: Linear, heads: usize, scale: f64, + use_flash_attn: bool, +} + +#[cfg(feature = "flash-attn")] +fn flash_attn( + q: &Tensor, + k: &Tensor, + v: &Tensor, + softmax_scale: f32, + causal: bool, +) -> Result { + candle_flash_attn::flash_attn(q, k, v, softmax_scale, causal) +} + +#[cfg(not(feature = "flash-attn"))] +fn flash_attn(_: &Tensor, _: &Tensor, _: &Tensor, _: f32, _: bool) -> Result { + unimplemented!("compile with '--features flash-attn'") } impl Attention { - pub fn new(query_dim: usize, heads: usize, dim_head: usize, vb: VarBuilder) -> Result { + pub fn new( + query_dim: usize, + heads: usize, + dim_head: usize, + use_flash_attn: bool, + vb: VarBuilder, + ) -> Result { let inner_dim = dim_head * heads; let scale = 1.0 / f64::sqrt(dim_head as f64); let to_q = linear(query_dim, inner_dim, vb.pp("to_q"))?; @@ -28,6 +51,7 @@ impl Attention { to_out, scale, heads, + use_flash_attn, }) } @@ -62,8 +86,28 @@ impl Attention { let key = self.head_to_batch_dim(&key)?; let value = self.head_to_batch_dim(&value)?; - let attn_prs = self.get_attention_scores(&query, &key)?; - let xs = attn_prs.matmul(&value)?; + let xs = if self.use_flash_attn { + let init_dtype = query.dtype(); + let q = query + .to_dtype(candle::DType::F16)? + .unsqueeze(0)? + .transpose(1, 2)?; + let k = key + .to_dtype(candle::DType::F16)? + .unsqueeze(0)? + .transpose(1, 2)?; + let v = value + .to_dtype(candle::DType::F16)? + .unsqueeze(0)? + .transpose(1, 2)?; + flash_attn(&q, &k, &v, self.scale as f32, false)? + .transpose(1, 2)? + .squeeze(0)? + .to_dtype(init_dtype)? + } else { + let attn_prs = self.get_attention_scores(&query, &key)?; + attn_prs.matmul(&value)? + }; let xs = self.batch_to_head_dim(&xs)?; self.to_out diff --git a/candle-transformers/src/models/wuerstchen/common.rs b/candle-transformers/src/models/wuerstchen/common.rs index 8416a1f1..c89ec919 100644 --- a/candle-transformers/src/models/wuerstchen/common.rs +++ b/candle-transformers/src/models/wuerstchen/common.rs @@ -174,10 +174,11 @@ impl AttnBlock { c_cond: usize, nhead: usize, self_attn: bool, + use_flash_attn: bool, vb: VarBuilder, ) -> Result { let norm = WLayerNorm::new(c)?; - let attention = Attention::new(c, nhead, c / nhead, vb.pp("attention"))?; + let attention = Attention::new(c, nhead, c / nhead, use_flash_attn, vb.pp("attention"))?; let kv_mapper_lin = candle_nn::linear(c_cond, c, vb.pp("kv_mapper.1"))?; Ok(Self { self_attn, diff --git a/candle-transformers/src/models/wuerstchen/diffnext.rs b/candle-transformers/src/models/wuerstchen/diffnext.rs index 501a2776..64a48c8a 100644 --- a/candle-transformers/src/models/wuerstchen/diffnext.rs +++ b/candle-transformers/src/models/wuerstchen/diffnext.rs @@ -88,6 +88,7 @@ pub struct WDiffNeXt { } impl WDiffNeXt { + #[allow(clippy::too_many_arguments)] pub fn new( c_in: usize, c_out: usize, @@ -95,6 +96,7 @@ impl WDiffNeXt { c_cond: usize, clip_embd: usize, patch_size: usize, + use_flash_attn: bool, vb: VarBuilder, ) -> Result { const C_HIDDEN: [usize; 4] = [320, 640, 1280, 1280]; @@ -169,8 +171,14 @@ impl WDiffNeXt { let attn_block = if i == 0 { None } else { - let attn_block = - AttnBlock::new(c_hidden, c_cond, NHEAD[i], true, vb.pp(layer_i))?; + let attn_block = AttnBlock::new( + c_hidden, + c_cond, + NHEAD[i], + true, + use_flash_attn, + vb.pp(layer_i), + )?; layer_i += 1; Some(attn_block) }; @@ -208,8 +216,14 @@ impl WDiffNeXt { let attn_block = if i == 0 { None } else { - let attn_block = - AttnBlock::new(c_hidden, c_cond, NHEAD[i], true, vb.pp(layer_i))?; + let attn_block = AttnBlock::new( + c_hidden, + c_cond, + NHEAD[i], + true, + use_flash_attn, + vb.pp(layer_i), + )?; layer_i += 1; Some(attn_block) }; diff --git a/candle-transformers/src/models/wuerstchen/prior.rs b/candle-transformers/src/models/wuerstchen/prior.rs index 168b70a6..97ccf0e2 100644 --- a/candle-transformers/src/models/wuerstchen/prior.rs +++ b/candle-transformers/src/models/wuerstchen/prior.rs @@ -21,6 +21,7 @@ pub struct WPrior { } impl WPrior { + #[allow(clippy::too_many_arguments)] pub fn new( c_in: usize, c: usize, @@ -28,6 +29,7 @@ impl WPrior { c_r: usize, depth: usize, nhead: usize, + use_flash_attn: bool, vb: VarBuilder, ) -> Result { let projection = candle_nn::conv2d(c_in, c, 1, Default::default(), vb.pp("projection"))?; @@ -44,6 +46,7 @@ impl WPrior { c, nhead, true, + use_flash_attn, vb.pp(format!("blocks.{}", 3 * index + 2)), )?; blocks.push(Block { From 9b24d89d2d637d72ff1bc52b9ea8bb7ccdd11c88 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 15:03:35 +0100 Subject: [PATCH 141/150] Tracing mode for T5. (#913) * Tracing mode for T5. * Tracing for the linear layer. --- candle-examples/examples/t5/main.rs | 13 ++++ candle-transformers/src/models/t5.rs | 90 +++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index f5972754..348e9a55 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -150,7 +150,20 @@ impl T5ModelBuilder { } fn main() -> Result<()> { + use tracing_chrome::ChromeLayerBuilder; + use tracing_subscriber::prelude::*; + let args = Args::parse(); + + let _guard = if args.tracing { + println!("tracing..."); + let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); + tracing_subscriber::registry().with(chrome_layer).init(); + Some(guard) + } else { + None + }; + let (builder, mut tokenizer) = T5ModelBuilder::load(&args)?; let device = &builder.device; let tokenizer = tokenizer diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index fd2720d3..efb2819b 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -1,11 +1,32 @@ // T5 Text Encoder // https://github.com/huggingface/transformers/blob/main/src/transformers/models/t5/modeling_t5.py -use candle::{DType, Device, Result, Tensor, D}; -use candle_nn::{embedding, linear_no_bias, Activation, Embedding, Linear, Module, VarBuilder}; +use candle::{DType, Device, Module, Result, Tensor, D}; +use candle_nn::{embedding, Activation, Embedding, VarBuilder}; use serde::Deserialize; use std::sync::Arc; +#[derive(Debug)] +struct Linear { + inner: candle_nn::Linear, + span: tracing::Span, +} + +impl Linear { + fn new(d1: usize, d2: usize, vb: VarBuilder) -> Result { + let inner = candle_nn::linear_no_bias(d1, d2, vb)?; + let span = tracing::span!(tracing::Level::TRACE, "linear"); + Ok(Self { inner, span }) + } +} + +impl Module for Linear { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + self.inner.forward(xs) + } +} + fn default_relative_attention_max_distance() -> usize { 128 } @@ -121,6 +142,7 @@ impl Config { struct T5LayerNorm { weight: Tensor, variance_epsilon: f64, + span: tracing::Span, } impl T5LayerNorm { @@ -129,10 +151,14 @@ impl T5LayerNorm { Ok(Self { weight, variance_epsilon: eps, + span: tracing::span!(tracing::Level::TRACE, "layer-norm"), }) } +} +impl Module for T5LayerNorm { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let dtype = xs.dtype(); let xs_f32 = xs.to_dtype(DType::F32)?; // variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True) @@ -149,20 +175,25 @@ struct T5DenseActDense { wi: Linear, wo: Linear, act: Activation, + span: tracing::Span, } impl T5DenseActDense { fn load(vb: VarBuilder, cfg: &Config) -> Result { - let wi = linear_no_bias(cfg.d_model, cfg.d_ff, vb.pp("wi"))?; - let wo = linear_no_bias(cfg.d_ff, cfg.d_model, vb.pp("wo"))?; + let wi = Linear::new(cfg.d_model, cfg.d_ff, vb.pp("wi"))?; + let wo = Linear::new(cfg.d_ff, cfg.d_model, vb.pp("wo"))?; Ok(Self { wi, wo, act: Activation::Relu, + span: tracing::span!(tracing::Level::TRACE, "dense-act-dense"), }) } +} +impl Module for T5DenseActDense { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let xs = self.wi.forward(xs)?; let xs = self.act.forward(&xs)?; let xs = self.wo.forward(&xs)?; @@ -176,22 +207,27 @@ struct T5DenseGatedActDense { wi_1: Linear, wo: Linear, act: Activation, + span: tracing::Span, } impl T5DenseGatedActDense { fn load(vb: VarBuilder, cfg: &Config) -> Result { - let wi_0 = linear_no_bias(cfg.d_model, cfg.d_ff, vb.pp("wi_0"))?; - let wi_1 = linear_no_bias(cfg.d_model, cfg.d_ff, vb.pp("wi_1"))?; - let wo = linear_no_bias(cfg.d_ff, cfg.d_model, vb.pp("wo"))?; + let wi_0 = Linear::new(cfg.d_model, cfg.d_ff, vb.pp("wi_0"))?; + let wi_1 = Linear::new(cfg.d_model, cfg.d_ff, vb.pp("wi_1"))?; + let wo = Linear::new(cfg.d_ff, cfg.d_model, vb.pp("wo"))?; Ok(Self { wi_0, wi_1, wo, act: Activation::NewGelu, + span: tracing::span!(tracing::Level::TRACE, "dense-gated-act-dense"), }) } +} +impl Module for T5DenseGatedActDense { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let hidden_gelu = self.act.forward(&self.wi_0.forward(xs)?)?; let hidden_linear = self.wi_1.forward(xs)?; let xs = hidden_gelu.broadcast_mul(&hidden_linear)?; @@ -205,6 +241,7 @@ struct T5LayerFF { dense_act: Option, gated_dense_act: Option, layer_norm: T5LayerNorm, + span: tracing::Span, } impl T5LayerFF { @@ -226,10 +263,14 @@ impl T5LayerFF { dense_act, gated_dense_act, layer_norm, + span: tracing::span!(tracing::Level::TRACE, "layer-ff"), }) } +} +impl Module for T5LayerFF { fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); let ys = self.layer_norm.forward(xs)?; let ys = match &self.dense_act { Some(dense_act) => dense_act.forward(&ys)?, @@ -254,6 +295,7 @@ struct T5Attention { inner_dim: usize, use_cache: bool, kv_cache: Option<(Tensor, Tensor)>, + span: tracing::Span, } impl T5Attention { @@ -264,10 +306,10 @@ impl T5Attention { cfg: &Config, ) -> Result { let inner_dim = cfg.num_heads * cfg.d_kv; - let q = linear_no_bias(cfg.d_model, inner_dim, vb.pp("q"))?; - let k = linear_no_bias(cfg.d_model, inner_dim, vb.pp("k"))?; - let v = linear_no_bias(cfg.d_model, inner_dim, vb.pp("v"))?; - let o = linear_no_bias(inner_dim, cfg.d_model, vb.pp("o"))?; + let q = Linear::new(cfg.d_model, inner_dim, vb.pp("q"))?; + let k = Linear::new(cfg.d_model, inner_dim, vb.pp("k"))?; + let v = Linear::new(cfg.d_model, inner_dim, vb.pp("v"))?; + let o = Linear::new(inner_dim, cfg.d_model, vb.pp("o"))?; let relative_attention_bias = if has_relative_attention_bias { let emb = embedding( cfg.relative_attention_num_buckets, @@ -291,6 +333,7 @@ impl T5Attention { inner_dim, use_cache: cfg.use_cache && decoder, kv_cache: None, + span: tracing::span!(tracing::Level::TRACE, "attention"), }) } @@ -303,6 +346,7 @@ impl T5Attention { ) -> Result<(Tensor, Option)> { // Performs Self-attention (if key_value_states is None) or attention // over source sentence (provided by key_value_states). + let _enter = self.span.enter(); let kv_input = match key_value_states { None => xs, Some(key_value_states) => key_value_states, @@ -419,6 +463,7 @@ impl T5Attention { struct T5LayerSelfAttention { self_attention: T5Attention, layer_norm: T5LayerNorm, + span: tracing::Span, } impl T5LayerSelfAttention { @@ -429,6 +474,7 @@ impl T5LayerSelfAttention { Ok(Self { self_attention, layer_norm, + span: tracing::span!(tracing::Level::TRACE, "self-attn"), }) } @@ -438,6 +484,7 @@ impl T5LayerSelfAttention { position_bias: Option<&Tensor>, mask: Option<&Tensor>, ) -> Result<(Tensor, Option)> { + let _enter = self.span.enter(); let normed_xs = self.layer_norm.forward(xs)?; let (ys, position_bias) = self.self_attention @@ -451,6 +498,7 @@ impl T5LayerSelfAttention { struct T5LayerCrossAttention { cross_attention: T5Attention, layer_norm: T5LayerNorm, + span: tracing::Span, } impl T5LayerCrossAttention { @@ -461,6 +509,7 @@ impl T5LayerCrossAttention { Ok(Self { cross_attention, layer_norm, + span: tracing::span!(tracing::Level::TRACE, "cross-attn"), }) } @@ -470,6 +519,7 @@ impl T5LayerCrossAttention { position_bias: Option<&Tensor>, key_value_states: &Tensor, ) -> Result<(Tensor, Option)> { + let _enter = self.span.enter(); let normed_hidden_states = self.layer_norm.forward(hidden_states)?; let (ys, position_bias) = self.cross_attention.forward( &normed_hidden_states, @@ -487,6 +537,7 @@ struct T5Block { self_attn: T5LayerSelfAttention, cross_attn: Option, ff: T5LayerFF, + span: tracing::Span, } impl T5Block { @@ -510,6 +561,7 @@ impl T5Block { self_attn, cross_attn, ff, + span: tracing::span!(tracing::Level::TRACE, "block"), }) } @@ -519,6 +571,7 @@ impl T5Block { position_bias: Option<&Tensor>, encoder_hidden_states: Option<&Tensor>, ) -> Result<(Tensor, Option)> { + let _enter = self.span.enter(); // TODO: Cache masks let mask = match self.cross_attn.is_some() { true => { @@ -550,6 +603,7 @@ struct T5Stack { block: Vec, shared: Arc, final_layer_norm: T5LayerNorm, + span: tracing::Span, } impl T5Stack { @@ -566,6 +620,7 @@ impl T5Stack { block, shared: shared.clone(), final_layer_norm, + span: tracing::span!(tracing::Level::TRACE, "stack"), }) } @@ -574,6 +629,7 @@ impl T5Stack { input_ids: &Tensor, encoder_hidden_states: Option<&Tensor>, ) -> Result { + let _enter = self.span.enter(); let input_embeds = self.shared.as_ref().forward(input_ids)?; let mut hidden_states = input_embeds; let mut position_bias = None; @@ -592,6 +648,7 @@ impl T5Stack { pub struct T5EncoderModel { encoder: T5Stack, device: Device, + span: tracing::Span, } impl T5EncoderModel { @@ -602,10 +659,12 @@ impl T5EncoderModel { Ok(Self { encoder, device: vb.device().clone(), + span: tracing::span!(tracing::Level::TRACE, "encoder"), }) } pub fn forward(&mut self, input_ids: &Tensor) -> Result { + let _enter = self.span.enter(); self.encoder.forward(input_ids, None) } @@ -623,6 +682,7 @@ pub struct T5ForConditionalGeneration { lm_head: Option, shared: Arc, device: Device, + span_decode: tracing::Span, } impl T5ForConditionalGeneration { @@ -648,11 +708,7 @@ impl T5ForConditionalGeneration { let lm_head = if tie_word_embeddings { None } else { - Some(linear_no_bias( - cfg.d_model, - cfg.vocab_size, - vb.pp("lm_head"), - )?) + Some(Linear::new(cfg.d_model, cfg.vocab_size, vb.pp("lm_head"))?) }; Ok(Self { @@ -663,6 +719,7 @@ impl T5ForConditionalGeneration { lm_head, shared, device: vb.device().clone(), + span_decode: tracing::span!(tracing::Level::TRACE, "decode"), }) } @@ -675,6 +732,7 @@ impl T5ForConditionalGeneration { decoder_input_ids: &Tensor, encoder_output: &Tensor, ) -> Result { + let _enter = self.span_decode.enter(); let decoder_output = self .decoder .forward(decoder_input_ids, Some(encoder_output))?; From 3a0d3e05df74c214f21fe8c2188424e475542569 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 16:37:51 +0100 Subject: [PATCH 142/150] Add more t5 tracing. (#914) * Add more t5 tracing. * Rever the sm change. --- candle-transformers/src/models/t5.rs | 40 ++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index efb2819b..ffa2764b 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -2,10 +2,35 @@ // https://github.com/huggingface/transformers/blob/main/src/transformers/models/t5/modeling_t5.py use candle::{DType, Device, Module, Result, Tensor, D}; -use candle_nn::{embedding, Activation, Embedding, VarBuilder}; +use candle_nn::{Activation, VarBuilder}; use serde::Deserialize; use std::sync::Arc; +#[derive(Debug)] +struct Embedding { + inner: candle_nn::Embedding, + span: tracing::Span, +} + +impl Embedding { + fn new(d1: usize, d2: usize, vb: VarBuilder) -> Result { + let inner = candle_nn::embedding(d1, d2, vb)?; + let span = tracing::span!(tracing::Level::TRACE, "embedding"); + Ok(Self { inner, span }) + } + + fn embeddings(&self) -> &Tensor { + self.inner.embeddings() + } +} + +impl Module for Embedding { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + self.inner.forward(xs) + } +} + #[derive(Debug)] struct Linear { inner: candle_nn::Linear, @@ -296,6 +321,7 @@ struct T5Attention { use_cache: bool, kv_cache: Option<(Tensor, Tensor)>, span: tracing::Span, + span_sm: tracing::Span, } impl T5Attention { @@ -311,7 +337,7 @@ impl T5Attention { let v = Linear::new(cfg.d_model, inner_dim, vb.pp("v"))?; let o = Linear::new(inner_dim, cfg.d_model, vb.pp("o"))?; let relative_attention_bias = if has_relative_attention_bias { - let emb = embedding( + let emb = Embedding::new( cfg.relative_attention_num_buckets, cfg.num_heads, vb.pp("relative_attention_bias"), @@ -334,6 +360,7 @@ impl T5Attention { use_cache: cfg.use_cache && decoder, kv_cache: None, span: tracing::span!(tracing::Level::TRACE, "attention"), + span_sm: tracing::span!(tracing::Level::TRACE, "attention-sm"), }) } @@ -449,7 +476,10 @@ impl T5Attention { }, }; - let attn_weights = candle_nn::ops::softmax(&scores, D::Minus1)?; + let attn_weights = { + let _enter = self.span_sm.enter(); + candle_nn::ops::softmax(&scores, D::Minus1)? + }; let attn_output = attn_weights.matmul(&v)?; let attn_output = attn_output .transpose(1, 2)? @@ -653,7 +683,7 @@ pub struct T5EncoderModel { impl T5EncoderModel { pub fn load(vb: VarBuilder, cfg: &Config) -> Result { - let shared = embedding(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; + let shared = Embedding::new(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; let shared = Arc::new(shared); let encoder = T5Stack::load(false, vb.pp("encoder"), &shared, cfg)?; Ok(Self { @@ -689,7 +719,7 @@ impl T5ForConditionalGeneration { pub fn load(vb: VarBuilder, cfg: &Config) -> Result { assert!(cfg.is_encoder_decoder); let d_model = cfg.d_model; - let shared = embedding(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; + let shared = Embedding::new(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; let shared = Arc::new(shared); let mut encoder_cfg = cfg.clone(); From ab1d40ea97b387f0dd05f77db37c840a4d624a08 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Wed, 20 Sep 2023 20:20:54 +0100 Subject: [PATCH 143/150] Add more t5 tracing. (#915) --- candle-transformers/src/models/t5.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index ffa2764b..2b71fcda 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -321,6 +321,8 @@ struct T5Attention { use_cache: bool, kv_cache: Option<(Tensor, Tensor)>, span: tracing::Span, + span_cache: tracing::Span, + span_mm: tracing::Span, span_sm: tracing::Span, } @@ -360,6 +362,8 @@ impl T5Attention { use_cache: cfg.use_cache && decoder, kv_cache: None, span: tracing::span!(tracing::Level::TRACE, "attention"), + span_cache: tracing::span!(tracing::Level::TRACE, "attention-cache"), + span_mm: tracing::span!(tracing::Level::TRACE, "attention-mm"), span_sm: tracing::span!(tracing::Level::TRACE, "attention-sm"), }) } @@ -397,6 +401,7 @@ impl T5Attention { .contiguous()?; if self.use_cache { + let _enter = self.span_cache.enter(); if let Some((kv_cache_k, kv_cache_v)) = &self.kv_cache { k = Tensor::cat(&[kv_cache_k, &k], 2)?.contiguous()?; v = Tensor::cat(&[kv_cache_v, &v], 2)?.contiguous()?; @@ -404,7 +409,10 @@ impl T5Attention { self.kv_cache = Some((k.clone(), v.clone())); }; // TODO: Use flash_attn. - let scores = q.matmul(&k.t()?)?; + let scores = { + let _enter = self.span_mm.enter(); + q.matmul(&k.t()?)? + }; let scores = match mask { None => scores, Some(mask) => masked_fill( @@ -713,6 +721,7 @@ pub struct T5ForConditionalGeneration { shared: Arc, device: Device, span_decode: tracing::Span, + span_decode_head: tracing::Span, } impl T5ForConditionalGeneration { @@ -750,6 +759,7 @@ impl T5ForConditionalGeneration { shared, device: vb.device().clone(), span_decode: tracing::span!(tracing::Level::TRACE, "decode"), + span_decode_head: tracing::span!(tracing::Level::TRACE, "decode-head"), }) } @@ -778,9 +788,12 @@ impl T5ForConditionalGeneration { .narrow(1, decoder_output.dim(1)? - 1, 1)? .squeeze(1)?) * scaling_factor)?; - let output = match self.lm_head { - None => sequence_output.matmul(&self.shared.embeddings().t()?)?, - Some(ref lm_head) => lm_head.forward(&sequence_output)?, + let output = { + let _enter = self.span_decode_head.enter(); + match self.lm_head { + None => sequence_output.matmul(&self.shared.embeddings().t()?)?, + Some(ref lm_head) => lm_head.forward(&sequence_output)?, + } }; // TODO: Rescale output before projecting on vocab? * (self.model_dim**-0.5) From 7b26e513f15a0c7cd55ccfe48525bda1079427ce Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 21 Sep 2023 06:19:10 +0100 Subject: [PATCH 144/150] Add the erf function. (#917) --- candle-core/src/backprop.rs | 1 + candle-core/src/op.rs | 36 +++++++++++++++++++++++++++++++ candle-core/src/tensor.rs | 1 + candle-core/tests/tensor_tests.rs | 7 ++++++ 4 files changed, 45 insertions(+) diff --git a/candle-core/src/backprop.rs b/candle-core/src/backprop.rs index 3e2ae1ed..a2548198 100644 --- a/candle-core/src/backprop.rs +++ b/candle-core/src/backprop.rs @@ -442,6 +442,7 @@ impl Tensor { *sum_grad = sum_grad.add(&arg_grad)? } Op::Unary(_, UnaryOp::Gelu) => Err(Error::BackwardNotSupported { op: "gelu" })?, + Op::Unary(_, UnaryOp::Erf) => Err(Error::BackwardNotSupported { op: "erf" })?, Op::Unary(_, UnaryOp::GeluErf) => { Err(Error::BackwardNotSupported { op: "gelu-erf" })? } diff --git a/candle-core/src/op.rs b/candle-core/src/op.rs index 26dc6609..4882a205 100644 --- a/candle-core/src/op.rs +++ b/candle-core/src/op.rs @@ -59,6 +59,7 @@ pub enum UnaryOp { Sqrt, Gelu, GeluErf, + Erf, Relu, Tanh, } @@ -327,6 +328,7 @@ pub(crate) struct Sqr; pub(crate) struct Sqrt; pub(crate) struct Gelu; pub(crate) struct GeluErf; +pub(crate) struct Erf; pub(crate) struct Relu; pub(crate) struct Tanh; @@ -623,6 +625,40 @@ impl UnaryOpT for Gelu { } } +impl UnaryOpT for Erf { + const NAME: &'static str = "erf"; + const KERNEL: &'static str = "uerf"; + const V: Self = Erf; + #[inline(always)] + fn bf16(v: bf16) -> bf16 { + bf16::from_f64(Self::f64(v.to_f64())) + } + #[inline(always)] + fn f16(v: f16) -> f16 { + f16::from_f64(Self::f64(v.to_f64())) + } + #[inline(always)] + fn f32(v: f32) -> f32 { + Self::f64(v as f64) as f32 + } + #[inline(always)] + fn f64(v: f64) -> f64 { + crate::cpu::erf::erf(v) + } + #[inline(always)] + fn u8(_: u8) -> u8 { + 0 + } + #[inline(always)] + fn u32(_: u32) -> u32 { + 0 + } + #[inline(always)] + fn i64(_: i64) -> i64 { + 0 + } +} + impl UnaryOpT for GeluErf { const NAME: &'static str = "gelu_erf"; const KERNEL: &'static str = "ugelu_erf"; diff --git a/candle-core/src/tensor.rs b/candle-core/src/tensor.rs index eafd7002..9dccf2b5 100644 --- a/candle-core/src/tensor.rs +++ b/candle-core/src/tensor.rs @@ -490,6 +490,7 @@ impl Tensor { unary_op!(sqrt, Sqrt); unary_op!(gelu, Gelu); unary_op!(gelu_erf, GeluErf); + unary_op!(erf, Erf); unary_op!(relu, Relu); /// Retrieves the single scalar value hold in the tensor. If the tensor contains multiple diff --git a/candle-core/tests/tensor_tests.rs b/candle-core/tests/tensor_tests.rs index 408f4c55..edd0bd79 100644 --- a/candle-core/tests/tensor_tests.rs +++ b/candle-core/tests/tensor_tests.rs @@ -61,6 +61,13 @@ fn unary_op(device: &Device) -> Result<()> { [2.6906, -0.0647, -0.1091, 1.7353, 2.7928] ] ); + assert_eq!( + test_utils::to_vec2_round(&tensor.erf()?, 4)?, + [ + [-1.0, 0.8427, 1.0, -0.1125, 0.5205], + [0.9999, -0.9891, -0.3079, 0.9891, 0.9999] + ] + ); Ok(()) } From c89b82b2d419bd2e99ffc64c90a2615e97d4ea66 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 21 Sep 2023 09:01:06 +0100 Subject: [PATCH 145/150] Add a clear cache function to the t5 model. (#919) --- candle-transformers/src/models/t5.rs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index 2b71fcda..94cf5233 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -495,6 +495,10 @@ impl T5Attention { let attn_output = self.o.forward(&attn_output)?; Ok((attn_output, position_bias)) } + + fn clear_kv_cache(&mut self) { + self.kv_cache = None + } } #[derive(Debug)] @@ -530,6 +534,10 @@ impl T5LayerSelfAttention { let ys = (xs + ys)?; Ok((ys, position_bias)) } + + fn clear_kv_cache(&mut self) { + self.self_attention.clear_kv_cache() + } } #[derive(Debug)] @@ -568,6 +576,10 @@ impl T5LayerCrossAttention { let ys = (hidden_states + ys)?; Ok((ys, position_bias)) } + + fn clear_kv_cache(&mut self) { + self.cross_attention.clear_kv_cache() + } } #[derive(Debug)] @@ -634,6 +646,11 @@ impl T5Block { // TODO: clamp for f16? Ok((xs, position_bias)) } + + fn clear_kv_cache(&mut self) { + self.self_attn.clear_kv_cache(); + self.cross_attn.iter_mut().for_each(|c| c.clear_kv_cache()); + } } #[derive(Debug)] @@ -680,6 +697,10 @@ impl T5Stack { } self.final_layer_norm.forward(&hidden_states) } + + fn clear_kv_cache(&mut self) { + self.block.iter_mut().for_each(|b| b.clear_kv_cache()) + } } #[derive(Debug)] @@ -709,6 +730,10 @@ impl T5EncoderModel { pub fn device(&self) -> &Device { &self.device } + + pub fn clear_kv_cache(&mut self) { + self.encoder.clear_kv_cache() + } } #[derive(Debug)] @@ -808,4 +833,9 @@ impl T5ForConditionalGeneration { pub fn device(&self) -> &Device { &self.device } + + pub fn clear_kv_cache(&mut self) { + self.encoder.clear_kv_cache(); + self.decoder.clear_kv_cache(); + } } From 2619c4307fe02db031d3a41cfbed91b12b97df31 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 21 Sep 2023 11:13:39 +0100 Subject: [PATCH 146/150] Add a quantized version of the t5 model. (#921) --- .gitignore | 1 + candle-core/src/quantized/mod.rs | 2 +- candle-transformers/src/models/mod.rs | 1 + .../src/models/quantized_t5.rs | 869 ++++++++++++++++++ candle-transformers/src/models/t5.rs | 2 +- 5 files changed, 873 insertions(+), 2 deletions(-) create mode 100644 candle-transformers/src/models/quantized_t5.rs diff --git a/.gitignore b/.gitignore index 2748d37e..d0a8c320 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ flamegraph.svg *.dylib *.so *.swp +*.swo trace-*.json candle-wasm-examples/*/build diff --git a/candle-core/src/quantized/mod.rs b/candle-core/src/quantized/mod.rs index 5c2bb2b2..f627f0f6 100644 --- a/candle-core/src/quantized/mod.rs +++ b/candle-core/src/quantized/mod.rs @@ -229,7 +229,7 @@ impl QTensor { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct QMatMul(std::sync::Arc); impl QMatMul { diff --git a/candle-transformers/src/models/mod.rs b/candle-transformers/src/models/mod.rs index a20254d9..d783a2c6 100644 --- a/candle-transformers/src/models/mod.rs +++ b/candle-transformers/src/models/mod.rs @@ -5,6 +5,7 @@ pub mod efficientnet; pub mod falcon; pub mod llama; pub mod quantized_llama; +pub mod quantized_t5; pub mod segment_anything; pub mod stable_diffusion; pub mod t5; diff --git a/candle-transformers/src/models/quantized_t5.rs b/candle-transformers/src/models/quantized_t5.rs new file mode 100644 index 00000000..c14500ba --- /dev/null +++ b/candle-transformers/src/models/quantized_t5.rs @@ -0,0 +1,869 @@ +// T5 Text Model, quantized version +// https://github.com/huggingface/transformers/blob/main/src/transformers/models/t5/modeling_t5.py + +use candle::quantized::QTensor; +use candle::{DType, Device, Module, Result, Shape, Tensor, D}; +use candle_nn::Activation; +use serde::Deserialize; +use std::sync::Arc; + +// VarBuilder specialized for QTensors +pub struct VarBuilder { + data: Arc>>, + path: Vec, + device: Device, +} + +impl VarBuilder { + fn pp(&self, s: S) -> Self { + let mut path = self.path.clone(); + path.push(s.to_string()); + Self { + data: self.data.clone(), + path, + device: self.device.clone(), + } + } + + fn path(&self, tensor_name: &str) -> String { + if self.path.is_empty() { + tensor_name.to_string() + } else { + [&self.path.join("."), tensor_name].join(".") + } + } + + fn get>(&self, s: S, name: &str) -> Result> { + let path = self.path(name); + match self.data.get(&path) { + None => { + candle::bail!("cannot find tensor {name}") + } + Some(qtensor) => { + let shape = s.into(); + if qtensor.shape() != &shape { + candle::bail!( + "shape mismatch for {name}, got {:?}, expected {shape:?}", + qtensor.shape() + ) + } + Ok(qtensor.clone()) + } + } + } +} + +#[derive(Debug)] +struct Embedding { + inner: candle_nn::Embedding, + span: tracing::Span, +} + +impl Embedding { + fn new(d1: usize, d2: usize, vb: VarBuilder) -> Result { + let embeddings = vb.get((d1, d2), "weight")?.dequantize(&vb.device)?; + let inner = candle_nn::Embedding::new(embeddings, d2); + let span = tracing::span!(tracing::Level::TRACE, "embedding"); + Ok(Self { inner, span }) + } + + fn embeddings(&self) -> &Tensor { + self.inner.embeddings() + } +} + +impl Module for Embedding { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + self.inner.forward(xs) + } +} + +// QMatMul wrapper adding some tracing. +struct QMatMul { + inner: candle::quantized::QMatMul, + span: tracing::Span, +} + +impl QMatMul { + fn new(out_dim: usize, in_dim: usize, vb: VarBuilder) -> Result { + let ws = vb.get((out_dim, in_dim), "weight")?; + let inner = candle::quantized::QMatMul::from_arc(ws); + let span = tracing::span!(tracing::Level::TRACE, "qmatmul"); + Ok(Self { inner, span }) + } +} + +impl Module for QMatMul { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + self.inner.forward(xs) + } +} + +impl std::fmt::Debug for QMatMul { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "QMatMul") + } +} + +fn default_relative_attention_max_distance() -> usize { + 128 +} + +fn default_is_decoder() -> bool { + false +} + +fn default_use_cache() -> bool { + true +} + +fn default_tie_word_embeddings() -> bool { + true +} + +fn get_mask(size: usize, device: &Device) -> Result { + let mask: Vec<_> = (0..size) + .flat_map(|i| (0..size).map(move |j| u8::from(j > i))) + .collect(); + Tensor::from_slice(&mask, (size, size), device) +} + +fn masked_fill(on_false: &Tensor, mask: &Tensor, on_true: f32) -> Result { + let shape = mask.shape(); + let on_true = Tensor::new(on_true, on_false.device())?.broadcast_as(shape.dims())?; + let m = mask.where_cond(&on_true, on_false)?; + Ok(m) +} + +#[derive(Debug, Clone, PartialEq, Deserialize)] +pub struct Config { + vocab_size: usize, + d_model: usize, + d_kv: usize, + d_ff: usize, + num_layers: usize, + num_decoder_layers: Option, + num_heads: usize, + relative_attention_num_buckets: usize, + #[serde(default = "default_relative_attention_max_distance")] + relative_attention_max_distance: usize, + dropout_rate: f64, + layer_norm_epsilon: f64, + initializer_factor: f64, + #[serde(default)] + feed_forward_proj: Activation, + #[serde(default = "default_tie_word_embeddings")] + tie_word_embeddings: bool, + #[serde(default = "default_is_decoder")] + is_decoder: bool, + is_encoder_decoder: bool, + #[serde(default = "default_use_cache")] + pub use_cache: bool, + pub pad_token_id: usize, + pub eos_token_id: usize, +} + +impl Default for Config { + fn default() -> Self { + Self { + vocab_size: 32128, + d_model: 512, + d_kv: 64, + d_ff: 2048, + num_layers: 6, + num_decoder_layers: None, + num_heads: 8, + relative_attention_num_buckets: 32, + relative_attention_max_distance: 128, + dropout_rate: 0.1, + layer_norm_epsilon: 1e-6, + initializer_factor: 1.0, + feed_forward_proj: Activation::Relu, + tie_word_embeddings: true, + is_decoder: false, + is_encoder_decoder: true, + use_cache: true, + pad_token_id: 0, + eos_token_id: 1, + } + } +} + +#[derive(Debug)] +struct T5LayerNorm { + weight: Tensor, + variance_epsilon: f64, + span: tracing::Span, +} + +impl T5LayerNorm { + fn load(h: usize, eps: f64, vb: VarBuilder) -> Result { + let weight = vb.get(h, "weight")?.dequantize(&vb.device)?; + Ok(Self { + weight, + variance_epsilon: eps, + span: tracing::span!(tracing::Level::TRACE, "layer-norm"), + }) + } +} + +impl Module for T5LayerNorm { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + let dtype = xs.dtype(); + let xs_f32 = xs.to_dtype(DType::F32)?; + // variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True) + let variance = xs_f32.sqr()?.mean_keepdim(D::Minus1)?; + let xs = xs.broadcast_div(&(variance + self.variance_epsilon)?.sqrt()?)?; + let xs = xs.to_dtype(dtype)?; + let xs = xs.broadcast_mul(&self.weight)?; + Ok(xs) + } +} + +#[derive(Debug)] +struct T5DenseActDense { + wi: QMatMul, + wo: QMatMul, + act: Activation, + span: tracing::Span, +} + +impl T5DenseActDense { + fn load(vb: VarBuilder, cfg: &Config) -> Result { + let wi = QMatMul::new(cfg.d_model, cfg.d_ff, vb.pp("wi"))?; + let wo = QMatMul::new(cfg.d_ff, cfg.d_model, vb.pp("wo"))?; + Ok(Self { + wi, + wo, + act: Activation::Relu, + span: tracing::span!(tracing::Level::TRACE, "dense-act-dense"), + }) + } +} + +impl Module for T5DenseActDense { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + let xs = self.wi.forward(xs)?; + let xs = self.act.forward(&xs)?; + let xs = self.wo.forward(&xs)?; + Ok(xs) + } +} + +#[derive(Debug)] +struct T5DenseGatedActDense { + wi_0: QMatMul, + wi_1: QMatMul, + wo: QMatMul, + act: Activation, + span: tracing::Span, +} + +impl T5DenseGatedActDense { + fn load(vb: VarBuilder, cfg: &Config) -> Result { + let wi_0 = QMatMul::new(cfg.d_model, cfg.d_ff, vb.pp("wi_0"))?; + let wi_1 = QMatMul::new(cfg.d_model, cfg.d_ff, vb.pp("wi_1"))?; + let wo = QMatMul::new(cfg.d_ff, cfg.d_model, vb.pp("wo"))?; + Ok(Self { + wi_0, + wi_1, + wo, + act: Activation::NewGelu, + span: tracing::span!(tracing::Level::TRACE, "dense-gated-act-dense"), + }) + } +} + +impl Module for T5DenseGatedActDense { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + let hidden_gelu = self.act.forward(&self.wi_0.forward(xs)?)?; + let hidden_linear = self.wi_1.forward(xs)?; + let xs = hidden_gelu.broadcast_mul(&hidden_linear)?; + let xs = self.wo.forward(&xs)?; + Ok(xs) + } +} + +#[derive(Debug)] +struct T5LayerFF { + dense_act: Option, + gated_dense_act: Option, + layer_norm: T5LayerNorm, + span: tracing::Span, +} + +impl T5LayerFF { + fn load(vb: VarBuilder, cfg: &Config) -> Result { + let layer_norm = + T5LayerNorm::load(cfg.d_model, cfg.layer_norm_epsilon, vb.pp("layer_norm"))?; + let (dense_act, gated_dense_act) = if cfg.feed_forward_proj == Activation::NewGelu { + ( + None, + Some(T5DenseGatedActDense::load(vb.pp("DenseReluDense"), cfg)?), + ) + } else { + ( + Some(T5DenseActDense::load(vb.pp("DenseReluDense"), cfg)?), + None, + ) + }; + Ok(Self { + dense_act, + gated_dense_act, + layer_norm, + span: tracing::span!(tracing::Level::TRACE, "layer-ff"), + }) + } +} + +impl Module for T5LayerFF { + fn forward(&self, xs: &Tensor) -> Result { + let _enter = self.span.enter(); + let ys = self.layer_norm.forward(xs)?; + let ys = match &self.dense_act { + Some(dense_act) => dense_act.forward(&ys)?, + None => self.gated_dense_act.as_ref().unwrap().forward(&ys)?, + }; + let xs = (xs + ys)?; + Ok(xs) + } +} + +#[derive(Debug)] +struct T5Attention { + q: QMatMul, + k: QMatMul, + v: QMatMul, + o: QMatMul, + n_heads: usize, + d_kv: usize, + relative_attention_bias: Option, + relative_attention_num_buckets: usize, + relative_attention_max_distance: usize, + inner_dim: usize, + use_cache: bool, + kv_cache: Option<(Tensor, Tensor)>, + span: tracing::Span, + span_cache: tracing::Span, + span_mm: tracing::Span, + span_sm: tracing::Span, +} + +impl T5Attention { + fn load( + has_relative_attention_bias: bool, + decoder: bool, + vb: VarBuilder, + cfg: &Config, + ) -> Result { + let inner_dim = cfg.num_heads * cfg.d_kv; + let q = QMatMul::new(cfg.d_model, inner_dim, vb.pp("q"))?; + let k = QMatMul::new(cfg.d_model, inner_dim, vb.pp("k"))?; + let v = QMatMul::new(cfg.d_model, inner_dim, vb.pp("v"))?; + let o = QMatMul::new(inner_dim, cfg.d_model, vb.pp("o"))?; + let relative_attention_bias = if has_relative_attention_bias { + let emb = Embedding::new( + cfg.relative_attention_num_buckets, + cfg.num_heads, + vb.pp("relative_attention_bias"), + )?; + Some(emb) + } else { + None + }; + Ok(Self { + q, + k, + v, + o, + n_heads: cfg.num_heads, + d_kv: cfg.d_kv, + relative_attention_bias, + relative_attention_num_buckets: cfg.relative_attention_num_buckets, + relative_attention_max_distance: cfg.relative_attention_max_distance, + inner_dim, + use_cache: cfg.use_cache && decoder, + kv_cache: None, + span: tracing::span!(tracing::Level::TRACE, "attention"), + span_cache: tracing::span!(tracing::Level::TRACE, "attention-cache"), + span_mm: tracing::span!(tracing::Level::TRACE, "attention-mm"), + span_sm: tracing::span!(tracing::Level::TRACE, "attention-sm"), + }) + } + + fn forward( + &mut self, + xs: &Tensor, + position_bias: Option<&Tensor>, + key_value_states: Option<&Tensor>, + mask: Option<&Tensor>, + ) -> Result<(Tensor, Option)> { + // Performs Self-attention (if key_value_states is None) or attention + // over source sentence (provided by key_value_states). + let _enter = self.span.enter(); + let kv_input = match key_value_states { + None => xs, + Some(key_value_states) => key_value_states, + }; + let (b_sz, q_len) = (xs.dim(0)?, xs.dim(1)?); + let kv_len = kv_input.dim(1)?; + let q = self.q.forward(xs)?; + let k = self.k.forward(kv_input)?; + let v = self.v.forward(kv_input)?; + let q = q + .reshape((b_sz, q_len, self.n_heads, self.d_kv))? + .transpose(1, 2)? + .contiguous()?; + let mut k = k + .reshape((b_sz, kv_len, self.n_heads, self.d_kv))? + .transpose(1, 2)? + .contiguous()?; + let mut v = v + .reshape((b_sz, kv_len, self.n_heads, self.d_kv))? + .transpose(1, 2)? + .contiguous()?; + + if self.use_cache { + let _enter = self.span_cache.enter(); + if let Some((kv_cache_k, kv_cache_v)) = &self.kv_cache { + k = Tensor::cat(&[kv_cache_k, &k], 2)?.contiguous()?; + v = Tensor::cat(&[kv_cache_v, &v], 2)?.contiguous()?; + }; + self.kv_cache = Some((k.clone(), v.clone())); + }; + // TODO: Use flash_attn. + let scores = { + let _enter = self.span_mm.enter(); + q.matmul(&k.t()?)? + }; + let scores = match mask { + None => scores, + Some(mask) => masked_fill( + &scores, + &mask + .unsqueeze(0)? + .unsqueeze(0)? + .repeat((b_sz, self.n_heads))?, + f32::NEG_INFINITY, + )?, + }; + + let (scores, position_bias) = match position_bias { + Some(position_bias) => ( + scores.broadcast_add(position_bias)?, + Some(position_bias.clone()), + ), + None => match &self.relative_attention_bias { + None => (scores, None), + Some(relative_attention_bias) => { + // This only handles the bidirectional case. + let kv_len = k.dim(2)?; + let (q_start, q_end) = match self.use_cache { + true => ((kv_len - q_len) as u32, kv_len as u32), + false => (0_u32, kv_len as u32), + }; + let num_buckets = self.relative_attention_num_buckets as u32 / 2; + let max_exact = num_buckets / 2; + let relative_position = (q_start..q_end) + .map(|i| { + (0..kv_len as u32) + .map(|j| { + if i < j { + if j - i < max_exact { + j - i + num_buckets + } else { + let b = f32::log( + (j - i) as f32 / max_exact as f32, + self.relative_attention_max_distance as f32 + / max_exact as f32, + ) * (num_buckets - max_exact) as f32; + u32::min( + max_exact + num_buckets + b as u32, + self.relative_attention_num_buckets as u32 - 1, + ) + } + } else if i - j < max_exact { + i - j + } else { + let b = f32::log( + (i - j) as f32 / max_exact as f32, + self.relative_attention_max_distance as f32 + / max_exact as f32, + ) * (num_buckets - max_exact) as f32; + max_exact + b as u32 + } + }) + .collect::>() + }) + .collect::>>(); + let relative_buckets = Tensor::new(relative_position, q.device())?; + let position_bias = relative_attention_bias + .forward(&relative_buckets)? + .permute((2, 0, 1))? + .unsqueeze(0)?; + (scores.broadcast_add(&position_bias)?, Some(position_bias)) + // TODO: position_bias_masked? + } + }, + }; + + let attn_weights = { + let _enter = self.span_sm.enter(); + candle_nn::ops::softmax(&scores, D::Minus1)? + }; + let attn_output = attn_weights.matmul(&v)?; + let attn_output = attn_output + .transpose(1, 2)? + .reshape((b_sz, q_len, self.inner_dim))?; + let attn_output = self.o.forward(&attn_output)?; + Ok((attn_output, position_bias)) + } + + fn clear_kv_cache(&mut self) { + self.kv_cache = None + } +} + +#[derive(Debug)] +struct T5LayerSelfAttention { + self_attention: T5Attention, + layer_norm: T5LayerNorm, + span: tracing::Span, +} + +impl T5LayerSelfAttention { + fn load(h: bool, d: bool, vb: VarBuilder, cfg: &Config) -> Result { + let self_attention = T5Attention::load(h, d, vb.pp("SelfAttention"), cfg)?; + let layer_norm = + T5LayerNorm::load(cfg.d_model, cfg.layer_norm_epsilon, vb.pp("layer_norm"))?; + Ok(Self { + self_attention, + layer_norm, + span: tracing::span!(tracing::Level::TRACE, "self-attn"), + }) + } + + fn forward( + &mut self, + xs: &Tensor, + position_bias: Option<&Tensor>, + mask: Option<&Tensor>, + ) -> Result<(Tensor, Option)> { + let _enter = self.span.enter(); + let normed_xs = self.layer_norm.forward(xs)?; + let (ys, position_bias) = + self.self_attention + .forward(&normed_xs, position_bias, None, mask)?; + let ys = (xs + ys)?; + Ok((ys, position_bias)) + } + + fn clear_kv_cache(&mut self) { + self.self_attention.clear_kv_cache() + } +} + +#[derive(Debug)] +struct T5LayerCrossAttention { + cross_attention: T5Attention, + layer_norm: T5LayerNorm, + span: tracing::Span, +} + +impl T5LayerCrossAttention { + fn load(decoder: bool, vb: VarBuilder, cfg: &Config) -> Result { + let cross_attention = T5Attention::load(false, decoder, vb.pp("EncDecAttention"), cfg)?; + let layer_norm = + T5LayerNorm::load(cfg.d_model, cfg.layer_norm_epsilon, vb.pp("layer_norm"))?; + Ok(Self { + cross_attention, + layer_norm, + span: tracing::span!(tracing::Level::TRACE, "cross-attn"), + }) + } + + fn forward( + &mut self, + hidden_states: &Tensor, + position_bias: Option<&Tensor>, + key_value_states: &Tensor, + ) -> Result<(Tensor, Option)> { + let _enter = self.span.enter(); + let normed_hidden_states = self.layer_norm.forward(hidden_states)?; + let (ys, position_bias) = self.cross_attention.forward( + &normed_hidden_states, + position_bias, + Some(key_value_states), + None, + )?; + let ys = (hidden_states + ys)?; + Ok((ys, position_bias)) + } + + fn clear_kv_cache(&mut self) { + self.cross_attention.clear_kv_cache() + } +} + +#[derive(Debug)] +struct T5Block { + self_attn: T5LayerSelfAttention, + cross_attn: Option, + ff: T5LayerFF, + span: tracing::Span, +} + +impl T5Block { + fn load( + has_relative_attention_bias: bool, + decoder: bool, + vb: VarBuilder, + cfg: &Config, + ) -> Result { + let vb = vb.pp("layer"); + let self_attn = + T5LayerSelfAttention::load(has_relative_attention_bias, decoder, vb.pp("0"), cfg)?; + let cross_attn = if cfg.is_decoder { + Some(T5LayerCrossAttention::load(decoder, vb.pp("1"), cfg)?) + } else { + None + }; + let ff_i = if cross_attn.is_some() { 2 } else { 1 }; + let ff = T5LayerFF::load(vb.pp(ff_i), cfg)?; + Ok(Self { + self_attn, + cross_attn, + ff, + span: tracing::span!(tracing::Level::TRACE, "block"), + }) + } + + fn forward( + &mut self, + xs: &Tensor, + position_bias: Option<&Tensor>, + encoder_hidden_states: Option<&Tensor>, + ) -> Result<(Tensor, Option)> { + let _enter = self.span.enter(); + // TODO: Cache masks + let mask = match self.cross_attn.is_some() { + true => { + let mask_len = xs.dim(1)?; + // If the input seq length is 1, no need for a mask, this is also helpful to avoid shape + // issues when using the KV cache in the decoder. + if mask_len <= 1 { + None + } else { + Some(get_mask(mask_len, xs.device())?) + } + } + false => None, + }; + let (mut xs, position_bias) = self.self_attn.forward(xs, position_bias, mask.as_ref())?; + // TODO: clamp for f16? + if let Some(cross_attn) = &mut self.cross_attn { + (xs, _) = cross_attn.forward(&xs, None, encoder_hidden_states.unwrap())?; + // TODO: clamp for f16? + } + let xs = self.ff.forward(&xs)?; + // TODO: clamp for f16? + Ok((xs, position_bias)) + } + + fn clear_kv_cache(&mut self) { + self.self_attn.clear_kv_cache(); + self.cross_attn.iter_mut().for_each(|c| c.clear_kv_cache()); + } +} + +#[derive(Debug)] +struct T5Stack { + block: Vec, + shared: Arc, + final_layer_norm: T5LayerNorm, + span: tracing::Span, +} + +impl T5Stack { + fn load(decoder: bool, vb: VarBuilder, shared: &Arc, cfg: &Config) -> Result { + let block = (0..cfg.num_layers) + .map(|i| T5Block::load(i == 0, decoder, vb.pp(format!("block.{i}")), cfg)) + .collect::>>()?; + let final_layer_norm = T5LayerNorm::load( + cfg.d_model, + cfg.layer_norm_epsilon, + vb.pp("final_layer_norm"), + )?; + Ok(Self { + block, + shared: shared.clone(), + final_layer_norm, + span: tracing::span!(tracing::Level::TRACE, "stack"), + }) + } + + fn forward( + &mut self, + input_ids: &Tensor, + encoder_hidden_states: Option<&Tensor>, + ) -> Result { + let _enter = self.span.enter(); + let input_embeds = self.shared.as_ref().forward(input_ids)?; + let mut hidden_states = input_embeds; + let mut position_bias = None; + for block in self.block.iter_mut() { + (hidden_states, position_bias) = block.forward( + &hidden_states, + position_bias.as_ref(), + encoder_hidden_states, + )? + } + self.final_layer_norm.forward(&hidden_states) + } + + fn clear_kv_cache(&mut self) { + self.block.iter_mut().for_each(|b| b.clear_kv_cache()) + } +} + +#[derive(Debug)] +pub struct T5EncoderModel { + encoder: T5Stack, + device: Device, + span: tracing::Span, +} + +impl T5EncoderModel { + pub fn load(vb: VarBuilder, cfg: &Config) -> Result { + let shared = Embedding::new(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; + let shared = Arc::new(shared); + let encoder = T5Stack::load(false, vb.pp("encoder"), &shared, cfg)?; + Ok(Self { + encoder, + device: vb.device.clone(), + span: tracing::span!(tracing::Level::TRACE, "encoder"), + }) + } + + pub fn forward(&mut self, input_ids: &Tensor) -> Result { + let _enter = self.span.enter(); + self.encoder.forward(input_ids, None) + } + + pub fn device(&self) -> &Device { + &self.device + } + + pub fn clear_kv_cache(&mut self) { + self.encoder.clear_kv_cache() + } +} + +#[derive(Debug)] +pub struct T5ForConditionalGeneration { + encoder: T5Stack, + decoder: T5Stack, + d_model: usize, + tie_word_embeddings: bool, + lm_head: Option, + shared: Arc, + device: Device, + span_decode: tracing::Span, + span_decode_head: tracing::Span, +} + +impl T5ForConditionalGeneration { + pub fn load(vb: VarBuilder, cfg: &Config) -> Result { + assert!(cfg.is_encoder_decoder); + let d_model = cfg.d_model; + let shared = Embedding::new(cfg.vocab_size, cfg.d_model, vb.pp("shared"))?; + let shared = Arc::new(shared); + + let mut encoder_cfg = cfg.clone(); + encoder_cfg.is_decoder = false; + encoder_cfg.use_cache = false; + encoder_cfg.is_encoder_decoder = false; + let encoder = T5Stack::load(false, vb.pp("encoder"), &shared, &encoder_cfg)?; + + let mut decoder_cfg = cfg.clone(); + decoder_cfg.is_decoder = true; + decoder_cfg.is_encoder_decoder = false; + decoder_cfg.num_layers = cfg.num_decoder_layers.unwrap_or(cfg.num_layers); + let decoder = T5Stack::load(true, vb.pp("decoder"), &shared, &decoder_cfg)?; + + let tie_word_embeddings = cfg.tie_word_embeddings; + let lm_head = if tie_word_embeddings { + None + } else { + Some(QMatMul::new(cfg.d_model, cfg.vocab_size, vb.pp("lm_head"))?) + }; + + Ok(Self { + encoder, + decoder, + d_model, + tie_word_embeddings, + lm_head, + shared, + device: vb.device.clone(), + span_decode: tracing::span!(tracing::Level::TRACE, "decode"), + span_decode_head: tracing::span!(tracing::Level::TRACE, "decode-head"), + }) + } + + pub fn encode(&mut self, input_ids: &Tensor) -> Result { + self.encoder.forward(input_ids, None) + } + + pub fn decode( + &mut self, + decoder_input_ids: &Tensor, + encoder_output: &Tensor, + ) -> Result { + let _enter = self.span_decode.enter(); + let decoder_output = self + .decoder + .forward(decoder_input_ids, Some(encoder_output))?; + + let scaling_factor = if self.tie_word_embeddings { + // Rescale output before projecting on vocab + // See https://github.com/tensorflow/mesh/blob/fa19d69eafc9a482aff0b59ddd96b025c0cb207d/mesh_tensorflow/transformer/transformer.py#L586 + (self.d_model as f64).sqrt() + } else { + 1.0 + }; + let sequence_output = ((decoder_output + .narrow(1, decoder_output.dim(1)? - 1, 1)? + .squeeze(1)?) + * scaling_factor)?; + let output = { + let _enter = self.span_decode_head.enter(); + match self.lm_head { + None => sequence_output.matmul(&self.shared.embeddings().t()?)?, + Some(ref lm_head) => lm_head.forward(&sequence_output)?, + } + }; + + // TODO: Rescale output before projecting on vocab? * (self.model_dim**-0.5) + Ok(output) + } + + pub fn forward(&mut self, input_ids: &Tensor, decoder_input_ids: &Tensor) -> Result { + let encoder_output = self.encode(input_ids)?; + self.decode(decoder_input_ids, &encoder_output) + } + + pub fn device(&self) -> &Device { + &self.device + } + + pub fn clear_kv_cache(&mut self) { + self.encoder.clear_kv_cache(); + self.decoder.clear_kv_cache(); + } +} diff --git a/candle-transformers/src/models/t5.rs b/candle-transformers/src/models/t5.rs index 94cf5233..539ae89b 100644 --- a/candle-transformers/src/models/t5.rs +++ b/candle-transformers/src/models/t5.rs @@ -1,4 +1,4 @@ -// T5 Text Encoder +// T5 Text Model // https://github.com/huggingface/transformers/blob/main/src/transformers/models/t5/modeling_t5.py use candle::{DType, Device, Module, Result, Tensor, D}; From 3b557765e8e1641d1289d33b177938abe10d24d2 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 21 Sep 2023 12:33:15 +0100 Subject: [PATCH 147/150] T5 quantized example (#922) * Load gguf files for the quantized t5. * Add the quantized t5 example. * Allow for loading local files. * Add some support for quantizing safetensor files. * Transpose before quantizing. * Quantized t5. * Retrieve the weights from the hub. --- candle-core/examples/tensor-tools.rs | 53 +++++ .../examples/quantized-t5/README.md | 17 ++ candle-examples/examples/quantized-t5/main.rs | 186 ++++++++++++++++++ .../src/models/quantized_t5.rs | 17 +- 4 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 candle-examples/examples/quantized-t5/README.md create mode 100644 candle-examples/examples/quantized-t5/main.rs diff --git a/candle-core/examples/tensor-tools.rs b/candle-core/examples/tensor-tools.rs index 2bc1fa2e..c3459004 100644 --- a/candle-core/examples/tensor-tools.rs +++ b/candle-core/examples/tensor-tools.rs @@ -218,12 +218,65 @@ fn run_ls(file: &std::path::PathBuf, format: Option, verbose: bool) -> R Ok(()) } +fn run_quantize_safetensors( + in_file: std::path::PathBuf, + out_file: std::path::PathBuf, + q: Quantization, +) -> Result<()> { + let mut out_file = std::fs::File::create(out_file)?; + let tensors = candle_core::safetensors::load(in_file, &Device::Cpu)?; + println!("tensors: {}", tensors.len()); + + let quantize_fn = match q { + Quantization::Q4_0 => QTensor::quantize::, + Quantization::Q4_1 => QTensor::quantize::, + Quantization::Q5_0 => QTensor::quantize::, + Quantization::Q5_1 => QTensor::quantize::, + Quantization::Q8_0 => QTensor::quantize::, + Quantization::Q8_1 => QTensor::quantize::, + Quantization::Q2k => QTensor::quantize::, + Quantization::Q3k => QTensor::quantize::, + Quantization::Q4k => QTensor::quantize::, + Quantization::Q5k => QTensor::quantize::, + Quantization::Q6k => QTensor::quantize::, + Quantization::Q8k => QTensor::quantize::, + Quantization::F16 => QTensor::quantize::, + Quantization::F32 => QTensor::quantize::, + }; + + let qtensors = tensors + .into_par_iter() + .map(|(name, tensor)| { + println!(" quantizing {name} {tensor:?}"); + let should_quantize = tensor.rank() == 2 && tensor.dim(0)? % 256 == 0; + let tensor = if should_quantize { + quantize_fn(&tensor)? + } else { + QTensor::quantize::(&tensor)? + }; + Ok((name, tensor)) + }) + .collect::>>()?; + let qtensors = qtensors + .iter() + .map(|(k, v)| (k.as_str(), v)) + .collect::>(); + gguf_file::write(&mut out_file, &[], &qtensors)?; + Ok(()) +} + fn run_quantize( in_file: std::path::PathBuf, out_file: std::path::PathBuf, q: Quantization, qmode: QuantizationMode, ) -> Result<()> { + if let Some(extension) = in_file.extension() { + if extension == "safetensors" { + return run_quantize_safetensors(in_file, out_file, q); + } + } + // Open the out file early so as to fail directly on missing directories etc. let mut out_file = std::fs::File::create(out_file)?; let mut in_ = std::fs::File::open(&in_file)?; diff --git a/candle-examples/examples/quantized-t5/README.md b/candle-examples/examples/quantized-t5/README.md new file mode 100644 index 00000000..1f6b99eb --- /dev/null +++ b/candle-examples/examples/quantized-t5/README.md @@ -0,0 +1,17 @@ +# candle-quantized-t5 + +This example uses a quantized version of the t5 model. + +```bash +$ cargo run --example quantized-t5 --release -- --prompt "translate to German: A beautiful candle." +... + Eine schöne Kerze. +``` + +The weight file is automatically retrieved from the hub. It is also possible to +generate quantized weight files from the original safetensors file by using the +`tensor-tools` command line utility via: + +```bash +cargo run --example tensor-tools --release -- quantize --quantization q6k PATH/TO/T5/model.safetensors /tmp/model.gguf +``` diff --git a/candle-examples/examples/quantized-t5/main.rs b/candle-examples/examples/quantized-t5/main.rs new file mode 100644 index 00000000..86d3762e --- /dev/null +++ b/candle-examples/examples/quantized-t5/main.rs @@ -0,0 +1,186 @@ +#[cfg(feature = "mkl")] +extern crate intel_mkl_src; + +#[cfg(feature = "accelerate")] +extern crate accelerate_src; +use std::io::Write; +use std::path::PathBuf; + +use candle_transformers::models::quantized_t5 as t5; + +use anyhow::{Error as E, Result}; +use candle::{Device, Tensor}; +use candle_transformers::generation::LogitsProcessor; +use clap::Parser; +use hf_hub::{api::sync::Api, Repo, RepoType}; +use tokenizers::Tokenizer; + +#[derive(Parser, Debug, Clone)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Enable tracing (generates a trace-timestamp.json file). + #[arg(long)] + tracing: bool, + + /// The model repository to use on the HuggingFace hub. + #[arg(long)] + model_id: Option, + + #[arg(long)] + revision: Option, + + #[arg(long)] + weight_file: Option, + + // Enable/disable decoding. + #[arg(long, default_value = "false")] + disable_cache: bool, + + /// Use this prompt, otherwise compute sentence similarities. + #[arg(long)] + prompt: String, + + /// The temperature used to generate samples. + #[arg(long, default_value_t = 0.8)] + temperature: f64, + + /// Nucleus sampling probability cutoff. + #[arg(long)] + top_p: Option, + + /// Penalty to be applied for repeating tokens, 1. means no penalty. + #[arg(long, default_value_t = 1.1)] + repeat_penalty: f32, + + /// The context size to consider for the repeat penalty. + #[arg(long, default_value_t = 64)] + repeat_last_n: usize, +} + +struct T5ModelBuilder { + device: Device, + config: t5::Config, + weights_filename: PathBuf, +} + +impl T5ModelBuilder { + pub fn load(args: &Args) -> Result<(Self, Tokenizer)> { + let device = Device::Cpu; + let default_model = "lmz/candle-quantized-t5".to_string(); + let (model_id, revision) = match (args.model_id.to_owned(), args.revision.to_owned()) { + (Some(model_id), Some(revision)) => (model_id, revision), + (Some(model_id), None) => (model_id, "main".to_string()), + (None, Some(revision)) => (default_model, revision), + (None, None) => (default_model, "main".to_string()), + }; + + let repo = Repo::with_revision(model_id, RepoType::Model, revision); + let api = Api::new()?; + let api = api.repo(repo); + let config_filename = api.get("config.json")?; + let tokenizer_filename = api.get("tokenizer.json")?; + let weights_filename = match &args.weight_file { + Some(filename) => std::path::PathBuf::from(filename), + None => api.get("model.gguf")?, + }; + let config = std::fs::read_to_string(config_filename)?; + let mut config: t5::Config = serde_json::from_str(&config)?; + config.use_cache = !args.disable_cache; + let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?; + Ok(( + Self { + device, + config, + weights_filename, + }, + tokenizer, + )) + } + + pub fn build_model(&self) -> Result { + let vb = t5::VarBuilder::from_gguf(&self.weights_filename)?; + Ok(t5::T5ForConditionalGeneration::load(vb, &self.config)?) + } +} + +fn main() -> Result<()> { + use tracing_chrome::ChromeLayerBuilder; + use tracing_subscriber::prelude::*; + + let args = Args::parse(); + + let _guard = if args.tracing { + println!("tracing..."); + let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); + tracing_subscriber::registry().with(chrome_layer).init(); + Some(guard) + } else { + None + }; + + let (builder, mut tokenizer) = T5ModelBuilder::load(&args)?; + let device = &builder.device; + let tokenizer = tokenizer + .with_padding(None) + .with_truncation(None) + .map_err(E::msg)?; + let tokens = tokenizer + .encode(args.prompt, true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + let input_token_ids = Tensor::new(&tokens[..], device)?.unsqueeze(0)?; + let mut model = builder.build_model()?; + let mut output_token_ids = [builder.config.pad_token_id as u32].to_vec(); + let temperature = if args.temperature <= 0. { + None + } else { + Some(args.temperature) + }; + let mut logits_processor = LogitsProcessor::new(299792458, temperature, args.top_p); + let encoder_output = model.encode(&input_token_ids)?; + let start = std::time::Instant::now(); + + for index in 0.. { + if output_token_ids.len() > 512 { + break; + } + let decoder_token_ids = if index == 0 || !builder.config.use_cache { + Tensor::new(output_token_ids.as_slice(), device)?.unsqueeze(0)? + } else { + let last_token = *output_token_ids.last().unwrap(); + Tensor::new(&[last_token], device)?.unsqueeze(0)? + }; + let logits = model + .decode(&decoder_token_ids, &encoder_output)? + .squeeze(0)?; + let logits = if args.repeat_penalty == 1. { + logits + } else { + let start_at = output_token_ids.len().saturating_sub(args.repeat_last_n); + candle_transformers::utils::apply_repeat_penalty( + &logits, + args.repeat_penalty, + &output_token_ids[start_at..], + )? + }; + + let next_token_id = logits_processor.sample(&logits)?; + if next_token_id as usize == builder.config.eos_token_id { + break; + } + output_token_ids.push(next_token_id); + if let Some(text) = tokenizer.id_to_token(next_token_id) { + let text = text.replace('▁', " ").replace("<0x0A>", "\n"); + print!("{text}"); + std::io::stdout().flush()?; + } + } + let dt = start.elapsed(); + println!( + "\n{} tokens generated ({:.2} token/s)\n", + output_token_ids.len(), + output_token_ids.len() as f64 / dt.as_secs_f64(), + ); + Ok(()) +} diff --git a/candle-transformers/src/models/quantized_t5.rs b/candle-transformers/src/models/quantized_t5.rs index c14500ba..a10c3b80 100644 --- a/candle-transformers/src/models/quantized_t5.rs +++ b/candle-transformers/src/models/quantized_t5.rs @@ -15,6 +15,21 @@ pub struct VarBuilder { } impl VarBuilder { + pub fn from_gguf>(p: P) -> Result { + let mut file = std::fs::File::open(p)?; + let content = candle::quantized::gguf_file::Content::read(&mut file)?; + let mut data = std::collections::HashMap::new(); + for tensor_name in content.tensor_infos.keys() { + let tensor = content.tensor(&mut file, tensor_name)?; + data.insert(tensor_name.to_string(), Arc::new(tensor)); + } + Ok(Self { + data: Arc::new(data), + path: Vec::new(), + device: Device::Cpu, + }) + } + fn pp(&self, s: S) -> Self { let mut path = self.path.clone(); path.push(s.to_string()); @@ -87,7 +102,7 @@ struct QMatMul { impl QMatMul { fn new(out_dim: usize, in_dim: usize, vb: VarBuilder) -> Result { - let ws = vb.get((out_dim, in_dim), "weight")?; + let ws = vb.get((in_dim, out_dim), "weight")?; let inner = candle::quantized::QMatMul::from_arc(ws); let span = tracing::span!(tracing::Level::TRACE, "qmatmul"); Ok(Self { inner, span }) From b43ca493f67a98aa6a6f53144ecb17a0a0d20fd0 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 21 Sep 2023 13:23:30 +0100 Subject: [PATCH 148/150] Add more quantized flan t5 variants (#923) * Add the quantized flan-t5-large variant. * Add more sizes. --- candle-examples/examples/quantized-t5/main.rs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/candle-examples/examples/quantized-t5/main.rs b/candle-examples/examples/quantized-t5/main.rs index 86d3762e..93a86309 100644 --- a/candle-examples/examples/quantized-t5/main.rs +++ b/candle-examples/examples/quantized-t5/main.rs @@ -11,10 +11,20 @@ use candle_transformers::models::quantized_t5 as t5; use anyhow::{Error as E, Result}; use candle::{Device, Tensor}; use candle_transformers::generation::LogitsProcessor; -use clap::Parser; +use clap::{Parser, ValueEnum}; use hf_hub::{api::sync::Api, Repo, RepoType}; use tokenizers::Tokenizer; +#[derive(Clone, Debug, Copy, ValueEnum)] +enum Which { + T5Small, + FlanT5Small, + FlanT5Base, + FlanT5Large, + FlanT5Xl, + FlanT5Xxl, +} + #[derive(Parser, Debug, Clone)] #[command(author, version, about, long_about = None)] struct Args { @@ -55,6 +65,10 @@ struct Args { /// The context size to consider for the repeat penalty. #[arg(long, default_value_t = 64)] repeat_last_n: usize, + + /// The model size to use. + #[arg(long, default_value = "t5-small")] + which: Which, } struct T5ModelBuilder { @@ -77,11 +91,25 @@ impl T5ModelBuilder { let repo = Repo::with_revision(model_id, RepoType::Model, revision); let api = Api::new()?; let api = api.repo(repo); - let config_filename = api.get("config.json")?; + let config_filename = match args.which { + Which::T5Small => api.get("config.json")?, + Which::FlanT5Small => api.get("config-flan-t5-small.json")?, + Which::FlanT5Base => api.get("config-flan-t5-base.json")?, + Which::FlanT5Large => api.get("config-flan-t5-large.json")?, + Which::FlanT5Xl => api.get("config-flan-t5-xl.json")?, + Which::FlanT5Xxl => api.get("config-flan-t5-xxl.json")?, + }; let tokenizer_filename = api.get("tokenizer.json")?; let weights_filename = match &args.weight_file { Some(filename) => std::path::PathBuf::from(filename), - None => api.get("model.gguf")?, + None => match args.which { + Which::T5Small => api.get("model.gguf")?, + Which::FlanT5Small => api.get("model-flan-t5-small.gguf")?, + Which::FlanT5Base => api.get("model-flan-t5-base.gguf")?, + Which::FlanT5Large => api.get("model-flan-t5-large.gguf")?, + Which::FlanT5Xl => api.get("model-flan-t5-xl.gguf")?, + Which::FlanT5Xxl => api.get("model-flan-t5-xxl.gguf")?, + }, }; let config = std::fs::read_to_string(config_filename)?; let mut config: t5::Config = serde_json::from_str(&config)?; From aa8ec06fd2b02c1039a46fcb518fd6d351487978 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Thu, 21 Sep 2023 14:48:13 +0100 Subject: [PATCH 149/150] Add the t5-xxl version. (#924) --- candle-examples/examples/t5/main.rs | 73 +++++++++++++++-------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/candle-examples/examples/t5/main.rs b/candle-examples/examples/t5/main.rs index 348e9a55..55929c33 100644 --- a/candle-examples/examples/t5/main.rs +++ b/candle-examples/examples/t5/main.rs @@ -8,12 +8,12 @@ use std::path::PathBuf; use candle_transformers::models::t5; -use anyhow::{anyhow, Error as E, Result}; +use anyhow::{Error as E, Result}; use candle::{DType, Device, Tensor}; use candle_nn::VarBuilder; use candle_transformers::generation::LogitsProcessor; use clap::Parser; -use hf_hub::{api::sync::Api, Cache, Repo, RepoType}; +use hf_hub::{api::sync::Api, Repo, RepoType}; use tokenizers::Tokenizer; const DTYPE: DType = DType::F32; @@ -25,10 +25,6 @@ struct Args { #[arg(long)] cpu: bool, - /// Run offline (you must have the files already cached) - #[arg(long)] - offline: bool, - /// Enable tracing (generates a trace-timestamp.json file). #[arg(long)] tracing: bool, @@ -80,7 +76,7 @@ struct Args { struct T5ModelBuilder { device: Device, config: t5::Config, - weights_filename: PathBuf, + weights_filename: Vec, } impl T5ModelBuilder { @@ -95,28 +91,21 @@ impl T5ModelBuilder { (None, None) => (default_model, default_revision), }; - let repo = Repo::with_revision(model_id, RepoType::Model, revision); - let (config_filename, tokenizer_filename, weights_filename) = if args.offline { - let cache = Cache::default().repo(repo); - ( - cache - .get("config.json") - .ok_or(anyhow!("Missing config file in cache"))?, - cache - .get("tokenizer.json") - .ok_or(anyhow!("Missing tokenizer file in cache"))?, - cache - .get("model.safetensors") - .ok_or(anyhow!("Missing weights file in cache"))?, - ) + let repo = Repo::with_revision(model_id.clone(), RepoType::Model, revision); + let api = Api::new()?; + let api = api.repo(repo); + let config_filename = api.get("config.json")?; + let tokenizer_filename = api.get("tokenizer.json")?; + let weights_filename = if model_id == "google/flan-t5-xxl" { + vec![ + api.get("model-00001-of-00005.safetensors")?, + api.get("model-00002-of-00005.safetensors")?, + api.get("model-00003-of-00005.safetensors")?, + api.get("model-00004-of-00005.safetensors")?, + api.get("model-00005-of-00005.safetensors")?, + ] } else { - let api = Api::new()?; - let api = api.repo(repo); - ( - api.get("config.json")?, - api.get("tokenizer.json")?, - api.get("model.safetensors")?, - ) + vec![api.get("model.safetensors")?] }; let config = std::fs::read_to_string(config_filename)?; let mut config: t5::Config = serde_json::from_str(&config)?; @@ -133,18 +122,30 @@ impl T5ModelBuilder { } pub fn build_encoder(&self) -> Result { - let weights = - unsafe { candle::safetensors::MmapedFile::new(self.weights_filename.clone())? }; - let weights = weights.deserialize()?; - let vb = VarBuilder::from_safetensors(vec![weights], DTYPE, &self.device); + let weights = self + .weights_filename + .iter() + .map(|f| unsafe { candle::safetensors::MmapedFile::new(f) }) + .collect::>>()?; + let weights = weights + .iter() + .map(|w| w.deserialize()) + .collect::>>()?; + let vb = VarBuilder::from_safetensors(weights, DTYPE, &self.device); Ok(t5::T5EncoderModel::load(vb, &self.config)?) } pub fn build_conditional_generation(&self) -> Result { - let weights = - unsafe { candle::safetensors::MmapedFile::new(self.weights_filename.clone())? }; - let weights = weights.deserialize()?; - let vb = VarBuilder::from_safetensors(vec![weights], DTYPE, &self.device); + let weights = self + .weights_filename + .iter() + .map(|f| unsafe { candle::safetensors::MmapedFile::new(f) }) + .collect::>>()?; + let weights = weights + .iter() + .map(|w| w.deserialize()) + .collect::>>()?; + let vb = VarBuilder::from_safetensors(weights, DTYPE, &self.device); Ok(t5::T5ForConditionalGeneration::load(vb, &self.config)?) } } From a96878f2357fbcebf9db8747dcbb55bc8200d8ab Mon Sep 17 00:00:00 2001 From: Gonzalo <456459+grzuy@users.noreply.github.com> Date: Thu, 21 Sep 2023 15:52:39 -0300 Subject: [PATCH 150/150] cuda cast i64 (#925) --- candle-kernels/src/cast.cu | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/candle-kernels/src/cast.cu b/candle-kernels/src/cast.cu index ab2045a3..ee20fe5f 100644 --- a/candle-kernels/src/cast.cu +++ b/candle-kernels/src/cast.cu @@ -77,20 +77,30 @@ CAST_OP(double, __half, cast_f64_f16) CAST_OP(uint32_t, uint32_t, cast_u32_u32) CAST_OP(uint32_t, uint8_t, cast_u32_u8 ) +CAST_OP(uint32_t, int64_t, cast_u32_i64 ) CAST_OP(uint32_t, float, cast_u32_f32) CAST_OP(uint32_t, double, cast_u32_f64) CAST_OP(uint8_t, uint32_t, cast_u8_u32) CAST_OP(uint8_t, uint8_t, cast_u8_u8 ) +CAST_OP(uint8_t, int64_t, cast_u8_i64 ) CAST_OP(uint8_t, float, cast_u8_f32) CAST_OP(uint8_t, double, cast_u8_f64) +CAST_OP(int64_t, uint32_t, cast_i64_u32) +CAST_OP(int64_t, uint8_t, cast_i64_u8 ) +CAST_OP(int64_t, int64_t, cast_i64_i64 ) +CAST_OP(int64_t, float, cast_i64_f32) +CAST_OP(int64_t, double, cast_i64_f64) + CAST_OP(float, uint8_t, cast_f32_u8 ) CAST_OP(float, uint32_t, cast_f32_u32) +CAST_OP(float, int64_t, cast_f32_i64 ) CAST_OP(float, float, cast_f32_f32) CAST_OP(float, double, cast_f32_f64) CAST_OP(double, uint8_t, cast_f64_u8 ) CAST_OP(double, uint32_t, cast_f64_u32) +CAST_OP(double, int64_t, cast_f64_i64 ) CAST_OP(double, float, cast_f64_f32) CAST_OP(double, double, cast_f64_f64)