feat: add SettingsPage with HuggingFace model search and fix flexbox layout
- Add SettingsPage component with macOS/Linear-style settings UI (sp-* classes) - Fix HuggingFace results list squeeze: sp-card-row--full now has align-items: stretch overriding the inherited flex-start, ensuring children fill 100% horizontal width - Fix sp-hf-file row layout: width 100%, grid columns for name/quant/size/download - Fix sp-hf-name truncation: explicit overflow/ellipsis/white-space in CSS - Add white-space: nowrap to sp-hf-size and sp-hf-quant-badge to protect secondary columns - Add runtime-deps crate, Tailwind/PostCSS config, runtime-defaults, MessageBubble test - Update Cargo workspace, inference-server, tauri runtime types and process handling
This commit is contained in:
1 parent
9da25c3688
commit
01b629cb4a
41 files changed
+6864
-822
No files matched your search
@@ -12,6 +12,8 @@ path = "src/main.rs"
|
||||
inference-core = { path = "../inference-core" }
|
||||
api-contracts = { path = "../api-contracts" }
|
||||
metrics = { path = "../metrics" }
|
||||
runtime-deps = { path = "../runtime-deps" }
|
||||
dirs = { workspace = true }
|
||||
tokio.workspace = true
|
||||
axum.workspace = true
|
||||
hyper.workspace = true
|
||||
|
||||
@@ -1,38 +1,4 @@
|
||||
fn main() {
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
let target_dir = std::path::Path::new(&out_dir)
|
||||
.ancestors()
|
||||
.nth(3) // Volta 3 níveis de `target/debug/build/inference-server-XYZ/out` para `target/debug/` ou `target/release/`
|
||||
.unwrap();
|
||||
|
||||
let vendor_dir = std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("vendor")
|
||||
.join("llama-bin");
|
||||
|
||||
if vendor_dir.exists() {
|
||||
println!(
|
||||
"cargo:warning=Copying DLLs from {:?} to {:?}",
|
||||
vendor_dir, target_dir
|
||||
);
|
||||
|
||||
// Vamos copiar todos os arquivos .dll
|
||||
if let Ok(entries) = std::fs::read_dir(&vendor_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("dll") {
|
||||
let file_name = path.file_name().unwrap();
|
||||
let dest = target_dir.join(file_name);
|
||||
let _ = std::fs::copy(&path, &dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuildar apenas se houver mudanças nessa build file ou nos bins da vendor
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=../../vendor/llama-bin");
|
||||
println!("cargo:rerun-if-changed=../../vendor/llama-runtime/manifest.json");
|
||||
}
|
||||
@@ -3,8 +3,12 @@ use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use runtime_deps::{
|
||||
apply_runtime_to_current_process, ensure_llama_runtime, recommended_variant_override_hint,
|
||||
EnsureRuntimeRequest,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
use inference_core::{EngineConfig, InferenceEngine, RuntimeProfile};
|
||||
use metrics::counters::PerfCounters;
|
||||
@@ -17,12 +21,25 @@ use routes::{build_router, AppState};
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// ── Tracing ──────────────────────────────────────────────────────────
|
||||
fmt()
|
||||
.with_env_filter(
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
|
||||
)
|
||||
.with_target(false)
|
||||
.init();
|
||||
// Set LOCALLLM_LOG_FORMAT=json for machine-readable output (e.g. log aggregators).
|
||||
// Set RUST_LOG=debug for verbose output.
|
||||
let env_filter = EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
match std::env::var("LOCALLLM_LOG_FORMAT").as_deref() {
|
||||
Ok("json") => {
|
||||
tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(fmt::layer().json().with_current_span(true).with_span_list(true))
|
||||
.init();
|
||||
}
|
||||
_ => {
|
||||
tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(fmt::layer().with_target(true))
|
||||
.init();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Engine config (reads env vars; sane defaults for dev) ────────────
|
||||
let model_path = std::env::var("LOCALLLM_MODEL")
|
||||
@@ -62,6 +79,45 @@ async fn main() -> Result<()> {
|
||||
let server_config = Arc::new(ServerConfig::from_env(model_name.clone()));
|
||||
|
||||
// ── Load engine ──────────────────────────────────────────────────────
|
||||
tracing::info!(
|
||||
model = %model_path.display(),
|
||||
profile = ?profile,
|
||||
n_gpu_layers = config.n_gpu_layers,
|
||||
n_ctx = config.n_ctx,
|
||||
n_batch = config.n_batch,
|
||||
n_ubatch = config.n_ubatch,
|
||||
n_threads = config.n_threads,
|
||||
n_threads_batch = config.n_threads_batch,
|
||||
flash_attn = config.flash_attn,
|
||||
use_mmap = config.use_mmap,
|
||||
"engine_config"
|
||||
);
|
||||
let binary_path = std::env::current_exe()?;
|
||||
let runtime_cache_root = dirs::data_local_dir()
|
||||
.unwrap_or_else(|| {
|
||||
binary_path
|
||||
.parent()
|
||||
.unwrap_or_else(|| std::path::Path::new("."))
|
||||
.to_path_buf()
|
||||
})
|
||||
.join("ForgeLM");
|
||||
let runtime = ensure_llama_runtime(&EnsureRuntimeRequest {
|
||||
cache_root: &runtime_cache_root,
|
||||
gpu_layers: config.n_gpu_layers,
|
||||
variant_override: None,
|
||||
binary_path: Some(&binary_path),
|
||||
})
|
||||
.map_err(|error| anyhow::anyhow!(
|
||||
"failed to prepare llama runtime dependencies: {error}. {}",
|
||||
recommended_variant_override_hint()
|
||||
))?;
|
||||
apply_runtime_to_current_process(&runtime);
|
||||
tracing::info!(
|
||||
dll = %runtime.dll_path.display(),
|
||||
variant = runtime.variant_id,
|
||||
source = ?runtime.source,
|
||||
"llama_runtime_ready"
|
||||
);
|
||||
let mut engine = InferenceEngine::load(config)?;
|
||||
engine.warmup()?;
|
||||
|
||||
@@ -81,6 +137,14 @@ async fn main() -> Result<()> {
|
||||
let router = build_router(state);
|
||||
let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||
|
||||
tracing::info!(
|
||||
bind_addr = %addr,
|
||||
model = %model_name,
|
||||
api_key_set = std::env::var("LOCALLLM_API_KEY").is_ok(),
|
||||
log_format = std::env::var("LOCALLLM_LOG_FORMAT").as_deref().unwrap_or("pretty"),
|
||||
"server_ready"
|
||||
);
|
||||
|
||||
// ── Startup banner ───────────────────────────────────────────────────
|
||||
let display_addr = if addr.ip().is_unspecified() {
|
||||
format!("http://127.0.0.1:{}", addr.port())
|
||||
@@ -125,8 +189,20 @@ fn env_u32(key: &str, default: u32) -> u32 {
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
fn resolve_batch_size(n_ctx: u32, default_n_batch: u32, explicit_batch: Option<u32>) -> u32 {
|
||||
explicit_batch.unwrap_or_else(|| n_ctx.max(default_n_batch).max(1))
|
||||
/// Choose the prefill batch size.
|
||||
///
|
||||
/// The batch size controls how many tokens are processed in a single `llama_decode`
|
||||
/// call during prefill. It must not be confused with the context window (`n_ctx`):
|
||||
/// a large `n_ctx` does NOT require an equally large `n_batch` — `decode_prefill`
|
||||
/// already chunks the prompt into `n_batch`-sized slices.
|
||||
///
|
||||
/// Setting `n_batch = n_ctx` for a 128 K context would pre-allocate ~128 K token
|
||||
/// slots in the batch buffer, consuming gigabytes of VRAM at startup before any
|
||||
/// request arrives. Instead we cap auto-selection at `MAX_AUTO_BATCH` and let
|
||||
/// callers pass `LOCALLLM_BATCH` when they need a larger explicit override.
|
||||
fn resolve_batch_size(_n_ctx: u32, default_n_batch: u32, explicit_batch: Option<u32>) -> u32 {
|
||||
const MAX_AUTO_BATCH: u32 = 4096;
|
||||
explicit_batch.unwrap_or_else(|| default_n_batch.min(MAX_AUTO_BATCH).max(1))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -134,8 +210,11 @@ mod tests {
|
||||
use super::resolve_batch_size;
|
||||
|
||||
#[test]
|
||||
fn defaults_batch_to_context_size_when_ctx_is_larger() {
|
||||
assert_eq!(resolve_batch_size(16_384, 512, None), 16_384);
|
||||
fn caps_auto_batch_at_max_regardless_of_ctx_size() {
|
||||
// Large n_ctx must NOT bloat n_batch — that would OOM the batch buffer.
|
||||
assert_eq!(resolve_batch_size(131_072, 512, None), 512);
|
||||
assert_eq!(resolve_batch_size(131_072, 4096, None), 4096);
|
||||
assert_eq!(resolve_batch_size(131_072, 8192, None), 4096); // capped at MAX_AUTO_BATCH
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -397,9 +397,17 @@ async fn stream_response(
|
||||
}
|
||||
};
|
||||
|
||||
Sse::new(stream)
|
||||
let response = Sse::new(stream)
|
||||
.keep_alive(KeepAlive::default())
|
||||
.into_response()
|
||||
.into_response();
|
||||
|
||||
axum::response::Response::builder()
|
||||
.status(response.status())
|
||||
.header("content-type", "text/event-stream; charset=utf-8")
|
||||
.header("cache-control", "no-cache")
|
||||
.header("connection", "keep-alive")
|
||||
.body(response.into_body())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn blocking_response(
|
||||
|
||||
Reference in new issue
Block a user