Files
obsidian/🤖 ЛОКАЛЬНЫЙ ИИ-ЭКСПЕРТ ПО БАНКОМАТНОМУ ПО.md
T

608 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
- **Платформа:** Windows 10/11 + WSL2 (Ubuntu 22.04 / 24.04)
- **Железо:** NVIDIA RTX 3060 Ti (8 ГБ VRAM)
- **Базовая модель для обучения:** Meta Llama 3.1 8B (`unsloth/llama-3.1-8b-Instruct-bnb-4bit`)
- **Рабочая директория:** `C:\ST\aandc\` (внутри WSL2: `/mnt/c/ST/aandc/`)
---
ЧАСТЬ 1. Развертывание среды и проброс GPU в WSL2
Шаг 1.1. Базовая установка подсистемы
Откройте **PowerShell от имени Администратора** на хостовой Windows:
powershell
```
wsl --install
wsl --set-default-version 2
```
Используйте код с осторожностью.
_Перезагрузите компьютер. При первом запуске терминала Ubuntu задайте логин и пароль._
Шаг 1.2. Защита от Out Of Memory (Выделение ресурсов)
1. В Windows нажмите `Win + R`, введите `%USERPROFILE%` и нажмите Enter.
2. Создайте в этой папке текстовый файл `.wslconfig` (без расширения `.txt`) и вставьте:
ini
```
[wsl2]
memory=16GB # Выделяем минимум 16 ГБ оперативной памяти (или 75% от системной RAM)
swap=8GB # Обязательный файл подкачки для подстраховки при компиляции моделей
localhostForwarding=true
```
Используйте код с осторожностью.
3. Перезапустите WSL в PowerShell: `wsl --shutdown`
Шаг 1.3. Настройка NVIDIA Container Toolkit
Запустите терминал **Ubuntu в WSL2** и выполните цепочку команд для чистой настройки проброса видеокарты:
bash
```
sudo apt-get update && sudo apt-get install -y ca-certificates curl gnupg2
# Удаляем старые битые кэши и ключи, если они создались
sudo rm -f /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo rm -f /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
# Скачивание официального валидного GPG-ключа NVIDIA
curl -fsSL https://github.io | sudo gpg --dearmor --yes -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
# Добавление стабильного репозитория пакетов
curl -s -L https://github.io | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
# Обновление списков и установка утилиты сопряжения
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
```
Используйте код с осторожностью.
_Для проверки введите `nvidia-smi`. На экране должна появиться таблица вашей видеокарты RTX 3060 Ti._
---
ЧАСТЬ 2. Установка Ollama и Запуск Базовой Модели
Шаг 2.1. Установка движка и скачивание Llama 3.1
Выполните в терминале WSL2:
bash
```
curl -fsSL https://ollama.com | sh
ollama pull llama3.1
```
Используйте код с осторожностью.
Шаг 2.2. Фоновый запуск сервера Ollama
Поскольку менеджер служб `systemd` в WSL часто ограничен, запускайте Ollama напрямую встроенным фоновым процессом Linux:
bash
```
ollama serve > /dev/null 2>&1 &
```
Используйте код с осторожностью.
_(Нажмите Enter, если терминал временно застынет. Проверить статус можно командой `ollama list`)._
---
ЧАСТЬ 3. Автоматизированная генерация ShareGPT-датасета
Шаг 3.1. Подготовка файлов в Windows и WSL2
1. В Windows создайте папку `C:\ST\aandc\manuals\`.
2. Скопируйте в неё все ваши **71 исходный технический PDF-мануал**. Внутри WSL2 они мгновенно станут доступны по пути `/mnt/c/ST/aandc/manuals/`.
3. В терминале WSL2 установите библиотеки обработки данных (обходя ограничения `PEP 668` системы Ubuntu):
bash
```
pip3 install pypdf requests --break-system-packages
```
Используйте код с осторожностью.
Шаг 3.2. Развертывание отказоустойчивого скрипта `generate_qa.py`
Создайте файл `/mnt/c/ST/aandc/generate_qa.py` и скопируйте в него этот код. Он очищает текст регулярными выражениями и корректно парсит ответы Ollama, даже если она возвращает одиночный словарь вместо списка:
python
```
import os
import re
import json
import pypdf
import requests
# === КОНФИГУРАЦИЯ ===
MODEL_NAME = "llama3.1:latest"
OLLAMA_URL = "http://localhost:11434/api/generate"
MANUALS_DIR = "/mnt/c/ST/aandc/manuals"
OUTPUT_FILE = "/mnt/c/ST/aandc/aandc_dataset.jsonl"
WINDOW_SIZE = 3 # Размер окна чтения документа (в страницах)
SYSTEM_PROMPT = (
"Вы — опытный technical writer и эксперт по банкоматному ПО.\n"
"Изучите предоставленный фрагмент технической документации.\n"
"Создайте от 2 до 4 высококачественных пар 'Вопрос-Ответ' на основе ТОЛЬКО этого текста.\n"
"Вопросы должны быть конкретными (коды ошибок, процедуры калибровки, настройка систем).\n"
"Ответы должны быть глубокими, технически точными, структурированными и без упоминания номеров страниц.\n"
"Формат ответа строго JSON списка ShareGPT:\n"
'[{"conversations": [{"from": "human", "value": "Вопрос"}, {"from": "gpt", "value": "Ответ"}]}]'
)
def clean_text(text):
"""Очистка текста от мусора, номеров страниц и колонтитулов"""
text = re.sub(r'Page \d+ of \d+', '', text, flags=re.IGNORECASE)
text = re.sub(r'^\d+\s*$', '', text, flags=re.MULTILINE)
text = re.sub(r'\n+', '\n', text)
text = re.sub(r' +', ' ', text)
return text.strip()
def extract_pdf_chunks(pdf_path, window_size=3):
"""Динамическое чтение любого PDF файла порциями страниц"""
try:
with open(pdf_path, 'rb') as f:
reader = pypdf.PdfReader(f)
total_pages = len(reader.pages)
for i in range(0, total_pages, window_size):
chunk_text = ""
for j in range(i, min(i + window_size, total_pages)):
page_text = reader.pages[j].extract_text()
if page_text:
chunk_text += page_text + "\n"
cleaned = clean_text(chunk_text)
if len(cleaned) > 100:
yield cleaned
except Exception as e:
print(f"Ошибка при чтении файла {os.path.basename(pdf_path)}: {e}")
def ask_ollama(context):
"""Запрос к локальному серверу Ollama за строгим JSON-форматом"""
prompt = f"ДОКУМЕНТАЦИЯ:\n{context}\n\nСгенерируй JSON по инструкции."
payload = {
"model": MODEL_NAME,
"prompt": prompt,
"system": SYSTEM_PROMPT,
"stream": False,
"format": "json"
}
try:
response = requests.post(OLLAMA_URL, json=payload)
if response.status_code == 200:
return response.json().get("response", "")
except Exception as e:
print(f"Ошибка связи с Ollama: {e}")
return None
def main():
if not os.path.exists(MANUALS_DIR):
print(f"Ошибка: Папка {MANUALS_DIR} не найдена!")
return
pdf_files = [f for f in os.listdir(MANUALS_DIR) if f.lower().endswith('.pdf')]
total_files = len(pdf_files)
if total_files == 0:
print(f"В папке {MANUALS_DIR} не найдено ни одного PDF файла.")
return
print(f"Найдено файлов для обработки: {total_files}")
print("Старт генерации профессионального датасета...")
# Файл затирает старую сырую базу 'w' и наполняется построчно (JSON Lines)
with open(OUTPUT_FILE, 'w', encoding='utf-8') as outfile:
for idx, pdf_name in enumerate(pdf_files, 1):
pdf_path = os.path.join(MANUALS_DIR, pdf_name)
print(f"[{idx}/{total_files}] Обработка: {pdf_name}")
for chunk in extract_pdf_chunks(pdf_path, WINDOW_SIZE):
raw_json = ask_ollama(chunk)
if not raw_json:
continue
try:
data = json.loads(raw_json)
# Сценарий 1: Модель вернула список объектов [ {...}, {...} ]
if isinstance(data, list):
for item in data:
if isinstance(item, dict) and "conversations" in item:
outfile.write(json.dumps(item, ensure_ascii=False) + "\n")
outfile.flush()
print(" ✅ Записана карточка (из списка)")
# Сценарий 2: Модель вернула один одиночный объект { "conversations": [...] }
elif isinstance(data, dict):
if "conversations" in data:
outfile.write(json.dumps(data, ensure_ascii=False) + "\n")
outfile.flush()
print(" ✅ Записана карточка (прямой словарь)")
else:
for key, val in data.items():
if isinstance(val, list):
for sub_item in val:
if isinstance(sub_item, dict) and "conversations" in sub_item:
outfile.write(json.dumps(sub_item, ensure_ascii=False) + "\n")
outfile.flush()
print(" ✅ Записана карточка (из вложенного ключа)")
except json.JSONDecodeError:
continue
print(f"\nУспешно! Все файлы обработаны. Новый датасет сохранен в: {OUTPUT_FILE}")
if __name__ == "__main__":
main()
```
Используйте код с осторожностью.
_Запуск: `python3 /mnt/c/ST/aandc/generate_qa.py`. На выходе сформируется файл на 756 качественных диалогов._
---
ЧАСТЬ 4. Стабильный Fine-Tuning LoRA на 8 ГБ VRAM
Для того чтобы обучение гарантированно шло без вылетов по памяти (`RuntimeError по fused cross entropy`), мы используем чистый, промышленный стек **Hugging Face (`transformers` + `peft` + `bitsandbytes`)**. Мы полностью убрали капризные компиляторы Unsloth, исключив любые пики выделения памяти на карте хоста.
Шаг 4.1. Установка стабильных зависимостей
Выполните в терминале WSL2:
bash
```
pip install torch torchvision torchaudio --index-url https://pytorch.org --break-system-packages
pip install transformers datasets peft bitsandbytes --break-system-packages
```
Используйте код с осторожностью.
Шаг 4.2. Развертывание «железного» скрипта обучения `train.py`
Создайте файл `/mnt/c/ST/aandc/train.py` и скопируйте в него этот код:
python
```
import os
import json
import torch
from datasets import Dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
Trainer,
DataCollatorForLanguageModeling
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
# === КОНФИГУРАЦИЯ ДЛЯ ГАРАНТИРОВАННОГО СТАРТА НА 8 ГБ VRAM ===
MODEL_NAME = "unsloth/llama-3.1-8b-Instruct-bnb-4bit" # Весовая база
max_seq_length = 512 # Идеальный размер контекста для защиты памяти от пиков
DATASET_PATH = "/mnt/c/ST/aandc/aandc_dataset.jsonl"
# Принудительная очистка видеопамяти
torch.cuda.empty_cache()
print("--- 1. Конфигурация 4-битного квантования bitsandbytes ---")
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True
)
print("--- 2. Загрузка модели и токенизатора напрямую через HF ---")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
quantization_config=bnb_config,
device_map="auto"
)
# Подготовка модели к PEFT обучению (стабилизация градиентов k-bit)
model = prepare_model_for_kbit_training(model)
print("--- 3. Конфигурация LoRA (Чистый PEFT) ---")
peft_config = LoraConfig(
r=16,
lora_alpha=16,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, peft_config)
print("--- 4. Отказоустойчивая валидация и загрузка датасета ---")
valid_dialogues = []
with open(DATASET_PATH, "r", encoding="utf-8") as f:
for idx, line in enumerate(f, 1):
if not line.strip():
continue
try:
item = json.loads(line)
if "conversations" not in item or not isinstance(item["conversations"], list):
continue
clean_conv = []
is_corrupted = False
for msg in item["conversations"]:
# Защита от изменения типов (строка вместо словаря на row 9)
if not isinstance(msg, dict) or "from" not in msg or "value" not in msg:
is_corrupted = True
break
clean_conv.append({
"from": str(msg["from"]).strip(),
"value": str(msg["value"]).strip()
})
if not is_corrupted and len(clean_conv) > 0:
valid_dialogues.append({"conversations": clean_conv})
except json.JSONDecodeError:
continue
print(f"Успешно загружено чистых диалогов: {len(valid_dialogues)}")
dataset = Dataset.from_list(valid_dialogues)
# Форматирование и ЯВНАЯ ТОКЕНИЗАЦИЯ строк под формат ролей Llama 3.1
def tokenize_prompts_func(examples):
conversations = examples["conversations"]
tokenized_inputs = {"input_ids": [], "attention_mask": []}
for con in conversations:
mapped_conversation = []
for msg in con:
# Превращаем "human" в "user", "gpt" в "assistant" для шаблона Llama 3.1
role = "user" if msg["from"] == "human" else "assistant"
mapped_conversation.append({"role": role, "content": msg["value"]})
# Применяем Jinja-шаблон чата
text = tokenizer.apply_chat_template(mapped_conversation, tokenize=False, add_generation_prompt=False)
# Токенизируем текст напрямую с жесткой обрезкой до max_seq_length
tokenized = tokenizer(text, truncation=True, max_length=max_seq_length, padding=False)
tokenized_inputs["input_ids"].append(tokenized["input_ids"])
tokenized_inputs["attention_mask"].append(tokenized["attention_mask"])
return tokenized_inputs
# Пересобираем датасет в готовые числовые тензоры
dataset = dataset.map(tokenize_prompts_func, batched=True, remove_columns=["conversations"])
print("--- 5. Инициализация индустриального Trainer ---")
trainer = Trainer(
model=model,
train_dataset=dataset,
data_collator=DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False), # Сборщик батчей
args=TrainingArguments(
per_device_train_batch_size=1, # Размер батча 1 исключает переполнение VRAM
gradient_accumulation_steps=8, # Накопление шагов (1 * 8 эмулирует реальный батч размера 8)
warmup_steps=5,
max_steps=60, # Обучение на 60 шагах. Для финала: num_train_epochs = 3
learning_rate=2e-4,
fp16=True,
logging_steps=1,
optim="adamw_8bit", # 8-битный оптимизатор экономит драгоценную VRAM
weight_decay=0.01,
lr_scheduler_type="linear",
seed=3407,
output_dir="outputs",
remove_unused_columns=False
),
)
print("--- 6. СТАРТ ТРЕНИРОВКИ (Чистый стек Hugging Face) ---")
trainer.train()
print("--- ТРЕНИРОВКА УСПЕШНО ЗАВЕРШЕНА ---")
print("--- 7. Сохранение LoRA адаптеров ---")
model.save_pretrained("/mnt/c/ST/aandc/aandc_lora_model")
tokenizer.save_pretrained("/mnt/c/ST/aandc/aandc_lora_model")
print("Обученные адаптеры сохранены в папку aandc_lora_model!")
```
Используйте код с осторожностью.
Шаг 4.3. Выполнение тренировки
Перед запуском обязательно **выгрузите Ollama из памяти**, чтобы освободить видеокарту хоста:
bash
```
pkill -f ollama
```
Используйте код с осторожностью.
Запустите скрипт:
bash
```
python3 /mnt/c/ST/aandc/train.py
```
Используйте код с осторожностью.
_Процесс займет около 2 часов. Значение `loss` будет плавно падать, фиксируя технические знания в весах LoRA._
---
ЧАСТЬ 5. Слияние Модели в Совместимый Монолит
Поскольку базовая модель `bnb-4bit` аппаратно заблокирована от обратной распаковки на CPU через `llama.cpp`, мы используем официальный индустриальный метод: **накатим ваши адаптеры на открытую, чистую базовую модель Llama 3.1 8B в формате `float16`** и запишем один полноценный монолитный файл.
Шаг 5.1. Запуск скрипта слияния (Merge & Unload)
Запустите интерактивный Python в терминале WSL2:
bash
```
python3
```
Используйте код с осторожностью.
Вставьте этот код (он скачает чистую базу от Unsloth и объединит её с вашими адаптерами):
python
```
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel
base_model_path = "unsloth/llama-3.1-8b-Instruct"
lora_path = "/mnt/c/ST/aandc/aandc_lora_model"
output_path = "/mnt/c/ST/aandc/atm_expert_fused_clean"
print("1. Загрузка открытой базовой модели Llama 3.1 (float16)...")
base_model = AutoModelForCausalLM.from_pretrained(base_model_path, torch_dtype=torch.bfloat16, device_map="cpu")
tokenizer = AutoTokenizer.from_pretrained(base_model_path)
print("2. Наложение обученных LoRA весов...")
model = PeftModel.from_pretrained(base_model, lora_path)
print("3. Слияние матриц в единый монолит...")
model = model.merge_and_unload()
print("4. Сохранение финальной совместимой модели...")
model.save_pretrained(output_path, safe_serialization=True)
tokenizer.save_pretrained(output_path)
print("--- СЛИЯНИЕ УСПЕШНО ЗАВЕРШЕНО ---")
exit()
```
Используйте код с осторожностью.
_После завершения в директории `C:\ST\aandc\atm_expert_fused_clean\` появится полноценная, совместимая с Ollama модель._
---
ЧАСТЬ 6. Сборка и Запуск Финального ИИ-Эксперта
Шаг 6.1. Настройка конфигурационного файла `Modelfile`
Перепишите ваш `Modelfile`, чтобы Ollama прочитала готовую монолитную папку, выполнив в консоли команду:
bash
```
cat << 'EOF' > /mnt/c/ST/aandc/Modelfile
# Ссылка на созданную монолитную папку
FROM /mnt/c/ST/aandc/atm_expert_fused_clean
TEMPLATE """{{ if .System }}<|start_header_id|>system<|end_header_id|>
{{ .System }}<|eot_id|>{{ end }}{{ if .Prompt }}<|start_header_id|>user<|end_header_id|>
{{ .Prompt }}<|eot_id|>{{ end }}<|start_header_id|>assistant<|end_header_id|>
{{ .Response }}<|eot_id|>"""
SYSTEM "Вы — специализированный локальный ИИ-эксперт по банкоматному ПО. Вы отвечаете строго на основе технической документации, четко, структурированно, используя точные инженерные термины и коды ошибок."
EOF
```
Используйте код с осторожностью.
Шаг 6.2. Компиляция модели внутри Ollama
Запустите сервер Ollama обратно в фоне и выполните сборку:
bash
```
ollama serve > /dev/null 2>&1 &
cd /mnt/c/ST/aandc/
ollama create atm_expert -f ./Modelfile
```
Используйте код с осторожностью.
_Ollama за секунды скопирует компоненты, переварит типы данных и напишет долгожданное `success`._
Шаг 6.3. Интерактивный запуск
Входите в чат с готовым экспертом:
bash
```
ollama run atm_expert
```
Используйте код с осторожностью.
_Модель будет отвечать глубоко, инженерно точно, без колонтитулов и «каши». Выход из чата — команда `/exit`._
---
ЧАСТЬ 7. Автономный Перенос Эксперта на Другой ПК
Поскольку модель собрана в монолит, перенос на любой другой компьютер (даже полностью отключенный от интернета) занимает ровно 1 минуту.
1. **Экспорт в GGUF-файл на текущем ПК:**
Выполните в терминале WSL2 команду экспорта:
bash
```
ollama show atm_expert --modelfile > /mnt/c/ST/aandc/atm_expert_final.gguf
```
Используйте код с осторожностью.
_Заберите получившийся файл `atm_expert_final.gguf` (размером около 4.7 ГБ) на флешку._
2. **Подготовка целевого ПК:**
Установите чистую Ollama на новый компьютер (скачав десктопный установщик для Windows или Mac с официального сайта ollama.com).
3. **Импорт модели на новом месте:**
Положите файл `atm_expert_final.gguf` в любую папку на новом ПК (например, `C:\ATM_AI\`). Создайте рядом текстовый файл `Modelfile` со следующим содержимым:
dockerfile
```
FROM ./atm_expert_final.gguf
SYSTEM "Вы — специализированный локальный ИИ-эксперт по банкоматному ПО. Вы отвечаете строго на основе технической документации, четко, структурированно, используя точные инженерные термины и коды ошибок."
```
Используйте код с осторожностью.
Откройте обычную командную строку Windows (cmd или PowerShell) в этой папке и зарегистрируйте модель:
powershell
```
cd C:\ATM_AI\
ollama create atm_expert -f ./Modelfile
```
Используйте код с осторожностью.
4. **Запуск:** Модель готова отвечать на новом ПК по стандартной команде: `ollama run atm_expert`. Если на целевом ПК нет видеокарты, обеспечьте ему минимум **16 ГБ оперативной памяти (RAM)** — Ollama автоматически и без сбоев переключит вычисления на процессор.
---