Локальный ИИ, не покидающий периметр компании

kolina

Client
Регистрация
05.10.2019
Сообщения
182
Благодарностей
78
Баллы
28
137472


Оборудование
Данная модель была обучена с учётом имеющегося оборудования: видеокарта с 8 GB памяти, процессор AMD Ryzen 7 (сопоставимый по производительности с Intel Core i7) и 32 GB оперативной памяти.

⏱ Затраченное время на обучение
Общее время обучения составило 15 часов. В процессе работы система трижды перезагружалась, что потребовало корректировки нагрузки и параметров запуска. Для обучения было использовано 1400 эталонных примеров. По мере накопления новых эталонных данных планируется проведение дополнительного дообучения модели.

Как мы развернули локальный ИИ на Windows-сервере, обучили/сконвертировали модель, завели её в LM Studio и подключили к ZennoPoster через OpenAI-совместимое API. Материал акцентирует безопасность: все данные обрабатываются на своём железе, без отправки во внешние облака.
Зачем ИИ на локальной инфраструктуре компании
  • Данные не покидают периметр компании. Отсутствуют облачные передачи и «телеметрия по умолчанию».
  • Предсказуемые расходы: нет тарификации за токены и сетевой трафик.
  • Гибкость: можно хранить несколько моделей, быстро переключаться и обкатывать свои системные промты/правила.
В странах СНГ это особенно актуально из-за требований к конфиденциальности и корпоративной политике безопасности.

Архитектура решения
  • LM Studio (Windows): удобный UI + локальный HTTP-сервер с OpenAI-совместимыми эндпоинтами (/v1/chat/completions, /v1/models, /v1/embeddings).
  • GGUF-модель (Gemma-2-9B-IT + наш LoRA, варианты F16, Q5_1, Q4_0).

    Что такое GGUF
    • GGUF — это формат хранения и запуска больших языковых моделей, разработанный для llama.cpp и совместимых фреймворков.
    • Его цель: удобная упаковка модели с метаданными, поддержка разных квантованных вариантов, оптимизация под локальный запуск (on-premise, edge, десктоп).
    • В отличие от старых форматов (GGML, GPTQ), GGUF более гибок и поддерживает расширенные параметры.

  • Базовая модель: Gemma-2-9B-IT
    • Gemma-2-9B-IT — это модель от Google, 9 миллиардов параметров, обученная с акцентом на инструкции (Instruction-Tuned).
    • Она хорошо подходит для диалогов, генерации текста, анализа данных.
    • Конфигурация использует её как основу, а сверху накладывается LoRA.

  • LoRA (Low-Rank Adaptation)
    • LoRA — метод дообучения модели без изменения всех весов.
    • Суть: добавляются небольшие матрицы-адаптеры, которые корректируют поведение модели.
    • Преимущество: можно быстро обучить под конкретные задачи (например, юридические документы, бизнес-брендологию, API-интеграции) без огромных затрат.
    • В нашем случае: Gemma-2-9B-IT + наш LoRA = базовая модель + адаптация под нашу бизнес задачу - логистика.
  • ZennoPoster: вызывает локальный LM Studio сервер, получает JSON-ответ, раскладывает в переменные и запускает дальнейшие сценарии.
Схема работы:
ZennoPoster → http://127.0.0.1:1234/v1/chat/completions → LM Studio → локальная модель (CPU/Vulkan/CUDA) → ответ в JSON.

Обучение: от сырых объявлений к локальной модели
1) Данные: что именно мы обучали

Цель. Научить модель из шумных объявлений по грузоперевозкам извлекать стандартизированный JSON:
JSON:
{
  "departure_point": "Страна, Город" | null,
  "arrival_point": "Страна, Город" | null,
  "tonn": number | null,
  "id_car_type": "Тент" | "Рефрижератор" | "Изотерма" | null,
  "date_loading_iso": "YYYY-MM-DD" | null,
  "opisanie": "строка" | null
}
Пример пары для обучения (в формате «чат»)
Мы обучаем в стиле инструкций: system задаёт железные правила, user — входной блок, assistant — эталонный JSON.
JSON:
{
  "messages": [
    {
      "role": "system",
      "content": "Ты — детерминированный парсер объявлений о грузоперевозках. Отвечай ТОЛЬКО валидным JSON-массивом без каких-либо префиксов. Схема каждого объекта: {\"departure_point\":\"Страна, Город\"|null, \"arrival_point\":\"Страна, Город\"|null, \"tonn\":number|null, \"id_car_type\":\"Тент\"|\"Рефрижератор\"|\"Изотерма\"|null, \"date_loading_iso\":\"YYYY-MM-DD\"|null, \"opisanie\":\"строка\"|null}. Правила нормализации стран ... (как в нашем системном промте)."
    },
    {
      "role": "user",
      "content": "Входной блок:\nГруз бутылки Вес до 20 тонн Авто тент, 10 машин из ТАШКЕНТА в ТБИЛИСИ\nСегодня: 2025-11-12\nЗадача: верни ТОЛЬКО валидный JSON."
    },
    {
      "role": "assistant",
      "content": "[{\"departure_point\":\"Узбекистан, Ташкент\",\"arrival_point\":\"Грузия, Тбилиси\",\"tonn\":20,\"id_car_type\":\"Тент\",\"date_loading_iso\":\"2025-11-12\",\"opisanie\":\"Бутылочная тара, до 20 т, 10 авто, тент.\"}]"
    }
  ]
}
Мы соблюдаем правило: в ответе только JSON-массив. Это критично — модель запоминает формат.

Быстрая заготовка очистки и упаковки (Python)
Python:
# make_dataset.py
import json, re, random, pathlib
from datetime import date

today = str(date.today())

phone_re = re.compile(r"\+?\d[\d\s\-\(\)]{7,}")
url_re   = re.compile(r"https?://\S+|t\.me/\S+|@\w+")

def clean(text: str) -> str:
    text = phone_re.sub("[контакты удалены]", text)
    text = url_re.sub("[ссылка удалена]", text)
    text = re.sub(r"[ \t]+", " ", text)
    return text.strip()

SYSTEM = ("Ты — детерминированный парсер объявлений о грузоперевозках. "
          "Отвечай ТОЛЬКО валидным JSON-массивом без префиксов/кодблоков. "
          "Схема каждого объекта: {\"departure_point\":\"Страна, Город\"|null, "
          "\"arrival_point\":\"Страна, Город\"|null, \"tonn\":number|null, "
          "\"id_car_type\":\"Тент\"|\"Рефрижератор\"|\"Изотерма\"|null, "
          "\"date_loading_iso\":\"YYYY-MM-DD\"|null, \"opisanie\":\"строка\"|null}. "
          "Нормализация стран: Россия, Узбекистан, Беларусь, Казахстан, Литва, Латвия, "
          "Эстония, Кыргызстан, Таджикистан, Туркменистан, Грузия, Армения, Азербайджан, "
          "Польша, Германия. Если нельзя определить — null. Кириллица обязательна.")

def pack(sample_text: str, gold_json: str):
    return {
        "messages": [
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": f"Входной блок:\n{clean(sample_text)}\nСегодня: {today}\nЗадача: верни ТОЛЬКО валидный JSON."},
            {"role": "assistant", "content": gold_json.strip()}
        ]
    }

# Пример: берем ваши пары (raw_text -> эталонный JSON),
# ниже – просто демонстрация. Реально идём по своей базе.
pairs = [
    ("Тент 20т Ташкент -> Тбилиси 10 авто",
     '[{"departure_point":"Узбекистан, Ташкент","arrival_point":"Грузия, Тбилиси","tonn":20,"id_car_type":"Тент","date_loading_iso":"' + today + '","opisanie":"Тент 20т"}]')
]

random.shuffle(pairs)
val_split = int(len(pairs) * 0.98)
train, val = pairs[:val_split], pairs[val_split:]

pathlib.Path("data").mkdir(exist_ok=True)
with open("data/train.jsonl", "w", encoding="utf-8") as f:
    for t, j in train:
        f.write(json.dumps(pack(t, j), ensure_ascii=False) + "\n")
with open("data/val.jsonl", "w", encoding="utf-8") as f:
    for t, j in val:
        f.write(json.dumps(pack(t, j), ensure_ascii=False) + "\n")
print("saved:", len(train), len(val))
На практике вы формируете gold_json автоматически (скриптом-правилами) + вручную проверяете подвыборку.
Ключевой критерий датасета — строгость формата и разнообразие формулировок.


2) Обучение: QLoRA на базе Gemma-2-9B-IT
Мы обучали адаптер (LoRA), затем сливали его в полные веса (fp16). Это даёт:
  • малые требования к GPU при обучении (QLoRA/4-бит),
  • возможность распространять только LoRA (если нужно),
  • единый итоговый чекпоинт после слияния.
Слияние LoRA в «полные веса» (fp16)
Python:
# merge_lora.py
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import torch, os, shutil

BASE   = "google/gemma-2-9b-it"
LORA   = "./outputs/gemma2-9b-it-logist-lora"
MERGED = "./outputs/gemma2-9b-it-lora-merged-fp16"

tok = AutoTokenizer.from_pretrained(BASE, use_fast=True)
base = AutoModelForCausalLM.from_pretrained(BASE, torch_dtype=torch.float16)

model = PeftModel.from_pretrained(base, LORA)
model = model.merge_and_unload()  # слияние адаптера
os.makedirs(MERGED, exist_ok=True)
model.save_pretrained(MERGED)
tok.save_pretrained(MERGED)

# скопировать generation_config/tokenizer json, если нужно:
for f in ["generation_config.json", "special_tokens_map.json", "tokenizer.json"]:
    src = os.path.join(LORA, f)
    if os.path.exists(src):
        shutil.copy(src, MERGED)
print("Merged to", MERGED)
На выходе — обычный HF-чекпоинт (fp16), без зависимостей от LoRA. Его уже легко конвертировать в GGUF.
3) Конвертация HF → GGUF и квантизация

Мы используем llama.cpp.

Конвертация в F16 GGUF
Python:
# Внутри репозитория llama.cpp
# D:\llama.cpp
python convert-hf-to-gguf.py --model-dir "D:\outputs\gemma2-9b-it-lora-merged-fp16" \
  --outfile "D:\models_out\Gemma-2-9b-it-logist.F16.gguf" --outtype f16
Для Gemma конвертер сам определит тип. Если словарь SentencePiece — тоже определяется автоматически.

Квантизация в Q5_1 и Q4_0
Python:
# llama-quantize (Windows)
# пример путей:
"D:\llama.cpp\build\bin\Release\llama-quantize.exe" ^
  "D:\models_out\Gemma-2-9b-it-logist.F16.gguf" ^
  "D:\models_out\Gemma-2-9b-it-logist.Q5_1.gguf" Q5_1

"D:\llama.cpp\build\bin\Release\llama-quantize.exe" ^
  "D:\models_out\Gemma-2-9b-it-logist.F16.gguf" ^
  "D:\models_out\Gemma-2-9b-it-logist.Q4_0.gguf" Q4_0
4) Лицензии и этика
  • Gemma-2 (Google) имеет свою лицензию — проверяйте условия распространения весов/адаптеров.
  • Если распространяете только LoRA — проще соблюсти требования (часто разрешено).
1. Установка LM Studio

2. Подготовка и хранение моделей


Мы использовали Gemma-2-9B-IT с нашим LoRA и получили файлы:
  • Gemma-2-9b-it-logist.F16.gguf — полноточная версия.
  • Gemma-2-9b-it-logist.Q5_1.gguf

  • Gemma-2-9b-it-logist.Q4_0.gguf — квантизованные варианты (меньше ОЗУ/ВРАМ, быстрее, иногда с небольшой потерей качества).
Важно: LM Studio корректно индексирует модели, когда каждая находится в отдельной папке.

3. Частые проблемы загрузки модели и решения

Симптом: «Error loading model. (Exit code: 1844674407… Unknown error)».

Проверяем по порядку:
  1. Установлен ли подходящий Runtime для GGUF (Vulkan/CUDA/CPU) и выбран по умолчанию.
  2. Модель точно в формате GGUF, а не safetensors.
  3. Модель лежит в отдельной папке, путь без «нестандартных» символов.
  4. Обновите Runtime до актуальной версии (вкладка Runtime → Update).

    137470
4. Системный промт и пресеты в LM Studio

Мы создали системный промт reed_logist — строгий парсер объявлений о грузоперевозках, который возвращает только валидный JSON-массив без лишнего текста.
Содержимое хранится в LM Studio → вкладка Context (preset).

Важный нюанс: сервер LM Studio не умеет подхватывать пресеты «по имени» через API (возможно можно, но я не смог этого сделать). Поэтому системный промт выбирали вручную после загрузки модели.

137471


5. Встраиваем в ZennoPoster-скрипт
C#:
// Вход
string zapros = (project.Variables["zapros"].Value ?? "").Trim(); // ваш «Входной блок…»
string today  = System.DateTime.Now.ToString("yyyy-MM-dd");
if (!zapros.Contains("Сегодня:")) zapros += "\nСегодня: " + today;

// Вызов
string endpoint = "http://127.0.0.1:1234/v1/chat/completions";
string model    = "logist@q5_1";
string sysPath  = @"D:\llm\prompts\reed_logist.txt";

string content = CallLmStudio(endpoint, model, sysPath, zapros, 0.2, 240000);

// Выходы ZP
project.Variables["otvet"].Value = content ?? "";
project.Variables["test"].Value  = string.IsNullOrWhiteSpace(content) ? "0" : "1";
7. Производительность и качество
  • Выбор квантизации:
    Q5_1 — золотая середина для качества/скорости.
    Q4_0 — ещё быстрее и меньше памяти, но сильнее проседает качество.
    F16 — лучшее качество, но требует больше ОЗУ/ВРАМ.
  • Движок: при наличии NVIDIA используйте CUDA llama.cpp; универсально — Vulkan. На слабых машинах можно оставить CPU.
  • Температура/топ-p: для строгого JSON-парсинга держите temperature низкой (0.1–0.3).
8. Безопасность
  • LM Studio сервер слушает 127.0.0.1:1234 — внешний доступ недоступен.
  • Все запросы и ответы остаются на хост-машине. Никаких облачных API.
Итог
Мы развернули полностью локальную цепочку:
  1. Получили свою модель (в том числе слияние LoRA) и подготовили квантизованные GGUF-варианты.
  2. Правильно разложили по папкам, чтобы LM Studio автоматически подхватил модели.
  3. Настроили Runtime (Vulkan/CUDA/CPU) и устранили проблемы загрузки.
  4. Создали строгий системный промт reed_logist, который гарантирует ответ строго в JSON.
  5. Подключили ZennoPoster к локальному OpenAI-совместимому серверу LM Studio и организовали обмен данными без выхода в интернет.




 

Для запуска проектов требуется программа ZennoPoster.
Это основное приложение, предназначенное для выполнения автоматизированных шаблонов действий (ботов).
Подробнее...

Для того чтобы запустить шаблон, откройте программу ZennoPoster. Нажмите кнопку «Добавить», и выберите файл проекта, который хотите запустить.
Подробнее о том, где и как выполняется проект.

Последнее редактирование:

kolina

Client
Регистрация
05.10.2019
Сообщения
182
Благодарностей
78
Баллы
28
Добавил видео
 
  • Спасибо
Реакции: code

heks

Client
Регистрация
01.10.2013
Сообщения
1 546
Благодарностей
474
Баллы
83
ниче не понял но красавчик))
 
  • Спасибо
Реакции: Gfoblin и kolina

todayer

Client
Регистрация
07.08.2013
Сообщения
995
Благодарностей
411
Баллы
63
процессор AMD Ryzen 7 (сопоставимый по производительности с Intel Core i7)
Я правильно понял, что вы использовали CUDA? Если так, то производительность процессора не играла особую роль. Верно?
 

code

Administrator
Регистрация
04.06.2025
Сообщения
231
Благодарностей
118
Баллы
43
Я правильно понял, что вы использовали CUDA? Если так, то производительность процессора не играла особую роль. Верно?
Да, вы правильно поняли. При использовании CUDA основная вычислительная нагрузка действительно ложится на GPU, а не на CPU, поэтому производительность процессора играет значительно меньшую роль.
 
  • Спасибо
Реакции: kolina и todayer

Кто просматривает тему: (Всего: 2, Пользователи: 0, Гости: 2)