Привет Хабр! С вами снова ServerFlow, и мы начинаем наш новый цикл статей о проектах связанных с GPU. В начале цикла мы хотим заняться непривычной для нас темой — нейросетями, а именно большими языковыми моделями LLM. В сентябре‑октябре, судя по новостям вышел особенно богатый урожай мультимодальных нейросетей в открытом доступе, в этом посте будем смотреть на Pixtral 12B и LLaMA 32 11B, а запускать их будем на двух народных и легко доступных на Б/У рынке карточках для работы с нейросетями – Nvidia Tesla P100 и P40. Однако, если тестировать GPU мы привыкли, то вот работа с LLM отличается от привычных нам бенчмарков или тестов в условном Blender. Так что если вам покажется, что всё это мы делаем будто в первый раз, то вам не кажется. Но надеемся зато наш пример окажется полезным тем, кто так же в первый раз захочет повторить аналогичный эксперимент.
Начинаем
В ServerFlow мы недавно начали тестирование новых языковых моделей Pixtral 12B и LLaMA 3.2 11B, выпущенных в сентябре. Обе модели распространяются по модели Open Source, а также отличаются тем, что не требуют значительных вычислительных ресурсов для своего запуска и почти одновременно в своих новых версиях они стали мультимодальными, то есть поддерживают не только генерацию текста, но и другой функционал, в данном случае появилось распознавание изображений. Однако, так как эти версии моделей совсем свежие, мы столкнулись с трудностями с поиском готовых решений для их инференса, либо невозможностью их запуска.
Стоит пояснить, что инференс — это не только процесс применения обученной модели к новым данным, но и программное обеспечение, которое этим процессом управляет. Такие программы часто называют «инференс‑движками». Они отвечают за то, как данные поступают в нейросеть и как результаты из неё выводятся. Эффективность инференс‑движка напрямую влияет на производительность нейросети в целом.
Более того, многие решения для инференса выполняют роль менеджеров пакетов для различных нейросетей. Они часто включают функционал для квантизации моделей и другие инструменты для оптимизации. В нашем исследовании мы обнаружили, что популярный инструмент llama.cpp не поддерживает работу с изображениями, что критично для мультимодальных моделей. В Ollama нужных нам моделей не оказалось.
Попытки использовать Mistral.rs на нашей системе EndeavourOS (основанной на Arch Linux) и vLLM через Docker также не увенчались успехом. В итоге мы разработали собственные скрипты для инференса, опираясь на примеры из сообщества HuggingFace, так как без инференса сама нейросеть это просто большой файл на много гигабайт, который сам по себе ничего не сделает. Также стоит отметить, что в процессе тестирования мы обнаружили один интересный графический интерфейс, также распространяющийся по модели Open Source — ComfyUI. Ранее мы думали он подходит только для моделей генерирующих изображения по типу StableDiffusion, но как оказалось и для языковых моделей он тоже подходит.
Отдельно отметим, что мы сосредоточились на тестировании квантизированных версий нейросетей — 4-битных и 8-битных. Это решение обусловлено тем, что объем памяти большинства обычных видеокарт варьируется в диапазоне 12–16 гигабайт, тогда как неквантизированным версиям Pixtral и LLaMA требуется около 24 гигабайт видеопамяти. Да и в рамках нашего теста, мы бы тогда были ограничены только P40 с её 24 гигабайтами видеопамяти, а P100 в таком случае выбыла бы из сравнения, так как у неё всего 16 гигабайт видеопамяти, куда бы полноразмерная нейросеть не поместилась.
Не бывает тестов без тестового-стенда
Не забудем рассказать о нашем стенде для тестирования нейросетей. Вместо привычного сервера мы использовали открытый стенд для большей гибкости при смене конфигураций. Вот характеристики нашей тестовой системы:
Материнская плата: Supermicro H11SSL-I (Rev 2.0)
Процессор: AMD EPYC™ 7502 (32 ядра / 64 потока, 2.5GHz-3.35GHz, 180W, 128MB L3)
Система охлаждения: 4U башенного типа c TDP 240W
Оперативная память: 128GB (8 x 16GB, 3200 MHz, ECC REG)
Системный накопитель: Samsung PM9A1 1TB
Видеокарта 1: NVIDIA Tesla P40 (24GB GDDR5)
Видеокарта 2: NVIDIA Tesla P100 (16GB HBM2)
Блок питания: Cougar BXM 1000 [CGR BX-1000]
О Python и pip
В ServerFlow мы уделяем особое внимание лучшим практикам работы с Python и его пакетным менеджером pip. Но к сожалению, многие онлайн‑руководства для Linux и Windows рекомендуют установку пакетов в общесистемное окружение, что может привести к серьезным проблемам.
Представьте себе, что ваша операционная система — это многоквартирный дом. Python в этом случае — один из его важных жильцов. Установка пакетов через pip в общую систему подобна тому, как если бы вы позволили этому жильцу бесконтрольно менять общие коммуникации дома. Рано или поздно это приведет к конфликтам и поломкам. Особенно остро эта проблема стоит в Linux, где Python часто выполняет критические системные функции. Изменения в общесистемных пакетах Python могут нарушить работу самой операционной системы, что сравнимо с повреждением фундамента нашего воображаемого дома. Именно по‑этому, мы стараемся избегать такого подхода.
Arch Linux, дистрибутив, который мы используем, подходит к этому вопросу с особой осторожностью, хотя и имеет славу не «мешающего пользователю ставить самому себе палки в колёса». Стоит отметить, что по умолчанию в Arch Linux pip вообще нету, а после его установки через sudo pacman‑S python‑pip
, Arch не разрешит его использовать за пределами виртуального окружения venv. Это похоже на то, как если бы управляющая компания нашего дома запретила жильцам самостоятельно менять общие коммуникации, предоставив им вместо этого возможность обустраивать свои квартиры по своему усмотрению. Такой подход Arch Linux может показаться непривычным на фоне того что почти все гайды и туториалы в интернете игнорируют использование venv, но он значительно повышает стабильность и безопасность системы в долгосрочной перспективе.
Pixtral 12B
Архитектура модели Pixtral
Начнём с установки pip на наш сервер. Для начала подключимся к нему по SSH и заранее пробросим порт 8188 с него на нашу локальную машину, чтобы проще было пользоваться WebUI для наших нейронок.
ssh -L 8188:127.0.0.1:8188 -p 47645 serverflow@IP_SSH_сервера
Установим pip через пакетный менеджер Pacman.
sudo pacman -S python-pip
И создадим, а также сразу активируем venv.
python3 -m venv pixtral
source pixtral/bin/activate
И установим все необходимые зависимости.
pip install --upgrade pip
pip install torch transformers bitsandbytes accelerate gradio huggingface_hub numpy pillow requests
И залогинимся в huggingface-cli, для этого у вас должна быть предварительно уже создана учётка на HuggingFace и создан там токен со всеми разрешениями, который вы дальше вводите в консоль уже на ssh сервере. Можно обойтись и без HuggingFace, делая всё более локальным, однако, для наших задач это будет излишнее усложнение процесса.
huggingface-cli login
Как мы уже сказали ранее, за неимением, либо невозможностью запуска имеющихся готовых решений для инференса мы решили написать собственный скрипт на Python.
main_log.py
import gradio as gr
from transformers import LlavaForConditionalGeneration, AutoProcessor, BitsAndBytesConfig, logging as transformers_logging
import torch
from PIL import Image
import requests
from io import BytesIO
import logging
import time
# Enable transformers logging at INFO level to see tokens per second and other performance info
transformers_logging.set_verbosity_info()
# Optionally, configure your own logger if you want additional control
logging.basicConfig(level=logging.INFO)
# Define the quantization config
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_quant_type="nf4"
)
# Model and processor ID
model_id = "Ertugrul/Pixtral-12B-Captioner-Relaxed"
# Load the model with 4-bit quantization
model = LlavaForConditionalGeneration.from_pretrained(
model_id,
device_map="auto",
torch_dtype=torch.bfloat16,
quantization_config=quantization_config
)
# Load the processor
processor = AutoProcessor.from_pretrained(model_id)
# Define image resizing function
def resize_image(image, target_size=768):
"""Resize the image to have the target size on the shortest side."""
width, height = image.size
if width < height:
new_width = target_size
new_height = int(height * (new_width / width))
else:
new_height = target_size
new_width = int(width * (new_height / height))
return image.resize((new_width, new_height), Image.LANCZOS)
# Define the Gradio inference function
def process_input(text_prompt, image_url):
# Fetch the image from the URL
try:
response = requests.get(image_url)
response.raise_for_status() # Ensure the request was successful
image = Image.open(BytesIO(response.content))
except requests.exceptions.RequestException as e:
return f"Не удалось загрузить изображение по URL: {e}", ""
image = resize_image(image, 768) # Resize for optimal processing
# Prepare conversation with the user prompt and image
conversation = [
{
"role": "user",
"content": [
{"type": "text", "text": f"{text_prompt}\n"},
{"type": "image"}
],
}
]
PROMPT = processor.apply_chat_template(conversation, add_generation_prompt=True)
inputs = processor(text=PROMPT, images=image, return_tensors="pt").to("cuda")
# Start time for inference
start_time = time.time()
# Generate response using the model with specified parameters
with torch.no_grad():
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
generate_ids = model.generate(
**inputs,
max_new_tokens=256, # Equivalent to n_predict
temperature=0.7, # As specified
top_k=40, # As specified
top_p=0.5, # Nucleus sampling
repetition_penalty=1.176470, # Repetition penalty
no_repeat_ngram_size=256 # Approximate repeat_last_n with repetition window
)
# End time for inference
end_time = time.time()
inference_time = end_time - start_time # Calculate inference time
# Decode the output
output_text = processor.batch_decode(
generate_ids[:, inputs.input_ids.shape[1]:],
skip_special_tokens=True,
clean_up_tokenization_spaces=True
)[0]
# Add inference time to output
return f"Вывод модели:\n{output_text}\n\nВремя инференса: {inference_time:.2f} секунд", f'<img src="{image_url}" alt="Входное изображение" width="300">'
# Gradio interface setup
with gr.Blocks() as demo:
title = gr.Markdown("## Описание изображения с помощью модели Pixtral")
with gr.Row():
text_input = gr.Textbox(label="Текстовый запрос", placeholder="Например: Опишите изображение.")
image_input = gr.Textbox(label="URL изображения", value="https://huggingface.co/spaces/aixsatoshi/Pixtral-12B/resolve/main/llamagiant.jpg")
# Initial image display
result_output = gr.Textbox(label="Вывод модели", lines=8, max_lines=20)
image_output = gr.HTML('<img src="https://huggingface.co/spaces/aixsatoshi/Pixtral-12B/resolve/main/llamagiant.jpg" alt="Входное изображение" width="300">')
submit_button = gr.Button("Запустить инференс")
submit_button.click(process_input, inputs=[text_input, image_input], outputs=[result_output, image_output])
demo.launch()
Библиотека transformers загружает предобученные веса модели в видеопамять, используя 4-битную квантизацию для уменьшения объема занимаемой памяти. Функция LlavaForConditionalGeneration.from_pretrained() инициализирует модель, а AutoProcessor подготавливает входные данные.
При инференсе входное изображение и текст преобразуются в тензоры с помощью processor() и передаются в модель. Функция model.generate() запускает процесс авторегрессивной генерации: модель итеративно предсказывает следующий токен*, используя механизм внимания для учета контекста изображения и предыдущих токенов. PyTorch выполняет эффективные матричные операции на GPU, применяя веса модели к входным данным.
Токены — это части, на которые модель разбивает входной и выходной текст. Это не обязательно целые слова; токенами могут быть части слов, отдельные символы или даже пунктуация. Например, слово «нейросеть» может быть разбито на токены «ней», «росе» и «ть». Модель работает именно с последовательностями этих токенов, а не с целыми словами.
Что касается Gradio, эта библиотека создает простой веб‑интерфейс, позволяющий вводить текст и URL изображения через браузер. Gradio автоматически обрабатывает HTTP‑запросы, вызывает функцию process_input() при нажатии кнопки и отображает результаты.
Запускаем Pixtral 12B 4bit на P100 -
CUDA_VISIBLE_DEVICES=0 python main_log.py
В консоли у вас должна будет появиться ссылка на WebUI, просто нажмите на неё -
* Running on local URL: http://127.0.0.1:7860
Ура, WebUI запустился! А это значит, что мы продолжаем.
Для мониторинга нагрузки на GPU мы использовали утилиту nvtop, которую легко установить через пакетный менеджер Pacman в Arch Linux — sudo pacman ‑S nvtop
.
Nvtop — это аналог диспетчера задач в Windows, но специализированный для видеокарт, для тех кто больше знаком с Linux, в своей сути он аналогичен htop который используется для мониторинга процессоров. Он позволяет в реальном времени отслеживать использование ресурсов GPU.
С помощью nvtop
мы наблюдали, что квантизированная до 4 бит модель Pixtral 12B потребляет около 9 гигабайт видеопамяти в режиме ожидания. При активном инференсе использование памяти увеличивается до ~10.7 гигабайт. Как мы уже говорили ранее, для неквантизированной модели нам бы потребовалось около 24 гигабайт памяти.
Инференс нашего запроса на 256 токенов занял у нас 41.36 секунд, с распознанием текста на русском квантизированная модель конечно хромает. Но зато модель хорошо справилась с распознанием происходящего на изображение, описав его достаточно подробно в деталях.
Запускаем Pixtral12B 4bit на P40
Теперь попробуем на P40, для этого просто поменяем параметр у CUDA с 0, на 1 -
CUDA_VISIBLE_DEVICES=1 python main_log.py
Английский язык модель распознает, очевидно лучше, но вот что удивительно – время инференса, оно изменилось совсем незначительно, после нескольких прогонов, стабильно ~40.35 секунд для 256 токенов.
Pixtral 12B 8bit
Следующим шагом мы решили протестировать модель с 8-битной квантизацией, ожидая увидеть примерно двукратное увеличение потребления памяти по сравнению с 4-битной версией. Результаты оказались близки к прогнозам: в режиме ожидания модель заняла 13.4 ГБ видеопамяти, а при инференсе — уже 15.5 ГБ, что в ~1.44 раза больше, чем требовалось при 4-битной квантизации.
Поскольку P100 имеет всего 16 ГБ памяти, мы решили перестраховаться и начать тесты с P40, располагающей более комфортными 22.5 ГБ. Это должно было дать нам необходимый запас для оценки компромисса между повышенной точностью 8-битной квантизации и увеличенным потреблением памяти.
Но для начала поменяем конфиг квантизации в скрипте на новый, вместо
# Define the quantization config
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_quant_type="nf4"
)
Будет
python
# Define the quantization config for 8-bit with bfloat16 compute dtype
quantization_config = BitsAndBytesConfig(
load_in_8bit=True, # Switch to 8-bit quantization
bnb_8bit_compute_dtype=torch.bfloat16 # Use bfloat16 for compute
)
Pixtral 12B 8bit на P40
Результаты 8-битной версии модели оказались неоднозначными. Качество генерации текста немного улучшилось: модель точнее распознала текст на изображение и формулировала более связные описания. Однако время инференса выросло до 121 секунды - в три раза больше, чем у 4-битной версии.
Pixtral 12B 8bit на P100
Когда мы переключились на P100, ожидая увидеть падение производительности, нас ждал сюрприз. Вопреки прогнозам, эта карта справилась с задачей за 119 секунд — чуть быстрее, чем P40. Хотя разница в две секунды находится в пределах погрешности, но всё же, заставляет задуматься в чём может быть дело.
Le Chat
Перейдём теперь в бесплатный сервис от Mistral AI — разработчиков модели Pixtral, в чат‑боте с интерфейсом схожим с ChatGPT доступна модель Pixtral 12B без квантизации, это 16 бит с плавающей запятой, в отличие от квантизации до целочисленных INT4, либо INT8.
Результаты полноразмерной модели Pixtral 12B в Le Chat показали улучшенное качество генерации текста. Описание получилось более точным и связным, хотя как и в случае с квантизированными моделями — не идеально. Время генерации составило примерно 3 секунды, что значительно быстрее по сравнению с нашими предыдущими тестами на локальном оборудовании.
LLaMA 3.2 11B
Помимо самописного варианта инференса на Python + Gradio наше внимание привлекло еще одно интересное решение — расширение ComfyUI‑PixtralLlamaMolmoVision для платформы ComfyUI. Это расширение предоставляет возможность работать не только с LLaMA 3.2 11B, но и с Pixtral 12B и Molmo.
Несмотря на то, что расширение ограничено использованием предварительно квантизированных моделей, оно идеально подходило для наших целей. Хотя теоретически мы могли бы адаптировать расширение под свои нужды или разработать собственное решение с нуля, наша основная задача заключалась в тестировании моделей на имеющихся видеокартах, а не в погружении в тонкости AI‑разработки.
К тому же, использование ComfyUI за собой открывает доступ к огромному сообществу, сформированному преимущественно вокруг моделей генеративных нейросетей для создания изображений, однако, как мы выяснили, для работы с текстовыми, ComfyUI также спокойно подходит.
Начало в ComfyUI такое же как и с Pixtral — заходим на наш SSH сервер, разве что порт заранее поменяем на 8188 8188:127.0.0.1:8188
. А дальше, нужно сперва скачать ComfyUI –
git clone https://github.com/comfyanonymous/ComfyUI.git
Затем создать venv внутри папки ComfyUI и установить в него все зависимости, с учётом того что у нас карточки NVIDIA.
python -m venv ComfyUI
source ComfyUI/bin/activatepip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu124
pip install -r requirements.txt
Проверяем что всё работает, запустим `python main.py` и перейдём по появившейся ссылке -
To see the GUI go to: http://127.0.0.1:8188
Далее устанавливаем ComfyUI-Manager, сначала копируем его репозиторий в ComfyUI/custom_nodes
git clone https://github.com/ltdrdata/ComfyUI-Manager.git
Теперь Перезапустите ComfyUI
В новом интерфейсе:
Нажмите кнопку Manager
Перейдите в раздел Custom Nodes Manager
Найдите и установите ComfyUI-PixtralLlamaMolmoVision
В разделе Install Missing Custom Nodes скачайте недостающие компоненты
Скачайте модель в папку ComfyUI/models/LLM по ссылке из репозитория
Перезагрузите ComfyUI
Скачайте шаблон рабочей области
pixtral_caption_workflow.json
из репозитория расширения или добавьте кастомные узлыДля тестирования LLaMA:
Используйте за основу pixtral_caption_workflow.json
Замените узлы на Llama Vision Model и Generate Text with Llama Vision
Запускаем LLaMA 11B 4bit на P100
Теперь приступим к тестам, запустим снова ComfyUI, но указав в CUDA нужную видеокарту и начнём с P100.
CUDA_VISIBLE_DEVICES=0 python main.py
В простое модель заняла 7.58 гигабайт видеопамяти. А вот во время инференса уже 8.92 гигабайта.
Результаты тестирования LLaMA нас приятно удивили. Модель справилась с задачей всего за 18.3 секунды, что значительно быстрее наших ожиданий. Качество выдачи также оказалось на высоком уровне.
Однако мы заметили интересную особенность: и LLaMA, и Pixtral склонны к излишней болтливости. Они стремятся описать изображение целиком, даже когда мы запрашиваем только текстовую информацию. При генерации большого объема текста также наблюдаются повторы, несмотря на использование параметра repetition_penalty.
Любопытно, что в интерфейсе Le Chat Pixtral ведет себя более сдержанно. Это наводит на мысль, что мы, возможно, упускаем какой-то ключевой параметр настройки, возможно в Le Chat более хитрый системный запрос который добавляется к пользовательскому, во избежание в том числе повторов. Если у вас есть идеи по этому поводу, будем рады обсудить их в комментариях.
Generated 114 tokens in 18.208 s (6.261 tok/s)
The image features an orc with a humorous caption that reads, "I didn't choose da ork life, da ork life chose me." The orc
is depicted wearing glasses and holding a drink, adding to the comedic tone of the image. The use of the phrase "da ork life" instead of "the orc life" adds a playful touch, implying that the orc has been thrust into this lifestyle against its will. Overall, the image is a lighthearted and humorous take on the idea of being forced into a particular role or identity.
Prompt executed in 18.30 seconds
Запускаем LLaMA 11B 4bit на P40
CUDA_VISIBLE_DEVICES=1 python main.py
И смотрим на результат:
Generated 31 tokens in 61.683 s (0.503 tok/s)
На изображении написано: "Вы повстречали духа воды и получаете +5 к удаче до конца недели."
Prompt executed in 66.07 seconds
В процессе тестирования мы обнаружили интересную особенность LLaMA: модель отлично справилась с распознаванием русского текста, когда запрос к ней был также на русском. Однако при запросе на английском она не распознавала русский текст, определяя его как «неизвестный иностранный язык». А также как можно заметить модель некорректно выводит русский текст в ComfyUI, так что вывод был скопирован из консоли, где ComfyUI был запущен.
Но наиболее удивительным результатом стала существенная разница в производительности между видеокартами NVIDIA Tesla P100 и P40. P100 оказалась в 3.6-4.7 раз быстрее, чем P40, несмотря на схожее количество CUDA-ядер и тот факт, что обе карты были выпущены примерно в одно время. Это противоречило ожиданиям, учитывая, что у P40 больше памяти (24 ГБ против 16 ГБ у P100). Однако P100 использовала более быструю память HBM2, в то время как P40 была оснащена GDDR5. Хотя из нашего понимания теории машинного-обучения, скорость памяти не должна так сильно сказываться во время инференса.
Из предположений – одним из факторов такой разницы в производительности могла быть различная степень поддержки операций с половинной точностью (FP16) и общая архитектура обработки чисел с плавающей запятой. P100, вероятно, имела более эффективную реализацию этих операций, что оказалось критичным для работы с нейронными сетями.
Заключение
Не смотря на то что это непривычный для нас опыт, всё оказалось не так уж и страшно. Да, не обошлось без нюансов, особенно тех что остались за кадром. Больше всего крови попили попытки запустить уже готовые решения для инференса — Mistral.rs, vLLM. А также загадкой осталась необъяснимая разница в производительности между P100 и P40, где более слабая видеокарта почему-то в Pixtral оказалась наравне, а в LLaMa, вовсе в несколько раз быстрее. Впрочем, на эти и другие вопросы постараемся найти ответы в следующих статьях цикла.
А если у вас есть догадки из-за чего так могло произойти, либо какие-то советы по инференсу нейросетей, либо какие тесты на этих, либо других видеокартах хотели бы увидеть — ждём вас в комментариях.