use burn_core as burn;
use burn::module::Param;
use burn::tensor::Device;
use burn::tensor::Tensor;
use burn_std::network::downloader::download_file_as_bytes;
use burn_store::pytorch::PytorchReader;
use burn_store::{ModuleSnapshot, PytorchStore};
use std::fs::{File, create_dir_all};
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use super::calibrators::{AfineAdapter, FrCalibratorWithLimit, NrCalibrator};
use super::metric::Afine;
const AFINE_URL: &str =
"https://huggingface.co/chaofengc/IQA-PyTorch-Weights/resolve/main/afine.pth";
const CACHE_FILENAME: &str = "afine.pth";
fn get_cache_dir() -> PathBuf {
let cache_dir = dirs::cache_dir()
.expect("Could not get cache directory")
.join("burn-dataset")
.join("afine");
if !cache_dir.exists() {
create_dir_all(&cache_dir).expect("Failed to create cache directory");
}
cache_dir
}
fn download_if_needed(url: &str, cache_path: &PathBuf, message: &str) {
if !cache_path.exists() {
let bytes = download_file_as_bytes(url, message);
let mut file = File::create(cache_path).expect("Failed to create cache file");
file.write_all(&bytes).expect("Failed to write weights");
}
}
pub(crate) fn load_pretrained_weights(mut afine: Afine) -> Afine {
let cache_dir = get_cache_dir();
let cache_path = cache_dir.join(CACHE_FILENAME);
download_if_needed(AFINE_URL, &cache_path, "Downloading A-FINE weights...");
afine.clip_visual = load_clip_shard(afine.clip_visual, &cache_path);
afine.qhead = load_qhead_shard(afine.qhead, &cache_path);
afine.dhead = load_simple_shard(afine.dhead, &cache_path, "fidelity", "fidelity head");
let device = afine.adapter.k.val().device();
afine.nr_calibrator = load_nr_calibrator_scalars(&cache_path, &device);
afine.fr_calibrator = load_fr_calibrator_scalars(&cache_path, &device);
afine.adapter = load_adapter_scalar(&cache_path, &device);
afine
}
fn read_scalar_value<P: AsRef<Path>>(
cache_path: P,
top_level_key: &str,
field_name: &str,
) -> Option<f32> {
let reader = PytorchReader::with_top_level_key(cache_path.as_ref(), top_level_key)
.map_err(|e| log::warn!("Failed to open shard '{top_level_key}': {e:?}"))
.ok()?;
let snapshot = reader.get(field_name)?;
let data = snapshot
.to_data()
.map_err(|e| log::warn!("Failed to read '{top_level_key}.{field_name}' tensor data: {e:?}"))
.ok()?;
let values = data
.to_vec::<f32>()
.map_err(|e| log::warn!("Failed to convert '{top_level_key}.{field_name}' to f32: {e:?}"))
.ok()?;
values.first().copied()
}
fn scalar_param(value: f32, device: &Device) -> Param<Tensor<1>> {
Param::from_tensor(Tensor::from_floats([value], device))
}
fn load_nr_calibrator_scalars(cache_path: &PathBuf, device: &Device) -> NrCalibrator {
const NR_YITA3_FALLBACK: f32 = 4.9592;
const NR_YITA4_FALLBACK: f32 = 21.5968;
let yita3 =
read_scalar_value(cache_path, "natural_scale", "yita3").unwrap_or(NR_YITA3_FALLBACK);
let yita4 =
read_scalar_value(cache_path, "natural_scale", "yita4").unwrap_or(NR_YITA4_FALLBACK);
NrCalibrator {
yita3: scalar_param(yita3, device),
yita4: scalar_param(yita4, device),
}
}
fn load_fr_calibrator_scalars(cache_path: &PathBuf, device: &Device) -> FrCalibratorWithLimit {
const FR_YITA3_FALLBACK: f32 = 0.5;
const FR_YITA4_FALLBACK: f32 = 0.15;
let yita3 =
read_scalar_value(cache_path, "fidelity_scale", "yita3").unwrap_or(FR_YITA3_FALLBACK);
let yita4 =
read_scalar_value(cache_path, "fidelity_scale", "yita4").unwrap_or(FR_YITA4_FALLBACK);
FrCalibratorWithLimit {
yita3: scalar_param(yita3, device),
yita4: scalar_param(yita4, device),
}
}
fn load_adapter_scalar(cache_path: &PathBuf, device: &Device) -> AfineAdapter {
const K_FALLBACK: f32 = 5.0;
let k = read_scalar_value(cache_path, "adapter", "k").unwrap_or(K_FALLBACK);
AfineAdapter {
k: scalar_param(k, device),
}
}
fn load_clip_shard(
mut clip: super::clip_vit::ClipVisualEncoder,
cache_path: &PathBuf,
) -> super::clip_vit::ClipVisualEncoder {
let mut store = PytorchStore::from_file(cache_path)
.with_top_level_key("finetuned_clip")
.allow_partial(true)
.skip_enum_variants(true)
.with_key_remapping(r"^positional_embedding$", "_text_positional_embedding_drop")
.with_key_remapping(r"^visual\.conv1\.", "patch_embed.")
.with_key_remapping(r"^visual\.class_embedding$", "class_token")
.with_key_remapping(r"^visual\.positional_embedding$", "positional_embedding")
.with_key_remapping(r"^visual\.ln_pre\.", "ln_pre.")
.with_key_remapping(r"^visual\.ln_post\.", "ln_post.")
.with_key_remapping(r"^visual\.transformer\.resblocks\.", "blocks.")
.with_key_remapping(r"\.attn\.in_proj_weight$", ".attn.qkv_proj.weight")
.with_key_remapping(r"\.attn\.in_proj_bias$", ".attn.qkv_proj.bias");
if let Err(e) = clip.load_from(&mut store) {
log::warn!(
"Some CLIP visual encoder weights could not be loaded: {:?}",
e
);
}
clip
}
fn load_qhead_shard(
mut qhead: super::heads::AfineQHead,
cache_path: &PathBuf,
) -> super::heads::AfineQHead {
let mut store = PytorchStore::from_file(cache_path)
.with_top_level_key("natural")
.allow_partial(true)
.skip_enum_variants(true)
.with_key_remapping(r"^proj_head\.0\.", "proj_head_fc1.")
.with_key_remapping(r"^proj_head\.2\.", "proj_head_fc2.");
if let Err(e) = qhead.load_from(&mut store) {
log::warn!("Some naturalness head weights could not be loaded: {:?}", e);
}
qhead
}
fn load_simple_shard<M>(
mut module: M,
cache_path: &PathBuf,
top_level_key: &'static str,
description: &str,
) -> M
where
M: ModuleSnapshot,
{
let mut store = PytorchStore::from_file(cache_path)
.with_top_level_key(top_level_key)
.allow_partial(true)
.skip_enum_variants(true);
if let Err(e) = module.load_from(&mut store) {
log::warn!("Some {} weights could not be loaded: {:?}", description, e);
}
module
}