Внятные сообщения preflight и предел dzn на размер привязки
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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. **Считать всю кампанию нативно.** Ни предела, ни платы за трансляцию — но и контейнера.
|
||||
|
||||
### Чего трансляция стоит по скорости
|
||||
|
||||
Плата есть, но она почти вся — накладные расходы на вызов, а не на счёт, и потому падает
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user