Skip to content

Commit ead5673

Browse files
PurpleDoubleDclaude
andcommitted
fix: model unload, GPU offloading, error logging (v2.2.1)
- Fix broken model unloading (add missing `prompt` field to /generate call) - Enable GPU offloading by default (num_gpu: 99 on all chat endpoints) - Replace silent .catch(() => {}) with console.warn for debugging - Bump version to 2.2.1 across all configs, docs, and landing page Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e6ce15c commit ead5673

9 files changed

Lines changed: 34 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
All notable changes to Locally Uncensored are documented here.
44

5+
## [2.2.1] - 2026-04-04
6+
7+
### Fixed
8+
- **Model unloading broken** — unload button and automatic unload on model switch silently failed (missing `prompt` field in Ollama `/generate` call), causing models to stay in RAM indefinitely
9+
- **No GPU offloading** — models ran entirely on CPU/RAM instead of GPU; added `num_gpu: 99` to all Ollama chat calls so layers are offloaded to GPU automatically (Ollama splits between GPU and CPU if VRAM is insufficient)
10+
- **Silent error swallowing** — unload errors were caught and discarded with `.catch(() => {})`; now logged to console for debugging
11+
512
## [1.9.0] - 2026-04-03
613

714
### Added

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ No cloud. No censorship. No data collection. Your AI, your rules.
3434

3535
---
3636

37-
## v2.2 — Latest Release
37+
## v2.2.1 — Latest Release (Hotfix)
3838

39-
**New:** Custom dark titlebar (no more Windows chrome), branded NSIS installer, Qwen3-Coder integration, Download Manager with multi-pull + pause/resume, auto-unload on model switch, redesigned Model Selector, Update Checker
39+
**Fixed:** Model unloading now works correctly, GPU offloading enabled by default (`num_gpu: 99`), silent error swallowing replaced with console logging. Resolves high CPU/RAM usage on systems with dedicated GPUs.
4040

41-
See the full [Release Notes](https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.0).
41+
See the full [Release Notes](https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.1).
4242

4343
---
4444

docs/index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<!-- Setup: docker compose up -d on your server, then replace YOUR-DOMAIN below -->
2828
<script defer data-domain="YOUR-DOMAIN.com" src="https://plausible.YOUR-DOMAIN.com/js/script.js"></script>
2929
<script type="application/ld+json">
30-
{"@context":"https://schema.org","@type":"SoftwareApplication","name":"Locally Uncensored","applicationCategory":"DeveloperApplication","applicationSubCategory":"Artificial Intelligence","operatingSystem":"Windows, macOS, Linux","description":"All-in-one local AI desktop app for uncensored chat, image generation, and video creation. Run Llama, Mistral, Qwen, FLUX, Stable Diffusion, and Wan 2.1 models locally with zero cloud dependency.","url":"https://locallyuncensored.com/","downloadUrl":"https://github.com/PurpleDoubleD/locally-uncensored","softwareVersion":"2.2.0","license":"https://opensource.org/licenses/MIT","author":{"@type":"Person","name":"PurpleDoubleD","url":"https://github.com/PurpleDoubleD"},"offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"featureList":["Uncensored AI Chat via Ollama","Image Generation via ComfyUI and Stable Diffusion","Video Generation with Wan 2.1 and AnimateDiff","25+ Built-in AI Personas","One-Click Setup on Windows, macOS, Linux","Model Auto-Detection","100% Offline and Private","No Docker Required"],"screenshot":"https://raw.githubusercontent.com/PurpleDoubleD/locally-uncensored/master/docs/screenshots/marketing_03_chat_personas.png","softwareRequirements":"Node.js 18+, Ollama","memoryRequirements":"8 GB RAM minimum","storageRequirements":"6 GB for default model"}
30+
{"@context":"https://schema.org","@type":"SoftwareApplication","name":"Locally Uncensored","applicationCategory":"DeveloperApplication","applicationSubCategory":"Artificial Intelligence","operatingSystem":"Windows, macOS, Linux","description":"All-in-one local AI desktop app for uncensored chat, image generation, and video creation. Run Llama, Mistral, Qwen, FLUX, Stable Diffusion, and Wan 2.1 models locally with zero cloud dependency.","url":"https://locallyuncensored.com/","downloadUrl":"https://github.com/PurpleDoubleD/locally-uncensored","softwareVersion":"2.2.1","license":"https://opensource.org/licenses/MIT","author":{"@type":"Person","name":"PurpleDoubleD","url":"https://github.com/PurpleDoubleD"},"offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"featureList":["Uncensored AI Chat via Ollama","Image Generation via ComfyUI and Stable Diffusion","Video Generation with Wan 2.1 and AnimateDiff","25+ Built-in AI Personas","One-Click Setup on Windows, macOS, Linux","Model Auto-Detection","100% Offline and Private","No Docker Required"],"screenshot":"https://raw.githubusercontent.com/PurpleDoubleD/locally-uncensored/master/docs/screenshots/marketing_03_chat_personas.png","softwareRequirements":"Node.js 18+, Ollama","memoryRequirements":"8 GB RAM minimum","storageRequirements":"6 GB for default model"}
3131
</script>
3232
<script type="application/ld+json">
3333
{"@context":"https://schema.org","@type":"Organization","name":"PurpleDoubleD","url":"https://github.com/PurpleDoubleD","sameAs":["https://github.com/PurpleDoubleD","https://reddit.com/user/GroundbreakingMall54"]}
@@ -283,11 +283,11 @@ <h1>Run uncensored AI locally.<br>Chat. Images. Video.</h1>
283283
</section>
284284

285285
<section class="install reveal" id="install">
286-
<p style="margin-bottom:1.5rem;font-size:1.1rem;color:#ededed;font-weight:600">Download v2.2.0</p>
286+
<p style="margin-bottom:1.5rem;font-size:1.1rem;color:#ededed;font-weight:600">Download v2.2.1</p>
287287
<div style="display:flex;gap:1rem;justify-content:center;margin-bottom:2rem;flex-wrap:wrap">
288-
<a href="https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.0" class="btn btn-primary" style="font-size:.9rem;padding:.6rem 1.5rem">Windows (.exe)</a>
289-
<a href="https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.0" class="btn btn-primary" style="font-size:.9rem;padding:.6rem 1.5rem">Linux (.AppImage)</a>
290-
<a href="https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.0" class="btn" style="font-size:.9rem;padding:.6rem 1.5rem">All Downloads</a>
288+
<a href="https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.1" class="btn btn-primary" style="font-size:.9rem;padding:.6rem 1.5rem">Windows (.exe)</a>
289+
<a href="https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.1" class="btn btn-primary" style="font-size:.9rem;padding:.6rem 1.5rem">Linux (.AppImage)</a>
290+
<a href="https://github.com/PurpleDoubleD/locally-uncensored/releases/tag/v2.2.1" class="btn" style="font-size:.9rem;padding:.6rem 1.5rem">All Downloads</a>
291291
</div>
292292
<p style="color:var(--text-tertiary);font-size:.8rem;margin-bottom:2rem">macOS: <a href="https://github.com/PurpleDoubleD/locally-uncensored#-build-from-source-all-platforms">Build from source</a></p>
293293
<div class="install-cmd" onclick="navigator.clipboard.writeText('git clone https://github.com/PurpleDoubleD/locally-uncensored.git && cd locally-uncensored && setup.bat')">

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "locally-uncensored",
3-
"version": "2.2.0",
3+
"version": "2.2.1",
44
"private": false,
55
"description": "Private, local AI chat & image/video generation. No cloud, no censorship, no data collection. Powered by Ollama & ComfyUI.",
66
"license": "MIT",

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "locally-uncensored"
3-
version = "2.2.0"
3+
version = "2.2.1"
44
description = "Private, local AI chat & image/video generation"
55
authors = ["purpledoubled"]
66
edition = "2021"

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/schema.json",
33
"productName": "Locally Uncensored",
4-
"version": "2.2.0",
4+
"version": "2.2.1",
55
"identifier": "com.purpledoubled.locally-uncensored",
66
"build": {
77
"beforeBuildCommand": "npm run build",

src/api/ollama.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@ export async function chatStream(
3636
options: { temperature?: number; top_p?: number; top_k?: number; num_predict?: number } = {},
3737
signal?: AbortSignal
3838
): Promise<Response> {
39+
const opts = { num_gpu: 99, ...options }
3940
const res = await localFetchStream(ollamaUrl("/chat"), {
4041
method: "POST",
41-
body: JSON.stringify({ model, messages, options, stream: true }),
42+
body: JSON.stringify({ model, messages, options: opts, stream: true }),
4243
})
4344
if (!res.ok) throw new Error("Failed to start chat")
4445
return res
@@ -52,9 +53,10 @@ export async function chatStreamWithTools(
5253
options: { temperature?: number; top_p?: number; top_k?: number; num_predict?: number } = {},
5354
signal?: AbortSignal
5455
): Promise<Response> {
56+
const opts = { num_gpu: 99, ...options }
5557
const res = await localFetchStream(ollamaUrl("/chat"), {
5658
method: "POST",
57-
body: JSON.stringify({ model, messages, tools, options, stream: true }),
59+
body: JSON.stringify({ model, messages, tools, options: opts, stream: true }),
5860
})
5961
if (!res.ok) {
6062
// Try to extract Ollama's error message
@@ -79,7 +81,7 @@ export async function chatWithTools(
7981
const res = await localFetch(ollamaUrl("/chat"), {
8082
method: "POST",
8183
headers: { "Content-Type": "application/json" },
82-
body: JSON.stringify({ model, messages, tools, options, stream: false }),
84+
body: JSON.stringify({ model, messages, tools, options: { num_gpu: 99, ...options }, stream: false }),
8385
})
8486
if (!res.ok) {
8587
try {
@@ -170,16 +172,19 @@ export async function listRunningModels(): Promise<string[]> {
170172
}
171173

172174
export async function unloadModel(name: string): Promise<void> {
173-
await localFetch(ollamaUrl("/generate"), {
175+
const res = await localFetch(ollamaUrl("/generate"), {
174176
method: "POST",
175-
body: JSON.stringify({ model: name, keep_alive: 0 }),
177+
body: JSON.stringify({ model: name, prompt: "", keep_alive: 0 }),
176178
})
179+
if (!res.ok) {
180+
console.warn(`[ollama] failed to unload model "${name}":`, res.status)
181+
}
177182
}
178183

179184
export async function unloadAllModels(): Promise<number> {
180185
const running = await listRunningModels()
181186
for (const name of running) {
182-
try { await unloadModel(name) } catch { /* continue */ }
187+
try { await unloadModel(name) } catch (e) { console.warn(`[ollama] unloadAll: failed for "${name}":`, e) }
183188
}
184189
return running.length
185190
}

src/api/providers/ollama-provider.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,12 @@ export class OllamaProvider implements ProviderClient {
6666
stream: true,
6767
}
6868

69-
const ollamaOptions: Record<string, any> = {}
69+
const ollamaOptions: Record<string, any> = { num_gpu: 99 }
7070
if (options?.temperature !== undefined) ollamaOptions.temperature = options.temperature
7171
if (options?.topP !== undefined) ollamaOptions.top_p = options.topP
7272
if (options?.topK !== undefined) ollamaOptions.top_k = options.topK
7373
if (options?.maxTokens) ollamaOptions.num_predict = options.maxTokens
74-
if (Object.keys(ollamaOptions).length > 0) body.options = ollamaOptions
74+
body.options = ollamaOptions
7575

7676
const res = await localFetchStream(this.apiUrl('/chat'), {
7777
method: 'POST',
@@ -119,12 +119,12 @@ export class OllamaProvider implements ProviderClient {
119119
stream: false,
120120
}
121121

122-
const ollamaOptions: Record<string, any> = {}
122+
const ollamaOptions: Record<string, any> = { num_gpu: 99 }
123123
if (options?.temperature !== undefined) ollamaOptions.temperature = options.temperature
124124
if (options?.topP !== undefined) ollamaOptions.top_p = options.topP
125125
if (options?.topK !== undefined) ollamaOptions.top_k = options.topK
126126
if (options?.maxTokens) ollamaOptions.num_predict = options.maxTokens
127-
if (Object.keys(ollamaOptions).length > 0) body.options = ollamaOptions
127+
body.options = ollamaOptions
128128

129129
const res = await localFetch(this.apiUrl('/chat'), {
130130
method: 'POST',

src/stores/modelStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const useModelStore = create<ModelState>()(
4646
const prev = get().activeModel
4747
set({ activeModel: name })
4848
if (prev && prev !== name && !prev.includes('::')) {
49-
unloadModel(prev).catch(() => {})
49+
unloadModel(prev).catch((e) => console.warn('[modelStore] failed to unload previous model:', prev, e))
5050
}
5151
},
5252

0 commit comments

Comments
 (0)