Compare commits
11 Commits
change-mod
...
vllm
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a6b8097f1 | |||
| 4c4d2ae635 | |||
| ec32fadec3 | |||
| 6ec3b0ed04 | |||
| 82b7be53e6 | |||
| 711ad223a5 | |||
| 370a11a463 | |||
| 957903d190 | |||
| 5c585544d3 | |||
| cd2c2d235d | |||
| fd4eef8e7f |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
/app/core
|
||||
/app/data/chroma_db
|
||||
/app/data/parsed_json/
|
||||
/app/data/docs/
|
||||
/app/__pycache__
|
||||
/models
|
||||
/cache
|
||||
|
||||
@@ -15,6 +15,10 @@ RUN apt-get update && \
|
||||
wget \
|
||||
libgl1 \
|
||||
libglib2.0-0 \
|
||||
build-essential \
|
||||
python3.10-dev \
|
||||
python3.10-distutils \
|
||||
libpython3-dev gcc g++ make \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Установка Python 3.10 как основного
|
||||
@@ -28,7 +32,7 @@ RUN pip install --upgrade pip && \
|
||||
WORKDIR /app
|
||||
|
||||
# Копируем только requirements.txt для кэширования слоя
|
||||
COPY offline_packages/requirements.txt /app/requirements.txt
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
|
||||
# Установка зависимостей (включая GPU-версии)
|
||||
RUN pip install --no-cache-dir -r /app/requirements.txt
|
||||
|
||||
@@ -207,7 +207,7 @@ df -h
|
||||
|
||||
### Клонирование базовой версии Qwen3-4B
|
||||
```bash
|
||||
git clone https://huggingface.co/Qwen/Qwen3-4B-Base
|
||||
git clone https://huggingface.co/Qwen/Qwen3-4B
|
||||
```
|
||||
|
||||
---
|
||||
@@ -334,7 +334,7 @@ history > history.md
|
||||
Был успешно выполнен полный цикл:
|
||||
- Установка Docker и поддержки GPU
|
||||
- Настройка NVIDIA Container Toolkit
|
||||
- Загрузка моделей **Qwen3-8B** и **Qwen3-4B-Base**
|
||||
- Загрузка моделей **Qwen3-4B**
|
||||
- Развёртывание LLM-приложения через Docker Compose
|
||||
- Отладка и пересборка контейнера
|
||||
- Подготовка офлайн-образа для переноса
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
|
||||
from pydantic import SecretStr
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
@@ -10,11 +9,11 @@ class Config(BaseSettings):
|
||||
PARSED_JSON_PATH: str = os.path.join(BASE_DIR, "data", "parsed_json")
|
||||
DOCS_CHROMA_PATH: str = os.path.join(BASE_DIR, "data", "chroma_db")
|
||||
DOCS_COLLECTION_NAME: str = "docs"
|
||||
MAX_CHUNK_SIZE: int = 512
|
||||
MAX_CHUNK_SIZE: int = 2048
|
||||
CHUNK_OVERLAP: int = 50
|
||||
LM_MODEL_NAME: str = "/models/paraphrase-multilingual-MiniLM-L12-v2"
|
||||
LM_MODEL_NAME: str = "/models/Qwen3-Embedding-0.6B"
|
||||
LOCAL_LLM_NAME: str = "/models/Qwen3-4B"
|
||||
QWEN_MODEL_NAME: str = "qwen3-chat"
|
||||
QWEN_MODEL_NAME: str = "Qwen3-8B"
|
||||
|
||||
|
||||
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 становится мощной и надёжной платформой. Главное — регулярно бэкапьте конфигурацию.
|
||||
240
app/main.py
240
app/main.py
@@ -1,58 +1,55 @@
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, AsyncGenerator
|
||||
import gradio as gr
|
||||
import uuid
|
||||
import torch
|
||||
import threading
|
||||
import asyncio
|
||||
from transformers import AutoTokenizer as HFTokenizer
|
||||
from loguru import logger
|
||||
from langchain_chroma import Chroma
|
||||
from langchain_huggingface import HuggingFaceEmbeddings
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
|
||||
|
||||
# Предполагается, что у тебя есть config.py с settings
|
||||
from vllm import AsyncLLMEngine, SamplingParams
|
||||
from vllm.engine.arg_utils import AsyncEngineArgs
|
||||
from config import settings
|
||||
|
||||
|
||||
class ChatWithAI:
|
||||
def __init__(self, model_name: str):
|
||||
self.model_name = model_name
|
||||
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},
|
||||
)
|
||||
|
||||
if provider == "qwen3":
|
||||
model_name = getattr(settings, "LOCAL_LLM_NAME", "/models/Qwen3-8B")
|
||||
logger.info(f"Инициализация vLLM движка с моделью: {model_name}")
|
||||
|
||||
# Аргументы для асинхронного движка vLLM
|
||||
engine_args = AsyncEngineArgs(
|
||||
model=model_name,
|
||||
tokenizer=model_name,
|
||||
tokenizer_mode="auto",
|
||||
trust_remote_code=True,
|
||||
dtype=torch.bfloat16, # float16
|
||||
gpu_memory_utilization=0.65,
|
||||
max_model_len=32768,
|
||||
enable_prefix_caching=True,
|
||||
tensor_parallel_size=1, # Увеличь, если у тебя несколько GPU
|
||||
enable_chunked_prefill=False,
|
||||
)
|
||||
self.engine = AsyncLLMEngine.from_engine_args(engine_args)
|
||||
self.tokenizer = HFTokenizer.from_pretrained(settings.LM_MODEL_NAME, trust_remote_code=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,
|
||||
)
|
||||
|
||||
self.tokenizer = None
|
||||
self.model = None
|
||||
self.streamer = None
|
||||
|
||||
self.load_model(model_name)
|
||||
|
||||
def load_model(self, model_name: str):
|
||||
"""Загружает модель и токенизатор, освобождает предыдущие ресурсы."""
|
||||
if hasattr(self, "model") and self.model is not None:
|
||||
del self.model
|
||||
torch.cuda.empty_cache()
|
||||
logger.info("Предыдущая модель удалена.")
|
||||
|
||||
logger.info(f"Загрузка модели: {model_name}")
|
||||
try:
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
||||
self.model = AutoModelForCausalLM.from_pretrained(
|
||||
model_name,
|
||||
torch_dtype=torch.float32,
|
||||
device_map="cuda",
|
||||
)
|
||||
logger.info(f"Модель {model_name} успешно загружена.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при загрузке модели {model_name}: {e}")
|
||||
raise
|
||||
|
||||
def get_relevant_context(self, query: str, k: int = 3) -> List[Dict[str, Any]]:
|
||||
def get_relevant_context(self, query: str, k: int = 5) -> List[Dict[str, Any]]:
|
||||
"""Получение релевантного контекста из базы данных."""
|
||||
try:
|
||||
results = self.chroma_db.similarity_search(query, k=k)
|
||||
@@ -77,9 +74,10 @@ class ChatWithAI:
|
||||
)
|
||||
return "\n---\n".join(formatted_context)
|
||||
|
||||
def generate_response_stream(self, query: str):
|
||||
"""Генерация ответа с потоковой передачей токенов."""
|
||||
async def generate_response_stream(self, query: str) -> AsyncGenerator[str, None]:
|
||||
"""Генерация ответа с потоковой передачей токенов через vLLM."""
|
||||
try:
|
||||
logger.info(f"Пользовательский запрос: {query}")
|
||||
context = self.get_relevant_context(query)
|
||||
if not context:
|
||||
yield "Извините, не удалось найти релевантный контекст для ответа."
|
||||
@@ -90,17 +88,17 @@ class ChatWithAI:
|
||||
messages = [
|
||||
{
|
||||
"role": "system",
|
||||
"content": """Ты — внутренний менеджер компании Amvera Cloud. Отвечаешь по делу без лишних вступлений.
|
||||
"content": """Ты — внутренний менеджер помощи пользоваткля по вопросам настройки сервера. Отвечаешь по делу без лишних вступлений.
|
||||
|
||||
Правила:
|
||||
1. Сразу переходи к сути, без фраз типа "На основе контекста"
|
||||
2. Используй только факты. Если точных данных нет — отвечай общими фразами об Marzban, но не придумывай конкретику
|
||||
3. Используй обычный текст без форматирования
|
||||
2. Используй только факты. Если точных данных нет — отвечай общими фразами об настройки сервера, но не придумывай конкретику
|
||||
3. Используй текст с форматированием.
|
||||
4. Включай ссылки только если они есть в контексте
|
||||
5. Говори от первого лица множественного числа: "Мы предоставляем", "У нас есть"
|
||||
6. При упоминании файлов делай это естественно, например: "Я прикреплю инструкцию, где подробно описаны шаги"
|
||||
7. На приветствия отвечай доброжелательно, на негатив — с легким юмором
|
||||
8. Можешь при ответах использовать общую информацию из открытых источников по Marzban, но опирайся на контекст
|
||||
8. Можешь при ответах использовать общую информацию из открытых источников по настройке сервера, но опирайся на контекст
|
||||
9. Если пользователь спрашивает о ценах, планах или технических характеристиках — давай конкретные ответы из контекста
|
||||
10. При технических вопросах предлагай практические решения
|
||||
|
||||
@@ -120,131 +118,67 @@ class ChatWithAI:
|
||||
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
|
||||
# Параметры генерации
|
||||
sampling_params = SamplingParams(
|
||||
temperature=0.3,
|
||||
top_p=0.9,
|
||||
max_tokens=2048,
|
||||
stop_token_ids=[], # можно добавить, если нужно
|
||||
)
|
||||
|
||||
# Параметры генерации
|
||||
generate_kwargs = {
|
||||
"input_ids": inputs["input_ids"],
|
||||
"max_new_tokens": 512,
|
||||
"temperature": 0.7,
|
||||
"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()
|
||||
|
||||
# Потоковая передача токенов
|
||||
# Генерация через vLLM
|
||||
final_output = ""
|
||||
buffer = ""
|
||||
for token in self.streamer:
|
||||
buffer += token
|
||||
yield buffer
|
||||
|
||||
# Генерируем асинхронно
|
||||
request_id = str(uuid.uuid4())
|
||||
try:
|
||||
async for output in self.engine.generate(prompt, sampling_params, request_id):
|
||||
if output.outputs:
|
||||
text = output.outputs[0].text
|
||||
# Отправляем только новые токены
|
||||
new_text = text[len(final_output):]
|
||||
final_output = text
|
||||
if new_text:
|
||||
buffer += new_text
|
||||
yield buffer # Постепенно возвращаем накопленный текст
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при генерации vLLM: {e}")
|
||||
yield "Произошла ошибка при генерации ответа."
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при генерации ответа: {e}")
|
||||
logger.error(f"Неожиданная ошибка: {e}")
|
||||
yield "Произошла ошибка при генерации ответа."
|
||||
|
||||
|
||||
# === Gradio интерфейс с выбором модели ===
|
||||
# === Gradio интерфейс ===
|
||||
def main():
|
||||
# Список доступных моделей (можно вынести в settings)
|
||||
MODEL_OPTIONS = {
|
||||
"Qwen3-4B (локальная)": getattr(settings, "LOCAL_LLM_NAME", "/models/Qwen3-4B"),
|
||||
"Alibaba/Qwen2.5-1.8B-Chat": "Alibaba/Qwen2.5-1.8B-Chat",
|
||||
"HuggingFaceH4/zephyr-7b-beta": "HuggingFaceH4/zephyr-7b-beta",
|
||||
# Добавь другие модели по желанию
|
||||
}
|
||||
chat = ChatWithAI(provider="qwen3")
|
||||
|
||||
chat = None # Будет инициализирована при выборе модели
|
||||
|
||||
def initialize_model(selected_model_key):
|
||||
"""Создаёт или перезагружает экземпляр ChatWithAI с выбранной моделью."""
|
||||
nonlocal chat
|
||||
if chat is not None:
|
||||
logger.info(f"Смена модели")
|
||||
# Удаляем экземпляр
|
||||
del chat
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
model_path = MODEL_OPTIONS[selected_model_key]
|
||||
try:
|
||||
chat = ChatWithAI(model_path)
|
||||
return gr.update(interactive=True, placeholder="Введите свой вопрос..."), \
|
||||
gr.update(visible=True), \
|
||||
f"✅ Модель загружена: {selected_model_key}"
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось загрузить модель {selected_model_key}: {e}")
|
||||
return gr.update(interactive=False), \
|
||||
gr.update(visible=False), \
|
||||
f"❌ Ошибка загрузки модели: {str(e)}"
|
||||
|
||||
def respond(message, history):
|
||||
async def respond(message, history):
|
||||
# Генерируем ответ по частям
|
||||
for token in chat.generate_response_stream(message):
|
||||
yield token
|
||||
try:
|
||||
async for token in chat.generate_response_stream(message):
|
||||
yield token
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в respond: {e}")
|
||||
yield "Извините, произошла ошибка при обработке запроса."
|
||||
|
||||
with gr.Blocks(title="Amvera Cloud Assistant") as demo:
|
||||
gr.Markdown("# 🤖 Amvera Cloud Assistant")
|
||||
gr.Markdown("Выберите модель и задайте вопрос — получите ответ с контекстом.")
|
||||
|
||||
with gr.Row():
|
||||
with gr.Column(scale=1):
|
||||
model_dropdown = gr.Dropdown(
|
||||
choices=list(MODEL_OPTIONS.keys()),
|
||||
value=list(MODEL_OPTIONS.keys())[0],
|
||||
label="Выберите модель",
|
||||
interactive=True
|
||||
)
|
||||
load_button = gr.Button("Загрузить модель", variant="primary")
|
||||
status_text = gr.Textbox(
|
||||
label="Состояние",
|
||||
value="Выберите модель и нажмите 'Загрузить модель'",
|
||||
interactive=False
|
||||
)
|
||||
|
||||
with gr.Column(scale=3):
|
||||
chatbot = gr.Chatbot(
|
||||
label="Чат",
|
||||
height=600,
|
||||
bubble_full_width=False,
|
||||
)
|
||||
msg = gr.Textbox(
|
||||
label="Сообщение",
|
||||
placeholder="Введите ваш вопрос...",
|
||||
interactive=False
|
||||
)
|
||||
clear = gr.Button("Очистить чат")
|
||||
|
||||
# Логика выбора модели
|
||||
load_button.click(
|
||||
initialize_model,
|
||||
inputs=[model_dropdown],
|
||||
outputs=[msg, chatbot, status_text]
|
||||
)
|
||||
|
||||
# Отправка сообщения
|
||||
msg.submit(
|
||||
respond,
|
||||
inputs=[msg, chatbot],
|
||||
outputs=[chatbot]
|
||||
)
|
||||
|
||||
# Очистка чата
|
||||
clear.click(lambda: None, None, chatbot, queue=False)
|
||||
|
||||
demo.launch(server_name="0.0.0.0", server_port=8080, share=False)
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -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"
|
||||
|
||||
31
install.sh
31
install.sh
@@ -9,17 +9,6 @@ echo \
|
||||
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
echo \
|
||||
'{ \
|
||||
"runtimes": { \
|
||||
"nvidia": { \
|
||||
"path": "nvidia-container-runtime", \
|
||||
"runtimeArgs": [] \
|
||||
} \
|
||||
}, \
|
||||
"default-runtime": "nvidia" \
|
||||
}' > /etc/docker/daemon.json
|
||||
|
||||
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' | \
|
||||
@@ -38,5 +27,25 @@ apt-get install -y \
|
||||
|
||||
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-4B
|
||||
git clone https://huggingface.co/Qwen/Qwen3-Embedding-0.6B
|
||||
|
||||
ls
|
||||
|
||||
lspci -k | grep -EA2 'VGA|3D'
|
||||
nvidia-smi
|
||||
|
||||
@@ -11,4 +11,8 @@ chromadb==0.6.3
|
||||
sentence-transformers==3.4.1
|
||||
langchain-chroma==0.2.2
|
||||
pydantic-settings==2.8.1
|
||||
langchain-text-splitters==0.3.7
|
||||
langchain-text-splitters==0.3.7
|
||||
vllm>=0.4.2
|
||||
aiohttp>=3.8.0
|
||||
fastapi>=0.100.0
|
||||
starlette>=0.27.0
|
||||
Reference in New Issue
Block a user