First Run

The first time LlamaForge starts with a fresh config.json (no ui_mode key on disk), config.py's migrate() classifies the install: if server_bin is already set (an existing llama.cpp build was found), the config is marked ui_mode: "advanced" and onboarded: true; otherwise it is marked ui_mode: "lite" and onboarded: false, which is what causes the onboarding wizard to appear. This check is idempotent — a config that already carries ui_mode is left untouched on every later startup.

Lite vs Advanced mode

LlamaForge has two UI densities, toggled at any time from the mode switch in the dashboard header (applyMode() in web/js/ui.js toggles a mode-lite class on <body> and persists the choice via PUT /api/config with ui_mode):

Finishing the wizard sets ui_mode to "lite"; skipping it sets ui_mode to "advanced". You can switch between them afterward at any time.

The onboarding wizard

wizMaybeStart() shows the wizard automatically whenever the server reports onboarding.onboarded as false. It walks five steps, defined in WIZ.steps in web/js/wizard.js:

  1. Engine — "Do you already have a llama.cpp build?" Choose Yes, I have one built or No — clone & build it for me, and a build flavor (official llama.cpp; a mainline fork; ik_llama is listed but disabled, marked "coming soon"). The clone path hands off to the same flow as the Build tab.
  2. Hardware — a read-only summary of detected GPUs and their VRAM (or "No GPU detected — CPU mode" if none).
  3. Model — pick an already-registered model from a dropdown. If none are registered, the step instead links to the Discover tab and closes the wizard so you can download one.
  4. Tune — choose a goal (Balanced, Max speed, Max context, or Coding) and click Auto-tune to call /api/autotune/recommend for that model and intent; the resulting knobs and their rationale are shown in a table. An optional Refine by benchmarking (~1 min) button calls /api/autotune/refine to try a few high-impact variants (e.g. alternate ubatch-size/batch-size) and keep whichever measured the highest tokens/second.
  5. Ready — confirms the chosen settings will be applied to the selected model and it will be loaded.

Finishing the wizard saves the recommended knobs with /api/save, loads the model with /api/load, and marks the config onboarded: true, ui_mode: "lite" regardless of whether the load itself succeeded (a failed load surfaces a toast telling you to load it manually from the Models tab). Skip instead marks the config onboarded: true, ui_mode: "advanced" and closes the wizard without touching any model.

What auto-tune decides

backend/autotune.py's recommend(meta, hw, intent, size_bytes) is a pure function that turns a GGUF's header metadata and the detected hardware into a small set of knobs — everything else is left at llama.cpp's own defaults. Four intents are supported: balanced, speed, context, coding.

Every knob recommend() sets comes with a plain-language reason, which is what populates the rationale column in the wizard's Tune step.

Auto-tune only ever writes the handful of knobs above. Anything else you set by hand on the Models tab afterward is preserved — per-model settings always win over the global [*] defaults.