use std::env;
use std::path::{Path, PathBuf};
use hf_hub::Cache;
use modelexpress_client::{
Client as MxClient, ClientConfig as MxClientConfig, ModelProvider as MxModelProvider,
};
use modelexpress_common::{cache as mx_cache, download as mx, envs as mx_envs};
use dynamo_runtime::config::environment_names::model as env_model;
mod huggingface;
pub(crate) use huggingface::{
HfRepoSpec, cached_hf_snapshot, download_hf_snapshot, finalize_hf_snapshot, huggingface_cache,
};
const NGC_URI_PREFIX: &str = "ngc://";
fn provider_for(model_name: &str) -> MxModelProvider {
if model_name.starts_with(NGC_URI_PREFIX) {
MxModelProvider::Ngc
} else {
MxModelProvider::HuggingFace
}
}
fn get_cached_model_path(
model_name: &str,
provider: MxModelProvider,
ignore_weights: bool,
) -> Option<PathBuf> {
let cache_dir = get_model_express_cache_dir(provider);
match provider {
MxModelProvider::HuggingFace => {
get_cached_model_path_in(model_name, ignore_weights, cache_dir)
}
_ => get_cached_provider_model_path_in(model_name, provider, ignore_weights, &cache_dir),
}
}
fn get_cached_model_path_in(
model_name: &str,
ignore_weights: bool,
cache_dir: PathBuf,
) -> Option<PathBuf> {
let cache = Cache::new(cache_dir);
let snapshot_path = cache
.model(model_name.to_string())
.get("config.json")?
.parent()?
.to_path_buf();
cached_model_dir(model_name, snapshot_path, ignore_weights)
}
fn get_cached_provider_model_path_in(
model_name: &str,
provider: MxModelProvider,
ignore_weights: bool,
cache_dir: &Path,
) -> Option<PathBuf> {
let model_dir = mx_cache::resolve_model_path(cache_dir, provider, model_name, None).ok()?;
cached_model_dir(model_name, model_dir, ignore_weights)
}
fn cached_model_dir(model_name: &str, dir: PathBuf, ignore_weights: bool) -> Option<PathBuf> {
if !has_required_model_files(&dir, ignore_weights) {
return None;
}
tracing::info!("Found cached model '{model_name}' at {dir:?}, skipping download");
Some(dir)
}
fn has_required_model_files(dir: &Path, ignore_weights: bool) -> bool {
let has = |filename: &str| dir.join(filename).exists();
if !has("config.json") {
return false;
}
let has_tokenizer = has("tokenizer.json") || has("tiktoken.model") || has_tiktoken_file(dir);
if !has_tokenizer {
return false;
}
ignore_weights
|| has("model.safetensors")
|| has("pytorch_model.bin")
|| shard_files_present(&dir.join("model.safetensors.index.json"))
|| shard_files_present(&dir.join("pytorch_model.bin.index.json"))
}
fn has_tiktoken_file(dir: &Path) -> bool {
std::fs::read_dir(dir)
.into_iter()
.flatten()
.flatten()
.any(|e| e.path().extension().is_some_and(|ext| ext == "tiktoken"))
}
fn shard_files_present(index_path: &Path) -> bool {
let Some(snapshot_dir) = index_path.parent() else {
return false;
};
let Ok(contents) = std::fs::read_to_string(index_path) else {
return false;
};
let Ok(value) = serde_json::from_str::<serde_json::Value>(&contents) else {
return false;
};
let Some(weight_map) = value.get("weight_map").and_then(|v| v.as_object()) else {
return false;
};
let shards: std::collections::HashSet<&str> =
weight_map.values().filter_map(|v| v.as_str()).collect();
if shards.is_empty() {
return false;
}
shards.iter().all(|s| snapshot_dir.join(s).exists())
}
fn is_offline_mode() -> bool {
dynamo_runtime::config::env_is_truthy(env_model::huggingface::HF_HUB_OFFLINE)
}
fn is_no_shared_storage() -> bool {
dynamo_runtime::config::env_is_truthy(env_model::model_express::MODEL_EXPRESS_NO_SHARED_STORAGE)
}
pub async fn from_hf(name: impl AsRef<Path>, ignore_weights: bool) -> anyhow::Result<PathBuf> {
let name = name.as_ref();
let model_name = name.display().to_string();
let provider = provider_for(&model_name);
if let Some(cached_path) = get_cached_model_path(&model_name, provider, ignore_weights) {
return Ok(cached_path);
}
if is_offline_mode() {
tracing::warn!(
"Offline mode enabled but model '{model_name}' not found in cache, attempting download anyway"
);
}
let mut config: MxClientConfig = MxClientConfig::default();
if provider == MxModelProvider::Ngc {
config.cache.local_path = get_model_express_cache_dir(provider);
}
if let Ok(endpoint) = env::var(env_model::model_express::MODEL_EXPRESS_URL) {
config = config.with_endpoint(endpoint);
}
if is_no_shared_storage() {
config.cache.shared_storage = false;
}
let result = match MxClient::new(config).await {
Ok(mut client) => {
tracing::info!("Successfully connected to ModelExpress server");
match client
.request_model(&model_name, provider, ignore_weights)
.await
{
Ok(()) => {
tracing::info!("Server download succeeded for model: {model_name}");
match client.get_model_path(&model_name, provider).await {
Ok(path) => Ok(path),
Err(e) => {
tracing::warn!(
"Failed to resolve local model path after server download for '{model_name}': {e}. \
Falling back to direct download."
);
mx_download_direct(&model_name, provider, ignore_weights).await
}
}
}
Err(e) => {
tracing::warn!(
"Server download failed for model '{model_name}': {e}. Falling back to direct download."
);
mx_download_direct(&model_name, provider, ignore_weights).await
}
}
}
Err(e) => {
tracing::warn!("Cannot connect to ModelExpress server: {e}. Using direct download.");
mx_download_direct(&model_name, provider, ignore_weights).await
}
};
match result {
Ok(path) => {
tracing::info!("ModelExpress download completed successfully for model: {model_name}");
Ok(path)
}
Err(e) => {
tracing::warn!("ModelExpress download failed for model '{model_name}': {e}");
Err(e)
}
}
}
async fn mx_download_direct(
model_name: &str,
provider: MxModelProvider,
ignore_weights: bool,
) -> anyhow::Result<PathBuf> {
let cache_dir = get_model_express_cache_dir(provider);
mx::download_model(model_name, provider, Some(cache_dir), ignore_weights).await
}
fn get_model_express_cache_dir(provider: MxModelProvider) -> PathBuf {
if provider == MxModelProvider::Ngc {
return mx_envs::cache_directory()
.unwrap_or_else(|| mx_envs::home_dir_or_cwd().join(".cache"));
}
cache_dir_from_values(
env::var(env_model::huggingface::HF_HUB_CACHE).ok(),
env::var(env_model::huggingface::HF_HOME).ok(),
env::var(env_model::model_express::MODEL_EXPRESS_CACHE_PATH).ok(),
env::var("HOME").ok(),
env::var("USERPROFILE").ok(),
)
}
fn cache_dir_from_values(
hf_hub_cache: Option<String>,
hf_home: Option<String>,
model_express_cache: Option<String>,
home: Option<String>,
userprofile: Option<String>,
) -> PathBuf {
if let Some(cache_path) = hf_hub_cache {
return PathBuf::from(cache_path);
}
if let Some(hf_home) = hf_home {
return PathBuf::from(hf_home).join("hub");
}
if let Some(cache_path) = model_express_cache {
return PathBuf::from(cache_path);
}
PathBuf::from(home.or(userprofile).unwrap_or_else(|| ".".to_string()))
.join(".cache/huggingface/hub")
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[serial_test::serial]
#[test]
fn hf_offline_mode_accepts_huggingface_truthy_values() {
for value in ["1", "true", "TRUE", "on", "ON", "yes", "YES"] {
temp_env::with_var(env_model::huggingface::HF_HUB_OFFLINE, Some(value), || {
assert!(is_offline_mode(), "rejected {value}")
});
}
}
#[test]
fn cache_dir_precedence_and_fallback() {
assert_eq!(
cache_dir_from_values(
Some("/hub-cache".to_string()),
Some("/hf-home".to_string()),
Some("/model-express".to_string()),
Some("/home".to_string()),
None,
),
PathBuf::from("/hub-cache")
);
assert_eq!(
cache_dir_from_values(
None,
Some("/hf-home".to_string()),
Some("/model-express".to_string()),
Some("/home".to_string()),
None,
),
PathBuf::from("/hf-home/hub")
);
assert_eq!(
cache_dir_from_values(
None,
None,
Some("/model-express".to_string()),
Some("/home".to_string()),
None,
),
PathBuf::from("/model-express")
);
assert_eq!(
cache_dir_from_values(None, None, None, None, Some("/profile".to_string())),
PathBuf::from("/profile/.cache/huggingface/hub")
);
}
fn build_hf_cache(cache_root: &Path, model_name: &str, files: &[&str]) -> PathBuf {
let repo_dir = cache_root.join(format!("models--{}", model_name.replace('/', "--")));
let snapshot_hash = "0000000000000000000000000000000000000000";
let snapshot_dir = repo_dir.join("snapshots").join(snapshot_hash);
let refs_dir = repo_dir.join("refs");
fs::create_dir_all(&snapshot_dir).unwrap();
fs::create_dir_all(&refs_dir).unwrap();
fs::write(refs_dir.join("main"), snapshot_hash).unwrap();
for f in files {
fs::write(snapshot_dir.join(f), "{}").unwrap();
}
snapshot_dir
}
#[test]
fn test_cached_path_metadata_only_satisfies_ignore_weights_true() {
let temp = TempDir::new().unwrap();
let model = "test-org/metadata-only";
let snapshot = build_hf_cache(temp.path(), model, &["config.json", "tokenizer.json"]);
let with_weights = get_cached_model_path_in(model, false, temp.path().to_path_buf());
let no_weights = get_cached_model_path_in(model, true, temp.path().to_path_buf());
assert!(
with_weights.is_none(),
"metadata-only cache must NOT satisfy ignore_weights=false"
);
assert_eq!(
no_weights.as_deref(),
Some(snapshot.as_path()),
"metadata-only cache must satisfy ignore_weights=true"
);
}
#[test]
fn test_cached_path_full_cache_satisfies_both_modes() {
let temp = TempDir::new().unwrap();
let model = "test-org/full-cache";
let snapshot = build_hf_cache(
temp.path(),
model,
&["config.json", "tokenizer.json", "model.safetensors"],
);
let with_weights = get_cached_model_path_in(model, false, temp.path().to_path_buf());
let no_weights = get_cached_model_path_in(model, true, temp.path().to_path_buf());
assert_eq!(with_weights.as_deref(), Some(snapshot.as_path()));
assert_eq!(no_weights.as_deref(), Some(snapshot.as_path()));
}
#[test]
fn test_cached_path_sharded_requires_all_shard_files() {
let temp = TempDir::new().unwrap();
let model = "test-org/sharded";
let snapshot = build_hf_cache(temp.path(), model, &["config.json", "tokenizer.json"]);
fs::write(
snapshot.join("model.safetensors.index.json"),
r#"{"weight_map": {"a.weight": "model-00001-of-00002.safetensors", "b.weight": "model-00002-of-00002.safetensors"}}"#,
)
.unwrap();
let incomplete = get_cached_model_path_in(model, false, temp.path().to_path_buf());
assert!(
incomplete.is_none(),
"sharded cache without shard files must NOT satisfy ignore_weights=false"
);
fs::write(snapshot.join("model-00001-of-00002.safetensors"), "").unwrap();
fs::write(snapshot.join("model-00002-of-00002.safetensors"), "").unwrap();
let complete = get_cached_model_path_in(model, false, temp.path().to_path_buf());
assert_eq!(complete.as_deref(), Some(snapshot.as_path()));
}
#[test]
fn test_cached_path_rejects_tokenizer_config_without_real_tokenizer() {
let temp = TempDir::new().unwrap();
let model = "test-org/tokenizer-config-only";
build_hf_cache(
temp.path(),
model,
&["config.json", "tokenizer_config.json"],
);
assert!(
get_cached_model_path_in(model, true, temp.path().to_path_buf()).is_none(),
"tokenizer_config.json alone must NOT satisfy ignore_weights=true",
);
assert!(
get_cached_model_path_in(model, false, temp.path().to_path_buf()).is_none(),
"tokenizer_config.json alone must NOT satisfy ignore_weights=false",
);
}
#[serial_test::serial]
#[tokio::test]
async fn test_from_hf_cache_first_in_online_mode() {
let temp = TempDir::new().unwrap();
let model = "test-org/cache-first-online";
let snapshot = build_hf_cache(
temp.path(),
model,
&["config.json", "tokenizer.json", "model.safetensors"],
);
temp_env::async_with_vars(
[
(
env_model::huggingface::HF_HUB_CACHE,
Some(temp.path().to_str().unwrap()),
),
(env_model::huggingface::HF_HUB_OFFLINE, None),
(env_model::huggingface::HF_HOME, None),
(env_model::model_express::MODEL_EXPRESS_CACHE_PATH, None),
],
async {
let result = from_hf(PathBuf::from(model), false).await;
assert_eq!(
result.ok().as_deref(),
Some(snapshot.as_path()),
"from_hf must return cached path in online mode without network"
);
},
)
.await;
}
#[test]
fn provider_for_routes_ngc_uris_to_ngc() {
assert_eq!(
provider_for("ngc://nvstaging/nim/nemotron-3-ultra:hf-rl-052726-nvfp4-e9744cc"),
MxModelProvider::Ngc
);
assert_eq!(
provider_for("Qwen/Qwen3-0.6B"),
MxModelProvider::HuggingFace
);
assert_eq!(
provider_for("/data/llms/Qwen3-0.6B"),
MxModelProvider::HuggingFace
);
}
const NGC_TEST_MODEL: &str = "ngc://test-org/test-team/test-model:v1";
fn build_ngc_cache(cache_root: &Path, files: &[&str]) -> PathBuf {
let model_dir = cache_root.join("ngc/test-org/test-team/models/test-model/v1");
fs::create_dir_all(&model_dir).unwrap();
for f in files {
fs::write(model_dir.join(f), "{}").unwrap();
}
model_dir
}
#[test]
fn test_ngc_cached_path_requires_weights_for_full_download() {
let temp = TempDir::new().unwrap();
let model_dir = build_ngc_cache(temp.path(), &["config.json", "tokenizer.json"]);
let lookup = |ignore_weights: bool| {
get_cached_provider_model_path_in(
NGC_TEST_MODEL,
MxModelProvider::Ngc,
ignore_weights,
temp.path(),
)
};
assert!(
lookup(false).is_none(),
"NGC cache without weights must NOT satisfy ignore_weights=false"
);
assert_eq!(lookup(true).as_deref(), Some(model_dir.as_path()));
fs::write(model_dir.join("model.safetensors"), "").unwrap();
assert_eq!(lookup(false).as_deref(), Some(model_dir.as_path()));
}
#[serial_test::serial]
#[tokio::test]
async fn test_from_hf_ngc_cache_first() {
for explicit_cache in [true, false] {
let temp = TempDir::new().unwrap();
let home = temp.path().join("home");
let hf_cache = temp.path().join("hf");
let cache_root = if explicit_cache {
temp.path().join("mx")
} else {
home.join(".cache")
};
let model_dir = build_ngc_cache(
&cache_root,
&["config.json", "tokenizer.json", "model.safetensors"],
);
temp_env::async_with_vars(
[
(
mx_envs::MODEL_EXPRESS_CACHE_DIRECTORY,
explicit_cache.then(|| cache_root.to_str().unwrap()),
),
(mx_envs::HOME, home.to_str()),
(env_model::huggingface::HF_HUB_CACHE, hf_cache.to_str()),
(env_model::huggingface::HF_HOME, hf_cache.to_str()),
(
env_model::model_express::MODEL_EXPRESS_CACHE_PATH,
hf_cache.to_str(),
),
],
async {
assert_eq!(
get_cached_model_path(NGC_TEST_MODEL, MxModelProvider::Ngc, false)
.as_deref(),
Some(model_dir.as_path())
);
assert_eq!(
get_model_express_cache_dir(MxModelProvider::HuggingFace),
hf_cache
);
let result = from_hf(PathBuf::from(NGC_TEST_MODEL), false).await;
assert_eq!(result.ok().as_deref(), Some(model_dir.as_path()));
},
)
.await;
}
}
}