pub mod audio;
pub mod bs1770;
pub mod chat_template;
pub mod coco_classes;
pub mod imagenet;
pub mod mnist_train;
pub mod token_output_stream;
pub mod wav;
use fuel::utils::{cuda_is_available, metal_is_available};
use fuel::{Device, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HostImage {
pub data: Vec<u8>,
pub height: usize,
pub width: usize,
}
impl HostImage {
pub fn from_hwc(hwc: &[u8], height: usize, width: usize) -> Result<Self> {
if hwc.len() != 3 * height * width {
fuel::bail!(
"HostImage::from_hwc: expected {} bytes for {height}x{width}x3, got {}",
3 * height * width,
hwc.len()
)
}
let mut data = vec![0u8; hwc.len()];
for h in 0..height {
for w in 0..width {
for c in 0..3 {
data[(c * height + h) * width + w] = hwc[(h * width + w) * 3 + c];
}
}
}
Ok(Self {
data,
height,
width,
})
}
pub fn to_hwc(&self) -> Vec<u8> {
let (h, w) = (self.height, self.width);
let mut out = vec![0u8; 3 * h * w];
for y in 0..h {
for x in 0..w {
for c in 0..3 {
out[(y * w + x) * 3 + c] = self.data[(c * h + y) * w + x];
}
}
}
out
}
}
pub fn device(cpu: bool) -> Result<Device> {
if cpu {
Ok(Device::cpu())
} else if cuda_is_available() {
fuel::cuda_backend::new_device(0)
} else if metal_is_available() {
fuel::metal_backend::new_device(0)
} else {
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
println!(
"Running on CPU, to run on GPU(metal), build this example with `--features metal`"
);
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
println!("Running on CPU, to run on GPU, build this example with `--features cuda`");
}
Ok(Device::cpu())
}
}
pub fn load_image<P: AsRef<std::path::Path>>(
p: P,
resize_longest: Option<usize>,
) -> Result<(HostImage, usize, usize)> {
let img = image::ImageReader::open(p)?
.decode()
.map_err(fuel::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) => {
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 = HostImage::from_hwc(&img.into_raw(), height, width)?;
Ok((data, initial_h, initial_w))
}
pub fn load_image_and_resize<P: AsRef<std::path::Path>>(
p: P,
width: usize,
height: usize,
) -> Result<HostImage> {
let img = image::ImageReader::open(p)?
.decode()
.map_err(fuel::Error::wrap)?
.resize_to_fill(
width as u32,
height as u32,
image::imageops::FilterType::Triangle,
);
let img = img.to_rgb8();
HostImage::from_hwc(&img.into_raw(), height, width)
}
pub fn save_image<P: AsRef<std::path::Path>>(img: &HostImage, p: P) -> Result<()> {
let p = p.as_ref();
let pixels = img.to_hwc();
let image: image::ImageBuffer<image::Rgb<u8>, Vec<u8>> =
match image::ImageBuffer::from_raw(img.width as u32, img.height as u32, pixels) {
Some(image) => image,
None => fuel::bail!("error saving image {p:?}"),
};
image.save(p).map_err(fuel::Error::wrap)?;
Ok(())
}
pub fn save_image_resize<P: AsRef<std::path::Path>>(
img: &HostImage,
p: P,
h: usize,
w: usize,
) -> Result<()> {
let p = p.as_ref();
let pixels = img.to_hwc();
let image: image::ImageBuffer<image::Rgb<u8>, Vec<u8>> =
match image::ImageBuffer::from_raw(img.width as u32, img.height as u32, pixels) {
Some(image) => image,
None => fuel::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(fuel::Error::wrap)?;
Ok(())
}
pub fn hub_load_safetensors(
repo: &hf_hub::api::sync::ApiRepo,
json_file: &str,
) -> Result<Vec<std::path::PathBuf>> {
let json_file = repo.get(json_file).map_err(fuel::Error::wrap)?;
let json_file = std::fs::File::open(json_file)?;
let json: serde_json::Value = serde_json::from_reader(&json_file).map_err(fuel::Error::wrap)?;
let weight_map = match json.get("weight_map") {
None => fuel::bail!("no weight map in {json_file:?}"),
Some(serde_json::Value::Object(map)) => map,
Some(_) => fuel::bail!("weight map in {json_file:?} is not a map"),
};
let mut safetensors_files = std::collections::HashSet::new();
for value in weight_map.values() {
if let Some(file) = value.as_str() {
safetensors_files.insert(file.to_string());
}
}
let safetensors_files = safetensors_files
.iter()
.map(|v| repo.get(v).map_err(fuel::Error::wrap))
.collect::<Result<Vec<_>>>()?;
Ok(safetensors_files)
}
pub fn hub_load_local_safetensors<P: AsRef<std::path::Path>>(
path: P,
json_file: &str,
) -> Result<Vec<std::path::PathBuf>> {
let path = path.as_ref();
let jsfile = std::fs::File::open(path.join(json_file))?;
let json: serde_json::Value = serde_json::from_reader(&jsfile).map_err(fuel::Error::wrap)?;
let weight_map = match json.get("weight_map") {
None => fuel::bail!("no weight map in {json_file:?}"),
Some(serde_json::Value::Object(map)) => map,
Some(_) => fuel::bail!("weight map in {json_file:?} is not a map"),
};
let mut safetensors_files = std::collections::HashSet::new();
for value in weight_map.values() {
if let Some(file) = value.as_str() {
safetensors_files.insert(file);
}
}
let safetensors_files: Vec<_> = safetensors_files
.into_iter()
.map(|v| path.join(v))
.collect();
Ok(safetensors_files)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hwc_chw_roundtrip_is_identity() -> Result<()> {
let hwc: Vec<u8> = (0..18).collect();
let img = HostImage::from_hwc(&hwc, 2, 3)?;
assert_eq!(img.height, 2);
assert_eq!(img.width, 3);
assert_eq!(img.to_hwc(), hwc);
Ok(())
}
#[test]
fn from_hwc_lays_out_channel_major() -> Result<()> {
let img = HostImage::from_hwc(&[1, 2, 3, 4, 5, 6], 1, 2)?;
assert_eq!(img.data, vec![1, 4, 2, 5, 3, 6]);
Ok(())
}
#[test]
fn from_hwc_does_not_transpose_a_non_square_image() -> Result<()> {
let hwc: Vec<u8> = (10..28).collect();
let img = HostImage::from_hwc(&hwc, 2, 3)?;
assert_eq!(&img.data[0..6], &[10, 13, 16, 19, 22, 25], "red plane");
assert_eq!(&img.data[6..12], &[11, 14, 17, 20, 23, 26], "green plane");
assert_eq!(&img.data[12..18], &[12, 15, 18, 21, 24, 27], "blue plane");
assert_eq!(img.to_hwc(), hwc);
Ok(())
}
#[test]
fn from_hwc_rejects_a_mismatched_length() {
let err = HostImage::from_hwc(&[0; 17], 2, 3).unwrap_err();
assert!(
err.to_string().contains("expected 18 bytes"),
"unexpected error: {err}"
);
}
}
#[cfg(test)]
mod bail_consolidation_tests {
fn always_bails() -> fuel::Result<()> {
fuel::bail!("deliberate failure for the fuel-core dissolution bail! consolidation test");
#[allow(unreachable_code)]
Ok(())
}
#[test]
fn facade_bail_still_produces_a_typed_err_with_the_message() {
let err = always_bails().expect_err("bail! must produce an Err, never panic");
assert!(err.to_string().contains("deliberate failure"));
}
}