From 6344c7d205650688e60f3ca899208b697061af9d Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 31 Aug 2026 18:35:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BD=D1=8F=D1=82=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20prefli?= =?UTF-8?q?ght=20=D0=B8=20=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=20dzn=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit preflight печатал последнюю строку вывода упавшего прогона, а у паники Rust последняя строка — «note: run with RUST_BACKTRACE=1», то есть ноль сведений о причине. Теперь из вывода достаётся собственное сообщение решателя, а для паники — то, что стоит ПОСЛЕ строки «panicked at». Вместо A10_turb_n2048_kbc note: run with `RUST_BACKTRACE=1` ... печатается A10_turb_n2048_kbc wgpu error: Validation Error | In Device::create_bind_group, label = 'level' | Buffer binding 0 range 150994944 exceeds `max_*_buffer_binding_size` limit 134217728 Заодно задокументировано само ограничение. dzn объявляет max_storage_buffer_binding_size = 128 МиБ против гигабайтов у нативных драйверов, при том что max_buffer_size у него 2047 МиБ: держать большой буфер можно, показать шейдеру одной привязкой — нет. Потолок выходит 3.73 млн узлов (около 1920x1920), и 14 прогонов кампании из 115 в него не влезают. На эти 14 приходится 70.7% стоимости, так что для кампании ограничение решающее. Co-Authored-By: Claude Opus 5 --- docs/theory/2d_solver/bench/README.md | 39 +++++++++++++++++++++++ docs/theory/2d_solver/bench/preflight.py | 40 ++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/docs/theory/2d_solver/bench/README.md b/docs/theory/2d_solver/bench/README.md index 0d195bc..ed14c29 100644 --- a/docs/theory/2d_solver/bench/README.md +++ b/docs/theory/2d_solver/bench/README.md @@ -83,6 +83,45 @@ dzn сообщает о себе `conformanceVersion = 0.0.0.0`: набор те Точность трансляция не портит. Но это замер на Intel; на своей карте прогоните сами — две минуты. +### Чего этот путь НЕ может: предел 128 МиБ на привязку + +Главное ограничение dzn, и оно жёсткое. Драйвер объявляет +`max_storage_buffer_binding_size = 128 МиБ` (2²⁷ байт), тогда как нативные драйверы дают +гигабайты. Сам буфер держать можно — `max_buffer_size` у dzn 2047 МиБ, — но показать +шейдеру одной привязкой больше 128 МиБ нельзя. + +Массив функций распределения занимает `nx · ny · 9 · 4` байт, значит потолок — +**3.73 млн узлов** (примерно 1920×1920). Всё, что крупнее, падает на создании bind group: + +``` +Buffer binding 0 range 150994944 exceeds `max_*_buffer_binding_size` limit 134217728 +``` + +В кампании таких прогонов **14 из 115**, и это не мелочь: на них приходится **70.7% +стоимости**, потому что дорогие прогоны — как раз крупносеточные. + +| прогон | сетка | буфер | +|---|---|---| +| B10_cyl_d128 | 3.93 млн узлов | 135 МБ | +| A10, A11 (турбулентность N=2048) | 4.19 млн | 144 МБ | +| D14_naca_chord192 | 4.72 млн | 162 МБ | +| I01–I09 (сверхмелкие сетки) | 8.39 млн | 288 МБ | +| A12 (турбулентность N=4096) | 16.8 млн | 576 МБ | + +`preflight` показывает их все с причиной — запускать его до кампании обязательно. + +Что с этим делать — три возможности, и ни одна не бесплатна: + +1. **Разделить кампанию.** 101 прогон в контейнере, 14 крупных — нативной сборкой (в + Windows или на настоящем Linux-сервере). Работает сегодня, кода не трогает, но это + два разных запуска. +2. **Перестроить буферы решателя** под привязку на направление: девять привязок по + `nx · ny · 4` байта вместо одной на `nx · ny · 9 · 4`. Тогда потолок поднимается до + 33.5 млн узлов и в контейнер влезает вся кампания. Цена — переделка раскладки в + ядрах WGSL (24 места обращения) и повторная валидация; на нативных драйверах смысла + в этом нет, так что понадобятся два варианта шейдера. +3. **Считать всю кампанию нативно.** Ни предела, ни платы за трансляцию — но и контейнера. + ### Чего трансляция стоит по скорости Плата есть, но она почти вся — накладные расходы на вызов, а не на счёт, и потому падает diff --git a/docs/theory/2d_solver/bench/preflight.py b/docs/theory/2d_solver/bench/preflight.py index e07e023..7e3254f 100644 --- a/docs/theory/2d_solver/bench/preflight.py +++ b/docs/theory/2d_solver/bench/preflight.py @@ -27,6 +27,39 @@ DROP = {"--gif", "--xt", "--case-csv", "--gif-field", "--gif-downsample", "--gif "--gif-every", "--gif-fps", "--series-every"} +# Строки, которые ничего не объясняют и только вытесняют полезные. +NOISE = ( + "note: run with `RUST_BACKTRACE", + "note: Some details are omitted", + "WARNING: dzn is not a conformant", + "stack backtrace", + "Caused by:", +) + + +def explain(output): + """Достать из вывода упавшего прогона то, ради чего его вообще читают. + + Раньше бралась последняя строка вывода — а у паники Rust последняя строка это + «note: run with RUST_BACKTRACE=1», то есть ровно ноль сведений о причине. + Полезное лежит либо в собственном сообщении решателя, либо на строке ПОСЛЕ + «panicked at»: там текст самой паники. + """ + out = [l.rstrip() for l in output.splitlines() if l.strip()] + out = [l for l in out if not any(n in l for n in NOISE)] + if not out: + return "(без вывода)" + for l in out: + if l.lstrip().startswith("ОШИБКА"): + return l.strip() + for i, l in enumerate(out): + if "panicked at" in l: + rest = [x.strip() for x in out[i + 1:]] + return " | ".join(rest[:4]) if rest else l.strip() + return out[-1].strip() + + + def main(): ap = argparse.ArgumentParser() ap.add_argument("--group", help="только эта группа, например I") @@ -64,14 +97,15 @@ def main(): ok = p.returncode == 0 print("." if ok else "X", end="", flush=True) if not ok: - tail = (p.stdout + p.stderr).decode("utf-8", "replace").strip().splitlines() - bad.append((r["id"], tail[-1] if tail else "(без вывода)")) + out = (p.stdout + p.stderr).decode("utf-8", "replace") + bad.append((r["id"], explain(out))) if i % 40 == 0: print(f" {i}/{len(runs)}", flush=True) print(f"\n\nпроверено сценариев: {len(runs)}, не запустились: {len(bad)}") for id_, msg in bad: - print(f" {id_:<28} {msg}") + print(f" {id_}") + print(f" {msg}") return 1 if bad else 0