This commit is contained in:
2026-02-25 23:48:01 +03:00
commit 6c4cf7ba2f
9 changed files with 894 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
# ROCm-совместимый образ с PyTorch
FROM rocm/pytorch:rocm5.7.3_ubuntu22.04_py3.10_pytorch2.1.2
# Установка системных зависимостей
RUN apt-get update && apt-get install -y \
ffmpeg \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Создание рабочей директории
WORKDIR /app
# Установка переменных окружения для Python
ENV PYTHONUNBUFFERED=1
ENV HSA_OVERRIDE_GFX_VERSION=10.3.0
# Установка Python зависимостей (без torch/torchaudio - уже в базовом образе)
RUN pip install --no-cache-dir \
fastapi==0.109.0 \
uvicorn[standard]==0.27.0 \
python-multipart==0.0.6 \
openai-whisper==20231117
# Копирование исходного кода
COPY main.py .
# Экспорт порта
EXPOSE 8080
# Запуск приложения
CMD ["python", "main.py"]
+148
View File
@@ -0,0 +1,148 @@
"""
Whisper API - FastAPI сервис для транскрибации аудио
Поддержка ROCm (AMD GPU)
Эндпоинт: POST /transcribe
"""
import os
import tempfile
import whisper
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.responses import JSONResponse
import torch
app = FastAPI(title="Whisper Transcription API")
# Загрузка модели при старте
MODEL_SIZE = os.getenv("MODEL_SIZE", "base")
DEVICE_ENV = os.getenv("DEVICE", "cuda")
# Определение устройства для инференса
# PyTorch на ROCm определяет AMD GPU как CUDA-совместимое устройство
def get_device():
"""Определение доступного устройства для инференса"""
if DEVICE_ENV == "cuda" and torch.cuda.is_available():
device_name = torch.cuda.get_device_name(0)
print(f"Используем GPU: {device_name}")
return "cuda"
else:
print("Используем CPU")
return "cpu"
DEVICE = get_device()
print(f"Загрузка модели Whisper {MODEL_SIZE} на устройство: {DEVICE}")
# Информация о GPU
if torch.cuda.is_available():
gpu_name = torch.cuda.get_device_name(0)
gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
print(f"GPU: {gpu_name}, память: {gpu_memory:.1f} GB")
try:
model = whisper.load_model(MODEL_SIZE, device=DEVICE)
print("Модель успешно загружена")
except Exception as e:
print(f"Ошибка загрузки модели: {e}")
model = None
@app.get("/health")
async def health_check():
"""Проверка здоровья сервиса"""
gpu_info = None
if torch.cuda.is_available():
gpu_info = {
"name": torch.cuda.get_device_name(0),
"available": True
}
return {
"status": "healthy",
"model_loaded": model is not None,
"device": DEVICE,
"gpu": gpu_info
}
@app.get("/")
async def root():
"""Корневой эндпоинт с информацией об API"""
return {
"service": "Whisper API (ROCm/AMD GPU)",
"model": MODEL_SIZE,
"device": DEVICE,
"gpu_available": torch.cuda.is_available(),
"endpoints": {
"transcribe": "POST /transcribe",
"health": "GET /health"
}
}
@app.post("/transcribe")
async def transcribe(
file: UploadFile = File(...),
task: str = Form("transcribe"),
language: str = Form(None)
):
"""
Транскрибация или перевод аудио файла
Параметры:
- file: аудио/видео файл
- task: "transcribe" или "translate"
- language: код языка (опционально, например "ru", "en")
Пример curl:
curl -X POST -F "file=@test.m4a" -F "task=transcribe" http://localhost:8080/transcribe
"""
if model is None:
raise HTTPException(status_code=503, detail="Модель Whisper не загружена")
# Проверка типа задачи
if task not in ["transcribe", "translate"]:
raise HTTPException(status_code=400, detail="Недопустимое значение task. Используйте 'transcribe' или 'translate'")
# Сохранение загруженного файла во временный файл
with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(file.filename)[1]) as tmp_input:
content = await file.read()
tmp_input.write(content)
tmp_input_path = tmp_input.name
try:
# Параметры транскрибации
transcribe_options = {
"task": task,
"verbose": False,
}
# Добавляем язык если указан
if language:
transcribe_options["language"] = language
# Выполнение транскрибации
result = model.transcribe(tmp_input_path, **transcribe_options)
# Возвращаем результат
return JSONResponse(content={
"text": result["text"],
"segments": result.get("segments", []),
"language": result.get("language", language),
"task": task,
"filename": file.filename,
"device_used": DEVICE
})
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка при транскрибации: {str(e)}")
finally:
# Удаление временного файла
try:
os.unlink(tmp_input_path)
except:
pass
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
+6
View File
@@ -0,0 +1,6 @@
fastapi==0.109.0
uvicorn[standard]==0.27.0
python-multipart==0.0.6
openai-whisper==20231117
torch==2.1.2
torchaudio==2.1.2