Перенос состояния через границу чанков: чья сторона неверна
Статус: РАЗРЕШЕНО ЧАСТИЧНО (2026-09-30). Дефект настоящий; виновник — наш код, а не f64-транскрипция. Точная строка — нет, механизм — да.
Измерено на main (5dd279d), ndarray, T = 32 = 2 чанка, против f64-оракула:
output rel = 8.290e-1 (output scale 6.759e-1)chunk 0 max |diff| = 4.907e-8 <- внутричанковая алгебра ВЕРНАchunk 1 max |diff| = 5.603e-1 <- перенос через границу НЕВЕРЕНfinal state rel = 8.930e-1oracle self-consistency (fwd-mode vs FD) = 2.160e-12Что было отброшено, и с каким замером
Section titled “Что было отброшено, и с каким замером”| гипотеза | результат |
|---|---|
пакетная переработка (ChunkPath::Batched) |
DM_GDN2_OPS=loop даёт тот же ответ |
член S_in · E_last |
сохраняется при обнулённом начальном состоянии |
формула khat0ᵀ v_new0 |
не подходит ни при одной из шести конвенций затухания; перенос однозначно восстановлен решением 16 ур. в 8 неизв. на столбец (ранг 8, подгонка до 3.0e-8) |
Что нашлось при чтении первоисточника
Section titled “Что нашлось при чтении первоисточника”fla/ops/common/chunk_delta_h.py @ 9f38d249 — это первоисточник формулы
переноса, а не транскрипция. Строки 246–284, блок if USE_G: / if USE_GK:
в конце обработки чанка:
if USE_G: # per-head decay b_g_last = exp2(g[... last_idx ...]) b_v = b_v * where(m_t, exp2(b_g_last - b_g), 0)[:, None] b_h1 *= b_g_last # ← decay ПЕРЕНОСИМОГО СОСТОЯНИЯif USE_GK: # per-key-dimension decay b_gk_last1 = gk[..., o_k1, :] b_h1 *= exp2(b_gk_last1)[None, :] # ← ВТОРОЙ, НЕЗАВИСИМЫЙ decayДва независимых затухания подряд. У нас:
src/kernel/chunk_cube.rs:541
S[kk][v] = S[kk][v]·g_last[kk] + Σ_r kgd[r][kk]·v_new[r][v]где kgd[r] = k_r · exp(g_last) / exp(g_r) (:117), g_last[kk] = exp(g_left)
(:120) — однофакторное, по размерности kk, без отдельного слагаемого.
Поиск по vendor/dormouse-fused/crates/burn-gdn2/src/ на gk / kg_decay /
gk_last / STATE_V_FIRST: нуль совпадений. Раздельного затухания по
размерностям ключа у нас нет вовсе, и флага выбора такого режима тоже нет.
Что это значит и почему это НЕ «транскрипция неверна»
Section titled “Что это значит и почему это НЕ «транскрипция неверна»”USE_GK — это опциональная конфигурация FLA, а не формула, которую мы
переписали неверно. Наш код реализует USE_G-эквивалент: одно затухание. Если
v USE_GK=False, формула переноса у FLA совпадает с нашей по виду.
Значит: расхождение 5.6e-1 на chunk 1 не объясняется отсутствием
USE_GK, если только эталон не считает в режиме USE_GK=True. Это
проверяемо и не проверено, и это первое, что должен сделать следующий
агент: посмотреть, при каком USE_GK / USE_G построена
tools/gen_reference_f64.py, и пересчитать в согласованном режиме.
Второе, что требует проверки: у FLA decay обоих слагаемых берётся из
exp2(...), то есть логарифм по основанию 2. Наш g_last — из
натурального exp. Если соглашение по основанию не выровнено при переводе
g в gk, значения будут согласованы на первом чанке (где перенос ещё не
участвует в накоплении) и разойдутся на втором. Это объясняет наблюдаемую
форму дефекта лучше, чем отсутствие USE_GK. burn-kda явно оговаривает
этот вопрос (autodiff.rs exp2/base-2 cumsum) — не проверено здесь.
Сужено до конкретной строки (2026-09-30, чтение кода)
Section titled “Сужено до конкретной строки (2026-09-30, чтение кода)”Записаны все присваивания в s_sh в chunk_cube.rs (строки 340–700).
Их ровно три:
| строка | что | путь |
|---|---|---|
| 373 | s_sh = state[...] — загрузка входа |
до цикла while t, один раз |
| 590 | s_sh = s_sh·glast[cb·kd+kk] + (acc_a+acc_b+acc_c+acc_d) |
основной путь, перенос |
| 677 | s_sh = state_in[...] |
другой путь |
И вычисление v_new, которое читает s_sh:
434: vn_a += w0 * s_sh[kk * vtile + v0]; // v_new = u - W·sГипотеза, которую надо проверить первой — расхождение нормировки внутри
чанка. kgd[r] = k_r·exp(g_last)/exp(g_r) (:117) нормирует вклад k к
концу чанка (gexp_l = gexp[c-1]). Значит и входящее состояние s_sh
должно быть нормировано к тому же концу, то есть домножено на exp(g_last)
до того, как из него считается v_new. Но :590 применяет glast
после — v_new на :434 читает ещё не затухавшее состояние.
Это объясняет наблюдаемую форму дефекта точно: на первом чанке входное
состояние нулевое, v_new не зависит от нормировки, и chunk 0 сходится на
4.9e-8. На втором чанке состояние ненулевое — и нормировка проявляется
целиком. Не «где-то расходится», а ровно «со второго чанка».
Проверяемое следствие: если гипотеза верна, подстановка
S ← S·glast в начало обработки чанка (то есть перенос :590 в ДО
вычисления v_new) должна снять красное. Если красное останется — гипотеза
опровергнута, и следующий кандидат: glast[cb·kd+kk] индексируется
cb = bh·nt + t, и при nt > 1 либо при переиспользовании буфера glast
между вызовами индекс указывает не на тот блок.
Замечание к states_out: :722 пишет финальное состояние без
общего decay — то есть наружу отдаётся сырое. Это может быть верно (сырое
состояние и подаётся на вход следующего вызова как state_initial), а может
и означать двойное затухание на стыке двух вызовов. Отдельно проверить.
- Прочитать
gen_reference_f64.py: при какихUSE_G/USE_GK/основании построен эталон. Одна строка в коде, ответ всё проясняет. - Пересчитать эталон в согласованном режиме и снять красное.
- Если всё ещё красное — чинить наш
S-перенос по формеS ← (S·g_head)·g_key + …, предварительно проверив, чтоSв нашей компоновке оси-«ключ» и есть та, по которой FLA умножает наgk_last.
Чего этот документ НЕ утверждает
Section titled “Чего этот документ НЕ утверждает”- Не утверждается, что виноват
chunk_cube.rs:541. Замечено расхождение формул, не совпадение ошибки. - Не утверждается, что эталон неверен. Он опровергнут на chunk 0 (4.9e-8), а его самосогласованность 2.16e-12 — против его собственной формулы он прав; вопрос только в том, в каком режиме он построен.
- Не утверждается, что
USE_GKнужен нам. Это конфигурация, а не дефект.
Обновление 2026-09-30: гипотеза A ОПРОВЕРГНУТА измерением
Section titled “Обновление 2026-09-30: гипотеза A ОПРОВЕРГНУТА измерением”Проверено само, не делегировано. Числа на main воспроизведены точно
(chunk 0 4.907e-8, chunk 1 5.603e-1, final state 8.930e-1).
Что было сделано. Снят множитель glast[cb*kd+kk] из переноса
(:758 в исполняемом пути, :590 — в другом), чтобы状态 не затухала дважды.
Результат: числа не изменились ни на одну цифру — chunk 1 так и
5.603e-1, final state так и 8.930e-1.
Что это доказывает. Правка не попала в исполняемый путь (или её эффект
точно нулевой), то есть проблема не в лишнем затухании на переносе. Иначе
chunk 1 сдвинулся бы. Правка откачена, дерево чистое.
Это отвергает саму диагностическую гипотезу документа в её исполнимой
форме. То, что остаётся подтверждённым, — это рассогласование нормировки,
которое я вывел из чтения кода: w_buf чанка t+1 построен к концу чанка t+1
(kgd[r] = k_r·exp(g_last)/exp(g_r), :117), а s_sh на входе чанка t+1 —
состояние на конец чанка t. Это чтение, а не измерение, и вчерашний план
«фальсифицировать одним движением» именно это и не смог подтвердить.
Почему не дальше, честно. Чтобы это довести, нужен вывод схемы
нормировки целиком: является ли u/w нормированными к началу или к концу
чанка, и в какой форме S должен входить в v_new = u − W·S. Из
chunk 0, сходящегося на 4.9e-8, следует, что внутри одного чанка всё
согласовано; расходится только стык. Но выбрать между «нормировать u,w к
началу» и «умножать s_sh на отношение затуханий между чанками» по чтению
нельзя — второй вариант требует деления на затухание, которое на длинных
последовательностях вырождается, и это отдельное численное решение, а не
строчная правка.
Состояние: гейт остаётся красным намеренно. Один приборный тест на изменение не дал ничего — это результат, и он записан.
Обновление 2026-09-30 (позднее): причина НЕ в затухании переноса
Section titled “Обновление 2026-09-30 (позднее): причина НЕ в затухании переноса”Три измерения, все на main/forward.rs, все с числами.
1. Тест идёт НЕ по кубическому ядру. module.rs:52 берёт fused-путь только
при is_cuda::<B>(); на ndarray это ложь, и выполняется тензорный
chunk_wy_forward_impl → chunk_wy_forward_batched/loop из forward.rs.
Все правки в chunk_cube.rs обязаны были дать ровно ноль. Проверено: снятие
glast в ОБОИХ путях ядра (:590 и :758) сдвинуло числа на ноль.
Это опровергает диагностику документа: весь разбор был про ядро, которое
здесь не исполняется. Агент, писавший документ, читал chunk_cube.rs, не
проверив, какой путь берёт ndarray.
2. Затухание переноса меняет ответ на 0.1 %. Заменено на
carry[ci] = exp(g_cumsum[ci,last] − g_cumsum[ci−1,last]) (затухание МЕЖДУ
чанками, а не от начала последовательности — исходный g_last был именно
последним, и на чанке 0 они совпадают):
| chunk 1 | final state | |
|---|---|---|
исходно (g_last) |
5.603e-01 | 8.930e-01 |
carry_decay |
5.609e-01 | 9.398e-01 |
Ошибка изменилась на 0.1 %. Значит затухание переноса — не причина. Правка откачена; исходная формула может быть верной при нормировке состояния к началу чанка.
3. Что остаётся доказанным. chunk 0 верен на 4.907e-08 при любом
затухании, chunk 1 расходится на O(1). Единственное, что меняется между
ними, — состояние перестаёт быть нулевым. Ошибка сосредоточена в
внутричанковой нормировке, а не на стыке:
let v_new = cut(u) - cut(w_wy).matmul(s_before); // в какой нормировке?state = s_before * g_last + k_dec.matmul(v_new); // k_dec = k5·exp(g_cum[last]−g_cum[t])k_dec нормирован к концу чанка. Если v_new нормирован к началу
(u = m_inv @ (w·g_exp) — произведение на полное накопленное затухание, а
m_inv решает систему внутричанково), то k_dec и v_new стоят в разных
нормировках, и их произведение бессмысленно. На чанке 0 это не видно: всё
нулевое.
Это следующий шаг, и он уже сформулирован проверяемо: определить нормировку
v_new и привести к ней k_dec (или наоборот). Выбор между «нормировать u,w
к концу» и «нормировать k_dec к началу» — численное решение, а не чтение.
Состояние: гейт красный намеренно, диагноз сужен с «одна строка ядра» до
«внутричанковая нормировка v_new против k_dec в forward.rs».
Обновление 2026-09-30 (третья сессия): f32 НЕ причина, и пропущенный гейт найден
Section titled “Обновление 2026-09-30 (третья сессия): f32 НЕ причина, и пропущенный гейт найден”Пять агентов до этого искали строку в chunk_cube.rs. Ниже — четыре измерения,
которые ломают неверную ветку диагностики, и одна структурная находка, которая
объясняет, почему дефект вообще дожил.
1. Эталон не устарел (numpy, без cargo)
Section titled “1. Эталон не устарел (numpy, без cargo)”tools/gen_bwd_f64.py::forward — построчная транскрипция
forward.rs::chunk_wy_forward_batched, включая рекурсию состояния. Оба
закоммиченных фикстура воспроизводятся текущим генератором до нуля:
| фикстура | out max|d| |
out_state max|d| |
|---|---|---|
ref_bwd_f64.bin (T=16, 1 чанк) |
0.000e+00 | 0.000e+00 |
ref_bwd_f64_carry.bin (T=32, 2 чанка) |
0.000e+00 | 0.000e+00 |
Значит «эталон построен в другом режиме USE_G/USE_GK» — мертвая гипотеза:
эталон построен ровно тем кодом, который напечатан рядом с ним.
2. f32 НЕ объясняет расхождение — это ключевое измерение
Section titled “2. f32 НЕ объясняет расхождение — это ключевое измерение”Та же алгебра, что и в Rust, воспроизведена в numpy для трёх комбинаций
dtype × алгоритм инверсии. Против закоммиченного out/out_state:
| dtype | инверсия | chunk 0 | chunk 1 | state rel |
|---|---|---|---|---|
| f64 | np.linalg.inv |
8.3e-17 | 1.1e-16 | 0.0 |
| f64 | Neumann (как в Rust) | 8.3e-17 | 2.2e-16 | 4.3e-16 |
| f32 | Neumann (как в Rust) | 7.2e-08 | 9.8e-08 | 3.2e-07 |
f32-симуляция ТОЙ ЖЕ алгебмы сходится на обоих чанках с шумом f32. Она
на 6 порядков ниже измеренных Rust 5.603e-01. Значит виноват не численный
формат и не инверсия, а что-то в коде Rust, чего нет в этой транскрипции —
и это не гипотеза, а исключение: арифметика совпадает, сходится, значит
расходится не она.
Побочно: инверсия Neumann здесь не вырождена. max|(-L)^j| для обоих
чанков падает до 1e-14 к j=14, максимальная сумма модулей строки |L| = 3.54
(чанк 0) и 4.27 (чанк 1), пик ряда — единица на I. nearest wrong formula
для инверсии здесь не существует.
3. Ответ на решающий вопрос брифа: да, один чанк сходится — но не по тому, что проверяли
Section titled “3. Ответ на решающий вопрос брифа: да, один чанк сходится — но не по тому, что проверяли”Одночанковая проверка the_forward_agrees_with_the_f64_transcription зелёная
(chunk 0 = 4.907e-08). Но она выбрасывает состояние:
let (out, _new_state) = chunk_wy_forward( // autodiff_bwd_f64.rs:251_new_state. То есть за всю жизнь гейта проверялся ВЫХОД чанка, и никогда —
состояние, которое чанк оставляет за собой. Это ровно та величина, которую
потребляет следующий чанк. Отсюда форма дефекта, и она не требует ни одной
из пяти отвергнутых гипотез:
- состояние считается после того, как
out_cуже положен вoutputs(строка 297 против 292-294) ⇒ на одном чанке ошибка состояния невидима по построению; - на двух чанках
s_beforeна чанке 1 ненулевой, и ошибка проявляется целиком.
Вот почему пять агентов читали chunk_cube.rs, а не находили дефект, и почему
замена затухания переноса сдвинула ответ на 0.1 %: меняли ЗНАЧЕНИЕ множителя,
а сломано ПРОИЗВОДСТВО состояния.
Пропущенный гейт добавлен: the_state_we_hand_back_is_the_state_the_oracle_computed
(1 и 2 чанка, против уже закоммиченных блоков out_state).
4. Ещё два отвергнутых кандидата (чтение кода burn, не измерение)
Section titled “4. Ещё два отвергнутых кандидата (чтение кода burn, не измерение)”cut(g_last)режет ось размера 1 диапазоном0..chunk_size. Не дефект:burn-std-0.22.0-pre.3/src/tensor/slice.rs:34-51клампит (slice.to_range(*dim_size)), получается[B,H,1,K], как и задумано.- Единственный matmul с транспонированным левым операндом —
cut(k_dec).swap_dims(2,3).matmul(v_new), ровно то самое сломаное слагаемое. Correlation сильная, ноburn-ndarray/src/tensor.rs:543-565для non-standard-layout идёт по веткеto_shape(dim).unwrap().as_standard_layout(), то есть копирует по логическим индексам, и значения верны. Не причина — но именно поэтому гипотеза выглядела правдоподобно.
Состояние: названное красное unexplained остаётся сильнее любой
правдоподобной строки. Что установлено: инверсия в порядке, f32 в порядке,
нормировки v_new/k_dec в транскрипции согласованы (иначе f32-симуляция
разошлась бы), и остаётся ровно одно — состояние, которое производит
forward.rs:296-297 (и его близнец :636 в петлевом рукаве).
ОБНОВЛЕНИЕ 2026-09-30 (позднее): ВИНОВЕН ЭТАЛОН, gen_bwd_f64.py:258-259
Section titled “ОБНОВЛЕНИЕ 2026-09-30 (позднее): ВИНОВЕН ЭТАЛОН, gen_bwd_f64.py:258-259”Одна строка, двойной экспоненциал. tools/gen_bwd_f64.py::forward:
258: g_last = E[:, :, :, c - 1:c, :] # E = exp(G) <- УЖЕ экспоненцировано259: decay_last = ... else np.exp(g_last - G) # exp(exp(G_last) - G) <- ДВОЙНОЙ expRust (forward.rs:269-277) делает правильно:
let decay_last = (g_cumsum.slice(last) - g_cumsum).exp(); // exp(G_last - G)g_last в оракуле — это E (экспонента суммы), и он используется в exp
второй раз. Правильная величина — G[..., c-1:c, :], лог- domain.
Это ровно та ошибка, о которой спрашивал бриф («определи нормировку v_new
конструкцией») — но нормировки в коде нет: расходится не Rust, а
эталон, и обе стороны используют k_dec = k·exp(G_last−G_t), то есть
нормировку к КОНЦУ чанка. v_new и k_dec стоят в ОДНОЙ нормировке, и
это подтверждено измерением, а не чтением.
Доказательство: пошаговое сравнение f32 Rust с двумя кандидатами
Section titled “Доказательство: пошаговое сравнение f32 Rust с двумя кандидатами”Один и тот же вход (ref_bwd_f64.bin, T=16, один чанк), один и тот же
f32 numpy-транскрипт, меняется только строка decay_last:
decay_last |
vs закоммиченный f64 out_state |
|---|---|
exp(E_last − G) (как в оракуле, двойной exp) |
3.09e-07 ✅ шум f32 |
exp(G_last − G) (как в Rust, одиночный exp) |
1.24e+00 ❌ |
|ref|max = 1.781. Оракул сходится с неправильной формулой, а Rust —
с правильной, и расхождение 1.24 это не шум, а весь масштаб.
Пошаговая сверка всех членов (f32 Rust из forward.rs против f32 numpy
транскрипта, против f64 оракула):
| член | Rust vs numpy | numpy vs f64-оракул |
|---|---|---|
t1 = s_before·g_last |
1.49e-08 | 1.70e+00 |
t2 = k_decᵀ·v_new |
1.04e-07 | 1.34e+00 |
sum = state |
1.04e-07 | 1.24e+00 |
k_decᵀ |
5.97e-08 | — |
v_new |
5.97e-08 | — |
Rust воспроизводит транскрипт своего собственного кода до 1.0e-07 и разошелся с оракулом на всех трёх членах сразу — ровно так ведёт себя неверный эталон, а не неверная реализация.
Почему пять агентов не нашли
Section titled “Почему пять агентов не нашли”Потому что искали в chunk_cube.rs. Соблазн «наш код неверен» держался
на том, что эталон — это транскрипция forward.rs (так и написано в
шапке autodiff_bwd_f64.rs), и читающий это решит, что оракул по
определению повторяет код, а значит идти надо в код. Он повторяет код
почти построчно — и в одной строке повторяет не ту формулу, потому
что в Python g_last уже экспоненцирован, и разница невидима глазом.
Урок, который стоит записать: транскрипция, построенная по соседнему коду, наследует его ошибки — и в этом конкретном случае ошибка была не в томе копируемом, а в ОДНОМ различающем символе. Гейт, который это поймал бы, — сверка оракула с двумя формулами, а не с одной.
Состояние: причина найдена и измерена; chunk_cube.rs и нормамировки
непричастны. Правка в оракуле + перегенерация фикстур.
Итог: фикс, границы применимости и оба направления гейта
Section titled “Итог: фикс, границы применимости и оба направления гейта”Правка — две строки в двух генераторах, ни одной в src/.
forward.rs побайтово совпадает с main после всей работы; diff чист.
tools/gen_bwd_f64.py:263-269—decay_lastв лог- домене.tools/fwd_mode.py:144-149— то же (иначе два метода оракула разошлись бы между собой, а не с нами).- Перегенерированы
tests/ref_bwd_f64.binиtests/ref_bwd_f64_carry.bin.ref_bwd_f64_faults.binне изменился ни на байт.
Числа после фикса
Section titled “Числа после фикса”| величина | было | стало |
|---|---|---|
chunk 1 max abs diff |
5.603e-01 | 3.327e-08 |
final state rel |
8.930e-01 | 2.621e-07 |
| state на ОДНОМ чанке | (не проверялось) | 1.193e-07 |
out rel, 2 чанка |
8.290e-01 | 2.6e-07 |
dstate градиент |
— | 2.039e-07 |
Бар FWD_BAR = 1e-3, шум f32 ~5e-7. Запас 4 порядка.
Оба направления гейта (требование брифа выполнено)
Section titled “Оба направления гейта (требование брифа выполнено)”- Вернул двойной exp в
gen_bwd_f64.py, перегенерировал, прогнал гейт:Числа в точности исходные из брифа. Новый гейт ловит старый дефект.two_chunks... FAILED chunk 1 5.603e-1, state 8.930e-1the_state_we_hand_back... FAILED one chunk 6.974e-1 - Вернул фикс, перегенерировал:
5 passed; 0 failed.
Толерантность не тронута, #[ignore] нет, тестов не удалено.
Новый гейт, которого не было
Section titled “Новый гейт, которого не было”the_state_we_hand_back_is_the_state_the_oracle_computed — 1 и 2 чанка.
Причина, по которой его не было: the_forward_agrees_with_the_f64_transcription
берёт let (out, _new_state) = ... (autodiff_bwd_f64.rs:251) и выбрасывает
состояние. Выход чанка проверялся всю жизнь; состояние, которое чанк
оставляет, — нет. Ошибка состояния невидима на одном чанке по построению,
потому что outputs.push(out_c) (строка 294) стоит до обновления состояния
(строка 296-297).
Это и есть тот гейт, который поймал бы дефект в день написания одночанковой
фикстуры, и он не требует ни градиента, ни второй стороны, ни новых данных:
блок out_state уже был в ref_bwd_f64.bin.
CUDA-ядро дефекта НЕ разделяет (пункт 4 брифа)
Section titled “CUDA-ядро дефекта НЕ разделяет (пункт 4 брифа)”chunk_cube.rs:117 — kgd[r] = k_r * gexp_l / gexp_r, то есть
k·exp(G_last)/exp(G_t) = k·exp(G_last−G_t): одна экспоненциация,
и glast (:119) — это gexp последней строки, тоже без двойного exp.
Проверено численно на случайном g:
| пара | max abs разница |
|---|---|
chunk_cube.rs kgd vs forward.rs decay_last |
2.220e-16 |
двойной exp оракула vs forward.rs |
4.681e+00 |
двойной exp оракула vs chunk_cube.rs |
4.681e+00 |
Три независимые реализации затухания (тензорный рукав, CUDA-ядро, FLA)
согласны; отличается только эталон. Поэтому chunk_cube.rs чинить не нужно,
и тест, сравнивающий два рукава, здесь не понадобился: расхождение было не
между рукавами.
Оговорка, которая остаётся: на ndarray ядро не исполняется
(module.rs:52 требует is_cuda), поэтому его численно проверяет только
CUDA-гейт, а этот прогон его не покрывает. Это долг, а не результат.
Про инертные фолты (открыто, не мой дефект)
Section titled “Про инертные фолты (открыто, не мой дефект)”decay-sign, no-state-decay, decay-on-v-new двигают loss на 0.000e+00
при ОДНОМ чанке. Проверено на старом генераторе тоже — они были инертны
и до правки, то есть это структурное свойство одночанкового случая (loss
зависит только от out, а out на одном чанке не читает decay_last), а не
следствие фикса. Генератор это различает и пишет в фикстуру loss фолта именно
для такой проверки. Не трогал.
Чего этот документ НЕ утверждает
Section titled “Чего этот документ НЕ утверждает”- Не утверждается, что
chunk_cube.rsбыл неверен. Он неверен не был, и показано это численно. - Не утверждается, что нормировки
v_new/k_decразличались. Они совпадают: обе стороны используютk·exp(G_last−G_t), то есть нормировку к КОНЦУ чанка. Вопрос брифа «определи нормировкуv_newконструкцией» отвечает тем, что её не пришлось менять — расходилась не нормировка, а базис, в котором она вычислялась (лог против экспоненты). - Слово «verified» здесь не используется без внешнего источника: конвенция
затухания сверена с
fla/ops/common/chunk_delta_h.py:236-240, commit9f38d249, файл на диске в/home/sehaxe/.cache/uv/archive-v0/DISaWrLCUXUgOZHC/. Всё остальное — собственная транскрипция, и так и помечено.