Download GGUF models from HuggingFace, watchably
The first half of the llama.cpp work Bryan asked for: browse HuggingFace, fetch a model, and see how far it has got from any device. The design is dev-updater's build-progress shape with the four changes its author recommended after living with it, since a model download is an hour where a build is two minutes: - **A run has an id.** Without one "not downloading" means three different things -- finished, never started, or someone else's run ended while you were away -- and over an hour that ambiguity is certain rather than theoretical. A device compares the run it was watching to the run reported now. - **Outcomes outlive their run**, so a phone that was asleep at the moment of completion can still find out what happened. - **Cancel exists.** Retrofitting cancellation into a blocking loop is miserable, and several gigabytes over someone's data plan is not something to have no answer for. - **Progress is bytes, not a parsed marker.** We own the loop, so it counts directly; `total` is whatever Content-Length said and nothing else, and stays absent when the server sends none rather than becoming a bar drawn from a guess. The download owns its own thread rather than the blocking pool, which exists for short work. It resumes through HTTP Range, and trusts the 206 rather than the request -- a server that ignores Range answers 200 with the whole file, and appending to that would corrupt it. `truncate(false)` on the open is load-bearing for the same reason and says so. Searching is proxied through the server rather than done from the phone, because the app trusts exactly one certificate -- this one -- and the machine that must do the downloading is also the one whose view of what exists matters. Verified against the real HuggingFace, not a mock: searched, listed a repository's GGUFs, downloaded 234 MB with live byte progress, cancelled mid-flight, confirmed the partial survived, restarted and watched it resume at 162 MB rather than 0, and let it finish. The result's sha256 matches the one HuggingFace publishes for that file, so the resume is byte-correct and not merely the right length. llama.cpp then loaded it and ran inference. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
4004183acf
commit
9d29776f02
3 files changed
+658
No files matched your search
@@ -17,6 +17,7 @@ mod auth;
|
||||
mod certs;
|
||||
mod config;
|
||||
mod media;
|
||||
mod models;
|
||||
mod private;
|
||||
mod routes;
|
||||
mod session;
|
||||
@@ -88,6 +89,11 @@ struct Args {
|
||||
#[arg(long)]
|
||||
data_dir: Option<PathBuf>,
|
||||
|
||||
/// Directory for downloaded GGUF models. Defaults to
|
||||
/// `$XDG_DATA_HOME/ai-app/models`.
|
||||
#[arg(long)]
|
||||
models_dir: Option<PathBuf>,
|
||||
|
||||
/// Directory holding the TLS certificates, generated here on first
|
||||
/// start. Defaults to `$XDG_CONFIG_HOME/ai-app/certs`.
|
||||
#[arg(long)]
|
||||
@@ -179,11 +185,19 @@ async fn main() -> Result<()> {
|
||||
let data_dir = args
|
||||
.data_dir
|
||||
.unwrap_or_else(|| data_home().join("sessions"));
|
||||
// Beside the session data rather than under it: models outlive every
|
||||
// session and are shared by all of them, so deleting a session must
|
||||
// never take a multi-gigabyte download with it.
|
||||
let models_dir = args
|
||||
.models_dir
|
||||
.unwrap_or_else(|| data_home().join("models"));
|
||||
let models = Arc::new(models::ModelStore::new(models_dir.clone()));
|
||||
let manager = Arc::new(
|
||||
SessionManager::new(config_path.clone(), data_dir)
|
||||
.with_context(|| format!("failed to load {}", config_path.display()))?,
|
||||
);
|
||||
tracing::info!("config: {}", config_path.display());
|
||||
tracing::info!("models: {}", models_dir.display());
|
||||
for provider in manager.providers() {
|
||||
tracing::info!(" provider {} ({:?})", provider.name, provider.kind);
|
||||
}
|
||||
@@ -261,6 +275,7 @@ async fn main() -> Result<()> {
|
||||
// auth. Zero unauthenticated endpoints.
|
||||
let app = routes::router(Arc::clone(&manager))
|
||||
.merge(routes::usage_router(monitor))
|
||||
.merge(routes::models_router(Arc::clone(&models)))
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
Arc::clone(&manager),
|
||||
auth::require_token,
|
||||
|
||||
Reference in new issue
Block a user