Это уже третья большая серия издевательств над NVIDIA CMP 90HX.
В первой статье я собрал домашний сервер на двух Xeon и пачке бывших майнинговых карт. Изначально идея была довольно приземленной: получить много видеопамяти и приличную вычислительную мощность за разумные деньги, чтобы запускать большие локальные LLM у себя дома — без аренды GPU, без оплаты токенов, без зависимости от чужого сервиса.
Потом выяснилось, что слово «майнинговая» у NVIDIA означает примерно следующее:
«Вот тебе почти целый GA102. Только пользоваться всем GA102 я тебе не разрешу».
Во второй статье я полез уже внутрь драйвера, разобрался с механизмом V67 и через служебную часть GPU добрался до вычислительных ограничений CMP 90HX.
После применения V67 получилось выставить полные вычислительные селекторы и вернуть карте значительную часть производительности, которая физически в ней уже присутствовала.
На этом месте хороший хозяин сервера, вероятно, поблагодарил бы железо за работу и начал им пользоваться.
Но способность вовремя остановиться — это же не про нас, да?:)
Следующим компонентом сервера стал кондиционер
После вычислительной разблокировки карты стали заниматься полезной работой заметно бодрее.
У полезной работы GPU есть простой побочный продукт — тепло.
До переделки системы охлаждения я видел примерно такие температуры:
Простой: около 60 °C
Нагрузка: до 90 °C
Для GPU это еще допустимо. Для длительных исследований, многочасовых нагрузок и дальнейшего разгона мне хотелось совершенно другого запаса.
Поэтому я подключил к серверу напольный кондиционер. Теперь сервер сосал только охлажденный воздух, а его тепло отводилось в окошко.

После этого:
Простой: около 9 °C
Полная нагрузка: около 60 °C
Девять градусов на GPU в простое!!!
В какой‑то момент видеокарты стали холоднее еды в холодильнике, и вопрос охлаждения я решил считать закрытым.
Этот запас пригодится еще дальше, потому что после снятия искусственных ограничений появляется вполне логичный вопрос о разгоне.
Но сначала я решил разобраться, насколько эффективно вообще использую несколько GPU.
Как раскладывается модель по картам?
После V67 следующим большим экспериментом стало распределение модели между видеокартами.
Чтобы дальше было понятно, зачем здесь вообще PCIe, коротко напомню два основных этапа работы LLM.
Когда я отправляю модели запрос, она сначала должна весь этот запрос прочитать‑ это Prefill.
ИИ Агент может одновременно получить системную инструкцию, исходники драйвера, логи ядра, результаты предыдущих команд, документацию, историю эксперимента и мой новый вопрос. Все это сначала надо пропустить через модель. Этот этап называется prefill. Для короткого разговора его легко не замечать. Для агента, который работает с большими объемами исходников и логов, prefill становится очень заметной частью общего времени.
Разница между условными 250 tok/s и 2000 tok/s ощущается буквально каждый запрос.
После обработки входа начинается генерация ответа — это decode.
Когда говорят, что модель работает со скоростью 30 или 70 токенов в секунду, обычно речь идет именно об этом этапе.
Для моего сервера важны оба показателя. Мне нужна быстрая генерация, и мне нужен быстрый вход, потому что локальные агенты постоянно читают большие объемы технической информации.
У llama.cpp модель можно распределять между видеокартами разными способами.
Самый простой для понимания — layer split.
В этом режиме разные слои модели находятся на разных GPU. Одна карта считает свой участок модели, после чего промежуточный результат передается следующей.
Другой вариант — tensor split.
Здесь одна большая операция распределяется между несколькими GPU одновременно. Вычислительная нагрузка распределяется эффективнее.Количество обменов между видеокартами одновременно резко растет.
НО! У CMP 90HX нет NVLink. Все эти данные едут через PCIe. Вот здесь старая майнинговая карта начала напоминать о своем происхождении...
Важная оговорка про результаты
Все эти эксперименты растянулись во времени, а программное окружение по ходу проекта немного менялось.
В отдельных тестах я использовал очень похожие, но все же немного разные модели.
По размеру, архитектуре и характеру нагрузки они близки, поэтому общая картина прослеживается хорошо, особенно когда изменение составляет десятки процентов.
Но воспринимать все приведенные дальше значения как лабораторный тест, где изменился строго один параметр, было бы неправильно.
Токен в токен эти результаты совпадать не обязаны. Мне здесь важнее масштаб эффекта.
Первый серьезный тест tensor split
На тот момент конфигурация выглядела так:
5 x CMP 90HX по 10 ГБ
4 карты — PCIe x8
1 карта — PCIe x4
Всего llama.cpp видел 49 383 MiB VRAM.
Compute unlock через V67 уже был применен.
Для одного из основных прогонов использовалась: Qwen3.8–27B‑Heretic. Все слои находились на GPU.
Базовые результаты:
Layer split
Prefill: 1992.67 tok/s
Decode: 23.76 tok/s
Tensor split
Prefill: 268.46 tok/s
Decode: 31.93 tok/s
По генерации tensor split очень понравился.
23.76 -> 31.93 tok/s
Абсолютный прирост составил 8.17 tok/s, то есть примерно 34.4%.
По prefill ситуация получилась противоположной.
1992.67 -> 268.46 tok/s
Падение примерно на 86.5%.
Tensor split явно давал больше производительности по decode. При этом PCIe очень сильно мешал ему нормально обрабатывать вход.
Вот после этого эксперимента я снова взялся за паяльник.
Видеокарте забыли положить PCIe в комплект
CMP 90HX физически построена на GA102. На плате разведен полноценный PCIe x16. Плата знает про x16. Разъем знает про x16.
Несколько десятков маленьких пассивных компонентов просто решили в этом не участвовать...
На дополнительных передающих линиях отсутствовали разделительные конденсаторы. В первой статье я уже ставил 220 нФ и поднимал часть карт до x8. После экспериментов с tensor split стало ясно, что пора заканчивать начатое.
Я допаял оставшиеся линии.

После этого каждая карта получила полноценный PCIe x16.
С точки зрения полезной пропускной способности PCIe Gen1 получаем примерно:
Gen1 x4 - 1 GB/s
Gen1 x8 - 2 GB/s
Gen1 x16 - 4 GB/s
Одна линия PCIe Gen1 работает на 2.5 GT/s. Но из‑за кодирования 8b/10b полезная пропускная способность составляет около 250 MB/s на линию в каждом направлении.
На этом физические контакты наконец закончились.
Что полный x16 дал модели?
После переделки я снова прогнал базовые тесты.
Для layer split почти ничего не произошло.
Было:
Prefill: 1992.67 tok/s
Decode: 23.76 tok/s
Стало:
Prefill: 2098.89 tok/s
Decode: 23.95 tok/s
Разница:
Prefill: примерно +5.3%
Decode: примерно +0.8%
Это вполне ожидаемо — Layer split сравнительно мало гоняет данные между всеми видеокартами одновременно. Tensor split отреагировал совсем иначе.
Было:
Prefill: 268.46 tok/s
Decode: 31.93 tok/s
После расширения PCIe:
Prefill: 489.16 tok/s
Decode: 37.34 tok/s
Разница:
Prefill: +220.70 tok/s, примерно +82.2%
Decode: +5.41 tok/s, примерно +16.9%
Здесь снова стоит помнить об оговорке выше: модели в отдельных прогонах немного различались. Но эффект настолько большой, что общая картина от этого не меняется.
PCIe оказался реальным узким местом для tensor split. Я увеличил пропускную способность шины, и режим, который активно обменивается данными между GPU, заметно ускорился.
После этого вопрос Gen2 возник практически автоматически ‑полные 16 линий я уже подключил.
Теперь хотелось заставить их работать быстрее...
2.5 GT/s на GA102
После всех аппаратных переделок карта стабильно показывала:
LnkSta:
Speed 2.5GT/s
Width x16
PCIe Gen1.
По полезной пропускной способности это примерно:
Gen1 x16 - 4 GB/s
Gen2 x16 - 8 GB/s
Ширина линии уже максимальная. Переход на Gen2 потенциально дает еще рост пропускной способности. Учитывая результаты tensor split, смысл этого эксперимента был вполне практическим.
Начались попытки:
Я пробовал готовые решения.
Разбирал rejoin15.
Потом rejoin16.
Играл с PCIe/XVE регистрами.
Запускал retrain.
В некоторых местах значения менялись.
Линк оставался 2.5 GT/s.
Еще раз.
2.5 GT/s.
И еще несколько часов спустя.
2.5 GT/s.
Карте очень нравился PCIe Gen1 и 2010 год...
До меня Gen2 уже пытались разблокировать
Здесь важно отдать должное предыдущим исследованиям.
Я не первый человек, который полез искать Gen2 у CMP 90HX.
В частности, jdowning100 в проекте cmpunlocker публиковал rejoin16 и заявлял рабочий PCIe Gen2 x4 на CMP 90HX.
В его опубликованных материалах указаны 5 GT/s и примерно 1.7 GB/s реальной передачи в каждую сторону на x4.
То есть сама идея разблокировки Gen2 и часть необходимых механизмов появились раньше. Именно на такие исследования я тоже опирался дальше.
При этом публичного подтверждения полноценного Gen2 x16 на CMP 90HX я найти не смог.
Поэтому на момент написания статьи я считаю полученный результат первым в мире публично подтвержденным PCIe Gen2 x16 на CMP 90HX.
Почему нельзя просто записать «Gen2»?
PCIe‑контроллер внутри GPU имеет несколько уровней управления.
То, что процессор видит, является только внешней частью. Внутри GPU есть собственные регистры XVE, маски привилегий, служебные процессоры и защищенные области. И тут очень пригодилось то, чем я занимался во второй статье. V67 уже был рабочим ключом в привилегированный контекст GPU.
Во второй статье через него открывался FEAT_OVR_PLM и снимались вычислительные ограничения. V67 я уже понимал. Нужно было найти правильную дверь.
Маленькая ремарка — зачем я вообще сюда полез и не использовал чужой метод полностью — чужие методы не работали:). Нет, я серьезно честно просидел двое суток пытаясь исправить ошибки сборки, ошибки записи регистров, и тонну прочего... Ничего из того что я нашел в интернете просто не сработало. Без вариантов.
И тут сервер получил собственного исследователя
На основном сервере в этот момент стояли четыре CMP 90HX.
На них работала локальная LLM. Отдельно я собрал тестовый компьютер. В него поставил пятую CMP 90HX. Агент на основном сервере получил SSH‑доступ к тестовой машине.
Получилась довольно идиотская с точки зрения маркетингового отдела NVIDIA ситуация. Четыре карты NVIDIA запускали искусственный интеллект, который пытался снять ограничение NVIDIA с пятой карты NVIDIA. Я дал ему задачу и решил ему не мешать.
Я рассчитывал на несколько суток (и именно поэтому не отдал задачу облачным моделям — я бы разорился на покупке токенов). И самое смешное здесь — мои ожидания.
Задача казалась достаточно неприятной: большой драйвер NVIDIA, внутренние регистры, несколько поколений патчей, постоянная пересборка модулей, перезагрузки стенда и проверка физического состояния PCIe. Я морально закладывался примерно на несколько суток исследования. Агент нашел рабочий путь примерно за час. Примерно час от постановки задачи до настоящего 5.0 GT/s.
Вот здесь идея всего моего домашнего LLM‑сервера внезапно очень хорошо оправдала сама себя. Я изначально собирал его ради возможности запускать серьезные модели локально. В результате локальная модель стала полноценным инженерным инструментом, который помог исследовать сам сервер.
И для такой задачи локальная инфраструктура оказалась особенно удобной — нет подписки на очередной облачный сервис, нет счетчика использованных токенов, нет ситуации, когда посреди удачного эксперимента интерфейс сообщает: «Лимит исчерпан. Попробуйте снова через несколько часов».
Агент может полчаса читать исходники. Потом еще час собирать драйверы и перезагружать стенд. Потом продолжить с тем же контекстом. Если эксперимент затянется на ночь, ничего принципиально не меняется‑ ограничением становится мое железо и электричество.
Для автономной инженерной работы это очень приятное свойство.
Кремний начал ковырять собственный драйвер
Базой стал NVIDIA Open Kernel Module 610.43.03.
В исследовании использовались предыдущие наработки compute unlock и rejoin16, а дальше агент экспериментировал с порядком инициализации, внутренними PCIe‑регистрами и повторным обучением линии.
Очень быстро выяснилась важная вещь — одной записи в PCIe‑регистр недостаточно. Имеет значение состояние GPU и порядок действий.
Рабочая последовательность в итоге получилась примерно такой: сначала штатный NVIDIA 610.43.03 нормально инициализирует GPU. После этого управление передается модифицированному модулю, через V67/fwsec открываются нужные privilege masks, выполняется FLR и повторная инициализация, а затем PCIe retrain.
Никакой красной кнопки ENABLE_GEN2 = 1 NVIDIA мне, к сожалению, не оставила. Пришлось собирать последовательность по кусочкам.
Еще один интересный эффект появился во время старта.
Если сразу после холодного включения загрузить модифицированный модуль, карта может упасть на RmInitAdapter.
Поэтому сначала CMP полностью проходит обычную инициализацию драйвером NVIDIA. После этого уже запущенная карта передается модифицированному модулю. По смыслу это просто передача уже нормально инициализированного GPU патченному драйверу.
Из всей массы регистров в итоге особенно важными оказались две области.
Первая:
0×823800
Это FEAT/PLM‑часть механизма, с которой я уже сталкивался во время снятия вычислительных ограничений.
Через V67/fwsec маска открывается до:
0xffffffff
Вторая:
0×088fe8
Она относится к XVE и внутренней защите PCIe‑части GPU.
Она тоже переводится в:
0xffffffff
Также в процедуре используются селекторы полной скорости:
ss0 = 0×88888888
ss1 = 0×00000008
После этого выполняется Function Level Reset, его можно представить как локальную перезагрузку PCIe‑устройства.
GPU сбрасывается, заново проходит инициализацию, затем линия PCIe обучается еще раз.
И тут PCIe стал Gen2
После открытия масок и повторного обучения линии Linux показал:

Физическая линия действительно обучилась на Gen2.
Проверяем реальной передачей данных
Показания регистров прекрасны.
Я все равно хотел проверить передачу данных.
Был запущен CUDA DMA bandwidth test с закрепленной областью оперативной памяти.
Результат:
RAM -> GPU: 6.33 GB/s
GPU -> RAM: 6.40 GB/s
А теперь самое приятное.
PCIe Gen1 x16 физически дает примерно 4 GB/s полезной полосы. Через него невозможно протолкнуть 6.4 GB/s. PCIe Gen2 x16 дает примерно 8 GB/s.
Реальные 6.3–6.4 GB/s выглядят совершенно нормально с учетом накладных расходов протокола и реализации DMA.
Итог всех издевательств и дальнейшие планы
После всех сделанных модификаций я постепенно убираю ограничения одно за другим.
Сейчас уже есть compute unlock, полный PCIe x16, PCIe Gen2 и охлаждение с огромным запасом.
Память у карт работает примерно на 9500 МГц. Выжимать из GDDR6X еще несколько процентов любой ценой мне пока не хочется. Она и без моей помощи прекрасно умеет превращать электричество в тепло.
Гораздо интереснее ядро.
При длительной LLM‑нагрузке я видел примерно до 200 Вт на карту при доступном лимите порядка 250 Вт. То есть я пока даже не упираюсь в power limit. Температурный запас после кондиционера тоже огромный. Получается довольно редкая для старого майнингового железа ситуация: температура не мешает, лимит мощности пока не мешает, PCIe постепенно перестает мешать. Можно спокойно исследовать зависимость производительности от частоты GPU.
При prefill модель хорошо загружает вычислительные блоки крупными матричными операциями, поэтому дополнительная частота ядра потенциально может дать заметный результат.
Decode больших плотных моделей часто сильнее зависит от пропускной способности памяти, поэтому там эффект может оказаться скромнее.
И это тоже будет полезным результатом.
Я мучался, чтобы вам не пришлось
Получить рабочую последовательность на отдельном тестовом компьютере — это замечательно.
Заставлять каждого следующего владельца CMP 90HX повторять весь этот путь — уже не очень.
Я использовал результаты работы агента, свои предыдущие наработки и код из нескольких открытых репозиториев, которые помогли добраться до отдельных частей механизма.
Из всего этого я собрал собственную утилиту.

В идеале пользователь просто скачивает проект, запускает утилиту и получает автоматизированную установку, compute unlock, Gen2 и проверку результата.
Конечно, внутри все еще происходит тот самый цирк с драйвером, V67, повторной инициализацией и retrain.Просто смотреть на него теперь приходится в основном мне.
Именно так я в целом вижу техническое комьюнити — один человек однажды тратит вечер, иногда неделю, иногда несколько месяцев...после этого следующий человек не обязан повторять тот же путь с самого начала. Он получает код, инструкцию и возможность идти дальше. В этот раз человеком, который уже помучался, оказался я.
Так что девиз третьей части статьи, и моего нового проекта CMP90HX PWNER простой:
«Я мучался, чтобы вам не пришлось».
Исходники, сама утилита, инструкция по установке и список проектов, на которых основаны отдельные части исследования, будут здесь:
В README я отдельно оставлю ссылки на репозитории и авторов, чьи наработки использовал.
Потому что такие исследования почти никогда не появляются из вакуума. Один человек находит одну дверь. Другой делает к ней ключ. Третий понимает, в какой момент этим ключом пользоваться. А четвертый в конце делает кнопку. В этот раз кнопку сделал я.
P. S.
Насколько именно, где прирост оказался максимальным и во что теперь упирается tensor split — это уже тема для финальных замеров и отдельного дополнения к статье.
Ну а потом, судя по всему, придется разгонять GPU.
Потому что способность вовремя остановиться я пока так и не усвоил...

