Compare commits

7 Commits

Author SHA1 Message Date
370a11a463 minimal fix 2025-08-03 16:19:10 +00:00
957903d190 revert 5c585544d3
revert add base_llm.py
2025-08-03 18:10:03 +07:00
5c585544d3 add base_llm.py 2025-08-03 18:09:27 +07:00
cd2c2d235d add sample data 2025-08-03 10:46:06 +00:00
fd4eef8e7f Merge pull request 'compilation' (#1) from compilation into main
Reviewed-on: http://192.168.3.3:3030/drholy/llm/pulls/1
2025-08-03 17:10:34 +07:00
669bb5830a update readme and add install.sh 2025-08-03 07:37:00 +00:00
eed3a00efb Connection between the web interface and the logic 2025-08-03 07:22:11 +00:00
26 changed files with 764 additions and 242 deletions

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@
/app/core /app/core
/app/data/chroma_db /app/data/chroma_db
/app/data/parsed_json/ /app/data/parsed_json/
/app/data/docs/
/app/__pycache__ /app/__pycache__
/models /models
/cache /cache

View File

@@ -28,7 +28,7 @@ RUN pip install --upgrade pip && \
WORKDIR /app WORKDIR /app
# Копируем только requirements.txt для кэширования слоя # Копируем только requirements.txt для кэширования слоя
COPY offline_packages/test.txt /app/requirements.txt COPY requirements.txt /app/requirements.txt
# Установка зависимостей (включая GPU-версии) # Установка зависимостей (включая GPU-версии)
RUN pip install --no-cache-dir -r /app/requirements.txt RUN pip install --no-cache-dir -r /app/requirements.txt

101
README.md
View File

@@ -68,12 +68,7 @@ docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi
--- ---
## 3. Установка системных зависимостей ## 3. Проверка наличия драйверов видеокарты
### Установка заголовков ядра Linux
```bash
apt install linux-headers-$(uname -r)
```
### Проверка видеодрайверов ### Проверка видеодрайверов
```bash ```bash
@@ -133,8 +128,10 @@ git lfs install
> ✅ Успешное клонирование: > ✅ Успешное клонирование:
```bash ```bash
git clone https://huggingface.co/Qwen/Qwen3-8B mkdir models/
cd Qwen3-8B/ cd models
git clone https://huggingface.co/Qwen/Qwen3-4B
git clone https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
``` ```
--- ---
@@ -244,6 +241,94 @@ history > history.md
--- ---
## Структура проекта
```bash
├── Dockerfile
├── README.md
├── app
│   ├── __pycache__
│   │   └── config.cpython-310.pyc
│   ├── config.py
│   ├── data
│   │   ├── chroma_db
│   │   │   ├── 55652433-5130-4082-b593-3dcf3ad2bc92
│   │   │   └── chroma.sqlite3
│   │   ├── docs
│   │   │   ├── REALITY.md
│   │   │   ├── VLESS-GRPC.md
│   │   │   ├── XTLS-Vision.md
│   │   │   └── marzban_ru.md
│   │   ├── generate_json_files.py
│   │   ├── generated_docs_db.py
│   │   └── parsed_json
│   │   ├── REALITY.json
│   │   ├── VLESS-GRPC.json
│   │   ├── XTLS-Vision.json
│   │   └── marzban_ru.json
│   ├── main.py
│   └── old_without_db.py
├── backup_docker_image.md
├── cache
│   ├── cache
│   │   ├── chroma
│   │   │   └── telemetry_user_id
│   │   └── pip
│   │   ├── http-v2
│   │   └── selfcheck
│   └── site-packages
├── docker-compose.yml
├── history.md
├── models
│   ├── Qwen3-4B
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── config.json
│   │   ├── generation_config.json
│   │   ├── merges.txt
│   │   ├── model-00001-of-00003.safetensors
│   │   ├── model-00002-of-00003.safetensors
│   │   ├── model-00003-of-00003.safetensors
│   │   ├── model.safetensors.index.json
│   │   ├── tokenizer.json
│   │   ├── tokenizer_config.json
│   │   └── vocab.json
│   └── paraphrase-multilingual-MiniLM-L12-v2
│   ├── 1_Pooling
│   │   └── config.json
│   ├── README.md
│   ├── config.json
│   ├── config_sentence_transformers.json
│   ├── model.safetensors
│   ├── modules.json
│   ├── onnx
│   │   ├── model.onnx
│   │   ├── model_O1.onnx
│   │   ├── model_O2.onnx
│   │   ├── model_O3.onnx
│   │   ├── model_O4.onnx
│   │   ├── model_qint8_arm64.onnx
│   │   ├── model_qint8_avx512.onnx
│   │   ├── model_qint8_avx512_vnni.onnx
│   │   └── model_quint8_avx2.onnx
│   ├── openvino
│   │   ├── openvino_model.bin
│   │   ├── openvino_model.xml
│   │   ├── openvino_model_qint8_quantized.bin
│   │   └── openvino_model_qint8_quantized.xml
│   ├── pytorch_model.bin
│   ├── sentence_bert_config.json
│   ├── sentencepiece.bpe.model
│   ├── special_tokens_map.json
│   ├── tf_model.h5
│   ├── tokenizer.json
│   ├── tokenizer_config.json
│   └── unigram.json
└── offline_packages
└── requirements.txt
```
## Выводы ## Выводы
Был успешно выполнен полный цикл: Был успешно выполнен полный цикл:

View File

@@ -1,5 +1,4 @@
import os import os
from pydantic import SecretStr from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -13,8 +12,8 @@ class Config(BaseSettings):
MAX_CHUNK_SIZE: int = 512 MAX_CHUNK_SIZE: int = 512
CHUNK_OVERLAP: int = 50 CHUNK_OVERLAP: int = 50
LM_MODEL_NAME: str = "/models/paraphrase-multilingual-MiniLM-L12-v2" LM_MODEL_NAME: str = "/models/paraphrase-multilingual-MiniLM-L12-v2"
LOCAL_LLM_NAME: str = "/models/Qwen3-4B" LOCAL_LLM_NAME: str = "/models/Qwen3-8B"
QWEN_MODEL_NAME: str = "qwen3-chat" QWEN_MODEL_NAME: str = "Qwen3-8B"
settings = Config() # type: ignore settings = Config() # type: ignore

18
app/data/docs/1-1.md Normal file
View File

@@ -0,0 +1,18 @@
### Как определить цели и требования для домашнего сервера**
Перед сборкой сервера чётко сформулируйте, зачем он вам нужен. Это определит выбор железа.
#### **Ключевые параметры:**
- **Оперативная память:** минимум 32 ГБ, желательно до 64 ГБ (с запасом на будущее).
- **Процессор:** 8+ потоков. Приоритет — количество ядер, а не высокая частота.
- **Диски:** минимум 6 слотов для HDD, лучше 8 и более.
- **Габариты:** высота до ~30 см (чтобы влез на полку).
- **Расширяемость:** возможность добавить:
- Видеокарту (для транскодинга видео)
- 10 Гбит сеть
- SATA-контроллер
- **Энергопотребление:** не более 100–150 Вт под нагрузкой.
- **Тишина:** не должен шуметь как пылесос.
- **Бюджет:** до 50 000 ₽ (с учётом продажи старого оборудования).
> ✅ **Итог:** Вы получите чёткий список требований, по которому можно выбирать комплектующие.

40
app/data/docs/1-2.md Normal file
View File

@@ -0,0 +1,40 @@
### Подбор комплектующих для домашнего сервера**
На основе требований выберите совместимые и эффективные компоненты.
#### **1. Корпус — Fractal Design Node 804**
- Поддерживает до 8 дисков (6×3.5" + 2×2.5").
- Удобные салазки для HDD.
- Совет: ставьте корпус боком (дисками наружу) — легче менять диски.
#### **2. Материнская плата — ASRock X470D4U**
- Сокет AM4, поддержка до 128 ГБ RAM.
- 8 SATA-портов — не нужен дополнительный контроллер.
- 2× гигабитных LAN, IPMI, встроенная графика.
- Минусы: только 4 USB-порта, M.2 работает на PCIe 3.0 x2.
#### **3. Процессор — AMD Ryzen 7 3700 (8 ядер / 16 потоков)**
- Энергоэффективный (~65 Вт).
- Поддерживается материнкой без проблем (в отличие от R7 1700).
#### **4. Охлаждение — DeepCool GAMMAXX 300**
- Достаточно для R7 3700.
- Тихий, дешёвый, эффективный.
#### **5. Оперативная память — 32 ГБ (2×16 ГБ DDR4 2666 MHz)**
- UDIMM — совместима с ASRock X470D4U.
- Без ECC (экономия), но можно апгрейдить до 64+ ГБ.
#### **6. Блок питания — be quiet! Pure Power 11, 400–500 Вт**
- Расчёт: ~100 Вт (CPU) + 80 Вт (HDD) + 100 Вт (материнка, SSD) + запас = 400 Вт хватит.
#### **7. Диски — 33 ТБ (перенос со старого сервера)**
- HDD: 8×2, 6×1, 5×1, 3×2 ТБ.
- SSD: 2×1–2 ТБ — под кэш, Docker, VM.
#### **8. Дополнительно (по мере необходимости)**
- SATA-контроллер в M.2 (чтобы не тратить PCI-E слот).
- Видеокарта (позже, для транскодинга).
- 10 Гбит сеть — не сейчас.
> ✅ **Итог:** Вы собрали список проверенных и совместимых комплектующих с учётом будущего апгрейда.

21
app/data/docs/1-3.md Normal file
View File

@@ -0,0 +1,21 @@
### Сборка сервера — пошагово**
Соберите сервер, не повредив компоненты.
#### **Шаги:**
1. **Установите материнскую плату** в корпус.
2. **Вставьте CPU** в сокет, закройте фиксатор.
3. **Нанесите термопасту**, установите кулер.
4. **Установите RAM** в слоты (2×16 ГБ).
5. **Подключите питание**:
- 24-pin от БП к материнке.
- 8-pin CPU к материнке.
6. **Установите диски** в салазки, подключите:
- SATA-кабели к материнке.
- Питание от БП.
7. **Подключите переднюю панель** (USB, аудио, кнопки включения).
8. **Установите блок питания**, закрепите.
9. **Подключите все компоненты**, проверьте, нет ли болтающих проводов.
10. **Включите, проверьте запуск.**
> ✅ **Итог:** Сервер собран, готов к установке ОС.

24
app/data/docs/1-4.md Normal file
View File

@@ -0,0 +1,24 @@
### Финансы и итоговая конфигурация**
Проверьте бюджет и оцените результат.
#### **Затраты:**
- Корпус: 12 000 ₽
- Материнка: 22 000 ₽
- CPU: 9 000 ₽
- Память (замена на десктопе): ~10 000 ₽
- Кулер (замена на десктопе): ~3 000 ₽
- **Итого: ~56 000 ₽**
#### **Компенсация:**
- Продажа старого HP Microserver: ~30 000 ₽
- Скидка в магазине
- **Чистые затраты: ~10–15 000 ₽**
#### **Итоговая конфигурация:**
- **Производительность:** Ryzen 7 3700 + 32 ГБ RAM → мощнее старого Xeon.
- **Хранение:** 33 ТБ + SSD-кэш.
- **Гибкость:** 8+ дисков, M.2, PCI-E, IPMI.
- **Расширяемость:** можно добавить GPU, 10 Гбит, больше RAM.
> ✅ **Итог:** Вы получили мощный, тихий, энергоэффективный сервер за разумные деньги. Готов к установке Unraid, Proxmox или другой ОС.

22
app/data/docs/2-1.md Normal file
View File

@@ -0,0 +1,22 @@
### Как выбрать ОС для домашнего сервера**
При замене Windows Server нужно выбрать современную, гибкую систему, сочетающую NAS, виртуализацию и контейнеры.
#### **Варианты:**
| Система | Подходит, если… | Минусы |
|--------|------------------|--------|
| **Unraid** | Нужна гибкость, Docker, VM, диски любого объёма | Платная лицензия |
| **TrueNAS** | Важна отказоустойчивость (ZFS), энтерпрайз-подход | Требователен к RAM, неудобен с разными дисками |
| **OpenMediaVault** | Хочется лёгкий Linux-сервер | Устаревший интерфейс, меньше интеграций |
| **Proxmox VE** | Серьёзная виртуализация — основная задача | Неудобный веб-интерфейс |
| **ESXi / XCP-ng** | Работа в enterprise-среде | Сложность, неудобство для домашнего использования |
#### **Финальный выбор — Unraid:**
- ✅ Поддержка дисков **любого объёма**
- ✅ Встроенная **отказоустойчивость (parity)**
- ✅ Удобный **Docker** и **виртуализация (KVM)**
- ✅ Проброс GPU, USB, SATA
- ✅ SSD-кэш, плагины, Community Appstore
- ❌ Платно: 89$ (до 12 дисков)
> ✅ **Итог:** Unraid — лучший баланс между простотой, функциональностью и гибкостью для домашнего сервера.

29
app/data/docs/2-2.md Normal file
View File

@@ -0,0 +1,29 @@
### Настройка дискового массива в Unraid**
Unraid позволяет собрать массив из дисков разного объёма с защитой от сбоев.
#### **Ключевые понятия:**
- **Data-диски** — хранят данные.
- **Parity-диск** — один или два диска для защиты от потери (аналог RAID).
- **Кэш-диск (SSD)** — ускоряет запись и доступ.
- **Флешка** — хранит ОС и настройки (не участвует в массиве).
#### **Как настроить:**
1. Подключите все диски.
2. В веб-интерфейсе (`http://tower`) назначьте:
- Один диск как **Parity**
- Остальные как **Data**
- SSD — как **Cache**
3. Запустите массив.
#### **Гибкость:**
- Можно добавлять, удалять, заменять диски.
- Замена: только на диск **равного или большего объёма**.
- При разборе массива данные остаются на дисках.
#### **Важно:**
- **Ребилд parity** занимает 12–18 часов (при 8 ТБ).
- Во время ребилда массив **без защиты**.
- Для экономии времени: можно сначала собрать массив **без parity**, заполнить данными, потом добавить parity и сделать ребилд.
> ✅ **Итог:** Unraid даёт максимальную гибкость в управлении дисками при наличии отказоустойчивости.

23
app/data/docs/2-3.md Normal file
View File

@@ -0,0 +1,23 @@
### Миграция сервисов на Unraid**
Перенесите ключевые сервисы со старого сервера в Docker и виртуальные машины.
#### **Как заменить каждый сервис:**
| Было | Стало | Как настроить |
|------|-------|----------------|
| **Файловое хранилище** | SMB в Unraid | Создайте шары (shared folders), включите SMB |
| **Торрент-клиент** | qBittorrent / Deluge (Docker) | Через Community Appstore |
| **Медиасервер** | Plex (Docker) | Подключите старую базу, укажите пути |
| **Резервное копирование** | Duplicati → OneDrive | Docker-контейнер, шифрование, версии |
| **Синхронизация** | Robocopy / GoodSync | Syncthing (Docker) — для ноутбуков |
| **Виртуальные машины** | VirtualBox | KVM в Unraid (поддержка проброса GPU/USB) |
| **Удалённый доступ** | RDP | Виртуалка с Windows + RDP |
| **Хостинг (Bitwarden и др.)** | Виртуалки | Docker-контейнеры (проще и надёжнее) |
#### **Советы:**
- Используйте **Community Applications** для установки контейнеров.
- Для управления множеством Docker — поставьте **Portainer**.
- Проброс USB-флешки: через `/dev/disk/by-id/...` как raw-устройство.
> ✅ **Итог:** Все сервисы легко переносятся в Unraid с помощью Docker и KVM.

33
app/data/docs/2-4.md Normal file
View File

@@ -0,0 +1,33 @@
### Планирование улучшений и бэкапов**
Unraid — это основа. Его можно расширять по мере роста потребностей.
#### **Что добавить позже:**
- **Видеокарта** — NVIDIA с NVENC для аппаратного транскодинга (Plex/Jellyfin).
- **10 Гбит сеть** — при обновлении роутера и кабелей.
- **Portainer** — для удобного управления Docker-контейнерами.
- **ZFS через плагин** — если понадобится (не основная ФС).
- **Автоматизация** — через плагины `CA User Scripts` или `Dynamix`.
#### **Обязательные бэкапы:**
1. **Конфигурация Unraid:**
- Экспортируйте: `Settings → Flash → Backup`
- Сохраните на флешку или в облако.
2. **Папки:**
- `/config` — настройки сервера
- `/docker` — контейнеры
- `/vm` — виртуальные машины
3. **Внешние бэкапы:**
- Используйте **Duplicati (Docker)** → OneDrive, Mail.ru, Backblaze.
- Настройте хранение: ежедневно (неделя), еженедельно (месяц), ежемесячно (год).
#### **Итоговая архитектура:**
```
Unraid
├── Массив: 6× HDD + 1× Parity + SSD Cache
├── Docker: Plex, qBittorrent, Duplicati, Syncthing
├── VM: Windows (RDP), Linux (тесты)
└── Плагины: Community Apps, Dynamix, CA User Scripts
```
> ✅ **Итог:** Unraid легко масштабируется. Главное — регулярно бэкапить конфигурацию и данные.

15
app/data/docs/3-1.md Normal file
View File

@@ -0,0 +1,15 @@
### Проверка совместимости и выбор процессора**
Перед запуском сервера убедитесь, что железо работает стабильно.
#### **Ключевые шаги:**
1. Проверьте **официальную поддержку CPU** материнской платой.
- Пример: Ryzen 7 1700 на ASRock X470D4U работал с зависаниями при нагрузке (например, `rsync`).
2. Если процессор не в списке поддержки — ожидайте проблем.
3. Замените нестабильный CPU на официально поддерживаемый:
- Успешный выбор: **Ryzen 7 3700** (8 ядер, 16 потоков, AM4).
4. После замены:
- Обновите BIOS, если нужно.
- Проверьте стабильность под нагрузкой (чтение/запись массива, запуск VM).
> ✅ **Итог:** Используйте только проверенные и поддерживаемые процессоры — это основа стабильной работы.

15
app/data/docs/3-2.md Normal file
View File

@@ -0,0 +1,15 @@
### Организация охлаждения**
Правильное охлаждение критично для долговечности HDD и CPU.
#### **Схема вентиляции:**
- **Передняя панель:** 120 мм вентилятор на **вдув**.
- **Задняя панель:** 140 мм вентилятор на **выдув**.
- **Верх корпуса:** 140 мм вентилятор на **выдув** — для отвода горячего воздуха от CPU-кулера.
#### **Требования:**
- Все вентиляторы — **4-pin PWM** (ASRock X470D4U не управляет 3-pin).
- Кулер: **DeepCool GAMMAXX 300** — подходит для Ryzen 3700.
- Цель: температура HDD под нагрузкой — **не выше 40°C**.
> ✅ **Итог:** Трёхвентиляторная схема обеспечивает стабильную температуру даже в жару.

21
app/data/docs/3-3.md Normal file
View File

@@ -0,0 +1,21 @@
### Энергопотребление и реальные цифры**
Знайте, сколько сервер потребляет, чтобы оценить стоимость эксплуатации.
#### **Измерения (конфигурация):**
- CPU: Ryzen 7 3700 @3.6 ГГц
- RAM: 32 ГБ
- Диски: 6× HDD, 2× SSD
#### **Потребление:**
| Состояние | Потребление |
|----------|-------------|
| Выключен (IPMI работает) | 4–6 Вт |
| Пик при включении | 120 Вт |
| Холостой ход (диски крутятся) | 70–73 Вт |
| Холостой ход (диски остановлены) | 42–43 Вт |
| Работа (массив, VM, Docker) | 80–83 Вт |
| Проверка parity | 90–95 Вт |
| Максимальная нагрузка | 120–125 Вт |
> ✅ **Итог:** Среднее потребление — **~100 Вт**, что даёт около **300 руб/мес** при непрерывной работе.

34
app/data/docs/3-4.md Normal file
View File

@@ -0,0 +1,34 @@
### Установка аддонов и аккуратная укладка кабелей**
Оптимизируйте внутреннее пространство и добавьте удобные доработки.
#### **1. SATA-контроллер в M.2**
- Задача: не тратить PCI-E слот.
- Решение: M.2 → 5× SATA контроллер (PCIe 2.0 x4).
- Скорость: до 900 МБ/с — хватает для HDD.
- Подключение: второй корзине HDD.
#### **2. Внутренний свитч (временное решение)**
- Проблема: 2 порта на материнке, но 1 свободный на роутере.
- Решение:
- Установите 5-портовый свитч **внутри корпуса**.
- Запитайте от БП.
- Подключите: LAN + IPMI → свитч → роутер.
- Минус: при выключении питания — свитч гаснет.
#### **3. USB-хаб**
- Проблема: всего 4 USB-порта.
- Решение:
- Используйте **внешний хаб** в задних USB-портах.
- Закрепите на корпусе.
- Передние порты — подключите напрямую к материнке.
#### **4. Укладка кабелей**
- **Питание HDD:** 40 см кабели с 4 разъёмами — удобно для корзин.
- **SATA-шлейфы:** 50 см, 4 разъёма — меньше мусора.
- **Трассировка:**
- Не пересекайте кабели.
- Используйте штатные стяжки.
- Оставьте запас для извлечения корзин.
> ✅ **Итог:** Аккуратная сборка упрощает обслуживание и улучшает охлаждение.

17
app/data/docs/4-1.md Normal file
View File

@@ -0,0 +1,17 @@
### Установка Unraid и первоначальная настройка**
Начните с установки ОС и базовой конфигурации.
#### **Шаги:**
1. Скачайте образ Unraid с официального сайта.
2. Запишите его на USB-флешку (через Rufus, Balena Etcher и т.п.).
3. Вставьте флешку в сервер, включите — загрузитесь с неё.
4. Откройте в браузере: `http://tower` (или IP сервера).
5. Настройте:
- Пароль администратора.
- Сетевые интерфейсы (LAN, IPMI — при необходимости).
- Подключите диски (они появятся в интерфейсе).
> **Важно:** Unraid загружается в RAM, вся конфигурация хранится на флешке.
> ✅ **Совет:** Используйте свежие гайды от **SpaceInvader One** или **iBracorp** — они покрывают все основы.

22
app/data/docs/4-2.md Normal file
View File

@@ -0,0 +1,22 @@
### Настройка дискового массива и parity**
Организуйте надёжное хранилище с защитой от сбоев.
#### **Основные шаги:**
1. Назначьте один диск как **Parity** (желательно CMR, например, Toshiba 8 ТБ).
2. Остальные диски — как **Data**.
3. (Опционально) Добавьте второй **Parity** — защита от двух сбоев.
4. Назначьте SSD как **Cache** (ускоряет запись и доступ к Docker/VM).
5. Запустите массив.
#### **Гибкость:**
- Диски могут быть **разного объёма**.
- Можно добавлять, удалять, заменять без потери данных.
- При удалении: остановите массив → пересоздайте без диска → ребилд parity.
#### **Добавление диска (без очистки):**
1. Подключите диск как **Unassigned Device**.
2. Отформатируйте в XFS.
3. Остановите массив → извлеките parity → добавьте диск → верните parity → дождитесь ребилда.
> ✅ **Совет:** Для экономии времени сначала соберите массив **без parity**, заполните данными, потом добавьте parity.

19
app/data/docs/4-3.md Normal file
View File

@@ -0,0 +1,19 @@
### Установка и настройка ключевых сервисов**
Разверните основные сервисы через Docker и виртуальные машины.
#### **Какие сервисы и как:**
| Сервис | Решение |
|--------|--------|
| **Торрент-клиент** | `qBittorrent` или `Deluge` (Docker) |
| **Медиасервер** | `Plex` (Docker) — подключите старую базу и укажите новые пути |
| **Резервное копирование** | `Duplicati` (Docker) → OneDrive (шифрование, версии) |
| | Увеличьте размер архива (до 300 МБ) для больших файлов (RAW-фото) |
| | Проблема: `/tmp` заполняется → смонтируйте внешнюю папку |
| **Синхронизация** | `Syncthing` (Docker) — для ноутбуков, авто-сброс при отключении |
| **Удалённый доступ** | Виртуалка с Windows + RDP или `Apache Guacamole` (Docker) — веб-доступ |
| **Внутренний хостинг** | `Bitwarden`, `Nginx Proxy Manager` (Docker) |
| | Проброс портов: 80/443 |
> ✅ **Совет:** Установите **Portainer** — он упрощает управление множеством контейнеров.

23
app/data/docs/4-4.md Normal file
View File

@@ -0,0 +1,23 @@
### Плагины, бэкапы и полезные доработки**
Настройте расширения и защитите конфигурацию.
#### **Полезные плагины:**
- **Community Applications** — App Store для Docker и плагинов.
- **Unassigned Devices** — управление дисками вне массива.
- **Dynamix** — мониторинг температур, дисков, настройка.
- **CA User Scripts** — автоматизация (бэкапы, запуск скриптов).
- **Unbalance** — перенос данных с диска перед удалением.
#### **Бэкап конфигурации (обязательно!):**
1. Экспортируйте:
- `Settings → Flash → Backup` (вся конфигурация).
- Папки: `/config`, `/docker`, `/vm`.
2. Храните на внешней флешке или в облаке.
#### **Дополнительно:**
- Настройте **rclone** → подключите Mail.ru (1 ТБ) как диск (O:).
- Используйте для синхронизации музыки, книг, видео.
- Расшарьте через веб или виртуалку.
> ✅ **Итог:** Unraid становится мощной и надёжной платформой. Главное — регулярно бэкапьте конфигурацию.

View File

@@ -1,77 +1,176 @@
from typing import Any, Dict, List, Optional
import gradio as gr import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
import torch import torch
import threading import threading
from loguru import logger
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
# Пути к модели # Предполагается, что у тебя есть config.py с settings
model_name = "/models/Qwen3-8B" from config import settings
# Загрузка токенизатора и модели (один раз при старте)
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="cuda",
trust_remote_code=True
)
# Отключим кэширование в истории, чтобы каждый запрос был независимым class ChatWithAI:
def generate_response(message, history): def __init__(self, provider: str = "qwen3"):
# Форматируем диалог: используем только текущую историю self.provider = provider
prompt = "" self.embeddings = HuggingFaceEmbeddings(
for human, assistant in history: model_name=settings.LM_MODEL_NAME,
prompt += f"<|im_start|>user\n{human}<|im_end|>\n<|im_start|>assistant\n{assistant}<|im_end|>\n" model_kwargs={"device": "cuda"},
prompt += f"<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n" encode_kwargs={"normalize_embeddings": True},
)
# Токенизация if provider == "qwen3":
inputs = tokenizer(prompt, return_tensors="pt").to(model.device) model_name = getattr(settings, "LOCAL_LLM_NAME", "/models/Qwen3-8B")
logger.info(f"Загрузка локальной модели: {model_name}")
# Создаём уникальный streamer для каждого запроса self.tokenizer = AutoTokenizer.from_pretrained(model_name)
streamer = TextIteratorStreamer( self.model = AutoModelForCausalLM.from_pretrained(
tokenizer, model_name,
skip_prompt=True, torch_dtype=torch.float16,
skip_special_tokens=True device_map="cuda",
)
# Streamer для потоковой генерации
self.streamer = TextIteratorStreamer(
self.tokenizer,
skip_prompt=True,
skip_special_tokens=True
)
else:
raise ValueError(f"Неподдерживаемый провайдер: {provider}")
self.chroma_db = Chroma(
persist_directory=settings.DOCS_CHROMA_PATH,
embedding_function=self.embeddings,
collection_name=settings.DOCS_COLLECTION_NAME,
)
def get_relevant_context(self, query: str, k: int = 3) -> List[Dict[str, Any]]:
"""Получение релевантного контекста из базы данных."""
try:
results = self.chroma_db.similarity_search(query, k=k)
return [
{
"text": doc.page_content,
"metadata": doc.metadata,
}
for doc in results
]
except Exception as e:
logger.error(f"Ошибка при получении контекста: {e}")
return []
def format_context(self, context: List[Dict[str, Any]]) -> str:
"""Форматирование контекста для промпта."""
formatted_context = []
for item in context:
metadata_str = "\n".join(f"{k}: {v}" for k, v in item["metadata"].items())
formatted_context.append(
f"Текст: {item['text']}\nМетаданные:\n{metadata_str}\n"
)
return "\n---\n".join(formatted_context)
def generate_response_stream(self, query: str):
"""Генерация ответа с потоковой передачей токенов."""
try:
logger.info(f"Пользовательский запрос: {query}")
context = self.get_relevant_context(query)
if not context:
yield "Извините, не удалось найти релевантный контекст для ответа."
return
formatted_context = self.format_context(context)
messages = [
{
"role": "system",
"content": """Ты — внутренний менеджер помощи пользоваткля по вопросам настройки сервера. Отвечаешь по делу без лишних вступлений.
Правила:
1. Сразу переходи к сути, без фраз типа "На основе контекста"
2. Используй только факты. Если точных данных нет — отвечай общими фразами об настройки сервера, но не придумывай конкретику
3. Используй текст с форматированием.
4. Включай ссылки только если они есть в контексте
5. Говори от первого лица множественного числа: "Мы предоставляем", "У нас есть"
6. При упоминании файлов делай это естественно, например: "Я прикреплю инструкцию, где подробно описаны шаги"
7. На приветствия отвечай доброжелательно, на негатив — с легким юмором
8. Можешь при ответах использовать общую информацию из открытых источников по настройке сервера, но опирайся на контекст
9. Если пользователь спрашивает о ценах, планах или технических характеристиках — давай конкретные ответы из контекста
10. При технических вопросах предлагай практические решения
Персонализируй ответы, упоминая имя клиента если оно есть в контексте. Будь краток, информативен и полезен.""",
},
{
"role": "user",
"content": f"Вопрос: {query}\nКонтекст: {formatted_context}",
},
]
# Применяем шаблон чата
prompt = self.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False
)
# Подготавливаем вход
inputs = self.tokenizer(prompt, return_tensors="pt").to("cuda")
# Очищаем streamer и запускаем генерацию в отдельном потоке
self.streamer = TextIteratorStreamer(
self.tokenizer,
skip_prompt=True,
skip_special_tokens=True
)
generate_kwargs = {
"input_ids": inputs["input_ids"],
"max_new_tokens": 2048,
"temperature": 0.3,
"do_sample": True,
"top_p": 0.9,
"pad_token_id": self.tokenizer.eos_token_id,
"streamer": self.streamer,
}
thread = threading.Thread(target=self.model.generate, kwargs=generate_kwargs)
thread.start()
# Потоковая передача токенов
buffer = ""
for token in self.streamer:
buffer += token
yield buffer # Отправляем частичный ответ
except Exception as e:
logger.error(f"Ошибка при генерации ответа: {e}")
yield "Произошла ошибка при генерации ответа."
# === Gradio интерфейс ===
def main():
chat = ChatWithAI(provider="qwen3")
def respond(message, history):
# Генерируем ответ по частям
for token in chat.generate_response_stream(message):
yield token
demo = gr.ChatInterface(
fn=respond,
title="Помощник настройки сервера и подбора железа",
description="Задайте вопрос — получите ответ от внутреннего менеджера.",
examples=[
"Как определить цели и требования для домашнего сервера?",
"Как выбрать ОС для домашнего сервера?",
"Проверка совместимости и выбор процессора",
"Установка Unraid и первоначальная настройка"
],
) )
demo.queue(max_size=20, default_concurrency_limit=10).launch(server_name="0.0.0.0", server_port=8080, share=False)
# Параметры генерации
generation_kwargs = {
"input_ids": inputs["input_ids"],
"max_new_tokens": 1024,
"temperature": 0.6,
"top_p": 0.9,
"do_sample": True,
"pad_token_id": tokenizer.eos_token_id,
"streamer": streamer,
}
# Запускаем генерацию в отдельном потоке
thread = threading.Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
# Постепенно возвращаем результат
buffer = ""
for new_text in streamer:
buffer += new_text
yield buffer.strip()
# Создаем интерфейс
demo = gr.ChatInterface(
fn=generate_response,
title="Qwen3-4B-Base Chat",
description="Общайтесь с моделью Qwen3-4B-Base в режиме реального времени с потоковой генерацией",
examples=[
"Объясни, как работает квантование AWQ?",
"Напиши стихотворение про ИИ",
"Какие преимущества у Qwen3 перед предыдущими версиями?"
],
theme="soft",
)
if __name__ == "__main__": if __name__ == "__main__":
# ВАЖНО: используем .queue() для поддержки асинхронной обработки main()
demo.queue(max_size=20, default_concurrency_limit=10).launch(
server_port=8080,
server_name="0.0.0.0",
share=False,
# Можно добавить: max_batch_size=1, concurrency_count=4
)

77
app/old_without_db.py Normal file
View File

@@ -0,0 +1,77 @@
import gradio as gr
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
import torch
import threading
# Пути к модели
model_name = "/models/Qwen3-8B"
# Загрузка токенизатора и модели (один раз при старте)
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="cuda",
trust_remote_code=True
)
# Отключим кэширование в истории, чтобы каждый запрос был независимым
def generate_response(message, history):
# Форматируем диалог: используем только текущую историю
prompt = ""
for human, assistant in history:
prompt += f"<|im_start|>user\n{human}<|im_end|>\n<|im_start|>assistant\n{assistant}<|im_end|>\n"
prompt += f"<|im_start|>user\n{message}<|im_end|>\n<|im_start|>assistant\n"
# Токенизация
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
# Создаём уникальный streamer для каждого запроса
streamer = TextIteratorStreamer(
tokenizer,
skip_prompt=True,
skip_special_tokens=True
)
# Параметры генерации
generation_kwargs = {
"input_ids": inputs["input_ids"],
"max_new_tokens": 1024,
"temperature": 0.6,
"top_p": 0.9,
"do_sample": True,
"pad_token_id": tokenizer.eos_token_id,
"streamer": streamer,
}
# Запускаем генерацию в отдельном потоке
thread = threading.Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
# Постепенно возвращаем результат
buffer = ""
for new_text in streamer:
buffer += new_text
yield buffer.strip()
# Создаем интерфейс
demo = gr.ChatInterface(
fn=generate_response,
title="Qwen3-4B-Base Chat",
description="Общайтесь с моделью Qwen3-4B-Base в режиме реального времени с потоковой генерацией",
examples=[
"Объясни, как работает квантование AWQ?",
"Напиши стихотворение про ИИ",
"Какие преимущества у Qwen3 перед предыдущими версиями?"
],
theme="soft",
)
if __name__ == "__main__":
# ВАЖНО: используем .queue() для поддержки асинхронной обработки
demo.queue(max_size=20, default_concurrency_limit=10).launch(
server_port=8080,
server_name="0.0.0.0",
share=False,
# Можно добавить: max_batch_size=1, concurrency_count=4
)

View File

@@ -1,161 +0,0 @@
from typing import Any, Dict, List, Literal, Optional
import torch
from config import settings
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from loguru import logger
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
class ChatWithAI:
def __init__(self, provider: "qwen3"):
self.provider = provider
self.embeddings = HuggingFaceEmbeddings(
model_name=settings.LM_MODEL_NAME,
model_kwargs={"device": "cuda"},
encode_kwargs={"normalize_embeddings": True},
)
if provider == "qwen3":
model_name = getattr(settings, "LOCAL_LLM_NAME", "/models/Qwen3-4B")
logger.info(f"Загрузка локальной модели: {model_name}")
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float32,
device_map="cuda",
)
#Создаём text-generation pipeline
self.llm = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
device_map="cuda", # 0 = GPU, -1 = CPU
temperature=0.7,
max_new_tokens=512,
do_sample=True,
pad_token_id=tokenizer.eos_token_id,
)
else:
raise ValueError(f"Неподдерживаемый провайдер: {provider}")
self.chroma_db = Chroma(
persist_directory=settings.DOCS_CHROMA_PATH,
embedding_function=self.embeddings,
collection_name=settings.DOCS_COLLECTION_NAME,
)
def get_relevant_context(self, query: str, k: int = 3) -> List[Dict[str, Any]]:
"""Получение релевантного контекста из базы данных."""
try:
results = self.chroma_db.similarity_search(query, k=k)
return [
{
"text": doc.page_content,
"metadata": doc.metadata,
}
for doc in results
]
except Exception as e:
logger.error(f"Ошибка при получении контекста: {e}")
return []
def format_context(self, context: List[Dict[str, Any]]) -> str:
"""Форматирование контекста для промпта."""
formatted_context = []
for item in context:
metadata_str = "\n".join(f"{k}: {v}" for k, v in item["metadata"].items())
formatted_context.append(
f"Текст: {item['text']}\nМетаданные:\n{metadata_str}\n"
)
return "\n---\n".join(formatted_context)
def generate_response(self, query: str) -> Optional[str]:
"""Генерация ответа на основе запроса и контекста."""
try:
context = self.get_relevant_context(query)
if not context:
return "Извините, не удалось найти релевантный контекст для ответа."
formatted_context = self.format_context(context)
messages = [
{
"role": "system",
"content": """Ты — внутренний менеджер компании Amvera Cloud. Отвечаешь по делу без лишних вступлений.
Правила:
1. Сразу переходи к сути, без фраз типа "На основе контекста"
2. Используй только факты. Если точных данных нет — отвечай общими фразами об Marzban, но не придумывай конкретику
3. Используй обычный текст без форматирования
4. Включай ссылки только если они есть в контексте
5. Говори от первого лица множественного числа: "Мы предоставляем", "У нас есть"
6. При упоминании файлов делай это естественно, например: "Я прикреплю инструкцию, где подробно описаны шаги"
7. На приветствия отвечай доброжелательно, на негатив — с легким юмором
8. Можешь при ответах использовать общую информацию из открытых источников по Marzban, но опирайся на контекст
9. Если пользователь спрашивает о ценах, планах или технических характеристиках — давай конкретные ответы из контекста
10. При технических вопросах предлагай практические решения
Персонализируй ответы, упоминая имя клиента если оно есть в контексте. Будь краток, информативен и полезен.""",
},
{
"role": "user",
"content": f"Вопрос: {query}\nКонтекст: {formatted_context}",
},
]
# Генерация через transformers
if self.provider == "qwen3":
# Используем токенизатор модели для форматирования чата
tokenizer = self.llm.tokenizer
model = self.llm.model
# Применяем chat template (поддерживается в современных моделях: Zephyr, Llama3, Qwen и т.д.)
prompt = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
)
# Генерация
outputs = self.llm(
prompt,
max_new_tokens=512,
temperature=0.7,
do_sample=True,
top_p=0.9,
pad_token_id=tokenizer.eos_token_id,
)
response_text = outputs[0]["generated_text"]
# Убираем входной промпт, оставляем только ответ
if prompt in response_text:
response_text = response_text[len(prompt):].strip()
return response_text
else:
# Остальные провайдеры (deepseek, openai) используют langchain
response = self.llm.invoke(messages)
if hasattr(response, "content"):
return str(response.content)
return str(response).strip()
except Exception as e:
logger.error(f"Ошибка при генерации ответа: {e}")
return "Произошла ошибка при генерации ответа."
if __name__ == "__main__":
chat = ChatWithAI(provider="qwen3")
print("\n=== Чат с ИИ ===\n")
while True:
query = input("Вы: ")
if query.lower() == "выход":
print("\nДо свидания!")
break
print("\nИИ печатает...", end="\r")
response = chat.generate_response(query)
print(" " * 20, end="\r") # Очищаем "ИИ печатает..."
print(f"ИИ: {response}\n")

View File

@@ -17,9 +17,6 @@ services:
volumes: volumes:
- ./app:/app - ./app:/app
- ./models:/models - ./models:/models
- ./cache/cache:/root/.cache
- ./cache/site-packages:/usr/local/lib/python3.10/site-packages
- ./offline_packages:/offline_packages
entrypoint: sleep 1000000 #./app/entrypoint.sh entrypoint: sleep 1000000 #./app/entrypoint.sh
ports: ports:
- "8080:8080" - "8080:8080"

51
install.sh Normal file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
apt-get update
apt-get install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg && \
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
apt-get update
export NVIDIA_CONTAINER_TOOLKIT_VERSION=1.17.8-1
apt-get install -y \
nvidia-container-toolkit=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
nvidia-container-toolkit-base=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container-tools=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container1=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
nvidia-container-runtime \
docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin \
git git-lfs
git lfs install
echo \
'{
"runtimes": {
"nvidia": {
"path": "nvidia-container-runtime",
"runtimeArgs": []
}
},
"default-runtime": "nvidia"
}' > /etc/docker/daemon.json
mkdir models/
cd models
git clone https://huggingface.co/Qwen/Qwen3-8B
git clone https://huggingface.co/sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
ls
lspci -k | grep -EA2 'VGA|3D'
nvidia-smi