Compare commits
7 Commits
2a15a4052e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 370a11a463 | |||
| 957903d190 | |||
| 5c585544d3 | |||
| cd2c2d235d | |||
| fd4eef8e7f | |||
| 669bb5830a | |||
| eed3a00efb |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
101
README.md
@@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Выводы
|
## Выводы
|
||||||
|
|
||||||
Был успешно выполнен полный цикл:
|
Был успешно выполнен полный цикл:
|
||||||
|
|||||||
@@ -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
18
app/data/docs/1-1.md
Normal 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
40
app/data/docs/1-2.md
Normal 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
21
app/data/docs/1-3.md
Normal 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
24
app/data/docs/1-4.md
Normal 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
22
app/data/docs/2-1.md
Normal 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
29
app/data/docs/2-2.md
Normal 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
23
app/data/docs/2-3.md
Normal 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
33
app/data/docs/2-4.md
Normal 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
15
app/data/docs/3-1.md
Normal 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
15
app/data/docs/3-2.md
Normal 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
21
app/data/docs/3-3.md
Normal 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
34
app/data/docs/3-4.md
Normal 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
17
app/data/docs/4-1.md
Normal 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
22
app/data/docs/4-2.md
Normal 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
19
app/data/docs/4-3.md
Normal 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
23
app/data/docs/4-4.md
Normal 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 становится мощной и надёжной платформой. Главное — регулярно бэкапьте конфигурацию.
|
||||||
229
app/main.py
229
app/main.py
@@ -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
77
app/old_without_db.py
Normal 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
|
||||||
|
)
|
||||||
161
app/test.py
161
app/test.py
@@ -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")
|
|
||||||
@@ -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
51
install.sh
Normal 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
|
||||||
Reference in New Issue
Block a user