11 · Версия материала 5
Умножьте строки на столбцы и используйте одну матрицу весов в нескольких пакетах
Выполните матричное умножение двумерных и пакетных тензоров скалярными циклами на Rust с проверкой внутренних размеров, согласованием форм по осям пакета и флагами транспонирования.
Предскажите произведение одной строки и одного столбца
Считайте две строки двумя позициями токенов с тремя признаками. Матрица активаций
имеет форму [2,3]:
[[1, 2, 3],
[4, 5, 6]]
Матрица весов проекции имеет форму [3,2]:
[[1, 2],
[0, 1],
[2, 0]]
Предскажите форму результата до умножения значений. Обе внутренние оси имеют
размер . Два внешних размера сохраняются, поэтому результат имеет форму
[2,2].
Теперь предскажите ячейку . Выберите строку из и столбец из , попарно умножьте элементы с одинаковой внутренней координатой и складывайте, начиная с :
Примените то же правило ко всем четырём парам «строка — столбец»:
Результат имеет форму [2,2] и значения:
[[ 7, 4],
[16, 13]]
Для последующего пакетного примера левый пакет 0 содержит исходную матрицу , а левый пакет 1 — следующую матрицу:
[[0, 1, 2],
[2, 1, 0]]
Правый операнд имеет форму [1,3,2] и хранит исходную матрицу ровно один раз.
Поскольку размер его единственной начальной оси равен , оба пакета результата
используют правый пакет .
Это не поэлементное умножение: формы и даже не совпадают. Одна ячейка результата использует целую строку и целый столбец, а общая внутренняя ось исчезает.
Просуммируйте произведения по одной общей внутренней оси
Матричное умножение задаётся формулой:
В рассматриваемом примере роль общего правого операнда играет матрица весов проекции , то есть .
Зафиксируйте строку результата и столбец . Инициализируйте накопленную сумму нулём. Для каждого от нуля до умножьте на и добавьте произведение к сумме. Порядок цикла — часть этой эталонной реализации, поскольку сложение чисел с плавающей точкой в общем случае неассоциативно.
Для пакетных форм и формула выполняется независимо для каждой совместимой координаты начальных осей пакета. В обозначениях пакетные координаты опущены, чтобы вновь введённое суммирование произведений строки и столбца оставалось на виду. Согласовывать формы можно только по начальным осям пакета; два размера должны быть равны.
Назовите каждый индекс и размер матриц
| Символ | Смысл в вычислении |
|---|---|
| Значение результата в строке и столбце . | |
| Значение левого входа в строке результата и позиции на общей внутренней оси. | |
| Значение правого входа в позиции на общей внутренней оси и столбце результата . | |
| Отсчитываемый от нуля индекс строки результата. | |
| Отсчитываемый от нуля индекс столбца результата. | |
| Отсчитываемая от нуля координата, которая проходит по обеим общим внутренним осям. | |
| Общий внутренний размер и, следовательно, число скалярных умножений, произведения которых складываются в одну ячейку. |
В результате сохраняются строк эффективной левой матрицы и столбцов
эффективной правой матрицы. Флаг транспонирования логически меняет местами только
две последние оси операнда. Поэтому хранимая форма [2,3] может
использоваться как логическая форма [3,2], если transpose_right=true; копировать
значения не требуется.
Неявное повышение ранга одномерного тензора намеренно не поддерживается. Вектор нужно представить с явной осью строки или столбца, чтобы в правиле определения формы результата не было скрытого особого случая.
От одного вектора фиксированного контекста к матрицам всех позиций
Нейронная языковая модель прямого распространения Бенжио и соавторов выбирает обучаемые векторы для фиксированного числа слов контекста, объединяет их в один вектор фиксированной длины и вычисляет оценки следующего слова с помощью обучаемых матрично-векторных преобразований. Общие признаки позволяют модели переносить сведения между похожими словами, но каждое предсказание всё ещё опирается на ограниченный контекст, а не на маскированное самовнимание по последовательности позиций.
Эта более ранняя модель описана в статье Bengio et al., A Neural Probabilistic Language Model. Бенжио и соавторы хранят обучаемые признаки слов в матрице, объединяют векторы слов из контекста фиксированной длины и вычисляют оценки следующего слова последовательными обучаемыми матрично-векторными преобразованиями и нелинейным скрытым слоем. В статье это вычисление записано как , а обучаемые признаки слов хранятся в матрице размера .
Эта модель уже относится к истории LLM: отдельные строки таблиц частот заменены обучаемыми признаками слов и общими параметрами нейронной сети. Существенное ограничение более раннего подхода здесь состоит в том, что он строится вокруг одного объединённого вектора контекста фиксированной длины. В статье не задаются API пакетных тензоров, правила шагов или типы ошибок, используемые в этом курсе.
Более поздние источники — статьи Vaswani et al., Attention Is All You Need и Radford et al., Language Models are Unsupervised Multitask Learners. Васвани и соавторы собирают запросы, ключи и значения в матрицы. Для вычисления внимания они получают масштабированные оценки «запрос — ключ», применяют к ним softmax и используют полученные веса для взвешивания значений; кроме того, они используют обучаемые проекции запросов, ключей и значений, выходную проекцию, а также два линейных преобразования в сети прямого распространения, одинаковой для каждой позиции. В отчёте о GPT-2 описана архитектура авторегрессионной языковой модели на основе Transformer: четыре варианта имеют от 12 до 48 слоёв, ширину от 768 до 1600 и контекст длиной 1024 токена. В обозначениях Transformer преобразование внимания записывается как .
Матричное умножение с проверкой форм суммирует произведения по общей оси. В современном декодере оно многократно используется в обучаемых проекциях, при вычислении оценок внимания и сумм значений с весами внимания. Согласование форм по осям пакета, флаги транспонирования, обход с учётом шагов, правила хранения, обработка нулевых размеров и явные ошибки — частные правила этой реализации, обеспечивающие корректность; они не заимствованы из статей.
Пример на Rust позволяет увидеть этот исторический переход, не утверждая при
этом, что код воспроизводит какую-либо из рассмотренных моделей.
fixed_context_projection вычисляет один вектор из трёх признаков контекста с
помощью цикла, рассчитанного на конкретную форму. Общий путь для тензоров применяет
то же правило умножения строки на столбец к нескольким строкам токенов и начальным
осям пакета.
rust/demos/ch11-matrix-multiplication/src/lib.rs#fixed-width-projection /// Projects one fixed-width context representation with scalar operations.
///
/// This shape-specific baseline makes the older matrix-vector calculation
/// explicit; it is not presented as the exact equation of any cited model.
pub fn fixed_context_projection(context: [f64; 3], weights: [[f64; 2]; 3]) -> [f64; 2] {
let mut output = [0.0; 2];
for column in 0..2 {
for inner in 0..3 {
output[column] += context[inner] * weights[inner][column];
}
}
output
} Проверьте формы до начала скалярных циклов
Тип ошибки позволяет различать каждое нарушенное условие. API проверяет ранг левого и правого операндов, эффективные внутренние размеры после применения флагов транспонирования, оси пакета слева направо, полное размещение результата и лишь затем — выделение памяти для результата. Поэтому для запроса, нарушающего несколько правил, первой всегда будет одна и та же ошибка.
rust/crates/llm-from-scratch/src/tensor/matmul.rs#matmul-errors /// A rejected matrix product, output layout, allocation, or converted view operation.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MatmulError {
/// An owned output layout violates the tensor storage invariant.
Tensor(TensorError),
/// A tensor-view error was converted into the matrix-multiplication error type.
View(TensorViewError),
/// Matrix multiplication does not promote a left vector in this chapter.
LeftRankTooSmall { rank: usize },
/// Matrix multiplication does not promote a right vector in this chapter.
RightRankTooSmall { rank: usize },
/// The effective final left axis and penultimate right axis differ.
InnerDimensionMismatch { left: usize, right: usize },
/// Two trailing-aligned batch dimensions are neither equal nor singleton.
IncompatibleBatch {
axis: usize,
left_dimension: usize,
right_dimension: usize,
},
/// The checked output shape is valid, but its value buffer cannot be reserved.
OutputAllocationFailed { elements: usize },
}
impl fmt::Display for MatmulError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Tensor(error) => error.fmt(formatter),
Self::View(error) => error.fmt(formatter),
Self::LeftRankTooSmall { rank } => {
write!(
formatter,
"left matmul input must have rank at least 2, got {rank}"
)
}
Self::RightRankTooSmall { rank } => {
write!(
formatter,
"right matmul input must have rank at least 2, got {rank}"
)
}
Self::InnerDimensionMismatch { left, right } => write!(
formatter,
"matmul inner dimensions do not match: left size {left}, right size {right}"
),
Self::IncompatibleBatch {
axis,
left_dimension,
right_dimension,
} => write!(
formatter,
"cannot broadcast batch axis {axis}: left size {left_dimension}, right size {right_dimension}"
),
Self::OutputAllocationFailed { elements } => write!(
formatter,
"cannot allocate output buffer for {elements} f64 values"
),
}
}
}
impl Error for MatmulError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Tensor(error) => Some(error),
Self::View(error) => Some(error),
_ => None,
}
}
}
impl From<TensorError> for MatmulError {
fn from(error: TensorError) -> Self {
Self::Tensor(error)
}
}
impl From<TensorViewError> for MatmulError {
fn from(error: TensorViewError) -> Self {
Self::View(error)
}
} Планировщик сопоставляет, начиная с последних, только те оси, которые
предшествуют двум матричным осям. Отсутствующие начальные оси считаются равными
единице, а для входной оси единичного размера всегда выбирается координата .
Планировщик проверяет полную форму результата, а не только её начальные оси
пакета. Благодаря этому допустим пустой результат, например для пары входных форм
[usize::MAX,2,0,3] и [1,2,3,1]: нулевой размер оси строк делает весь результат
пустым ещё до чтения входных данных.
TensorView::get остаётся общедоступным методом для координаты, переданной
вызывающим кодом. Такая координата может иметь неверный ранг или выходить за
границы оси, поэтому get проверяет каждый вызов. Матричное умножение работает
в других условиях: оба представления уже прошли проверку, а планировщик заранее
проверил эффективные размеры матриц, совместимость осей пакета и полную форму
результата. Поэтому скалярный цикл может повторно использовать один проверенный
план смещений, а не строить и проверять заново две координаты для каждого
произведения.
Для каждой ячейки результата каждому операнду требуется начальное смещение в хранилище, соответствующее . Назовём его базовым смещением ячейки. Шаг по общей внутренней оси переводит это смещение к значению для , затем для и так далее.
Для формы результата [...,M,N] план задаёт каждому операнду по одному
эффективному шагу для каждой оси результата. Ось пакета, размер которой во входе
больше 1, сохраняет шаг входного представления. Отсутствующая ось или ось
размера 1 получает шаг 0, чтобы при изменении координаты результата по этой
оси выбирался тот же входной пакет. У левого операнда ось строк сохраняет свой
эффективный шаг, а шаг по оси столбцов результата равен 0. У правого операнда
шаг по оси строк результата равен 0, а ось столбцов сохраняет эффективный шаг
по столбцам. Отдельно планировщик сохраняет шаги обоих операндов по общей
внутренней оси.
В учебном примере непрерывная матрица имеет шаги [3,1], а непрерывная
матрица — шаги [2,1]. При построчном обходе координат результата [0,0],
[0,1], [1,0], [1,1] план [3,0] для левого операнда выдаёт базовые
смещения [0,0,3,3], а план [0,1] для правого — [0,1,0,1]. Поэтому для
ячейки начальные смещения равны 3 и 0. Шаги 1 и 2 по общей
внутренней оси дают три пары смещений (3,0), (4,2) и (5,4), по которым в
порядке возрастания читаются произведения , и
.
Для пакетных форм [2,2,3] и [1,3,2] планы базовых смещений ячеек равны
[6,3,0] слева и [0,0,1] справа. Первый 0 в правом плане соответствует
единичной оси пакета: пакеты результата 0 и 1 оба начинают чтение с правого
пакета 0. Флаг транспонирования меняет местами роли двух последних размеров и
шагов операнда, но не перемещает хранимые значения.
Два курсора базовых смещений ячеек проверяются один раз, а затем обходят сначала пакеты, внутри каждого пакета — строки, а внутри строки — столбцы. Внутри каждой ячейки цикл сначала читает левое значение, затем правое, перемножает их и прибавляет произведение к накопленной сумме, последовательно увеличивая . Значения по-прежнему читаются обычным безопасным индексированием среза в Rust с проверкой границ. Внутри суммирования не создаётся вектор координат. Для среза или транспонированного представления не нужна неявная материализация: такое представление просто передаёт планировщику другие базовые смещения и шаги.
rust/crates/llm-from-scratch/src/tensor/matmul.rs#checked-matmul #[derive(Debug)]
struct MatmulPlan {
/// The owned result's logical row-major shape and element count.
output_shape: Vec<usize>,
output_len: usize,
/// The number of products accumulated into each output cell.
inner: usize,
/// One source-storage movement per output batch, row, and column axis.
left_cell_strides: Vec<usize>,
right_cell_strides: Vec<usize>,
/// Source-storage movement when the contracted index increases by one.
left_inner_stride: usize,
right_inner_stride: usize,
}
#[derive(Clone, Copy, Debug)]
struct EffectiveMatrixLayout {
rows: usize,
columns: usize,
row_stride: usize,
column_stride: usize,
}
/// Multiplies two rank-two or batched tensor views as stored.
pub fn matmul(left: &TensorView<'_>, right: &TensorView<'_>) -> Result<Tensor, MatmulError> {
matmul_with_transpose(left, right, false, false)
}
/// Multiplies two tensor views after optional logical final-axis transposes.
///
/// Inputs must have rank at least two. Only axes before the final two matrix
/// axes broadcast, using trailing alignment. The effective inner dimensions
/// must match exactly. The scalar contraction visits `k` in ascending order and
/// reads through offsets established by one checked strided plan per operand.
pub fn matmul_with_transpose(
left: &TensorView<'_>,
right: &TensorView<'_>,
transpose_left: bool,
transpose_right: bool,
) -> Result<Tensor, MatmulError> {
let plan = MatmulPlan::new(left, right, transpose_left, transpose_right)?;
let mut values = output_buffer(plan.output_len)?;
if plan.inner == 0 {
values.resize(plan.output_len, 0.0);
return Tensor::from_vec(plan.output_shape, values).map_err(Into::into);
}
let left_cell_offsets = left
.projected_offsets(&plan.output_shape, &plan.left_cell_strides, plan.output_len)
.expect("a checked matmul plan retains valid left cell offsets");
let right_cell_offsets = right
.projected_offsets(
&plan.output_shape,
&plan.right_cell_strides,
plan.output_len,
)
.expect("a checked matmul plan retains valid right cell offsets");
for (left_cell_offset, right_cell_offset) in left_cell_offsets.zip(right_cell_offsets) {
let mut sum = 0.0;
let mut left_offset = left_cell_offset;
let mut right_offset = right_cell_offset;
for inner_index in 0..plan.inner {
let left_value = left.value_at_storage_offset(left_offset);
let right_value = right.value_at_storage_offset(right_offset);
sum += left_value * right_value;
if inner_index + 1 < plan.inner {
left_offset = left_offset
.checked_add(plan.left_inner_stride)
.expect("a checked matmul plan cannot overflow along the left inner axis");
right_offset = right_offset
.checked_add(plan.right_inner_stride)
.expect("a checked matmul plan cannot overflow along the right inner axis");
}
}
values.push(sum);
}
Tensor::from_vec(plan.output_shape, values).map_err(Into::into)
}
impl MatmulPlan {
fn new(
left: &TensorView<'_>,
right: &TensorView<'_>,
transpose_left: bool,
transpose_right: bool,
) -> Result<Self, MatmulError> {
if left.rank() < 2 {
return Err(MatmulError::LeftRankTooSmall { rank: left.rank() });
}
if right.rank() < 2 {
return Err(MatmulError::RightRankTooSmall { rank: right.rank() });
}
let left_matrix = effective_matrix_layout(left, transpose_left);
let right_matrix = effective_matrix_layout(right, transpose_right);
let rows = left_matrix.rows;
let inner = left_matrix.columns;
let right_inner = right_matrix.rows;
let columns = right_matrix.columns;
if inner != right_inner {
return Err(MatmulError::InnerDimensionMismatch {
left: inner,
right: right_inner,
});
}
let left_batch_shape = &left.shape()[..left.rank() - 2];
let right_batch_shape = &right.shape()[..right.rank() - 2];
let batch_shape = broadcast_batch_shape(left_batch_shape, right_batch_shape)?;
let mut output_shape = batch_shape.clone();
output_shape.extend([rows, columns]);
let (_, output_len) = checked_row_major_layout(&output_shape)?;
let batch_rank = batch_shape.len();
let mut left_cell_strides = batch_effective_strides(left, batch_rank);
left_cell_strides.extend([left_matrix.row_stride, 0]);
let mut right_cell_strides = batch_effective_strides(right, batch_rank);
right_cell_strides.extend([0, right_matrix.column_stride]);
Ok(Self {
output_shape,
output_len,
inner,
left_cell_strides,
right_cell_strides,
left_inner_stride: left_matrix.column_stride,
right_inner_stride: right_matrix.row_stride,
})
}
}
fn effective_matrix_layout(input: &TensorView<'_>, transposed: bool) -> EffectiveMatrixLayout {
let matrix_axis = input.rank() - 2;
let stored = EffectiveMatrixLayout {
rows: input.shape()[matrix_axis],
columns: input.shape()[matrix_axis + 1],
row_stride: input.strides()[matrix_axis],
column_stride: input.strides()[matrix_axis + 1],
};
if transposed {
EffectiveMatrixLayout {
rows: stored.columns,
columns: stored.rows,
row_stride: stored.column_stride,
column_stride: stored.row_stride,
}
} else {
stored
}
}
fn broadcast_batch_shape(left: &[usize], right: &[usize]) -> Result<Vec<usize>, MatmulError> {
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(MatmulError::IncompatibleBatch {
axis,
left_dimension,
right_dimension,
});
};
output.push(dimension);
}
Ok(output)
}
fn batch_effective_strides(input: &TensorView<'_>, output_batch_rank: usize) -> Vec<usize> {
let input_batch_rank = input.rank() - 2;
let padding = output_batch_rank - input_batch_rank;
(0..output_batch_rank)
.map(|output_axis| {
if output_axis < padding {
return 0;
}
let input_axis = output_axis - padding;
if input.shape()[input_axis] == 1 {
0
} else {
input.strides()[input_axis]
}
})
.collect()
} Если размер оси пакета, строки или столбца равен нулю, результат пуст и входные
данные не читаются. Если равен нулю только , ячейки результата всё же
существуют, но ни в одном входе нет доступного значения внутренней оси. После
выделения памяти для результата реализация записывает в каждую ячейку
положительный 0.0, не создавая курсор смещений и не читая ни один вход.
Внутренние размеры и не согласуются между собой и отклоняются как
несовпадающие.
Небольшая вспомогательная функция создаёт двумерное произведение, правый операнд, хранимый в транспонированном виде, и два левых пакета, которые используют один пакет весов. Применение функции для конкретной формы к каждой строке даёт те же четыре значения, что и обобщённое по рангу матричное умножение.
rust/demos/ch11-matrix-multiplication/src/lib.rs#tiny-matmul-example /// Multiplies the same values as 2-D matrices, a logical transpose, and batches.
pub fn tiny_matrix_multiplication_example() -> Result<TinyMatrixMultiplicationExample, MatmulError>
{
let token_rows = Tensor::from_vec(TOKEN_SHAPE.to_vec(), TOKEN_VALUES.to_vec())?;
let weights = Tensor::from_vec(WEIGHT_SHAPE.to_vec(), WEIGHT_VALUES.to_vec())?;
let product = matmul(&token_rows.view(), &weights.view())?;
let stored_transpose = Tensor::from_vec(
STORED_TRANSPOSE_SHAPE.to_vec(),
STORED_TRANSPOSE_VALUES.to_vec(),
)?;
let transpose_product =
matmul_with_transpose(&token_rows.view(), &stored_transpose.view(), false, true)?;
let batched_token_rows =
Tensor::from_vec(BATCHED_TOKEN_SHAPE.to_vec(), BATCHED_TOKEN_VALUES.to_vec())?;
let batched_weights = Tensor::from_vec(BATCHED_WEIGHT_SHAPE.to_vec(), WEIGHT_VALUES.to_vec())?;
let batched_product = matmul(&batched_token_rows.view(), &batched_weights.view())?;
Ok(TinyMatrixMultiplicationExample {
token_rows,
weights,
product,
stored_transpose,
transpose_product,
batched_token_rows,
batched_weights,
batched_product,
})
} Учебный пример также показывает суммирование при нулевом внутреннем размере и точные ошибки ранга, внутреннего размера и размера пакета.
rust/demos/ch11-matrix-multiplication/src/main.rs#learner-matrix-multiplication-output let example = tiny_matrix_multiplication_example()?;
let zero_inner_left = Tensor::from_vec(vec![2, 0], vec![])?;
let zero_inner_right = Tensor::from_vec(vec![0, 2], vec![])?;
let zero_inner = matmul(&zero_inner_left.view(), &zero_inner_right.view())?;
let wrong_inner = Tensor::from_vec(vec![4, 2], vec![0.0; 8])?;
let inner_error = matmul(&example.token_rows.view(), &wrong_inner.view()).unwrap_err();
let wrong_batch = Tensor::from_vec(vec![3, 3, 2], vec![0.0; 18])?;
let batch_error = matmul(&example.batched_token_rows.view(), &wrong_batch.view()).unwrap_err();
let rank_one = Tensor::from_vec(vec![3], vec![1.0, 2.0, 3.0])?;
let rank_error =
matmul_with_transpose(&rank_one.view(), &example.weights.view(), false, false).unwrap_err(); Результаты примеров с целыми значениями можно сравнивать точно. Для скалярного произведения
с десятичными дробями вместо этого используется абсолютный допуск 1e-12, потому
что десятичные дроби в общем случае нельзя точно представить двоичными числами с
плавающей точкой. Пример со взаимным погашением также делает наблюдаемым порядок
накопления по возрастанию . В этой главе матричное умножение не делегируется
библиотеке.
Проследите одну ячейку результата, затем добавьте ось пакета
Изучите схему в четыре прохода:
- Стрелка вправо и сплошная граница выделяют выбранную строку матрицы ; стрелка вниз и пунктирная граница — выбранный столбец матрицы . На их пересечении находится ячейка с двойной рамкой.
- Просмотрите три карточки суммирования с двойной рамкой в порядке . Проверьте
произведения
4.0,0.0и12.0, затем накопленные суммы4.0,4.0,16.0. - Сопоставьте хранимую форму правого операнда
[2,3]с логической формой[3,2], затем обратите внимание: оба пакета результата используют правый пакет . - Рассмотрите пунктирные карточки ошибок. Внутренние размеры
3и4приводят к ошибке до начала скалярного цикла; при совпадающих матричных осях пакетные размеры2и3приводят к ошибке пакета.
Проследите сумму произведений строки и столбца и повторное использование весов
Сравните три матрицы, вычислите выбранную ячейку по внутреннему индексу и разберите транспонирование, повторное использование весов и ошибки форм.
- Форма левой матрицы активаций
[2, 3]- Форма матрицы весов проекции
[3, 2]- Форма произведения
[2, 2]
Выберите одну строку левой матрицы и один столбец правой
Сплошная стрелка и левая граница выделяют одну строку матрицы активаций. Стрелка вниз и пунктирная граница выделяют один столбец матрицы весов проекции. На их пересечении находится выбранная ячейка результата с двойной рамкой.
| Строка | Столбец 0 | Столбец 1 | Столбец 2 |
|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 |
| Выбранная строка левой матрицы: 1 | 4.0 | 5.0 | 6.0 |
| Строка | Выбранный столбец правой матрицы: Столбец 0 | Столбец 1 |
|---|---|---|
| 0 | 1.0 | 2.0 |
| 1 | 0.0 | 1.0 |
| 2 | 2.0 | 0.0 |
| Строка | Столбец 0 | Столбец 1 |
|---|---|---|
| 0 | 7.0 | 4.0 |
| 1 | Выбранная ячейка результата: 16.0 | 13.0 |
Накопите три произведения по внутреннему индексу
Каждая карточка с двойной рамкой соответствует одному шагу по внутреннему индексу; выбранное значение результата получается только после всех трёх произведений.
-
Произведение, добавляемое к сумме по внутренней оси: Слагаемое
- Произведение
- 4.0
- Накопленная сумма
- 4.0
-
Произведение, добавляемое к сумме по внутренней оси: Слагаемое
- Произведение
- 0.0
- Накопленная сумма
- 4.0
-
Произведение, добавляемое к сумме по внутренней оси: Слагаемое
- Произведение
- 12.0
- Накопленная сумма
- 16.0
Транспонируйте логически и повторно используйте веса без копирования
Флаг транспонирования меняет логический порядок двух последних осей без материализации, а изогнутая стрелка показывает, что единственный правый пакет используется для обоих пакетов результата.
- Хранимая форма
[2, 3]- Логическая форма
[3, 2]- Форма результата
[2, 2]- Значения результата
[7.0, 4.0, 16.0, 13.0]
-
Общий пакет весов, повторно используемый благодаря согласованию форм: Пакет результата 0
- Левый пакет
- 0
- Правый пакет
- 0
- Значения
[7.0, 4.0, 16.0, 13.0]
-
Общий пакет весов, повторно используемый благодаря согласованию форм: Пакет результата 1
- Левый пакет
- 1
- Правый пакет
- 0
- Значения
[4.0, 1.0, 2.0, 5.0]
Отклоните несовпадающие внутренние и пакетные размеры
Пунктирные карточки показывают точные размеры, которые отклоняются до выделения памяти или первого скалярного умножения.
- Отклонённое матричное умножение:
inner-dimension-mismatchРазмеры двух внутренних осей должны совпадать. - Отклонённое матричное умножение:
incompatible-batchПакетные размеры должны совпадать либо один из них должен быть единичным. Ось пакета 0:
Выбранная ячейка наглядно подтверждает весь расчёт: одна строка и один столбец дают ровно три произведения, а последняя накопленная сумма равна . Затем карточки пакетов показывают, что начальная ось пакета позволяет повторно использовать матрицу весов. Два примера с ошибками показывают различие между равенством внутренних размеров и согласованием форм по начальным осям пакета.
Предскажите произведения до запуска Rust
Запишите эффективные формы до вычисления значений.
- Предскажите форму результата и все четыре значения для матрицы из примера с формой
[2,3], умноженной на с формой[3,2]. - Повторно вычислите в порядке возрастания . Какая ось исчезает и почему?
- Сохраните с формой
[2,3]. Какой флаг восстанавливает логическую форму[3,2]и какой результат остаётся неизменным? - Для левой формы
[2,2,3]и правой формы[1,3,2]сопоставьте пакет результата1с координатами пакетов обоих входов и предскажите четыре его значения. - Предскажите произведение форм
[2,0]и[0,2]. Почему результат не является ошибкой? - Назовите первую ошибку для одномерного левого входа, для внутренних размеров
3и4, а затем для совместимых матричных осей с начальными пакетными размерами2и3. - Для непрерывной с шагами
[3,1]и с шагами[2,1]запишите оба плана базовых смещений ячеек и три пары смещений в исходных хранилищах, используемые для . Почему тот же алгоритм может работать со срезом, который не является непрерывным в памяти, без предварительного копирования? - Проверьте понимание: можно ли считать матричное умножение поэлементным умножением с одной общей суммой? Укажите разные индексы строки, столбца и внутренней оси.
Проверьте восемь предсказаний о матричном умножении
- Совпадающая внутренняя ось не входит в форму результата. Из двух внешних
размеров образуется форма
[2,2], а значения в построчном порядке равны[7,4,16,13]. - даёт накопленную сумму ; оставляет её равной ; увеличивает её до . Ось исчезает, потому что все три совпадающие позиции вносят вклад в одну ячейку.
- Установите
transpose_right=true. Хранимая форма[2,3]интерпретируется как логическая[3,2], а результат сохраняет форму[2,2]и значения[7,4,16,13]. - Пакет результата
1сопоставляется с левым пакетом1и правым пакетом0. Его произведение имеет форму[2,2]и значения[4,1,2,5]. - Форма
[2,2]содержит четыре значения положительного нуля. Каждая ячейка существует, но при в накопленную сумму, инициализированную как0.0, не поступает ни одного слагаемого. - Ошибки: сначала
LeftRankTooSmall { rank: 1 }, затемInnerDimensionMismatch { left: 3, right: 4 }, затемIncompatibleBatch { axis: 0, left_dimension: 2, right_dimension: 3 }. - План базовых смещений левого операнда равен
[3,0], а правого —[0,1]. Для начальные смещения равны3и0; шаги1и2по общей внутренней оси дают пары(3,0),(4,2)и(5,4). Срез передаёт тому же планировщику собственное проверенное базовое смещение и собственные шаги, поэтому память выделяется только для нового непрерывного результата. - Нет. Строка результата выбирает одну строку левой матрицы, столбец результата — один столбец правой, а внутренний индекс сопоставляет значения в общих позициях, перемножает их и складывает произведения. Другие пары строк и столбцов образуют другие ячейки, а не попадают в одну общую сумму.
Подготовьте обучаемые проекции и внимание
Теперь тензорное ядро может умножать двумерные и пакетные представления с произвольными шагами, требовать равенства внутренних размеров, согласовывать формы только по начальным осям пакета и интерпретировать необязательное транспонирование последних осей без копирования. Формы проверяются, а память выделяется с обработкой ошибок до начала скалярных вычислений; успешный результат возвращается как тензор с собственным непрерывным хранилищем.
Общедоступный поиск по координате по-прежнему проверяет каждую координату, переданную вызывающим кодом. Внутри уже проверенного матричного умножения два курсора базовых смещений ячеек и два шага по общей внутренней оси указывают, какой пакет, какую строку, какой столбец и какую позицию нужно выбрать, не создавая координаты заново для каждого скалярного произведения.
Именно это численное суммирование произведений по общей оси лежит в основе последующих обучаемых проекций, оценок внимания , сумм значений с весами внимания, сетей прямого распространения и их градиентов. Само по себе оно ещё не является обучаемым слоем или механизмом внимания: здесь нет параметров, смещения, маскирования, масштабирования, нормализации или графа обучения.
В главе 12 произвольные результаты матричного умножения превращаются в устойчивые вероятности и логарифмы вероятностей. Перед возведением в экспоненту в той главе вычитается максимум, чтобы большие логиты не приводили к переполнению, а выбранная ось нормализации остаётся явной как для распределения по словарю, так и для распределения внимания.