VOIDER commited on
Commit
1be11fc
·
verified ·
1 Parent(s): b161f01

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +104 -59
app.py CHANGED
@@ -2,27 +2,19 @@ import os
2
  import sys
3
  import subprocess
4
 
5
- # --- УСТАНОВКА LLAMA-CPP-PYTHON (Runtime) ---
6
- # Устанавливаем версию с поддержкой Vision (CPU)
7
  try:
8
- from llama_cpp import Llama
9
- from llama_cpp.llama_chat_format import Qwen2VLChatHandler
10
- print("Библиотека llama-cpp-python и Qwen2VLChatHandler найдены.")
11
  except ImportError:
12
- print("Установка свежей версии llama-cpp-python...")
 
13
  subprocess.check_call([
14
  sys.executable, "-m", "pip", "install",
15
- "llama-cpp-python>=0.3.2",
16
  "--extra-index-url", "https://abetlen.github.io/llama-cpp-python/whl/cpu"
17
  ])
18
- print("Установка завершена! Импортируем...")
19
- from llama_cpp import Llama
20
- # Пытаемся импортировать хендлер после установки
21
- try:
22
- from llama_cpp.llama_chat_format import Qwen2VLChatHandler
23
- except ImportError:
24
- print("ВАЖНО: Qwen2VLChatHandler не найден. Возможно, версия библиотеки старая.")
25
- Qwen2VLChatHandler = None
26
 
27
  import gradio as gr
28
  from huggingface_hub import hf_hub_download
@@ -31,45 +23,96 @@ import io
31
  import re
32
  from PIL import Image
33
 
34
- # Настройки модели
35
  REPO_ID = "mradermacher/VisualQuality-R1-7B-GGUF"
36
  MODEL_FILENAME = "VisualQuality-R1-7B.Q8_0.gguf"
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  llm = None
39
 
40
  def load_model():
41
  global llm
42
  if llm is None:
43
  print(f"Загрузка модели {MODEL_FILENAME}...")
44
- model_path = hf_hub_download(repo_id=REPO_ID, filename=MODEL_FILENAME)
45
-
46
- # Настраиваем обработчик диалога СПЕЦИАЛЬНО для Qwen2-VL
47
- # Это решает проблему "Invalid chat handler" и ошибки с токенами
48
- chat_handler = None
49
- if Qwen2VLChatHandler:
50
- print("Активация режима Qwen2-VL Vision...")
51
- # Передаем путь к модели как clip_model_path, так как в unified GGUF
52
- # визуальный энкодер находится внутри основного файла
53
- chat_handler = Qwen2VLChatHandler(clip_model_path=model_path)
54
-
55
- llm = Llama(
56
- model_path=model_path,
57
- n_ctx=12288, # Контекст (картинки занимают много токенов)
58
- n_gpu_layers=0, # CPU
59
- verbose=True,
60
- chat_handler=chat_handler, # Подключаем ручной обработчик
61
- n_batch=512 # Размер батча для CPU
62
- )
63
- print("Модель успешно загружена!")
64
  return llm
65
 
66
  def process_image(image):
67
- # Ресайз обязателен для Qwen2-VL на CPU, иначе вылетит контекст 32k+
68
- max_size = 1024
69
- if max(image.size) > max_size:
70
- ratio = max_size / max(image.size)
71
- new_size = (int(image.size[0] * ratio), int(image.size[1] * ratio))
72
- image = image.resize(new_size, Image.Resampling.LANCZOS)
73
 
74
  buffered = io.BytesIO()
75
  image = image.convert("RGB")
@@ -81,15 +124,15 @@ def evaluate_image(image, progress=gr.Progress()):
81
  return "Пожалуйста, загрузите изображение.", ""
82
 
83
  try:
84
- progress(0.1, desc="Инициализация модели...")
85
  model = load_model()
86
 
87
- progress(0.3, desc="Обработка изображения...")
88
- base64_image = process_image(image)
89
- image_url = f"data:image/jpeg;base64,{base64_image}"
90
 
91
  system_prompt = "You are doing the image quality assessment task."
92
- user_prompt_text = (
93
  "What is your overall rating on the quality of this picture? "
94
  "The rating should be a float between 1 and 5, rounded to two decimal places, "
95
  "with 1 representing very poor quality and 5 representing excellent quality. "
@@ -101,15 +144,16 @@ def evaluate_image(image, progress=gr.Progress()):
101
  {
102
  "role": "user",
103
  "content": [
104
- {"type": "image_url", "image_url": {"url": image_url}},
105
- {"type": "text", "text": user_prompt_text}
106
  ]
107
  }
108
  ]
109
 
110
  full_response = ""
111
- print("Генерация ответа...")
112
 
 
113
  stream = model.create_chat_completion(
114
  messages=messages,
115
  max_tokens=1024,
@@ -123,22 +167,23 @@ def evaluate_image(image, progress=gr.Progress()):
123
  if "content" in delta and delta["content"]:
124
  content = delta["content"]
125
  full_response += content
126
- yield full_response, "Вычисляется..."
127
 
128
- # Парсинг оценки
129
  score_match = re.search(r'<answer>\s*([\d\.]+)\s*</answer>', full_response)
130
- final_score = score_match.group(1) if score_match else "Не найдено"
131
 
132
  yield full_response, final_score
133
 
134
  except Exception as e:
135
- error_msg = f"Ошибка: {str(e)}"
136
- print(error_msg)
137
- yield error_msg, "Error"
138
 
139
- with gr.Blocks(title="VisualQuality-R1") as demo:
 
140
  gr.Markdown("# 👁️ VisualQuality-R1 (Qwen2-VL)")
141
- gr.Markdown("Оценка качества изображений. Запуск на CPU (может быть медленно).")
142
 
143
  with gr.Row():
144
  with gr.Column():
@@ -147,7 +192,7 @@ with gr.Blocks(title="VisualQuality-R1") as demo:
147
 
148
  with gr.Column():
149
  output_score = gr.Label(label="Оценка")
150
- output_text = gr.Textbox(label="CoT (Мысли модели)", lines=15)
151
 
152
  run_btn.click(evaluate_image, inputs=[input_img], outputs=[output_text, output_score])
153
 
 
2
  import sys
3
  import subprocess
4
 
5
+ # --- ПРОВЕРКА И УСТАНОВКА БИБЛИОТЕКИ ---
 
6
  try:
7
+ from llama_cpp import Llama, LlamaChatCompletionHandler
8
+ print("Библиотека llama-cpp-python найдена.")
 
9
  except ImportError:
10
+ print("Установка llama-cpp-python (CPU)...")
11
+ # Принудительно ставим 0.3.16 или новее с поддержкой CPU
12
  subprocess.check_call([
13
  sys.executable, "-m", "pip", "install",
14
+ "llama-cpp-python>=0.3.16",
15
  "--extra-index-url", "https://abetlen.github.io/llama-cpp-python/whl/cpu"
16
  ])
17
+ from llama_cpp import Llama, LlamaChatCompletionHandler
 
 
 
 
 
 
 
18
 
19
  import gradio as gr
20
  from huggingface_hub import hf_hub_download
 
23
  import re
24
  from PIL import Image
25
 
26
+ # Конфигурация
27
  REPO_ID = "mradermacher/VisualQuality-R1-7B-GGUF"
28
  MODEL_FILENAME = "VisualQuality-R1-7B.Q8_0.gguf"
29
 
30
+ # === ГЛАВНЫЙ ФИКС: СВОЙ ОБРАБОТЧИК ДЛЯ QWEN2-VL ===
31
+ # Мы не зависим от встроенных классов, а пишем свой.
32
+ class CustomQwen2VLHandler(LlamaChatCompletionHandler):
33
+ def __init__(self, clip_model_path=None, verbose=False):
34
+ self.clip_model_path = clip_model_path
35
+ self.verbose = verbose
36
+
37
+ def __call__(self, llama: Llama, messages, functions=None, function_call=None, tools=None, tool_choice=None, **kwargs):
38
+ # 1. Формируем промпт вручную с правильными тегами
39
+ prompt = ""
40
+ images = []
41
+
42
+ for message in messages:
43
+ role = message["role"]
44
+ content = message["content"]
45
+
46
+ # Начало сообщения
47
+ prompt += f"<|im_start|>{role}\n"
48
+
49
+ if isinstance(content, str):
50
+ prompt += content
51
+ elif isinstance(content, list):
52
+ for part in content:
53
+ if part["type"] == "text":
54
+ prompt += part["text"]
55
+ elif part["type"] == "image_url":
56
+ # Теги для Qwen2-VL: Vision Start -> Pad -> Vision End
57
+ prompt += "<|vision_start|><|image_pad|><|vision_end|>"
58
+
59
+ # Извлекаем байты из base64 для передачи в C++ слой
60
+ try:
61
+ image_url = part["image_url"]["url"]
62
+ if "base64," in image_url:
63
+ base64_data = image_url.split("base64,")[1]
64
+ image_bytes = base64.b64decode(base64_data)
65
+ images.append(image_bytes)
66
+ except Exception as e:
67
+ print(f"Ошибка декодирования картинки: {e}")
68
+
69
+ # Конец сообщения
70
+ prompt += "<|im_end|>\n"
71
+
72
+ # Добавляем триггер для ответа ассистента
73
+ prompt += "<|im_start|>assistant\n"
74
+
75
+ if self.verbose:
76
+ print(f"=== SENDED PROMPT ({len(prompt)} chars) ===")
77
+ print(prompt[:200] + "..." if len(prompt) > 200 else prompt)
78
+ print(f"=== IMAGES: {len(images)} ===")
79
+
80
+ # Возвращаем кортеж (prompt, images), который понимает llama.cpp
81
+ return prompt, images
82
+
83
  llm = None
84
 
85
  def load_model():
86
  global llm
87
  if llm is None:
88
  print(f"Загрузка модели {MODEL_FILENAME}...")
89
+ try:
90
+ model_path = hf_hub_download(repo_id=REPO_ID, filename=MODEL_FILENAME)
91
+
92
+ # Инициализируем НАШ кастомный хендлер
93
+ # clip_model_path указываем на тот же файл (так как это GGUF all-in-one)
94
+ chat_handler = CustomQwen2VLHandler(clip_model_path=model_path, verbose=True)
95
+
96
+ llm = Llama(
97
+ model_path=model_path,
98
+ n_ctx=8192, # Контекст (картинки большие, нужно место)
99
+ n_gpu_layers=0, # CPU
100
+ verbose=True,
101
+ chat_handler=chat_handler, # <-- ВАЖНО: Используем наш класс
102
+ n_batch=512,
103
+ logits_all=True
104
+ )
105
+ print("Модель успешно загружена с CustomQwen2VLHandler!")
106
+ except Exception as e:
107
+ print(f"Ошибка загрузки: {e}")
108
+ raise e
109
  return llm
110
 
111
  def process_image(image):
112
+ # Ресайз до 1024px макс, чтобы не перегружать CPU память и контекст
113
+ max_dim = 1024
114
+ if max(image.size) > max_dim:
115
+ image.thumbnail((max_dim, max_dim), Image.Resampling.LANCZOS)
 
 
116
 
117
  buffered = io.BytesIO()
118
  image = image.convert("RGB")
 
124
  return "Пожалуйста, загрузите изображение.", ""
125
 
126
  try:
127
+ progress(0.1, desc="Загрузка модели...")
128
  model = load_model()
129
 
130
+ progress(0.2, desc="Обработка...")
131
+ base64_img = process_image(image)
132
+ img_url = f"data:image/jpeg;base64,{base64_img}"
133
 
134
  system_prompt = "You are doing the image quality assessment task."
135
+ user_prompt = (
136
  "What is your overall rating on the quality of this picture? "
137
  "The rating should be a float between 1 and 5, rounded to two decimal places, "
138
  "with 1 representing very poor quality and 5 representing excellent quality. "
 
144
  {
145
  "role": "user",
146
  "content": [
147
+ {"type": "image_url", "image_url": {"url": img_url}},
148
+ {"type": "text", "text": user_prompt}
149
  ]
150
  }
151
  ]
152
 
153
  full_response = ""
154
+ print("Начинаю генерацию...")
155
 
156
+ # Запуск стриминга
157
  stream = model.create_chat_completion(
158
  messages=messages,
159
  max_tokens=1024,
 
167
  if "content" in delta and delta["content"]:
168
  content = delta["content"]
169
  full_response += content
170
+ yield full_response, "Думаю..."
171
 
172
+ # Поиск оценки
173
  score_match = re.search(r'<answer>\s*([\d\.]+)\s*</answer>', full_response)
174
+ final_score = score_match.group(1) if score_match else "Оценка не найдена"
175
 
176
  yield full_response, final_score
177
 
178
  except Exception as e:
179
+ err_msg = f"Произошла ошибка: {str(e)}"
180
+ print(err_msg)
181
+ yield err_msg, "Error"
182
 
183
+ # Интерфейс
184
+ with gr.Blocks(title="VisualQuality-R1 (Custom Handler)") as demo:
185
  gr.Markdown("# 👁️ VisualQuality-R1 (Qwen2-VL)")
186
+ gr.Markdown("Оценка качества изображений на CPU с кастомным обработчиком.")
187
 
188
  with gr.Row():
189
  with gr.Column():
 
192
 
193
  with gr.Column():
194
  output_score = gr.Label(label="Оценка")
195
+ output_text = gr.Textbox(label="CoT (Рассуждения)", lines=15)
196
 
197
  run_btn.click(evaluate_image, inputs=[input_img], outputs=[output_text, output_score])
198