
Производительность видеокарт Nvidia GeForce GTX 1660 Ti в llama.cpp на CUDA и Vulkan
Разработчики бэкенда Vulkan постоянно работают над совершенствованием его возможностей, не прекращая поддержки старых видеокарт. Благодаря этому последние версии llama.cpp, собранные на Vulkan, иногда работают эффективнее, чем сборки на основе чистого CUDA-рантайма. При этом NVIDIA отказалась от поддержки архитектур Maxwell, Pascal и Volta в CUDA 13+: для них последняя поддерживаемая версия CUDA Runtime — 12.9. Архитектура Turing (в том числе GTX 1660 Ti) в CUDA 13 по-прежнему поддерживается, но CUDA-бэкенд llama.cpp не всегда оптимален для старых карт, особенно без тензорных ядер. Последние версии шейдерного бэкенда Vulkan в llama.cpp на старых картах иногда обеспечивают более высокое быстродействие за счет возможности прямой трансляции матричных вычислений в низкоуровневые шейдеры, задействуя старые GPU на полную мощность.
В данной статье описывается тестирование производительности двух видеокарт Nvidia GeForce GTX 1660 Ti при работе с AI-моделями в llama.cpp локально (на одном компьютере), а также влияние на скорость генерации при включении в состав небольшого домашнего кластера. В работе использовалась старая машина с материнской платой ASRock Fatal1ty Z87 Killer (два слота PCIe 3.0 в режиме x8/x8), CPU Intel i5-4670 3.40GHz, 20GB RAM, подключенная к сети по 2.5G-каналу. Недостатки железа компенсировались дистрибутивом Gentoo с очищенным от основного мусора ядром 7.2.8, нативной сборкой системных пакетов, NCCL и RDMA.
При сборке llama.cpp сначала использовалась связка gcc-14.3.1 плюс CUDA 12.9.2, тестировались драйверы Nvidia 580.178.04 и 595.99.02, системный gcc-16.2.1.
В процессе тестирования был осуществлен переход на CUDA 13.4 (dev-util/nvidia-cuda-toolkit 13.4.1), драйвер 615.71.09, везде gcc-16.2.1.
Вывод утилиты nvidia-smi по состоянию видеокарт Nvidia GeForce GTX 1660 Ti с запущенным в дежурном режиме ggml-rpc-server (для работы в кластере):

Для работы с Vulkan в Gentoo необходимо подготовить системное окружение (заголовки Vulkan, загрузчик и компилятор шейдеров; сам Vulkan-драйвер входит в состав nvidia-drivers):
sudo emerge -av dev-util/vulkan-headers media-libs/vulkan-loader media-libs/shaderc
Чтобы проверить разницу в скорости (в t/s), производилась параллельная сборка бинарников с флагами Vulkan и CUDA (тестировались ggml-rpc-server и llama-bench). При тестировании выяснилось, что рекомендация разработчиков использовать флаг DGGML_CUDA_FORCE_MMQ=ON с архитектурами 61-virtual;80-virtual не влияет на скорость FP16-модели (это ожидаемо: флаг относится к матричным операциям над квантованными весами), но на квантованной модели Q8_0 сборка с FORCE_MMQ заметно быстрее сборки с FORCE_CUBLAS (163 против 46 t/s на pp512). Кроме того, для Turing нет необходимости оставаться на устаревших CUDA 12.9 и gcc-14: эти карты поддерживаются и в CUDA 13.4 (сборка под 61-virtual в CUDA 13.x при этом невозможна, так как поддержка Pascal там удалена). После установки последней на момент написания статьи версии CUDA Runtime 13.4 и перехода на сборку всех модулей llama.cpp компилятором gcc 16.2 в FP16 наблюдался небольшой регресс pp512 (около 83 t/s против около 90 t/s на CUDA 12.9 с той же архитектурой 75), превышающий погрешность измерений. Для квантованных моделей прямого сравнения CUDA 12.9 и 13.4 в статье нет.
Сборка llama.cpp для видеокарт Nvidia GeForce GTX 1600-й серии под CUDA и Vulkan
Скачивание исходников:
git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
Команда сборки CUDA-версии модулей llama.cpp для CUDA 12.9 с gcc-14 с использованием рекомендуемой разработчиками конфигурации:
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DGGML_CUDA=ON \
-DGGML_RPC=ON \
-DGGML_LTO=ON \
-DGGML_CUDA_VMM=ON \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
-DGGML_CUDA_FORCE_MMQ=ON \
-DCMAKE_CUDA_ARCHITECTURES="61-virtual;80-virtual" \
-DCMAKE_C_COMPILER=/usr/x86_64-pc-linux-gnu/gcc-bin/14/gcc \
-DCMAKE_CXX_COMPILER=/usr/x86_64-pc-linux-gnu/gcc-bin/14/g++ \
-DCMAKE_C_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition" \
-DCMAKE_CXX_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition" \
-DCMAKE_CUDA_FLAGS="-O3 -ccbin /usr/x86_64-pc-linux-gnu/gcc-bin/14/gcc -Wno-deprecated-gpu-targets" \
&& cmake --build build -j$(nproc) --target ggml-rpc-server llama-server llama-bench llama-cli
Автором для работы с llama.cpp используется отдельный каталог llama_cpp. Готовые бинарники удобно копировать из сборочной папки llama.cpp/build/bin/ командой:
rsync -rtv --delete --exclude='*.sh' ~/llama.cpp/build/bin/ ~/llama_cpp/
Обновлять исходный код llama.cpp удобно командой:
cd ~/llama.cpp && git reset --hard HEAD && git pull
После этого можно собирать новейшие бинарники, предварительно удалив каталог build командой rm -rf build.
Команда сборки Vulkan-версии llama.cpp с системным gcc (на момент написания статьи — 16.2.1):
rm -rf build && cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DGGML_VULKAN_RUN_TESTS=OFF \
-DGGML_VULKAN=ON \
-DGGML_VULKAN_CHECK_RESULTS=OFF \
-DGGML_RPC=ON \
-DGGML_LTO=ON \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \
-DCMAKE_CXX_STANDARD=20 \
-DCMAKE_C_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta" \
-DCMAKE_CXX_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta -fdevirtualize-speculatively -flto-toplevel-asm-heuristics -std=c++20" \
&& cmake --build build -j$(nproc) --target ggml-rpc-server llama-bench
Установка нового драйвера и CUDA:
sudo sh -c "echo 'x11-drivers/nvidia-drivers ~amd64' >> /etc/portage/package.accept_keywords/nvidia"
sudo sh -c "echo 'dev-util/nvidia-cuda-toolkit ~amd64' >> /etc/portage/package.accept_keywords/nvidia"
Новые версии Vulkan:
sudo sh -c "cat << 'EOF' >> /etc/portage/package.accept_keywords/vulkan
dev-util/glslang ~amd64
dev-util/spirv-tools ~amd64
dev-util/spirv-headers ~amd64
media-libs/vulkan-loader ~amd64
media-libs/shaderc ~amd64
EOF"
sudo emerge -av dev-util/vulkan-headers media-libs/vulkan-loader media-libs/shaderc

Тестирование производительности llama.cpp на разных сборках
Для тестирования использовалась модель Qwen3VL-4B-Instruct-F16 размером около 8GB. Интерес к FP16-версии был вызван высокой точностью таких моделей и аппаратной поддержкой со стороны Nvidia Tesla V100 — самой мощной карты, имеющейся в кластере автора, собранном из «музейного» железа.
mkdir AI_Models && cd AI_Models && wget https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct-GGUF/resolve/main/Qwen3VL-4B-Instruct-F16.gguf
Для тестирования сначала отключаем дежурный ggml-rpc-сервис llama.cpp:
sudo rc-service llama-rpc stop
Для теста используем команду:
$HOME/llama_cpp/llama-bench -m $HOME/AI_Models/Qwen3VL-4B-Instruct-F16.gguf -ngl 99 -p 512 -n 128
Тестирование показывает, что ggml_cuda_init видит 2 CUDA устройства с общей памятью 11495 MiB (NVIDIA GeForce GTX 1660 Ti, compute capability 7.5, VMM: yes, VRAM: 5746 MiB):
CUDA-Build llama.cpp f00a64c14 (11234), собранный с флагом DGGML_CUDA_FORCE_MMQ=ON для архитектур 61-virtual;80-virtual, обрабатывает Prompt Processing (pp512) с низкой скоростью — около 90–91 токенов в секунду. Генерация токенов (tg128) происходит с быстродействием порядка 30–31 t/s.
Нагрузка двух видеокарт NVIDIA GeForce GTX 1660 Ti при тестировании в llama-bench с моделью Qwen3VL-4B-Instruct-F16.gguf:

Запускаем тест на двух Nvidia GTX 1660 Ti (сборка с Vulkan), тестируем быстродействие с флагом GGML_VK_DISABLE_F16=1 и без него:
GGML_VK_DISABLE_F16=1 $HOME/llama.cpp/build/bin/llama-bench -m $HOME/AI_Models/Qwen3VL-4B-Instruct-F16.gguf -ngl 99 -p 512 -n 128
В ходе теста того же релиза llama.cpp, но собранного под Vulkan, система обрабатывает промпт pp512 со скоростью 258 t/s, генерирует с быстродействием порядка 31 t/s:

Без флага GGML_VK_DISABLE_F16=1 быстродействие заметно выше:
$HOME/llama.cpp/build/bin/llama-bench -m $HOME/AI_Models/Qwen3VL-4B-Instruct-F16.gguf -ngl 99 -p 512 -n 128
Промпт pp512 — 665 токенов в секунду, генерация — 31:

Сводная таблица результатов тестирования сборок llama.cpp под CUDA 12.9 (FORCE_MMQ=ON, 61-virtual;80-virtual, gcc-14) и Vulkan в Gentoo:

Тестирование показало неожиданные результаты: Prompt Processing на Vulkan оказался быстрее CUDA в 7.3 раза (!) — 664.85 t/s против 91.03 t/s на CUDA. Вероятно, причина в пути обработки FP16 в CUDA-бэкенде на этих картах (у TU116 нет тензорных ядер), но контрольные сборки, описанные ниже, показали, что дело не в архитектуре сборки и не в версии CUDA: точная причина не установлена.
На FP16-модели скорость генерации (tg128) была примерно одинаковой во всех сборках (~31 t/s), упираясь в пропускную способность памяти видеокарт (у 1660 Ti это GDDR6, 288 ГБ/с; слои модели распределены по двум картам последовательно). На квантованной Q8_0 это не так — см. ниже. За счет колоссального отрыва в Prompt Processing сборка на Vulkan на локальном компьютере обходит CUDA-версию. При дальнейшем тестировании в кластере выяснилось, что общее быстродействие с подключенной Vulkan-нодой значительно ниже, чем при подключении ее CUDA-версии. Вероятно, это связано с недостаточной оптимизацией Vulkan-бэкенда при работе через RPC.
После этого было принято решение обновить CUDA Runtime, Nvidia-драйверы и Vulkan-библиотеки в Gentoo на самую новую ветку (~amd64). Цель — проверить, изменит ли новый рантайм результат для архитектуры Nvidia Turing (SM7.5) на видеокартах GTX 1600-й серии.
Обновленная команда сборки llama.cpp под CUDA Runtime 13.4, архитектуру 7.5 и системный gcc-16:
cd ~/llama.cpp && git reset --hard HEAD && git pull
Сборка с флагами DGGML_CUDA_F16=ON, DGGML_CUDA_FORCE_CUBLAS=ON:
rm -rf build && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON -DGGML_RPC=ON -DGGML_LTO=ON -DGGML_CUDA_VMM=ON -DGGML_CUDA_NCCL=ON -DGGML_CUDA_F16=ON -DGGML_CUDA_FA_ALL_QUANTS=ON -DGGML_CUDA_FORCE_CUBLAS=ON -DCMAKE_CUDA_ARCHITECTURES="75" -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_CXX_STANDARD=20 -DCMAKE_C_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta" -DCMAKE_CXX_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta -fdevirtualize-speculatively -flto-toplevel-asm-heuristics -std=c++20" && cmake --build build -j$(nproc) --target ggml-rpc-server llama-bench
Тестируем эту CUDA-сборку:
$HOME/llama.cpp/build/bin/llama-bench -m $HOME/AI_Models/Qwen3VL-4B-Instruct-F16.gguf -ngl 99 -p 512 -n 128
Промпт — 83.10, генерация — 30.75 (build: c85b92c69, 11256):

Тестируем Vulkan-бэкенд: промпт — 665.96 ± 2.17 т/с, генерация — 31.18 (build: c85b92c69, 11256):

Тестируем GGUF-модель Ornith-1.5-9B-Q8_0 (9.55GB) на Vulkan:
$HOME/llama.cpp/build/bin/llama-bench -m $HOME/AI_Models/Ornith-1.5-9B-Q8_0.gguf -ngl 99 -p 512 -n 128
GGUF-модель на Vulkan обрабатывает промпт со скоростью 345 т/с, генерирует текст — 21.67 т/с:

Собираем сборку без DGGML_CUDA_F16=ON, но с DGGML_CUDA_FORCE_CUBLAS=ON:
rm -rf build && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON -DGGML_RPC=ON -DGGML_LTO=ON -DGGML_CUDA_VMM=ON -DGGML_CUDA_NCCL=ON -DGGML_CUDA_FA_ALL_QUANTS=ON -DGGML_CUDA_FORCE_CUBLAS=ON -DCMAKE_CUDA_ARCHITECTURES="75" -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_CXX_STANDARD=20 -DCMAKE_C_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta" -DCMAKE_CXX_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta -fdevirtualize-speculatively -flto-toplevel-asm-heuristics -std=c++20" && cmake --build build -j$(nproc) --target ggml-rpc-server llama-bench
Тест GGUF-модели на CUDA-бэкенде, сборка с флагом DGGML_CUDA_FORCE_CUBLAS=ON (сборка под архитектуру 7.5 с CUDA 13.4): промпт — 46.35, генерация — 28.7:

Собираем llama.cpp с флагом DGGML_CUDA_FORCE_MMQ=ON вместо DGGML_CUDA_FORCE_CUBLAS=ON:
rm -rf build && cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DGGML_CUDA=ON -DGGML_RPC=ON -DGGML_LTO=ON -DGGML_CUDA_VMM=ON -DGGML_CUDA_NCCL=ON -DGGML_CUDA_FA_ALL_QUANTS=ON -DGGML_CUDA_FORCE_MMQ=ON -DCMAKE_CUDA_ARCHITECTURES="75" -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -DCMAKE_CXX_STANDARD=20 -DCMAKE_C_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta" -DCMAKE_CXX_FLAGS="-O3 -march=native -pipe -fno-semantic-interposition -ftree-vectorize -funroll-loops -fipa-pta -fdevirtualize-speculatively -flto-toplevel-asm-heuristics -std=c++20" && cmake --build build -j$(nproc) --target ggml-rpc-server llama-bench
GGUF-модель на CUDA-сборке с флагом DGGML_CUDA_FORCE_MMQ=ON: промпт — 163.22, генерация — 28.74:

Тест с флагом -fa 1 показал аналогичный результат.

Тест GGUF-модели Ornith-1.5-9B-Q8_0 показал, что картина зависит от типа модели: Vulkan быстрее в обработке промпта (345 против 163 t/s, примерно в 2.1 раза), но CUDA быстрее в генерации (28.74 против 21.67 t/s, примерно на 33%). Ограничение по пропускной способности памяти (288 ГБ/с при размере модели около 9.5 ГБ) даёт потолок около 30 t/s, к которому CUDA-сборка подходит почти вплотную, а Vulkan остаётся заметно ниже.
Контрольные тесты на F16-модели (две GTX 1660 Ti), разделяющие влияние архитектуры сборки, версии CUDA и флагов:
Сборка (F16-модель Qwen3VL-4B, pp512 / tg128) | pp512, t/s | tg128, t/s |
CUDA 12.9, gcc-14, 61-virtual;80-virtual + FORCE_MMQ (build 11234) | 91.03 ± 0.08 | 30.83 ± 0.03 |
То же, build 11240 | 90.89 ± 0.07 | 30.85 ± 0.02 |
CUDA 12.9, gcc-14, архитектура 75 (build 11234) | 89.96 ± 0.44 | 30.75 ± 0.01 |
CUDA 13.4, gcc-16, архитектура 75, F16 + FORCE_CUBLAS (build 11256) | 83.10 ± 0.23 | 30.75 ± 0.02 |
CUDA 13.4, gcc-16, архитектура 75, без F16 и FORCE_CUBLAS (build 11256) | 83.56 ± 0.14 | 30.75 ± 0.02 |
Vulkan (build 11256) | 665.96 ± 2.17 | 31.18 ± 0.22 |
Выводы контрольных тестов:
1) на CUDA 12.9 результат не зависит от архитектуры сборки — Pascal-PTX (61-virtual;80-virtual с FORCE_MMQ) и нативная 75 дают около 90 t/s;
2) флаги F16, FORCE_CUBLAS и FORCE_MMQ на F16-модели скорость не меняют;
3) при переходе на CUDA 13.4 и gcc-16 pp512 снижается примерно на 7% (около 83 t/s);
4) предупреждение llama.cpp о нехватке тензорных ядер и совет собрать под 61-virtual;80-virtual с FORCE_MMQ печатаются во всех сборках и на скорость F16 не влияют, а на CUDA 13.x такая сборка невозможна, так как Pascal там не поддерживается. Разрыв с Vulkan (около 7 раз) ни одной из этих настроек не объясняется.
Заключение
Проведенное тестирование наглядно продемонстрировало, что списывать со счетов видеокарты поколения Nvidia Turing (в частности, GTX 1660 Ti) еще рано, однако подход к их оптимизации в современных AI-задачах требует переосмысления.
Для карт Turing без тензорных ядер (TU116, GTX 1660 Ti) CUDA-бэкенд llama.cpp в FP16 показывает низкую скорость Prompt Processing (83–91 t/s) независимо от архитектуры сборки, версии CUDA Runtime (12.9 или 13.4) и флагов DGGML_CUDA_F16, DGGML_CUDA_FORCE_CUBLAS, DGGML_CUDA_FORCE_MMQ. Точная причина не установлена: отсутствие тензорных ядер само по себе её не объясняет, поскольку Vulkan на тех же картах их тоже не использует (matrix cores: none) и работает в 7 раз быстрее. Флаг DGGML_CUDA_FORCE_MMQ на F16-модели бесполезен, но на квантованной Q8_0 даёт многократный выигрыш относительно DGGML_CUDA_FORCE_CUBLAS (163 против 46 t/s на pp512).
В то же время шейдерный бэкенд Vulkan за счет нативной работы с FP16/FP32 на ядрах Turing показал феноменальный результат на F16-модели, обойдя CUDA в локальном тесте Prompt Processing более чем в 7 раз (664.85 t/s против 91.03 t/s). На квантованной Q8_0 выигрыш скромнее — примерно в 2.1 раза (345 против 163 t/s), а генерация на Vulkan медленнее (21.67 против 28.74 t/s). Vulkan заметно оживляет старые GPU при локальном использовании, когда важна скорость обработки длинных промптов.
Однако при попытке масштабировать вычисления в домашний кластер через RPC Vulkan-бэкенд теряет преимущество. По крайней мере, при работе с Q8_0 Qwen3.8-27B в кластере из 4-х машин скорость работы сборки на CUDA была выше примерно на 50% (7-8 т/с против 5). Вероятно, причина в особенностях реализации RPC для этого бэкенда (копирование данных Host-Device-Host, барьеры синхронизации).
Итоговый вердикт:
Если LLM запускаются локально на одной машине на картах GTX 1660 Ti (Turing без тензорных ядер) и важна скорость обработки промпта — стоит использовать Vulkan-сборку; если важна скорость генерации на квантованных моделях, быстрее CUDA.
Если эксплуатируется распределенный домашний кластер из разношерстного «музейного» железа — для сетевых нод по-прежнему придется выбирать CUDA, жертвуя локальной скоростью обработки промпта ради более высокой общей скорости кластера.


