llm-tuner 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,75 +6,77 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
7
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D22-brightgreen.svg)](package.json)
8
8
 
9
- Load a local LLM with the best configuration for the context you need, on LM Studio or Ollama, on Windows, macOS, and Linux. You choose what "best" means through deterministic load profiles: speed, a balanced trade-off, or maximum answer quality.
9
+ Load a local LLM with the fastest configuration for the context you need, on LM Studio or Ollama, across Windows, macOS, and Linux. You choose what "best" means through deterministic load profiles: raw speed (`speed`), a balanced trade-off (`balanced`), or maximum answer quality (`quality`).
10
10
 
11
- No LLM decides the configuration. LLM Tuner reads your hardware topology and model GGUF metadata, mathematically calculates what fits into VRAM, benchmarks the top candidate configurations on real hardware, and saves the winner as a persistent **preset**. The next time you load that model with the same context and profile, the preset is applied instantly (in ~20s) without re-measuring.
11
+ No LLM decides the configuration. LLM Tuner reads your hardware topology and model GGUF metadata, mathematically calculates what fits into VRAM, benchmarks the top candidate configurations on real hardware, and saves the winner as a persistent **preset**. The next time you load that model with the same context and profile, the preset is applied immediately (~20s) without re-measuring.
12
12
 
13
13
  ---
14
14
 
15
15
  ## Table of Contents
16
16
 
17
- - [Features](#features)
17
+ - [Key Features](#key-features)
18
18
  - [How It Works](#how-it-works)
19
19
  - [Load Profiles](#load-profiles)
20
20
  - [Installation](#installation)
21
21
  - [Usage](#usage)
22
- - [Interactive Wizard](#interactive-wizard)
23
- - [Desktop App](#desktop-app)
22
+ - [Interactive CLI & Slash Commands](#interactive-cli--slash-commands)
23
+ - [Desktop Application](#desktop-application)
24
+ - [Remote Server Mode & Daemon](#remote-server-mode--daemon)
25
+ - [MCP Server Integration for AI Agents](#mcp-server-integration-for-ai-agents)
24
26
  - [Non-Interactive CLI](#non-interactive-cli)
25
- - [CLI Options Reference](#cli-options-reference)
27
+ - [CLI Options & Flags Reference](#cli-options--flags-reference)
26
28
  - [Tuning Pipeline](#tuning-pipeline)
27
- - [Preset Storage](#preset-storage)
29
+ - [Architecture & Storage Paths](#architecture--storage-paths)
28
30
  - [Platform Support](#platform-support)
29
- - [Project Architecture](#project-architecture)
30
31
  - [Development & Testing](#development--testing)
31
- - [Notes & Troubleshooting](#notes--troubleshooting)
32
+ - [Troubleshooting & Platform Notes](#troubleshooting--platform-notes)
32
33
  - [License](#license)
33
34
 
34
35
  ---
35
36
 
36
- ## Features
37
+ ## Key Features
37
38
 
38
39
  - **Deterministic Optimization**: Uses GGUF tensor-level metadata, PCIe bandwidth measurements, and real token-generation benchmarks—never an AI guessing parameters.
39
- - **Multi-GPU & Topology Aware**: Splits layers intelligently, prioritizing the GPU with the widest PCIe link and no display server attached.
40
+ - **Multi-GPU & Topology Aware**: Splits layers intelligently across multiple GPUs, prioritizing cards with wider PCIe links and no attached display server.
40
41
  - **Tailored Load Profiles**: Optimize for raw tokens/second (`speed`), quality-weighted speed (`balanced`), or zero-loss GPU retention (`quality`).
41
- - **Engine Support**: First-class support for both LM Studio (direct `llama-server` management) and Ollama (isolated private benchmarks).
42
- - **Persistent Presets**: Measures once per hardware, model, context, and profile. Automatically invalidates presets if hardware or model weights change.
42
+ - **Engine Support**: First-class support for both LM Studio (direct `llama-server` management) and Ollama (isolated benchmark servers and systemd/service integration).
43
+ - **Persistent Presets**: Measures once per hardware fingerprint, model, context, and profile. Automatically invalidates presets if hardware or model weights change.
43
44
  - **Dual Interfaces**: Interactive terminal wizard (`llm-tuner`) and cross-platform desktop UI (`llm-tuner-desktop`).
44
- - **AI Agent MCP Integration**: Connect your local engines (LM Studio, Ollama) as Model Context Protocol (MCP) servers to Claude Desktop, Claude Code, OpenCode, Antigravity, VS Code, and ChatGPT/Codex with custom naming and cross-platform safety.
45
- - **Bilingual**: Full English and Spanish localization for both terminal and desktop interfaces.
45
+ - **Remote Server & Daemon Mode**: Run `llm-tuner /serve` on a dedicated GPU machine with timing-safe Bearer token authentication and sanitized hardware telemetry, then control it from any local CLI or Desktop client.
46
+ - **AI Agent MCP Integration**: Connect your local or remote engines (LM Studio, Ollama) as Model Context Protocol (MCP) servers to Claude Desktop, Claude Code, OpenCode, Antigravity, VS Code, and ChatGPT/Codex.
47
+ - **Bilingual**: Full English (`en`) and Spanish (`es`) localization across both terminal and desktop interfaces.
46
48
 
47
49
  ---
48
50
 
49
51
  ## How It Works
50
52
 
51
- 1. **Engine Selection**: Select LM Studio or Ollama. If not installed, LLM Tuner provides official installer commands.
52
- 2. **Model & Context**: Pick any locally downloaded model and set the desired context window (e.g. 16,384 tokens).
53
- 3. **Load Profile**: Choose between `speed`, `balanced` (default), or `quality`. The system highlights a recommended profile based on free VRAM.
54
- 4. **Load**:
55
- - **Preset Hit**: If a preset exists for that exact model, context, profile, and hardware, it applies immediately and loads the model (~20s).
56
- - **Preset Miss**: Unloads existing models to free VRAM, evaluates top candidate configurations (2–3 min), benchmarks actual generation speeds, saves the best preset, and loads the model.
53
+ 1. **Engine Selection**: Select LM Studio or Ollama. If neither is installed, LLM Tuner offers official installation plans.
54
+ 2. **Model & Context**: Pick any locally downloaded model and set the desired context window (between `512` and the model's training limit, e.g., `16384` tokens).
55
+ 3. **Load Profile**: Choose between `speed`, `balanced` (default), or `quality`. The planner recommends the highest-quality profile that fits fully in free VRAM.
56
+ 4. **Load & Benchmark**:
57
+ - **Preset Hit**: If a valid preset exists for that exact model, context, profile, and hardware fingerprint, it is applied and loaded immediately (~20s).
58
+ - **Preset Miss**: Unloads loaded models to free VRAM, evaluates top candidate configurations (2–3 min), measures short and deep-context generation speeds, saves the winning preset, and loads the model.
57
59
 
58
- Benchmarks run again only if the context or profile changes, the hardware changes (GPU, VRAM, PCIe link), the model file changes, or downloaded variants change (for `speed` and `quality`). You can also force a benchmark run with `--force`.
60
+ Benchmarks run again only if the context or profile changes, the hardware changes (GPU, VRAM, PCIe link), the model file changes, or downloaded variants change (for `speed` and `quality`). You can also force re-measurement anytime with `--force`.
59
61
 
60
62
  ---
61
63
 
62
64
  ## Load Profiles
63
65
 
64
- More tokens per second does not always equal better answers. Profiles let you decide when answer quality can be traded for speed or memory efficiency:
66
+ More tokens per second does not always mean better answers. Load profiles control when answer quality can be traded for speed or memory efficiency:
65
67
 
66
- | Profile | Optimization Goal | Trade-offs & Behaviour |
67
- |---|---|---|
68
- | `speed` | Maximum tokens per second. | Uses the lightest downloaded quantization variant and any KV cache type (including `q4_0`). |
69
- | `balanced` | Speed weighted by KV cache quality. *(Default)* | Balanced trade-off; runs with the selected variant and weighs speed by KV precision. |
70
- | `quality` | Zero degradation that still fits fully on GPU. | Keeps the heaviest downloaded variant that fits entirely into VRAM, using precise KV cache (`f16` or `q8_0`). Falls back to `balanced` if no full offload is possible. |
68
+ | Profile | Optimization Goal | Trade-offs & Behavior |
69
+ | :--- | :--- | :--- |
70
+ | `speed` | Maximum tokens per second. | Selects the lightest downloaded quantization variant and allows any KV cache type (`f16`, `q8_0`, `q4_0`). |
71
+ | `balanced` | Speed weighted by KV cache quality. *(Default)* | Uses the selected model variant and ranks candidates by `tps × kvQuality`. |
72
+ | `quality` | Least degradation that still runs fully on GPU. | Picks the heaviest downloaded variant that fits 100% into VRAM with a precise KV cache (`f16` or `q8_0`). Falls back to `balanced` if no full-GPU config fits. |
71
73
 
72
- Lossless optimizations (layer distribution across GPUs, thread count, GPU selection order) are applied across all profiles.
74
+ Lossless optimizations (layer distribution across GPUs, GPU ordering, CPU thread count) are applied across all profiles.
73
75
 
74
76
  ### Variant Switching
75
77
 
76
- - **Ollama**: Automatically selects and loads the best tag variant for the active profile among tags identified as the same model.
77
- - **LM Studio**: Since LM Studio cannot switch variants programmatically, LLM Tuner optimizes the active variant and advises you when another variant better suits your chosen profile.
78
+ - **Ollama**: Automatically selects and loads the optimal tag variant for the active profile among tags recognized as the same model family.
79
+ - **LM Studio**: Optimizes the currently active variant and notifies you if another downloaded (or downloadable `Q8_0` / `Q6_K` / `Q5_K_M`) quantization better suits your chosen profile.
78
80
 
79
81
  ---
80
82
 
@@ -86,9 +88,16 @@ Install globally via npm:
86
88
  npm install -g llm-tuner
87
89
  ```
88
90
 
89
- Prerequisites:
90
- - **Node.js**: version 22 or higher (`node >= 22`).
91
- - **Local Engine**: LM Studio (0.4.25+) or Ollama installed (or let the wizard guide installation).
91
+ Or run directly without permanent installation via `npx`:
92
+
93
+ ```bash
94
+ npx llm-tuner
95
+ ```
96
+
97
+ ### Prerequisites
98
+
99
+ - **Node.js**: `>= 22` (`engines.node` in [`package.json`](package.json)).
100
+ - **Local Engine**: LM Studio (0.4.25+) or Ollama installed (or install via the built-in wizard).
92
101
 
93
102
  ---
94
103
 
@@ -96,167 +105,235 @@ Prerequisites:
96
105
 
97
106
  ### Interactive CLI & Slash Commands
98
107
 
99
- Launch the terminal interface:
108
+ Launch the interactive terminal menu:
100
109
 
101
110
  ```bash
102
111
  llm-tuner
103
112
  ```
104
113
 
105
- Running `llm-tuner` without arguments presents an interactive menu to choose a command:
106
- - `/load`: Tune and load a model with optimal hardware settings
107
- - `/mcp`: Connect your local engine as an MCP server to AI agents (Claude, OpenCode, VS Code, Codex)
108
- - `/presets`: Inspect saved tuning presets
109
- - `/settings`: Configure language (`en` / `es`) and visual theme (`system` / `light` / `dark`)
110
- - `/help`: Display help and usage options
111
- - `/exit`: Exit the interactive menu
114
+ Running `llm-tuner` without arguments opens an interactive command loop:
115
+ - `/load`: Tune and load a model with optimal hardware settings.
116
+ - `/mcp`: Connect your local engine to AI coding agents as an MCP server.
117
+ - `/presets`: View saved tuning presets.
118
+ - `/serve`: Start remote server mode with API authentication.
119
+ - `/settings`: Configure interface language (`en` / `es`) and visual theme (`system` / `light` / `dark`).
120
+ - `/help`: View usage help and CLI flags.
121
+ - `/exit`: Exit the interactive menu.
112
122
 
113
- You can also run any slash command directly from your terminal:
123
+ You can also invoke any slash command (or its plain positional alias) directly from your shell:
114
124
 
115
125
  ```bash
116
126
  llm-tuner /load
117
127
  llm-tuner /mcp
118
128
  llm-tuner /presets
129
+ llm-tuner /serve
119
130
  llm-tuner /settings
131
+ llm-tuner /help
120
132
  ```
121
133
 
122
- ### Desktop App
134
+ ### Desktop Application
123
135
 
124
- Launch the desktop interface:
136
+ Launch the graphical desktop app:
125
137
 
126
138
  ```bash
127
139
  llm-tuner-desktop
128
140
  ```
129
141
 
130
- > **Note**: Electron downloads automatically during the first launch of `llm-tuner-desktop`. A working network connection is required for that initial launch.
142
+ > [!NOTE]
143
+ > The Electron desktop runtime is downloaded on first launch if not already cached. On Linux, `llm-tuner-desktop` also registers a desktop entry at `~/.local/share/applications/llm-tuner.desktop`.
144
+
145
+ ### Remote Server Mode & Daemon
146
+
147
+ Run LLM Tuner as a network daemon on a dedicated GPU host:
148
+
149
+ ```bash
150
+ # Start on default 0.0.0.0:7860 (generates and saves a 32-byte hex token if omitted)
151
+ llm-tuner /serve
152
+
153
+ # Customize host, port, and Bearer token
154
+ llm-tuner /serve --host 0.0.0.0 --port 7860 --token my-secret-token
155
+ ```
156
+
157
+ - **Security & Telemetry Sanitization**: Remote and server-mode requests require `Authorization: Bearer <token>` verified via `crypto.timingSafeEqual`. Remote `/api/state` responses strip host-identifying details (OS distro, CPU brand, PCI bus IDs, GPU UUIDs) and block remote `/api/install` calls (`403 Forbidden`).
158
+ - **Client Connection**: Add the remote server (Name, URL, Token) in the Desktop/Web UI under **Settings → Servers**, or select a configured server from the CLI with `--server <id|name>`.
159
+
160
+ ### MCP Server Integration for AI Agents
161
+
162
+ Register LM Studio (`@mzxrai/mcp-openai`) or Ollama (`ollama-mcp-server`) across your installed AI coding agents:
163
+
164
+ ```bash
165
+ # Interactive wizard
166
+ llm-tuner /mcp
167
+
168
+ # With flags
169
+ llm-tuner /mcp --engine lmstudio --name my-local-llm --host http://127.0.0.1:1234/v1 --apiKey sk-lm-local
170
+ ```
171
+
172
+ Supported AI agents:
173
+ - **Claude Desktop** (`claude_desktop_config.json`)
174
+ - **Claude Code** (`~/.claude.json`)
175
+ - **OpenCode** (`opencode.json`)
176
+ - **Antigravity** (`~/.gemini/config/mcp_config.json`)
177
+ - **VS Code** (`Code/User/mcp.json`)
178
+ - **ChatGPT / Codex** (`~/.codex/config.toml`)
131
179
 
132
180
  ### Non-Interactive CLI
133
181
 
134
182
  Run directly in headless, scripted, or CI environments:
135
183
 
136
184
  ```bash
137
- # Basic run with balanced profile
185
+ # Tune and load non-interactively with the default balanced profile
138
186
  llm-tuner --engine lmstudio --model qwen/qwen2.5-coder-32b --ctx 16384 --yes
139
187
 
140
- # Specify load profile
141
- llm-tuner --engine lmstudio --model qwen/qwen2.5-coder-32b --ctx 16384 --profile quality --yes
188
+ # Optimize for maximum answer quality and output JSON
189
+ llm-tuner --engine ollama --model qwen2.5-coder:14b --ctx 32768 --profile quality --yes --json
142
190
 
143
- # List saved presets
144
- llm-tuner --presets
191
+ # Dry-run measurement without modifying engine files or saving a preset
192
+ llm-tuner --engine lmstudio --model qwen/qwen2.5-coder-32b --ctx 16384 --dry-run
145
193
 
146
- # Configure language and theme
147
- llm-tuner /settings
194
+ # Target a configured remote server
195
+ llm-tuner --server gpu-rig --engine ollama --model llama3.1:8b --ctx 16384 --yes
148
196
  ```
149
197
 
150
- ### CLI Options Reference
151
-
152
- | Flag | Description | Default |
153
- |---|---|---|
154
- | `--engine <lmstudio\|ollama>` | Target engine. | Interactive prompt |
155
- | `--model <key>` | Model key or Ollama tag. | Interactive prompt |
156
- | `--ctx <tokens>` | Context length in tokens (e.g. `16384`). | Interactive prompt |
198
+ ### CLI Options & Flags Reference
199
+
200
+ | Flag / Command | Description | Default |
201
+ | :--- | :--- | :--- |
202
+ | `/load`, `load` | Tune and load a model with optimal hardware settings. | Default when tuning flags are passed |
203
+ | `/mcp`, `mcp` | Configure LM Studio or Ollama as an MCP server for AI agents. | — |
204
+ | `/presets`, `presets`, `--presets` | List saved tuning presets (filterable by `--engine` and `--model`). | — |
205
+ | `/serve`, `serve`, `--serve` | Start remote server daemon mode with Bearer authentication. | — |
206
+ | `/settings`, `settings`, `--settings` | Open the interactive settings menu (language & theme). | — |
207
+ | `/help`, `help`, `-h`, `--help` | Show CLI usage help and presets directory path. | — |
208
+ | `-v`, `--version` | Print the installed version (`0.4.4`). | — |
209
+ | `--engine <lmstudio\|ollama>` | Target local LLM engine. | Interactive prompt |
210
+ | `--model <key>` | Model key (LM Studio) or model tag (Ollama). | Interactive prompt |
211
+ | `--ctx <tokens>` | Context length in tokens (e.g., `16384` or `16k`). | Interactive prompt |
157
212
  | `--profile <speed\|balanced\|quality>` | Load profile to optimize for. | `balanced` |
158
- | `--force` | Force re-measurement even if a valid preset exists. | `false` |
213
+ | `--force` | Re-run benchmarks even if a valid preset already exists. | `false` |
159
214
  | `--candidates <n>` | Number of top candidate configurations to benchmark. | `3` |
160
- | `--yes` | Run non-interactively without confirmation prompts. | `false` |
161
- | `--dry-run` | Measure without saving presets or modifying engine configurations. | `false` |
162
- | `--presets` | List all saved presets. | — |
163
- | `--json` | Output results as JSON. | `false` |
164
- | `--lang <en\|es>` | Set and persist interface language. | System / `en` |
165
- | `--theme <system\|light\|dark>` | Set and persist interface theme. | `system` |
166
- | `/settings` | Open interactive settings menu. | — |
167
- | `--help` | Show usage instructions. | — |
215
+ | `--yes` | Skip confirmation prompts. | `false` |
216
+ | `--dry-run` | Measure and preview changes without applying config or saving a preset. | `false` |
217
+ | `--json` | Emit final execution result as JSON. | `false` |
218
+ | `--lang <en\|es>` | Set and persist interface language. | Saved setting / `en` |
219
+ | `--theme <system\|light\|dark>` | Set and persist visual theme. | `system` |
220
+ | `--server <id\|name>` | Set the active target server from saved settings. | `local` |
221
+ | `--host <host>` | Bind address for `/serve` or custom endpoint host for `/mcp`. | `0.0.0.0` (`/serve`) |
222
+ | `--port <port>` | TCP port for `/serve`. | `7860` |
223
+ | `--token <token>` | Authentication Bearer token for `/serve`. | Auto-generated 32-byte hex |
224
+ | `--name <name>` | Custom MCP server name when running `/mcp`. | Engine ID (`lmstudio` / `ollama`) |
225
+ | `--apiKey <key>` | Custom API key for LM Studio when running `/mcp`. | `sk-lm-local` |
168
226
 
169
227
  ---
170
228
 
171
229
  ## Tuning Pipeline
172
230
 
173
- For each KV cache type allowed by the selected profile (`f16`, `q8_0`, `q4_0`):
231
+ For each KV cache type permitted by the active profile (`f16`, `q8_0`, `q4_0`):
174
232
 
175
- 1. **GGUF Tensor Inspection**: Parses GGUF tensors to obtain actual byte sizes per layer.
176
- 2. **Context Memory Modeling**: Models KV cache size taking into account attention heads, dimensions, and sliding window attention (SWA).
177
- 3. **Multi-GPU Split**: Allocates layers proportionally to VRAM headroom, ordering GPUs by PCIe bandwidth and avoiding active display adapters.
178
- 4. **MoE Offloading**: For Mixture of Experts (MoE) architectures that exceed VRAM, calculates the exact expert count that can reside in system RAM (`--n-cpu-moe`).
179
- 5. **Empirical Benchmarks**: Benchmarks candidates using both a short prompt and a deep prompt filling ~50% of the context.
180
- 6. **Configuration Application**:
181
- - **LM Studio**: Updates per-model config in `~/.lmstudio/.internal/user-concrete-model-default-config/` and updates `hardware-config.json`.
182
- - **Ollama**: Creates an optimized Modelfile (`<model>-tuned-<ctx>`) and sets system server environment variables. A backup (`*.bak-llm-tuner-*`) is created before changing files.
233
+ 1. **GGUF Tensor Inspection**: Reads GGUF tensor headers directly to determine exact byte weights per layer, embedding, and output head.
234
+ 2. **Context Memory Modeling**: Computes KV cache footprint across attention heads, head dimensions, and sliding window attention (SWA) layers.
235
+ 3. **Multi-GPU Layer Placement**: Distributes layers across GPUs proportionally to usable VRAM headroom, ranking GPUs by PCIe generation/width and penalizing display-attached cards.
236
+ 4. **MoE Expert Offloading**: For Mixture-of-Experts (MoE) models that exceed VRAM, calculates how many expert layers can spill into system RAM (`cpuMoeLayers` / `--n-cpu-moe`).
237
+ 5. **Empirical Benchmarking**: Measures token generation throughput on a short prompt and a deep prompt (~50% of target context), sampling CPU load and peak VRAM.
238
+ 6. **Atomic Application & Backups**:
239
+ - **LM Studio**: Writes per-model configuration under `~/.lmstudio/.internal/user-concrete-model-default-config/` and updates `hardware-config.json`.
240
+ - **Ollama**: Creates a tuned model tag (`<model>-tuned-<ctx>`) with fixed `num_ctx`, `num_gpu`, and `num_thread`, and configures service environment variables (`OLLAMA_FLASH_ATTENTION`, `OLLAMA_KV_CACHE_TYPE`). Timestamped backups (`*.bak-llm-tuner-*`) are created before modifying files.
183
241
 
184
242
  ---
185
243
 
186
- ## Preset Storage
244
+ ## Architecture & Storage Paths
187
245
 
188
- Presets are stored deterministically per engine, model, context, and profile:
246
+ ### Repository Structure
189
247
 
190
- - **Linux**: `~/.config/llm-tuner/presets/`
191
- - **macOS**: `~/Library/Application Support/llm-tuner/presets/`
192
- - **Windows**: `%APPDATA%\llm-tuner\presets\`
248
+ ```text
249
+ llmtuner/
250
+ ├── src/
251
+ │ ├── cli/ # CLI router, interactive wizard, and slash commands
252
+ │ │ ├── commands/ # /load, /mcp, /presets, /serve, /settings, /help
253
+ │ │ ├── animation.js # Live terminal telemetry and progress animation
254
+ │ │ ├── colors.js # Theme-aware ANSI styling
255
+ │ │ ├── index.js # CLI binary entrypoint (llm-tuner)
256
+ │ │ ├── nav.js # Backspace/escape step navigation wrapper
257
+ │ │ ├── profile.js # CLI profile selection helpers
258
+ │ │ └── router.js # Argument parser and interactive menu loop
259
+ │ ├── core/ # Hardware detection, GGUF parser, estimator, presets, MCP
260
+ │ │ ├── engines/ # LM Studio and Ollama engine adapters
261
+ │ │ ├── mcp/ # Multi-agent MCP registry plus JSON and TOML adapters
262
+ │ │ ├── benchmark.js # Short and deep-context benchmark runner
263
+ │ │ ├── client.js # Unified HTTP/SSE TunerApiClient for local/remote servers
264
+ │ │ ├── estimator.js # VRAM placement and KV cache modeling
265
+ │ │ ├── gguf.js # Binary GGUF header and tensor reader
266
+ │ │ ├── hardware.js # GPU, PCIe, RAM, and CPU topology detection
267
+ │ │ ├── installer.js # Cross-platform engine installation plans
268
+ │ │ ├── llama-server.js # Direct llama-server harness for LM Studio benchmarks
269
+ │ │ ├── presets.js # Hardware-fingerprinted preset storage
270
+ │ │ ├── profiles.js # Speed, balanced, and quality profile rules
271
+ │ │ ├── settings.js # Persistent user settings and server profiles
272
+ │ │ ├── telemetry.js # Live CPU, RAM, and GPU VRAM sampling
273
+ │ │ ├── tuner.js # Main Tuner orchestrator class
274
+ │ │ └── updater.js # npm registry version check
275
+ │ ├── i18n/ # English (en.js) and Spanish (es.js) catalogs
276
+ │ ├── platform/ # Privileged Ollama service authorization (sudo/polkit)
277
+ │ └── web/ # Embedded HTTP + SSE server and static Desktop/Web UI
278
+ ├── electron/ # Desktop launcher (launch.js) and Electron main process
279
+ ├── scripts/ # Local utility and evaluation helper scripts
280
+ ├── test/ # Node.js built-in test suite (test/*.test.js)
281
+ └── docs/ # Design specs, user stories, and hardware evaluation reports
282
+ ```
193
283
 
194
- ---
284
+ ### Configuration & Preset Paths
195
285
 
196
- ## Platform Support
286
+ By default, settings (`settings.json`), server config (`server.json`), and presets (`presets/*.json`) are stored in the per-user configuration directory (overridable via the `LLM_TUNER_CONFIG_DIR` environment variable):
197
287
 
198
- | Platform & Engine | Status | Notes |
199
- |---|---|---|
200
- | **Linux + NVIDIA + LM Studio** | ✅ Verified | Calibrated with real measurements (2× RTX 5070, Ryzen 7 5800X). |
201
- | **Linux + Ollama** | ✅ Verified | Tested via private instance harnesses and unit test suite. |
202
- | **Windows / macOS** | 🧪 Supported | Official paths, system commands, and detection logic implemented. |
203
- | **AMD ROCm / Apple Silicon / Intel Arc** | 🧪 Supported | Hardware VRAM detection enabled; memory bandwidth sourced from lookup tables. |
288
+ | Operating System | Base Configuration Directory | Presets Directory |
289
+ | :--- | :--- | :--- |
290
+ | **Linux** | `~/.config/llm-tuner/` (or `$XDG_CONFIG_HOME/llm-tuner/`) | `~/.config/llm-tuner/presets/` |
291
+ | **macOS** | `~/Library/Application Support/llm-tuner/` | `~/Library/Application Support/llm-tuner/presets/` |
292
+ | **Windows** | `%APPDATA%\llm-tuner\` | `%APPDATA%\llm-tuner\presets\` |
204
293
 
205
294
  ---
206
295
 
207
- ## Project Architecture
296
+ ## Platform Support
208
297
 
209
- ```
210
- llmtuner/
211
- ├── src/
212
- │ ├── core/ # Core tuning engine, estimator, hardware detection, presets, profiles
213
- │ │ ├── engines/ # LM Studio and Ollama driver adapters
214
- │ │ ├── estimator.js# Mathematical memory and VRAM modeling
215
- │ │ ├── gguf.js # GGUF tensor parser
216
- │ │ ├── hardware.js # GPU detection, PCIe links, VRAM monitoring
217
- │ │ ├── presets.js # Preset persistence and invalidation logic
218
- │ │ └── profiles.js # Speed, balanced, and quality profile rules
219
- │ ├── cli/ # Interactive wizard and CLI argument parsing
220
- │ ├── i18n/ # English and Spanish localization catalogs
221
- │ └── web/ # Embedded HTTP/SSE server powering the desktop GUI
222
- ├── electron/ # Desktop launcher and main process wrapper
223
- ├── test/ # Node.js native test suite
224
- └── docs/ # Technical specs, real benchmark measurements, and notes
225
- ```
298
+ | Platform & Engine | Status | Notes |
299
+ | :--- | :--- | :--- |
300
+ | **Linux + NVIDIA + LM Studio** | Verified | Calibrated with real hardware measurements (2× RTX 5070, Ryzen 7 5800X). |
301
+ | **Linux + Ollama** | Verified | Tested with isolated benchmark instances, systemd overrides, and unit tests. |
302
+ | **Windows / macOS** | Supported | Official paths, installer plans, `setx`/`launchctl` environment updates, and GPU detection implemented. |
303
+ | **AMD ROCm / Apple Silicon / Intel Arc** | Supported | VRAM and unified memory detection supported; bandwidth estimated from architecture lookup tables. |
226
304
 
227
305
  ---
228
306
 
229
307
  ## Development & Testing
230
308
 
231
- Clone and set up locally:
309
+ Clone the repository and install dependencies:
232
310
 
233
311
  ```bash
234
312
  git clone https://github.com/ronald32acunadev/llmtuner.git
235
313
  cd llmtuner
236
314
  npm install
237
315
 
238
- # Start CLI wizard
316
+ # Run the CLI wizard
239
317
  npm start
240
318
 
241
- # Start Desktop app
319
+ # Launch the Electron desktop application
242
320
  npm run desktop
243
321
 
244
- # Run test suite
322
+ # Run the automated Node.js test suite
245
323
  npm test
246
324
  ```
247
325
 
248
- The test suite includes regression tests verifying real dual-RTX 5070 measurements with Qwen2.5-Coder-32B.
249
-
250
326
  ---
251
327
 
252
- ## Notes & Troubleshooting
328
+ ## Troubleshooting & Platform Notes
253
329
 
254
- - **Desktop Runtime Download**: The Electron runtime is downloaded on the first execution of `llm-tuner-desktop`. If downloading in a restricted offline environment, extract `electron-v*.zip` into the `dist/` directory of the installed `electron` package and create `path.txt` with the content `electron`.
255
- - **Root / Sudo Installs**: Avoid running `npm install -g --unsafe-perm` under root without proper directory ownership, as Electron may fail to unpack its binary into user space.
256
- - **LM Studio Compatibility**: Internal config paths are calibrated and tested against LM Studio 0.4.25+.
330
+ - **First Desktop Launch (`llm-tuner-desktop`)**: The Electron binary is downloaded on initial install/run. If Electron cannot start, run `llm-tuner` in the terminal—it works without the desktop runtime.
331
+ - **Linux Sandbox on NTFS/exFAT or Non-Root Mounts**: `electron/launch.js` automatically checks `chrome-sandbox` permissions on Linux and sets `ELECTRON_DISABLE_SANDBOX=1` when SUID root (`04755`) is unavailable on the filesystem.
332
+ - **Ollama Service Authorization**: Updating Ollama server environment variables (`OLLAMA_KV_CACHE_TYPE`, `OLLAMA_FLASH_ATTENTION`) on Linux may prompt for administrator authorization (`sudo` or `pkexec`). If settings are already up to date, the service is not restarted. Non-interactive (`--json`) runs never block on password prompts.
333
+ - **LM Studio Must Be Closed Before Applying**: LM Studio overwrites its configuration files on exit; close LM Studio before applying a new hardware configuration if prompted.
257
334
 
258
335
  ---
259
336
 
260
337
  ## License
261
338
 
262
- [MIT](LICENSE) © Ronald Daniel Acuña Arias
339
+ Released under the [MIT License](LICENSE).
@@ -28,6 +28,7 @@ try {
28
28
  await fail(error);
29
29
  }
30
30
  const env = { ...process.env };
31
+ env.LLM_TUNER_NODE_EXEC_PATH = process.execPath;
31
32
 
32
33
  if (process.platform === 'linux') {
33
34
  try {
package/electron/main.js CHANGED
@@ -1,7 +1,13 @@
1
- import { app, BrowserWindow, shell, nativeImage } from 'electron';
1
+ import { app, BrowserWindow, shell, nativeImage, ipcMain, clipboard } from 'electron';
2
2
  import path from 'node:path';
3
+ import fs from 'node:fs/promises';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  import { startServer } from '../src/web/server.js';
6
+ import { createOllamaAuthorizer } from '../src/platform/ollama-authorize.js';
7
+ import { createServerAutostart } from '../src/platform/server-autostart.js';
8
+ import { readServerConfig, serverConfigPath } from '../src/cli/commands/serve.js';
9
+ import { createServerControls, isTrustedServerSender } from './server-controls.js';
10
+ import { resolveConnectableHost } from '../src/core/util.js';
5
11
 
6
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
13
 
@@ -20,7 +26,7 @@ if (process.platform === 'win32') {
20
26
  let server;
21
27
 
22
28
  async function createWindow() {
23
- const started = await startServer({ port: 0 });
29
+ const started = await startServer({ port: 0, authorizeOllama: createOllamaAuthorizer({ mode: 'desktop' }) });
24
30
  server = started.server;
25
31
 
26
32
  const iconFile = process.platform === 'win32' ? 'icon.ico' : 'icon.png';
@@ -33,7 +39,11 @@ async function createWindow() {
33
39
  title: 'LLM Tuner',
34
40
  icon: !icon.isEmpty() ? icon : path.join(__dirname, iconFile),
35
41
  autoHideMenuBar: true,
36
- webPreferences: { contextIsolation: true, sandbox: true },
42
+ webPreferences: {
43
+ contextIsolation: true,
44
+ sandbox: true,
45
+ preload: path.join(__dirname, 'preload.cjs'),
46
+ },
37
47
  });
38
48
 
39
49
  // External links open in the system browser.
@@ -42,6 +52,43 @@ async function createWindow() {
42
52
  return { action: 'deny' };
43
53
  });
44
54
 
55
+ const controls = createServerControls({
56
+ isTrusted: (event) => isTrustedServerSender(event, win.webContents, new URL(started.url).origin),
57
+ autostart: {
58
+ status: () => createServerAutostart({ configPath: serverConfigPath() }).status(),
59
+ setEnabled: async (enabled) => {
60
+ const nodePath = process.env.LLM_TUNER_NODE_EXEC_PATH;
61
+ if (enabled) {
62
+ if (!nodePath || !path.isAbsolute(nodePath)) {
63
+ throw new Error('Please launch using llm-tuner-desktop to enable autostart.');
64
+ }
65
+ await fs.access(nodePath, fs.constants.X_OK);
66
+ }
67
+ return await createServerAutostart({
68
+ configPath: serverConfigPath(),
69
+ execPath: enabled ? (nodePath || process.execPath) : undefined,
70
+ }).setEnabled(enabled);
71
+ },
72
+ },
73
+ readConfig: readServerConfig,
74
+ resolveHost: resolveConnectableHost,
75
+ copyText: async (text) => {
76
+ await clipboard.writeText(text);
77
+ },
78
+ });
79
+
80
+ ipcMain.handle('llm-tuner:server-autostart-status', (event) => controls.getAutostartStatus(event));
81
+ ipcMain.handle('llm-tuner:server-autostart-set', (event, enabled) => controls.setAutostartEnabled(event, enabled));
82
+ ipcMain.handle('llm-tuner:server-connection-reveal', (event) => controls.revealConnection(event));
83
+ ipcMain.handle('llm-tuner:server-token-copy', (event) => controls.copyToken(event));
84
+
85
+ win.on('closed', () => {
86
+ ipcMain.removeHandler('llm-tuner:server-autostart-status');
87
+ ipcMain.removeHandler('llm-tuner:server-autostart-set');
88
+ ipcMain.removeHandler('llm-tuner:server-connection-reveal');
89
+ ipcMain.removeHandler('llm-tuner:server-token-copy');
90
+ });
91
+
45
92
  await win.loadURL(started.url);
46
93
  }
47
94
 
@@ -0,0 +1,8 @@
1
+ const { contextBridge, ipcRenderer } = require('electron');
2
+
3
+ contextBridge.exposeInMainWorld('llmTunerServer', Object.freeze({
4
+ getAutostartStatus: () => ipcRenderer.invoke('llm-tuner:server-autostart-status'),
5
+ setAutostartEnabled: (enabled) => ipcRenderer.invoke('llm-tuner:server-autostart-set', enabled),
6
+ revealConnection: () => ipcRenderer.invoke('llm-tuner:server-connection-reveal'),
7
+ copyToken: () => ipcRenderer.invoke('llm-tuner:server-token-copy')
8
+ }));
@@ -0,0 +1,69 @@
1
+ import { resolveConnectableHost } from '../src/core/util.js';
2
+
3
+ export function isTrustedServerSender(event, webContents, expectedOrigin) {
4
+ if (!event || !webContents) return false;
5
+ const frame = event.senderFrame;
6
+ if (!frame) return false;
7
+ return (
8
+ event.sender === webContents &&
9
+ frame === webContents.mainFrame &&
10
+ frame.origin === expectedOrigin
11
+ );
12
+ }
13
+
14
+ export function createServerControls({ isTrusted, autostart, readConfig, copyText, resolveHost }) {
15
+ const checkAuth = (event) => {
16
+ if (!isTrusted(event)) {
17
+ throw new Error('Local server controls are not authorized.');
18
+ }
19
+ };
20
+
21
+ return {
22
+ async getAutostartStatus(event) {
23
+ checkAuth(event);
24
+ return await autostart.status();
25
+ },
26
+
27
+ async setAutostartEnabled(event, enabled) {
28
+ checkAuth(event);
29
+ if (typeof enabled !== 'boolean') {
30
+ throw new Error('Value must be a boolean');
31
+ }
32
+ const result = await autostart.setEnabled(enabled);
33
+ if (result !== enabled) {
34
+ throw new Error(`Could not confirm autostart state change to ${enabled}`);
35
+ }
36
+ return result;
37
+ },
38
+
39
+ async revealConnection(event) {
40
+ checkAuth(event);
41
+ const config = await readConfig();
42
+ if (!config) {
43
+ return { configured: false };
44
+ }
45
+
46
+ const { host, port, token } = config;
47
+ if (
48
+ typeof host !== 'string' || host.length === 0 ||
49
+ !Number.isInteger(port) || port < 1 || port > 65535 ||
50
+ typeof token !== 'string' || token.length === 0
51
+ ) {
52
+ throw new Error('Invalid server configuration');
53
+ }
54
+
55
+ const displayHost = resolveHost ? resolveHost(host) : host;
56
+ return { configured: true, host: displayHost, port, token };
57
+ },
58
+
59
+ async copyToken(event) {
60
+ checkAuth(event);
61
+ const config = await readConfig();
62
+ if (!config || typeof config.token !== 'string' || config.token.length === 0) {
63
+ throw new Error('Configuration error: no token available to copy');
64
+ }
65
+ await copyText(config.token);
66
+ return { copied: true };
67
+ }
68
+ };
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-tuner",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Detects your hardware and finds the fastest configuration to run local LLMs with LM Studio or Ollama.",
5
5
  "keywords": [
6
6
  "llm",