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/data/chroma_db
/app/data/parsed_json/
/app/data/docs/
/app/__pycache__
/models
/cache

View File

@@ -28,7 +28,7 @@ RUN pip install --upgrade pip && \
WORKDIR /app
# Копируем только requirements.txt для кэширования слоя
COPY offline_packages/test.txt /app/requirements.txt
COPY requirements.txt /app/requirements.txt
# Установка зависимостей (включая GPU-версии)
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. Установка системных зависимостей
### Установка заголовков ядра Linux
```bash
apt install linux-headers-$(uname -r)
```
## 3. Проверка наличия драйверов видеокарты
### Проверка видеодрайверов
```bash
@@ -133,8 +128,10 @@ git lfs install
> ✅ Успешное клонирование:
```bash
git clone https://huggingface.co/Qwen/Qwen3-8B
cd Qwen3-8B/
mkdir models/
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
from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict
@@ -13,8 +12,8 @@ class Config(BaseSettings):
MAX_CHUNK_SIZE: int = 512
CHUNK_OVERLAP: int = 50
LM_MODEL_NAME: str = "/models/paraphrase-multilingual-MiniLM-L12-v2"
LOCAL_LLM_NAME: str = "/models/Qwen3-4B"
QWEN_MODEL_NAME: str = "qwen3-chat"
LOCAL_LLM_NAME: str = "/models/Qwen3-8B"
QWEN_MODEL_NAME: str = "Qwen3-8B"
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
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
import torch
import threading
from loguru import logger
from langchain_chroma import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
# Пути к модели
model_name = "/models/Qwen3-8B"
# Предполагается, что у тебя есть config.py с settings
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
)
# Отключим кэширование в истории, чтобы каждый запрос был независимым
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"
class ChatWithAI:
def __init__(self, provider: str = "qwen3"):
self.provider = provider
self.embeddings = HuggingFaceEmbeddings(
model_name=settings.LM_MODEL_NAME,
model_kwargs={"device": "cuda"},
encode_kwargs={"normalize_embeddings": True},
)
# Токенизация
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
if provider == "qwen3":
model_name = getattr(settings, "LOCAL_LLM_NAME", "/models/Qwen3-8B")
logger.info(f"Загрузка локальной модели: {model_name}")
# Создаём уникальный streamer для каждого запроса
streamer = TextIteratorStreamer(
tokenizer,
skip_prompt=True,
skip_special_tokens=True
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
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__":
# ВАЖНО: используем .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
)
main()

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:
- ./app:/app
- ./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
ports:
- "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