PunchUNILORIN student wins NCDMB essay contest, gets N1mESPNWhat's next for the Indiana Fever after front office departures?ESPN DeportesGurú de las Diagonales: Eagles están oficialmente en alertaInquirerMilitary war games lunched in Dinagat IslandsRTP DesportoRui Borges e Debast. Treinador confirma que vai falhar próximos dois jogosThe Jerusalem PostMerz warns that Germany faces pressure from extremist parties, 'massive threat' from RussiaBollywood HungamaEXCLUSIVE: Jennifer Winget, Sapna Pabbi and Vipin Sharma join Arjun Rampal in Hansal Mehta–Robbie Grewal’s BillionaireZDF heuteAktuelle Pressemitteilungen des ZDFFrance 24Paris: Protest starts at Porte d'Italie, after last-minute change to the routeThe Sydney Morning HeraldMultiple people shot in drive-by shooting south of BrisbanePremium TimesUnilever Nigeria posts 20% jump in nine-month profit, raises interim dividend fourfoldSRF News– Hitlers «Teehaus» am Obersalzberg niedergebrannt
The Daily Newsstand · Free, Always
Saturday, October 24, 2026

Разблокировали тензорные ядра RTX 3060: +83 % и почему на квантованной модели это почти ноль

Translate

23 октября 2026 года в репозиторий Zagitta/gpu-unlock влили поддержку GA106, то есть RTX 3060. Мы к этому моменту копались в своей карте почти сутки, поэтому успели снять замеры до и после и получить несколько вещей, которых в чужих отчётах нет.

Коротко результат: FP16 на матрице 8192³ вырос с 27,1 до 49,5 TFLOPS (×1,83). Контрольный FP32 не сдвинулся ни на десятую. На нашей квантованной модели выигрыш ноль; на квантованном стеке вообще он около пяти процентов, и это отдельный разговор ниже. Оба факта измерены на одной карте, одним скриптом.

Статья не про то, как повторить. Она про то, как отличить настоящее ускорение от красивого числа, и почему у нас на это ушло двое суток.

Что именно урезают

В разговорах про «урезанные карты» смешивают две разные вещи, и это первая ловушка.

Что режут

Чем

Обратимо

Отбраковка

число SM, шину, объём VRAM

фьюзы и лазер на заводе

нет, это физика

Гейтинг

скорость конвейеров

регистр-селектор, пересчитывается каждый холодный старт

да, до загрузки драйвера

Отбраковка честна: кристалл с двумя битыми SM продают как младшую карту, а не выбрасывают. Гейтинг это маркетинговая сегментация: один и тот же кристалл уезжает как RTX 3090, как 3080 Ti и как A6000, а чтобы A6000 не купили по цене 3090, нужен замок.

Замок живёт в регистрах, которые прошивка карты вычисляет при каждом включении питания, а не хранит. Всё, что вычисляется каждый раз заново и до поры ничем не защищено, кто-то другой может записать. Окно ровно одно: между подачей питания и загрузкой драйвера.

Для GA102/GA106 это три регистра в BAR0:

Регистр

Смещение

Что это

FEAT_OVR_PLM

0x00823804

маска привилегий: младшие 4 бита чтение по уровням, следующие 4 запись

FEAT_OVR_SM_SPD (SS0)

0x0082381c

восемь 4-битных полей: DP, FFMA, FMLA16, FMLA32, IMLA0…3

FEAT_OVR_SM_SPD_1 (SS1)

0x00823820

одно поле, IMLA4

Порядок имён в этой таблице не задан: апстрим называет набор полей, а не их позиции, поэтому мы читаем значения, но не приписываем имена к конкретным нибблам.

Холодное значение FEAT_OVR_PLM одинаково у всех GA10x, для которых у нас есть логи (GA102 и GA106): 0xffffff8f, младший байт 1000 1111, то есть читать могут все четыре уровня, а писать только L3. Поэтому модуль не пишет в селекторы напрямую: он сначала поднимает уровень привилегий, и только потом записывает во все поля скорость.

В каждом поле старший бит это флаг «override включён», младшие три бита задают скорость (00 полная, 01 половина, 10 четверть, 11 восьмая; два последних автор репозитория не подтверждал). Отсюда вещь, которую легко прочитать наоборот: 7 и 8 это не соседние ступени одной шкалы. 7 это 0111, override выключен, и младшие биты при этом ничего не значат. 8 это 1000, override включён плюс полная скорость. Числа, отличающиеся на единицу, отвечают на разные вопросы, а не задают разную скорость.

Проба до записи

Мы начали с чтения, и это оказалось правильным решением не из осторожности, а из структуры задачи: у инструмента есть гейты, которые срабатывают до первой записи в карту. Их можно проверить заранее, тем же доступом на чтение, и если первый из них не проходит, писать не нужно вообще.

Модуль первым делом разбирает VBIOS самой карты, по смещению 0x300000 в BAR0. Оттуда он берёт четыре поля: engine_mask, ucode_id, sig_count, sig_versions. У нас есть готовый read-only пробник (gpu_probe.py, отдельно от модуля, только чтение), который парсит этот заголовок и прогоняет гейты в том порядке, в котором их проходит модуль.

Вот что он показал на нашей карте:

FWSEC geometry: engine=0x400 ucode=9 signatures=3 versions=0x7
                bytes=1152 imem=0xe200 dmem=0x800
                image_off=0x416bc size=0xea00

И вот с чем это надо сравнить. В репозитории есть интеграционный тест, в котором зафиксированы числа, снятые с настоящего PROM платы GA102. Оговорка, без которой эти числа значат больше, чем есть: блок с эталоном исполняется только если тесту передать дамп PROM (в коде это if (argc == 2), рядом стоит комментарий «Optional integration check; normal tests contain no board-owned blobs»). В обычном прогоне эти строки не исполняются: это зафиксированное ожидание из чужого дампа, а не проверка, которая сработает у читающего репозиторий. Сверить с ними свои числа всё равно можно, и это полезно.

Поле

GA102 (эталон)

Наша GA106

engine_mask

0x400

0x400

совпало

ucode_id

9

9

совпало

sig_count

3

3

совпало

sig_versions

0x7

0x7

совпало

dmem

0x800

0x800

совпало

imem

0xda00

0xe200

разное

image_off

0x4289c

0x416bc

разное

image_size

0xe200

0xea00

разное

Совпали поля, по которым модуль принимает решения: engine_mask (единственное принимаемое значение 0x400), ucode_id (выбирает фьюз), sig_versions (выбирает слот подписи), плюс sig_count и dmem. Разошлись три поля, описывающие сам образ: размер imem, смещение образа и его размер.

И это не небрежность замера, а ответ на вопрос, почему не пришлось собирать свой payload. Модуль эти три поля не сверяет с константами: он берёт их из ROM карты (memcpy(image, rom + fw.image_off, fw.image_size), а fw.imem_size уходит в запуск Falcon). Отличающееся читается с платы, а чужими остаются только загрузчик и подпись, поэтому GA102-ные ассеты подошли к GA106 без единой правки.

Одно наблюдение оставляю как наблюдение, а не как вывод: наши значения перекликаются с чужими крест-накрест, наш imem = 0xe200 равен чужому image_size = 0xe200. Это повод не считать наш разбор полей окончательно верным. На вывод выше это не влияет (гейт стоит на engine_mask, геометрия читается с карты), но если кто-то будет сверять построчно, пусть знает, где у нас сомнение.

Второе, что дала проба: engine_mask = 0x400 это единственное значение, которое модуль принимает. Любое другое он отвергает, и если бы там оказалось другое число, эксперимент закончился бы на чтении, с нулём записей и с честным ответом «на этой плате так нельзя».

Все гейты прошли. Только после этого мы собрали модуль.

Окно и холодная карта

Схема включения:

питание -> ROM платы -> FWSEC -> Booter -> GSP -> драйвер
                          |         |
                          |         +-- здесь карта принимает подпись
                          +-- здесь поднимается WPR2

Модуль встраивается в тот момент, когда драйвер ещё не привязан. Отсюда требование, которое легко прочитать как суеверие: выключать питание, а не перезагружаться.

Отказ вызывает не ребут сам по себе, а состояние, в которое карту привёл драйвер. Писать можно только в том цикле питания, где драйвер ещё ни разу не поднимал WPR2, и модуль так и говорит: card already initialised since power on (WPR2 enabled); power off first. Тёплый ребут в Linux селекторы при этом не сбрасывает (это видно по чужому отчёту в разделе про отрицательный результат), но окно он не открывает заново: окно закрылось при первой загрузке драйвера, а не при выключении.

Для проверки мы сделали live-USB Ubuntu, где nouveau в чёрном списке, а драйвер NVIDIA не загружен вообще. Так карта остаётся нетронутой до самого момента запуска.

Три полезных грабли, каждая стоила времени:

Первая. /media/ubuntu появляется только после монтирования флешки, а загрузочная флешка Ubuntu монтируется в /media/cdrom и только на чтение. Логи, которые пишутся «рядом со скриптом», при зависании исчезают. Лечится fsync после каждой строки и второй флешкой на запись.

Вторая. sudo -i запускает логин-шелл в домашней папке root и не наследует текущий каталог. cd /media/... && sudo -i даёт вам /root, а не то, что вы ожидали.

Третья. Перед insmod нужно проверить, что карта не занята драйвером. Правильная команда lspci -nnk -s 01:00.0, а не -d 10de:01:00.0: второе это фильтр «вендор:устройство», он вернёт пусто, а пустоту легко прочитать как «драйвер не привязан».

Замер

Скрипт замера один и тот же: файл копировался на флешку и запускался и в Linux, и в Windows. Хеш запинен в манифесте для Linux-прогона, для Windows-копии сверку по хешу мы не делали. Это оговорка, а не мелочь: без неё «тот же скрипт» это слово, а не факт.

Протокол: GEMM 4096³ и 8192³, плотная матрица, накопление FP32, медиана из 20 прогонов после 3 прогревочных, CuPy 14.2.0 с cuBLAS из wheel.

Тест

До

После

fp16 8192³

27,1

49,5

×1,83

fp16 4096³

23,9

46,5

×1,95

fp32 8192³ (контроль)

9,9

9,9

×1,00

fp32 4096³ (контроль)

7,8

7,8

×1,00

Отношение fp16/fp32: 2,74 → 5,00.

Контроль не сдвинулся ни на десятую. Это не украшение: без неподвижного контроля «стало быстрее» может означать что угодно, включая то, что вы померили не тот конвейер.

Питание и частота, выборка каждые 200 мс:

До

После

частота ядра

не сэмплировалась

1747–1777 МГц под нагрузкой (12 сэмплов от 150 Вт, медиана 1762); выше 1900 только в недогрузе

пик мощности

170 Вт, то есть упёрлась в лимит

170,7 Вт при лимите 170

Лимит питания у нашей платы зашит в VBIOS: Max Power Limit = Default = 170. Поднять его через nvidia-smi нельзя, и по ваттам карта упирается в потолок в обеих сессиях.

Частота «до» в таблице отсутствует не случайно: в первой сессии nvidia-smi dmon писался без колонки частот, поэтому 1891 МГц для «до» это вывод из 27,1 TFLOPS, а не замер, и сравнивать его с сэмплом нельзя. Зато сэмпл после даёт независимую проверку главного утверждения. Если из 49,5 TFLOPS и частоты под нагрузкой (1747–1777 МГц) вывести, сколько операций на SM за такт делает карта, получается 995–1012 против ожидаемых 1024. Удвоение видно не только в TFLOPS: оно сходится с частотой, которую мерил отдельный прибор, а не тот же самый скрипт.

Как понять, что прибор мерил то, что нужно

Самый полезный приём за все двое суток оказался бесплатным: предсказать базовую линию из первых принципов.

У GA106 28 SM, 512 флоп на SM на такт для FP16 с накоплением FP32 (это как раз гейтованная половина), частота буста около 1,89 ГГц.

28 × 512 × 1,89 = 27,2 TFLOPS   (предсказание)
замер            = 27,1 TFLOPS

Совпало. Значит cuBLAS взял именно тот путь, который и удваивается, а не какой-то другой. Дальше этот же приём сверяется на чужой карте: 84 SM × 512 × 1,384 ГГц = 59,5 против 59,6, измеренных на 3090 Ti в чужом отчёте.

Без этого шага «27,1» это просто число. С ним это утверждение о том, что именно вы измеряете.

Чего это не даёт

Здесь начинается неприятная часть, и ради неё стоило писать статью.

В том же PR, на такой же карте, автор прогнал llama.cpp на квантованной модели:

gemma-4-12B Q4_K_XL, RTX 3060:
  pp512   1460,5 -> 1467,1 tok/s   (+0,5 %, шум)
  tg128     43,63 -> 43,65 tok/s   (+0,05 %, шум)

На этой модели и этом профиле ноль. Квантованный путь в llama.cpp не идёт через тот конвейер, который переключает unlock.

Что именно там происходит, этот замер не различает: он говорит только, что выигрыша нет. INT8 на Ampere это отдельный путь со своими полями в селекторах (IMLA0…4), и его пиковую производительность мы не мерили. Подтверждённого замера, который сказал бы, какое именно ядро исполняет конкретный тип квантованного матмула в ggml на sm_86, у нас нет, и подставлять догадку вместо него мы не будем.

Второе ограничение, отдельно от квантов: декод упирается в пропускную способность памяти. У 3060 это 360 ГБ/с на 192-битной GDDR6. Селектор скорость конвейеров, а не память, поэтому на генерацию токен за токеном unlock не влияет вообще.

Мы считали, что prefill (обработка промпта) даст выигрыш, и это отчасти верно: по логам нашего движка на 37 запросах подряд prefill занимает 26 % времени, а при промахе кэша промпта доходит до 51 % и 95 %. Но 26 % времени не превращаются в 13 % ускорения, если внутри этих 26 % работают не те ядра.

Где ноль перестаёт быть нулём. Ноль верен для квантованного пути и для декода, но он не универсален: прирост равен доле bf16/fp16/tf32-математики в нагрузке. Это видно по чужим замерам, опубликованным alesha pro (числа его, мы их не перепроверяли): bf16 на тензорных ядрах 82 → 155 TFLOPS (×1,89), cuBLAS BF16 GEMM 8192³ 64 → 99, TF32 41 → 79 (×1,93). На реальной нагрузке, Strata с Qwen3.8 Flash-Next на двух картах, prefill дал +12 % на 4K и 10K и +10 % на 90K (3205 → 3536 т/с), причём автор прямо оговаривает, что для этого движка случай почти худший: большая часть prefill там на int8 и копированиях.

И ещё одна точка, которая правит нашу же прежнюю формулировку. В том же чужом отчёте есть замер на int8-стеке: vLLM с Qwen 27B (W4A8 Marlin), профилировка показала около 29 % времени GPU в BF16/FP16-ядрах с накоплением FP32, сквозной выигрыш около 5 %, на грани шума между прогонами. То есть на квантованном стеке выигрыш не ноль: он мал, потому что таких ядер там меньше трети. Это же и объясняет, почему формулировка «ноль для квантованного инференса» шире замера, а верная: ноль для нашего профиля.

То есть на одной кривой шесть точек: наш ноль на Gemma Q4 в llama.cpp, наш ×1,83 на чистом FP16 GEMM, чужие ×1,89 и ×1,93 на 3090, чужие +10-12 % на живом движке и чужие +5 % на стеке, который уже сидит на int8. Наш ноль это не опровержение кривой, а один из её концов, и обещать по нему «всё остальное тоже ноль» было бы неправдой.

Отрицательный результат, который стоит записать

Мы проверили, переживут ли селекторы переход в Windows.

Схема, которую мы задумывали: разблокировать в Linux, не снимая питания сделать reboot, выбрать Windows, прогнать тот же скрипт. Что питание между сессиями действительно не снималось, мы тогда не зафиксировали, и это оказалось главной дырой теста.

Windows 10, CuPy 14.2.0, CUDA API 13.40, тот же gemm_bench.py:
  fp16 8192³ = 27,1 и 27,0 TFLOPS
  fp32 8192³ = 9,9 и 9,9

Ровно сток. При этом по чужому отчёту тёплый ребут в Linux селекторы сохраняет: там отношение TFLOPS на мегагерц после ребута не изменилось, то есть сам ребут их не стирает.

Различающий тест короткий, и мы его не прогнали: разблокировать в Linux, прочитать FEAT_OVR_SM_SPD, сделать reboot (не poweroff), сразу загрузить Windows, прогнать тот же скрипт. Около 43–48 TFLOPS означало бы, что селекторы живы и обход до загрузки ОС не нужен. Около 27 при подтверждённой непрерывности питания означало бы, что их сбрасывает инициализация драйвера.

Практическая рекомендация на сегодня: либо отдельный диск с Linux, либо подход до загрузки ОС. Второе уже сделано другими: есть UEFI-реализация под Windows, которая стартует до bootmgr, разблокирует карту и передаёт управление дальше. У автора она проверена на одной системе (RTX 3090 FE, BIOS ASUS 3057) и требует отдельного EFI-раздела, выключенного Secure Boot и снятого BitLocker. Для 3060 готового и проверенного варианта там нет.

Что осталось открытым

  • GA106 и квантованный инференс. Ни у кого нет замера, который разделил бы «int8-ядра» и «деквант в fp16» для конкретного ggml-ядра на sm_86. Ближайшее, что есть, это чужой профиль int8-стека (около 29 % времени в BF16/FP16-ядрах, около 5 % сквозного выигрыша) и он не про ggml.

  • Потолок питания. У 3060 Max Power Limit зависит от платы и, по внешним отчётам, встречается от 170 до 212 Вт. У нашей заперт на 170, поднять нельзя. У кого-то, возможно, есть запас, но проверить это можно только своей командой nvidia-smi -q -d POWER.

  • Чего мы не сделали. Не прогоняли модель через наш движок до и после. Синтетика показывает потолок конвейера, реальная нагрузка может дать меньше. Это следующая работа, и она уже не про регистры.

Что в сухом остатке

Механизм разблокировки на GA106 работает и подтверждён на двух платах независимо: нашей и автора PR. Оговорка, без которой фраза «работает на GA106» шире замера: обе платы это одна и та же модель, RTX 3060 12 GB, 10de:2504. В апстриме рядом с ней лежат ещё восемь ID GA106, помеченных как непроверенные, и чужие утверждения вида «на всех потребительских Ampere» относятся к их автору, а не к нашему замеру. Гейт-поля ROM у GA106 совпадают с GA102, а геометрия образа читается с самой карты, поэтому payload не понадобился свой. Ускорение ровно там, где обещано: FP16 с накоплением FP32, на синтетике почти удвоение. BF16 мы не мерили: CuPy 14.2.0 не отдаёт bf16-матмул, поэтому про него говорим по устройству конвейера, а не по замеру.

И одновременно: на нашей квантованной модели это ноль, для декода это ноль, в Windows мы ускорения не получили.

Обе вещи верны одновременно, и мне кажется, что публиковать их надо вместе. Иначе получается ещё одна статья про «+83 %», после которой кто-то купит карту, прогонит свою модель и не увидит разницы.

Ссылки

  • Zagitta/gpu-unlock, модуль и документация по механизму

  • PR #13, поддержка GA106 и замеры автора

  • issue #1, холодная 3090 Ti и поведение после тёплого ребута в Linux

  • impacifist/Windows-GPU-UNLOCK, UEFI-подход до загрузки ОС

  • keylase/nvidia-patch, тот же класс гейтинга, но на уровне драйвера и на десять лет раньше

  • xero00000/gpu-test, независимые замеры на 3080, 3060 Ti и 2080 Super

  • Пост alesha pro, замеры на 3090 из раздела «Где ноль перестаёт быть нулём»

Все наши числа сняты на одной карте: RTX 3060 12 GB, GA106-302, 10de:2504. Linux-замеры: ASRock Z77, i5-3570K, PCIe 3.0 x8, вывод изображения на встроенном видео, Ubuntu 24.04 live, ядро 7.0.0-31, драйвер 580.178.04. Windows-замеры: Windows 10, CuPy 14.2.0, CUDA API 13.40; хост и версия драйвера в те логи не попали. Числа по 3090, 3090 Ti и по llama.cpp взяты из чужих отчётов и помечены как чужие.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.