В 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>
153 lines
7.4 KiB
Bash
153 lines
7.4 KiB
Bash
#!/usr/bin/env bash
|
||
# Драйвер валидационной кампании. Читает scenarios.json, гоняет прогоны по одному, кладёт
|
||
# логи, ряды, сводки и гифки каждого в собственную папку и ведёт общую таблицу.
|
||
#
|
||
# ./run_campaign.sh --dry-run смета: что и сколько будет считаться
|
||
# ./run_campaign.sh --calibrate замерить фактические MLUPS этой машины
|
||
# ./run_campaign.sh --smoke три коротких прогона: проверить обвязку
|
||
# ./run_campaign.sh --resume считать, пропуская уже готовое
|
||
# ./run_campaign.sh --group A,B только выбранные группы
|
||
# ./run_campaign.sh --only cyl_re150 по подстроке идентификатора
|
||
# ./run_campaign.sh --budget-hours 24 остановиться, когда время выйдет
|
||
#
|
||
# Прогон, который упал или развалился, помечается в сводке и НЕ останавливает кампанию:
|
||
# группы E и G специально ищут предел устойчивости.
|
||
|
||
set -u
|
||
cd "$(dirname "$0")"
|
||
|
||
BIN="${KBC2D_BIN:-../target/release/kbc2d}"
|
||
SCEN="${KBC2D_SCENARIOS:-scenarios.json}"
|
||
OUT="${KBC2D_OUT:-out}"
|
||
# ВНИМАНИЕ: имя GROUPS занято самим bash (список групп пользователя), присваивание в неё
|
||
# молча игнорируется, а "$GROUPS" под root разворачивается в 0 — и выборка съедает всю
|
||
# кампанию, не сказав ни слова. Отсюда GRP_SEL.
|
||
DRY=0; RESUME=0; CALIB=0; SMOKE=0; GRP_SEL=""; ONLY=""; BUDGET=""
|
||
GPU_MLUPS="${KBC2D_GPU_MLUPS:-1200}"
|
||
CPU_MLUPS="${KBC2D_CPU_MLUPS:-22}"
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--dry-run) DRY=1 ;;
|
||
--resume) RESUME=1 ;;
|
||
--calibrate) CALIB=1 ;;
|
||
--smoke) SMOKE=1 ;;
|
||
--group) GRP_SEL="$2"; shift ;;
|
||
--only) ONLY="$2"; shift ;;
|
||
--budget-hours) BUDGET="$2"; shift ;;
|
||
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||
*) echo "неизвестный ключ: $1" >&2; exit 2 ;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
command -v python3 >/dev/null 2>&1 && PY=python3 || PY=python
|
||
[ -x "$BIN" ] || { echo "не найден бинарь решателя: $BIN (собери cargo build --release)" >&2; exit 1; }
|
||
[ -f "$SCEN" ] || { echo "не найден список сценариев: $SCEN" >&2; exit 1; }
|
||
|
||
# ── калибровка: три коротких прогона на месте, чтобы оценки в часах были не гаданием ──
|
||
if [ "$CALIB" = 1 ]; then
|
||
echo "Калибровка на этой машине (три коротких прогона)…"
|
||
# 1920x960 добавлена не для красоты: на 95% стоимости кампании сетки крупнее
|
||
# 600 тыс. узлов, и оценивать по мелким — значит занижать пропускную способность.
|
||
for spec in "240 120 gpu" "960 480 gpu" "1920 960 gpu" "480 240 cpu"; do
|
||
set -- $spec
|
||
m=$("$BIN" --nx "$1" --ny "$2" --size 16 --refine 1 --steps 3000 --report-every 3000 \
|
||
--verbose full --backend "$3" 2>/dev/null | grep -o '[0-9.]* MLUPS' | tail -1)
|
||
echo " $1x$2 на $3: ${m:-не измерено}"
|
||
done
|
||
echo
|
||
echo "Подставь замеренное в переменные окружения и запусти смету:"
|
||
echo " KBC2D_GPU_MLUPS=<число> KBC2D_CPU_MLUPS=<число> ./run_campaign.sh --dry-run"
|
||
exit 0
|
||
fi
|
||
|
||
mkdir -p "$OUT"
|
||
SUMMARY="$OUT/summary.csv"
|
||
LOG="$OUT/campaign.log"
|
||
[ -f "$SUMMARY" ] || echo "id,group,backend,status,seconds,steps,cost,title" > "$SUMMARY"
|
||
|
||
# Выборку и порядок считает python: разбирать JSON башем — верный способ ошибиться.
|
||
PLAN=$("$PY" - "$SCEN" "$GRP_SEL" "$ONLY" "$SMOKE" <<'PYEOF'
|
||
import json, sys
|
||
scen, groups, only, smoke = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] == "1"
|
||
runs = json.load(open(scen, encoding="utf-8"))["runs"]
|
||
if groups:
|
||
keep = {g.strip().upper() for g in groups.split(",")}
|
||
runs = [r for r in runs if r["group"].upper() in keep]
|
||
if only:
|
||
runs = [r for r in runs if only in r["id"]]
|
||
if smoke:
|
||
# три самых дешёвых прогона: обвязку проверяем, а не физику
|
||
runs = sorted(runs, key=lambda r: r["cost"])[:3]
|
||
for r in runs:
|
||
print("\t".join([r["id"], r["group"], r["backend"], str(r["cost"]),
|
||
str(r["steps"]), r["title"], "\x1f".join(r["args"])]))
|
||
PYEOF
|
||
)
|
||
|
||
[ -n "$PLAN" ] || { echo "под выборку не попал ни один прогон"; exit 0; }
|
||
|
||
total_cost=0; n=0
|
||
while IFS=$'\t' read -r id grp bk cost steps title args; do
|
||
n=$((n + 1)); total_cost=$((total_cost + cost))
|
||
done <<< "$PLAN"
|
||
|
||
est_h=$("$PY" -c "
|
||
import sys
|
||
print('%.1f' % (float(sys.argv[1]) / (float(sys.argv[2]) * 1e6) / 3600))" "$total_cost" "$GPU_MLUPS")
|
||
|
||
echo "прогонов: $n обновлений узлов: $total_cost ≈ $est_h ч при $GPU_MLUPS MLUPS"
|
||
echo "результаты: $OUT/<id>/"
|
||
echo
|
||
|
||
if [ "$DRY" = 1 ]; then
|
||
printf '%-28s %-3s %-4s %12s %10s %s\n' id гр бэк шагов часов название
|
||
while IFS=$'\t' read -r id grp bk cost steps title args; do
|
||
m=$GPU_MLUPS; [ "$bk" = cpu ] && m=$CPU_MLUPS
|
||
h=$("$PY" -c "import sys;print('%.2f'%(float(sys.argv[1])/(float(sys.argv[2])*1e6)/3600))" "$cost" "$m")
|
||
printf '%-28s %-3s %-4s %12s %10s %s\n' "$id" "$grp" "$bk" "$steps" "$h" "$title"
|
||
done <<< "$PLAN"
|
||
exit 0
|
||
fi
|
||
|
||
started=$(date +%s)
|
||
while IFS=$'\t' read -r id grp bk cost steps title args; do
|
||
dir="$OUT/$id"
|
||
if [ "$RESUME" = 1 ] && [ -f "$dir/summary.json" ]; then
|
||
echo "· $id — уже посчитан, пропускаю"
|
||
continue
|
||
fi
|
||
if [ -n "$BUDGET" ]; then
|
||
spent=$(( ($(date +%s) - started) ))
|
||
limit=$("$PY" -c "import sys;print(int(float(sys.argv[1])*3600))" "$BUDGET")
|
||
[ "$spent" -ge "$limit" ] && { echo "бюджет $BUDGET ч исчерпан, останавливаюсь"; break; }
|
||
fi
|
||
|
||
mkdir -p "$dir"
|
||
# аргументы разделены символом 0x1f: в них есть пробелы (список тел) и запятые
|
||
IFS=$'\x1f' read -r -a ARGV <<< "$args"
|
||
ARGV+=(--summary "$dir/summary.json" --csv "$dir/series.csv" --verbose full --report-every 5000)
|
||
# относительные имена файлов из сценария кладём внутрь папки прогона
|
||
for i in "${!ARGV[@]}"; do
|
||
case "${ARGV[$((i-1))]:-}" in
|
||
--gif|--xt|--case-csv) ARGV[$i]="$dir/${ARGV[$i]}" ;;
|
||
esac
|
||
done
|
||
|
||
printf '%s\n' "$BIN ${ARGV[*]}" > "$dir/cmd.txt"
|
||
echo "▶ $id ($grp, $bk) $title"
|
||
t0=$(date +%s)
|
||
if "$BIN" "${ARGV[@]}" > "$dir/log.txt" 2>&1; then st=ok; else st=fail; fi
|
||
t1=$(date +%s); dt=$((t1 - t0))
|
||
grep -q "РАЗВАЛИЛСЯ" "$dir/log.txt" 2>/dev/null && st=blewup
|
||
sed -n '/ИТОГОВЫЙ ОТЧЁТ/,$p' "$dir/log.txt" > "$dir/report.txt" 2>/dev/null
|
||
|
||
printf '%s,%s,%s,%s,%s,%s,%s,"%s"\n' "$id" "$grp" "$bk" "$st" "$dt" "$steps" "$cost" "$title" >> "$SUMMARY"
|
||
echo "$(date -Is) $id $st ${dt}s" >> "$LOG"
|
||
echo " → $st за ${dt} с"
|
||
done <<< "$PLAN"
|
||
|
||
echo
|
||
echo "готово. сводная таблица: $SUMMARY"
|