diff --git a/.idea/dictionaries/bt.xml b/.idea/dictionaries/bt.xml
index 75f4f76930..489e55b875 100644
--- a/.idea/dictionaries/bt.xml
+++ b/.idea/dictionaries/bt.xml
@@ -31,6 +31,7 @@
hifigan
hparams
huggingface
+ kasisnu
lipsync
mocap
mochi
diff --git a/crates/desktop/src-tauri/src/image_endpoint.rs b/crates/desktop/src-tauri/src/image_endpoint.rs
index 4ceb6c4153..bd249aee18 100644
--- a/crates/desktop/src-tauri/src/image_endpoint.rs
+++ b/crates/desktop/src-tauri/src/image_endpoint.rs
@@ -1,11 +1,22 @@
+use std::env::args;
+use std::sync::{Arc, RwLock};
use crate::model::model_file::StableDiffusionVersion;
use crate::sd::{self, Args};
use anyhow::Result;
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine};
-use candle_core::Device;
+use candle_core::{DType, Device};
+use candle_transformers::models::stable_diffusion;
+use once_cell::sync::Lazy;
+use crate::ml::model_cache::ModelCache;
+use crate::ml::models::unet_model::UNetModel;
const PROMPT: &str = "A beautiful landscape with mountains and a lake";
+const HEIGHT: usize = 512;
+const WIDTH: usize = 512;
+
+static MODEL_CACHE: Lazy>>> = Lazy::new(|| Arc::new(RwLock::new(None)));
+
#[tauri::command]
pub fn infer_image(image: &str) -> Result {
println!("infer_image called; generating image with SDXL Turbo...");
@@ -22,30 +33,101 @@ pub fn infer_image(image: &str) -> Result {
}
};
- let args = Args {
- prompt: PROMPT.to_string(),
- uncond_prompt: "".to_string(),
- cpu: use_cpu,
- height: Some(512),
- width: Some(512),
- n_steps: Some(1),
- num_samples: 1,
- seed: None,
- sd_version: StableDiffusionVersion::Turbo,
- guidance_scale: Some(0.0),
+ let sd_version = StableDiffusionVersion::Turbo;
+
+ let sd_config = match sd_version {
+ StableDiffusionVersion::Turbo => {
+ println!("Configuring SDXL Turbo");
+ stable_diffusion::StableDiffusionConfig::sdxl_turbo(None, Some(HEIGHT), Some(WIDTH))
+ }
+ _ => {
+ println!("Configuring SD v2.1");
+ stable_diffusion::StableDiffusionConfig::v2_1(None, Some(HEIGHT), Some(WIDTH))
+ }
};
+
+ let api = hf_hub::api::sync::Api::new()
+ .map_err(|err| err.to_string())?;
+
+
+ match MODEL_CACHE.write() {
+ Err(err) => return Err(err.to_string()),
+ Ok(mut maybe_cache) => {
+ match &*maybe_cache {
+ None => {
+ let device = if use_cpu {
+ println!("Using CPU for computation");
+ Device::Cpu
+ } else {
+ println!("Attempting to use CUDA device");
+ match Device::new_cuda(0) {
+ Ok(cuda_device) => {
+ println!("Successfully initialized CUDA device");
+ cuda_device
+ }
+ Err(e) => {
+ println!("Failed to initialize CUDA device: {}. Falling back to CPU", e);
+ Device::Cpu
+ }
+ }
+ };
+
+ let cache = ModelCache::new(device, sd_version.clone(), sd_config.clone())
+ .map_err(|err| err.to_string())?;
+
+ *maybe_cache = Some(cache);
+ }
+ Some(_model) => {
+ println!("Model cache already exists");
+ }
+ }
- match sd::run(args) {
- Ok(_) => {
- let img_data = std::fs::read("temp.png")
- .map_err(|e| format!("Failed to read generated image: {}", e))?;
- let encoded = BASE64_STANDARD.encode(&img_data);
- println!("Generated image encoded successfully");
-
- let _ = std::fs::remove_file("temp.png");
-
- Ok(encoded)
}
- Err(e) => Err(format!("Failed to generate image: {}", e))
}
+
+
+
+ match MODEL_CACHE.read() {
+ Err(err) => return Err(err.to_string()),
+ Ok(maybe_model) => {
+ match &*maybe_model {
+ None => {
+ Err("model does not exist".to_string())
+ }
+ Some(model_cache) => {
+
+ let args = Args {
+ api,
+ prompt: PROMPT.to_string(),
+ uncond_prompt: "".to_string(),
+ cpu: use_cpu,
+ height: Some(HEIGHT),
+ width: Some(WIDTH),
+ n_steps: Some(1),
+ num_samples: 1,
+ seed: None,
+ sd_version,
+ guidance_scale: Some(0.0),
+ model_cache,
+ };
+
+ match sd::run(args) {
+ Ok(_) => {
+ let img_data = std::fs::read("temp.png")
+ .map_err(|e| format!("Failed to read generated image: {}", e))?;
+ let encoded = BASE64_STANDARD.encode(&img_data);
+ println!("Generated image encoded successfully");
+
+ let _ = std::fs::remove_file("temp.png");
+
+ Ok(encoded)
+ }
+ Err(e) => Err(format!("Failed to generate image: {}", e))
+ }
+
+ }
+ }
+ }
+ }
+
}
diff --git a/crates/desktop/src-tauri/src/lib.rs b/crates/desktop/src-tauri/src/lib.rs
index 8a2645a2da..e8d63d2419 100644
--- a/crates/desktop/src-tauri/src/lib.rs
+++ b/crates/desktop/src-tauri/src/lib.rs
@@ -2,6 +2,7 @@ mod flip_image;
mod image_endpoint;
mod sd;
pub mod model;
+pub mod ml;
use std::sync::{Arc, RwLock};
use once_cell::sync::Lazy;
diff --git a/crates/desktop/src-tauri/src/ml/mod.rs b/crates/desktop/src-tauri/src/ml/mod.rs
new file mode 100644
index 0000000000..e8a187da65
--- /dev/null
+++ b/crates/desktop/src-tauri/src/ml/mod.rs
@@ -0,0 +1,2 @@
+pub mod model_cache;
+pub mod models;
\ No newline at end of file
diff --git a/crates/desktop/src-tauri/src/ml/model_cache.rs b/crates/desktop/src-tauri/src/ml/model_cache.rs
new file mode 100644
index 0000000000..1732e3714f
--- /dev/null
+++ b/crates/desktop/src-tauri/src/ml/model_cache.rs
@@ -0,0 +1,90 @@
+use crate::ml::models::unet_model::UNetModel;
+use crate::model::model_file::StableDiffusionVersion;
+use anyhow::anyhow;
+use candle_core::{DType, Device, Tensor};
+use candle_transformers::models::stable_diffusion::StableDiffusionConfig;
+use hf_hub::api::sync::Api;
+use std::sync::{Arc, RwLock};
+
+// Simple registry for now. We can build complex machinery that aids in
+// VRAM utilization, disk space saving, intelligent scheduling, etc. in the future.
+#[derive(Clone)]
+pub struct ModelCache {
+ // TODO(bt,2025-02-14): Support for more than one device in the future.
+ device: Device,
+ hf_api: Api,
+ sd_config: StableDiffusionConfig,
+ sd_version: StableDiffusionVersion,
+ unet: Arc>>,
+}
+
+impl ModelCache {
+ pub fn new(device: Device, sd_version: StableDiffusionVersion, sd_config: StableDiffusionConfig) -> anyhow::Result {
+ Ok(Self {
+ device,
+ hf_api: Api::new()?,
+ unet: Arc::new(RwLock::new(None)),
+ sd_config,
+ sd_version,
+ })
+ }
+
+ pub fn unet_inference(&self, latent_model_input: &Tensor, timestep: f64, text_embeddings: &Tensor) -> anyhow::Result {
+ println!("unet_inference");
+ match self.try_unet_inference(latent_model_input, timestep, text_embeddings) {
+ Err(err) => Err(err),
+ Ok(Some(result)) => Ok(result),
+ Ok(None) => {
+ // Model wasn't previously loaded
+ self.load_unet()?;
+ self.try_unet_inference(latent_model_input, timestep, text_embeddings)?
+ .ok_or(anyhow!("model was not previously loaded"))
+ }
+ }
+ }
+
+ fn load_unet(&self) -> anyhow::Result<()> {
+ println!("load_unet");
+ match self.unet.write() {
+ Err(err) => return Err(anyhow!("lock error: {:?}", err)),
+ Ok(mut maybe_model) => {
+ match &*maybe_model {
+ Some(_model) => {} // Fall through
+ None => {
+ let repo = self.sd_version.repo();
+
+ println!("Downloading UNET model files from: {} ...", repo);
+
+ let unet_file = self.hf_api.model(repo.to_string())
+ .get("unet/diffusion_pytorch_model.safetensors")
+ .map_err(|err| anyhow!("error fetching model: {:?}", err))?;
+
+ println!("Building UNET model... (3)");
+ let unet = UNetModel::new(&self.sd_config, unet_file, &self.device, DType::F32)
+ .map_err(|err| anyhow!("error initializing unet model: {:?}", err))?;
+
+ println!("Built UNET...");
+ *maybe_model = Some(unet);
+ }
+ }
+ }
+ }
+ Ok(())
+ }
+
+ pub fn try_unet_inference(&self, latent_model_input: &Tensor, timestep: f64, text_embeddings: &Tensor) -> anyhow::Result