← Все главы

25 · Версия материала 5

Нормализуйте масштаб, не вычитая среднее

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

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

Возьмите x=[3,4]x=[3,4], обучаемый коэффициент масштаба g=[1.5,0.5]g=[1.5,0.5] и ε=105\varepsilon=10^{-5}. Прежде чем запускать пример, вычислите среднее квадратов:

32+422=12.5.\frac{3^2+4^2}{2}=12.5.

Величина, обратная RMS, приблизительно равна 0.2828430.282843. Предскажите нормализованный вектор до применения коэффициента, а затем покоординатно умножьте его на gg. Пример должен дать примерно [0.848528,1.131370][0.848528,1.131370] до применения коэффициента и [1.272792,0.565685][1.272792,0.565685] после него.

Теперь умножьте вход на положительный коэффициент 1010. Чтобы проверить инвариантность к масштабу, рассматривайте x^\hat{x} — вектор после масштабирования по RMS, но до применения обучаемого коэффициента gg. Итоговый выход после применения gg в это сравнение не входит. Сначала предскажите x^\hat{x} при ε=0\varepsilon=0, затем — при ε=105\varepsilon=10^{-5}. Повторите то же сравнение для x=[0.0003,0.0004]x=[0.0003,0.0004], по-прежнему не применяя обучаемый коэффициент; среднее квадратов этого вектора меньше стабилизирующей добавки.

Нормализуйте последнюю ось признаков

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

RMSNorm(x)=gx1dixi2+ε\operatorname{RMSNorm}(x)=g\odot\frac{x}{\sqrt{\frac{1}{d}\sum_i x_i^2+\varepsilon}}

Одна и та же статистика используется для всех dd координат этого вектора. Затем обучаемый коэффициент отдельно масштабирует каждый признак. Среднее по признакам не вычитается, а статистики одного примера не зависят от других.

Для положительного скаляра aa и ненулевого вектора идеальная формула с нулевым эпсилоном удовлетворяет равенству

RMSNorm0(ax)=RMSNorm0(x).\operatorname{RMSNorm}_{0}(ax)=\operatorname{RMSNorm}_{0}(x).

При конечном эпсилоне знаменатель становится равен a2RMS(x)2+ε\sqrt{a^2\operatorname{RMS}(x)^2+\varepsilon}: эпсилон не растёт вместе с сигналом. Среднее квадратов нормализованного вектора равно

mean(x^2)=RMS(x)2RMS(x)2+ε.\operatorname{mean}(\hat{x}^2)= \frac{\operatorname{RMS}(x)^2} {\operatorname{RMS}(x)^2+\varepsilon}.

Когда сигнал значительно больше эпсилона, это значение приближается к 11; когда эпсилон преобладает — к 00. Поэтому на практике RMSNorm приближённо инвариантна к масштабу вдали от нуля, но не обладает точной инвариантностью для всех входов.

Не смешивайте масштаб, коэффициент и оси

  • xx — один вектор признаков, взятый с последней оси входа.
  • dd — ненулевая ширина этой оси, а ii выбирает одну координату.
  • xix_i — координата ii, а ixi2/d\sum_i x_i^2/d — среднее квадратов координат вектора xx.
  • ε0\varepsilon\ge0 стабилизирует вычисление обратного квадратного корня.
  • x^\hat{x} — вектор после масштабирования по RMS, но до обучаемого покоординатного масштабирования.
  • gdg\in\mathbb{R}^{d} — обучаемый вектор коэффициентов масштаба, а \odot обозначает покоординатное умножение.
  • Полная форма выхода совпадает с формой входа.

RMSNorm не приводит модуль каждой координаты к 11. Она управляет совокупным среднеквадратичным масштабом вектора. Кроме того, RMSNorm не центрирует вектор, не обрезает значения и не смешивает примеры мини-батча.

От статистик мини-пакета к языковым моделям с предварительной RMSNorm

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

Первый ранний первоисточник — Иоффе и Сегеди, Batch Normalization. Иоффе и Сегеди включают нормализацию в архитектуру и вычисляют её статистики отдельно для каждого мини-батча при обучении.

Второй ранний источник — Ба, Кирос и Хинтон, Layer Normalization. Ба, Кирос и Хинтон вычисляют среднее и дисперсию по суммарным входным сигналам нейронов слоя для одного обучающего примера, устраняя зависимость между примерами.

RMSNorm отказывается от вычитания среднего и сохраняет масштабирование вектора признаков по его среднеквадратичному значению; позднее в LLaMA RMSNorm применили перед каждым подслоем Transformer.

Первый более поздний источник — Чжан и Зеннрих, Root Mean Square Layer Normalization. Чжан и Зеннрих исключают среднее из статистик, нормализуют по среднеквадратичному значению и сохраняют инвариантность формулы без эпсилона к положительному масштабированию, отказавшись от инвариантности к центрированию. В формуле статьи эпсилона нет. В этой реализации положительный эпсилон добавлен, чтобы нулевые и очень малые векторы оставались конечными; поведение вблизи нуля следует из этого изменённого знаменателя.

Второй более поздний источник — Туврон и соавторы, LLaMA. Туврон и соавторы нормализуют вход каждого подслоя Transformer и используют для этого RMSNorm.

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

Исполняемое сравнение фиксирует опорный вектор [1,3][1,3] и меняет только другой пример в том же батче. Выход BatchNorm для опорного вектора меняется; LayerNorm и RMSNorm обрабатывают каждый пример независимо. LayerNorm центрирует опорный вектор, а RMSNorm сохраняет ненулевое среднее выхода:

Сравните оси нормализации и поведение при центрировании rust/demos/ch25-rmsnorm/src/lib.rs#historical-normalization-contrast
fn batch_normalize_anchor(anchor: &[f64], companion: &[f64], epsilon: f64) -> Vec<f64> {
    anchor
        .iter()
        .zip(companion)
        .map(|(&anchor, &companion)| {
            let mean = (anchor + companion) / 2.0;
            let variance = ((anchor - mean).powi(2) + (companion - mean).powi(2)) / 2.0;
            (anchor - mean) / (variance + epsilon).sqrt()
        })
        .collect()
}

fn layer_normalize(values: &[f64], epsilon: f64) -> Vec<f64> {
    let mean = values.iter().sum::<f64>() / values.len() as f64;
    let variance = values
        .iter()
        .map(|value| (value - mean).powi(2))
        .sum::<f64>()
        / values.len() as f64;
    values
        .iter()
        .map(|value| (value - mean) / (variance + epsilon).sqrt())
        .collect()
}

fn historical_evidence() -> Result<HistoryEvidence, FixtureError> {
    let anchor = [1.0, 3.0];
    let batch_anchor_a = batch_normalize_anchor(&anchor, &[5.0, 7.0], EPSILON);
    let batch_anchor_b = batch_normalize_anchor(&anchor, &anchor, EPSILON);
    let layer_norm = layer_normalize(&anchor, EPSILON);
    let rms_norm = normalized(&anchor, IDEAL_EPSILON)?;
    let rms_mean = rms_norm.iter().sum::<f64>() / rms_norm.len() as f64;
    Ok(HistoryEvidence {
        batch_anchor_a,
        batch_anchor_b,
        layer_norm,
        rms_norm,
        rms_mean,
    })
}

Соберите RMSNorm из уже реализованных операций ленты

RmsNorm владеет одним одномерным NamedParameter и не имеет смещения. Сначала проверяются эпсилон, имя и ширина коэффициента масштаба, ранг входа и ширина последней оси. Затем прямой проход вычисляет RMSNorm как последовательность уже реализованных операций: умножения, среднего по последней оси с сохранением размерности, добавления скаляра, логарифма, экспоненты, согласования форм (broadcasting) и ещё одного умножения. Поэтому и вход, и коэффициент остаются связаны с общей лентой обратного автоматического дифференцирования:

Нормализуйте последнюю ось с помощью уже реализованных дифференцируемых операций rust/crates/llm-from-scratch/src/nn/rmsnorm.rs#rmsnorm-layer
/// Inspectable tensors from one composed RMSNorm forward pass.
#[derive(Clone, Debug)]
pub struct RmsNormForward {
    mean_square: TensorValue,
    inverse_rms: TensorValue,
    normalized: TensorValue,
    output: TensorValue,
}

impl RmsNormForward {
    pub fn mean_square(&self) -> &TensorValue {
        &self.mean_square
    }

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

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

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

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

/// One learned gain applied after final-axis root-mean-square rescaling.
#[derive(Clone, Debug)]
pub struct RmsNorm {
    epsilon: f64,
    feature_width: usize,
    parameters: NamedParameters,
}

impl RmsNorm {
    /// Creates a gain vector initialized to one.
    pub fn new(
        gain_name: impl Into<String>,
        feature_width: usize,
        epsilon: f64,
    ) -> Result<Self, RmsNormError> {
        validate_epsilon(epsilon)?;
        if feature_width == 0 {
            return Err(RmsNormError::EmptyFeatureWidth);
        }
        let gain_name = gain_name.into();
        validate_name(&gain_name)?;
        let mut values = Vec::new();
        values.try_reserve_exact(feature_width).map_err(|_| {
            RmsNormError::GainAllocationFailed {
                elements: feature_width,
            }
        })?;
        values.resize(feature_width, 1.0);
        let gain = NamedParameter::from_tensor(
            gain_name,
            Tensor::from_vec(vec![feature_width], values).map_err(InitializationError::from)?,
        )?;
        Self::from_gain(gain, epsilon)
    }

    /// Builds the layer from one externally named rank-one trainable gain.
    pub fn from_gain(gain: NamedParameter, epsilon: f64) -> Result<Self, RmsNormError> {
        validate_epsilon(epsilon)?;
        let shape = gain.tensor().shape();
        if shape.len() != 1 {
            return Err(RmsNormError::GainRank { shape });
        }
        let feature_width = shape[0];
        if feature_width == 0 {
            return Err(RmsNormError::EmptyFeatureWidth);
        }
        Ok(Self {
            epsilon,
            feature_width,
            parameters: NamedParameters::try_new(vec![gain])?,
        })
    }

    /// Normalizes the final feature axis and returns only the scaled output.
    pub fn forward(&self, input: &TensorValue) -> Result<TensorValue, RmsNormError> {
        self.forward_with_intermediates(input)
            .map(RmsNormForward::into_output)
    }

    /// Normalizes the final feature axis and preserves each teaching value.
    pub fn forward_with_intermediates(
        &self,
        input: &TensorValue,
    ) -> Result<RmsNormForward, RmsNormError> {
        let shape = input.shape();
        let Some(&actual_width) = shape.last() else {
            return Err(RmsNormError::InputRankZero);
        };
        if actual_width != self.feature_width {
            return Err(RmsNormError::InputWidthMismatch {
                expected: self.feature_width,
                actual: actual_width,
            });
        }
        let feature_axis = shape.len() - 1;
        let squared = input
            .mul(input)
            .map_err(autodiff_error(RmsNormStage::Square))?;
        let mean_square = squared
            .mean_axis(feature_axis, true)
            .map_err(autodiff_error(RmsNormStage::MeanSquare))?;
        if self.epsilon == 0.0 {
            for (row, value) in mean_square.value().as_slice().iter().enumerate() {
                if *value == 0.0 {
                    return Err(RmsNormError::ZeroEnergyRow { row });
                }
            }
        }
        let epsilon = scalar_constant(self.epsilon, RmsNormStage::EpsilonConstant)?;
        let stabilized = mean_square
            .add(&epsilon)
            .map_err(autodiff_error(RmsNormStage::Stabilize))?;
        let log_mean_square = stabilized
            .log()
            .map_err(autodiff_error(RmsNormStage::LogMeanSquare))?;
        let exponent = scalar_constant(-0.5, RmsNormStage::ExponentConstant)?;
        let scaled_log = log_mean_square
            .mul(&exponent)
            .map_err(autodiff_error(RmsNormStage::ScaleLog))?;
        let inverse_rms = scaled_log
            .exp()
            .map_err(autodiff_error(RmsNormStage::ReciprocalRoot))?;
        let normalized = input
            .mul(&inverse_rms)
            .map_err(autodiff_error(RmsNormStage::Normalize))?;
        let output = normalized
            .mul(self.gain().tensor())
            .map_err(autodiff_error(RmsNormStage::ApplyGain))?;

        Ok(RmsNormForward {
            mean_square,
            inverse_rms,
            normalized,
            output,
        })
    }

    pub fn gain(&self) -> &NamedParameter {
        &self.parameters.as_slice()[0]
    }

    pub fn parameters(&self) -> &[NamedParameter] {
        self.parameters.as_slice()
    }

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

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

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

Явно опишите ошибки конфигурации и численные границы rust/crates/llm-from-scratch/src/nn/rmsnorm.rs#rmsnorm-errors
/// A rejected RMSNorm configuration, input, or delegated tape operation.
#[derive(Clone, Debug, PartialEq)]
pub enum RmsNormError {
    InvalidEpsilon {
        value: f64,
    },
    EmptyFeatureWidth,
    GainRank {
        shape: Vec<usize>,
    },
    GainAllocationFailed {
        elements: usize,
    },
    InputRankZero,
    InputWidthMismatch {
        expected: usize,
        actual: usize,
    },
    ZeroEnergyRow {
        row: usize,
    },
    Initialization(InitializationError),
    Autodiff {
        stage: RmsNormStage,
        source: TensorAutodiffError,
    },
}

impl fmt::Display for RmsNormError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidEpsilon { value } => write!(
                formatter,
                "RMSNorm epsilon must be finite and nonnegative, got {value:?}"
            ),
            Self::EmptyFeatureWidth => {
                formatter.write_str("RMSNorm feature width must be greater than zero")
            }
            Self::GainRank { shape } => {
                write!(
                    formatter,
                    "RMSNorm gain must have rank 1, got shape {shape:?}"
                )
            }
            Self::GainAllocationFailed { elements } => write!(
                formatter,
                "could not reserve storage for {elements} RMSNorm gain values"
            ),
            Self::InputRankZero => formatter.write_str("RMSNorm input must have at least one axis"),
            Self::InputWidthMismatch { expected, actual } => write!(
                formatter,
                "RMSNorm input feature width must be {expected}, got {actual}"
            ),
            Self::ZeroEnergyRow { row } => write!(
                formatter,
                "RMSNorm epsilon is zero but feature row {row} has zero mean square"
            ),
            Self::Initialization(source) => source.fmt(formatter),
            Self::Autodiff { stage, source } => {
                write!(formatter, "RMSNorm {stage}: {source}")
            }
        }
    }
}

impl Error for RmsNormError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::Initialization(source) => Some(source),
            Self::Autodiff { source, .. } => Some(source),
            _ => None,
        }
    }
}

impl From<InitializationError> for RmsNormError {
    fn from(error: InitializationError) -> Self {
        Self::Initialization(error)
    }
}

В основном примере обратный проход получает yˉ=[1,2]\bar y=[1,-2]. Результат — xˉ[0.407293,0.305470]\bar x\approx[0.407293,-0.305470] и gˉ[0.848528,2.262741]\bar g\approx[0.848528,-2.262741]. Пример также проверяет независимость строк для входа формы [2,2][2,2] и пустой внешний батч. Параметр с фиксированным именем decoder.block.0.attention_norm.gain входит только во множество без затухания AdamWParameterGroups. Такая группировка относится к политике оптимизатора, а не к формуле RMSNorm:

Соберите данные прямого и обратного проходов, поведения эпсилона, форм и оптимизатора rust/demos/ch25-rmsnorm/src/lib.rs#rmsnorm-fixture
pub fn learner_evidence() -> Result<LearnerEvidence, FixtureError> {
    let primary = primary_once()?;
    let replay = primary_once()?;
    let ideal_scale = scale_evidence("ideal", &INPUT_VALUES, IDEAL_EPSILON)?;
    let production_scale = scale_evidence("production", &INPUT_VALUES, EPSILON)?;
    let near_zero_scale = scale_evidence("near-zero", &TINY_VALUES, EPSILON)?;

    let zero_layer = layer(&[1.0, 1.0], EPSILON)?;
    let zero_output = zero_layer
        .forward(&TensorValue::constant(tensor(&[2], &[0.0, 0.0]))?)?
        .value()
        .as_slice()
        .to_vec();
    let batch_output = layer(&GAIN_VALUES, EPSILON)?
        .forward(&TensorValue::constant(tensor(
            &[2, 2],
            &[3.0, 4.0, 0.0, 5.0],
        ))?)?
        .value_snapshot();

    let groups = AdamWParameterGroups::new([] as [&str; 0], [GAIN_NAME])?;
    let no_decay = groups.decayed_names().count() == 0
        && groups.excluded_names().collect::<Vec<_>>() == [GAIN_NAME];
    let (input_checks, gain_checks, gradcheck_passed) = gradient_evidence(&primary)?;

    Ok(LearnerEvidence {
        replay_bitwise: primary_matches(&primary, &replay),
        primary,
        ideal_scale,
        production_scale,
        near_zero_scale,
        zero_output,
        batch_output,
        history: historical_evidence()?,
        errors: error_evidence()?,
        input_checks,
        gain_checks,
        gradcheck_passed,
        no_decay,
    })
}

Центральные разности проверяют обе координаты входа и обе координаты коэффициента масштаба с шагом 10610^{-6} и допуском 2×1062\times10^{-6}:

Проверьте оба дифференцируемых входа численно rust/demos/ch25-rmsnorm/src/lib.rs#rmsnorm-gradcheck
fn gradient_evidence(primary: &PrimaryEvidence) -> Result<(usize, usize, bool), FixtureError> {
    let upstream = &primary.upstream;
    let input_report = sampled_tensor_gradient_check(
        &mut primary.input.clone(),
        &primary.input_gradient.view(),
        STEP,
        TOLERANCE,
        INPUT_VALUES.len(),
        |probe| {
            dot_output(
                &layer(&GAIN_VALUES, EPSILON).expect("fixture layer is valid"),
                probe,
                upstream,
            )
        },
    )?;
    let gain_report = sampled_tensor_gradient_check(
        &mut primary.gain.clone(),
        &primary.gain_gradient.view(),
        STEP,
        TOLERANCE,
        GAIN_VALUES.len(),
        |probe| {
            dot_output(
                &RmsNorm::from_gain(
                    NamedParameter::from_tensor(GAIN_NAME, probe.clone())
                        .expect("probe gain is finite"),
                    EPSILON,
                )
                .expect("probe gain shape is valid"),
                &primary.input,
                upstream,
            )
        },
    )?;
    Ok((
        input_report.checks.len(),
        gain_report.checks.len(),
        input_report.passed && gain_report.passed,
    ))
}
Запустите проверенный пример RMSNorm rust/demos/ch25-rmsnorm/src/main.rs
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let evidence = ch25_rmsnorm::learner_evidence()?;
    print!("{}", ch25_rmsnorm::render_report(&evidence));
    Ok(())
}

Выполните cargo run --quiet --locked -p ch25-rmsnorm, чтобы вывести те же проверенные значения прямого и обратного проходов, масштабирования и граничных случаев.

Сравните масштабирование по обе стороны границы влияния эпсилона

На схеме собраны проверенные значения основного примера, градиенты, сравнения масштабов, поведение нулевого входа и батча, результаты исторических методов и отклонённые запросы:

Проследите один вектор признаков через RMSNorm

Проследите точные значения из примера на Rust: от входа через масштабирование по RMS и обучаемый коэффициент до результатов сравнения масштабов, исторических методов, градиентов и отклонённых граничных случаев.

  • Сплошная рамка — вход
  • Пунктирная рамка — масштабирование по RMS
  • Двойная рамка — применение коэффициента

Измените масштаб одного вектора признаков последней оси

Вход
  1. x0=3.000000x_{0}=3.000000
  2. x1=4.000000x_{1}=4.000000
Среднее квадратов

m2=[12.500000]m_2=[12.500000]

Величина, обратная RMS

r1=[0.282843]r^{-1}=[0.282843]

ε=0.000010\varepsilon=0.000010

Вектор после масштабирования по RMS
  1. x^0=0.848528\hat x_{0}=0.848528
  2. x^1=1.131370\hat x_{1}=1.131370
Обучаемый коэффициент масштаба
  1. g0=1.500000g_{0}=1.500000
  2. g1=0.500000g_{1}=0.500000
Выход после применения коэффициента
  1. y0=1.272792y_{0}=1.272792
  2. y1=0.565685y_{1}=0.565685

Длины полос служат локальными ориентирами; точные значения указаны числами.

Сравните случаи с нулевым и конечным эпсилоном

Идеальный случай с нулевым эпсилоном

ε=0.000000,a=10.000000\varepsilon=0.000000,\quad a=10.000000

Исходный вектор
x^=[0.848528,1.131371]\hat x=[0.848528,1.131371]
Вход, умноженный на десять
ax^=[0.848528,1.131371]\widehat{ax}=[0.848528,1.131371]
Наибольшая абсолютная разность
Δmax=0.000000000000000222\Delta_{\mathrm{max}}=0.000000000000000222
Обычный вход с рабочим эпсилоном

ε=0.000010,a=10.000000\varepsilon=0.000010,\quad a=10.000000

Исходный вектор
x^=[0.848528,1.131370]\hat x=[0.848528,1.131370]
Вход, умноженный на десять
ax^=[0.848528,1.131371]\widehat{ax}=[0.848528,1.131371]
Наибольшая абсолютная разность
Δmax=0.000000448\Delta_{\mathrm{max}}=0.000000448
Вход, для которого преобладает эпсилон

ε=0.000010,a=10.000000\varepsilon=0.000010,\quad a=10.000000

Исходный вектор
x^=[0.094281,0.125708]\hat x=[0.094281,0.125708]
Вход, умноженный на десять
ax^=[0.632456,0.843274]\widehat{ax}=[0.632456,0.843274]
Наибольшая абсолютная разность
Δmax=0.717566\Delta_{\mathrm{max}}=0.717566

Для обычного входа разность мала, а для входа вблизи нуля хорошо заметна.

Сравните, откуда берётся статистика каждого метода

Опорный вектор сравнивается в двух отдельных батчах: сначала с первым дополнительным примером, затем со вторым.
Выход после применения коэффициента Первый дополнительный пример Второй дополнительный пример
BatchNorm [0.999999,0.999999][-0.999999,-0.999999] [0.000000,0.000000][0.000000,0.000000]
LayerNorm [0.999995,0.999995][-0.999995,0.999995] [0.999995,0.999995][-0.999995,0.999995]
RMSNorm [0.447214,1.341641][0.447214,1.341641] [0.447214,1.341641][0.447214,1.341641]

Среднее выхода RMSNorm: mean(x^)=0.894427\operatorname{mean}(\hat x)=0.894427

Проверьте градиенты, формы и граничные случаи

Результаты обратного прохода

yˉ=[1.000000,2.000000]\bar y=[1.000000,-2.000000]

Градиент по входу
xˉ=[0.407293,0.305470]\bar x=[0.407293,-0.305470]
Градиент коэффициента масштаба
gˉ=[0.848528,2.262741]\bar g=[0.848528,-2.262741]
Параметр масштаба с фиксированным именем

decoder.block.0.attention_norm.gain

g2g\in\mathbb{R}^{2}

Группировка параметров оптимизатора: no_decay=true

Полностью нулевой вход

x=[0.000000,0.000000]x=[0.000000,0.000000]

y=[0.000000,0.000000]y=[0.000000,0.000000]

Принято · finite=true

Независимые строки последней оси

shape(y)=[2,2]\operatorname{shape}(y)=[2,2]

y0,:=[1.272792,0.565685]y_{0,:}=[1.272792,0.565685]

y1,:=[0.000000,0.707106]y_{1,:}=[0.000000,0.707106]

axis=last

Отклонённые граничные случаи
  • Отклонено rank-zero У входа должна быть хотя бы одна ось.
  • Отклонено width-mismatch Ширина последней оси признаков должна совпадать с шириной коэффициента масштаба.
  • Отклонено zero-energy-epsilon-zero При нулевом эпсилоне нельзя нормализовать строку с нулевым средним квадратов.
Проверки из примера на Rust

mean(x^2)=0.999999\operatorname{mean}(\hat x^2)=0.999999

Результаты обратного прохода

input_checks=2

gain_checks=2

τ=0.000002\tau=0.000002

Принято

gradcheck=true

replay=bitwise

Читайте точные значения слева направо: вход, общий коэффициент RMS, масштабированный по RMS вектор, обучаемый коэффициент и итоговый выход. Сплошная, пунктирная и двойная рамки различают три состояния вектора, не полагаясь только на цвет. Затем карточки масштабирования показывают, почему умножение обычного вектора на десять почти не меняет результат с конечным эпсилоном, а такое же действие выводит очень малый вектор далеко из режима, в котором преобладает эпсилон.

Сначала сделайте предсказания

  1. Вычислите среднее квадратов и величину, обратную RMS, для x=[3,4]x=[3,4].
  2. Примените g=[1.5,0.5]g=[1.5,0.5] к нормализованному вектору.
  3. Для ненулевого вектора и ε=0\varepsilon=0 предскажите вектор x^\hat{x} после умножения входа на 1010 и масштабирования по RMS; рассматривайте его до применения обучаемого коэффициента gg.
  4. Объясните, почему положительный ε\varepsilon намного сильнее меняет вектор вблизи нуля.
  5. Предскажите результат для полностью нулевой строки при положительном и нулевом эпсилоне.
  6. Определите, вычитает ли RMSNorm среднее по признакам или смешивает примеры батча.
  7. Предскажите форму выхода и форму градиента коэффициента масштаба для входа [B,T,d][B,T,d].
  8. Объясните, почему исключение из затухания относится к политике оптимизатора, а не к формуле.
  9. Укажите место RMSNorm относительно тождественного и обучаемого путей в остаточном блоке с предварительной нормализацией.
Проверить предсказания
  1. Среднее квадратов равно 12.512.5, а величина, обратная RMS, — примерно 0.2828430.282843.
  2. Выход приблизительно равен [1.272792,0.565685][1.272792,0.565685].
  3. При ε=0\varepsilon=0 положительный множитель, на который умножен вход, сокращается при вычислении x^\hat{x}. Поэтому до применения обучаемого коэффициента gg этот вектор не меняется.
  4. Конечный эпсилон не масштабируется вместе с сигналом и потому преобладает в знаменателе очень малого вектора.
  5. Положительный эпсилон возвращает [0,0][0,0]; при нулевом эпсилоне строка, у которой среднее квадратов равно нулю, отклоняется до логарифма.
  6. Ни то ни другое: используются только признаки последней оси одного примера, а их среднее не вычитается.
  7. Форма выхода — [B,T,d][B,T,d], а форма градиента коэффициента масштаба — [d][d].
  8. Оптимизатор решает, для каких именованных параметров применять затухание; в уравнении RMSNorm этого нет.
  9. Обучаемая ветвь получает выход RMSNorm, а тождественный путь переносит остаточный поток без изменений.

Далее спроецируйте нормализованные признаки в тензоры запросов, ключей и значений

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