← Все главы

28 · Версия материала 2

Закройте доступ к будущим ключам каузальной маской

Разберите, как нижнетреугольная каузальная маска с разрешённой диагональю закрывает доступ к будущим ключам Transformer, делает их вероятности внимания строго нулевыми и сохраняет выходы предыдущих позиций.

Предскажите видимый треугольник

Добавьте к примеру из главы 27 третью позицию:

Q=[032111],K=[301221],V=[331324].Q=\begin{bmatrix}0&3\\2&-1\\1&1\end{bmatrix},\quad K=\begin{bmatrix}3&0\\-1&2\\2&1\end{bmatrix},\quad V=\begin{bmatrix}3&-3\\1&3\\-2&4\end{bmatrix}.

Здесь один пакет, три позиции токенов, ширина запросов и ключей равна двум, ширина значений — тоже двум. Следовательно,

Q,K,V1×3×2.Q,K,V\in\mathbb{R}^{1\times3\times2}.

До маскирования строки исходных оценок равны [0,6,3][0,6,3], [6,4,3][6,-4,3] и [3,1,3][3,1,3]. Сначала отметьте ячейки, доступные каждому запросу. Запросу 00 доступен только ключ 00; запросу 11 — ключи 00 и 11; запросу 22 — все три ключа. Итого получается шесть доступных и три закрытые ячейки. Все три диагональные ячейки входят в число доступных.

Примените маску до softmax

Для строки запроса ii и столбца ключа jj используйте аддитивную маску

Mij={0jij>i,A=softmax(S+M)M_{ij}=\begin{cases}0&j\le i\\-\infty&j>i\end{cases},\quad A=\operatorname{softmax}(S+M)

Диагональ намеренно остаётся доступной. При обучении декодера целевые входы сдвинуты на одну позицию. Поэтому представление на диагонали содержит уже известный предыдущий токен, а не цель, которую модель предсказывает. Сдвиг и маска вместе сохраняют авторегрессионную зависимость.

Пусть тензор масштабированных оценок равен

S=QKdk.S=\frac{QK^\top}{\sqrt{d_k}}.

До нормировки добавьте ограничение видимости, затем смешайте значения:

A=softmax(S+M),O=AV.A=\operatorname{softmax}(S+M),\qquad O=AV.

Для этого примера

A[1000.9991510.00084900.4458080.1083830.445808].A\approx \begin{bmatrix} 1&0&0\\ 0.999151&0.000849&0\\ 0.445808&0.108383&0.445808 \end{bmatrix}.

Вероятность внимания к каждому закрытому ключу строго равна нулю. Сумма вероятностей по каждому доступному префиксу равна единице:

j=0iAbij=1.\sum_{j=0}^{i}A_{bij}=1.

Получаются строки

O[332.9983032.9949080.5541920.770959].O\approx \begin{bmatrix} 3&-3\\ 2.998303&-2.994908\\ 0.554192&0.770959 \end{bmatrix}.

Нельзя сначала применить обычный softmax ко всем ключам, а затем обнулить вероятности будущих ключей: в знаменателе вероятностей доступных ключей по-прежнему будут учтены закрытые ключи. Большое по модулю конечное отрицательное число можно использовать как приближение идеальной маски, но математически оно не равно -\infty.

Не путайте ограничение видимости с позиционной информацией

  • SS — тензор масштабированных оценок соответствия запросов и ключей до маскирования.
  • MM — аддитивная маска видимости.
  • ii обозначает позицию запроса, а jj — позицию ключа.
  • AA содержит вероятности внимания после исключения будущих ключей.
  • QQ, KK и VV сохраняют роли запросов, ключей и значений из главы 27.
  • OO содержит по одной взвешенной смеси доступных строк значений для каждой позиции запроса.
  • BB — размер пакета, TT — число токенов, dkd_k — ширина запросов и ключей, а dvd_v — ширина значений.

Полное правило для форм остаётся таким:

Q,KB×T×dk,VB×T×dv,M({})T×T,S,AB×T×T,OB×T×dv.Q,K\in\mathbb{R}^{B\times T\times d_k},\quad V\in\mathbb{R}^{B\times T\times d_v},\quad M\in\left(\mathbb{R}\cup\{-\infty\}\right)^{T\times T},\quad S,A\in\mathbb{R}^{B\times T\times T},\quad O\in\mathbb{R}^{B\times T\times d_v}.

Для доступной ячейки нормировка строки имеет вид

Abij=exp(Sbij)r=0iexp(Sbir),ji,A_{bij}= \frac{\exp(S_{bij})}{\sum_{r=0}^{i}\exp(S_{bir})}, \qquad j\le i,

а для закрытой ячейки

Abij=0,j>i.A_{bij}=0,\qquad j>i.

Это правило задаёт видимость, но не положение. Чтобы различать порядок, строкам нужен отдельный абсолютный или относительный позиционный сигнал. Маски заполнения, переменная длина последовательности, несколько голов, выходная проекция и кеш ключей и значений — тоже отдельные задачи.

От рекуррентного состояния префикса к явной маске декодера

Грейвс, Generating Sequences With Recurrent Neural Networks обучает модель предсказывать следующий элемент, последовательно обрабатывая элементы последовательности. При генерации каждый выбранный элемент становится следующим рекуррентным входом, поэтому будущих элементов ещё не существует. Граница префикса возникает из последовательной рекуррентной обработки, где состояние приходится обновлять шаг за шагом. Это утверждение относится к генерации текста в статье, а не к отдельной модели синтеза рукописного текста с дополнительным условным входом.

Васвани и соавторы, Attention Is All You Need объединяют запросы в матрицы и маскируют самовнимание декодера так, чтобы строка не могла обратиться к последующим позициям. Недопустимым оценкам до softmax присваивается -\infty; вместе с эмбеддингами выходной последовательности, сдвинутыми на одну позицию, это означает, что строка ii зависит только от известных выходов до позиции ii.

Поэтому при обучении строки внимания для известных целевых позиций можно вычислять вместе, не позволяя более ранней строке использовать более позднюю цель. При этом обычная авторегрессионная генерация по-прежнему добавляет по одному токену. Декодерный Transformer применяет эту каузальную границу в каждом слое самовнимания. Маска задаёт границу видимости, но не кодирует положение.

Оставьте маску доступной для проверки, а записанные значения — конечными

causal_additive_mask строит обычный тензор формы [T,T][T,T]. В доступных ячейках находится 00, а в будущих — -\infty:

Постройте доступную для проверки нижнетреугольную маску с разрешённой диагональю rust/crates/llm-from-scratch/src/attention/causal_mask.rs#causal-mask-construction
/// Builds an additive square mask with zero for `key <= query` and negative
/// infinity for future keys.
pub fn causal_additive_mask(tokens: usize) -> Result<Tensor, CausalMaskingError> {
    let elements = tokens
        .checked_mul(tokens)
        .ok_or(CausalMaskingError::MaskTensor(TensorError::ShapeOverflow))?;
    let mut values = Vec::new();
    values
        .try_reserve_exact(elements)
        .map_err(|_| CausalMaskingError::MaskAllocationFailed { elements })?;
    for query in 0..tokens {
        for key in 0..tokens {
            values.push(if key <= query { 0.0 } else { f64::NEG_INFINITY });
        }
    }
    Tensor::from_vec(vec![tokens, tokens], values).map_err(CausalMaskingError::MaskTensor)
}

Дифференцируемый TensorValue отклоняет неконечные данные листовых тензоров. Операция causal_softmax реализует ту же математическую границу, не записывая -\infty: она читает только jij\le i, вычитает максимум доступного префикса, нормирует доступные ячейки и записывает точное значение с плавающей точкой +0.0+0.0 во все закрытые ячейки.

Нормируйте доступные префиксы, сохраняя записанные значения для автоматического дифференцирования конечными rust/crates/llm-from-scratch/src/autograd/model_ops.rs#causal-softmax-forward
fn causal_softmax_forward(input: &Tensor) -> Result<Tensor, TensorAutodiffError> {
    if input.rank() < 2 {
        return Err(ModelOpError::CausalSoftmaxRank { rank: input.rank() }.into());
    }
    let queries = input.shape()[input.rank() - 2];
    let keys = input.shape()[input.rank() - 1];
    if queries != keys {
        return Err(ModelOpError::CausalSoftmaxNonSquare { queries, keys }.into());
    }
    if queries == 0 {
        return Err(ModelOpError::CausalSoftmaxEmptyTokens.into());
    }

    let mut probabilities = zeros(input.shape())?;
    let grids = input.len() / (queries * keys);
    for grid in 0..grids {
        for query in 0..queries {
            let row_start = (grid * queries + query) * keys;
            let allowed = &input.as_slice()[row_start..=row_start + query];
            let maximum = allowed.iter().copied().fold(f64::NEG_INFINITY, f64::max);
            let mut exponential_tail = 0.0;
            let mut skipped_one_maximum = false;
            for &score in allowed {
                let shifted = score - maximum;
                if shifted == 0.0 && !skipped_one_maximum {
                    skipped_one_maximum = true;
                } else {
                    exponential_tail += shifted.exp();
                }
            }
            debug_assert!(skipped_one_maximum);
            let denominator = 1.0 + exponential_tail;
            for (key, &score) in allowed.iter().enumerate() {
                let probability = (score - maximum).exp() / denominator;
                probabilities.as_mut_slice()[row_start + key] =
                    if probability == 0.0 { 0.0 } else { probability };
            }
        }
    }
    Ok(probabilities)
}

Операция принимает тензоры оценок ранга не меньше двух, у которых размеры последних двух осей равны. Поэтому она поддерживает форму [B,H,T,T][B,H,T,T], которая понадобится далее, не смешивая ведущие оси. Операция отклоняет ранг меньше двух, разные размеры последних осей, пустую ось токенов и уже освобождённый операнд.

causal_scaled_dot_product_self_attention повторно использует построение оценок из главы 27, применяет causal_softmax и умножает результат на VV:

Соберите проверяемое каузальное самовнимание на основе масштабированного скалярного произведения rust/crates/llm-from-scratch/src/attention/causal_mask.rs#causal-self-attention-forward
/// Inspectable evidence from one causally masked attention head.
#[derive(Clone, Debug)]
pub struct CausalSelfAttentionForward {
    raw_scores: TensorValue,
    scaled_scores: TensorValue,
    additive_mask: Tensor,
    weights: TensorValue,
    output: TensorValue,
    scale: f64,
    key_width: usize,
    value_width: usize,
}

impl CausalSelfAttentionForward {
    pub fn raw_scores(&self) -> &TensorValue {
        &self.raw_scores
    }

    pub fn dot_products(&self) -> &TensorValue {
        &self.raw_scores
    }

    pub fn scaled_scores(&self) -> &TensorValue {
        &self.scaled_scores
    }

    /// The plain additive mask. It is intentionally not a tape value because
    /// its blocked cells contain negative infinity.
    pub const fn additive_mask(&self) -> &Tensor {
        &self.additive_mask
    }

    pub fn weights(&self) -> &TensorValue {
        &self.weights
    }

    pub fn probabilities(&self) -> &TensorValue {
        &self.weights
    }

    pub fn output(&self) -> &TensorValue {
        &self.output
    }

    pub const fn scale(&self) -> f64 {
        self.scale
    }

    pub const fn key_width(&self) -> usize {
        self.key_width
    }

    pub const fn value_width(&self) -> usize {
        self.value_width
    }

    pub fn into_output(self) -> TensorValue {
        self.output
    }
}

/// Computes one scaled self-attention head with an inclusive-prefix mask.
pub fn causal_scaled_dot_product_self_attention(
    query: &TensorValue,
    key: &TensorValue,
    value: &TensorValue,
) -> Result<CausalSelfAttentionForward, CausalMaskingError> {
    let prepared = scaled_self_attention_scores(query, key, value)?;
    let tokens = query.shape()[1];
    let additive_mask = causal_additive_mask(tokens)?;
    let weights = prepared
        .scaled_scores
        .causal_softmax()
        .map_err(autodiff_error(CausalMaskingStage::MaskedSoftmax))?;
    let output = weights
        .matmul(value)
        .map_err(autodiff_error(CausalMaskingStage::ValueMixture))?;

    Ok(CausalSelfAttentionForward {
        raw_scores: prepared.raw_scores,
        scaled_scores: prepared.scaled_scores,
        additive_mask,
        weights,
        output,
        scale: prepared.scale,
        key_width: prepared.key_width,
        value_width: prepared.value_width,
    })
}

Для доступной оценки обратный проход подчиняется формуле

Sˉbij=Abij(Aˉbijr=0iAˉbirAbir),ji,\bar S_{bij}=A_{bij} \left(\bar A_{bij}-\sum_{r=0}^{i}\bar A_{bir}A_{bir}\right) ,\qquad j\le i,

а закрытая оценка получает

Sˉbij=0,j>i.\bar S_{bij}=0,\qquad j>i.

Все шесть координат каждого из тензоров QQ, KK и VV согласуются с центральными разностями при шаге 10610^{-6} и допуске 4×1064\times10^{-6}. Среди проверенных граничных случаев также есть один токен, пустые пакеты, тензоры оценок ранга два и четыре, независимые ведущие оси, очень большие закрытые оценки, типизированные ошибки, операнды из освобождённой ленты и побитное совпадение при повторном запуске.

Исполняемый пример выводит проверенные данные: маску, вероятности, выходы, результат проверки неизменности префикса, градиенты и граничные случаи:

Выведите пример каузального маскирования и проверенные граничные случаи rust/demos/ch28-causal-masking/src/main.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let evidence = ch28_causal_masking::learner_evidence()?;
    print!("{}", ch28_causal_masking::render_report(&evidence));
    Ok(())
}

Выполните cargo run --quiet --locked -p ch28-causal-masking, чтобы увидеть полный результат примера.

Проследите путь внимания по нижнему треугольнику

Проследите каузальную границу в каждой строке внимания

Проследите путь строк запросов, ключей и значений через нижнетреугольное ограничение видимости, затем сравните исходные выходы с выходами после замены суффикса и изучите градиенты.

  • Доступно: сплошная рамка
  • Закрыто: штриховая рамка
  • Разрешённая диагональ: двойная рамка
  • Побитно совпадает с исходным
  • Изменилось

Проследите один расчёт по нижнему треугольнику

В каждой строке доступны диагональная ячейка и все предыдущие столбцы ключей; различия обозначены и рамкой, и текстом.

Начните со строк запросов, ключей и значений
  1. Строки запросов: какую часть префикса может использовать эта позиция?

    QQ

    q0q_0 0.0000000.0000003.0000003.000000
    q1q_1 2.0000002.0000001.000000-1.000000
    q2q_2 1.0000001.0000001.0000001.000000

    Форма: [1,3,2][1,3,2]

  2. Строки ключей: с какими доступными позициями можно сопоставить запрос?

    KK

    k0k_0 3.0000003.0000000.0000000.000000
    k1k_1 1.000000-1.0000002.0000002.000000
    k2k_2 2.0000002.0000001.0000001.000000

    Форма: [1,3,2][1,3,2]

  3. Строки значений: какое содержимое могут внести видимые позиции?

    VV

    v0v_0 3.0000003.0000003.000000-3.000000
    v1v_1 1.0000001.0000003.0000003.000000
    v2v_2 2.000000-2.0000004.0000004.000000

    Форма: [1,3,2][1,3,2]

Mij={0jij>iM_{ij}=\begin{cases}0&j\le i\\-\infty&j>i\end{cases}

Отметьте нижний треугольник вместе с диагональю

MM

Строки запросов и столбцы ключей · Значение аддитивной маски
qi\kjq_i\backslash k_j k0k_0k1k_1k2k_2
q0q_0 0.0000000.000000 Диагональ -\infty Закрыто -\infty Закрыто
q1q_1 0.0000000.000000 Доступно 0.0000000.000000 Диагональ -\infty Закрыто
q2q_2 0.0000000.000000 Доступно 0.0000000.000000 Доступно 0.0000000.000000 Диагональ
Исключите будущие оценки до нормировки

S+MS+M

Доступность по префиксу
qi\kjq_i\backslash k_j k0k_0k1k_1k2k_2
q0q_0 0.0000000.000000 Диагональ -\infty Закрыто -\infty Закрыто
q1q_1 4.2426414.242641 Доступно 2.828427-2.828427 Диагональ -\infty Закрыто
q2q_2 2.1213202.121320 Доступно 0.7071070.707107 Доступно 2.1213202.121320 Диагональ
Нормируйте каждый доступный префикс

A=softmax(S+M)A=\operatorname{softmax}(S+M)

Сумма вероятностей по доступному префиксу
qi\kjq_i\backslash k_j k0k_0k1k_1k2k_2
q0q_0 1.0000001.000000 Диагональ 0.0000000.000000 Закрыто 0.0000000.000000 Закрыто
q1q_1 0.9991510.999151 Доступно 0.0008490.000849 Диагональ 0.0000000.000000 Закрыто
q2q_2 0.4458080.445808 Доступно 0.1083830.108383 Доступно 0.4458080.445808 Диагональ
  • q0q_0: 1.0000001.000000
  • q1q_1: 1.0000001.000000
  • q2q_2: 1.0000001.000000
Смешайте только доступные строки значений
qiq_i Слагаемые после умножения на веса Строка выхода
q0q_0 [3.000000,3.000000][3.000000,-3.000000][0.000000,0.000000][0.000000,0.000000][0.000000,0.000000][0.000000,0.000000] [3.000000,3.000000][3.000000,-3.000000]
q1q_1 [2.997454,2.997454][2.997454,-2.997454][0.000849,0.002546][0.000849,0.002546][0.000000,0.000000][0.000000,0.000000] [2.998303,2.994908][2.998303,-2.994908]
q2q_2 [1.337425,1.337425][1.337425,-1.337425][0.108383,0.325150][0.108383,0.325150][0.891617,1.783233][-0.891617,1.783233] [0.554192,0.770959][0.554192,0.770959]

Измените суффикс и проверьте предыдущие выходы

Изменяются только последний ключ и последнее значение; первые две строки выхода побитно совпадают с исходными.

Замените последний ключ и последнее значение

k2k_2

До замены: [2.000000,1.000000][2.000000,1.000000] После замены: [2.000000,4.000000][-2.000000,4.000000]

v2v_2

До замены: [2.000000,4.000000][-2.000000,4.000000] После замены: [5.000000,1.000000][5.000000,-1.000000]

ii Исходный выход После замены суффикса Результат для префикса
00 [3.000000,3.000000][3.000000,-3.000000] [3.000000,3.000000][3.000000,-3.000000] Побитно совпадает с исходным
11 [2.998303,2.994908][2.998303,-2.994908] [2.998303,2.994908][2.998303,-2.994908] Побитно совпадает с исходным
22 [0.554192,0.770959][0.554192,0.770959] [3.287932,1.591834][3.287932,-1.591834] Изменилось

Проверьте обратный проход и граничные случаи

Начальные градиенты по всем выходам и только по префиксу показывают, куда градиент может пройти, а куда — нет.

Начальный градиент по всем выходам: [1.0000000.5000000.2500002.0000001.0000000.750000]\begin{bmatrix}1.000000&-0.500000\\0.250000&2.000000\\-1.000000&0.750000\end{bmatrix}
Qˉ\bar Q [0.0000000.0000000.0275790.0137901.9444241.756510]\begin{bmatrix}0.000000&0.000000\\-0.027579&0.013790\\-1.944424&1.756510\end{bmatrix}
Kˉ\bar K [1.6763431.6556580.1077460.0870621.5685961.568596]\begin{bmatrix}-1.676343&-1.655658\\0.107746&0.087062\\1.568596&1.568596\end{bmatrix}
Vˉ\bar V [0.8039801.8326590.1081710.0829850.4458080.334356]\begin{bmatrix}0.803980&1.832659\\-0.108171&0.082985\\-0.445808&0.334356\end{bmatrix}
Начальный градиент только по префиксу: [1.0000001.0000000.5000002.0000000.0000000.000000]\begin{bmatrix}1.000000&-1.000000\\0.500000&2.000000\\0.000000&0.000000\end{bmatrix}
Qˉ\bar Q [0.0000000.0000000.0263800.0131900.0000000.000000]\begin{bmatrix}0.000000&0.000000\\-0.026380&0.013190\\0.000000&0.000000\end{bmatrix}
Kˉ\bar K [0.0131900.0065950.0131900.0065950.0000000.000000]\begin{bmatrix}-0.013190&0.006595\\0.013190&-0.006595\\0.000000&0.000000\end{bmatrix}
Vˉ\bar V [1.4995760.9983030.0004240.0016970.0000000.000000]\begin{bmatrix}1.499576&0.998303\\0.000424&0.001697\\0.000000&0.000000\end{bmatrix}
Начальный градиент только по префиксу

Для функции потерь только по префиксу изменённый суффикс получает нулевой градиент.

Проверено
Граничный случай с одним токеном

A=[1.000000]A=[1.000000]

O=[5.000000,2.000000]O=[5.000000,-2.000000]

Сохранение форм для пустого пакета

[0,3,3][0,3,3][0,3,2][0,3,2]

Проверено
Проверенные каузальные свойства

Записанные значения для автоматического дифференцирования остаются конечными: Проверено

Вероятности будущих ключей: Строго равны нулю

Предыдущие выходы после замены суффикса: Побитно совпадают с исходными

Отклонённые недопустимые входы
  • Внимание без позиций токенов empty-tokens Отклонено
  • Ранг тензора оценок меньше двух causal-softmax-rank: rank=1 Отклонено
  • Последние две оси тензора оценок имеют разную длину causal-softmax-non-square: queries=2|keys=3 Отклонено
  • Неверный ранг тензора запросов score-input-rank: input=query|rank=2 Отклонено
  • Число токенов в запросах, ключах и значениях различается score-token-mismatch: query=3|key=2|value=3 Отклонено
  • Операнд оценок относится к уже освобождённой ленте операций released-operand: operation=causal-softmax|operand=0 Отклонено

От префикса при рекуррентной генерации к декодеру Transformer

Рекуррентные вычисления неявно ограничивают модель префиксом, а самовнимание декодера Transformer задаёт эту границу явно.

  1. Префикс при рекуррентной генерации

    При рекуррентной генерации существует только уже созданный префикс, а рекуррентное состояние продвигается на одну позицию за шаг.

  2. Явная маска декодера Transformer

    При обучении сдвинутые входы декодера и каузальная маска позволяют вместе обрабатывать известные целевые позиции, не давая более ранним строкам использовать более поздние цели; генерация остаётся последовательной.

Треугольные таблицы показывают, какие оценки сохраняются до нормировки. Сплошная, штриховая и двойная рамки различают доступные, закрытые и диагональные ячейки, не полагаясь только на цвет.

Если изменить только

k2:[2,1][2,4],v2:[2,4][5,1]k_2:[2,1]\to[-2,4],\qquad v_2:[-2,4]\to[5,-1]

то o0o_0 и o1o_1 побитно совпадут с исходными, а

o2[3.287932,1.591834].o'_2\approx[3.287932,-1.591834].

Сначала сделайте прогноз, затем откройте ответы

  1. Запишите множество индексов доступных ключей для строк запросов 00, 11 и 22.
  2. Предскажите, может ли замена только k2k_2 и v2v_2 изменить o0o_0 или o1o_1.
  3. Объясните, почему все три диагональные ячейки доступны, хотя модель не может получить доступ к токену, который нужно предсказать.
  4. Предскажите градиенты запроса и ключа для каузальной головы самовнимания с одним токеном.
  5. Объясните, почему после обнуления будущих вероятностей вслед за обычным softmax сумма строки перестаёт быть равной единице.
  6. Определите, позволяет ли маска параллельно обрабатывать позиции токенов при обучении, когда целевые токены известны, при авторегрессионной генерации, в обоих случаях или ни в одном.
  7. Назовите отдельный механизм, который появится в главе 29, не изменяя нижнетреугольную границу.
Проверьте прогнозы
  1. Множества равны {0}\{0\}, {0,1}\{0,1\} и {0,1,2}\{0,1,2\}.
  2. Ни один из предыдущих выходов не изменится; последний ключ и последнее значение доступны только o2o_2.
  3. Входы декодера сдвинуты на одну целевую позицию, поэтому на диагонали находится уже известный предыдущий токен, а не предсказываемая цель.
  4. Единственная вероятность постоянно равна 11, поэтому оба градиента строго равны нулю.
  5. Обнуление после softmax удаляет часть вероятностной массы, но не пересчитывает знаменатель по доступному префиксу.
  6. При обучении строки для известных целевых позиций можно вычислять вместе; при генерации токены по-прежнему добавляются по одному.
  7. Относительная информация о позициях должна задавать порядок, сохраняя прежнее каузальное правило видимости.

Сохраняйте границу префикса по мере роста декодера

Теперь накопительный декодер создаёт выход в позиции ii, используя только ключи и значения до позиции ii включительно. Если функция потерь зависит лишь от первых двух позиций, градиент по будущему суффиксу строго равен нулю:

L1q2=L1k2=L1v2=0.\frac{\partial L_{\le1}}{\partial q_2} =\frac{\partial L_{\le1}}{\partial k_2} =\frac{\partial L_{\le1}}{\partial v_2}=0.

Именно такая информационная граница нужна авторегрессионному декодеру. В главе 29 появится относительная информация о позициях, но граница не расширится.