В прошлом посте я упоминал, что использую LLM.swift - библиотеку для запуска локальных LLM-моделей на macOS. Концепция крутая, но реализация - сырая. Чтобы ScreenCat стабильно работал, пришлось серьёзно допиливать.
Вот, что именно улучшил:
🔸 1. Чистка контекста между запросами
Без
clearContext() модель сохраняла "память" о предыдущих сообщениях. В итоге кот мог отвечать на то, чего не было.Решается так:
bot.stop()
bot.clearContext()
await bot.respond(to: text)
🔸 2. Очередь запросов и работа в фоне
Если запустить несколько запросов подряд — ответы могли пересекаться.
Добавил очередь на
Task, чтобы каждый запрос дожидался предыдущего, с пониженным приоритетом:let waitFor = tail
let next = Task(priority: .utility) {
if let prev = waitFor {
_ = try await prev.value
}
return try await self.run(text, systemPrompt: systemPrompt)
}
tail = next
return try await next.value
🔸 3. Кастомизация производительности (`n_gpu_layers`, `nThreads`, `nBatch`)
В оригинальной библиотеке
LLM.swift всё это было захардкожено.Я добавил ручной контроль - чтобы под слабое и мощное железо выставлялись оптимальные настройки.
Пример:
let threads = ProcessInfo.processInfo.processorCount / 2
let bot = LLM(
from: modelUrl,
template: template,
topK: 40,
topP: 0.92,
temp: 0.6,
historyLimit: 0,
maxTokenCount: 1024,
nThreads: threads,
nThreadsBatch: threads,
nBatch: 8,
n_gpu_layers: suggestedGPULayers()
)!
И функция:
static func suggestedGPULayers() -> Int32 {
let gb = ProcessInfo.processInfo.physicalMemory / (1024 * 1024 * 1024)
if gb <= 8 { return 0 }
if isHighEndAppleSilicon() { return 20 }
if gb >= 16 { return 10 }
return 0
}🔸 4. Фикс bot.stop()
В оригинале
bot.stop() не работал как надо — генерация продолжалась.Я пофиксил это: теперь метод реально прерывает вывод, как только нужно (например, по
<|eot_id|> или внешнему событию).🧠 Если кому нужно — могу сделать Pull Request в
LLM.swift с этими доработками.А если интересно попробовать готовый движок из ScreenCat — тоже пишите, соберу демку 🧩
