The Daily Newsstand · Free, Always
Sunday, September 27, 2026

Часть 4. Обошел еще одно ограничение NVIDIA: первым в мире включил P2P на CMP90HX, а помогли мне в этом… инженеры NVIDIA

Translate

В предыдущих частях я довольно долго издевался над CMP 90HX. Сначала выяснилось, что внутри майнинговой карты NVIDIA оставила вполне полноценный GA102, но программно объяснила ему, что он теперь шахтер и ничего другого в жизни не умеет. Потом оказалось, что производитель еще и физически урезал интерфейс PCIe, хотя сам кристалл прекрасно умеет работать с полноценной шиной. После нескольких итераций пайки, ковыряния регистров и общения с железом способами, которые производитель явно не включал в руководство пользователя, сервер уже выглядел куда интереснее исходного состояния.

Часть 1. https://habr.com/ru/articles/1020334/

Часть 2. https://habr.com/ru/articles/1076224/

Часть 3. https://habr.com/ru/articles/1082724/

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

В итоге я полез разбираться, можно ли открыть на CMP 90HX настоящий P2P. И получилось - впервые включить прямой обмен между этими картами, заставить одну CMP реально обращаться к памяти другой и получить нормальную скорость передачи.

Самое забавное, что по дороге мне очень сильно помогли сами инженеры NVIDIA. Не лично, конечно. Они просто оставили в драйвере ровно те внутренние механизмы, которыми сами пользовались при разработке и отладке.

Мне оставалось только понять, как всё это работает.

Спойлер - слово “только” здесь сильно преуменьшает объем страданий.

Зачем мне вообще понадобился P2P

Немного отступлю, для тех кто не читал предыдущие части. Когда модель целиком не помещается в память одной видеокарты, ее приходится распределять между несколькими GPU. В llama.cpp это можно делать по-разному. При разделении по слоям разные части модели располагаются на разных картах: одна обработала свои слои, передала результат следующей, та обработала свои и так далее. В llama.cpp этот режим называется layer split.

Есть и другой режим - tensor split. Здесь одна крупная математическая операция распределяется сразу между несколькими видеокартами. Они одновременно считают разные части матрицы, после чего должны обмениваться промежуточными результатами. Чем быстрее сами GPU, тем заметнее становится ситуация, когда вычисления уже закончились, а карты стоят и ждут, пока к ним приедут данные.

Без прямого обмена путь между двумя картами на моей системе выглядит так:

Сначала блок нужно прочитать из видеопамяти первой карты, отправить через ее контроллер PCIe и линии PCIe в корневой комплекс процессора, после чего через контроллер памяти записать в обычную RAM. Затем начинается вторая передача - этот же блок снова читается из RAM, опять проходит через контроллер памяти и корневой комплекс PCIe, второй раз едет по шине и только после этого оказывается в видеопамяти второй карты.

То есть ради одной передачи между двумя GPU фактически выполняются две:

GPU 0 -> RAM

и затем:

RAM -> GPU 1

Системная память становится промежуточным складом, куда я сначала полностью привожу груз, чтобы тут же погрузить его обратно и отправить в соседнее здание.

В моем тесте весь этот маршрут давал эффективную скорость от первой карты до второй около:

STAGED_E2E_EFFECTIVE_GBPS ... 1.60

То есть примерно 1,60 ГБ/с.

При настоящем прямом обмене для двух карт на одном процессорном узле маршрут должен быть заметно короче:

VRAM GPU 0 -> PCIe-контроллер GPU 0 -> PCIe -> корневой комплекс процессора -> PCIe -> GPU 1 -> VRAM GPU 1

Карты у меня не соединены отдельным кабелем типа NVLink, поэтому инфраструктура PCIe процессора никуда не исчезает - на этих картах по другому мы сделать не можем. Зато полностью пропадают несколько лишних действий в изначальной цепочке.

Такой прямой обмен между устройствами и называется P2P - peer-to-peer. Одна видеокарта получает возможность обращаться непосредственно к памяти другой, не используя системную RAM как перевалочный пункт.

На CMP 90HX штатный драйвер NVIDIA считает, что такой возможности нет, но после предыдущих экспериментов слово “нет” от NVIDIA я уже воспринимал как интересную гипотезу, которую стоит проверить самостоятельно.

Для начала спросим сам драйвер

Сначала я посмотрел, как NVIDIA вообще видит топологию моих пяти карт:

Две CMP находятся на стороне первого процессора - 02:00.0 и 03:00.0. Еще три - 81:00.0, 82:00.0 и 83:00.0 - подключены ко второму.

Упрощенно система выглядит так:

Карты внутри одного процессорного узла связывались через один корневой мост PCIe - это обозначается как PHB. Между картами, принадлежащими разным процессорам, уже NODE, то есть обмену придется проходить еще и через межпроцессорную часть системы - об этом поговорим позже.

Для пар внутри одного PCIe-узла я получал GNS - GPU not supported, для части других сочетаний - TNS- Topology not supported, а с некоторыми возможностями просто NS. Общий смысл простой - драйвер считает прямой обмен неподдерживаемым.

Причем железо для такого категоричного ответа выглядело подозрительно способным. Это всё тот же GA102, который в других вариантах исполнения (речь о RTX серии) спокойно умеет P2P.

Поэтому возник простой вопрос - что именно в драйвере приводит к этому запрету и можно ли заставить его пройти по тому же пути, который используется на поддерживаемых картах серии RTX и Tesla?

Как одна видеокарта вообще может увидеть память другой?

Чтобы дальше не получилась магия вида “исправил драйвер и заработало”, придется немного разобрать механизм доступа к памяти PCIe-устройств.

В адресном пространстве компьютера PCIe-устройству выделяются специальные диапазоны, через которые процессор и другие устройства могут обращаться к его ресурсам. Базовые адреса этих диапазонов задаются регистрами BAR - Base Address Register.

У видеокарт NVIDIA для доступа к видеопамяти особенно важна область BAR1. Если сильно упростить, через нее часть памяти GPU представляется остальной PCIe-системе как диапазон адресов. Транзакция приходит на адрес внутри этого диапазона, а уже видеокарта направляет ее в собственную память.

На моих CMP размер доступного BAR1 составлял 16384 MiB.

То есть речь не шла о крошечном окне в несколько сотен мегабайт. В адресное пространство уже можно было вывести всю память, и соат.

Но одного BAR1 недостаточно. Допустим, GPU 0 выделил буфер в своей памяти. Чтобы GPU 1 мог с ним работать, драйвер должен создать для второй карты отображение этого участка. После этого определенный адрес в адресном пространстве GPU 1 будет вести не в его локальную видеопамять, а через PCIe к памяти GPU 0.

Сама видеокарта при этом тоже работает с виртуальными адресами. Для их преобразования в реальные адреса памяти внутри GPU существует собственный блок управления памятью - GMMU, GPU Memory Management Unit. Он использует таблицы страниц, в которых описано, куда должен вести конкретный виртуальный адрес и какими свойствами обладает соответствующая область памяти.

При обычной работе запись таблицы страниц приводит в локальную видеопамять. Для P2P драйвер должен сформировать запись, которая описывает память соседней карты и способ доступа к ней через PCIe.

Получается следующая цепочка: сначала драйвер определяет, можно ли двум GPU работать напрямую. Затем выбирает разрешенные операции - чтение, запись и дополнительные виды доступа. После этого создается отображение памяти соседней карты, настраивается путь через BAR1, формируются записи таблиц виртуальной памяти, и только затем CUDA получает рабочий адрес.

Снова полез в драйвер NVIDIA

К счастью, значительную часть модулей ядра NVIDIA под Linux сейчас можно изучать в исходниках. Но это не означает, что вся логика управления GPU открыта.

В современных NVIDIA часть служебных функций выполняется отдельным процессором внутри самой видеокарты. Он называется GSP - GPU System Processor. Открытый модуль ядра общается с ним и получает от него информацию о состоянии карты и ее возможностях.

Начал я с участков драйвера, связанных с прямым обменом между GPU, работой шины, созданием удаленного отображения памяти и построением таблиц виртуальной памяти:

nv-p2p.c
kern_bus_gm107.c
kern_bus_gp100.c
nv_gpu_ops.c
gmmu_fmt.c

Дальше пришлось отслеживать весь путь - от определения возможностей соседней карты до момента, когда ее память реально становится доступной другому GPU.

И здесь я немного забегу вперед. По мере разбора драйвера мне начали попадаться внутренние механизмы с названиями:

PeerMappingOverride
RMForceP2PType
ForceP2P
RMDisableFeatureDisablement

Сейчас я перечисляю их именно забегая вперед. В тот момент я еще не понимал, какой из них за что отвечает, какие реально участвуют в нужном пути и какая комбинация понадобится.

Но уже сами названия были очень интересными.

Особенно:

RMDisableFeatureDisablement

Если переводить смысл максимально близко - “отключить отключение возможностей”.

В какой-то момент инженеры NVIDIA, видимо, настолько устали от собственных ограничений, что написали специальный переключатель, отключающий механизм отключения возможностей,и я их прекрасно понимаю.

Первая гипотеза - достаточно ли ForceP2P?

Первым действительно перспективным механизмом оказался ForceP2P.

Это набор битов, каждый из которых отвечает за определенные возможности прямого обмена.

В исследованной реализации значение:

0x11

позволяло принудительно разрешить нужные операции чтения и записи.

Вариант:

0x111

добавлял дополнительные возможности, включая атомарные операции.

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

На этом этапе хотелось поверить в очень красивую версию событий. Инженеры NVIDIA оставили внутренний флаг, я выставляю 0x111, пересобираю драйвер и заканчиваю статью.

Разумеется, этого не произошло.

Я собрал измененный NVIDIA Open Kernel Module 610.43.03. Для карт с идентификатором CMP90HX задействовал найденный механизм принудительного разрешения P2P.

Драйвер собрался, модуль загрузился, все пять карт определились. Ничего не зависло и не выдало ошибку.

Проверяю P2P - не работает.

Конечно....

Тогда появилась гипотеза про GSP

При дальнейшем разборе выяснилось, что открытый модуль драйвера не сам принимает абсолютно все решения. Часть информации о возможностях GPU он получает от GSP.

Для каждой отдельно взятой пары видеокарт существуют структуры, в которых описано, какие варианты прямого взаимодействия разрешены. В частности, там есть информация о прямом чтении и записи по PCIe. Для CMP 90HX штатно приходило состояние:

NOT_SUPPORTED

Появилась вполне логичная гипотеза:

Допустим, я разрешаю P2P в открытом модуле драйвера. После этого драйвер получает от GSP сведения о возможностях карты, а там снова записано NOT_SUPPORTED. Получается, что сколько ни меняй верхний уровень, закрытая часть позже снова всё запретит.

Тогда очевидное решение - найти место, где драйвер получает эти данные от GSP, и подменить результат уже после получения.

Я начал смотреть структуры peerGpuCaps, поля pcieP2PReadCaps, pcieP2PWriteCaps и все места, где эти значения дальше используются. Логика казалась вполне стройной: GSP сказал “нет”, я после него скажу “да”.

Но именно в этот момент выяснилось, что я собирался вручную реализовать механизм, который инженеры NVIDIA уже сделали сами.

Мне помогли инженеры NVIDIA

Те внутренние параметры, о которых я говорил забегая вперед, начали складываться в полноценную картину.

ForceP2P, RMForceP2PType, PeerMappingOverride и связанные проверки действительно позволяют драйверу принудительно переопределять обычные ограничения возможностей.

То есть GSP вполне может вернуть:

NOT_SUPPORTED

Потому что конкретная CMP 90HX действительно помечена как карта без P2P.

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

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

Но одного разрешения P2P всё равно недостаточно

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

После проверки возможностей драйвер должен реально создать отображение памяти соседнего GPU, то есть выделенный участок видеопамяти на одной карте должен появиться в адресном пространстве другой карты. Для этого штатный механизм NVIDIA должен выбрать правильный способ доступа, подготовить BAR1, создать служебные структуры и сформировать необходимые записи в таблицах GMMU. Постепенно стало ясно, что существует принципиальная разница между “драйвер считает P2P разрешенным” и “GPU действительно получил адрес памяти соседней карты”.

Здесь помогли исследования обычных GA102

На этом этапе очень пригодились старые исследования P2P на обычных видеокартах семейства GA102 - прежде всего RTX 3080 и RTX 3090.

Архитектура близкая, поэтому по уже исследованным вариантам можно было понять, какие функции NVIDIA вызывает после того, как прямой обмен признан разрешенным.

Готового патча для CMP 90HX там, конечно, не было. Версии драйвера отличаются, часть логики со временем переехала в GSP, идентификаторы устройств другие.

Но это сильно сузило область поиска. Вместо “изучи весь драйвер NVIDIA” получилось примерно “изучи вот эти несколько особенно неприятных функций”. Уже хорошо.

Змея кусает себя за хвост

Дальше начался этап, который в кино занял бы двадцать секунд под энергичную музыку. В реальности это были часы сборки nvidia.ko и чтения логов, которые я, конечно же, затем съел.

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

Для проверки использовался отдельный CUDA-тест, который выполнял запись из памяти одной карты в память другой, и замерял скорость.

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

Иногда драйвер выглядел полностью поддавшимся, а тест придерживался другого мнения.

Постепенно стала окончательно понятна разница между двумя этапами. Сначала нужно заставить драйвер разрешить P2P с помощью уже существующих внутренних механизмов. Затем драйвер должен действительно провести свою штатную процедуру создания удаленного отображения памяти.

И здесь я сдался

В какой-то момент я решил, что дальше всё-таки придется лезть значительно глубже, возможно уже внутрь закрытой части GSP.

К этому времени вычислительную часть CMP удалось разблокировать, PCIe я тоже достаточно намучил, а P2P несколько дней подряд отвечал разными вариантами “нет”.

В комментариях к предыдущей статье как раз спросили про прямой обмен.

И я написал, что всё - похоже, не получится.

После этого закрыл вкладку с комментариями и продолжил ковырять драйвер.

А потом штатный механизм наконец прошел до конца

Следующий прорыв произошел, когда драйвер наконец дошел до создания служебной области, необходимой для настройки доступа между двумя GPU.

В коде NVIDIA она называется mailbox - буквально “почтовый ящик”. Это часть уже существующего механизма NVIDIA, используемая при настройке доступа между картами.

В логах появился нужный путь, я увидел флаг который сам для себя оставил в драйвере:

CMP90HX_TRACE_GP100_CREATE_MAILBOX
attributes=0x1

И дальше произошло главное - CUDA действительно смогла включить доступ к памяти соседней карты.

Проверка стала возвращать:

can_access = 1

После этого собственное CUDA-ядро смогло прочитать удаленный буфер, а данные после передачи совпали с исходными. Вот теперь это был настоящий P2P.

Именно здесь я впервые получил работающий P2P на CMP 90HX. Оставалось посмотреть скорость.

250 МБ/с. Отлично поработал

Сразу поясню - все тесты на этом этапе я проводил на PCIe Gen1.

Причина здесь была не только в желании менять по одной переменной за раз. Моя реализация разблокировки PCIe сейчас, мягко говоря, не быстрая. Полный проход по всем картам занимает примерно 20-30 минут - во время разблокировки каждая карта проходит от 1 до 26 циклов переинициализации.

Причем это единственная рабочая реализация, которую я вообще смог получить для этих CMP. Поэтому если бы я после каждой гипотезы еще переключал все карты между Gen1 и Gen2, повторял процедуру, перезапускал тесты, возвращал всё обратно и снова шел по кругу, статью пришлось бы заканчивать уже на пенсии. Так что на этапе поиска причины я сознательно оставил PCIe в Gen1 и не трогал его.

Даже на Gen1 x16 я ожидал увидеть скорость хотя бы в гигабайтах в секунду.

Получил:

0,24-0,26 ГБ/с

То есть примерно 250 МБ/с.

Прямой обмен между двумя GPU, ради которого я несколько дней разбирал драйвер NVIDIA, работал значительно медленнее обычной передачи через оперативную память.

Для сравнения напоминаю путь через RAM:

STAGED_E2E_EFFECTIVE_GBPS ... 1.60

Промежуточное копирование через системную память было примерно в шесть раз быстрее моего свежеразблокированного P2P.

Я нашел правильный служебный путь в драйвере, дошел до штатного отображения памяти соседней карты, получил настоящий прямой доступ - и он с разгромным счетом проиграл тому самому маршруту через RAM, от которого я хотел избавиться.

С технической точки зрения это был огромный успех.

С человеческой - возникало желание очень аккуратно закрыть ноутбук в обратном направлении, и обе половинки кинуть в сервер.

250 МБ/с на самом деле были очень полезным результатом

Именно эта странная скорость сильно сузила круг поиска.

До появления реального обмена я мог подозревать буквально что угодно в цепочке, начиная от флагов в драйвере, заканчивая физическими шинами. Теперь большая часть этих вариантов отпала.

Удаленный адрес работает. CUDA-ядро действительно его читает. Штатная процедура создания отображения выполняется.

Значит, вопрос уже не “почему P2P не работает”, а “почему рабочий P2P передает данные со скоростью 0,25 ГБ/с”.

Копаем дальше. Первым подозреваемым стал ACS

В PCI Express есть механизм, который управляет тем, разрешено ли устройствам общаться напрямую или их трафик следует принудительно отправить через вышестоящий мост.

Называется он ACS - Access Control Services.

Он нужен для изоляции устройств и особенно полезен при виртуализации. Например, два устройства могут физически находиться рядом на одном участке дерева PCIe, но ACS заставит их транзакции сначала подняться к корневому порту.

Для безопасности это прекрасно. Для P2P потенциально отвратительно.

В настройках ACS есть отдельные флаги перенаправления запросов и завершений транзакций - ReqRedir и CmpltRedir. Если они активны, обмен может идти более длинным маршрутом вместо максимально короткого пути внутри PCIe-системы. А 250 МБ/с очень хорошо выглядели как результат какого-нибудь особенно неудачного путешествия пакетов по материнской плате.

Смотрю управляющее значение ACS на нужных корневых портах. Для 00:02.0 и 00:03.0 оно было = 001d. То есть перенаправление включено.

Отключаю перенаправление, получаю:

0000:00:02.0 ACSCtl old=001d new=0011
0000:00:03.0 ACSCtl old=001d new=0011

И проверяю, что нужные флаги действительно выключились:

ReqRedir-
CmpltRedir-

И вроде бы вот он - виновник найден! Есть логичная теория, конкретный параметр и подтверждение, что я его действительно изменил.

Запускаю тест:

STAGED_E2E_EFFECTIVE_GBPS ... 1.60
P2P_BATCHED_GBPS ........... 0.24

Почти ничего не изменилось.

ACS торжественно оправдан. Однако легче мне от этого не стало.

Может быть, это вообще не настоящий P2P?

Вдруг я настолько хорошо убедил драйвер воспользоваться P2P, что он уже сам считает всё правильным, а реальная передача внутри какой-нибудь функции CUDA всё равно незаметно проходит через системную память?

Поэтому одному cudaMemcpyPeerAsync я больше не доверял.

Отдельно запускал обычную функцию прямого копирования CUDA, отдельно собственное ядро, которое самостоятельно читало удаленный адрес и записывало результат в локальный буфер.

Если бы одна из функций CUDA тихо делала дополнительное копирование через RAM, эти два варианта должны были вести себя заметно по-разному.

Но оба давали примерно те же 0,24-0,26 ГБ/с.

Плюс в логах продолжало появляться создание служебной области штатным механизмом NVIDIA:

CMP90HX_TRACE_GP100_CREATE_MAILBOX
attributes=0x1

Так что P2P был настоящим. Просто очень медленным.

Потом полез в таблицы виртуальной памяти GPU

Следующей версией стало описание удаленной памяти в таблицах страниц GMMU.

Именно по этим записям GPU определяет, куда направить обращение к конкретному виртуальному адресу и каким способом работать с соответствующей областью.

Теоретически можно было представить ситуацию, когда адрес формируется правильно, но удаленная память описана каким-нибудь неудачным способом, поэтому каждое обращение становится очень дорогим.

Но здесь опять мешал один факт - система работала слишком правильно.

Буферы совпадали, ошибок доступа не было, драйвер не выдавал ошибок, а скорость была стабильно плохой, а не случайной. Это худший тип неисправности.

Когда что-то просто не работает - хотя бы можешь увидеть где. Когда всё работает правильно, но со скоростью флешки - приходится думать.

Очевидные виновники закончились

К этому моменту я уже успел проверить казалось бы все что можно.

Каждая неудачная гипотеза одновременно раздражала и давала полезную информацию.

Теперь я точно знал: P2P существует, удаленная память действительно читается напрямую, данные корректны, размер BAR1 нормальный, а отключение перенаправления ACS почти ничего не меняет. Значит, пора было посмотреть не на саму видеокарту, а на то, что происходит с адресами PCIe-устройств уже на стороне процессора.

Я забыл про IOMMU

PCIe-устройства в современной системе не обязательно работают непосредственно с физическими адресами.

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

Называется он IOMMU - Input-Output Memory Management Unit.

По смыслу он похож на обычный блок управления виртуальной памятью процессора. Программа обращается по одному адресу, аппаратура переводит его в другой. Только IOMMU выполняет аналогичную работу для устройств ввода-вывода - например, видеокарт.

Это нужно для виртуализации, безопасного проброса PCIe-устройств и защиты памяти от произвольного доступа со стороны железа.

В общем, крайне полезная штука. Особенно пока ты не пытаешься заставить две майнинговые видеокарты напрямую лазить друг другу в память.

Проверяю текущий режим, и вижу:

iommu: Default domain type: Translated

Translated означает, что обращения устройств проходят через полноценное преобразование адресов IOMMU. Конкретно в этом месте виновник предварительно был обнаружен. Потому что кроме него вариантов просто не оставалось.

Теперь меняю режим IOMMU

Вместо полного отключения я решил перевести конкретные группы PCIe-устройств с CMP в режим прямого соответствия адресов.

Переключаю тип домена каждой нужной группы в identity.

В режиме identity адрес проходит без обычного преобразования таблицами IOMMU - входной адрес соответствует выходному.

Повторяю тест на той же паре карт.

До переключения:

0,24-0,26 ГБ/с

После:

KERNEL_DST_EXEC_READ_PEER_WRITE_LOCAL ... gbps=3.080
KERNEL_SRC_EXEC_READ_LOCAL_WRITE_PEER ... gbps=3.350

Тут я сначала не поверил.

Повторяю через другой механизм:

P2P_BATCHED_GBPS ... 3.08
P2P_BATCHED_GBPS ... 3.35

А передача через оперативную память:

STAGED_E2E_EFFECTIVE_GBPS ... 1.60

Вот теперь результат уже выглядел так, как должен выглядеть настоящий P2P.

От 250 МБ/с до 3,35 ГБ/с

Рост получился больше чем на порядок.

Причем драйвер между этими двумя тестами не менялся. Механизм создания удаленного отображения был тем же. ACS уже был проверен.

Изменился только режим работы IOMMU.

На паре карт внутри одного процессорного узла я получил 3,08-3,35 ГБ/с вместо прежних 0,24-0,26 ГБ/с. Старый маршрут через оперативную память при этом давал около 1,60 ГБ/с.

Вот теперь прямой обмен наконец стал не только технически существующим, но и практически полезным.

Что в итоге пришлось сделать с драйвером

Штатно CMP 90HX определяется как устройство без P2P, поэтому CUDA даже не доходит до попытки создать нормальный доступ к памяти соседней карты.

Сначала я нашел внутренние механизмы вроде ForceP2P, RMForceP2PType, PeerMappingOverride и RMDisableFeatureDisablement. Потом возникла гипотеза, что придется вручную переопределять сведения, возвращаемые GSP.

Но оказалось, что разработчики NVIDIA уже предусмотрели этот сценарий в самом драйвере. Внутренние флаги позволяют принудительно пройти обычные проверки и дальше задействовать штатный механизм создания доступа через BAR1 и GMMU.

Инженеры NVIDIA уже оставили нужный инструментарий, а мне лишь оставалось понять, как им правильно воспользоваться.

А что с двумя процессорами?

На картах, подключенных к одному процессору, P2P работает нормально - 3,08-3,35 ГБ/с на Gen1 против примерно 1,60 ГБ/с через RAM.

Но мой сервер двухпроцессорный. Часть CMP подключена к одному Xeon, часть - ко второму. При обмене между такими картами трафику приходится пересекать соединение между процессорами, и там скорость заметно падает.

Поэтому одинаково хорошо использовать P2P между всеми пятью картами именно на моей системе не получится. Ограничением становится уже сама двухпроцессорная топология. С этим я уже действительно ничего не сделаю.

Теперь Gen2

На этом этапе эксперимент с Gen1 уже был закончен. P2P работал, причина странных 250 МБ/с была найдена, а IOMMU больше не мешала.

Теперь наконец имело смысл потратить еще один цикл на Gen2.

Как я писал выше, моя нынешняя реализация переключения всех CMP на Gen2 - это не мгновенный флаг. Полный проход по пяти картам занимает примерно 20-30 минут. Если бы я делал это после каждой гипотезы, я бы успел состариться раньше, чем закончил таблицу результатов.

А теперь - наконец-то меняю только одну переменную - скорость самой PCIe.

Gen1 работает со скоростью 2,5 GT/s на линию. Gen2 удваивает ее до 5 GT/s.

После разблокировки обе карты подняли 5.0 GT/s при ширине x16.

Проверяю ту же пару 02:00.0 и 03:00.0. Буфер 256 МиБ, 32 повторения.

Получаю:

0000:02:00.0 -> 0000:03:00.0 : 6.70 GB/s
0000:03:00.0 -> 0000:02:00.0 : 6.70 GB/s

6,70 ГБ/с в обе стороны.

То есть удвоение скорости физического интерфейса почти ровно удвоило и пропускную способность P2P. Это, пожалуй, лучший финальный контрольный опыт из возможных: после всех патчей, карта наконец ведет себя совершенно скучно и предсказуемо.

И всё-таки я уже написал, что не получится (послесловие)

Тот комментарий, пожалуй, удалять не буду.

Я начинал эту серию с дешевых майнинговых карт, из которых просто хотелось получить побольше доступной видеопамяти. Постепенно выяснилось, что за надписью CMP скрывается гораздо больше возможностей, чем NVIDIA решила оставить пользователю.

Возможность включения P2P я в итоге добавил и в свою утилиту CMP90HX PWNER. Теперь вместо ручного повторения всей последовательности с патчами, параметрами драйвера и проверками можно запустить уже готовый режим, который применяет нужные изменения и подготавливает карту к прямому обмену. То есть очередной эксперимент, который начинался с ручного ковыряния драйвера, в итоге превратился в еще одну функцию утилиты.

https://github.com/iatethelogs/cmp90hx_pwner

И теперь мне действительно интересно - кто знает, что еще мы сможем выжать из этих карт.

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.