Запуск в WSL2 через docker compose без NVIDIA-runtime

В WSL2 драйвера Vulkan для Linux у NVIDIA нет: карта отдаётся через /dev/dxg по
протоколу WDDM, нативный libGLX_nvidia про него не знает и перечисляет ноль
устройств, поэтому Container Toolkit нечего подкладывать внутрь и docker падает
с `could not select device driver "nvidia"`.

С /dev/dxg умеет говорить dzn (Dozen) — драйвер Mesa, транслирующий Vulkan в
D3D12. Он положен в образ, и NVIDIA-runtime для этого пути не нужен вовсе: нужны
проброс устройства и монтирование /usr/lib/wsl, где Microsoft держит libd3d12.so.

Правка в решателе одна: флаги инстанса wgpu теперь читаются из окружения
(InstanceFlags::from_build_config().with_env()). Без этого переменная
WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER не действует, а wgpu по умолчанию
МОЛЧА прячет адаптеры, не прошедшие тесты соответствия Vulkan, — под это правило
попадает dzn, и решатель сообщал, что GPU не найден. Поведение по умолчанию не
изменилось: без переменной такие адаптеры по-прежнему скрыты.

База образа сменена с debian:bookworm-slim на archlinux:base: в пакетах Mesa у
Debian и Ubuntu dzn не собирают (проверено по спискам файлов), в Arch он лежит
отдельным пакетом той же версии Mesa, что и на хосте WSL.

Новое:
 * docker-compose.wsl.yml — путь через /dev/dxg, с профилем проверок и с build:
   на случай, когда доступа к реестру нет;
 * bench/parity.py — сверка GPU-пути с CPU в f64 на течениях, где расхождение
   f32 и f64 не нарастает. Соответствие dzn вендором не проверено, значит
   проверяем сами, а не верим на слово.

Замерено на Intel Iris Xe (та же карта, нативный драйвер против dzn):
 * точность: cd 2.39486 против 2.39486, energy_end 5.74813e-05 против
   5.74810e-05 — совпадение до 5-6 значащих цифр;
 * скорость: плата за трансляцию падает с ростом сетки, 4.2x на 61 тыс. узлов,
   1.46x на 461 тыс., 1.30x на 1.84 млн. На 95.1% стоимости кампании сетки
   крупнее 600 тыс. узлов, поэтому ожидаемое удорожание — около трети, не в разы.

В калибровку добавлена сетка 1920x960: оценивать кампанию по 240x120 значит
занижать пропускную способность вчетверо. Образ опубликован как
notbigghost/kbc2d:1.1.0 (он же latest), проверен вытягиванием из реестра.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 17:15:53 +03:00
co-authored by Claude Opus 5
parent e75b404f8a
commit 81985b169e
9 changed files with 488 additions and 28 deletions
+72 -1
View File
@@ -6,7 +6,7 @@
## Быстрый старт на сервере
Образ опубликован, собирать ничего не нужно: **`notbigghost/kbc2d:1.0.0`**. Исходники на
Образ опубликован, собирать ничего не нужно: **`notbigghost/kbc2d:1.1.0`**. Исходники на
сервере тоже не нужны — переносится один файл `docker-compose.server.yml`.
```sh
@@ -40,6 +40,77 @@ KBC2D_GPU_MLUPS=1450 KBC2D_CPU_MLUPS=40 docker compose -f $C --profile check run
docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi
```
## Запуск в WSL2
Отдельный путь, потому что в WSL2 **драйвера Vulkan для Linux у NVIDIA не существует**.
Карта отдаётся через `/dev/dxg` по протоколу WDDM; нативный `libGLX_nvidia` про него не
знает и возвращает ноль устройств — загрузчик его выбрасывает. Отсюда и `could not select
device driver "nvidia"`: NVIDIA Container Toolkit тут не поможет, потому что подкладывать
внутрь нечего.
Зато с `/dev/dxg` умеет говорить **dzn** (Dozen) — драйвер Mesa, транслирующий Vulkan в
D3D12. Он лежит в образе. NVIDIA-runtime при этом **не нужен вовсе**: достаточно проброса
устройства и монтирования `/usr/lib/wsl`, где Microsoft держит `libd3d12.so`.
```sh
C=docker-compose.wsl.yml
docker compose -f $C --profile check run --rm vulkan # 1. карта видна?
docker compose -f $C --profile check run --rm parity # 2. считает ли она правильно?
docker compose -f $C --profile check run --rm calibrate # 3. и с какой скоростью?
docker compose -f $C --profile check run --rm preflight # 4. все сценарии стартуют?
docker compose -f $C --profile check run --rm plan # 5. смета в часах
docker compose -f $C up -d # 6. кампания
```
Если образа нет ни локально, ни в реестре — `docker compose -f $C build`, доступ к Docker
Hub не обязателен.
### Шаг parity обязателен
dzn сообщает о себе `conformanceVersion = 0.0.0.0`: набор тестов соответствия Vulkan он не
проходил. wgpu по этой причине по умолчанию **прячет** такие адаптеры, и решатель сообщает,
что GPU не найден. Согласие считать на непроверенном драйвере даётся явно — переменной
`WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER=1`, она прописана в `docker-compose.wsl.yml`.
Раз соответствие не проверено вендором, его проверяем сами. `bench/parity.py` гоняет два
коротких эталона на CPU в f64 и на GPU и сверяет числа: вихрь Тейлора–Грина (есть точное
решение) и стационарное обтекание цилиндра при Re=20. Течения выбраны намеренно не
хаотические — там расхождение f32 и f64 не нарастает, поэтому заметное различие означает
проблему драйвера, а не разрядности.
Замерено на Intel Iris Xe: **dzn совпадает с нативным драйвером той же карты до 5–6
значащих цифр** (`energy_end` 3.02277e-07 против 3.02283e-07, `cd` 2.39486 против 2.39486).
Точность трансляция не портит. Но это замер на Intel; на своей карте прогоните сами — две
минуты.
### Чего трансляция стоит по скорости
Плата есть, но она почти вся — накладные расходы на вызов, а не на счёт, и потому падает
с ростом сетки. Замерено на Intel Iris Xe, один и тот же решатель:
| сетка | узлов | нативный Vulkan | dzn в контейнере | плата |
|---|---|---|---|---|
| 320×192 | 61 тыс. | 188.8 MLUPS | 44.5 MLUPS | **4.2×** |
| 960×480 | 461 тыс. | 125.3 MLUPS | 86.0 MLUPS | **1.46×** |
| 1920×960 | 1.84 млн | 128.7 MLUPS | 98.7 MLUPS | **1.30×** |
Каждый шаг решателя — несколько отправок в очередь; на мелкой сетке трансляция вызова
стоит дороже самого счёта, на крупной размазывается. Для кампании это решающее
обстоятельство, потому что дорогие прогоны в ней как раз крупные:
| узлов в сетке | прогонов | доля стоимости кампании |
|---|---|---|
| < 150 тыс. | 18 | 0.0% |
| 150–600 тыс. | 47 | 4.9% |
| > 600 тыс. | 50 | **95.1%** |
То есть 95% времени кампания проводит там, где плата 1.3–1.5×, и почти не бывает там, где
она четырёхкратна. Ожидаемое удорожание по всей кампании — около трети: 90 часов
превращаются примерно в 115–120, а не в 400.
Проверьте на своей карте: профиль `calibrate` меряет три сетки, и ориентироваться надо на
**1920×960** — она представительна для кампании, а 240×120 показывает худший случай.
## Своя сборка образа
Если нужен образ из текущего состояния репозитория, а не опубликованный: