10 · Версия материала 5
Согласуйте совместимые формы и агрегируйте значения по заданной оси
Согласуйте вектор смещения с тензором признаков токенов, затем вычислите сумму, среднее и максимум по явно заданным осям с проверкой их допустимости.
Предскажите, как один вектор смещения применяется к двум строкам токенов
В современном декодере у тензора активаций часто последней идёт ось признаков. Начнём с двух позиций токенов, в каждой из которых находятся три значения признаков:
tokens shape [2,3]
[[1, 2, 3],
[4, 5, 6]]
У вектора смещения по признакам нет оси токенов:
bias shape [3]
[10, 20, 30]
Прежде чем смотреть ответ, предскажите форму результата и шесть сумм. Нужно ли
отклонить вектор смещения из-за ранга 1, сначала скопировать его в новый входной
тензор формы [2,3] или сопоставить каждой строке токенов по однозначному
правилу совместимости?
Применим согласование форм (broadcasting), начиная с последних осей:
tokens: [2,3]
bias: [1,3] # отсутствующая начальная ось считается осью размера 1
result: [2,3]
Размер оси признаков совпадает с . На первой согласованной оси единственная входная координата повторно используется для обеих координат оси результата размера . Поэтому координата результата обращается к координате токена и координате вектора смещения . Получаем:
Теперь, прежде чем считать, предскажите три результата агрегирования значений по
оси (редукции): сумму по оси токенов 0, среднее по оси признаков 1 с
сохранённой редуцируемой осью и максимум по оси признаков 1 с удалением этой
оси. Результаты соответственно равны: форма [3] и значения [25,47,69];
форма [2,1] и значения [22,25]; форма [2] и значения [33,36].
Сопоставьте координаты при согласовании форм и редуцируйте одну ось
Согласование форм и вычисление среднего можно записать вместе:
В первом выражении правила формы тензора отделены от скалярной арифметики. — одно значение результата в полной координате . Функция может складывать, умножать или выполнять другое скалярное вычисление. Она получает левое значение из тензора в координате и правое значение из тензора в координате .
Каждое отображение удаляет оси результата, которых не было во входном тензоре, и выбирает нулевую координату на согласованной входной оси размера 1. Остальные координаты сохраняются. В зафиксированном примере:
Во втором выражении координаты вне оси зафиксированы,
обходит элементов этой оси, а их сумма в заданном порядке делится на
. Среднее определено только при ; иначе реализация возвращает
типизированную ошибку пустой оси. Полная координата составляется из
фиксированных координат и текущего . При keep_dim=true выбранная ось
остаётся на прежнем месте в форме результата и получает размер 1. Это не
добавляет слагаемое к среднему и не меняет делитель.
Учтите каждую координату, размер оси и отображение
| Символ | Практический смысл |
|---|---|
| Значение результата в полной координате . | |
| Полная координата одного значения результата или входа редукции; все индексы отсчитываются от нуля. | |
| Скалярная поэлементная функция, применяемая к одной согласованной паре значений. | |
| Левый входной тензор. | |
| Правый входной тензор. | |
| Отображение координаты результата в : отсутствующие начальные оси отбрасываются, а на расширяемых осях размера 1 используется ноль. | |
| То же правило отображения для . | |
| Среднее по оси при фиксированных координатах . | |
| Все координаты, которые остаются фиксированными при редукции по оси . | |
| Явно указанная ось редукции, отсчитываемая от нуля. | |
| Размер редуцируемой оси . | |
| Координата на этой оси, принимающая значения от нуля до . | |
| Входное значение редукции в полной координате , составленной из фиксированных и изменяющейся . |
Совместимость проверяется по каждой оси после согласования начиная с последних осей. Два размера совместимы, если они равны или хотя бы один из них равен 1. Отсутствующая начальная ось ведёт себя как ось размера 1. В результат переходит совпадающий размер, а если размеры различаются — тот, который не равен 1. Последнее уточнение важно: из нуля и единицы получается ноль, а не единица.
Скаляр имеет форму [] и может быть согласован с любым результатом, поскольку у
него нет несовместимых осей. Выполнить редукцию скаляра этой операцией нельзя:
у него нет допустимой оси. Пустой результат не содержит координат, поэтому
переданная скалярная функция не вызывается ни разу.
От фиксированного контекста к вычислениям над всем тензором декодера
Бенжио и соавторы описывают n-граммные модели как таблицы условных вероятностей для фиксированного числа предыдущих слов; в их нейронной языковой модели обучаемые векторы признаков слов контекста конкатенируются, скрытый слой использует гиперболический тангенс, а вероятности следующего слова вычисляются с помощью softmax. Однако предсказание по-прежнему строится для одного выбранного окна фиксированной длины. В более поздних декодерах Transformer вычисления, напротив, охватывают доступный каждой позиции авторегрессионный префикс и организованы в тензоры с явными осями пакета, последовательности и голов внимания.
Для n-граммы порядка такой фиксированный контекст содержит предыдущих слов.
Более ранний источник — Bengio et al., A Neural Probabilistic Language Model.
Эта модель выходит за рамки отдельных строк таблицы на основе подсчётов: она обучает общие распределённые представления слов и параметры нейронной сети, однако на вход по-прежнему поступает выбранное окно фиксированной длины. Поэлементный гиперболический тангенс и softmax по словарю действительно входят в вычисления модели; глава не утверждает, что в статье был задан универсальный интерфейс тензоров.
Более поздние источники — Vaswani et al., Attention Is All You Need и официальный файл model.py модели GPT-2 от OpenAI. Васвани и соавторы задают маскированное самовнимание декодера через матрицы запросов, ключей и значений, применяют softmax к масштабированным оценкам «запрос — ключ», используют для каждого подслоя остаточное соединение с последующей нормализацией слоя и отдельно, но одинаково применяют одну и ту же сеть прямого распространения к каждой позиции. В официальной реализации GPT-2 явно обозначены оси пакета, последовательности, признаков, голов внимания, позиций назначения и позиций источника. В softmax из значений вычитается максимум по последней оси, затем вычисляются экспоненты сдвинутых значений, и каждая из них делится на их сумму по той же оси; обе редукции сохраняют ось. Нормализация сначала вычисляет средние по последней оси, а затем применяет векторы масштаба и смещения, размер которых совпадает с размером оси признаков.
В этих более поздних вычислениях отдельные оси тензора обозначают элемент пакета, позицию токена, голову внимания, позицию назначения, позицию источника и признак. Поэлементные функции активации, остаточное сложение, маски, масштабы и аффинные параметры признаков применяются к тензорам с учётом этих осей. Для softmax и нормализации нужно выполнять редукцию по нужной оси и сохранять форму, достаточную для последующего объединения результата с исходным тензором.
Согласование форм и редукции по явно указанным осям позволяют в этом курсе применять ко всему тензору декодера скаляры и параметры, размер которых совпадает с размером оси признаков, а также вычислять статистики по нужным осям для softmax в механизме внимания и нормализации признаков. Точное правило согласования начиная с последних осей, ошибки формы, поведение пустых осей, возможность сохранить редуцируемую ось и правила выделения памяти относятся к этой реализации. Источники по моделям задают сами вычисления, а руководство NumPy описывает вспомогательное правило согласования форм.
Небольшой пример на Rust подчёркивает эту границу.
fixed_context_feature_step выполняет один расчёт для трёх признаков в контексте
фиксированной ширины. Общий путь применяет тот же вектор смещения по признакам к
двум строкам токенов и выполняет редукции по заданным осям. Этот небольшой расчёт
связывает одно представление контекста с явными осями токенов и признаков, но не
является новшеством Transformer или полной реализацией какой-либо из двух
процитированных моделей.
rust/demos/ch10-broadcasting-reductions/src/lib.rs#tiny-token-feature-example /// Applies one fixed-width feature offset to one context representation.
///
/// This shape-specific baseline stands for an earlier one-example calculation;
/// it is not presented as the exact equation of any cited model.
pub fn fixed_context_feature_step(context: [f64; 3], feature_bias: [f64; 3]) -> [f64; 3] {
[
context[0] + feature_bias[0],
context[1] + feature_bias[1],
context[2] + feature_bias[2],
]
}
/// Runs the same scalar operations over explicit token and feature axes.
pub fn tiny_token_feature_example() -> Result<TinyTokenFeatureExample, TensorOpError> {
let tokens = Tensor::from_vec(TOKEN_SHAPE.to_vec(), TOKEN_VALUES.to_vec())?;
let bias = Tensor::from_vec(BIAS_SHAPE.to_vec(), BIAS_VALUES.to_vec())?;
let biased = map_binary(&tokens.view(), &bias.view(), |value, offset| value + offset)?;
let squared = map_unary(&tokens.view(), |value| value * value)?;
let sum_axis_0 = sum_axis(&biased.view(), 0, false)?;
let mean_axis_1_kept = mean_axis(&biased.view(), 1, true)?;
let max_axis_1 = max_axis(&biased.view(), 1, false)?;
Ok(TinyTokenFeatureExample {
tokens,
bias,
biased,
squared,
sum_axis_0,
mean_axis_1_kept,
max_axis_1,
})
} Сначала определите формы, затем вычисляйте значения
broadcast_shape только планирует форму. Функция согласует ранги начиная с
последних осей, считает отсутствующие начальные оси осями размера 1 и обходит
согласованные выходные оси слева направо. Равные размеры переходят в результат
без изменений. Если один размер равен 1, выбирается другой. В противном случае
ошибка содержит точную выходную ось и оба размера.
После проверки совместимости планировщик передаёт форму существующему коду
компоновки тензора, который проверяет все произведения последующих размеров.
Поэтому для [0,usize::MAX,1] и [1,1,2] ошибка ShapeOverflow возникает до
выделения памяти: внешний ноль делает входную форму допустимой, но в
запланированной форме usize::MAX умножается на , что не помещается в
usize. Ошибка совместимости имеет приоритет над этой проверкой формы.
rust/crates/llm-from-scratch/src/tensor/ops.rs#broadcast-planning /// Computes the checked output shape for trailing-axis broadcasting.
///
/// Missing leading dimensions act as size one. Aligned dimensions are
/// compatible when they are equal or either one is size one. Compatibility is
/// reported from the leftmost aligned output axis before layout overflow.
pub fn broadcast_shape(left: &[usize], right: &[usize]) -> Result<Vec<usize>, TensorOpError> {
let output_rank = left.len().max(right.len());
let left_padding = output_rank - left.len();
let right_padding = output_rank - right.len();
let mut output = Vec::with_capacity(output_rank);
for axis in 0..output_rank {
let left_dimension = left
.get(axis.wrapping_sub(left_padding))
.copied()
.unwrap_or(1);
let right_dimension = right
.get(axis.wrapping_sub(right_padding))
.copied()
.unwrap_or(1);
let dimension = if left_dimension == right_dimension {
left_dimension
} else if left_dimension == 1 {
right_dimension
} else if right_dimension == 1 {
left_dimension
} else {
return Err(TensorOpError::IncompatibleBroadcast {
axis,
left_dimension,
right_dimension,
});
};
output.push(dimension);
}
checked_row_major_layout(&output)?;
Ok(output)
} Правило совместимости совпадает с руководством NumPy по согласованию форм: при согласовании начиная с последних осей размеры совместимы, если они равны или один из них равен 1. Эта реализация дополнительно явно задаёт случай нуля и единицы: размер, отличный от 1, равен нулю, поэтому результат остаётся пустым. Руководство не служит источником исторического развития LLM в этой главе и не задаёт ошибки этой реализации, обход логического представления или правила владения результатом.
TensorView::get остаётся общедоступным способом прочитать одно значение по
координате, переданной вызывающим кодом. Число компонентов такой координаты
может не совпадать с рангом тензора, а индекс — выходить за границы своей оси,
поэтому каждый вызов сначала проверяет координату и лишь затем читает
хранилище. Поэлементные операции начинают работу с уже корректными
представлениями; кроме того, map_binary до обхода значений проверяет правило
«размеры равны или один из них равен 1». После этого внутренний план шагов один
раз проверяется и используется на всём обходе.
Для каждой оси результата map_binary назначает каждому входу эффективный шаг
по элементам. Для отсутствующей начальной оси входа и для согласованной входной
оси размера 1 этот шаг равен 0: когда соответствующая координата результата
меняется, нужно снова выбрать то же входное значение. На остальных
согласованных осях сохраняется исходный шаг TensorView. В зафиксированном
примере шаги тензора токенов [3,1] задают смещения в исходном хранилище
[0,1,2,3,4,5], а эффективные шаги вектора смещения [0,1] задают
[0,1,2,0,1,2].
Попарный обход этих двух последовательностей реализует шесть отображений
координат, предсказанных выше. map_unary использует такой же проверенный обход
по смещениям с собственными шагами входного представления. map_unary и
map_binary не создают новый вектор координат для каждого значения и не
возвращаются к общедоступной проверке координаты. Изменился только внутренний
способ обхода: сохраняются порядок аргументов бинарной функции «левый, затем
правый», логический построчный порядок вызовов, обычное индексирование среза в
Rust с проверкой границ и выделение памяти для нового непрерывного результата.
Для пустого результата переданная функция не вызывается. Транспонированное
представление или срез задаёт другие шаги без неявного вызова materialize.
Выделение памяти для каждого результата выполняется с обработкой ошибки.
Например, [usize::MAX,0] — допустимая пустая входная форма, но сумма по её
пустой оси потребовала бы собственный результат из usize::MAX нулей. До начала
обхода реализация возвращает
OutputAllocationFailed { elements: usize::MAX }, а не допускает panic при
запросе ёмкости.
rust/crates/llm-from-scratch/src/tensor/ops.rs#elementwise-maps /// Applies one scalar function in logical row-major order and owns the result.
pub fn map_unary<F>(input: &TensorView<'_>, mut operation: F) -> Result<Tensor, TensorOpError>
where
F: FnMut(f64) -> f64,
{
let mut values = output_buffer(input.len())?;
for input_offset in input.logical_offsets() {
values.push(operation(input.value_at_storage_offset(input_offset)));
}
Tensor::from_vec(input.shape().to_vec(), values).map_err(Into::into)
}
/// Applies one scalar function across two trailing-axis-compatible views.
pub fn map_binary<F>(
left: &TensorView<'_>,
right: &TensorView<'_>,
mut operation: F,
) -> Result<Tensor, TensorOpError>
where
F: FnMut(f64, f64) -> f64,
{
let output_shape = broadcast_shape(left.shape(), right.shape())?;
let (_, output_len) = checked_row_major_layout(&output_shape)?;
let mut values = output_buffer(output_len)?;
let left_strides = broadcast_effective_strides(left, output_shape.len());
let right_strides = broadcast_effective_strides(right, output_shape.len());
let left_offsets = left
.projected_offsets(&output_shape, &left_strides, output_len)
.expect("a compatible broadcast retains a valid left traversal plan");
let right_offsets = right
.projected_offsets(&output_shape, &right_strides, output_len)
.expect("a compatible broadcast retains a valid right traversal plan");
for (left_offset, right_offset) in left_offsets.zip(right_offsets) {
let left_value = left.value_at_storage_offset(left_offset);
let right_value = right.value_at_storage_offset(right_offset);
values.push(operation(left_value, right_value));
}
Tensor::from_vec(output_shape, values).map_err(Into::into)
} Три редукции используют общие правила проверки оси, построения формы результата
и фиксированного обхода по возрастанию координат. Сумма по выбранной пустой оси
использует нейтральный элемент сложения 0.0. Для среднего и максимума значения
в такой группе не существует, поэтому возвращаются разные типизированные ошибки.
Если нулевой размер имеет другая, не выбранная для редукции ось, результат
просто не содержит ни одного значения.
После проверки выбранной оси и формы результата каждой непустой выходной группе
соответствует одно базовое смещение в исходном хранилище. Редукция обходит
группу по возрастанию координаты выбранной оси, каждый раз прибавляя к текущему
смещению шаг по элементам этой оси. Для непрерывного тензора результата формы
[2,3] редукция по оси 0 использует базовые смещения [0,1,2] и шаг 3,
поэтому получает группы смещений [0,3], [1,4] и [2,5]. Для оси 1
базовые смещения равны [0,3], а шаг — 1; группы равны [0,1,2] и
[3,4,5]. Транспонированное представление или срез задаёт другие базовые
смещения и шаги, но логические группы и порядок вычислений не меняются. Сумма
по выбранной пустой оси записывает 0.0, не читая базовое смещение. Если
нулевой размер имеет другая, не выбранная ось, в результате нет групп и чтение
исходного хранилища не выполняется.
Для каждой непустой группы поиск максимума начинается со значения при
координате ноль на выбранной оси. Оно заменяется только строго большим
значением; первый встретившийся NaN явно сохраняется. При равенстве остаётся
первый битовый шаблон, в том числе знак нуля в паре -0.0 и +0.0. Явное
правило не позволяет случайно унаследовать обработку NaN или равных значений из
вспомогательной функции.
rust/crates/llm-from-scratch/src/tensor/ops.rs#axis-reductions /// Sums one explicit axis in ascending index order.
///
/// An empty selected axis uses the additive identity, so every output group is
/// `0.0`. `keep_dim` replaces the selected extent with one instead of removing
/// the axis.
pub fn sum_axis(
input: &TensorView<'_>,
axis: usize,
keep_dim: bool,
) -> Result<Tensor, TensorOpError> {
reduce_axis(input, axis, keep_dim, Reduction::Sum)
}
/// Averages one explicit nonempty axis in ascending index order.
pub fn mean_axis(
input: &TensorView<'_>,
axis: usize,
keep_dim: bool,
) -> Result<Tensor, TensorOpError> {
reduce_axis(input, axis, keep_dim, Reduction::Mean)
}
/// Selects the maximum over one explicit nonempty axis.
///
/// The fold propagates the first NaN and keeps the earlier value on equal
/// comparisons, including the earlier signed-zero bit pattern.
pub fn max_axis(
input: &TensorView<'_>,
axis: usize,
keep_dim: bool,
) -> Result<Tensor, TensorOpError> {
reduce_axis(input, axis, keep_dim, Reduction::Max)
}
#[derive(Clone, Copy)]
enum Reduction {
Sum,
Mean,
Max,
}
fn reduce_axis(
input: &TensorView<'_>,
axis: usize,
keep_dim: bool,
reduction: Reduction,
) -> Result<Tensor, TensorOpError> {
if axis >= input.rank() {
return Err(TensorOpError::ReductionAxisOutOfBounds {
axis,
rank: input.rank(),
});
}
let axis_len = input.shape()[axis];
match reduction {
Reduction::Mean if axis_len == 0 => {
return Err(TensorOpError::EmptyMeanAxis { axis });
}
Reduction::Max if axis_len == 0 => {
return Err(TensorOpError::EmptyMaxAxis { axis });
}
_ => {}
}
let output_shape = reduction_shape(input.shape(), axis, keep_dim);
let (_, output_len) = checked_row_major_layout(&output_shape)?;
let mut values = output_buffer(output_len)?;
if axis_len == 0 {
debug_assert!(matches!(reduction, Reduction::Sum));
values.resize(output_len, 0.0);
return Tensor::from_vec(output_shape, values).map_err(Into::into);
}
let group_strides = reduction_group_strides(input.strides(), axis, keep_dim);
let group_offsets = input
.projected_offsets(&output_shape, &group_strides, output_len)
.expect("a checked reduction retains a valid group traversal plan");
let axis_stride = input.strides()[axis];
for group_offset in group_offsets {
let value = match reduction {
Reduction::Sum | Reduction::Mean => {
let mut total = 0.0;
let mut input_offset = group_offset;
for index in 0..axis_len {
total += input.value_at_storage_offset(input_offset);
if index + 1 < axis_len {
input_offset = input_offset
.checked_add(axis_stride)
.expect("a checked view cannot overflow along a reduction axis");
}
}
if matches!(reduction, Reduction::Mean) {
total / axis_len as f64
} else {
total
}
}
Reduction::Max => {
let mut input_offset = group_offset;
let mut maximum = input.value_at_storage_offset(input_offset);
for _ in 1..axis_len {
input_offset = input_offset
.checked_add(axis_stride)
.expect("a checked view cannot overflow along a reduction axis");
let candidate = input.value_at_storage_offset(input_offset);
if !maximum.is_nan() && (candidate.is_nan() || candidate > maximum) {
maximum = candidate;
}
}
maximum
}
};
values.push(value);
}
Tensor::from_vec(output_shape, values).map_err(Into::into)
} Исполняемый пример на Rust демонстрирует заданный расчёт с формой,
характерной для модели, согласование со скаляром, пустой результат без вызовов
переданной функции, нейтральный элемент
пустой суммы и каждую типизированную ошибку. Целочисленные результаты можно
сравнивать точно. Для среднего значений [0.1,0.2,0.3] подходит абсолютный
допуск 1e-12, поскольку эти десятичные дроби не имеют точного представления
двоичным числом с плавающей точкой.
rust/demos/ch10-broadcasting-reductions/src/main.rs#learner-broadcasting-output let example = tiny_token_feature_example()?;
let scalar = Tensor::from_vec(vec![], vec![0.5])?;
let scalar_broadcast = map_binary(&example.tokens.view(), &scalar.view(), |value, offset| {
value + offset
})?;
let empty = empty_fixture()?;
let mut closure_calls = 0;
let empty_broadcast = map_binary(&empty.view(), &example.bias.view(), |value, offset| {
closure_calls += 1;
value + offset
})?;
let empty_sum = sum_axis(&empty.view(), 1, false)?;
let incompatible = Tensor::from_vec(vec![2], vec![1.0, 2.0])?;
let broadcast_error = map_binary(
&example.tokens.view(),
&incompatible.view(),
|left, right| left + right,
)
.unwrap_err();
let mean_error = mean_axis(&empty.view(), 1, false).unwrap_err();
let max_error = max_axis(&empty.view(), 1, false).unwrap_err();
let scalar_reduction_error = sum_axis(&scalar.view(), 0, false).unwrap_err(); Пример сохраняет границу главы явной: он показывает скалярные поэлементные операции, согласование форм, редукции по заданным осям и типизированные ошибки, но не реализует softmax, нормализацию или матричное умножение. Эти последующие операции будут использовать построенные здесь проверяемые примитивы.
Проследите повторное использование признаков и редукции по осям
Изучите схему в четыре прохода:
- Сравните каждую исходную форму с результатом согласования начиная с последних осей.
- Проследите все шесть координат результата до одной координаты токена и одной координаты вектора смещения. Обратите внимание: каждая координата смещения встречается в обеих строках токенов.
- Сравните плоские группы для суммы по оси 0 и среднего или максимума по оси 1. Перед чтением каждой формы результата проверьте поле «Сохранить редуцируемую ось».
- Разберите три отклонённых записи: несовместимые размеры
3и2, пустое среднее и пустой максимум.
Один вектор смещения для двух строк, затем редукция по заданным осям
Согласуйте вектор смещения из трёх признаков с двумя строками токенов, проследите шесть отображений координат и сравните сумму, среднее, максимум и три отклонённых запроса.
Согласуйте формы и сопоставьте координаты результата координатам входов
Отсутствующая начальная ось вектора смещения имеет размер 1, поэтому его координаты повторно выбираются для обеих строк токенов. Маркер обозначает отображение координат, а не копирование.
- Тензор: Форма тензора признаков токенов Исходная форма
[2,3]Согласованная форма[2,3] - Координата на оси размера 1 используется повторно: Форма вектора смещения Исходная форма
[3]Согласованная форма[1,3] - Форма результата согласования
[2,3]
| Координата результата | Координата токена | Координата смещения | Результат |
|---|---|---|---|
[0,0] | [0,0] | Координата на оси размера 1 используется повторно: [0] | 11.0 |
[0,1] | [0,1] | Координата на оси размера 1 используется повторно: [1] | 22.0 |
[0,2] | [0,2] | Координата на оси размера 1 используется повторно: [2] | 33.0 |
[1,0] | [1,0] | Координата на оси размера 1 используется повторно: [0] | 14.0 |
[1,1] | [1,1] | Координата на оси размера 1 используется повторно: [1] | 25.0 |
[1,2] | [1,2] | Координата на оси размера 1 используется повторно: [2] | 36.0 |
broadcast
- Запрос:
-
[2,3]/[2] - Данные проверки:
- Ось 1,
- Причина отклонения:
- Согласованные размеры осей 3 и 2 различаются, и ни один из них не равен 1.
Выполняйте редукцию по одной заданной оси
Стрелка вниз отмечает значения, объединяемые по оси 0 или 1. В отклонённых строках объясняется, почему для выбранной пустой оси нельзя вычислить среднее или максимум.
| Операция | Ось | Сохранить редуцируемую ось | Форма результата | Группа / Значения |
|---|---|---|---|---|
Значения объединяются редукцией: sum | 0 | Нет | [3] | Группа 0: [11.0,14.0] Результат: 25.0 Группа 1: [22.0,25.0] Результат: 47.0 Группа 2: [33.0,36.0] Результат: 69.0 |
Значения объединяются редукцией: mean | 1 | Да | [2,1] | Группа 0: [11.0,22.0,33.0] Результат: 22.0 Группа 1: [14.0,25.0,36.0] Результат: 25.0 |
Значения объединяются редукцией: max | 1 | Нет | [2] | Группа 0: [11.0,22.0,33.0] Результат: 33.0 Группа 1: [14.0,25.0,36.0] Результат: 36.0 |
Операция отклонена: mean | 1 | Не применяется | Операция отклонена | Запрос: [2,0,3] Причина отклонения: Для выбранной пустой оси среднее не определено. |
Операция отклонена: max | 1 | Не применяется | Операция отклонена | Запрос: [2,0,3] Причина отклонения: Для выбранной пустой оси максимум не определён. |
Маркер повторного использования показывает, что одна координата вектора смещения выбирается для обеих строк токенов; он не означает, что тензор смещения был заранее скопирован. Стрелки вниз обозначают группы, объединяемые каждой редукцией. Для каждого отклонённого запроса маркер отказа связан с несовместимыми размерами или выбранной пустой осью, из-за которой результат не определён.
Предскажите допустимые формы и результаты редукции
Сначала запишите форму и отображение координат, а затем вычисляйте значения.
- Согласуйте
[2,1,3]с[4,3]. Предскажите форму результата и обе исходные координаты для координаты результата[1,2,0]. - Определите, совместимы ли формы
[2,3]и[2]. Если нет, назовите выходную ось и два несовместимых размера. - Согласуйте
[0,3]с[1,3]. Предскажите форму результата и число вызовов переданной скалярной функции. - Для примера с вектором смещения предскажите сумму по оси 0, среднее по оси 1 с сохранённой редуцируемой осью и максимум по оси 1 без сохранения.
- Выполните сумму, среднее и максимум по выбранной пустой оси
1тензора формы[2,0,3]. - Вычислите среднее значений
[0.1,0.2,0.3]ранга 1 по оси0, не сохраняя её. Предскажите форму результата и объясните необходимость допуска. - Примените правило максимума к
[1,NaN_A,NaN_B]и[-0.0,+0.0,-1.0]. Предскажите, какая полезная нагрузка NaN и какой знак нуля сохранятся. - Для тензора токенов с шагами
[3,1]и вектора смещения с шагом[1]запишите оба плана эффективных шагов и шесть смещений в исходном хранилище, которые выдаёт каждый план. Затем запишите базовые смещения редукции по оси0, шаг выбранной оси и группы смещений. Почему эти внутренние планы можно использовать повторно, аTensorView::getдолжен проверять каждую координату, полученную от вызывающего кода?
Проверить восемь ответов о согласовании форм и редукции
- При согласовании начиная с последних осей получаем
[2,1,3]и[1,4,3], поэтому форма результата равна[2,4,3]. Координата результата[1,2,0]сопоставляется левой координате[1,0,0], поскольку размер средней оси левого тензора равен 1, и правой координате[2,0], поскольку отсутствующая начальная ось правого тензора отбрасывается. - Согласование начиная справа сравнивает
3и2на выходной оси1. Размеры различаются, и ни один из них не равен 1, поэтому возвращаетсяIncompatibleBroadcast { axis: 1, left_dimension: 3, right_dimension: 2 }. - Ноль и единица совместимы, а размер, отличный от 1, равен нулю. Поэтому
форма результата —
[0,3]. В нём нет логических значений, и переданная скалярная функция не вызывается ни разу. - Группы суммы по оси 0 равны
[11,14],[22,25]и[33,36]; результат имеет форму[3]и значения[25,47,69]. Среднее по оси 1 объединяет каждую строку и даёт сохранённую форму[2,1]со значениями[22,25]. Максимум по оси 1 удаляет ось и даёт форму[2]со значениями[33,36]. - Для каждой из шести выходных групп сумма использует нейтральный элемент
сложения, поэтому возвращает форму
[2,3], заполненную значением0.0. Среднее возвращаетEmptyMeanAxis { axis: 1 }, а максимум —EmptyMaxAxis { axis: 1 }: для выбранной пустой группы ни одно из этих значений не определено. - После удаления единственной оси получается скалярная форма
[]с одним значением, близким к0.2. Десятичные дроби0.1,0.2и0.3нельзя точно представить двоичным числом с плавающей точкой, поэтому вместо сравнения битовых шаблонов подходит абсолютный допуск1e-12. - При обходе по возрастанию координат
NaN_Aвстречается первым, и далее сохраняется его полезная нагрузка. Во второй строке первым максимумом становится-0.0;+0.0равен ему, поэтому сохраняется более ранний битовый шаблон отрицательного нуля. - План тензора токенов равен
[3,1]и выдаёт смещения[0,1,2,3,4,5]. В плане вектора смещения отсутствующей начальной оси соответствует нулевой шаг, поэтому план равен[0,1]и выдаёт[0,1,2,0,1,2]. Для редукции по оси0базовые смещения равны[0,1,2], шаг выбранной оси равен3, а группы смещений —[0,3],[1,4]и[2,5]. Эти планы содержат только метаданные, вычисленные из корректного представления и проверенной операции, и не принимают новых координат от вызывающего кода.TensorView::get, напротив, получает новую координату, поэтому при каждом вызове должен проверить её ранг и границы осей.
Важно не перепутать: согласование форм не является командой заранее копировать меньший тензор, пока формы не станут одинаковыми. Это правило сопоставления координат совместимых осей; реализация сразу вычисляет результат через эти отображения и выделяет хранилище только для него. Редукция также не означает «сложить всё»: нужно явно указать ось, координата которой меняется, пока остальные координаты остаются фиксированными.
Подготовьте примитивы для нормализации и softmax
Теперь тензорное ядро может объединять с тензорами токенов скаляры или значения, размер которых совпадает с размером оси признаков, применять унарные функции активации к логическим представлениям и в фиксированном порядке вычислять сумму, среднее или максимум по одной явно указанной оси. Операции проверяют корректность входов и возвращают тензоры с собственным хранилищем; поэтому последующий код модели получает предсказуемую границу формы и владения.
Общедоступный доступ по координате по-прежнему проверяет каждую координату, полученную от вызывающего кода. Внутри уже проверенной операции те же правила координат выражаются нулевыми эффективными шагами при согласовании форм и одним проверенным планом «базовое смещение плюс шаг» для каждой группы редукции. Такой обход не строит и не проверяет заново координату для каждого скалярного значения.
Это вспомогательные части, а не готовые слои модели. Устойчивый softmax внимания будет вычитать максимум по оси, поэлементно вычислять экспоненту, суммировать по той же оси с её сохранением и делить. Для нормализации признаков понадобятся статистики по оси признаков и параметры того же размера. Полные алгоритмы будут построены в последующих главах.
Сначала глава 11 добавит матричное умножение. В отличие от согласования форм, внутренние размеры сомножителей, по которым выполняется суммирование, должны совпадать; это инвариант операции. Выходные размеры определяются строками и столбцами, а не правилом «равны или один равен 1». Явная граница не позволит удобному планировщику поэлементных операций незаметно подменить обучаемые линейные преобразования.