18 · Версия материала 7
Сопоставьте ID токенов с обучаемыми векторами
Создайте обучаемую таблицу токенов, один раз проверьте ID токенов в публичной точке входа, передайте преобразованные селекторы во владение внутреннему проверенному плану выбора строк по индексам и сложите градиенты повторяющихся токенов.
Предскажите три выбранные строки и один общий градиент
Возьмём обучаемую таблицу из четырёх строк словаря, по признака в каждой:
До запуска прочитайте ID слева направо. Первая и третья позиции выбирают строку , а средняя — строку . Значит, ожидаемый результат равен и имеет форму . Повторение копирует значение в две позиции, но не создаёт вторую обучаемую строку.
One-hot-обозначение наглядно показывает этот выбор. Например, ID соответствует . При умножении такого индикатора на остаётся . Программа явно создаёт весь индикатор, включая нулевые значения, только в исполняемом сравнении с one-hot-вектором:
rust/demos/ch18-token-embeddings/src/lib.rs#one-hot-baseline /// Materializes the tiny historical algebraic baseline for comparison only.
pub fn explicit_one_hot_product(table: &Tensor, token_ids: &[u32]) -> (Vec<Vec<u8>>, Vec<f64>) {
assert_eq!(table.shape().len(), 2);
let vocabulary_size = table.shape()[0];
let width = table.shape()[1];
let mut indicators = Vec::with_capacity(token_ids.len());
let mut output = Vec::with_capacity(token_ids.len() * width);
for &token_id in token_ids {
let selected = usize::try_from(token_id).expect("u32 token ID must fit usize");
assert!(selected < vocabulary_size);
let mut one_hot = vec![0_u8; vocabulary_size];
one_hot[selected] = 1;
for feature in 0..width {
let value = one_hot
.iter()
.enumerate()
.map(|(row, &active)| f64::from(active) * table.as_slice()[row * width + feature])
.sum();
output.push(value);
}
indicators.push(one_hot);
}
(indicators, output)
} Сам слой напрямую выбирает строки и проверяет, что оба способа дают в точности одинаковые значения:
rust/demos/ch18-token-embeddings/src/lib.rs#known-token-lookup let embedding = known_embedding();
let output = embedding.forward(&TOKEN_IDS, &TOKEN_SHAPE)?;
let (_, one_hot_output) = explicit_one_hot_product(&known_table(), &TOKEN_IDS);
let one_hot_matches = output.value().as_slice() == one_hot_output; Выбирайте строки в прямом проходе и накапливайте вклады в обратном
Полное правило выбора строки и обратного прохода имеет вид:
В прямом проходе в каждую выходную позицию копируется одна выбранная строка. Обозначим скалярную функцию потерь через . Черта сверху — краткое обозначение сопряжённой величины обратного режима: — градиент, приходящий в выходную позицию, а — градиент строки таблицы. При обратном проходе градиенты передаются по тем же связям от выходных позиций к выбранным строкам. Если несколько позиций выбрали одну строку, поступившие в эти позиции векторы градиента складываются по признакам в общей строке параметров.
При заданной начальной сопряжённой величине строка получает , а строка — . Неиспользованные строки и получают нули. Градиента по ID токенов нет: это дискретные селекторы, а не операнды ленты.
Не смешивайте оси словаря и признаков
- — обучаемая таблица токенов формы .
- — размер словаря и, следовательно, число строк в .
- — ширина эмбеддинга и, следовательно, число признаков в каждой строке.
- — целочисленный ID токена в элементе пакета и позиции последовательности .
- обозначает один элемент пакета, а — одну позицию его последовательности.
- означает все координаты на последней оси признаков.
- — вектор ширины , выбранный в позиции .
- — вектор входящей сопряжённой величины в этой выходной позиции.
- — градиент строки таблицы после накопления вкладов всех совпадающих позиций.
- — индекс одной строки словаря.
- Сумма перебирает все , для которых равно .
Числовое расстояние между ID не является смысловым расстоянием. ID и соседствуют только в нумерации токенизатора; станут ли соответствующие обучаемые векторы похожими, зависит от обучения.
От разреженного one-hot-кода к векторам на входе Transformer
Разреженное one-hot-представление слова отводит одну координату каждому элементу словаря, но не выражает степень сходства между словами. Кроме того, явно создавать и обрабатывать такой вектор размером со словарь расточительно, когда нужна лишь одна строка.
Bengio et al., A Neural Probabilistic Language Model: Bengio и соавторы задают отображение индекса словарного слова в набор распределённых признаков с помощью обучаемой матрицы: каждому элементу словаря соответствует строка, а каждому обучаемому признаку — столбец. Одна и та же матрица используется для всех позиций контекста и обучается вместе с моделью предсказания следующего слова.
Bengio и соавторы обучают общую плотную таблицу признаков слов вместе с нейросетевой моделью следующего слова. Transformer использует обучаемые эмбеддинги подсловных токенов: к ним добавляется позиционная информация, после чего результат поступает в стек слоёв внимания и сетей прямого распространения.
Vaswani et al., Attention Is All You Need: Vaswani и соавторы используют для токенов BPE или WordPiece обучаемые эмбеддинги, ширина которых совпадает с шириной модели, и перед стеком Transformer добавляют к ним позиционное кодирование. Масштабирование эмбеддингов в прямом проходе не относится к инициализации параметров.
ID токенов поступают на числовой вход декодера: по ним выбираются строки одной обучаемой таблицы «словарь на признаки». Все вхождения повторяющегося ID используют одну и ту же строку параметров, поэтому их градиентные вклады при обратном проходе складываются. Позиционная информация, масштабирование эмбеддингов в прямом проходе, внимание и совместное использование весов с выходной проекцией рассматриваются позже.
One-hot-векторы явно указывают выбранный токен, но имеют размер словаря и почти целиком состоят из нулей. Плотные обучаемые признаки позволяют нейросетевым языковым моделям использовать статистические сведения об одних словах при оценке других. В Transformer обучаемые эмбеддинги служат числовыми представлениями токенов на входе последующих слоёв обработки последовательности. Алгебраическое тождество для one-hot-вектора объясняет прямой выбор строки, а общая обучаемая строка — сложение градиентов повторяющихся токенов.
Главный переход здесь — от разреженного кодирования идентификаторов к обучаемым векторам, общим для всех вхождений одного токена. Конкретная реализация может выбирать другие целочисленные типы, схемы хранения, инициализаторы и соглашения об ошибках: уравнения выбора строки и обратного режима от этого не меняются. Произведение с one-hot-вектором служит алгебраическим пояснением, а не утверждением, что в какой-либо из работ такие разреженные векторы хранятся явно.
Проверьте ID токенов один раз и повторно используйте правило выбора строк
Слой сообщает об ошибках таблицы, формы токенов, числа ID и выхода за границы таблицы, а также об ошибках выделения памяти при преобразовании индексов и об ошибках, переданных операциями автодифференцирования. Если возникает ошибка, метод не возвращает частично сформированный результат:
rust/crates/llm-from-scratch/src/nn/embedding.rs#embedding-errors /// A rejected embedding table, token layout, selector, or delegated operation.
#[derive(Clone, Debug, PartialEq)]
pub enum EmbeddingError {
Initialization(InitializationError),
Autodiff(TensorAutodiffError),
TableRank {
rank: usize,
},
EmptyVocabulary,
ZeroEmbeddingWidth,
TokenShape(TensorError),
TokenCountMismatch {
expected: usize,
actual: usize,
},
TokenIdOutOfBounds {
position: usize,
id: u32,
vocabulary_size: usize,
},
IndexAllocationFailed {
elements: usize,
},
}
impl fmt::Display for EmbeddingError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Initialization(error) => error.fmt(formatter),
Self::Autodiff(error) => error.fmt(formatter),
Self::TableRank { rank } => {
write!(
formatter,
"embedding table must have rank two, got rank {rank}"
)
}
Self::EmptyVocabulary => {
formatter.write_str("embedding vocabulary must contain at least one row")
}
Self::ZeroEmbeddingWidth => {
formatter.write_str("embedding width must be greater than zero")
}
Self::TokenShape(error) => write!(formatter, "invalid token-ID shape: {error}"),
Self::TokenCountMismatch { expected, actual } => write!(
formatter,
"token-ID shape needs {expected} IDs, but received {actual}"
),
Self::TokenIdOutOfBounds {
position,
id,
vocabulary_size,
} => write!(
formatter,
"token ID {id} at flat position {position} is out of bounds for vocabulary size {vocabulary_size}"
),
Self::IndexAllocationFailed { elements } => write!(
formatter,
"could not reserve {elements} converted embedding indices"
),
}
}
}
impl Error for EmbeddingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Initialization(error) => Some(error),
Self::Autodiff(error) => Some(error),
Self::TokenShape(error) => Some(error),
_ => None,
}
}
}
impl From<InitializationError> for EmbeddingError {
fn from(error: InitializationError) -> Self {
Self::Initialization(error)
}
}
impl From<TensorAutodiffError> for EmbeddingError {
fn from(error: TensorAutodiffError) -> Self {
Self::Autodiff(error)
}
} Из внешнего кода получить Embedding можно тремя способами: вызвать new,
вызвать from_parameter или клонировать уже проверенный слой. Оба конструктора
гарантируют, что таблица имеет ранг два и ненулевые размеры, клон сохраняет эту
таблицу, а закрытые поля не позволяют заменить её извне. При создании слоя
размер словаря и ширина признаков сохраняются отдельно:
rust/crates/llm-from-scratch/src/nn/embedding.rs#embedding-layer /// One named trainable `[vocabulary_size, embedding_width]` token table.
#[derive(Debug)]
pub struct Embedding {
table: NamedParameter,
vocabulary_size: usize,
embedding_width: usize,
}
impl Clone for Embedding {
/// Clones the layer handle while preserving the table's tape-leaf identity.
fn clone(&self) -> Self {
Self {
table: self.table.clone(),
vocabulary_size: self.vocabulary_size,
embedding_width: self.embedding_width,
}
}
}
impl Embedding {
/// Initializes one table transactionally with Chapter 17's shape-based policy.
///
/// The complete parameter name is used as supplied. Validation checks the
/// name before the vocabulary and width, and every error preserves `rng`.
pub fn new(
parameter_name: impl Into<String>,
vocabulary_size: usize,
embedding_width: usize,
rng: &mut SplitMix64,
) -> Result<Self, EmbeddingError> {
let mut trial = rng.clone();
let table = NamedParameter::xavier_uniform(
parameter_name,
vocabulary_size,
embedding_width,
&mut trial,
)
.map_err(|error| match error {
InitializationError::ZeroFanIn => EmbeddingError::EmptyVocabulary,
InitializationError::ZeroFanOut => EmbeddingError::ZeroEmbeddingWidth,
other => EmbeddingError::Initialization(other),
})?;
let embedding = Self::from_parameter(table)?;
*rng = trial;
Ok(embedding)
}
/// Gives embedding semantics to an existing named trainable rank-two table.
pub fn from_parameter(table: NamedParameter) -> Result<Self, EmbeddingError> {
let shape = table.tensor().shape();
if shape.len() != 2 {
return Err(EmbeddingError::TableRank { rank: shape.len() });
}
if shape[0] == 0 {
return Err(EmbeddingError::EmptyVocabulary);
}
if shape[1] == 0 {
return Err(EmbeddingError::ZeroEmbeddingWidth);
}
Ok(Self {
table,
vocabulary_size: shape[0],
embedding_width: shape[1],
})
}
} Embedding::forward — публичная точка входа для ID токенов, поступающих как
значения типа u32. К моменту вызова форма таблицы уже проверена. Метод
выполняет остальные проверки в неизменном порядке:
- вычисляет число позиций, заданное
token_shape, и сообщает об ошибке формы или переполнении при вычислении; - требует, чтобы длина
token_idsв точности совпадала с этим числом; и - просматривает ID в плоском порядке и сообщает о первом значении
u32, которое нельзя представить какusizeили которое не попадает в диапазон строк .
Только после успешной проверки всех ID метод резервирует Vec<usize> и
преобразует селекторы. Затем лента проверяет, что операнд с таблицей ещё
доступен. Если он доступен, метод создаёт значение закрытого типа
RowGatherPlan из главы 16 и передаёт ему вектор селекторов во владение; этот
тип доступен только внутри крейта. Полученный
проверенный план выбора строк по индексам владеет преобразованными селекторами и
их логической формой, а форму выхода вычисляет с проверкой переполнения.
Внутренний конструктор не проверяет повторно ранг таблицы, число селекторов и их
границы. Ранг таблицы гарантируют конструкторы Embedding; при клонировании этот
инвариант сохраняется. Текущий вызов forward уже проверил логическую форму,
число и границы селекторов:
rust/crates/llm-from-scratch/src/nn/embedding.rs#embedding-forward-boundary impl Embedding {
/// Selects one table row per `u32` token ID and appends the feature axis.
///
/// Token IDs remain integer selectors rather than differentiable operands.
/// After this boundary validates and converts them, the shared gather kernel
/// consumes the sealed facts without scanning the selectors again.
pub fn forward(
&self,
token_ids: &[u32],
token_shape: &[usize],
) -> Result<TensorValue, EmbeddingError> {
let (_, expected) =
checked_row_major_layout(token_shape).map_err(EmbeddingError::TokenShape)?;
if token_ids.len() != expected {
return Err(EmbeddingError::TokenCountMismatch {
expected,
actual: token_ids.len(),
});
}
for (position, &id) in token_ids.iter().enumerate() {
let valid = usize::try_from(id)
.ok()
.is_some_and(|index| index < self.vocabulary_size);
if !valid {
return Err(EmbeddingError::TokenIdOutOfBounds {
position,
id,
vocabulary_size: self.vocabulary_size,
});
}
}
let mut indices = Vec::new();
indices
.try_reserve_exact(expected)
.map_err(|_| EmbeddingError::IndexAllocationFailed { elements: expected })?;
for &id in token_ids {
indices.push(usize::try_from(id).expect("validated u32 token ID must fit usize"));
}
self.table
.tensor()
.gather_rows_with_plan(move |table| {
RowGatherPlan::from_validated_indices(table, indices, token_shape.to_vec())
})
.map_err(EmbeddingError::Autodiff)
}
} Повторно использовать результаты этих проверок без новой проверки может только
внутренний код, получивший проверенный план выбора строк по индексам. Публичный метод
TensorValue::gather_rows по-прежнему принимает произвольные входные данные,
поэтому сам проверяет ранг таблицы, форму и число селекторов, а также первый
индекс за пределами таблицы. Наличие плана ещё не означает, что память уже
выделена: создание буфера выходных значений всё равно может завершиться ошибкой.
После успешного выделения памяти общее ядро копирует строки и сохраняет те же
данные для обратного прохода. Поэтому слой избегает второго просмотра
селекторов, но не дублирует ни выбор строк, ни VJP. Изменились только владение
проверенными данными и их повторное использование. Значения и формы выхода,
порядок ошибок, формула выбора строк, данные, сохранённые для VJP, и накопление
градиентов повторяющихся ID остались прежними.
Точная неоднородная начальная сопряжённая величина делает накопление повторяющихся вкладов наблюдаемым:
rust/demos/ch18-token-embeddings/src/lib.rs#repeated-token-gradient let upstream = Tensor::from_vec(vec![1, 3, 2], UPSTREAM_VALUES.to_vec())?;
output.backward_with_seed(&upstream.view(), GraphRetention::Retain)?;
let table_gradient = embedding
.table()
.tensor()
.gradient_snapshot()
.expect("trainable table stores its gradient"); При создании слоя используется матричный инициализатор из главы 17, которому и передаются как два размера таблицы. Это явное соглашение об инициализации: оно не следует ни из операции выбора строки, ни из работы о Transformer. Другая реализация может выбрать иной инициализатор, не меняя уравнений прямого и обратного прохода. Повторный запрос с тем же начальным значением генератора воспроизводит значения, но отдельно созданные слои остаются разными листьями. Клонирование слоя намеренно сохраняет идентичность параметра:
rust/demos/ch18-token-embeddings/src/lib.rs#initialized-token-embedding let mut first_rng = SplitMix64::from_seed(18);
let mut second_rng = SplitMix64::from_seed(18);
let initialized = Embedding::new("token_embedding.weight", 4, 2, &mut first_rng)?;
let reproduced = Embedding::new("token_embedding.weight", 4, 2, &mut second_rng)?;
let initialized_reproducible =
*initialized.table().tensor().value() == *reproduced.table().tensor().value();
let clone_same_node = initialized
.table()
.tensor()
.is_same_node(initialized.clone().table().tensor()); Исполняемый пример также проверяет вход формы , не содержащий ни одного ID, и первый ID за пределами таблицы:
rust/demos/ch18-token-embeddings/src/lib.rs#embedding-edge-cases let empty_output = embedding.forward(&[], &[0])?.value_snapshot();
let bounds_rejected = matches!(
embedding.forward(&[4], &[1]),
Err(EmbeddingError::TokenIdOutOfBounds {
position: 0,
id: 4,
vocabulary_size: 4,
})
); Вывод исполняемого примера содержит выбранные строки и накопленный градиент таблицы. Скалярный ID формы даёт вектор формы ; к любой пустой ведущей форме добавляется ширина эмбеддинга, но значений не появляется. Например, форма превращается в . Ошибки формы и количества сообщаются раньше первого недопустимого селектора. Проверка всех координат конечными разностями с шагом совпадает с аналитическим градиентом таблицы в пределах абсолютного допуска .
rust/demos/ch18-token-embeddings/src/main.rs#learner-token-embeddings-output let report = learner_report()?;
println!(
"table: {} shape={}",
report.table_name,
shape(&report.table_shape)
);
println!(
"ids: shape={} values={}",
shape(&report.token_shape),
report
.token_ids
.iter()
.map(u32::to_string)
.collect::<Vec<_>>()
.join(",")
);
println!(
"output: shape={} values={}",
shape(report.output.shape()),
fixed_list(report.output.as_slice())
);
println!(
"one-hot multiplication equals lookup: {}",
report.one_hot_matches
);
println!(
"upstream: shape={} values={}",
shape(report.upstream.shape()),
fixed_list(report.upstream.as_slice())
);
println!(
"table gradient: shape={} values={}",
shape(report.table_gradient.shape()),
fixed_list(report.table_gradient.as_slice())
); Проследите каждый выбор обратно до общей строки
В прямой части каждая позиция сопоставляется со своим ID токена, one-hot-тождеством, выбранной строкой таблицы и выходным вектором. В обратной части каждый вектор входящего градиента сопоставляется со строкой, которая его получает. Строку выбирают в двух позициях, поэтому различие видно явно: в прямом проходе её значение копируется в два выхода, а в обратном оба градиентных вклада складываются в одной строке параметров.
rust/demos/ch18-token-embeddings/src/diagram_trace.rs#token-embeddings-trace pub fn render_trace() -> Result<String, Box<dyn Error>> {
let embedding = known_embedding();
let output = embedding.forward(&TOKEN_IDS, &TOKEN_SHAPE)?;
let upstream = Tensor::from_vec(vec![1, 3, 2], UPSTREAM_VALUES.to_vec())?;
output.backward_with_seed(&upstream.view(), GraphRetention::Retain)?;
let gradient = embedding
.table()
.tensor()
.gradient()
.expect("trainable table stores its gradient");
let (one_hot_rows, baseline) = explicit_one_hot_product(&known_table(), &TOKEN_IDS);
assert_eq!(baseline, output.value().as_slice());
let mut repeated_ids = TOKEN_IDS
.iter()
.copied()
.filter(|candidate| TOKEN_IDS.iter().filter(|id| *id == candidate).count() > 1)
.collect::<Vec<_>>();
repeated_ids.sort_unstable();
repeated_ids.dedup();
assert_eq!(
repeated_ids.len(),
1,
"fixture must contain one repeated token ID"
);
let repeated_id = repeated_ids[0];
let repeated_positions = TOKEN_IDS
.iter()
.enumerate()
.filter_map(|(position, id)| (*id == repeated_id).then_some(position))
.collect::<Vec<_>>();
let mut trace = String::new();
writeln!(trace, "TRACE token-embeddings-v1 BEGIN")?;
writeln!(
trace,
"FIXTURE name=known-table-repeated-id parameter={} vocabulary={} width={} table-shape={} id-shape={} output-shape={} upstream-shape={} gradient-shape={} accumulation=scatter-add",
embedding.table().name(),
embedding.vocabulary_size(),
embedding.embedding_width(),
shape(embedding.table().tensor().shape().as_slice()),
shape(&TOKEN_SHAPE),
shape(output.shape().as_slice()),
shape(upstream.shape()),
shape(gradient.shape()),
)?;
writeln!(
trace,
"IDS values={} repeated-id={repeated_id} repeated-flat-positions={}",
token_list(&TOKEN_IDS),
integer_list(&repeated_positions),
)?;
for row in 0..TABLE_SHAPE[0] {
let uses = TOKEN_IDS
.iter()
.filter(|&&id| usize::try_from(id).ok() == Some(row))
.count();
let state = match uses {
0 => "unused",
1 => "selected-once",
_ => "selected-repeated",
};
let start = row * embedding.embedding_width();
writeln!(
trace,
"TABLE row={row} uses={uses} state={state} values={}",
fixed_list(
&embedding.table().tensor().value().as_slice()
[start..start + embedding.embedding_width()]
)
)?;
}
for (flat, (&id, one_hot)) in TOKEN_IDS.iter().zip(&one_hot_rows).enumerate() {
let start = flat * embedding.embedding_width();
let coordinate = row_major_coordinate(flat, &TOKEN_SHAPE);
let uses = TOKEN_IDS
.iter()
.filter(|&&candidate| candidate == id)
.count();
writeln!(
trace,
"LOOKUP flat={flat} coordinate={} id={id} sharing={} one-hot={} selected-row={id} output={} upstream={}",
integer_list(&coordinate),
if uses > 1 {
"repeated-row"
} else {
"single-row"
},
integer_list(
&one_hot
.iter()
.map(|&value| usize::from(value))
.collect::<Vec<_>>()
),
fixed_list(&output.value().as_slice()[start..start + embedding.embedding_width()]),
fixed_list(&UPSTREAM_VALUES[start..start + embedding.embedding_width()])
)?;
}
for row in 0..TABLE_SHAPE[0] {
let positions = TOKEN_IDS
.iter()
.enumerate()
.filter_map(|(position, &id)| {
(usize::try_from(id).ok() == Some(row)).then_some(position)
})
.collect::<Vec<_>>();
let positions_text = if positions.is_empty() {
"none".to_owned()
} else {
integer_list(&positions)
};
let contributions_text = if positions.is_empty() {
"none".to_owned()
} else {
positions
.iter()
.map(|position| {
let start = position * TABLE_SHAPE[1];
fixed_list(&UPSTREAM_VALUES[start..start + TABLE_SHAPE[1]])
})
.collect::<Vec<_>>()
.join("|")
};
let start = row * embedding.embedding_width();
let rule = match positions.len() {
0 => "unused-zero",
1 => "single-copy",
_ => "repeated-sum",
};
writeln!(
trace,
"ROW-GRADIENT row={row} flat-positions={positions_text} contributions={contributions_text} rule={rule} accumulated={}",
fixed_list(&gradient.as_slice()[start..start + embedding.embedding_width()])
)?;
}
writeln!(trace, "TRACE token-embeddings-v1 END")?;
Ok(trace)
} Читайте прямую и обратную части вместе. Повторный выбор обозначен текстом, ромбом и двойной линией, а не только цветом, поэтому общая строка заметна при любой цветовой схеме.
Сначала предскажите, затем сверьтесь с исполняемым примером
- Предскажите все шесть выходных значений для ID .
- Запишите one-hot-строку длины четыре для ID и умножьте её на .
- Предскажите все значения градиента таблицы для заданной начальной сопряжённой величины.
- Объясните, почему вклады складываются в градиент строки , а не образуют градиенты двух независимых параметров.
- Предскажите формы выхода для форм ID , и при .
- Укажите порядок, в котором метод сообщает об ошибках: неверная форма токенов, несовпадение числа ID и ID вне таблицы. Затем найдите первый недопустимый ID в для таблицы из четырёх строк.
- Объясните, почему после своих проверок
Embedding::forwardможет передать преобразованные селекторы внутреннему проверенному плану выбора строк по индексам, а публичная операция выбора строк обязана сама проверить исходные селекторы. Какое выделение памяти всё ещё может завершиться ошибкой после создания плана? - Предскажите, создаёт ли клонирование слоя эмбеддингов новый обучаемый листовой узел-параметр.
- Объясните, почему ID не получают градиент и почему соседние номера ID не означают смыслового сходства.
- Определите, требует ли работа Bengio и соавторов явно создавать one-hot-векторы.
- Определите, получает ли повторное вхождение токена новый параметр эмбеддинга.
Сверить ответы
- Результат равен .
- ID соответствует , поэтому умножение на даёт .
- Градиент таблицы равен .
- Оба вхождения выбрали одну и ту же строку общей таблицы параметров, поэтому и складываются в градиенте этой строки.
- Формы выхода равны , и .
- Сначала метод проверяет форму токенов, затем точное число ID и только после этого просматривает ID в плоском порядке. ID в плоской позиции — первый недопустимый селектор, потому что допустимы строки от до .
- Каждый
Embeddingсодержит таблицу ранга два, полученную через публичный конструктор или сохранённую при клонировании, аforwardуже проверил форму, число и границы ID до их преобразования. Проверенный план выбора строк по индексам владеет преобразованными селекторами и их логической формой. У публичного вызоваTensorValue::gather_rowsтаких гарантий нет, поэтому он выполняет все проверки сам. Даже после создания плана при выделении буфера выходных значений всё ещё может возникнуть ошибка. - Нет. Клон — отдельный объект слоя, но он хранит тот же именованный листовой узел-параметр таблицы; нового обучаемого листа не возникает.
- ID — дискретные селекторы вне ленты, а их нумерация не задаёт геометрии.
- Нет. One-hot-умножение здесь служит только алгебраическим пояснением.
- Нет. Повторение снова использует одну строку таблицы; последующий учёт позиции различает вхождения, не создавая отдельных параметров эмбеддинга для каждой позиции.
Используйте ширину последней оси как входную ширину обучаемой проекции
Теперь совокупная модель умеет превращать тензоры ID токенов в дифференцируемые тензоры признаков: к исходной форме добавляется последняя ось, размер которой равен ширине эмбеддинга, а одна общая именованная таблица «словарь на признаки» остаётся параметром. В главе 19 размер последней оси эмбеддинга станет входной шириной обучаемой проекции: выбор строки по индексу извлекает вектор токена, а линейный слой смешивает его координаты.
Так декодер получает числовые представления токенов, пока ещё без учёта позиции. Глава 19 сохраняет все ведущие оси пакета и последовательности и применяет обучаемую матрицу к последней оси признаков. В следующих главах RoPE сделает внимание позиционно-зависимым, поворачивая уже спроецированные запросы и ключи; повторяющиеся вхождения по-прежнему используют одну строку таблицы эмбеддингов.