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.es.md +202 -125
- package/README.md +197 -120
- package/electron/launch.js +1 -0
- package/electron/main.js +50 -3
- package/electron/preload.cjs +8 -0
- package/electron/server-controls.js +69 -0
- package/package.json +1 -1
- package/src/cli/commands/help.js +1 -0
- package/src/cli/commands/load.js +82 -32
- package/src/cli/commands/mcp.js +16 -2
- package/src/cli/commands/presets.js +12 -2
- package/src/cli/commands/serve.js +59 -0
- package/src/cli/commands/settings.js +60 -5
- package/src/cli/router.js +32 -0
- package/src/cli/server-login.js +47 -0
- package/src/core/client.js +127 -0
- package/src/core/engines/ollama-service.js +158 -0
- package/src/core/engines/ollama.js +16 -23
- package/src/core/index.js +3 -2
- package/src/core/settings.js +50 -2
- package/src/core/tuner.js +2 -2
- package/src/core/util.js +26 -0
- package/src/i18n/en.js +59 -1
- package/src/i18n/es.js +59 -1
- package/src/platform/ollama-authorize.js +79 -0
- package/src/platform/server-autostart.js +187 -0
- package/src/web/public/app.js +149 -3
- package/src/web/public/index.html +57 -0
- package/src/web/public/server-settings.js +127 -0
- package/src/web/server.js +207 -17
package/README.md
CHANGED
|
@@ -6,75 +6,77 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](package.json)
|
|
8
8
|
|
|
9
|
-
Load a local LLM with the
|
|
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
|
|
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
|
|
23
|
-
- [Desktop
|
|
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
|
-
- [
|
|
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
|
-
- [
|
|
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
|
|
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
|
|
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
|
-
- **
|
|
45
|
-
- **
|
|
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
|
|
52
|
-
2. **Model & Context**: Pick any locally downloaded model and set the desired context window (e.g
|
|
53
|
-
3. **Load Profile**: Choose between `speed`, `balanced` (default), or `quality`. The
|
|
54
|
-
4. **Load**:
|
|
55
|
-
- **Preset Hit**: If a preset exists for that exact model, context, profile, and hardware, it
|
|
56
|
-
- **Preset Miss**: Unloads
|
|
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
|
|
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
|
|
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 &
|
|
67
|
-
|
|
68
|
-
| `speed` | Maximum tokens per second. |
|
|
69
|
-
| `balanced` | Speed weighted by KV cache quality. *(Default)* |
|
|
70
|
-
| `quality` |
|
|
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,
|
|
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
|
|
77
|
-
- **LM Studio**:
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
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
|
|
106
|
-
- `/load`: Tune and load a model with optimal hardware settings
|
|
107
|
-
- `/mcp`: Connect your local engine
|
|
108
|
-
- `/presets`:
|
|
109
|
-
- `/
|
|
110
|
-
- `/
|
|
111
|
-
- `/
|
|
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
|
|
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
|
|
134
|
+
### Desktop Application
|
|
123
135
|
|
|
124
|
-
Launch the desktop
|
|
136
|
+
Launch the graphical desktop app:
|
|
125
137
|
|
|
126
138
|
```bash
|
|
127
139
|
llm-tuner-desktop
|
|
128
140
|
```
|
|
129
141
|
|
|
130
|
-
>
|
|
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
|
-
#
|
|
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
|
-
#
|
|
141
|
-
llm-tuner --engine
|
|
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
|
-
#
|
|
144
|
-
llm-tuner --
|
|
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
|
-
#
|
|
147
|
-
llm-tuner
|
|
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
|
-
|
|
|
155
|
-
|
|
|
156
|
-
| `--
|
|
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` |
|
|
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` |
|
|
161
|
-
| `--dry-run` | Measure without
|
|
162
|
-
| `--
|
|
163
|
-
| `--
|
|
164
|
-
| `--
|
|
165
|
-
| `--
|
|
166
|
-
| `/
|
|
167
|
-
| `--
|
|
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
|
|
231
|
+
For each KV cache type permitted by the active profile (`f16`, `q8_0`, `q4_0`):
|
|
174
232
|
|
|
175
|
-
1. **GGUF Tensor Inspection**:
|
|
176
|
-
2. **Context Memory Modeling**:
|
|
177
|
-
3. **Multi-GPU
|
|
178
|
-
4. **MoE Offloading**: For Mixture
|
|
179
|
-
5. **Empirical
|
|
180
|
-
6. **
|
|
181
|
-
- **LM Studio**:
|
|
182
|
-
- **Ollama**: Creates
|
|
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
|
-
##
|
|
244
|
+
## Architecture & Storage Paths
|
|
187
245
|
|
|
188
|
-
|
|
246
|
+
### Repository Structure
|
|
189
247
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
199
|
-
|
|
200
|
-
| **Linux
|
|
201
|
-
| **
|
|
202
|
-
| **Windows
|
|
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
|
-
##
|
|
296
|
+
## Platform Support
|
|
208
297
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
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
|
-
#
|
|
316
|
+
# Run the CLI wizard
|
|
239
317
|
npm start
|
|
240
318
|
|
|
241
|
-
#
|
|
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
|
-
##
|
|
328
|
+
## Troubleshooting & Platform Notes
|
|
253
329
|
|
|
254
|
-
- **Desktop
|
|
255
|
-
- **
|
|
256
|
-
- **
|
|
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)
|
|
339
|
+
Released under the [MIT License](LICENSE).
|
package/electron/launch.js
CHANGED
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: {
|
|
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
|
+
}
|